代码提交

This commit is contained in:
fan 2023-07-03 13:16:08 +08:00
commit 2e1b7d5c8a
198 changed files with 41564 additions and 0 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

7
.eslintignore Normal file
View File

@ -0,0 +1,7 @@
/dist
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.js
/src-ssr

90
.eslintrc.cjs Normal file
View File

@ -0,0 +1,90 @@
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
parserOptions: {
parser: require.resolve('@typescript-eslint/parser'),
extraFileExtensions: [ '.vue' ]
},
env: {
browser: true,
es2021: true,
node: true,
'vue/setup-compiler-macros': true
},
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
// ESLint typescript rules
'plugin:@typescript-eslint/recommended',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
'prettier'
],
plugins: [
// required to apply rules which need type information
'@typescript-eslint',
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue'
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE
],
globals: {
ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly'
},
// add your custom rules here
rules: {
'prefer-promise-reject-errors': 'off',
quotes: ['warn', 'single', { avoidEscape: true }],
// this rule, if on, would require explicit return type on the `render` function
'@typescript-eslint/explicit-function-return-type': 'off',
// in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled
'@typescript-eslint/no-var-requires': 'off',
// The core 'no-unused-vars' rules (in the eslint:recommended ruleset)
// does not work with type definitions
'no-unused-vars': 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
.DS_Store
.thumbs.db
node_modules
# Quasar core related directories
.quasar
/dist
# Cordova related directories and files
/src-cordova/node_modules
/src-cordova/platforms
/src-cordova/plugins
/src-cordova/www
# Capacitor related directories and files
/src-capacitor/www
/src-capacitor/node_modules
# BEX related directories and files
/src-bex/www
/src-bex/js/core
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln

2
.gitmodule Normal file
View File

@ -0,0 +1,2 @@
[submodule "graphic-pixi"]
branch = bj-rtss

7
.gitmodules vendored Normal file
View File

@ -0,0 +1,7 @@
[submodule "bj-rtss-message"]
path = bj-rtss-message
url = https://git.code.tencent.com/beijing-rtss-test/bj-rtss-message.git
[submodule "graphic-pixi"]
path = graphic-pixi
url = https://git.code.tencent.com/jl-framework/graphic-pixi.git
branch = bj-rtss

3
.npmrc Normal file
View File

@ -0,0 +1,3 @@
# pnpm-related options
shamefully-hoist=true
strict-peer-dependencies=false

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"semi": true
}

15
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"vue.volar",
"wayou.vscode-todo-highlight"
],
"unwantedRecommendations": [
"octref.vetur",
"hookyqr.beautify",
"dbaeumer.jshint",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

16
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": [
"source.fixAll.eslint"
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue"
],
"typescript.tsdk": "node_modules/typescript/lib"
}

62
README.md Normal file
View File

@ -0,0 +1,62 @@
# BjRtssClient (bj-rtss-client)
beijing rtss 测试系统
## 项目 clone 说明
此项目包含子模块,拉取方式:
- 1.在克隆主项目的时候带上参数 --recurse-submodules
- 2.或者 clone 完成后在项目目录中执行git submodule init && git submodule update
## 安装依赖
```bash
yarn
# or
npm install
```
## 编译 proto 文件
```bash
yarn run proto
# or
npm run proto
```
### Start the app in development mode (hot-code reloading, error reporting, etc.)
```bash
quasar dev
# or
yarn run dev
# or
npm run dev
```
### Lint the files
```bash
yarn lint
# or
npm run lint
```
### Format the files
```bash
yarn format
# or
npm run format
```
### Build the app for production
```bash
quasar build
```
### Customize the configuration
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).

1
graphic-pixi Submodule

@ -0,0 +1 @@
Subproject commit 7e4eaed0cf06d68c75cb51c30329eff5fe4d1e3f

21
index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title><%= productName %></title>
<meta charset="utf-8">
<meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
</head>
<body>
<!-- quasar:entry-point -->
</body>
</html>

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "bj-rtss-client",
"version": "0.0.1",
"description": "beijing rtss 测试系统",
"productName": "BJRTSSCLIENT",
"author": "walker <shengxuqiang@joylink.club>",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev",
"build": "quasar build",
"protoc": "node scripts/proto.cjs",
"sync": "node scripts/sync.cjs"
},
"dependencies": {
"@pixi/filter-outline": "^5.2.0",
"@pixi/graphics-extras": "^7.2.4",
"@quasar/extras": "^1.0.0",
"@stomp/stompjs": "^7.0.0",
"axios": "^1.2.1",
"google-protobuf": "^3.21.2",
"js-base64": "^3.7.5",
"pinia": "^2.0.11",
"pixi-viewport": "^5.0.1",
"pixi.js": "^7.2.4",
"quasar": "^2.6.0",
"vue": "^3.0.0",
"vue-router": "^4.0.0"
},
"devDependencies": {
"@quasar/app-vite": "^1.0.0",
"@types/google-protobuf": "^3.15.6",
"@types/node": "^12.20.21",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"autoprefixer": "^10.4.2",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.1.0",
"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": {
"node": "^18 || ^16 || ^14.19",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
}

27
postcss.config.cjs Normal file
View File

@ -0,0 +1,27 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// https://github.com/postcss/autoprefixer
require('autoprefixer')({
overrideBrowserslist: [
'last 4 Chrome versions',
'last 4 Firefox versions',
'last 4 Edge versions',
'last 4 Safari versions',
'last 4 Android versions',
'last 4 ChromeAndroid versions',
'last 4 FirefoxAndroid versions',
'last 4 iOS versions'
]
})
// https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then
// 1. yarn/npm install postcss-rtlcss
// 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line:
// require('postcss-rtlcss')
]
}

21
public/drawIcon.svg Normal file
View File

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="icon-curve" viewBox="0 0 1280 1024">
<path
d="M1280 614.4c0-32-32-64-64-64-38.4 0-64 32-64 64 0 44.8-12.8 172.8-70.4 236.8-32 32-70.4 44.8-121.6 44.8-89.6 0-153.6-243.2-204.8-416-64-249.6-128-480-294.4-480C224 0 115.2 364.8 44.8 601.6c-12.8 51.2-25.6 96-38.4 121.6-12.8 32 6.4 70.4 38.4 83.2 32 12.8 70.4-6.4 83.2-38.4 12.8-32 25.6-76.8 44.8-134.4C217.6 460.8 320 128 460.8 128 531.2 128 595.2 358.4 640 512c70.4 249.6 140.8 512 326.4 512 83.2 0 153.6-25.6 204.8-83.2 115.2-121.6 108.8-320 108.8-326.4z"></path>
</symbol>
<symbol id="icon-fans" viewBox="0 0 1024 1024">
<path d="M947.303 517.937c-16.16-18.18-39.391-29.289-63.629-29.289h-240.377c-6.061-30.299-21.21-56.56-44.441-75.748l120.189-208.058c12.119-21.21 14.141-46.46 6.061-69.69s-25.249-40.398-47.47-49.49c-53.529-21.21-109.077-31.31-166.649-31.31s-113.118 10.1-166.649 31.31c-22.22 9.090-39.391 27.27-47.47 49.49-8.079 23.23-6.061 48.481 6.061 69.69l120.189 208.058c-22.22 19.188-38.379 46.46-44.441 75.748h-239.367c-24.238 0-47.47 11.109-63.629 29.289-16.16 18.18-22.22 42.42-19.188 66.659 9.090 56.56 27.27 110.087 56.56 159.578 28.278 49.49 65.65 92.917 110.087 128.269 15.149 12.119 33.329 18.18 51.51 18.18 5.050 0 10.1 0 15.149-1.009 24.238-4.041 44.441-19.188 56.56-40.398l120.189-208.058c6.061 2.020 12.119 4.041 19.188 5.050v289.867c0 14.141 11.109 25.249 25.249 25.249s25.249-11.109 25.249-25.249v-289.867c6.061-1.009 13.13-3.028 19.188-5.050l120.189 208.058c12.119 21.21 33.329 35.349 56.56 40.398 5.050 1.009 10.1 1.009 15.149 1.009 18.18 0 36.357-6.061 51.51-18.18 44.441-35.349 81.81-78.778 110.087-128.269 28.278-49.49 47.47-104.027 56.56-159.578 5.050-23.23-2.020-47.47-18.18-66.659zM303.942 823.961c-5.050 8.079-13.13 14.141-22.22 16.16s-18.18-1.009-26.259-6.061c-39.391-31.31-72.72-70.7-97.969-114.127-25.249-44.441-42.42-91.908-50.499-142.407-1.009-9.090 1.009-18.18 7.069-25.249s15.149-11.109 25.249-11.109h241.387c6.061 30.299 21.21 56.56 44.441 75.748l-121.197 207.047zM347.373 179.591c-5.050-8.079-6.061-18.18-3.028-27.27 3.028-9.090 10.1-16.16 18.18-19.188 47.47-18.18 96.958-28.278 148.468-28.278s100.998 9.090 148.468 28.278c9.090 3.028 15.149 10.1 18.18 19.188 3.028 9.090 2.020 19.188-3.028 27.27l-118.167 208.058c-14.141-5.050-28.278-7.069-44.441-7.069s-30.299 3.028-44.441 7.069l-120.189-208.058zM512 598.735c-46.46 0-83.829-37.37-83.829-83.829s37.37-83.829 83.829-83.829 83.829 37.37 83.829 83.829-37.37 83.829-83.829 83.829zM917.006 576.516c-8.079 50.499-24.238 97.969-50.499 142.407-25.249 44.441-58.579 82.818-97.969 114.127-7.069 6.061-16.16 8.079-26.259 6.061-9.090-2.020-18.18-8.079-22.22-16.16l-120.189-207.047c22.22-19.188 38.379-46.46 44.441-75.748h240.377c10.1 0 19.188 4.041 25.249 11.109 5.050 7.069 8.079 16.16 7.069 25.249z" p-id="12530"></path>
</symbol>
<symbol id="icon-platform" viewBox="0 0 1024 1024">
<path d="M199.68 564.906667L170.666667 721.92h250.88L392.533333 564.906667H199.68zM179.2 512h235.52c11.946667 0 23.893333 8.533333 25.6 22.186667l39.253333 209.92c1.706667 6.826667 0 15.36-5.12 22.186666s-11.946667 10.24-20.48 10.24H139.946667c-8.533333 0-15.36-3.413333-20.48-10.24-5.12-6.826667-6.826667-13.653333-5.12-22.186666L153.6 534.186667c1.706667-13.653333 11.946667-22.186667 25.6-22.186667z m411.306667 209.92h250.88l-29.013334-157.013333H619.52l-29.013333 157.013333zM597.333333 512h235.52c11.946667 0 23.893333 8.533333 25.6 22.186667l39.253334 209.92c1.706667 6.826667 0 15.36-5.12 22.186666s-11.946667 10.24-20.48 10.24H558.08c-8.533333 0-15.36-3.413333-20.48-10.24-5.12-6.826667-6.826667-13.653333-5.12-22.186666l39.253333-209.92c3.413333-13.653333 13.653333-22.186667 25.6-22.186667z m-216.746666-52.906667H631.466667l-29.013334-157.013333H409.6l-29.013333 157.013333z m6.826666-209.92h235.52c11.946667 0 23.893333 8.533333 25.6 22.186667l39.253334 209.92c1.706667 6.826667 0 15.36-5.12 22.186667s-11.946667 10.24-20.48 10.24H348.16c-8.533333 0-15.36-3.413333-20.48-10.24-5.12-6.826667-6.826667-13.653333-5.12-22.186667l39.253333-209.92c3.413333-11.946667 13.653333-22.186667 25.6-22.186667z m0 0" p-id="9166"></path>
</symbol>
<symbol id="icon-station" viewBox="0 0 1024 1024">
<path d="M890.3 390.6l-309.1-299c-38.3-37-100.5-37.1-138.9-0.3l-308.5 296c-9.2 8.9-14.4 20.9-14.4 33.4v456c0 52.2 43.9 84.4 98.1 84.4h147.2c54.2 0 98.1-32.3 98.1-84.4v-47.2c0-26.1 22-47.2 49.1-47.2s49.1 21.2 49.1 47.2v47.2c0 52.2 43.9 84.4 98.1 84.4h147.2c54.2 0 98.1-32.3 98.1-84.4V424c0.2-12.5-5-24.5-14.1-33.4z m-83.9 438.9c0 26.1-22 37.2-49.1 37.2h-49.1c-27.1 0-49.1-11.1-49.1-37.2v-47.2c0-52.2-43.9-104.5-98.1-104.5h-98.1c-54.2 0-98.1 52.3-98.1 104.5v47.2c0 26.1-22 37.2-49.1 37.2h-49.1c-27.1 0-49.1-11.1-49.1-37.2V448.7c0-6.3 2.6-12.3 7.2-16.7l252.1-242.6c19.2-18.5 50.3-18.5 69.4 0L799.3 433c4.6 4.4 7.2 10.4 7.2 16.7v379.8z" p-id="20574"></path>
</symbol>
<!-- <symbol id="icon-signal" viewBox="0 0 1024 1024">
<path d="M63.33 895.64V127.99h64v767.65z" fill="#fdfafa" p-id="24054"></path><path d="M750 365.57a146.25 146.25 0 1 1-103.36 42.83A145.29 145.29 0 0 1 750 365.57m0-64c-116.12 0-210.25 94.13-210.25 210.25S633.93 722.06 750 722.06s210.3-94.13 210.3-210.25S866.16 301.57 750 301.57z" fill="#fdfafa" p-id="24055"></path><path d="M336.94 365.57a146 146 0 1 1-103.27 42.78 145.09 145.09 0 0 1 103.27-42.78m0-64c-116 0-210 94-210 210s94 210 210 210 210-94 210-210-94-210-210-210z" fill="#fdfafa" p-id="24056"></path>
</symbol> -->
<symbol id="icon-signal" viewBox="0 0 1024 1024">
<path d="M63.33 895.64V127.99h64v767.65z" fill="#ffffff" p-id="1554"></path><path d="M750 365.57a146.25 146.25 0 1 1-103.36 42.83A145.29 145.29 0 0 1 750 365.57m0-64c-116.12 0-210.25 94.13-210.25 210.25S633.93 722.06 750 722.06s210.3-94.13 210.3-210.25S866.16 301.57 750 301.57z" fill="#ffffff" p-id="1555"></path><path d="M336.94 365.57a146 146 0 1 1-103.27 42.78 145.09 145.09 0 0 1 103.27-42.78m0-64c-116 0-210 94-210 210s94 210 210 210 210-94 210-210-94-210-210-210z" fill="#ffffff" p-id="1556"></path>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

213
quasar.config.js Normal file
View File

@ -0,0 +1,213 @@
/* eslint-env node */
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const { configure } = require('quasar/wrappers');
const BasePath = 'bjrtss';
module.exports = configure(function (/* ctx */) {
return {
eslint: {
// fix: true,
// include: [],
// exclude: [],
// rawOptions: {},
warnings: true,
errors: true,
exclude: ['src/protos/*'],
},
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: ['axios', '@pixi/graphics-extras'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ['app.scss'],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it
'material-symbols-outlined',
],
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
build: {
target: {
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node16',
},
vueRouterMode: 'history', // available values: 'hash', 'history'
vueRouterBase: BasePath,
// vueDevtools,
// vueOptionsAPI: false,
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
publicPath: BasePath,
// analyze: true,
// env: {},
// rawDefine: {}
// ignorePublicFolder: true,
// minify: false,
// polyfillModulePreload: true,
// distDir
// distDir: `dist/${BasePath}`,
// extendViteConf (viteConf) {},
// viteVuePluginOptions: {},
// vitePlugins: [
// [ 'package-name', { ..options.. } ]
// ]
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
// https: true
open: true, // opens browser window automatically
base: BasePath,
},
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
framework: {
config: {
notify: {
position: 'top',
timeout: 2000,
progress: true,
},
},
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
lang: 'zh-CN',
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: ['Notify', 'Dialog', 'Dark', 'AppFullscreen', 'Loading'],
},
// animations: 'all', // --- includes all animations
// https://v2.quasar.dev/options/animations
animations: [],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#sourcefiles
// sourceFiles: {
// rootComponent: 'src/App.vue',
// router: 'src/router/index',
// store: 'src/store/index',
// registerServiceWorker: 'src-pwa/register-service-worker',
// serviceWorker: 'src-pwa/custom-service-worker',
// pwaManifestFile: 'src-pwa/manifest.json',
// electronMain: 'src-electron/electron-main',
// electronPreload: 'src-electron/electron-preload'
// },
// https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
ssr: {
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
// extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false,
// manualStoreHydration: true,
// manualPostHydrationTrigger: true,
prodPort: 3000, // The default port that the production server should use
// (gets superseded if process.env.PORT is specified at runtime)
middlewares: [
'render', // keep this as last one
],
},
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
pwa: {
workboxMode: 'generateSW', // or 'injectManifest'
injectPwaMetaTags: true,
swFilename: 'sw.js',
manifestFilename: 'manifest.json',
useCredentialsForManifestTag: false,
// useFilenameHashes: true,
// extendGenerateSWOptions (cfg) {}
// extendInjectManifestOptions (cfg) {},
// extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true,
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
electron: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
inspectPort: 5858,
bundler: 'packager', // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: 'bj-rtss-client',
},
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: ['my-content-script'],
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
},
};
});

91
scripts/proto.cjs Normal file
View File

@ -0,0 +1,91 @@
/**
* 将proto文件编译到 src/proto/
*/
const { readdirSync } = require('fs');
const { resolve } = require('path');
const os = require('os');
const { exec } = require('child_process');
const messageDir = resolve(__dirname, '../bj-rtss-message');
const protoDir = resolve(messageDir, 'protos');
const destDir = resolve(__dirname, '../src/protos');
/**
* 递归处理所有proto文件生成
* @param {*} file 文件
* @param {*} path 目录
*/
function recursiveGenerate(file, path = [], cmds = []) {
if (file.isFile()) {
// 文件,生成
if (file.name.endsWith('.proto')) {
cmds.push(buildGenerateCmd(file.name, path));
} else {
console.warn('不是proto文件', file.name);
}
} else if (file.isDirectory()) {
// 文件夹,递归
readdirSync(resolve(protoDir, ...path, file.name), {
withFileTypes: true,
}).forEach((f) => {
const subPath = [...path, file.name];
recursiveGenerate(f, subPath, cmds);
});
}
}
const isLinux = os.type().toLowerCase().includes('linux');
function buildGenerateCmd(name, path = []) {
const protoPath = resolve(protoDir, ...path);
const tsPath = resolve(destDir, ...path);
let cmd = ['protoc', `-I=${protoPath}`, `--ts_out=${tsPath}`, `${name}`];
let cmdStr = cmd.join(' ');
return cmdStr;
}
function main() {
const protocDir = resolve(messageDir, 'protoc-23.1');
const protocBin = resolve(
protocDir,
`bin/${isLinux ? 'linux-x86_64' : 'win64'}`
);
const prepareCmds = [];
const setPathCmd = isLinux
? ['export', `PATH=${protocBin}:${protocDir}:"$PATH"`].join(' ')
: ['set', `PATH=${protocBin};${protocDir};%PATH%`].join(' ');
const protocVersionCmd = ['protoc', '--version'].join(' ');
prepareCmds.push(setPathCmd);
prepareCmds.push(protocVersionCmd);
readdirSync(protoDir, {
withFileTypes: true,
}).forEach((f) => {
recursiveGenerate(f, [], prepareCmds);
});
console.log(prepareCmds);
exec(
prepareCmds.join(' && '),
{
maxBuffer: 1024 * 2000,
},
(err, stdout, stderr) => {
if (err) {
console.error(err);
throw err;
} else if (stderr.length > 0) {
console.error(stderr.toString());
throw new Error(stderr.toString());
} else {
console.log(stdout);
}
}
);
}
// 执行
main();

70
scripts/sync.cjs Normal file
View File

@ -0,0 +1,70 @@
/**
* 同步图形框架文件到 src/jl-graphic/
*/
const {
readdirSync,
existsSync,
copyFileSync,
mkdirSync,
rmSync,
} = require('fs');
const { resolve } = require('path');
const jlGraphicSrcPath = resolve(__dirname, '../graphic-pixi/src/jlgraphic');
const jlGraphicLibPath = resolve(__dirname, '../src/jl-graphic');
/**
* 检查并初始化当前项目引入的jl-graphic库
*/
function checkAndInitJlGraphicLib() {
const exist = existsSync(jlGraphicLibPath);
if (exist) {
console.log('jl-graphic文件夹已存在,清空');
readdirSync(jlGraphicLibPath, {
withFileTypes: true,
}).forEach((file) => {
if (file.isDirectory()) {
rmSync(resolve(jlGraphicLibPath, file.name), { recursive: true });
} else {
rmSync(resolve(jlGraphicLibPath, file.name));
}
});
} else {
console.log('jl-graphic文件夹不存在,创建');
// 文件夹不存在,创建
mkdirSync(jlGraphicLibPath);
}
}
function copyJlGraphicFiles() {
readdirSync(jlGraphicSrcPath, {
withFileTypes: true,
}).forEach((file) => {
recursiveCopyFiles(file);
});
}
function recursiveCopyFiles(file, path = []) {
if (file.isFile()) {
const fileSrcPath = resolve(jlGraphicSrcPath, ...path, file.name);
const fileDestPath = resolve(jlGraphicLibPath, ...path, file.name);
console.log(`copy file ${fileSrcPath} -> ${fileDestPath}`);
copyFileSync(fileSrcPath, fileDestPath);
} else if (file.isDirectory()) {
const srcDir = resolve(jlGraphicSrcPath, ...path, file.name);
const dirPath = resolve(jlGraphicLibPath, ...path, file.name);
mkdirSync(dirPath);
readdirSync(srcDir, {
withFileTypes: true,
}).forEach((subFile) => {
recursiveCopyFiles(subFile, [...path, file.name]);
});
}
}
function main() {
checkAndInitJlGraphicLib();
copyJlGraphicFiles();
}
main();

7
src/App.vue Normal file
View File

@ -0,0 +1,7 @@
<template>
<router-view />
</template>
<script setup lang="ts">
</script>

42
src/api/ApiCommon.ts Normal file
View File

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

86
src/api/DraftApi.ts Normal file
View File

@ -0,0 +1,86 @@
import { api } from 'src/boot/axios';
import { PageDto, PageQueryDto } from './ApiCommon';
const DraftUriBase = '/api/drafting';
interface Item {
id: number;
name: string;
proto: string;
type: string;
createdAt: string;
updateAt: string;
creatorId?: number;
}
export class PagingQueryParams extends PageQueryDto {
name?: string;
}
/**
*
* @param params
* @returns
*/
export async function pageQuery(
params: PagingQueryParams
): Promise<PageDto<Item>> {
const response = await api.get(`${DraftUriBase}/paging`, {
params: params,
});
return response.data;
}
/**
* 稿
* @param params
* @returns
*/
export function createDraft(draftData: { name: string; type: string }) {
return api.post(`${DraftUriBase}`, draftData);
}
/**
* 稿
* @param id 稿id
*/
export function deleteDraft(id: number) {
return api.delete(`${DraftUriBase}/${id}`);
}
/**
* 稿
* @param params
* @returns
*/
export async function getDraft(id: number): Promise<Item> {
const response = await api.get(`${DraftUriBase}/${id}`);
return response.data;
}
/**
* 稿
* @param data
* @returns
*/
export function saveDraft(
id: number,
data: {
proto: string;
}
) {
return api.put(`${DraftUriBase}/${id}`, data);
}
/**
* 稿
* @param data
* @returns
*/
export async function saveAsDraft(
id: number,
data: { name: string; proto: string }
): Promise<Item> {
const response = await api.post(`${DraftUriBase}/${id}/saveAs`, data);
return response.data;
}

79
src/api/LineInfoApi.ts Normal file
View File

@ -0,0 +1,79 @@
import { api } from 'src/boot/axios';
import { PageDto, PageQueryDto } from './ApiCommon';
const UriBase = '/api/lineInfo';
export interface createParams {
name: string;
lineId: number;
config?: string;
}
interface Item extends createParams {
id: number;
createdAt: string;
updateAt: string;
}
export class PagingQueryParams extends PageQueryDto {
name?: string;
}
/**
*
* @param params
* @returns
*/
export async function pageQuery(
params: PagingQueryParams
): Promise<PageDto<Item>> {
const response = await api.get(`${UriBase}/paging`, {
params: params,
});
return response.data;
}
/**
* 线
* @param params
* @returns
*/
export function createLine(data: createParams) {
return api.post(`${UriBase}`, data);
}
/**
* 线
* @param id 线id
*/
export function deleteLine(id: number) {
return api.delete(`${UriBase}/${id}`);
}
/**
* 线
* @param id 稿id
*/
export function saveLineData(id: number, data: createParams) {
return api.put(`${UriBase}/${id}`, data);
}
/**
* 线
* @param params
* @returns
*/
export async function getLineInfo(id: number): Promise<Item> {
const response = await api.get(`${UriBase}/${id}`);
return response.data;
}
/**
* 线
* @param params
* @returns
*/
export async function getLineList(): Promise<Array<Item>> {
const response = await api.get(`${UriBase}/list`);
return response.data;
}

89
src/api/PublishApi.ts Normal file
View File

@ -0,0 +1,89 @@
import { api } from 'src/boot/axios';
import { PageDto, PageQueryDto } from './ApiCommon';
const PublishUriBase = '/api/publishedGi';
interface Item {
id: number;
name: string;
proto: string;
createdAt: string;
updateAt: string;
creatorId?: number;
}
export class PagingQueryParams extends PageQueryDto {
name?: string;
}
/**
* 稿
* @param id 稿id
*/
export function publishDraft(data: {
name: string;
lineId?: number;
draftingId: number;
}) {
return api.post(`${PublishUriBase}/publish`, data);
}
/**
*
* @param params
* @returns
*/
export async function getDraft(): Promise<Item> {
const response = await api.get(`${PublishUriBase}/list`);
return response.data;
}
/**
*
* @param params
* @returns
*/
export async function pageQuery(
params: PagingQueryParams
): Promise<PageDto<Item>> {
const response = await api.get(`${PublishUriBase}/paging`, {
params: params,
});
return response.data;
}
/**
*
* @param id 稿id
*/
export function deletePublish(id: number) {
return api.delete(`${PublishUriBase}/${id}`);
}
/**
*
* @param id id
*/
export async function getPublishMapInfoById(id: number): Promise<Item> {
const response = await api.get(`${PublishUriBase}/${id}`);
return response.data;
}
/**
* 线
*/
export async function getPublishLineNet(): Promise<Item> {
const response = await api.get(`${PublishUriBase}/publish/lineNetwork/info`);
return response.data;
}
/**
*
* @param id 线ID
* @param type 线
*/
export async function getPublishMapInfoByLineId(
lineId: number,
type: string
): Promise<Item> {
const response = await api.get(`${PublishUriBase}/${type}/${lineId}`);
return response.data;
}

77
src/api/UserApi.ts Normal file
View File

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

View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 356 360">
<path
d="M43.4 303.4c0 3.8-2.3 6.3-7.1 6.3h-15v-22h14.4c4.3 0 6.2 2.2 6.2 5.2 0 2.6-1.5 4.4-3.4 5 2.8.4 4.9 2.5 4.9 5.5zm-8-13H24.1v6.9H35c2.1 0 4-1.3 4-3.8 0-2.2-1.3-3.1-3.7-3.1zm5.1 12.6c0-2.3-1.8-3.7-4-3.7H24.2v7.7h11.7c3.4 0 4.6-1.8 4.6-4zm36.3 4v2.7H56v-22h20.6v2.7H58.9v6.8h14.6v2.3H58.9v7.5h17.9zm23-5.8v8.5H97v-8.5l-11-13.4h3.4l8.9 11 8.8-11h3.4l-10.8 13.4zm19.1-1.8V298c0-7.9 5.2-10.7 12.7-10.7 7.5 0 13 2.8 13 10.7v1.4c0 7.9-5.5 10.8-13 10.8s-12.7-3-12.7-10.8zm22.7 0V298c0-5.7-3.9-8-10-8-6 0-9.8 2.3-9.8 8v1.4c0 5.8 3.8 8.1 9.8 8.1 6 0 10-2.3 10-8.1zm37.2-11.6v21.9h-2.9l-15.8-17.9v17.9h-2.8v-22h3l15.6 18v-18h2.9zm37.9 10.2v1.3c0 7.8-5.2 10.4-12.4 10.4H193v-22h11.2c7.2 0 12.4 2.8 12.4 10.3zm-3 0c0-5.3-3.3-7.6-9.4-7.6h-8.4V307h8.4c6 0 9.5-2 9.5-7.7V298zm50.8-7.6h-9.7v19.3h-3v-19.3h-9.7v-2.6h22.4v2.6zm34.4-2.6v21.9h-3v-10.1h-16.8v10h-2.8v-21.8h2.8v9.2H296v-9.2h2.9zm34.9 19.2v2.7h-20.7v-22h20.6v2.7H316v6.8h14.5v2.3H316v7.5h17.8zM24 340.2v7.3h13.9v2.4h-14v9.6H21v-22h20v2.7H24zm41.5 11.4h-9.8v7.9H53v-22h13.3c5.1 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6H66c3.1 0 5.3-1.5 5.3-4.7 0-3.3-2.2-4.1-5.3-4.1H55.7v8.8zm47.9 6.2H89l-2 4.3h-3.2l10.7-22.2H98l10.7 22.2h-3.2l-2-4.3zm-1-2.3l-6.3-13-6 13h12.2zm46.3-15.3v21.9H146v-17.2L135.7 358h-2.1l-10.2-15.6v17h-2.8v-21.8h3l11 16.9 11.3-17h3zm35 19.3v2.6h-20.7v-22h20.6v2.7H166v6.8h14.5v2.3H166v7.6h17.8zm47-19.3l-8.3 22h-3l-7.1-18.6-7 18.6h-3l-8.2-22h3.3L204 356l6.8-18.5h3.4L221 356l6.6-18.5h3.3zm10 11.6v-1.4c0-7.8 5.2-10.7 12.7-10.7 7.6 0 13 2.9 13 10.7v1.4c0 7.9-5.4 10.8-13 10.8-7.5 0-12.7-3-12.7-10.8zm22.8 0v-1.4c0-5.7-4-8-10-8s-9.9 2.3-9.9 8v1.4c0 5.8 3.8 8.2 9.8 8.2 6.1 0 10-2.4 10-8.2zm28.3 2.4h-9.8v7.9h-2.8v-22h13.2c5.2 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6h10.2c3 0 5.2-1.5 5.2-4.7 0-3.3-2.1-4.1-5.2-4.1h-10.2v8.8zm40.3-1.5l-6.8 5.6v6.4h-2.9v-22h2.9v12.3l15.2-12.2h3.7l-9.9 8.1 10.3 13.8h-3.6l-8.9-12z" />
<path fill="#050A14"
d="M188.4 71.7a10.4 10.4 0 01-20.8 0 10.4 10.4 0 1120.8 0zM224.2 45c-2.2-3.9-5-7.5-8.2-10.7l-12 7c-3.7-3.2-8-5.7-12.6-7.3a49.4 49.4 0 00-9.7 13.9 59 59 0 0140.1 14l7.6-4.4a57 57 0 00-5.2-12.5zM178 125.1c4.5 0 9-.6 13.4-1.7v-14a40 40 0 0012.5-7.2 47.7 47.7 0 00-7.1-15.3 59 59 0 01-32.2 27.7v8.7c4.4 1.2 8.9 1.8 13.4 1.8zM131.8 45c-2.3 4-4 8.1-5.2 12.5l12 7a40 40 0 000 14.4c5.7 1.5 11.3 2 16.9 1.5a59 59 0 01-8-41.7l-7.5-4.3c-3.2 3.2-6 6.7-8.2 10.6z" />
<path fill="#00B4FF"
d="M224.2 98.4c2.3-3.9 4-8 5.2-12.4l-12-7a40 40 0 000-14.5c-5.7-1.5-11.3-2-16.9-1.5a59 59 0 018 41.7l7.5 4.4c3.2-3.2 6-6.8 8.2-10.7zm-92.4 0c2.2 4 5 7.5 8.2 10.7l12-7a40 40 0 0012.6 7.3c4-4.1 7.3-8.8 9.7-13.8a59 59 0 01-40-14l-7.7 4.4c1.2 4.3 3 8.5 5.2 12.4zm46.2-80c-4.5 0-9 .5-13.4 1.7V34a40 40 0 00-12.5 7.2c1.5 5.7 4 10.8 7.1 15.4a59 59 0 0132.2-27.7V20a53.3 53.3 0 00-13.4-1.8z" />
<path fill="#00B4FF"
d="M178 9.2a62.6 62.6 0 11-.1 125.2A62.6 62.6 0 01178 9.2m0-9.2a71.7 71.7 0 100 143.5A71.7 71.7 0 00178 0z" />
<path fill="#050A14"
d="M96.6 212v4.3c-9.2-.8-15.4-5.8-15.4-17.8V180h4.6v18.4c0 8.6 4 12.6 10.8 13.5zm16-31.9v18.4c0 8.9-4.3 12.8-10.9 13.5v4.4c9.2-.7 15.5-5.6 15.5-18v-18.3h-4.7zM62.2 199v-2.2c0-12.7-8.8-17.4-21-17.4-12.1 0-20.7 4.7-20.7 17.4v2.2c0 12.8 8.6 17.6 20.7 17.6 1.5 0 3-.1 4.4-.3l11.8 6.2 2-3.3-8.2-4-6.4-3.1a32 32 0 01-3.6.2c-9.8 0-16-3.9-16-13.3v-2.2c0-9.3 6.2-13.1 16-13.1 9.9 0 16.3 3.8 16.3 13.1v2.2c0 5.3-2.1 8.7-5.6 10.8l4.8 2.4c3.4-2.8 5.5-7 5.5-13.2zM168 215.6h5.1L156 179.7h-4.8l17 36zM143 205l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.8-3.7H143zm133.7 10.7h5.2l-17.3-35.9h-4.8l17 36zm-25-10.7l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.7-3.7h-14.8zm73.8-2.5c6-1.2 9-5.4 9-11.4 0-8-4.5-10.9-12.9-10.9h-21.4v35.5h4.6v-31.3h16.5c5 0 8.5 1.4 8.5 6.7 0 5.2-3.5 7.7-8.5 7.7h-11.4v4.1h10.7l9.3 12.8h5.5l-9.9-13.2zm-117.4 9.9c-9.7 0-14.7-2.5-18.6-6.3l-2.2 3.8c5.1 5 11 6.7 21 6.7 1.6 0 3.1-.1 4.6-.3l-1.9-4h-3zm18.4-7c0-6.4-4.7-8.6-13.8-9.4l-10.1-1c-6.7-.7-9.3-2.2-9.3-5.6 0-2.5 1.4-4 4.6-5l-1.8-3.8c-4.7 1.4-7.5 4.2-7.5 8.9 0 5.2 3.4 8.7 13 9.6l11.3 1.2c6.4.6 8.9 2 8.9 5.4 0 2.7-2.1 4.7-6 5.8l1.8 3.9c5.3-1.6 8.9-4.7 8.9-10zm-20.3-21.9c7.9 0 13.3 1.8 18.1 5.7l1.8-3.9a30 30 0 00-19.6-5.9c-2 0-4 .1-5.7.3l1.9 4 3.5-.2z" />
<path fill="#00B4FF"
d="M.5 251.9c29.6-.5 59.2-.8 88.8-1l88.7-.3 88.7.3 44.4.4 44.4.6-44.4.6-44.4.4-88.7.3-88.7-.3a7981 7981 0 01-88.8-1z" />
<path fill="none" d="M-565.2 324H-252v15.8h-313.2z" />
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

0
src/boot/.gitkeep Normal file
View File

View File

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

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

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

View File

@ -0,0 +1,85 @@
<template>
<q-list bordered separator>
<div v-for="(menu, ii) in list" :key="ii">
<div v-if="menu.children">
<q-expansion-item :icon="menu.icon" :label="menu.label">
<q-card>
<q-list bordered separator>
<q-item
v-for="(item, index) in menu.children"
:key="index"
clickable
:to="item.path"
exact
:inset-level="1"
>
<q-item-section avatar>
<q-icon :name="item.icon"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>{{ item.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-card>
</q-expansion-item>
</div>
<div v-else>
<q-item clickable :to="menu.path" exact>
<q-item-section avatar>
<q-icon :name="menu.icon"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>{{ menu.label }}</q-item-label>
</q-item-section>
</q-item>
</div>
</div>
</q-list>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const list = reactive([
{
path: '',
label: '系统管理',
icon: 'dataset',
children: [
{
path: '/sysManage/user',
label: '用户管理',
icon: 'manage_accounts',
},
],
},
{
path: '',
label: '数据管理',
icon: 'list_alt',
children: [
{
path: '/dataManage/draft',
label: '草稿管理',
icon: 'app_registration',
},
{
path: '/dataManage/publish',
label: '发布管理',
icon: 'app_registration',
},
{
path: '/dataManage/lineInfo',
label: '线路信息管理',
icon: 'app_registration',
},
],
},
{
path: '/monitor',
label: '监控',
icon: 'computer',
},
]);
</script>

View File

@ -0,0 +1,133 @@
<template>
<!-- 绘制图形模板属性 -->
<div v-if="drawStore.drawMode">
<q-card flat>
<q-card-section>
<div class="text-h6">{{ drawStore.drawGraphicName + ' 模板' }}</div>
</q-card-section>
<q-separator inset></q-separator>
<q-card-section>
<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">
<train-template></train-template>
</template> -->
</q-card-section>
</q-card>
</div>
<!-- 画布或图形对象属性 -->
<div v-else-if="drawStore.selectedGraphics !== null">
<q-card flat>
<q-card-section>
<div class="text-h6">{{ drawStore.selectedObjName + ' 属性' }}</div>
</q-card-section>
<q-separator inset></q-separator>
<template v-if="drawStore.selectedGraphics.length === 0">
<q-card-section>
<canvas-property></canvas-property>
</q-card-section>
</template>
<template v-else-if="drawStore.selectedGraphics.length === 1">
<q-card-section>
<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>
<station-line-property
v-if="drawStore.selectedGraphicType === StationLine.Type"
></station-line-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>
<TurnoutProperty
v-else-if="drawStore.selectedGraphicType === Turnout.Type"
></TurnoutProperty>
<SectionProperty
v-else-if="drawStore.selectedGraphicType === Section.Type"
></SectionProperty>
<run-line-property
v-else-if="drawStore.selectedGraphicType === RunLine.Type"
></run-line-property>
<train-window-property
v-else-if="drawStore.selectedGraphicType === TrainWindow.Type"
></train-window-property>
<path-line-property
v-else-if="drawStore.selectedGraphicType === PathLine.Type"
></path-line-property>
<axle-counting-property
v-else-if="drawStore.selectedGraphicType === AxleCounting.Type"
></axle-counting-property>
<separator-property
v-else-if="drawStore.selectedGraphicType === Separator.Type"
></separator-property>
</q-card-section>
</template>
</q-card>
</div>
</template>
<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 TrainTemplate from './templates/TrainTemplate.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 StationLineProperty from './properties/StationLineProperty.vue';
// import TrainProperty from './properties/TrainProperty.vue';
import TrainWindowProperty from './properties/TrainWindowProperty.vue';
import AxleCountingProperty from './properties/AxleCountingProperty.vue';
import IscsFanProperty from './properties/IscsFanProperty.vue';
import SignalProperty from './properties/SignalProperty.vue';
import TurnoutProperty from './properties/TurnoutProperty.vue';
import SectionProperty from './properties/SectionProperty.vue';
import RunLineProperty from './properties/RunLineProperty.vue';
import PathLineProperty from './properties/PathLineProperty.vue';
import SeparatorProperty from './properties/SeparatorProperty.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 { StationLine } from 'src/graphics/stationLine/StationLine';
// 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';
import { Turnout } from 'src/graphics/turnout/Turnout';
import { RunLine } from 'src/graphics/runLine/RunLine';
import { Section } from 'src/graphics/section/Section';
import { TrainWindow } from 'src/graphics/trainWindow/TrainWindow';
import { PathLine } from 'src/graphics/pathLine/PathLine';
import { AxleCounting } from 'src/graphics/axleCounting/AxleCounting';
import { Separator } from 'src/graphics/separator/Separator';
const drawStore = useDrawStore();
</script>

View File

@ -0,0 +1,164 @@
<template>
<q-form class="q-gutter-sm">
<q-input
outlined
readonly
v-model="axleCountingModel.id"
label="id"
hint=""
/>
<q-input
outlined
label="计轴名称"
type="textarea"
@blur="onUpdate"
v-model="axleCountingModel.code"
lazy-rules
autogrow
/>
<q-select
outlined
style="margin-top: 10px"
v-model="kilometerSystem.coordinateSystem"
:options="CoordinateSystemOptions"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="坐标系"
></q-select>
<q-input
outlined
style="margin-top: 10px"
v-model.number="kilometerSystem.kilometer"
type="number"
@blur="onUpdate"
label="公里标(mm):"
/>
<q-list bordered separator class="rounded-borders">
<q-item>
<q-item-section no-wrap class="q-gutter-y-sm column">
<q-item-label> 关联的区段 </q-item-label>
<div class="q-gutter-sm row">
<q-chip
v-for="item in sectionRelations"
:key="item"
square
color="primary"
text-color="white"
>
{{ item }}
</q-chip>
</div>
</q-item-section>
</q-item>
<q-item>
<q-item-section no-wrap class="q-gutter-y-sm column">
<q-item-label> 关联的道岔 </q-item-label>
<div class="q-gutter-sm row">
<q-chip
v-for="item in turnoutRelations"
:key="item"
square
color="primary"
text-color="white"
>
{{ item }}
</q-chip>
</div>
</q-item-section>
</q-item>
</q-list>
</q-form>
</template>
<script setup lang="ts">
import { AxleCountingData } from 'src/drawApp/graphics/AxleCountingInteraction';
import { AxleCounting } from 'src/graphics/axleCounting/AxleCounting';
import { Section } from 'src/graphics/section/Section';
import { Turnout } from 'src/graphics/turnout/Turnout';
import { useDrawStore } from 'src/stores/draw-store';
import { computed, onMounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const axleCountingModel = reactive(new AxleCountingData());
const kilometerSystem = reactive({ coordinateSystem: '', kilometer: 0 });
const CoordinateSystemOptions = [
{ label: '车辆段', value: 'DEPOT' },
{ label: '停车场', value: 'PARKING_LOT' },
{ label: '正线', value: 'MAIN_LINE' },
{ label: '换线', value: 'TRANSFER' },
];
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == AxleCounting.Type) {
axleCountingModel.copyFrom(val.saveData() as AxleCountingData);
if (axleCountingModel.kilometerSystem) {
kilometerSystem.coordinateSystem =
axleCountingModel.kilometerSystem.coordinateSystem;
kilometerSystem.kilometer = axleCountingModel.kilometerSystem.kilometer;
}
}
}
);
onMounted(() => {
const axleCounting = drawStore.selectedGraphic as AxleCounting;
if (axleCounting) {
axleCountingModel.copyFrom(axleCounting.saveData());
if (axleCountingModel.kilometerSystem) {
kilometerSystem.coordinateSystem =
axleCountingModel.kilometerSystem.coordinateSystem;
kilometerSystem.kilometer = axleCountingModel.kilometerSystem.kilometer;
}
}
});
function onUpdate() {
const axleCounting = drawStore.selectedGraphic as AxleCounting;
axleCountingModel.kilometerSystem = {
coordinateSystem: kilometerSystem.coordinateSystem,
kilometer: kilometerSystem.kilometer,
};
if (axleCounting) {
drawStore
.getDrawApp()
.updateGraphicAndRecord(axleCounting, axleCountingModel);
}
}
const sectionRelations = computed(() => {
const axleCounting = drawStore.selectedGraphic as AxleCounting;
const sectionRelations =
axleCounting?.relationManage.getRelationsOfGraphicAndOtherType(
axleCounting,
Section.Type
);
const ref = sectionRelations.map(
(relation) =>
`${relation.getOtherGraphic<Section>(axleCounting).datas.code}(${
relation.getOtherRelationParam(axleCounting).param
})`
);
return Array.from(new Set(ref));
});
const turnoutRelations = computed(() => {
const axleCounting = drawStore.selectedGraphic as AxleCounting;
const turnoutRelations =
axleCounting?.relationManage.getRelationsOfGraphicAndOtherType(
axleCounting,
Turnout.Type
);
const ref = turnoutRelations.map(
(relation) =>
`${relation.getOtherGraphic<Turnout>(axleCounting).datas.code}(${
relation.getOtherRelationParam(axleCounting).param
})`
);
return Array.from(new Set(ref));
});
</script>

View File

@ -0,0 +1,80 @@
<template>
<q-form>
<q-input
outlined
v-model.number="canvas.width"
@blur="onUpdate"
label="画布宽 *"
lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
/>
<q-input
outlined
type="number"
v-model.number="canvas.height"
@blur="onUpdate"
label="画布高 *"
lazy-rules
:rules="[(val) => val > 0 || '画布高必须大于0']"
/>
<q-input
outlined
v-model="canvas.backgroundColor"
@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
:model-value="canvas.backgroundColor"
@change="
(val) => {
canvas.backgroundColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</q-form>
</template>
<script setup lang="ts">
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, onUnmounted, reactive } from 'vue';
const drawStore = useDrawStore();
const canvas = reactive({
width: 1920,
height: 1080,
backgroundColor: '#ffffff',
});
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,
viewportTransform: app.canvas.properties.viewportTransform,
});
}
</script>

View File

@ -0,0 +1,42 @@
<template>
<q-form>
<q-input outlined readonly v-model="model.id" label="id" :rules="[]" />
</q-form>
</template>
<script setup lang="ts">
import { IscsFanData } from 'src/drawApp/graphics/IscsFanInteraction';
import { IscsFan } from 'src/graphics/iscs-fan/IscsFan';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const model = reactive(new IscsFanData());
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == IscsFan.Type) {
// console.log('Iscs');
model.copyFrom(val.saveData() as IscsFanData);
}
}
);
onMounted(() => {
// console.log('Iscs mounted');
const g = drawStore.selectedGraphic as IscsFan;
if (g) {
model.copyFrom(g.saveData());
}
});
function onUpdate() {
console.log('Iscs风机 属性更新');
const g = drawStore.selectedGraphic as IscsFan;
if (g) {
drawStore.getDrawApp().updateGraphicAndRecord(g, model);
}
}
</script>

View File

@ -0,0 +1,96 @@
<template>
<q-form>
<q-input outlined readonly v-model="linkModel.id" label="id" hint="" />
<q-input
outlined
v-model.number="linkModel.lineWidth"
type="number"
@blur="onUpdate"
label="线宽"
lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
/>
<q-input
outlined
v-model="linkModel.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="linkModel.lineColor"
@change="
(val) => {
linkModel.lineColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<!-- <q-btn-toggle
disable
v-model="linkModel.curve"
:options="[
{ label: '直线', value: false },
{ label: '曲线', value: true },
]"
/> -->
<q-input
v-if="linkModel.curve"
outlined
v-model.number="linkModel.segmentsCount"
type="number"
@blur="onUpdate"
label="曲线分段数量"
lazy-rules
:rules="[(val) => (val && val > 0) || '曲线分段数量必须大于0']"
/>
</q-form>
</template>
<script setup lang="ts">
import { LinkData } from 'src/drawApp/graphics/LinkInteraction';
import { Link } from 'src/graphics/link/Link';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const linkModel = reactive(new LinkData());
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Link.Type) {
// console.log('link');
linkModel.copyFrom(val.saveData() as LinkData);
}
}
);
onMounted(() => {
// console.log('link mounted');
const link = drawStore.selectedGraphic as Link;
if (link) {
linkModel.copyFrom(link.saveData());
}
});
function onUpdate() {
console.log('link 属性更新');
const link = drawStore.selectedGraphic as Link;
if (link) {
drawStore.getDrawApp().updateGraphicAndRecord(link, linkModel);
}
}
</script>

View File

@ -0,0 +1,91 @@
<template>
<q-form>
<q-input outlined readonly v-model="pathLineModel.id" label="id" hint="" />
<q-checkbox
v-model="pathLineModel.isUp"
label="是否上行"
@update:model-value="onUpdate"
/>
<q-select
outlined
style="margin-top: 10px"
v-model="pathLineModel.code"
:options="lineList"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="关联线路"
></q-select>
<template :key="item" v-for="item in pathLineModel.kilometerPoints">
<!-- <div style="display: flex; margin-top: 5px">
<q-input
outlined
v-model.number="item.point.x"
type="number"
@blur="onUpdate"
:label="`${item.stName}x:`"
/>
<q-input
outlined
v-model.number="item.point.y"
type="number"
@blur="onUpdate"
:label="`${item.stName}y:`"
/>
</div> -->
<div style="display: flex; margin-top: 5px">
<q-input outlined readonly v-model="item.stName" label="车站" hint="" />
<q-input
outlined
v-model.number="item.kilometer"
type="number"
@blur="onUpdate"
:label="`${item.stName}公里标:`"
/>
</div>
</template>
</q-form>
</template>
<script setup lang="ts">
import { PathLineData } from 'src/drawApp/graphics/PathLineInteraction';
import { PathLine } from 'src/graphics/pathLine/PathLine';
import { useDrawStore } from 'src/stores/draw-store';
import { reactive, onMounted, watch } from 'vue';
import { getLineList } from 'src/api/LineInfoApi';
const drawStore = useDrawStore();
const pathLineModel = reactive(new PathLineData());
const lineList: { label: string; value: string }[] = reactive([]);
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == PathLine.Type) {
pathLineModel.copyFrom(val.saveData() as PathLineData);
}
}
);
onMounted(() => {
getLineList()
.then((res) => {
res.forEach((item) => {
lineList.push({ value: item.lineId + '', label: item.name });
});
})
.catch((err) => {
console.error('获取线路列表失败:' + err.message);
});
const pathLine = drawStore.selectedGraphic as PathLine;
if (pathLine) {
pathLineModel.copyFrom(pathLine.saveData());
}
});
function onUpdate() {
const pathLine = drawStore.selectedGraphic as PathLine;
if (pathLine) {
drawStore.getDrawApp().updateGraphicAndRecord(pathLine, pathLineModel);
}
}
</script>

View File

@ -0,0 +1,75 @@
<template>
<q-form>
<q-input outlined readonly v-model="platformModel.id" label="id" hint="" />
<q-select
outlined
@blur="onUpdate"
v-model="hasDoor"
:options="optionsDoor"
label="是否有屏蔽门"
/>
<q-select
outlined
@blur="onUpdate"
v-model="direction"
:options="optionsDirection"
label="方向"
/>
</q-form>
</template>
<script setup lang="ts">
import { PlatformData } from 'src/drawApp/graphics/PlatformInteraction';
import { Platform } from 'src/graphics/platform/Platform';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, ref, watch } from 'vue';
const drawStore = useDrawStore();
const platformModel = reactive(new PlatformData());
const hasDoor = ref('是');
const optionsDoor = ['是', '否'];
const direction = ref('向上');
const optionsDirection = ['向上', '向下'];
enum showSelect {
= 'true',
= 'false',
向上 = 'up',
向下 = 'down',
}
enum showSelectData {
true = '是',
false = '否',
up = '向上',
down = '向下',
}
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Platform.Type) {
platformModel.copyFrom(val.saveData() as PlatformData);
hasDoor.value = (showSelectData as never)[platformModel.hasdoor + ''];
direction.value = (showSelectData as never)[platformModel.direction];
}
}
);
onMounted(() => {
const platform = drawStore.selectedGraphic as Platform;
if (platform) {
platformModel.copyFrom(platform.saveData());
hasDoor.value = (showSelectData as never)[platformModel.hasdoor + ''];
direction.value = (showSelectData as never)[platformModel.direction];
}
});
function onUpdate() {
platformModel.hasdoor = JSON.parse((showSelect as never)[hasDoor.value]);
platformModel.direction = (showSelect as never)[direction.value];
const platform = drawStore.selectedGraphic as Platform;
if (platform) {
drawStore.getDrawApp().updateGraphicAndRecord(platform, platformModel);
}
}
</script>

View File

@ -0,0 +1,100 @@
<template>
<q-form>
<q-input outlined readonly v-model="rectModel.id" label="id" hint="" />
<q-input
outlined
v-model.number="rectModel.lineWidth"
type="number"
@blur="onUpdate"
label="线宽"
lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
/>
<q-input
outlined
v-model="rectModel.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="rectModel.lineColor"
@change="
(val) => {
rectModel.lineColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input
outlined
v-model.number="rectModel.width"
type="number"
@blur="onUpdate"
label="宽度"
lazy-rules
:rules="[(val) => (val && val > 0) || '宽度必须大于0']"
/>
<q-input
outlined
v-model.number="rectModel.height"
type="number"
@blur="onUpdate"
label="高度"
lazy-rules
:rules="[(val) => (val && val > 0) || '宽度必须大于0']"
/>
<q-input
outlined
v-model.number="rectModel.radius"
type="number"
@blur="onUpdate"
label="圆角半径"
lazy-rules
:rules="[(val) => (val && val > 0) || '圆角半径必须大于0']"
/>
</q-form>
</template>
<script setup lang="ts">
import { RectData } from 'src/drawApp/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 rectModel = reactive(new RectData());
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Rect.Type) {
rectModel.copyFrom(val.saveData() as RectData);
}
}
);
onMounted(() => {
const Rect = drawStore.selectedGraphic as Rect;
if (Rect) {
rectModel.copyFrom(Rect.saveData());
}
});
function onUpdate() {
const Rect = drawStore.selectedGraphic as Rect;
if (Rect) {
drawStore.getDrawApp().updateGraphicAndRecord(Rect, rectModel);
}
}
</script>

View File

@ -0,0 +1,189 @@
<template>
<q-form>
<q-input outlined readonly v-model="runLineModel.id" label="id" hint="" />
<q-select
outlined
style="margin-top: 10px"
v-model="runLineModel.lineId"
:options="lineList"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="关联线路"
>
</q-select>
<q-input
outlined
style="margin-top: 10px"
v-model="runLineModel.code"
@blur="onUpdate"
label="名称"
/>
<q-input
outlined
v-model="runLineModel.nameColor"
style="margin-top: 10px"
@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="runLineModel.nameColor"
@change="
(val) => {
runLineModel.nameColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input
outlined
v-model="runLineModel.nameBgColor"
@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="runLineModel.nameBgColor"
@change="
(val) => {
runLineModel.nameBgColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<template :key="item" v-for="(item, index) in runLineModel.points">
<div style="display: flex; margin-top: 5px">
<q-input
outlined
v-model.number="item.x"
type="number"
@blur="onUpdate"
:label="`x${index + 1}:`"
/>
<q-input
outlined
v-model.number="item.y"
type="number"
@blur="onUpdate"
:label="`y${index + 1}:`"
/>
</div>
</template>
<q-select
outlined
style="margin-top: 10px"
v-model="runLineModel.containSta"
:options="stationLines"
option-value="id"
option-label="code"
:multiple="true"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="关联车站"
>
<template v-slot:after>
<q-btn
color="primary"
label="生成"
@click="generateContainSta"
/> </template
></q-select>
<div style="margin-top: 10px; display: flex; justify-content: center">
<q-btn
color="primary"
style="margin: auto"
@click="generatePathLine"
label="生成轨迹线"
/>
</div>
</q-form>
</template>
<script setup lang="ts">
import { RunLineData } from 'src/drawApp/graphics/RunLineInteraction';
import { RunLine } from 'src/graphics/runLine/RunLine';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch, ref } from 'vue';
import { Point } from 'pixi.js';
import {
IStationLineData,
StationLine,
} from 'src/graphics/stationLine/StationLine';
import { getLineList } from 'src/api/LineInfoApi';
const drawStore = useDrawStore();
const runLineModel = reactive(new RunLineData());
const stationLines: IStationLineData[] = reactive([]);
const lineList: { label: string; value: string }[] = reactive([]);
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == RunLine.Type) {
runLineModel.copyFrom(val.saveData() as RunLineData);
}
}
);
onMounted(() => {
getLineList()
.then((res) => {
res.forEach((item) => {
lineList.push({ value: item.lineId + '', label: item.name });
});
})
.catch((err) => {
console.error('获取线路列表失败:' + err.message);
});
const runLine = drawStore.selectedGraphic as RunLine;
const stations = drawStore
.getDrawApp()
.queryStore.queryByType(StationLine.Type) as StationLine[];
stations.forEach((item) => stationLines.push(item.datas));
if (runLine) {
runLineModel.copyFrom(runLine.saveData());
}
});
function onUpdate() {
const runLine = drawStore.selectedGraphic as RunLine;
if (runLine) {
drawStore.getDrawApp().updateGraphicAndRecord(runLine, runLineModel);
}
}
function generatePathLine() {
const runLine = drawStore.selectedGraphic as RunLine;
if (runLine) {
const points = runLineModel.points;
const points1: Point[] = [];
points.forEach((p) => points1.push(new Point(p.x, p.y)));
runLine.generatePathLine(points1);
}
}
function generateContainSta() {
const runLine = drawStore.selectedGraphic as RunLine;
if (runLine) {
runLine.generateContainSta();
runLineModel.copyFrom(runLine.saveData());
}
}
</script>

View File

@ -0,0 +1,132 @@
<template>
<q-form>
<q-input outlined readonly v-model="sectionModel.id" label="id" hint="" />
<q-input
outlined
v-model.number="sectionModel.code"
@blur="onUpdate"
label="编号"
/>
<q-field class="q-mt-lg" outlined label="关联区段" readonly stack-label>
<template #control>
<q-chip
color="primary"
text-color="white"
v-for="code in sectionRelations"
:key="code"
square
>{{ code }}</q-chip
>
</template>
</q-field>
<q-field class="q-mt-lg" outlined label="关联道岔" readonly stack-label>
<template #control>
<q-chip
color="primary"
text-color="white"
v-for="code in turnoutRelations"
:key="code"
square
>{{ code }}</q-chip
>
</template>
</q-field>
<q-input class="q-mt-lg" outlined v-model="splitNum" type="number">
<template #after>
<q-btn
:disable="sectionModel.data.sectionType !== SectionType.Physical"
color="primary"
@click="splitSection"
>拆分为逻辑区段</q-btn
>
</template>
</q-input>
</q-form>
</template>
<script setup lang="ts">
import { SectionData } from 'src/drawApp/graphics/SectionInteraction';
import { Section, SectionType } from 'src/graphics/section/Section';
import { Turnout } from 'src/graphics/turnout/Turnout';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { useDrawStore } from 'src/stores/draw-store';
import { computed, ref, shallowRef, toRaw, watchEffect } from 'vue';
const drawStore = useDrawStore();
const sectionModel = shallowRef(new SectionData());
const splitNum = ref(3);
function splitSection() {
const sectionData = toRaw(sectionModel.value);
const section = toRaw(drawStore.selectedGraphic as Section);
const app = drawStore.getDrawApp();
const points = section.getSplitPoints(splitNum.value);
const childIds: string[] = [];
points.forEach((ps, i) => {
const data = new SectionData();
data.points = ps.map((p) => new graphicData.Point({ x: p.x, y: p.y }));
data.id = app.drawAssistants
.find((as) => as.name === Section.name)!
.nextId();
data.code = `${sectionData.code}-${'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.charAt(
i % 26
)}`;
data.sectionType = SectionType.Logic;
const section = app.graphicTemplateMap.get(Section.name)!.new();
section?.loadData(data);
app.addGraphics(section);
childIds.push(data.id);
});
sectionData.children = childIds;
app.updateGraphicAndRecord(section, sectionData);
}
const sectionRelations = computed(() => {
const section = drawStore.selectedGraphic as Section;
const sectionRelations =
section?.relationManage.getRelationsOfGraphicAndOtherType(
section,
Section.Type
);
return sectionRelations.map(
(relation) =>
`${relation.getRelationParam(section).param}: ${
relation.getOtherGraphic<Section>(section).datas.code
}(${relation.getOtherRelationParam(section).param})`
);
});
const turnoutRelations = computed(() => {
const section = drawStore.selectedGraphic as Section;
const turnoutRelations =
section?.relationManage.getRelationsOfGraphicAndOtherType(
section,
Turnout.Type
);
return turnoutRelations.map(
(relation) =>
`${relation.getRelationParam(section).param}: ${
relation.getOtherGraphic<Turnout>(section).datas.code
}(${relation.getOtherRelationParam(section).param})`
);
});
watchEffect(() => {
const section = drawStore.selectedGraphic;
if (section && section instanceof Section) {
sectionModel.value = section.saveData();
}
});
const onUpdate = () => {
const section = drawStore.selectedGraphic as Section;
if (section) {
drawStore.getDrawApp().updateGraphicAndRecord(section, sectionModel.value);
}
};
</script>

View File

@ -0,0 +1,57 @@
<template>
<q-form>
<q-input outlined readonly v-model="separatorModel.id" label="id" hint="" />
<q-select
outlined
style="margin-top: 10px"
v-model="separatorModel.separatorType"
:options="typeOptions"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="分隔符类型"
></q-select>
</q-form>
</template>
<script setup lang="ts">
import { SeparatorData } from 'src/drawApp/graphics/SeparatorInteraction';
import { Separator } from 'src/graphics/separator/Separator';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const separatorModel = reactive(new SeparatorData());
const typeOptions = [
{ label: '区段分隔符', value: 'section' },
{ label: '道岔分隔符', value: 'turnout' },
{ label: '左断路分隔符', value: 'endA' },
{ label: '右断路分隔符', value: 'endB' },
];
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Separator.Type) {
separatorModel.copyFrom(val.saveData() as SeparatorData);
}
}
);
onMounted(() => {
const Separator = drawStore.selectedGraphic as Separator;
if (Separator) {
separatorModel.copyFrom(Separator.saveData());
}
});
function onUpdate() {
const Separator = drawStore.selectedGraphic as Separator;
if (Separator) {
drawStore.getDrawApp().updateGraphicAndRecord(Separator, separatorModel);
}
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<q-form>
<q-input outlined readonly v-model="signalModel.id" label="id" hint="" />
<q-input
outlined
v-model.number="signalModel.code"
@blur="onUpdate"
label="编号"
/>
<q-select
outlined
style="margin-top: 10px"
v-model="kilometerSystem.coordinateSystem"
:options="CoordinateSystemOptions"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="坐标系"
></q-select>
<q-input
outlined
style="margin-top: 10px"
v-model.number="kilometerSystem.kilometer"
type="number"
@blur="onUpdate"
label="公里标(mm):"
/>
</q-form>
</template>
<script setup lang="ts">
import { SignalData } from 'src/drawApp/graphics/SignalInteraction';
import { Signal } from 'src/graphics/signal/Signal';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const signalModel = reactive(new SignalData());
const kilometerSystem = reactive({ coordinateSystem: '', kilometer: 0 });
const CoordinateSystemOptions = [
{ label: '车辆段', value: 'DEPOT' },
{ label: '停车场', value: 'PARKING_LOT' },
{ label: '正线', value: 'MAIN_LINE' },
{ label: '换线', value: 'TRANSFER' },
];
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Signal.Type) {
signalModel.copyFrom(val.saveData() as SignalData);
if (signalModel.kilometerSystem) {
kilometerSystem.coordinateSystem =
signalModel.kilometerSystem.coordinateSystem;
kilometerSystem.kilometer = signalModel.kilometerSystem.kilometer;
}
}
}
);
onMounted(() => {
const signal = drawStore.selectedGraphic as Signal;
if (signal) {
signalModel.copyFrom(signal.saveData());
if (signalModel.kilometerSystem) {
kilometerSystem.coordinateSystem =
signalModel.kilometerSystem.coordinateSystem;
kilometerSystem.kilometer = signalModel.kilometerSystem.kilometer;
}
}
});
function onUpdate() {
const signal = drawStore.selectedGraphic as Signal;
signalModel.kilometerSystem = {
coordinateSystem: kilometerSystem.coordinateSystem,
kilometer: kilometerSystem.kilometer,
};
if (signal) {
drawStore.getDrawApp().updateGraphicAndRecord(signal, signalModel);
}
}
</script>

View File

@ -0,0 +1,87 @@
<template>
<q-form>
<q-input
outlined
readonly
v-model="stationLineModel.id"
label="id"
hint=""
/>
<q-input
outlined
label="车站名称"
type="textarea"
@blur="onUpdate"
v-model="stationLineModel.code"
lazy-rules
autogrow
/>
<q-checkbox
v-model="stationLineModel.hideName"
label="名称是否隐藏"
@update:model-value="onUpdate"
/>
<q-select
outlined
@blur="onUpdate"
v-model="hasTransfer"
:options="optionsCircle"
label="是否有换乘"
/>
</q-form>
</template>
<script setup lang="ts">
import { StationLineData } from 'src/drawApp/graphics/StationLineInteraction';
import { StationLine } from 'src/graphics/stationLine/StationLine';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, ref, watch } from 'vue';
const drawStore = useDrawStore();
const stationLineModel = reactive(new StationLineData());
const hasTransfer = ref('是');
const optionsCircle = ['是', '否'];
enum showSelect {
= 'true',
= 'false',
}
enum showSelectData {
true = '是',
false = '否',
}
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == StationLine.Type) {
stationLineModel.copyFrom(val.saveData() as StationLineData);
hasTransfer.value = (showSelectData as never)[
stationLineModel.hasTransfer + ''
];
}
}
);
onMounted(() => {
const stationLine = drawStore.selectedGraphic as StationLine;
if (stationLine) {
stationLineModel.copyFrom(stationLine.saveData());
hasTransfer.value = (showSelectData as never)[
stationLineModel.hasTransfer + ''
];
}
});
function onUpdate() {
stationLineModel.hasTransfer = JSON.parse(
(showSelect as never)[hasTransfer.value]
);
const stationLine = drawStore.selectedGraphic as StationLine;
if (stationLine) {
drawStore
.getDrawApp()
.updateGraphicAndRecord(stationLine, stationLineModel);
}
}
</script>

View File

@ -0,0 +1,127 @@
<template>
<q-form class="q-gutter-sm">
<q-input outlined readonly v-model="stationModel.id" label="id" hint="" />
<q-input
outlined
label="车站名称"
type="textarea"
@blur="onUpdate"
v-model="stationModel.code"
lazy-rules
autogrow
/>
<q-select
outlined
style="margin-top: 10px"
v-model="kilometerSystem.coordinateSystem"
:options="CoordinateSystemOptions"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="坐标系"
></q-select>
<q-input
outlined
style="margin-top: 10px"
v-model.number="kilometerSystem.kilometer"
type="number"
@blur="onUpdate"
label="公里标(mm):"
/>
<q-select
outlined
@blur="onUpdate"
v-model="hasControl"
:options="optionsControl"
label="是否有控制"
/>
<q-select
outlined
@blur="onUpdate"
v-model="concentrationStations"
:options="optionsControl"
label="是否集中站"
/>
</q-form>
</template>
<script setup lang="ts">
import { StationData } from 'src/drawApp/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 hasControl = ref('是');
const concentrationStations = ref('否');
const optionsControl = ['是', '否'];
enum showSelect {
= 'true',
= 'false',
}
enum showSelectData {
true = '是',
false = '否',
}
const kilometerSystem = reactive({ coordinateSystem: '', kilometer: 0 });
const CoordinateSystemOptions = [
{ label: '车辆段', value: 'DEPOT' },
{ label: '停车场', value: 'PARKING_LOT' },
{ label: '正线', value: 'MAIN_LINE' },
{ label: '换线', value: 'TRANSFER' },
];
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Station.Type) {
stationModel.copyFrom(val.saveData() as StationData);
hasControl.value = (showSelectData as never)[
stationModel.hasControl + ''
];
concentrationStations.value = (showSelectData as never)[
stationModel.concentrationStations + ''
];
if (stationModel.kilometerSystem) {
kilometerSystem.coordinateSystem =
stationModel.kilometerSystem.coordinateSystem;
kilometerSystem.kilometer = stationModel.kilometerSystem.kilometer;
}
}
}
);
onMounted(() => {
const station = drawStore.selectedGraphic as Station;
if (station) {
stationModel.copyFrom(station.saveData());
hasControl.value = (showSelectData as never)[stationModel.hasControl + ''];
concentrationStations.value = (showSelectData as never)[
stationModel.concentrationStations + ''
];
if (stationModel.kilometerSystem) {
kilometerSystem.coordinateSystem =
stationModel.kilometerSystem.coordinateSystem;
kilometerSystem.kilometer = stationModel.kilometerSystem.kilometer;
}
}
});
function onUpdate() {
stationModel.hasControl = JSON.parse((showSelect as never)[hasControl.value]);
stationModel.concentrationStations = JSON.parse(
(showSelect as never)[concentrationStations.value]
);
stationModel.kilometerSystem = {
coordinateSystem: kilometerSystem.coordinateSystem,
kilometer: kilometerSystem.kilometer,
};
const station = drawStore.selectedGraphic as Station;
if (station) {
drawStore.getDrawApp().updateGraphicAndRecord(station, stationModel);
}
}
</script>

View File

@ -0,0 +1,84 @@
<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-select
outlined
@blur="onUpdate"
v-model="hasBorder"
:options="optionsDoor"
label="是否有边框"
/>
<q-select
outlined
@blur="onUpdate"
v-model="trainDirection"
:options="optionsDirection"
label="行驶方向"
/>
</q-form>
</template>
<script setup lang="ts">
import { TrainData } from 'src/drawApp/graphics/TrainInteraction';
import { Train } from 'src/graphics/train/Train';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, ref, watch } from 'vue';
const drawStore = useDrawStore();
const trainModel = reactive(new TrainData());
const hasBorder = ref('是');
const optionsDoor = ['是', '否'];
const trainDirection = ref('向左');
const optionsDirection = ['向左', '向右'];
enum showSelect {
= 'true',
= 'false',
向左 = 'left',
向右 = 'right',
}
enum showSelectData {
true = '是',
false = '否',
left = '向左',
right = '向右',
}
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Train.Type) {
trainModel.copyFrom(val.saveData() as TrainData);
hasBorder.value = (showSelectData as never)[trainModel.hasBorder + ''];
trainDirection.value = (showSelectData as never)[
trainModel.trainDirection
];
}
}
);
onMounted(() => {
const train = drawStore.selectedGraphic as Train;
if (train) {
trainModel.copyFrom(train.saveData());
hasBorder.value = (showSelectData as never)[trainModel.hasBorder + ''];
trainDirection.value = (showSelectData as never)[trainModel.trainDirection];
}
});
function onUpdate() {
trainModel.hasBorder = JSON.parse((showSelect as never)[hasBorder.value]);
trainModel.trainDirection = (showSelect as never)[trainDirection.value];
const train = drawStore.selectedGraphic as Train;
if (train) {
drawStore.getDrawApp().updateGraphicAndRecord(train, trainModel);
}
}
</script>

View File

@ -0,0 +1,86 @@
<template>
<q-form class="q-gutter-sm">
<q-input
outlined
readonly
v-model="trainWindowModel.id"
label="id"
hint=""
/>
<q-input
outlined
label="车次窗名称"
type="textarea"
@blur="onUpdate"
v-model="trainWindowModel.code"
lazy-rules
autogrow
/>
<q-list bordered separator class="rounded-borders">
<q-item>
<q-item-section no-wrap class="q-gutter-y-sm column">
<q-item-label> 关联的区段 </q-item-label>
<div class="q-gutter-sm row">
<q-chip
v-for="item in relatedProcessPositions"
:key="item.id"
square
color="primary"
text-color="white"
>
{{ item.datas.code }}
</q-chip>
</div>
</q-item-section>
</q-item>
</q-list>
</q-form>
</template>
<script setup lang="ts">
import { TrainWindowData } from 'src/drawApp/graphics/TrainWindowInteraction';
import { Section } from 'src/graphics/section/Section';
import { TrainWindow } from 'src/graphics/trainWindow/TrainWindow';
import { useDrawStore } from 'src/stores/draw-store';
import { computed, onMounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const trainWindowModel = reactive(new TrainWindowData());
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == TrainWindow.Type) {
trainWindowModel.copyFrom(val.saveData() as TrainWindowData);
}
}
);
onMounted(() => {
const trainWindow = drawStore.selectedGraphic as TrainWindow;
if (trainWindow) {
trainWindowModel.copyFrom(trainWindow.saveData());
}
});
function onUpdate() {
const trainWindow = drawStore.selectedGraphic as TrainWindow;
if (trainWindow) {
drawStore
.getDrawApp()
.updateGraphicAndRecord(trainWindow, trainWindowModel);
}
}
const relatedProcessPositions = computed((): Section[] => {
if (
drawStore.selectedGraphic &&
drawStore.selectedGraphic.type === 'TrainWindow'
) {
return (drawStore.selectedGraphic as TrainWindow).getRelatedSections();
}
return [];
});
</script>

View File

@ -0,0 +1,149 @@
<template>
<q-form>
<q-input outlined readonly v-model="turnoutModel.id" label="id" hint="" />
<q-input
outlined
v-model.number="turnoutModel.code"
@blur="onUpdate"
label="编号"
/>
<q-select
outlined
style="margin-top: 10px"
v-model="kilometerSystem[0].coordinateSystem"
:options="CoordinateSystemOptions"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="坐标系"
></q-select>
<q-input
outlined
style="margin-top: 10px"
v-model.number="kilometerSystem[0].kilometer"
type="number"
@blur="onUpdate"
label="公里标(mm):"
/>
<q-select
outlined
style="margin-top: 10px"
v-model="kilometerSystem[1].coordinateSystem"
:options="CoordinateSystemOptions"
:map-options="true"
:emit-value="true"
@update:model-value="onUpdate"
label="坐标系2"
></q-select>
<q-input
outlined
style="margin-top: 10px"
v-model.number="kilometerSystem[1].kilometer"
type="number"
@blur="onUpdate"
label="公里标(mm):"
/>
<q-field class="q-mt-lg" outlined label="关联区段" readonly stack-label>
<template #control>
<q-chip
color="primary"
text-color="white"
v-for="code in sectionRelations"
:key="code"
square
>{{ code }}</q-chip
>
</template>
</q-field>
<q-field class="q-mt-lg" outlined label="关联道岔" readonly stack-label>
<template #control>
<q-chip
color="primary"
text-color="white"
v-for="code in turnoutRelations"
:key="code"
square
>{{ code }}</q-chip
>
</template>
</q-field>
</q-form>
</template>
<script setup lang="ts">
import { TurnoutData } from 'src/drawApp/graphics/TurnoutInteraction';
import { Section } from 'src/graphics/section/Section';
import { Turnout } from 'src/graphics/turnout/Turnout';
import { useDrawStore } from 'src/stores/draw-store';
import { computed, reactive, shallowRef, watchEffect } from 'vue';
const drawStore = useDrawStore();
const CoordinateSystemOptions = [
{ label: '车辆段', value: 'DEPOT' },
{ label: '停车场', value: 'PARKING_LOT' },
{ label: '正线', value: 'MAIN_LINE' },
{ label: '换线', value: 'TRANSFER' },
];
const turnoutModel = shallowRef(new TurnoutData());
const kilometerSystem = reactive([
{ coordinateSystem: '', kilometer: 0 },
{ coordinateSystem: '', kilometer: 0 },
]);
const sectionRelations = computed(() => {
const turnout = drawStore.selectedGraphic as Turnout;
const sectionRelations =
turnout.relationManage.getRelationsOfGraphicAndOtherType(
turnout,
Section.Type
);
return sectionRelations.map(
(relation) =>
`${relation.getRelationParam(turnout).param}: ${
relation.getOtherGraphic<Section>(turnout).datas.code
}(${relation.getOtherRelationParam(turnout).param})`
);
});
const turnoutRelations = computed(() => {
const turnout = drawStore.selectedGraphic as Turnout;
const turnoutRelations =
turnout?.relationManage.getRelationsOfGraphicAndOtherType(
turnout,
Turnout.Type
);
return turnoutRelations.map(
(relation) =>
`${relation.getRelationParam(turnout).param}: ${
relation.getOtherGraphic<Turnout>(turnout).datas.code
}(${relation.getOtherRelationParam(turnout).param})`
);
});
watchEffect(() => {
const turnout = drawStore.selectedGraphic;
if (turnout && turnout instanceof Turnout) {
turnoutModel.value = turnout.saveData();
if (turnoutModel.value.kilometerSystem.length > 0) {
kilometerSystem.forEach((ks, i) => {
ks.coordinateSystem =
turnoutModel.value.kilometerSystem[i].coordinateSystem;
ks.kilometer = turnoutModel.value.kilometerSystem[i].kilometer;
});
}
}
});
const onUpdate = () => {
const turnout = drawStore.selectedGraphic as Turnout;
turnoutModel.value.kilometerSystem = kilometerSystem.map((ks) => ({
coordinateSystem: ks.coordinateSystem,
kilometer: ks.kilometer,
}));
if (turnout) {
drawStore.getDrawApp().updateGraphicAndRecord(turnout, turnoutModel.value);
}
};
</script>

View File

@ -0,0 +1,96 @@
<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-btn-toggle
v-model="template.curve"
@update:model-value="onUpdate"
:options="[
{ label: '直线', value: false },
{ label: '曲线', value: true },
]"
/>
<q-input
v-if="template.curve"
outlined
v-model.number="template.segmentsCount"
type="number"
@blur="onUpdate"
label="曲线分段数量 *"
lazy-rules
:rules="[(val) => (val && val > 0) || '曲线分段数量必须大于0']"
/>
</q-form>
</template>
<script setup lang="ts">
import { LinkTemplate } from 'src/graphics/link/Link';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive } from 'vue';
const drawStore = useDrawStore();
const template = reactive({
lineWidth: 1,
lineColor: '#0000ff',
curve: false,
segmentsCount: 10,
});
onMounted(() => {
const type = drawStore.drawGraphicType;
if (type) {
const gt = drawStore.drawGraphicTemplate;
if (gt) {
const lt = gt as LinkTemplate;
template.lineWidth = lt.lineWidth;
template.lineColor = lt.lineColor;
template.curve = lt.curve;
template.segmentsCount = lt.segmentsCount;
}
}
});
function onUpdate() {
const gt = drawStore.drawGraphicTemplate as LinkTemplate;
if (gt) {
gt.lineWidth = template.lineWidth;
gt.lineColor = template.lineColor;
gt.curve = template.curve;
gt.segmentsCount = template.segmentsCount;
}
}
</script>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,51 @@
<template>
<q-form>
<q-select
outlined
@blur="onUpdate"
v-model="template.hasBorder"
:options="optionsDoor"
label="是否有边框"
/>
<q-select
outlined
@blur="onUpdate"
v-model="template.trainDirection"
:options="optionsDirection"
label="行驶方向"
/>
</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({
trainDirection: 'left',
hasBorder: true,
});
const optionsDoor = ['是', '否'];
const optionsDirection = ['向左', '向右'];
onMounted(() => {
const type = drawStore.drawGraphicType;
if (type) {
const gt = drawStore.drawGraphicTemplate;
if (gt) {
const lt = gt as TrainTemplate;
template.trainDirection = lt.trainDirection;
template.hasBorder = lt.hasBorder;
}
}
});
function onUpdate() {
const gt = drawStore.drawGraphicTemplate as TrainTemplate;
if (gt) {
gt.trainDirection = template.trainDirection;
gt.hasBorder = template.hasBorder;
}
}
</script>

View File

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

15
src/configs/UrlManage.ts Normal file
View File

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

1
src/css/app.scss Normal file
View File

@ -0,0 +1 @@
// app global css in SCSS form

View File

@ -0,0 +1,25 @@
// Quasar SCSS (& Sass) Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
// Check documentation for full list of Quasar variables
// Your own variables (that are declared here) and Quasar's own
// ones will be available out of the box in your .vue/.scss/.sass files
// It's highly recommended to change the default colors
// to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1976D2;
$secondary : #26A69A;
$accent : #9C27B0;
$dark : #1D1D1D;
$dark-page : #121212;
$positive : #21BA45;
$negative : #C10015;
$info : #31CCEC;
$warning : #F2C037;

View File

@ -0,0 +1,56 @@
import * as pb_1 from 'google-protobuf';
import {
AxleCounting,
IAxleCountingData,
} from 'src/graphics/axleCounting/AxleCounting';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase } from './GraphicDataBase';
import { KilometerSystem } from 'src/graphics/signal/Signal';
export class AxleCountingData
extends GraphicDataBase
implements IAxleCountingData
{
constructor(data?: graphicData.AxleCounting) {
let axleCounting;
if (!data) {
axleCounting = new graphicData.AxleCounting({
common: GraphicDataBase.defaultCommonInfo(AxleCounting.Type),
});
} else {
axleCounting = data;
}
super(axleCounting);
}
public get data(): graphicData.AxleCounting {
return this.getData<graphicData.AxleCounting>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
get kilometerSystem(): KilometerSystem {
return this.data.kilometerSystem;
}
set kilometerSystem(v: KilometerSystem) {
this.data.kilometerSystem = new graphicData.KilometerSystem(v);
}
get axleCountingRef(): graphicData.RelatedRef[] {
return this.data.axleCountingRef;
}
set axleCountingRef(points: graphicData.RelatedRef[]) {
this.data.axleCountingRef = points;
}
clone(): AxleCountingData {
return new AxleCountingData(this.data.cloneMessage());
}
copyFrom(data: AxleCountingData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: AxleCountingData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

@ -0,0 +1,153 @@
import * as pb_1 from 'google-protobuf';
import {
ChildTransform,
GraphicData,
GraphicState,
GraphicTransform,
IChildTransform,
IGraphicTransform,
} from 'src/jl-graphic';
// import { toStorageTransform } from '..';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { IPointData, Point } from 'pixi.js';
import { state } from 'src/protos/device_status';
export interface ICommonInfo {
id: string;
graphicType: string;
transform: IGraphicTransform;
childTransforms: IChildTransform[];
}
export function fromStoragePoint(p: graphicData.Point): Point {
return new Point(p.x, p.y);
}
export function toStoragePoint(p: IPointData): graphicData.Point {
return new graphicData.Point({ x: p.x, y: p.y });
}
export function fromStorageTransfrom(
transfrom: graphicData.Transform
): GraphicTransform {
return new GraphicTransform(
fromStoragePoint(transfrom.position),
fromStoragePoint(transfrom.scale),
transfrom.rotation,
fromStoragePoint(transfrom.skew)
);
}
export function toStorageTransform(
transform: GraphicTransform
): graphicData.Transform {
return new graphicData.Transform({
position: toStoragePoint(transform.position),
scale: toStoragePoint(transform.scale),
rotation: transform.rotation,
skew: toStoragePoint(transform.skew),
});
}
export interface IProtoGraphicData extends pb_1.Message {
common: ICommonInfo;
code: string;
}
export abstract class GraphicDataBase implements GraphicData {
_data: IProtoGraphicData;
constructor(data: IProtoGraphicData) {
this._data = data;
}
static defaultCommonInfo(graphicType: string): graphicData.CommonInfo {
return new graphicData.CommonInfo({
id: '',
graphicType: graphicType,
transform: new graphicData.Transform({
position: new graphicData.Point({ x: 0, y: 0 }),
scale: new graphicData.Point({ x: 1, y: 1 }),
rotation: 0,
skew: new graphicData.Point({ x: 0, y: 0 }),
}),
childTransforms: [],
});
}
getData<D extends IProtoGraphicData>(): D {
return this._data as D;
}
get id(): string {
return this._data.common.id;
}
set id(v: string) {
this._data.common.id = v;
}
get graphicType(): string {
return this._data.common.graphicType;
}
set graphicType(v: string) {
this._data.common.graphicType = v;
}
get transform(): GraphicTransform {
return GraphicTransform.from(this._data.common.transform);
}
set transform(v: GraphicTransform) {
this._data.common.transform = toStorageTransform(v);
}
get childTransforms(): ChildTransform[] | undefined {
const cts: ChildTransform[] = [];
if (this._data.common.childTransforms) {
this._data.common.childTransforms.forEach((ct) => {
cts.push(ChildTransform.from(ct));
});
}
return cts;
}
set childTransforms(v: ChildTransform[] | undefined) {
if (v) {
const cts: graphicData.ChildTransform[] = [];
v.forEach((ct) =>
cts.push(
new graphicData.ChildTransform({
...ct,
transform: toStorageTransform(ct.transform),
})
)
);
this._data.common.childTransforms = cts;
} else {
this._data.common.childTransforms = [];
}
}
clone(): GraphicData {
throw new Error('Method not implemented.');
}
copyFrom(gd: GraphicDataBase): void {
pb_1.Message.copyInto(gd._data, this._data);
}
eq(other: GraphicDataBase): boolean {
return pb_1.Message.equals(this._data, other._data);
}
}
export abstract class GraphicStateBase implements GraphicState {
_graphicType: string;
_state: pb_1.Message;
constructor(state: pb_1.Message, graphicType: string) {
this._state = state;
this._graphicType = graphicType;
}
abstract get code(): string;
abstract copyFrom(data: GraphicState): void;
abstract eq(data: GraphicState): boolean;
getState<S extends pb_1.Message>(): S {
return this._state as S;
}
get graphicType(): string {
return this._graphicType;
}
clone(): GraphicState {
throw new Error('Method not implemented.');
}
}

View File

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

View File

@ -0,0 +1,182 @@
import * as pb_1 from 'google-protobuf';
import { IPointData, DisplayObject, FederatedMouseEvent } from 'pixi.js';
import { ILinkData, Link } from 'src/graphics/link/Link';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase } from './GraphicDataBase';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import {
GraphicApp,
GraphicInteractionPlugin,
JlGraphic,
} from 'src/jl-graphic';
import {
addWayPoint,
clearWayPoint,
getWaypointRangeIndex,
PolylineEditPlugin,
removeLineWayPoint,
} from 'src/jl-graphic/plugins/GraphicEditPlugin';
export class LinkData extends GraphicDataBase implements ILinkData {
constructor(data?: graphicData.Link) {
let link;
if (!data) {
link = new graphicData.Link({
common: GraphicDataBase.defaultCommonInfo(Link.Type),
});
} else {
link = data;
}
super(link);
}
public get data(): graphicData.Link {
return this.getData<graphicData.Link>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
get curve(): boolean {
return this.data.curve;
}
set curve(v: boolean) {
this.data.curve = v;
}
get curveNumber(): number {
return this.data.curve ? 1 : 0;
}
set curveNumber(v: number) {
this.data.curve = v === 0 ? false : true;
}
get segmentsCount(): number {
return this.data.segmentsCount;
}
set segmentsCount(v: number) {
this.data.segmentsCount = 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 })
);
}
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;
}
clone(): LinkData {
return new LinkData(this.data.cloneMessage());
}
copyFrom(data: LinkData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: LinkData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}
export const addWaypointConfig: MenuItemOptions = {
name: '添加路径点',
};
export const removeWaypointConfig: MenuItemOptions = {
name: '移除路径点',
};
export const clearWaypointsConfig: MenuItemOptions = {
name: '清除所有路径点',
};
const LinkEditMenu: ContextMenu = ContextMenu.init({
name: '轨道编辑菜单',
groups: [
{
items: [addWaypointConfig, clearWaypointsConfig],
},
],
});
const EpEditMenu: ContextMenu = ContextMenu.init({
name: '轨道编辑菜单2',
groups: [
{
items: [removeWaypointConfig, clearWaypointsConfig],
},
],
});
export class DrawLinkPlugin extends GraphicInteractionPlugin<Link> {
static Name = 'link_draw_right_menu';
constructor(app: GraphicApp) {
super(DrawLinkPlugin.Name, app);
app.registerMenu(LinkEditMenu);
app.registerMenu(EpEditMenu);
}
static init(app: GraphicApp) {
return new DrawLinkPlugin(app);
}
filter(...grahpics: JlGraphic[]): Link[] | undefined {
return grahpics.filter((g) => g.type === Link.Type).map((g) => g as Link);
}
bind(g: Link): void {
g.on('_rightclick', this.onContextMenu, this);
g.on('selected', this.onSelected, this);
}
unbind(g: Link): void {
g.off('_rightclick', this.onContextMenu, this);
g.off('selected', this.onSelected, this);
}
onSelected(g: DisplayObject) {
const polylineEditPlugin = g.assistantAppendMap.get(
PolylineEditPlugin.Name
) as PolylineEditPlugin;
const link = g.getGraphic() as Link;
polylineEditPlugin.editedPoints.forEach((ep, index) => {
ep.on('rightclick', (e: FederatedMouseEvent) => {
this.app.registerMenu(EpEditMenu);
removeWaypointConfig.handler = () => {
removeLineWayPoint(link, index);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(link, false);
};
EpEditMenu.open(e.global);
});
});
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const link = target.getGraphic() as Link;
this.app.updateSelected(link);
addWaypointConfig.handler = () => {
const linePoints = link.linePoints;
const p = link.screenToLocalPoint(e.global);
const { start, end } = getWaypointRangeIndex(
linePoints,
false,
p,
link.datas.lineWidth
);
addWayPoint(link, false, start, end, p);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(link, false);
};
LinkEditMenu.open(e.global);
}
}

View File

@ -0,0 +1,70 @@
import * as pb_1 from 'google-protobuf';
import { IPointData } from 'pixi.js';
import {
IPathLineData,
PathLine,
KilometerPoint,
} from 'src/graphics/pathLine/PathLine';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase } from './GraphicDataBase';
export class PathLineData extends GraphicDataBase implements IPathLineData {
constructor(data?: graphicData.PathLine) {
let pathLine;
if (!data) {
pathLine = new graphicData.PathLine({
common: GraphicDataBase.defaultCommonInfo(PathLine.Type),
});
} else {
pathLine = data;
}
super(pathLine);
}
public get data(): graphicData.PathLine {
return this.getData<graphicData.PathLine>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = 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 })
);
}
get isUp(): boolean {
return this.data.isUp;
}
set isUp(v: boolean) {
this.data.isUp = v;
}
get kilometerPoints(): KilometerPoint[] {
return this.data.kilometerPoints;
}
set kilometerPoints(kilometerPoints: KilometerPoint[]) {
this.data.kilometerPoints = kilometerPoints.map(
(p) =>
new graphicData.KilometerPoint({
point: new graphicData.Point({ x: p.point.x, y: p.point.y }),
kilometer: p.kilometer,
stName: p.stName,
})
);
}
clone(): PathLineData {
return new PathLineData(this.data.cloneMessage());
}
copyFrom(data: PathLineData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: PathLineData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

@ -0,0 +1,307 @@
import * as pb_1 from 'google-protobuf';
import {
IPlatformData,
IPlatformState,
Platform,
} from 'src/graphics/platform/Platform';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase, GraphicStateBase } from './GraphicDataBase';
import { state } from 'src/protos/device_status';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import {
GraphicApp,
GraphicInteractionPlugin,
JlGraphic,
} from 'src/jl-graphic';
import { DisplayObject, FederatedMouseEvent } from 'pixi.js';
export class PlatformData extends GraphicDataBase implements IPlatformData {
constructor(data?: graphicData.Platform) {
let platform;
if (!data) {
platform = new graphicData.Platform({
common: GraphicDataBase.defaultCommonInfo(Platform.Type),
});
} else {
platform = data;
}
super(platform);
}
public get data(): graphicData.Platform {
return this.getData<graphicData.Platform>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
get hasdoor(): boolean {
return this.data.hasdoor;
}
set hasdoor(v: boolean) {
this.data.hasdoor = v;
}
get direction(): string {
return this.data.direction;
}
set direction(v: string) {
this.data.direction = v;
}
clone(): PlatformData {
return new PlatformData(this.data.cloneMessage());
}
copyFrom(data: PlatformData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: PlatformData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}
export class PlatformState extends GraphicStateBase implements IPlatformState {
constructor(proto?: state.Platform) {
let states;
if (proto) {
states = proto;
} else {
states = new state.Platform();
}
super(states, Platform.Type);
}
get emergstop(): boolean {
return this.states.emergstop;
}
set emergstop(v: boolean) {
this.states.emergstop = v;
}
get trainberth(): boolean {
return this.states.trainberth;
}
set trainberth(v: boolean) {
this.states.trainberth = v;
}
get close(): boolean {
return this.states.close;
}
set close(v: boolean) {
this.states.close = v;
}
get upHold(): boolean {
return this.states.upHold;
}
set upHold(v: boolean) {
this.states.upHold = v;
}
get downHold(): boolean {
return this.states.downHold;
}
set downHold(v: boolean) {
this.states.downHold = v;
}
get upOccHold(): boolean {
return this.states.upOccHold;
}
set upOccHold(v: boolean) {
this.states.upOccHold = v;
}
get downOccHold(): boolean {
return this.states.downOccHold;
}
set downOccHold(v: boolean) {
this.states.downOccHold = v;
}
get psdOpen(): boolean {
return this.states.psdOpen;
}
set psdOpen(v: boolean) {
this.states.psdOpen = v;
}
get psdCut(): boolean {
return this.states.psdCut;
}
set psdCut(v: boolean) {
this.states.psdCut = v;
}
get upSkipstop(): boolean {
return this.states.upSkipstop;
}
set upSkipstop(v: boolean) {
this.states.upSkipstop = v;
}
get downSkipstop(): boolean {
return this.states.downSkipstop;
}
set downSkipstop(v: boolean) {
this.states.downSkipstop = v;
}
get upTrainSkipstop(): boolean {
return this.states.upTrainSkipstop;
}
set upTrainSkipstop(v: boolean) {
this.states.upTrainSkipstop = v;
}
get downTrainSkipstop(): boolean {
return this.states.downTrainSkipstop;
}
set downTrainSkipstop(v: boolean) {
this.states.downTrainSkipstop = v;
}
get nextSectionRunTime(): number {
return this.states.nextSectionRunTime;
}
set nextSectionRunTime(v: number) {
this.states.nextSectionRunTime = v;
}
get nextSectionRunLevel(): number {
return this.states.nextSectionRunLevel;
}
set nextSectionRunLevel(v: number) {
this.states.nextSectionRunLevel = v;
}
get stopTime(): number {
return this.states.stopTime;
}
set stopTime(v: number) {
this.states.stopTime = v;
}
get states(): state.Platform {
return this.getState<state.Platform>();
}
clone(): PlatformState {
return new PlatformState(this.states.cloneMessage());
}
}
const holdConfig: MenuItemOptions = {
name: '扣车',
};
const removeHoldrConfig: MenuItemOptions = {
name: '取消扣车',
};
const batchHoldConfig: MenuItemOptions = {
name: '批量扣车',
};
const removeBatchHoldConfig: MenuItemOptions = {
name: '批量取消扣车',
};
const earlyDepartureConfig: MenuItemOptions = {
name: '提前发车',
};
const skipStopConfig: MenuItemOptions = {
name: '设置跳停',
};
const removeSkipStopConfig: MenuItemOptions = {
name: '取消跳停',
};
const dockTimeConfig: MenuItemOptions = {
name: '设置停站时间',
};
const operatingLevelConfig: MenuItemOptions = {
name: '设置运行等级',
};
const numberOfRegionalTrainsConfig: MenuItemOptions = {
name: '区间列车数量限制',
};
const removeNumberOfRegionalTrainsConfig: MenuItemOptions = {
name: '取消区间列车数量限制',
};
const platformMessadeConfig: MenuItemOptions = {
name: '站台详细信息',
};
const PlatformOperateMenu: ContextMenu = ContextMenu.init({
name: '站台操作菜单',
groups: [
{
items: [
holdConfig,
removeHoldrConfig,
skipStopConfig,
removeSkipStopConfig,
],
},
],
});
const dispatchPlatformOperateMenu: ContextMenu = ContextMenu.init({
name: '调度仿真站台操作菜单',
groups: [
{
items: [
holdConfig,
removeHoldrConfig,
batchHoldConfig,
removeBatchHoldConfig,
earlyDepartureConfig,
skipStopConfig,
removeSkipStopConfig,
dockTimeConfig,
operatingLevelConfig,
numberOfRegionalTrainsConfig,
removeNumberOfRegionalTrainsConfig,
platformMessadeConfig,
],
},
],
});
export class PlatformOperateInteraction extends GraphicInteractionPlugin<Platform> {
static Name = 'platform_operate_menu';
constructor(app: GraphicApp) {
super(PlatformOperateInteraction.Name, app);
app.registerMenu(PlatformOperateMenu);
}
static init(app: GraphicApp) {
return new PlatformOperateInteraction(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.selectable = true;
g.on('_rightclick', this.onContextMenu, this);
}
unbind(g: Platform): void {
g.selectable = false;
g.eventMode = 'none';
g.off('_rightclick', this.onContextMenu, this);
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const platform = target.getGraphic() as Platform;
this.app.updateSelected(platform);
holdConfig.handler = () => {
platform.states.upHold = true;
platform.states.upOccHold = true;
platform.doRepaint();
};
removeHoldrConfig.handler = () => {
platform.states.upHold = false;
platform.states.upOccHold = false;
platform.doRepaint();
};
skipStopConfig.handler = () => {
platform.states.upSkipstop = true;
platform.doRepaint();
};
removeSkipStopConfig.handler = () => {
platform.states.upSkipstop = false;
platform.doRepaint();
};
PlatformOperateMenu.open(e.global);
}
}

View File

@ -0,0 +1,197 @@
import * as pb_1 from 'google-protobuf';
import { IPolygonData, Polygon } from 'src/graphics/polygon/Polygon';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase } from './GraphicDataBase';
import { DisplayObject, FederatedMouseEvent, IPointData } from 'pixi.js';
import {
GraphicApp,
GraphicInteractionPlugin,
JlGraphic,
} from 'src/jl-graphic';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import {
PolylineEditPlugin,
clearWayPoint,
removeLineWayPoint,
} from 'src/jl-graphic/plugins/GraphicEditPlugin';
import {
addPolygonSegmentingPoint,
getWayLineIndex,
} from 'src/graphics/polygon/PolygonUtils';
export class PolygonData extends GraphicDataBase implements IPolygonData {
constructor(data?: graphicData.Polygon) {
let polygon;
if (!data) {
polygon = new graphicData.Polygon({
common: GraphicDataBase.defaultCommonInfo(Polygon.Type),
});
} else {
polygon = data;
}
super(polygon);
}
public get data(): graphicData.Polygon {
return this.getData<graphicData.Polygon>();
}
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 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(): PolygonData {
return new PolygonData(this.data.cloneMessage());
}
copyFrom(data: PolygonData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: PolygonData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}
/**
* polygon编辑菜单配置
*/
const threeSegmentingConfig: MenuItemOptions = {
name: '3等分',
};
const foreSegmentingConfig: MenuItemOptions = {
name: '4等分',
};
const fiveSegmentingConfig: MenuItemOptions = {
name: '5等分',
};
const chooseSegmentingConfig: MenuItemOptions = {
name: '细分',
subMenu: [
{
name: '内置图形',
items: [
threeSegmentingConfig,
foreSegmentingConfig,
fiveSegmentingConfig,
],
},
],
};
const removeWaypointConfig: MenuItemOptions = {
name: '移除路径点',
};
const clearWaypointsConfig: MenuItemOptions = {
name: '清除所有路径点',
};
const PolygonEditMenu: ContextMenu = ContextMenu.init({
name: '多边形边的编辑菜单',
groups: [
{
items: [chooseSegmentingConfig, clearWaypointsConfig],
},
],
});
const EpEditMenu: ContextMenu = ContextMenu.init({
name: '多边形点的编辑菜单',
groups: [
{
items: [removeWaypointConfig, clearWaypointsConfig],
},
],
});
export class DrawPolygonPlugin extends GraphicInteractionPlugin<Polygon> {
static Name = 'polygon_draw_right_menu';
constructor(app: GraphicApp) {
super(DrawPolygonPlugin.Name, app);
app.registerMenu(PolygonEditMenu);
app.registerMenu(EpEditMenu);
}
static init(app: GraphicApp) {
return new DrawPolygonPlugin(app);
}
filter(...grahpics: JlGraphic[]): Polygon[] | undefined {
return grahpics
.filter((g) => g.type === Polygon.Type)
.map((g) => g as Polygon);
}
bind(g: Polygon): void {
g.on('_rightclick', this.onContextMenu, this);
g.on('selected', this.onSelected, this);
}
unbind(g: Polygon): void {
g.off('_rightclick', this.onContextMenu, this);
g.off('selected', this.onSelected, this);
}
onSelected(g: DisplayObject) {
const polylineEditPlugin = g.assistantAppendMap.get(
PolylineEditPlugin.Name
) as PolylineEditPlugin;
const link = g.getGraphic() as Polygon;
polylineEditPlugin.editedPoints.forEach((ep, index) => {
ep.on('rightclick', (e: FederatedMouseEvent) => {
this.app.registerMenu(EpEditMenu);
removeWaypointConfig.handler = () => {
removeLineWayPoint(link, index);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(link, false);
};
EpEditMenu.open(e.global);
});
});
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const polygon = target.getGraphic() as Polygon;
this.app.updateSelected(polygon);
const linePoints = polygon.addOnePoints();
const p = polygon.screenToLocalPoint(e.global);
const { start, end } = getWayLineIndex(linePoints, p);
chooseSegmentingConfig.handler = () => {
addPolygonSegmentingPoint(polygon, start, end);
};
threeSegmentingConfig.handler = () => {
addPolygonSegmentingPoint(polygon, start, end, 3);
};
foreSegmentingConfig.handler = () => {
addPolygonSegmentingPoint(polygon, start, end, 4);
};
fiveSegmentingConfig.handler = () => {
addPolygonSegmentingPoint(polygon, start, end, 5);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(polygon, false);
};
PolygonEditMenu.open(e.global);
}
}

View File

@ -0,0 +1,75 @@
import * as pb_1 from 'google-protobuf';
import { IPointData } from 'pixi.js';
import { IRectData, Rect } from 'src/graphics/rect/Rect';
import { graphicData } from 'src/protos/stationLayoutGraphics';
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(Rect.Type),
});
} 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 radius(): number {
return this.data.radius;
}
set radius(v: number) {
this.data.radius = v;
}
clone(): RectData {
return new RectData(this.data.cloneMessage());
}
copyFrom(data: RectData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: RectData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

@ -0,0 +1,214 @@
import * as pb_1 from 'google-protobuf';
import {
IRunLineData,
RunLine,
runLineConsts,
} from 'src/graphics/runLine/RunLine';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase } from './GraphicDataBase';
import {
GraphicInteractionPlugin,
GraphicApp,
JlGraphic,
} from 'src/jl-graphic';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import { FederatedMouseEvent, DisplayObject, IPointData } from 'pixi.js';
import {
addWayPoint,
clearWayPoint,
getWaypointRangeIndex,
PolylineEditPlugin,
removeLineWayPoint,
} from 'src/jl-graphic/plugins/GraphicEditPlugin';
import { RunLineGraphicHitArea } from 'src/graphics/runLine/RunLineDrawAssistant';
export class RunLineData extends GraphicDataBase implements IRunLineData {
constructor(data?: graphicData.RunLine) {
let runLine;
if (!data) {
runLine = new graphicData.RunLine({
common: GraphicDataBase.defaultCommonInfo(RunLine.Type),
});
} else {
runLine = data;
}
super(runLine);
}
public get data(): graphicData.RunLine {
return this.getData<graphicData.RunLine>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = 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 })
);
}
get nameColor(): string {
return this.data.nameColor;
}
set nameColor(v: string) {
this.data.nameColor = v;
}
get nameBgColor(): string {
return this.data.nameBgColor;
}
set nameBgColor(v: string) {
this.data.nameBgColor = v;
}
get containSta(): string[] {
return this.data.containSta;
}
set containSta(v: string[]) {
this.data.containSta = v;
}
get linkPathLines(): string[] {
return this.data.linkPathLines;
}
set linkPathLines(v: string[]) {
this.data.linkPathLines = v;
}
get lineId(): string {
return this.data.lineId;
}
set lineId(v: string) {
this.data.lineId = v;
}
clone(): RunLineData {
return new RunLineData(this.data.cloneMessage());
}
copyFrom(data: RunLineData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: RunLineData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}
export const addWaypointConfig: MenuItemOptions = {
name: '添加路径点',
};
export const removeWaypointConfig: MenuItemOptions = {
name: '移除路径点',
};
export const clearWaypointsConfig: MenuItemOptions = {
name: '清除所有路径点',
};
const RunLineEditMenu: ContextMenu = ContextMenu.init({
name: '运行线编辑菜单',
groups: [
{
items: [addWaypointConfig, clearWaypointsConfig],
},
],
});
const EpEditMenu: ContextMenu = ContextMenu.init({
name: '运行线编辑菜单2',
groups: [
{
items: [removeWaypointConfig, clearWaypointsConfig],
},
],
});
export class DrawRunLinePlugin extends GraphicInteractionPlugin<RunLine> {
static Name = 'runline_draw_right_menu';
constructor(app: GraphicApp) {
super(DrawRunLinePlugin.Name, app);
app.registerMenu(RunLineEditMenu);
app.registerMenu(EpEditMenu);
}
static init(app: GraphicApp) {
return new DrawRunLinePlugin(app);
}
filter(...grahpics: JlGraphic[]): RunLine[] | undefined {
return grahpics
.filter((g) => g.type === RunLine.Type)
.map((g) => g as RunLine);
}
bind(g: RunLine): void {
g.on('_rightclick', this.onContextMenu, this);
g.on('selected', this.onSelected, this);
}
unbind(g: RunLine): void {
g.off('_rightclick', this.onContextMenu, this);
g.off('selected', this.onSelected, this);
}
onSelected(g: DisplayObject) {
const polylineEditPlugin = g.assistantAppendMap.get(
PolylineEditPlugin.Name
) as PolylineEditPlugin;
const runLine = g.getGraphic() as RunLine;
polylineEditPlugin.editedPoints.forEach((ep, index) => {
ep.on('rightclick', (e: FederatedMouseEvent) => {
this.app.registerMenu(EpEditMenu);
removeWaypointConfig.handler = () => {
removeLineWayPoint(runLine, index);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(runLine, false);
};
EpEditMenu.open(e.global);
});
});
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const runLine = target.getGraphic() as RunLine;
this.app.updateSelected(runLine);
addWaypointConfig.handler = () => {
const linePoints = runLine.linePoints;
const p = runLine.screenToLocalPoint(e.global);
const { start, end } = getWaypointRangeIndex(
linePoints,
false,
p,
runLineConsts.runLineWidth
);
addWayPoint(runLine, false, start, end, p);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(runLine, false);
};
RunLineEditMenu.open(e.global);
}
}
export class RunLineOperateInteraction extends GraphicInteractionPlugin<RunLine> {
static Name = 'runLine_operate_menu';
constructor(app: GraphicApp) {
super(RunLineOperateInteraction.Name, app);
app.registerMenu(EpEditMenu);
}
static init(app: GraphicApp) {
return new RunLineOperateInteraction(app);
}
filter(...grahpics: JlGraphic[]): RunLine[] | undefined {
return grahpics
.filter((g) => g.type === RunLine.Type)
.map((g) => g as RunLine);
}
bind(g: RunLine): void {
g.eventMode = 'static';
g.cursor = 'pointer';
g.lineBody.hitArea = new RunLineGraphicHitArea(g);
g.selectable = true;
}
unbind(g: RunLine): void {
g.selectable = false;
g.eventMode = 'none';
}
}

View File

@ -0,0 +1,69 @@
import * as pb_1 from 'google-protobuf';
import { GraphicDataBase } from './GraphicDataBase';
import { ISectionData, Section } from 'src/graphics/section/Section';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { IPointData } from 'pixi.js';
export class SectionData extends GraphicDataBase implements ISectionData {
constructor(data?: graphicData.Section) {
let section;
if (!data) {
section = new graphicData.Section({
common: GraphicDataBase.defaultCommonInfo(Section.Type),
});
} else {
section = data;
}
super(section);
}
public get data(): graphicData.Section {
return this.getData<graphicData.Section>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = 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 })
);
}
get paRef(): graphicData.RelatedRef {
return this.data.paRef;
}
set paRef(ref: graphicData.RelatedRef) {
this.data.paRef = ref;
}
get pbRef(): graphicData.RelatedRef {
return this.data.pbRef;
}
set pbRef(ref: graphicData.RelatedRef) {
this.data.pbRef = ref;
}
get sectionType(): graphicData.Section.SectionType {
return this.data.sectionType;
}
set sectionType(type: graphicData.Section.SectionType) {
this.data.sectionType = type;
}
get children(): string[] {
return this.data.children;
}
set children(children: string[]) {
this.data.children = children;
}
clone(): SectionData {
return new SectionData(this.data.cloneMessage());
}
copyFrom(data: SectionData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: SectionData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

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

View File

@ -0,0 +1,369 @@
import * as pb_1 from 'google-protobuf';
import {
ISignalData,
Signal,
ISignalState,
KilometerSystem,
} from 'src/graphics/signal/Signal';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase, GraphicStateBase } from './GraphicDataBase';
import {
GraphicInteractionPlugin,
GraphicApp,
JlGraphic,
} from 'src/jl-graphic';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import { FederatedMouseEvent, DisplayObject } from 'pixi.js';
import { state } from 'src/protos/device_status';
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;
}
get mirror(): boolean {
return this.data.mirror;
}
set mirror(v: boolean) {
this.data.mirror = v;
}
get kilometerSystem(): KilometerSystem {
return this.data.kilometerSystem;
}
set kilometerSystem(v: KilometerSystem) {
this.data.kilometerSystem = new graphicData.KilometerSystem(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);
}
}
export class SignalState extends GraphicStateBase implements ISignalState {
constructor(proto?: state.Signal) {
let states;
if (proto) {
states = proto;
} else {
states = new state.Signal();
}
super(states, Signal.Type);
}
get code(): string {
return this.states.id;
}
get redOpen(): boolean {
return this.states.redOpen;
}
set redOpen(v: boolean) {
this.states.redOpen = v;
}
get redFlash(): boolean {
return this.states.redFlash;
}
set redFlash(v: boolean) {
this.states.redFlash = v;
}
get greenOpen(): boolean {
return this.states.greenOpen;
}
set greenOpen(v: boolean) {
this.states.greenOpen = v;
}
get greenFlash(): boolean {
return this.states.greenFlash;
}
set greenFlash(v: boolean) {
this.states.greenFlash = v;
}
get yellowOpen(): boolean {
return this.states.yellowOpen;
}
set yellowOpen(v: boolean) {
this.states.yellowOpen = v;
}
get yellowFlash(): boolean {
return this.states.yellowFlash;
}
set yellowFlash(v: boolean) {
this.states.yellowFlash = v;
}
get whiteOpen(): boolean {
return this.states.whiteOpen;
}
set whiteOpen(v: boolean) {
this.states.whiteOpen = v;
}
get whiteFlash(): boolean {
return this.states.whiteFlash;
}
set whiteFlash(v: boolean) {
this.states.whiteFlash = v;
}
get blueOpen(): boolean {
return this.states.blueOpen;
}
set blueOpen(v: boolean) {
this.states.blueOpen = v;
}
get blueFlash(): boolean {
return this.states.blueFlash;
}
set blueFlash(v: boolean) {
this.states.blueFlash = v;
}
get fleetMode(): boolean {
return this.states.fleetMode;
}
set fleetMode(v: boolean) {
this.states.fleetMode = v;
}
get ctrlFleetMode(): boolean {
return this.states.ctrlFleetMode;
}
set ctrlFleetMode(v: boolean) {
this.states.ctrlFleetMode = v;
}
get autoMode(): boolean {
return this.states.autoMode;
}
set autoMode(v: boolean) {
this.states.autoMode = v;
}
get ctrlAutoMode(): boolean {
return this.states.ctrlAutoMode;
}
set ctrlAutoMode(v: boolean) {
this.states.ctrlAutoMode = v;
}
get extinguish(): boolean {
return this.states.extinguish;
}
set extinguish(v: boolean) {
this.states.extinguish = v;
}
get approachLock(): boolean {
return this.states.approachLock;
}
set approachLock(v: boolean) {
this.states.approachLock = v;
}
get protectRoute(): boolean {
return this.states.protectRoute;
}
set protectRoute(v: boolean) {
this.states.protectRoute = v;
}
get autoRouteDisable(): boolean {
return this.states.autoRouteDisable;
}
set autoRouteDisable(v: boolean) {
this.states.autoRouteDisable = v;
}
get callon(): boolean {
return this.states.callon;
}
set callon(v: boolean) {
this.states.callon = v;
}
get yellowYellow(): boolean {
return this.states.yellowYellow;
}
set yellowYellow(v: boolean) {
this.states.yellowYellow = v;
}
get yellowGreen(): boolean {
return this.states.yellowGreen;
}
set yellowGreen(v: boolean) {
this.states.yellowGreen = v;
}
get blocked(): boolean {
return this.states.blocked;
}
set blocked(v: boolean) {
this.states.blocked = v;
}
get lampFailure(): boolean {
return this.states.lampFailure;
}
set lampFailure(v: boolean) {
this.states.lampFailure = v;
}
get states(): state.Signal {
return this.getState<state.Signal>();
}
clone(): SignalState {
return new SignalState(this.states.cloneMessage());
}
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);
}
}
const mirrorFlipConfig: MenuItemOptions = {
name: '镜像翻转',
};
const signalCloseConfig: MenuItemOptions = {
name: '信号机关闭',
};
const signalRedFlashConfig: MenuItemOptions = {
name: '信号机红闪',
};
const signalOpenConfig: MenuItemOptions = {
name: '信号机开放',
};
const signalFleetConfig: MenuItemOptions = {
name: '连锁自动进路',
};
const humanControlConfig: MenuItemOptions = {
name: '进路交人工控',
};
const logicConfig: MenuItemOptions = {
name: '逻辑点灯',
};
const SignalEditMenu: ContextMenu = ContextMenu.init({
name: '信号机编辑菜单',
groups: [
{
items: [mirrorFlipConfig],
},
],
});
const SignalOperateMenu: ContextMenu = ContextMenu.init({
name: '信号机操作菜单',
groups: [
{
items: [
signalCloseConfig,
signalOpenConfig,
signalFleetConfig,
humanControlConfig,
logicConfig,
signalRedFlashConfig,
],
},
],
});
export class DrawSignalInteraction extends GraphicInteractionPlugin<Signal> {
static Name = 'signal_draw_right_menu';
constructor(app: GraphicApp) {
super(DrawSignalInteraction.Name, app);
app.registerMenu(SignalEditMenu);
}
static init(app: GraphicApp) {
return new DrawSignalInteraction(app);
}
filter(...grahpics: JlGraphic[]): Signal[] | undefined {
return grahpics
.filter((g) => g.type === Signal.Type)
.map((g) => g as Signal);
}
bind(g: Signal): void {
g.on('_rightclick', this.onContextMenu, this);
}
unbind(g: Signal): void {
g.off('_rightclick', this.onContextMenu, this);
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const signal = target.getGraphic() as Signal;
this.app.updateSelected(signal);
mirrorFlipConfig.handler = () => {
signal.mirror = !signal.mirror;
};
SignalEditMenu.open(e.global);
}
}
export class SignalOperateInteraction extends GraphicInteractionPlugin<Signal> {
static Name = 'signal_operate_menu';
constructor(app: GraphicApp) {
super(SignalOperateInteraction.Name, app);
app.registerMenu(SignalOperateMenu);
}
static init(app: GraphicApp) {
return new SignalOperateInteraction(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.selectable = true;
g.on('_rightclick', this.onContextMenu, this);
}
unbind(g: Signal): void {
g.selectable = false;
g.eventMode = 'none';
g.off('_rightclick', this.onContextMenu, this);
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const signal = target.getGraphic() as Signal;
this.app.updateSelected(signal);
signalCloseConfig.handler = () => {
signal.states.redOpen = true;
signal.states.greenOpen = false;
signal.doRepaint();
};
signalOpenConfig.handler = () => {
signal.states.redOpen = false;
signal.states.greenOpen = true;
signal.doRepaint();
};
signalFleetConfig.handler = () => {
signal.states.fleetMode = true;
signal.doRepaint();
};
humanControlConfig.handler = () => {
signal.states.autoRouteDisable = true;
signal.doRepaint();
};
logicConfig.handler = () => {
signal.states.extinguish = true;
signal.doRepaint();
};
signalRedFlashConfig.handler = () => {
signal.states.redFlash = true;
signal.states.redOpen = false;
signal.states.greenOpen = false;
signal.doRepaint();
};
SignalOperateMenu.open(e.global);
}
}

View File

@ -0,0 +1,178 @@
import * as pb_1 from 'google-protobuf';
import {
IStationData,
IStationState,
Station,
} from 'src/graphics/station/Station';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase, GraphicStateBase } from './GraphicDataBase';
import { state } from 'src/protos/device_status';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import {
GraphicApp,
GraphicInteractionPlugin,
JlGraphic,
} from 'src/jl-graphic';
import { DisplayObject, FederatedMouseEvent } from 'pixi.js';
import { KilometerSystem } from 'src/graphics/signal/Signal';
export class StationData extends GraphicDataBase implements IStationData {
constructor(data?: graphicData.Station) {
let station;
if (!data) {
station = new graphicData.Station({
common: GraphicDataBase.defaultCommonInfo(Station.Type),
});
} 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 kilometerSystem(): KilometerSystem {
return this.data.kilometerSystem;
}
set kilometerSystem(v: KilometerSystem) {
this.data.kilometerSystem = new graphicData.KilometerSystem(v);
}
get hasControl(): boolean {
return this.data.hasControl;
}
set hasControl(v: boolean) {
this.data.hasControl = v;
}
get concentrationStations(): boolean {
return this.data.concentrationStations;
}
set concentrationStations(v: boolean) {
this.data.concentrationStations = v;
}
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);
}
}
export class StationState extends GraphicStateBase implements IStationState {
constructor(proto?: state.Rtu) {
let states;
if (proto) {
states = proto;
} else {
states = new state.Station();
}
super(states, Station.Type);
}
get ipRtuStusDown(): boolean {
return this.states.ipRtuStusDown;
}
set ipRtuStusDown(v: boolean) {
this.states.ipRtuStusDown = v;
}
get ipRtuStusInLocalCtrl(): boolean {
return this.states.ipRtuStusInLocalCtrl;
}
set ipRtuStusInLocalCtrl(v: boolean) {
this.states.ipRtuStusInLocalCtrl = v;
}
get ipRtuStusInCentralCtrl(): boolean {
return this.states.ipRtuStusInCentralCtrl;
}
set ipRtuStusInCentralCtrl(v: boolean) {
this.states.ipRtuStusInCentralCtrl = v;
}
get ipRtuStusInEmergencyCtrl(): boolean {
return this.states.ipRtuStusInEmergencyCtrl;
}
set ipRtuStusInEmergencyCtrl(v: boolean) {
this.states.ipRtuStusInEmergencyCtrl = v;
}
get states(): state.Rtu {
return this.getState<state.Rtu>();
}
clone(): StationState {
return new StationState(this.states.cloneMessage());
}
}
const powerUnlockConfig: MenuItemOptions = {
name: '上电解锁',
};
const chainConfig: MenuItemOptions = {
name: '全站设置连锁自动触发',
};
const removeChainConfig: MenuItemOptions = {
name: '全站取消连锁自动触发',
};
const StationOperateMenu: ContextMenu = ContextMenu.init({
name: '车站操作菜单',
groups: [
{
items: [powerUnlockConfig, chainConfig, removeChainConfig],
},
],
});
export class StationOperateInteraction extends GraphicInteractionPlugin<Station> {
static Name = 'station_operate_menu';
constructor(app: GraphicApp) {
super(StationOperateInteraction.Name, app);
app.registerMenu(StationOperateMenu);
}
static init(app: GraphicApp) {
return new StationOperateInteraction(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.selectable = true;
g.on('_rightclick', this.onContextMenu, this);
}
unbind(g: Station): void {
g.selectable = false;
g.eventMode = 'none';
g.off('_rightclick', this.onContextMenu, this);
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const station = target.getGraphic() as Station;
this.app.updateSelected(station);
powerUnlockConfig.handler = () => {
station.states.ipRtuStusInLocalCtrl = true;
station.doRepaint();
};
chainConfig.handler = () => {
station.states.ipRtuStusDown = true;
station.doRepaint();
};
removeChainConfig.handler = () => {
console.log(2222);
};
StationOperateMenu.open(e.global);
}
}

View File

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

View File

@ -0,0 +1,296 @@
import * as pb_1 from 'google-protobuf';
import { ITrainData, ITrainState, Train } from 'src/graphics/train/Train';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase, GraphicStateBase } from './GraphicDataBase';
import { state } from 'src/protos/device_status';
import { train } from 'src/protos/train';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import {
GraphicApp,
GraphicInteractionPlugin,
JlGraphic,
} from 'src/jl-graphic';
import { DisplayObject, FederatedMouseEvent } from 'pixi.js';
export class TrainData extends GraphicDataBase implements ITrainData {
constructor(data?: graphicData.Train) {
let train;
if (!data) {
train = new graphicData.Train({
common: GraphicDataBase.defaultCommonInfo(Train.Type),
});
} 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;
}
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);
}
}
export class TrainState extends GraphicStateBase implements ITrainState {
constructor(proto?: StateTrain) {
let states;
if (proto) {
states = proto;
} else {
states = new StateTrain();
}
super(states, Train.Type);
}
get states(): StateTrain {
return this.getState<StateTrain>();
}
get lineId(): number {
return this.states.lineId;
}
set lineId(v: number) {
this.states.lineId = v;
}
get rtuId(): number {
return this.states.rtuId;
}
set rtuId(v: number) {
this.states.rtuId = v;
}
get window(): train.NccWindow {
if (!this.states.window) {
this.states.window = new train.NccWindow();
}
return this.states.window;
}
set window(v: train.NccWindow) {
this.states.window = v;
}
get devType(): number {
return this.states.devType;
}
set devType(v: number) {
this.states.devType = v;
}
get devName(): string {
return this.states.devName;
}
set devName(v: string) {
this.states.devName = v;
}
get id(): string {
return this.states.id;
}
set id(v: string) {
this.states.id = v;
}
get groupId(): string {
return this.states.groupId;
}
set groupId(v: string) {
this.states.groupId = v;
}
get trainId(): string {
return this.states.trainId;
}
set trainId(v: string) {
this.states.trainId = v;
}
get globalId(): string {
return this.states.globalId;
}
set globalId(v: string) {
this.states.globalId = v;
}
get destinationId(): number {
return this.states.destinationId;
}
set destinationId(v: number) {
this.states.destinationId = v;
}
get rollingStock(): number {
return this.states.rollingStock;
}
set rollingStock(v: number) {
this.states.rollingStock = v;
}
get driverId(): string {
return this.states.driverId;
}
set driverId(v: string) {
this.states.driverId = v;
}
get otpTime(): number {
return this.states.otpTime;
}
set otpTime(v: number) {
this.states.otpTime = v;
}
get mode(): state.TrainMode {
if (!this.states.mode) {
this.states.mode = new state.TrainMode();
}
return this.states.mode;
}
set mode(v: state.TrainMode) {
this.states.mode = v;
}
get arriveTime(): number {
return this.states.arriveTime;
}
set arriveTime(v: number) {
this.states.arriveTime = v;
}
get departTime(): number {
return this.states.departTime;
}
set departTime(v: number) {
this.states.departTime = v;
}
get speed(): number {
return this.states.speed;
}
set speed(v: number) {
this.states.speed = v;
}
get show(): boolean {
return this.states.show;
}
set show(v: boolean) {
this.states.show = v;
}
get type(): boolean {
return this.states.type;
}
set type(v: boolean) {
this.states.type = v;
}
get routeId(): number {
return this.states.routeId;
}
set routeId(v: number) {
this.states.routeId = v;
}
get rate(): number {
return this.states.rate;
}
set rate(v: number) {
this.states.rate = v;
}
clone(): TrainState {
return new TrainState(this.states.cloneMessage());
}
}
class StateTrain extends train.TrainInfo {
id: string;
constructor(data?: train.TrainInfo) {
super(data);
if (data?.trainIndex) {
this.id = data?.trainIndex;
} else {
this.id = '';
}
}
}
const negativeDirectionConfig: MenuItemOptions = {
name: '反方向运行',
};
const HoldTrainConfig: MenuItemOptions = {
name: '扣车',
};
const openDoorConfig: MenuItemOptions = {
name: '开门',
};
const editGroupConfig: MenuItemOptions = {
name: '修改车组号',
};
const TrainOperateMenu: ContextMenu = ContextMenu.init({
name: '列车操作菜单',
groups: [
{
items: [
negativeDirectionConfig,
HoldTrainConfig,
openDoorConfig,
editGroupConfig,
],
},
],
});
export class TrainOperateInteraction extends GraphicInteractionPlugin<Train> {
static Name = 'train_operate_menu';
constructor(app: GraphicApp) {
super(TrainOperateInteraction.Name, app);
app.registerMenu(TrainOperateMenu);
}
static init(app: GraphicApp) {
return new TrainOperateInteraction(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.selectable = true;
g.on('_rightclick', this.onContextMenu, this);
}
unbind(g: Train): void {
g.selectable = false;
g.eventMode = 'none';
g.off('_rightclick', this.onContextMenu, this);
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const train = target.getGraphic() as Train;
this.app.updateSelected(train);
negativeDirectionConfig.handler = () => {
const mode = train.states.mode;
if (!train.states.mode.ipModeTrainDirUp) {
mode.ipModeTrainDirUp = true;
mode.ipModeTrainDirDown = false;
} else if (!train.states.mode.ipModeTrainDirDown) {
mode.ipModeTrainDirUp = false;
mode.ipModeTrainDirDown = true;
}
train.chagneDirection();
};
HoldTrainConfig.handler = () => {
train.states.mode.ipModeTrainHolded =
!train.states.mode.ipModeTrainHolded;
train.chagneState();
};
openDoorConfig.handler = () => {
train.states.mode.ipModeTrainDoorOpen =
!train.states.mode.ipModeTrainDoorOpen;
train.chagneState();
};
editGroupConfig.handler = () => {
train.states.trainId = '022';
train.doRepaint();
};
TrainOperateMenu.open(e.global);
}
}

View File

@ -0,0 +1,110 @@
import * as pb_1 from 'google-protobuf';
import {
ITrainLineData,
TrainLine,
ITrainLineState,
} from 'src/graphics/trainLine/TrainLine';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { GraphicDataBase, GraphicStateBase } from './GraphicDataBase';
import { state } from 'src/protos/ws_message';
export class TrainLineData extends GraphicDataBase implements ITrainLineData {
constructor(data?: graphicData.TrainLine) {
let fan;
if (data) {
fan = data;
} else {
fan = new graphicData.TrainLine({
common: GraphicDataBase.defaultCommonInfo(TrainLine.Type),
});
}
super(fan);
}
public get data(): graphicData.TrainLine {
return this.getData<graphicData.TrainLine>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
clone(): TrainLineData {
return new TrainLineData(this.data.cloneMessage());
}
copyFrom(data: TrainLineData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: TrainLineData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}
export class TrainLineState
extends GraphicStateBase
implements ITrainLineState
{
constructor(proto?: state.WsLineNetTrainOffsetMessage) {
let states;
if (proto) {
states = proto;
} else {
states = new state.WsLineNetTrainOffsetMessage();
}
super(states, TrainLine.Type);
}
get code(): string {
return this.states.groupId;
}
get lineId(): number {
return this.states.lineId;
}
set lineId(v: number) {
this.states.lineId = v;
}
get trainIndex(): string {
return this.states.trainIndex;
}
set trainIndex(v: string) {
this.states.trainIndex = v;
}
get groupId(): string {
return this.states.groupId;
}
set groupId(v: string) {
this.states.groupId = v;
}
get show(): boolean {
return this.states.show;
}
set show(v: boolean) {
this.states.show = v;
}
get kilometerCode(): number {
return this.states.kilometerCode;
}
set kilometerCode(v: number) {
this.states.kilometerCode = v;
}
get dir(): number {
return this.states.dir;
}
set dir(v: number) {
this.states.dir = v;
}
get states(): state.WsLineNetTrainOffsetMessage {
return this.getState<state.WsLineNetTrainOffsetMessage>();
}
clone(): TrainLineState {
return new TrainLineState(this.states.cloneMessage());
}
copyFrom(data: GraphicStateBase): void {
pb_1.Message.copyInto(data._state, this._state);
}
eq(data: GraphicStateBase): boolean {
return pb_1.Message.equals(this._state, data._state);
}
}

View File

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

View File

@ -0,0 +1,93 @@
import { ITurnoutData, Turnout } from 'src/graphics/turnout/Turnout';
import * as pb_1 from 'google-protobuf';
import { GraphicDataBase } from './GraphicDataBase';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { IPointData } from 'pixi.js';
import { KilometerSystem } from 'src/graphics/signal/Signal';
function getDefaultEndPoint() {
return {
pointA: [new graphicData.Point([50, 0])],
pointB: [new graphicData.Point([-50, 0])],
pointC: [new graphicData.Point([-50, -50])],
};
}
export class TurnoutData extends GraphicDataBase implements ITurnoutData {
constructor(data?: graphicData.Turnout) {
let turnout = new graphicData.Turnout();
if (!data) {
turnout.common = GraphicDataBase.defaultCommonInfo(Turnout.Type);
const p = getDefaultEndPoint();
turnout.pointA = p.pointA;
turnout.pointB = p.pointB;
turnout.pointC = p.pointC;
} else {
turnout = data;
}
super(turnout);
}
get data(): graphicData.Turnout {
return this.getData<graphicData.Turnout>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
get pointA(): IPointData[] {
return this.data.pointA;
}
set pointA(v: IPointData[]) {
this.data.pointA = v.map((p) => new graphicData.Point({ x: p.x, y: p.y }));
}
get pointB(): IPointData[] {
return this.data.pointB;
}
set pointB(v: IPointData[]) {
this.data.pointB = v.map((p) => new graphicData.Point({ x: p.x, y: p.y }));
}
get pointC(): IPointData[] {
return this.data.pointC;
}
set pointC(v: IPointData[]) {
this.data.pointC = v.map((p) => new graphicData.Point({ x: p.x, y: p.y }));
}
get paRef(): graphicData.RelatedRef {
return this.data.paRef;
}
set paRef(ref: graphicData.RelatedRef) {
this.data.paRef = ref;
}
get pbRef(): graphicData.RelatedRef {
return this.data.pbRef;
}
set pbRef(ref: graphicData.RelatedRef) {
this.data.pbRef = ref;
}
get pcRef(): graphicData.RelatedRef {
return this.data.pcRef;
}
set pcRef(ref: graphicData.RelatedRef) {
this.data.pcRef = ref;
}
get kilometerSystem(): KilometerSystem[] {
return this.data.kilometerSystem;
}
set kilometerSystem(value: KilometerSystem[]) {
this.data.kilometerSystem = value.map(
(v) => new graphicData.KilometerSystem(v)
);
}
clone(): TurnoutData {
return new TurnoutData(this.data.cloneMessage());
}
copyFrom(data: TurnoutData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: TurnoutData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

393
src/drawApp/index.ts Normal file
View File

@ -0,0 +1,393 @@
import { fromUint8Array, toUint8Array } from 'js-base64';
import { IPointData, Point } from 'pixi.js';
import { IscsFan } from 'src/graphics/iscs-fan/IscsFan';
import { Link } from 'src/graphics/link/Link';
import { LinkDraw } from 'src/graphics/link/LinkDrawAssistant';
import { Train, TrainTemplate } from 'src/graphics/train/Train';
import { TrainDraw } from 'src/graphics/train/TrainDrawAssistant';
import { Signal, SignalTemplate } from 'src/graphics/signal/Signal';
import { SignalDraw } from 'src/graphics/signal/SignalDrawAssistant';
import {
CombinationKey,
GraphicApp,
GraphicData,
GraphicTransform,
JlDrawApp,
KeyListener,
} from 'src/jl-graphic';
import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu';
import { MenuItemOptions } from 'src/jl-graphic/ui/Menu';
import { IscsFanData } from './graphics/IscsFanInteraction';
import { LinkData } from './graphics/LinkInteraction';
import { TrainData, TrainState } from './graphics/TrainInteraction';
import {
SignalData,
DrawSignalInteraction,
SignalState,
} from './graphics/SignalInteraction';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { Rect, RectTemplate } from 'src/graphics/rect/Rect';
import { RectDraw } from 'src/graphics/rect/RectDrawAssistant';
import { RectData } from './graphics/RectInteraction';
import { Platform, PlatformTemplate } from 'src/graphics/platform/Platform';
import { PlatformData, PlatformState } from './graphics/PlatformInteraction';
import { PlatformDraw } from 'src/graphics/platform/PlatformDrawAssistant';
import { Station, StationTemplate } from 'src/graphics/station/Station';
import { StationDraw } from 'src/graphics/station/StationDrawAssistant';
import { StationData, StationState } from './graphics/StationInteraction';
import {
StationLine,
StationLineTemplate,
} from 'src/graphics/stationLine/StationLine';
import { StationLineDraw } from 'src/graphics/stationLine/StationLineDrawAssistant';
import { StationLineData } from './graphics/StationLineInteraction';
import {
ItrainLineTemplate,
TrainLine,
} from 'src/graphics/trainLine/TrainLine';
import { TrainLineDraw } from 'src/graphics/trainLine/TrainLineAssistant';
import { TrainLineData } from './graphics/TrainLineInteraction';
import {
OneClickGenerateDraw,
OneClickGenerateTemplate,
} from 'src/graphics/trainWindow/oneClickDrawAssistant';
import {
TrainWindow,
TrainWindowTemplate,
} from 'src/graphics/trainWindow/TrainWindow';
import { TrainWindowDraw } from 'src/graphics/trainWindow/TrainWindowDrawAssistant';
import { TrainWindowData } from './graphics/TrainWindowInteraction';
import {
AxleCounting,
AxleCountingTemplate,
} from 'src/graphics/axleCounting/AxleCounting';
import { AxleCountingDraw } from 'src/graphics/axleCounting/AxleCountingDrawAssistant';
import { AxleCountingData } from './graphics/AxleCountingInteraction';
import { Turnout, TurnoutTemplate } from 'src/graphics/turnout/Turnout';
import { TurnoutDraw } from 'src/graphics/turnout/TurnoutDrawAssistant';
import { TurnoutData } from './graphics/TurnoutInteraction';
import { RunLine, RunLineTemplate } from 'src/graphics/runLine/RunLine';
import { RunLineDraw } from 'src/graphics/runLine/RunLineDrawAssistant';
import { RunLineData, DrawRunLinePlugin } from './graphics/RunLineInteraction';
import { saveDraft, getDraft } from 'src/api/DraftApi';
import { useDrawStore } from 'src/stores/draw-store';
import { successNotify, errorNotify } from '../utils/CommonNotify';
import { Section, SectionTemplate } from 'src/graphics/section/Section';
import { SectionDraw } from 'src/graphics/section/SectionDrawAssistant';
import { SectionData } from './graphics/SectionInteraction';
import { PathLine, PathLineTemplate } from 'src/graphics/pathLine/PathLine';
import { PathLineDraw } from 'src/graphics/pathLine/PathLineDrawAssistant';
import { PathLineData } from './graphics/PathLineInteraction';
import { toStorageTransform } from './graphics/GraphicDataBase';
import { SeparatorDraw } from 'src/graphics/separator/SeparatorDrawAssistant';
import { Separator, SeparatorTemplate } from 'src/graphics/separator/Separator';
import { SeparatorData } from './graphics/SeparatorInteraction';
// export function fromStoragePoint(p: graphicData.Point): Point {
// return new Point(p.x, p.y);
// }
// export function toStoragePoint(p: IPointData): graphicData.Point {
// return new graphicData.Point({ x: p.x, y: p.y });
// }
// export function fromStorageTransfrom(
// transfrom: graphicData.Transform
// ): GraphicTransform {
// return new GraphicTransform(
// fromStoragePoint(transfrom.position),
// fromStoragePoint(transfrom.scale),
// transfrom.rotation,
// fromStoragePoint(transfrom.skew)
// );
// }
// export function toStorageTransform(
// transform: GraphicTransform
// ): graphicData.Transform {
// return new graphicData.Transform({
// position: toStoragePoint(transform.position),
// scale: toStoragePoint(transform.scale),
// rotation: transform.rotation,
// skew: toStoragePoint(transform.skew),
// });
// }
const UndoOptions: MenuItemOptions = {
name: '撤销',
};
const RedoOptions: MenuItemOptions = {
name: '重做',
};
const SelectAllOptions: MenuItemOptions = {
name: '全选',
};
export const DefaultCanvasMenu = new ContextMenu({
name: '绘制-画布菜单',
groups: [
{
items: [UndoOptions, RedoOptions],
},
{
items: [SelectAllOptions],
},
],
});
let drawApp: JlDrawApp | null = null;
export function getDrawApp(): JlDrawApp | null {
return drawApp;
}
export function destroyDrawApp(): void {
if (drawApp) {
drawApp.destroy();
drawApp = null;
}
}
export function initDrawApp(dom: HTMLElement): JlDrawApp {
drawApp = new JlDrawApp(dom);
const app = drawApp;
//根据草稿图类型加载绘图工具
const draftType = useDrawStore().$state.draftType;
let drawAssistants: (
| PlatformDraw
| StationDraw
| SignalDraw
| TurnoutDraw
| RunLineDraw
| SectionDraw
| StationLineDraw
| RectDraw
| TrainLineDraw
| PathLineDraw
| TrainWindowDraw
| TrainDraw
| OneClickGenerateDraw
| AxleCountingDraw
| SeparatorDraw
)[] = [];
if (draftType === 'Line') {
drawAssistants = [
new PlatformDraw(
app,
new PlatformTemplate(new PlatformData(), new PlatformState())
),
new StationDraw(
app,
new StationTemplate(new StationData(), new StationState())
),
new SignalDraw(
app,
new SignalTemplate(new SignalData(), new SignalState())
),
new TrainDraw(app, new TrainTemplate(new TrainData(), new TrainState())),
new SectionDraw(app, new SectionTemplate(new SectionData())),
new TurnoutDraw(app, new TurnoutTemplate(new TurnoutData())),
new TrainWindowDraw(app, new TrainWindowTemplate(new TrainWindowData())),
new OneClickGenerateDraw(app, new OneClickGenerateTemplate()),
new AxleCountingDraw(
app,
new AxleCountingTemplate(new AxleCountingData())
),
new SeparatorDraw(app, new SeparatorTemplate(new SeparatorData())),
];
DrawSignalInteraction.init(app);
} else {
drawAssistants = [
new StationLineDraw(app, new StationLineTemplate(new StationLineData())),
new RectDraw(app, new RectTemplate(new RectData())),
new RunLineDraw(app, new RunLineTemplate(new RunLineData())),
new TrainLineDraw(app, new ItrainLineTemplate(new TrainLineData())),
new PathLineDraw(app, new PathLineTemplate(new PathLineData())),
];
DrawRunLinePlugin.init(app);
}
app.setOptions({ drawAssistants: drawAssistants });
// 画布右键菜单
app.registerMenu(DefaultCanvasMenu);
app.canvas.on('_rightclick', (e) => {
if (app._drawing) return;
UndoOptions.disabled = !app.opRecord.hasUndo;
RedoOptions.disabled = !app.opRecord.hasRedo;
UndoOptions.handler = () => {
app.opRecord.undo();
};
RedoOptions.handler = () => {
app.opRecord.redo();
};
SelectAllOptions.handler = () => {
app.selectAllGraphics();
};
DefaultCanvasMenu.open(e.global);
});
app.addKeyboardListener(
new KeyListener({
value: 'KeyS',
global: true,
combinations: [CombinationKey.Ctrl],
onPress: () => {
saveDrawToServer(app);
},
})
);
return drawApp;
}
export function saveDrawToServer(app: JlDrawApp) {
const base64 = saveDrawDatas(app);
const drawStore = useDrawStore();
const id = drawStore.draftId;
if (!id) {
return;
}
saveDraft(id as number, { proto: base64 })
.then(() => {
successNotify('保存数据成功!');
})
.catch((err) => {
errorNotify(err.message, err);
});
}
// const StorageKey = 'graphic-storage';
export function saveDrawDatas(app: JlDrawApp) {
const storage = new graphicData.RtssGraphicStorage();
const canvasData = app.canvas.saveData();
storage.canvas = new graphicData.Canvas({
...canvasData,
viewportTransform: toStorageTransform(canvasData.viewportTransform),
});
const graphics = app.queryStore.getAllGraphics();
graphics.forEach((g) => {
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 (Train.Type === g.type) {
const trainData = (g as Train).saveData();
storage.train.push((trainData as TrainData).data);
} else if (Turnout.Type === g.type) {
const turnoutData = (g as Turnout).saveData();
storage.turnouts.push((turnoutData as TurnoutData).data);
} else if (Signal.Type === g.type) {
const signalData = (g as Signal).saveData();
storage.signals.push((signalData as SignalData).data);
} else if (RunLine.Type === g.type) {
const runLineData = (g as RunLine).saveData();
storage.runLines.push((runLineData as RunLineData).data);
} else if (Section.Type === g.type) {
const sectionData = (g as Section).saveData();
storage.section.push((sectionData as SectionData).data);
} else if (StationLine.Type === g.type) {
const stationLineData = (g as StationLine).saveData();
storage.stationLines.push((stationLineData as StationLineData).data);
} else if (TrainLine.Type === g.type) {
const trainLineData = (g as TrainLine).saveData();
storage.trainLines.push((trainLineData as TrainLineData).data);
} else if (PathLine.Type === g.type) {
const pathLineData = (g as PathLine).saveData();
storage.pathLines.push((pathLineData as PathLineData).data);
} else if (TrainWindow.Type === g.type) {
const trainWindowData = (g as TrainWindow).saveData();
storage.trainWindows.push((trainWindowData as TrainWindowData).data);
} else if (AxleCounting.Type === g.type) {
const axleCountingData = (g as AxleCounting).saveData();
storage.axleCountings.push((axleCountingData as AxleCountingData).data);
} else if (Separator.Type === g.type) {
const separatorData = (g as Separator).saveData();
storage.separators.push((separatorData as SeparatorData).data);
}
});
const base64 = fromUint8Array(storage.serialize());
console.log('保存数据', storage);
// localStorage.setItem(StorageKey, base64);
return base64;
}
export async function loadDrawDatas(app: GraphicApp) {
// localStorage.removeItem(StorageKey);
// const base64 = localStorage.getItem(StorageKey);
// console.log('加载数据', base64);
const drawStore = useDrawStore();
const id = drawStore.draftId;
if (!id) {
return;
}
const { proto: base64 } = await getDraft(id);
if (base64) {
const storage = graphicData.RtssGraphicStorage.deserialize(
toUint8Array(base64)
);
console.log('加载数据', storage);
app.updateCanvas(storage.canvas);
const datas: GraphicData[] = [];
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.train.forEach((train) => {
datas.push(new TrainData(train));
});
storage.turnouts.forEach((turnout) => {
datas.push(new TurnoutData(turnout));
});
storage.signals.forEach((signal) => {
datas.push(new SignalData(signal));
});
storage.runLines.forEach((runLine) => {
datas.push(new RunLineData(runLine));
});
storage.section.forEach((section) => {
datas.push(new SectionData(section));
});
storage.stationLines.forEach((stationLine) => {
datas.push(new StationLineData(stationLine));
});
storage.trainLines.forEach((trainLine) => {
datas.push(new TrainLineData(trainLine));
});
storage.pathLines.forEach((pathLine) => {
datas.push(new PathLineData(pathLine));
});
storage.trainWindows.forEach((trainWindow) => {
datas.push(new TrainWindowData(trainWindow));
});
storage.axleCountings.forEach((axleCounting) => {
datas.push(new AxleCountingData(axleCounting));
});
storage.separators.forEach((separator) => {
datas.push(new SeparatorData(separator));
});
app.loadGraphic(datas);
} else {
app.loadGraphic([]);
}
}

137
src/drawApp/lineApp.ts Normal file
View File

@ -0,0 +1,137 @@
import {
GraphicApp,
GraphicData,
StompCli,
AppWsMsgBroker,
GraphicState,
} from 'src/jl-graphic';
import { TrainData, TrainState } from './graphics/TrainInteraction';
import { TrainTemplate } from 'src/graphics/train/Train';
import {
SignalData,
SignalOperateInteraction,
SignalState,
} from './graphics/SignalInteraction';
import { SignalTemplate, Signal } from 'src/graphics/signal/Signal';
import {
PlatformData,
PlatformOperateInteraction,
PlatformState,
} from './graphics/PlatformInteraction';
import { PlatformTemplate, Platform } from 'src/graphics/platform/Platform';
import { StationData, StationState } from './graphics/StationInteraction';
import { StationTemplate } from 'src/graphics/station/Station';
import { TurnoutData } from './graphics/TurnoutInteraction';
import { TurnoutTemplate } from 'src/graphics/turnout/Turnout';
import { SectionData } from './graphics/SectionInteraction';
import { SectionTemplate } from 'src/graphics/section/Section';
import { getPublishMapInfoByLineId } from 'src/api/PublishApi';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { useLineStore } from 'src/stores/line-store';
import { toUint8Array } from 'js-base64';
import { getWebsocketUrl } from 'src/configs/UrlManage';
import { getJwtToken } from 'src/configs/TokenManage';
import { state } from 'src/protos/ws_message';
let lineApp: GraphicApp | null = null;
let msgBroker: AppWsMsgBroker | null = null;
export function getLineApp(): GraphicApp | null {
return lineApp;
}
export function destroyLineApp(): void {
if (lineApp) {
lineApp.destroy();
lineApp = null;
}
if (msgBroker) {
msgBroker.close();
}
}
export function initLineApp(dom: HTMLElement): GraphicApp {
lineApp = new GraphicApp(dom);
const graphicTemplate = [
new TrainTemplate(new TrainData(), new TrainState()),
new SignalTemplate(new SignalData(), new SignalState()),
new PlatformTemplate(new PlatformData(), new PlatformState()),
new StationTemplate(new StationData(), new StationState()),
new TurnoutTemplate(new TurnoutData()),
new SectionTemplate(new SectionData()),
];
lineApp.registerGraphicTemplates(...graphicTemplate);
lineApp.setOptions({
mouseToolOptions: {
boxSelect: false,
viewportDrag: true,
wheelZoom: true,
},
interactiveTypeOptions: {
interactiveGraphicTypeIncludes: [Signal.Type, Platform.Type],
},
});
SignalOperateInteraction.init(lineApp);
PlatformOperateInteraction.init(lineApp);
return lineApp;
}
export async function loadLineDatas(app: GraphicApp) {
const lineStore = useLineStore();
const lineId = lineStore.lineId;
if (!lineId) {
return;
}
const { proto: base64, name: lineName } = await getPublishMapInfoByLineId(
lineId,
'line'
);
lineStore.setLineName(lineName);
if (base64) {
const storage = graphicData.RtssGraphicStorage.deserialize(
toUint8Array(base64)
);
console.log('加载数据', storage);
app.updateCanvas(storage.canvas);
const datas: GraphicData[] = [];
storage.Platforms.forEach((platform) => {
const g = new PlatformData(platform);
datas.push(g);
});
storage.stations.forEach((station) => {
datas.push(new StationData(station));
});
storage.train.forEach((train) => {
datas.push(new TrainData(train));
});
storage.turnouts.forEach((turnout) => {
datas.push(new TurnoutData(turnout));
});
storage.signals.forEach((signal) => {
datas.push(new SignalData(signal));
});
storage.section.forEach((section) => {
datas.push(new SectionData(section));
});
await app.loadGraphic(datas);
StompCli.new({
wsUrl: `${getWebsocketUrl()}`,
token: getJwtToken() as string,
});
msgBroker = new AppWsMsgBroker(app);
const states: GraphicState[] = [];
msgBroker.subscribe({
destination: `/queue/line/${lineId}/device`,
messageConverter: (message: Uint8Array) => {
const storage = state.WsLineMessage.deserialize(message);
storage.signal.forEach((item) => {
states.push(new SignalState(item));
});
return states;
},
});
} else {
app.loadGraphic([]);
}
}

132
src/drawApp/lineNetApp.ts Normal file
View File

@ -0,0 +1,132 @@
import {
GraphicApp,
GraphicData,
StompCli,
AppWsMsgBroker,
GraphicState,
} from 'src/jl-graphic';
import { getPublishLineNet } from 'src/api/PublishApi';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { state } from 'src/protos/ws_message';
import { RunLine, RunLineTemplate } from 'src/graphics/runLine/RunLine';
import {
RunLineData,
RunLineOperateInteraction,
} from './graphics/RunLineInteraction';
import { PathLineTemplate, PathLine } from 'src/graphics/pathLine/PathLine';
import { PathLineData } from './graphics/PathLineInteraction';
import {
StationLineTemplate,
StationLine,
} from 'src/graphics/stationLine/StationLine';
import { StationLineData } from './graphics/StationLineInteraction';
import { ItrainLineTemplate } from 'src/graphics/trainLine/TrainLine';
import { TrainLineData, TrainLineState } from './graphics/TrainLineInteraction';
import { RectTemplate } from 'src/graphics/rect/Rect';
import { RectData } from './graphics/RectInteraction';
import { useLineNetStore } from 'src/stores/line-net-store';
import { toUint8Array } from 'js-base64';
import { getWebsocketUrl } from 'src/configs/UrlManage';
import { getJwtToken } from 'src/configs/TokenManage';
let lineNetApp: GraphicApp | null = null;
let msgBroker: AppWsMsgBroker | null = null;
export function getLineNetApp(): GraphicApp | null {
return lineNetApp;
}
export function destroyLineNetApp(): void {
if (lineNetApp) {
lineNetApp.destroy();
lineNetApp = null;
}
if (msgBroker) {
msgBroker.close();
}
}
export function initLineNetApp(dom: HTMLElement): GraphicApp {
lineNetApp = new GraphicApp(dom);
const graphicTemplate = [
new RunLineTemplate(new RunLineData()),
new PathLineTemplate(new PathLineData()),
new StationLineTemplate(new StationLineData()),
new ItrainLineTemplate(new TrainLineData(), new TrainLineState()),
new RectTemplate(new RectData()),
];
lineNetApp.registerGraphicTemplates(...graphicTemplate);
lineNetApp.setOptions({
mouseToolOptions: {
boxSelect: false,
viewportDrag: true,
wheelZoom: true,
},
interactiveTypeOptions: {
interactiveGraphicTypeIncludes: [RunLine.Type, StationLine.Type],
},
});
RunLineOperateInteraction.init(lineNetApp);
return lineNetApp;
}
export async function loadLineNetDatas(app: GraphicApp) {
const lineNetStore = useLineNetStore();
const { proto: base64, name: lineNetName } = await getPublishLineNet();
lineNetStore.setLineNetName(lineNetName);
if (base64) {
const storage = graphicData.RtssGraphicStorage.deserialize(
toUint8Array(base64)
);
console.log('加载数据', storage);
app.updateCanvas(storage.canvas);
const datas: GraphicData[] = [];
storage.runLines.forEach((runLine) => {
const g = new RunLineData(runLine);
datas.push(g);
});
storage.pathLines.forEach((pathLine) => {
const g = new PathLineData(pathLine);
datas.push(g);
});
storage.stationLines.forEach((stationLine) => {
const g = new StationLineData(stationLine);
datas.push(g);
});
storage.trainLines.forEach((trainLine) => {
const g = new TrainLineData(trainLine);
datas.push(g);
});
storage.rects.forEach((rect) => {
const g = new RectData(rect);
datas.push(g);
});
await app.loadGraphic(datas);
const pathLineList = app.queryStore.queryByType(PathLine.Type);
pathLineList.forEach((pathLine) => {
pathLine.visible = false;
});
StompCli.new({
wsUrl: `${getWebsocketUrl()}`,
token: getJwtToken() as string,
});
msgBroker = new AppWsMsgBroker(app);
const states: GraphicState[] = [];
msgBroker.subscribe({
destination: '/queue/lineNet',
messageConverter: (message: Uint8Array) => {
const storage = state.WsLineNetMessage.deserialize(message);
console.log(storage, 'storage');
storage.offset.forEach((item) => {
states.push(new TrainLineState(item));
});
return states;
},
});
} else {
app.loadGraphic([]);
}
}

9
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
/* eslint-disable */
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: string;
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
VUE_ROUTER_BASE: string | undefined;
}
}

View File

@ -0,0 +1,116 @@
import { Graphics } from 'pixi.js';
import { calculateMirrorPoint } from 'src/jl-graphic';
import { graphicData } from 'src/protos/stationLayoutGraphics';
import { Turnout, TurnoutPort } from './turnout/Turnout';
import { Section, SectionPort } from './section/Section';
import { TrainWindow } from './trainWindow/TrainWindow';
import { AxleCounting } from './axleCounting/AxleCounting';
/**
*
* @param polygon
* @param x x坐标
* @param y y坐标
* @param length
* @param radius
* @param lineWidth 线
* @param mirror ()
*/
export function drawArrow(
polygon: Graphics,
x: number,
y: number,
length: number,
radius: number,
lineWidth: number,
mirror: boolean
) {
const trianglAcme = { x, y };
let triangleP1 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y + Math.cos(Math.PI / 6) * radius,
};
let triangleP2 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y - Math.cos(Math.PI / 6) * radius,
};
let lineP1 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y + lineWidth / 2,
};
let lineP2 = {
x: x - length,
y: y + lineWidth / 2,
};
let lineP3 = {
x: x - length,
y: y - lineWidth / 2,
};
let lineP4 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y - lineWidth / 2,
};
if (mirror) {
triangleP1 = calculateMirrorPoint(trianglAcme, triangleP1);
triangleP2 = calculateMirrorPoint(trianglAcme, triangleP2);
lineP1 = calculateMirrorPoint(trianglAcme, lineP1);
lineP2 = calculateMirrorPoint(trianglAcme, lineP2);
lineP3 = calculateMirrorPoint(trianglAcme, lineP3);
lineP4 = calculateMirrorPoint(trianglAcme, lineP4);
}
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
);
}
export function createRelatedRefProto(
type: string,
id: string,
port?: TurnoutPort | SectionPort
) {
const typeMap = new Map([
[Section.Type, graphicData.RelatedRef.DeviceType.Section],
[Turnout.Type, graphicData.RelatedRef.DeviceType.Turnout],
[TrainWindow.Type, graphicData.RelatedRef.DeviceType.TrainWindow],
[AxleCounting.Type, graphicData.RelatedRef.DeviceType.AxleCounting],
]);
const protoDeviceType = typeMap.get(type);
if (protoDeviceType === undefined) throw Error(`输入的type有误: ${type}`);
const protoData = new graphicData.RelatedRef({
deviceType: protoDeviceType,
id,
});
if (port) {
if (port === 'A')
protoData.devicePort = graphicData.RelatedRef.DevicePort.A;
if (port === 'B')
protoData.devicePort = graphicData.RelatedRef.DevicePort.B;
if (port === 'C')
protoData.devicePort = graphicData.RelatedRef.DevicePort.C;
}
return protoData;
}
export function protoPort2Data(port: graphicData.RelatedRef.DevicePort) {
if (port === graphicData.RelatedRef.DevicePort.A) return 'A';
if (port === graphicData.RelatedRef.DevicePort.B) return 'B';
if (port === graphicData.RelatedRef.DevicePort.C) return 'C';
}
export interface IRelatedRefData {
deviceType: graphicData.RelatedRef.DeviceType; //关联的设备类型
id: string; //关联的设备ID
devicePort: graphicData.RelatedRef.DevicePort; //关联的设备端口
}

View File

@ -0,0 +1,116 @@
import { Color, Container, Graphics } from 'pixi.js';
import {
GraphicData,
GraphicRelationParam,
JlGraphic,
JlGraphicTemplate,
VectorText,
} from 'src/jl-graphic';
import { IRelatedRefData, protoPort2Data } from '../CommonGraphics';
import { KilometerSystem } from '../signal/Signal';
export interface IAxleCountingData extends GraphicData {
get code(): string; // 编号
set code(v: string);
get kilometerSystem(): KilometerSystem;
set kilometerSystem(v: KilometerSystem);
get axleCountingRef(): IRelatedRefData[]; //关联的设备
set axleCountingRef(ref: IRelatedRefData[]);
clone(): IAxleCountingData;
copyFrom(data: IAxleCountingData): void;
eq(other: IAxleCountingData): boolean;
}
export const AxleCountingConsts = {
radius: 6,
borderWidth: 1,
circleColorBlue: '0x08F80D',
codeColor: '0xF48815',
codeFontSize: 22,
codeOffsetY: 30,
kilometerCodeColor: '0xFFFFFF',
kilometerCodeFontSize: 14,
kilometerCodeOffsetY: 95,
offsetSection: 50,
};
class TwoCircleGraphic extends Container {
circleA: Graphics = new Graphics();
circleB: Graphics = new Graphics();
line: Graphics = new Graphics();
constructor() {
super();
this.addChild(this.circleA);
this.addChild(this.circleB);
this.addChild(this.line);
}
draw(): void {
this.drawCircle(this.circleA);
this.drawCircle(this.circleB);
this.circleA.position.set(-12, 0);
this.circleB.position.set(12, 0);
this.line.clear();
this.line.lineStyle(1, new Color(AxleCountingConsts.circleColorBlue));
this.line.moveTo(-24, 0);
this.line.lineTo(24, 0);
}
drawCircle(circle: Graphics): void {
circle.clear();
circle.lineStyle(
AxleCountingConsts.borderWidth,
new Color(AxleCountingConsts.circleColorBlue)
);
circle.beginFill(AxleCountingConsts.circleColorBlue, 1);
circle.drawCircle(0, 0, AxleCountingConsts.radius);
circle.endFill;
}
clear(): void {
this.circleA.clear();
this.circleB.clear();
}
}
export class AxleCounting extends JlGraphic {
static Type = 'AxleCounting';
twoCircle: TwoCircleGraphic = new TwoCircleGraphic();
kilometerGraph: VectorText = new VectorText(''); //公里标
direction: number;
constructor(direction: number) {
super(AxleCounting.Type);
this.addChild(this.twoCircle);
this.addChild(this.kilometerGraph);
this.kilometerGraph.name = 'kilometer';
this.direction = direction;
}
get datas(): IAxleCountingData {
return this.getDatas<IAxleCountingData>();
}
doRepaint(): void {
this.twoCircle.draw();
}
loadRelations(): void {
if (this.datas.axleCountingRef.length) {
this.datas.axleCountingRef.forEach((device) => {
this.relationManage.addRelation(
new GraphicRelationParam(this, 'A'),
new GraphicRelationParam(
this.queryStore.queryById(device.id),
protoPort2Data(device.devicePort)
)
);
});
}
}
}
export class AxleCountingTemplate extends JlGraphicTemplate<AxleCounting> {
constructor(dataTemplate: IAxleCountingData) {
super(AxleCounting.Type, {
dataTemplate,
});
}
new(): AxleCounting {
const axleCounting = new AxleCounting(1);
axleCounting.loadData(this.datas);
return axleCounting;
}
}

View File

@ -0,0 +1,394 @@
import { FederatedPointerEvent, IPoint, Point } from 'pixi.js';
import {
AbsorbableLine,
AbsorbablePosition,
GraphicDrawAssistant,
GraphicIdGenerator,
GraphicInteractionPlugin,
JlDrawApp,
JlGraphic,
distance2,
} from 'src/jl-graphic';
import {
IAxleCountingData,
AxleCounting,
AxleCountingTemplate,
AxleCountingConsts,
} from './AxleCounting';
import { Section, SectionPort, SectionType } from '../section/Section';
import { Turnout, TurnoutPort } from '../turnout/Turnout';
import { IRelatedRefData, createRelatedRefProto } from '../CommonGraphics';
import { Signal } from '../signal/Signal';
export interface IAxleCountingDrawOptions {
newData: () => IAxleCountingData;
}
enum DevicePort {
A = 0,
B = 1,
C = 2,
}
enum DevicePortN {
A,
B,
C,
}
export class AxleCountingDraw extends GraphicDrawAssistant<
AxleCountingTemplate,
IAxleCountingData
> {
codeGraph: AxleCounting;
constructor(app: JlDrawApp, template: AxleCountingTemplate) {
super(app, template, 'sym_o_circle', '不展示');
this.codeGraph = this.graphicTemplate.new();
this.container.addChild(this.codeGraph);
AxleCountingInteraction.init(app);
}
bind(): void {
super.bind();
this.codeGraph.loadData(this.graphicTemplate.datas);
this.codeGraph.doRepaint();
}
clearCache(): void {
//this.codeGraph.destroy();
}
onLeftDown(e: FederatedPointerEvent): void {
this.container.position.copyFrom(this.toCanvasCoordinates(e.global));
this.createAndStore(true);
}
redraw(p: Point): void {
this.container.position.copyFrom(p);
}
prepareData(data: IAxleCountingData): boolean {
data.transform = this.container.saveTransform();
return true;
}
draw(
ps: IPoint,
direction: number,
graphic: Section | Turnout,
port: TurnoutPort | SectionPort,
map: Map<string, number>,
reftype: string,
refGraphic: Section | Turnout,
refPort: TurnoutPort | SectionPort
) {
if (
graphic.type == 'Turnout' &&
reftype == 'Turnout' &&
port == TurnoutPort.B &&
refPort == TurnoutPort.B
) {
const points = (graphic as Turnout).getPortPoints();
const portPs = graphic.localToCanvasPoints(points[1][0])[0];
let hasSingle = false;
const singles = this.app.queryStore.queryByType<Signal>(Signal.Type);
singles.forEach((single) => {
if (distance2(portPs, single.position) < 50) {
hasSingle = true;
}
});
if (!hasSingle) {
map.set(`${graphic.id}-${port}`, 1);
map.set(`${refGraphic.id}-${refPort}`, 1);
return;
}
}
if (
!map.has(`${refGraphic.id}-${refPort}`) &&
!map.has(`${graphic.id}-${port}`)
) {
map.set(`${graphic.id}-${port}`, 1);
map.set(`${refGraphic.id}-${refPort}`, 1);
const refData1 = createRelatedRefProto(reftype, refGraphic.id, refPort);
const refData2 = createRelatedRefProto(graphic.type, graphic.id, port);
const axleCounting = new AxleCounting(direction);
axleCounting.loadData(this.graphicTemplate.datas);
if (graphic.type == 'Turnout') {
axleCounting.position.set(ps.x, ps.y);
} else {
axleCounting.position.set(
ps.x,
ps.y - AxleCountingConsts.offsetSection * direction
);
}
axleCounting.id = GraphicIdGenerator.next();
axleCounting.datas.axleCountingRef = [refData2, refData1];
axleCounting.datas.code = `${graphic.datas.code}-${port}+${refGraphic.datas.code}-${refPort}`;
this.storeGraphic(axleCounting);
axleCounting.loadRelations();
}
}
drawAdd(
ps: IPoint,
direction: number,
graphic: Section | Turnout,
port: TurnoutPort | SectionPort,
map: Map<string, number>
) {
if (!map.has(`${graphic.id}-${port}`)) {
map.set(`${graphic.id}-${port}`, 1);
const refData = createRelatedRefProto(graphic.type, graphic.id, port);
const axleCounting = new AxleCounting(direction);
axleCounting.loadData(this.graphicTemplate.datas);
if (graphic.type == 'Turnout') {
axleCounting.position.set(ps.x, ps.y);
} else {
axleCounting.position.set(
ps.x,
ps.y - AxleCountingConsts.offsetSection * direction
);
}
axleCounting.id = GraphicIdGenerator.next();
axleCounting.datas.axleCountingRef = [refData];
axleCounting.datas.code = `${graphic.datas.code}-${port}`;
this.storeGraphic(axleCounting);
axleCounting.loadRelations();
}
}
oneGenerates(height: Point) {
const map = new Map();
const axleCountings = this.app.queryStore.queryByType<AxleCounting>(
AxleCounting.Type
);
const axleCountingRefs: IRelatedRefData[] = [];
axleCountings.forEach((axleCounting) => {
axleCountingRefs.push(...axleCounting.datas.axleCountingRef);
});
axleCountingRefs.forEach((axleCountingRef) => {
map.set(
`${axleCountingRef.id}-${DevicePortN[axleCountingRef.devicePort]}`,
1
);
});
//由区段生成计轴--区段和区段
const sections = this.app.queryStore
.queryByType<Section>(Section.Type)
.filter((section) => {
return (
section.relationManage.getRelationsOfGraphicAndOtherType(
section,
AxleCounting.Type
).length < 2
);
});
sections.forEach((section) => {
const sectionRelations =
section.relationManage.getRelationsOfGraphic(section);
const ps = section.localToCanvasPoint(section.getStartPoint());
const pe = section.localToCanvasPoint(section.getEndPoint());
sectionRelations.forEach((relation) => {
const port = relation.getRelationParam(section).param;
const refDevice = relation.getOtherGraphic<Section>(section);
const refDevicePort = relation.getOtherRelationParam(section).param;
let direction = 1;
let axleCountingPs = ps;
if (port == 'B') {
axleCountingPs = pe;
}
if (axleCountingPs.y > height.y) {
direction = -1;
}
if (
section.datas.sectionType === SectionType.Logic ||
section.datas.children.includes(refDevice.id)
) {
return;
}
if (refDevice.type == Section.Type || refDevice.type == Turnout.Type)
this.draw(
axleCountingPs,
direction,
section,
port,
map,
refDevice.type,
refDevice,
refDevicePort
);
});
});
//由区段生成计轴--单独区段
sections.forEach((section) => {
const axleCountingRelations =
section.relationManage.getRelationsOfGraphicAndOtherType(
section,
AxleCounting.Type
);
const ps = section.localToCanvasPoint(section.getStartPoint());
const pe = section.localToCanvasPoint(section.getEndPoint());
if (axleCountingRelations.length < 2) {
axleCountingRelations.forEach((relation) => {
const port = relation.getRelationParam(section).param;
let addPort = SectionPort.A;
let direction = 1;
let axleCountingPs = ps;
if (port == 'A') {
axleCountingPs = pe;
addPort = SectionPort.B;
}
if (axleCountingPs.y > height.y) {
direction = -1;
}
if (axleCountingPs.y > height.y) {
direction = -1;
}
this.drawAdd(axleCountingPs, direction, section, addPort, map);
});
}
});
//由道岔生成计轴--道岔和道岔
const turnouts = this.app.queryStore
.queryByType<Turnout>(Turnout.Type)
.filter((turnout) => {
return (
turnout.relationManage.getRelationsOfGraphicAndOtherType(
turnout,
AxleCounting.Type
).length < 3
);
});
turnouts.forEach((turnout) => {
const points = turnout.getPortPoints();
//道岔关联的道岔
const turnoutRelations =
turnout.relationManage.getRelationsOfGraphicAndOtherType(
turnout,
Turnout.Type
);
turnoutRelations.forEach((relation) => {
const port = relation.getRelationParam(turnout).param;
const portIndex = DevicePort[port] as unknown as number;
const refTurnout = relation.getOtherGraphic<Turnout>(turnout);
const refTurnoutPort = relation.getOtherRelationParam(turnout).param;
const portPs = turnout.localToCanvasPoints(...points[portIndex])[
points[portIndex].length - 1
];
this.draw(
portPs,
1,
turnout,
port,
map,
Turnout.Type,
refTurnout,
refTurnoutPort
);
});
});
//由道岔生成计轴--单独道岔
turnouts.forEach((turnout) => {
const axleCountingRelations =
turnout.relationManage.getRelationsOfGraphicAndOtherType(
turnout,
AxleCounting.Type
);
const points = turnout.getPortPoints();
const turnoutPort = [TurnoutPort.A, TurnoutPort.B, TurnoutPort.C];
if (axleCountingRelations.length == 1) {
const port = axleCountingRelations[0].getRelationParam(turnout).param;
const otherPort = turnoutPort.filter((p) => {
return p !== port && p;
});
otherPort.forEach((port) => {
const portIndex = DevicePort[port] as unknown as number;
const axleCountingPs1 = turnout.localToCanvasPoints(
...points[portIndex]
)[points[portIndex].length - 1];
this.drawAdd(axleCountingPs1, 1, turnout, port, map);
});
} else if (axleCountingRelations.length == 2) {
const port = axleCountingRelations.map(
(item) => item.getRelationParam(turnout).param
);
const otherPort = turnoutPort.filter((p) => {
return p !== port[0] && p !== port[1] && p;
});
if (otherPort.length) {
const portIndex = DevicePort[otherPort[0]] as unknown as number;
const axleCountingPs1 = turnout.localToCanvasPoints(
...points[portIndex]
)[points[portIndex].length - 1];
this.drawAdd(axleCountingPs1, 1, turnout, otherPort[0], map);
}
} else if (axleCountingRelations.length == 0) {
const axleCountingPsA = turnout.localToCanvasPoints(points[0][0])[0];
const axleCountingPsC = turnout.localToCanvasPoints(points[2][0])[0];
this.drawAdd(axleCountingPsA, 1, turnout, TurnoutPort.A, map);
this.drawAdd(axleCountingPsC, 1, turnout, TurnoutPort.C, map);
}
});
}
}
function buildAbsorbablePositions(
axleCounting: AxleCounting
): AbsorbablePosition[] {
const aps: AbsorbablePosition[] = [];
const axleCountings = axleCounting.queryStore.queryByType<AxleCounting>(
AxleCounting.Type
);
const { width } = axleCounting.getGraphicApp().canvas;
axleCountings.forEach((other) => {
if (other.id == axleCounting.id) {
return;
}
const ps = other.datas.transform.position;
const xs = new AbsorbableLine({ x: 0, y: ps.y }, { x: width, y: ps.y });
aps.push(xs);
});
return aps;
}
export class AxleCountingInteraction extends GraphicInteractionPlugin<AxleCounting> {
static Name = 'AxleCounting_transform';
constructor(app: JlDrawApp) {
super(AxleCountingInteraction.Name, app);
}
static init(app: JlDrawApp) {
return new AxleCountingInteraction(app);
}
filter(...grahpics: JlGraphic[]): AxleCounting[] | undefined {
return grahpics
.filter((g) => g.type === AxleCounting.Type)
.map((g) => g as AxleCounting);
}
bind(g: AxleCounting): void {
g.eventMode = 'static';
g.cursor = 'pointer';
g.scalable = true;
g.rotatable = true;
g.kilometerGraph.eventMode = 'static';
g.kilometerGraph.cursor = 'pointer';
g.kilometerGraph.draggable = true;
g.kilometerGraph.selectable = true;
g.kilometerGraph.transformSave = true;
g.on('selected', this.onSelected, this);
}
unbind(g: AxleCounting): void {
g.eventMode = 'none';
g.scalable = false;
g.rotatable = false;
g.kilometerGraph.eventMode = 'none';
g.kilometerGraph.draggable = false;
g.kilometerGraph.selectable = false;
g.kilometerGraph.transformSave = false;
g.off('selected', this.onSelected, this);
}
onSelected(): void {
const AxleCounting = this.app.selectedGraphics[0] as AxleCounting;
this.app.setOptions({
absorbablePositions: buildAbsorbablePositions(AxleCounting),
});
}
}

View File

@ -0,0 +1,125 @@
import {
GraphicAnimation,
GraphicData,
JlGraphic,
JlGraphicTemplate,
} from 'src/jl-graphic';
import ISCS_FAN_Assets from './iscs-fan-spritesheet.png';
import ISCS_FAN_JSON from './iscs-fan-data.json';
import { Assets, Sprite, Spritesheet, Texture } from 'pixi.js';
interface FanTextures {
border: Texture;
blue: Texture;
gray: Texture;
green: Texture;
red: Texture;
yellow: Texture;
}
export interface IIscsFanData extends GraphicData {
get code(): string;
set code(v: string);
}
export class IscsFan extends JlGraphic {
static Type = 'IscsFan';
_border: Sprite;
_fan: Sprite;
fanTextures: FanTextures;
__state = 0;
constructor(fanTextures: FanTextures) {
super(IscsFan.Type);
this.fanTextures = fanTextures;
this._border = new Sprite();
this._border.texture = this.fanTextures.border;
this._border.anchor.set(0.5);
this._fan = new Sprite();
this._fan.texture = this.fanTextures.gray;
this._fan.anchor.set(0.5);
this.addChild(this._border);
this.addChild(this._fan);
}
doRepaint(): void {
if (this.__state === 0) {
// 停止
this.stopFanRun();
this._fan.rotation = 0;
this._fan.texture = this.fanTextures.gray;
} else if (this.__state === 1) {
// 正常运行
this._fan.texture = this.fanTextures.green;
// 动画
this.initFanRun();
} else if (this.__state === 2) {
// 报警运行
this._fan.texture = this.fanTextures.yellow;
// 动画
this.initFanRun();
} else if (this.__state === 3) {
// 故障
this.stopFanRun();
this._fan.rotation = 0;
this._fan.texture = this.fanTextures.red;
} else if (this.__state === 4) {
// 通信故障
// 停止
this.stopFanRun();
this._fan.rotation = 0;
this._fan.texture = this.fanTextures.blue;
}
}
initFanRun() {
// 动画
const name = 'fan_run';
let fanRun = this.animation(name);
if (!fanRun) {
fanRun = GraphicAnimation.init({
name: 'fan_run',
run: (dt: number) => {
this._fan.angle = (this._fan.angle + dt) % 360;
},
});
this.addAnimation(fanRun);
}
const speed = Math.round(Math.random() * 10) + 1;
fanRun.xSpeed = speed;
fanRun.resume();
}
stopFanRun() {
const name = 'fan_run';
const fanRun = this.animation(name);
if (fanRun) {
fanRun.pause();
}
}
}
export class IscsFanTemplate extends JlGraphicTemplate<IscsFan> {
fanTextures?: FanTextures;
constructor() {
super(IscsFan.Type, {});
}
new(): IscsFan {
if (this.fanTextures) {
return new IscsFan(this.fanTextures);
}
throw new Error('资源未加载/加载失败');
}
async loadAssets(): Promise<FanTextures> {
const texture = await Assets.load(ISCS_FAN_Assets);
const iscsFanSheet = new Spritesheet(texture, ISCS_FAN_JSON);
const result = await iscsFanSheet.parse();
this.fanTextures = {
border: result['fan-border.png'],
blue: result['fan-blue.png'],
gray: result['fan-gray.png'],
green: result['fan-green.png'],
red: result['fan-red.png'],
yellow: result['fan-yellow.png'],
};
return this.fanTextures as FanTextures;
}
}

View File

@ -0,0 +1,77 @@
import { FederatedMouseEvent, Point } from 'pixi.js';
import {
GraphicDrawAssistant,
GraphicInteractionPlugin,
JlDrawApp,
JlGraphic,
} from 'src/jl-graphic';
import { IIscsFanData, IscsFan, IscsFanTemplate } from './IscsFan';
export class IscsFanDraw extends GraphicDrawAssistant<
IscsFanTemplate,
IIscsFanData
> {
_iscsFan: IscsFan | null = null;
constructor(app: JlDrawApp) {
const template = new IscsFanTemplate();
super(app, template, 'svguse:../drawIcon.svg#icon-fans', '风机');
IscsFanInteraction.init(app);
}
bind(): void {
super.bind();
if (!this._iscsFan) {
this._iscsFan = this.graphicTemplate.new();
this.container.addChild(this._iscsFan);
}
}
public get iscsFan(): IscsFan {
if (!this._iscsFan) {
throw new Error('风机绘制逻辑异常');
}
return this._iscsFan;
}
redraw(cp: Point): void {
this.iscsFan.position.copyFrom(cp);
}
onLeftUp(e: FederatedMouseEvent): void {
this.iscsFan.position.copyFrom(this.toCanvasCoordinates(e.global));
this.createAndStore(false);
}
prepareData(data: IIscsFanData): boolean {
data.transform = this.iscsFan.saveTransform();
return true;
}
onEsc(): void {
this.finish();
}
}
export class IscsFanInteraction extends GraphicInteractionPlugin<IscsFan> {
static Name = 'iscs_fan_transform';
constructor(app: JlDrawApp) {
super(IscsFanInteraction.Name, app);
}
static init(app: JlDrawApp) {
return new IscsFanInteraction(app);
}
filter(...grahpics: JlGraphic[]): IscsFan[] | undefined {
return grahpics
.filter((g) => g.type === IscsFan.Type)
.map((g) => g as IscsFan);
}
bind(g: IscsFan): void {
g.eventMode = 'static';
g.cursor = 'pointer';
g.scalable = true;
g.rotatable = true;
}
unbind(g: IscsFan): void {
g.eventMode = 'none';
g.scalable = false;
g.rotatable = false;
}
}

View File

@ -0,0 +1,66 @@
{"frames": {
"fan-blue.png":
{
"frame": {"x":0,"y":0,"w":41,"h":41},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":41,"h":41},
"sourceSize": {"w":41,"h":41},
"anchor": {"x":0.5,"y":0.5}
},
"fan-border.png":
{
"frame": {"x":41,"y":0,"w":41,"h":41},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":41,"h":41},
"sourceSize": {"w":41,"h":41},
"anchor": {"x":0.5,"y":0.5}
},
"fan-gray.png":
{
"frame": {"x":82,"y":0,"w":41,"h":41},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":41,"h":41},
"sourceSize": {"w":41,"h":41},
"anchor": {"x":0.5,"y":0.5}
},
"fan-green.png":
{
"frame": {"x":123,"y":0,"w":41,"h":41},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":41,"h":41},
"sourceSize": {"w":41,"h":41},
"anchor": {"x":0.5,"y":0.5}
},
"fan-red.png":
{
"frame": {"x":164,"y":0,"w":41,"h":41},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":41,"h":41},
"sourceSize": {"w":41,"h":41},
"anchor": {"x":0.5,"y":0.5}
},
"fan-yellow.png":
{
"frame": {"x":205,"y":0,"w":41,"h":41},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":41,"h":41},
"sourceSize": {"w":41,"h":41},
"anchor": {"x":0.5,"y":0.5}
}},
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "1.1",
"image": "test-iscs-fan.png",
"format": "RGBA8888",
"size": {"w":246,"h":41},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:e7620bd2d73cc0b3e2deea9704e7eefc:f129a1d9e4b9ba57720b3861c22b155b:eb2d421f7759984b7713aa4aa5354134$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

102
src/graphics/link/Link.ts Normal file
View File

@ -0,0 +1,102 @@
import { Color, Graphics, IPointData } from 'pixi.js';
import {
GraphicData,
JlGraphic,
JlGraphicTemplate,
convertToBezierParams,
} from 'src/jl-graphic';
import { ILineGraphic } from 'src/jl-graphic/plugins/GraphicEditPlugin';
export interface ILinkData extends GraphicData {
get code(): string; // 编号
set code(v: string);
get curve(): boolean; // 是否曲线
set curve(v: boolean);
get segmentsCount(): number; // 曲线分段数
set segmentsCount(v: number);
get points(): IPointData[]; // 线坐标点
set points(points: IPointData[]);
get lineWidth(): number; // 线宽
set lineWidth(v: number);
get lineColor(): string; // 线色
set lineColor(v: string);
clone(): ILinkData;
copyFrom(data: ILinkData): void;
eq(other: ILinkData): boolean;
}
export class Link extends JlGraphic implements ILineGraphic {
static Type = 'Link';
lineGraphic: Graphics;
constructor() {
super(Link.Type);
this.lineGraphic = new Graphics();
this.addChild(this.lineGraphic);
}
get datas(): ILinkData {
return this.getDatas<ILinkData>();
}
doRepaint(): void {
if (this.datas.points.length < 2) {
throw new Error('Link坐标数据异常');
}
this.lineGraphic.clear();
this.lineGraphic.lineStyle(
this.datas.lineWidth,
new Color(this.datas.lineColor)
);
if (this.datas.curve) {
// 曲线
const bps = convertToBezierParams(this.datas.points);
bps.forEach((bp) => {
this.lineGraphic.drawBezierCurve(
bp.p1,
bp.p2,
bp.cp1,
bp.cp2,
this.datas.segmentsCount
);
});
} else {
// 直线
const start = this.getStartPoint();
this.lineGraphic.moveTo(start.x, start.y);
for (let i = 0; i < this.datas.points.length; i++) {
const p = this.datas.points[i];
this.lineGraphic.lineTo(p.x, p.y);
}
}
}
get linePoints(): IPointData[] {
return this.datas.points;
}
set linePoints(points: IPointData[]) {
const old = this.datas.clone();
old.points = points;
this.updateData(old);
}
getStartPoint(): IPointData {
return this.datas.points[0];
}
getEndPoint(): IPointData {
return this.datas.points[this.datas.points.length - 1];
}
}
export class LinkTemplate extends JlGraphicTemplate<Link> {
curve: boolean;
lineWidth: number;
lineColor: string;
segmentsCount: number;
constructor() {
super(Link.Type, {});
this.lineWidth = 2;
this.lineColor = '#000000';
this.curve = false;
this.segmentsCount = 10;
}
new(): Link {
return new Link();
}
}

View File

@ -0,0 +1,336 @@
import {
Color,
DisplayObject,
FederatedMouseEvent,
FederatedPointerEvent,
Graphics,
IHitArea,
Point,
} from 'pixi.js';
import {
AbsorbablePosition,
DraggablePoint,
GraphicApp,
GraphicDrawAssistant,
GraphicInteractionPlugin,
GraphicTransformEvent,
JlDrawApp,
JlGraphic,
KeyListener,
calculateMirrorPoint,
convertToBezierParams,
linePoint,
pointPolygon,
} from 'src/jl-graphic';
import AbsorbablePoint, {
AbsorbableCircle,
} from 'src/jl-graphic/graphic/AbsorbablePosition';
import {
BezierCurveEditPlugin,
ILineGraphic,
PolylineEditPlugin,
} from 'src/jl-graphic/plugins/GraphicEditPlugin';
import { ILinkData, Link, LinkTemplate } from './Link';
export interface ILinkDrawOptions {
newData: () => ILinkData;
}
export class LinkDraw extends GraphicDrawAssistant<LinkTemplate, ILinkData> {
points: Point[] = [];
graphic: Graphics = new Graphics();
// 快捷切曲线绘制
keyqListener: KeyListener = new KeyListener({
value: 'KeyQ',
global: true,
onPress: () => {
if (this.points.length == 0) {
this.graphicTemplate.curve = true;
}
},
});
// 快捷切直线绘制
keyzListener: KeyListener = new KeyListener({
value: 'KeyZ',
global: true,
onPress: () => {
if (this.points.length == 0) {
this.graphicTemplate.curve = false;
}
},
});
constructor(app: JlDrawApp) {
super(app, new LinkTemplate(), 'sym_o_horizontal_rule', '轨道Link');
this.container.addChild(this.graphic);
this.graphicTemplate.curve = true;
LinkPointsEditPlugin.init(app);
}
bind(): void {
super.bind();
this.app.addKeyboardListener(this.keyqListener, this.keyzListener);
}
unbind(): void {
super.unbind();
this.app.removeKeyboardListener(this.keyqListener, this.keyzListener);
}
clearCache(): void {
this.points = [];
this.graphic.clear();
}
onRightClick(): void {
this.createAndStore(true);
}
onLeftDown(e: FederatedPointerEvent): void {
const { x, y } = this.toCanvasCoordinates(e.global);
const p = new Point(x, y);
if (this.graphicTemplate.curve) {
if (this.points.length == 0) {
this.points.push(p);
} else {
this.points.push(p, p.clone());
}
} else {
this.points.push(p);
}
}
onLeftUp(e: FederatedMouseEvent): void {
const template = this.graphicTemplate;
if (template.curve) {
const mp = this.toCanvasCoordinates(e.global);
if (this.points.length == 1) {
this.points.push(new Point(mp.x, mp.y));
} else if (this.points.length > 1) {
const cp2 = this.points[this.points.length - 2];
const p = this.points[this.points.length - 1];
cp2.copyFrom(calculateMirrorPoint(p, mp));
this.points.push(mp);
}
}
}
redraw(p: Point): void {
if (this.points.length < 1) return;
this.graphic.clear();
const template = this.graphicTemplate;
this.graphic.lineStyle(template.lineWidth, new Color(template.lineColor));
const ps = [...this.points];
if (template.curve) {
// 曲线
if (ps.length == 1) {
this.graphic.moveTo(ps[0].x, ps[0].y);
this.graphic.lineTo(p.x, p.y);
} else {
if ((ps.length + 1) % 3 == 0) {
ps.push(p.clone(), p.clone());
} else {
const cp = ps[ps.length - 2];
const p1 = ps[ps.length - 1];
const mp = calculateMirrorPoint(p1, p);
cp.copyFrom(mp);
}
const bps = convertToBezierParams(ps);
bps.forEach((bp) =>
this.graphic.drawBezierCurve(
bp.p1,
bp.p2,
bp.cp1,
bp.cp2,
template.segmentsCount
)
);
}
} else {
ps.push(p);
// 直线
this.graphic.moveTo(ps[0].x, ps[0].y);
for (let i = 1; i < ps.length; i++) {
const p = ps[i];
this.graphic.lineTo(p.x, p.y);
}
}
}
prepareData(data: ILinkData): boolean {
const template = this.graphicTemplate;
if (
(!template.curve && this.points.length < 2) ||
(template.curve && this.points.length < 4)
) {
console.log('Link绘制因点不够取消绘制');
return false;
}
if (template.curve) {
this.points.pop();
}
data.curve = template.curve;
data.segmentsCount = template.segmentsCount;
data.points = this.points;
data.lineWidth = template.lineWidth;
data.lineColor = template.lineColor;
data.segmentsCount = template.segmentsCount;
return true;
}
}
export class LinkGraphicHitArea implements IHitArea {
link: Link;
constructor(link: Link) {
this.link = link;
}
contains(x: number, y: number): boolean {
const p = new Point(x, y);
if (this.link.datas.curve) {
// 曲线
const bps = convertToBezierParams(this.link.datas.points);
for (let i = 0; i < bps.length; i++) {
const bp = bps[i];
if (
pointPolygon(
p,
[bp.p1, bp.cp1, bp.cp2, bp.p2],
this.link.datas.lineWidth
)
) {
return true;
}
}
} else {
// 直线
for (let i = 1; i < this.link.datas.points.length; i++) {
const p1 = this.link.datas.points[i - 1];
const p2 = this.link.datas.points[i];
if (linePoint(p1, p2, p, this.link.datas.lineWidth)) {
return true;
}
}
}
return false;
}
}
/**
*
* @param link
* @returns
*/
function buildAbsorbablePositions(link: Link): AbsorbablePosition[] {
const aps: AbsorbablePosition[] = [];
const links = link.queryStore.queryByType<Link>(Link.Type);
links.forEach((other) => {
if (other.id == link.id) {
return;
}
const apa = new AbsorbablePoint(
other.localToCanvasPoint(other.getStartPoint())
);
const apb = new AbsorbablePoint(
other.localToCanvasPoint(other.getEndPoint())
);
aps.push(apa, apb);
});
aps.push(new AbsorbableCircle(new Point(450, 410), 30));
return aps;
}
/**
*
* @param g
* @param dp
* @param index
*/
function onEditPointCreate(
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),
});
}
});
}
}
/**
* link路径编辑
*/
export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
static Name = 'LinkPointsDrag';
constructor(app: GraphicApp) {
super(LinkPointsEditPlugin.Name, app);
}
static init(app: GraphicApp): LinkPointsEditPlugin {
return new LinkPointsEditPlugin(app);
}
filter(...grahpics: JlGraphic[]): Link[] | undefined {
return grahpics.filter((g) => g.type == Link.Type) as Link[];
}
bind(g: Link): void {
g.lineGraphic.eventMode = 'static';
g.lineGraphic.cursor = 'pointer';
g.lineGraphic.hitArea = new LinkGraphicHitArea(g);
g.on('selected', this.onSelected, this);
g.on('unselected', this.onUnselected, this);
}
unbind(g: Link): void {
g.off('selected', this.onSelected, this);
g.off('unselected', this.onUnselected, this);
}
onSelected(g: DisplayObject): void {
const link = g as Link;
let lep;
if (link.datas.curve) {
// 曲线
lep = link.getAssistantAppend<BezierCurveEditPlugin>(
BezierCurveEditPlugin.Name
);
if (!lep) {
lep = new BezierCurveEditPlugin(link, {
onEditPointCreate,
});
link.addAssistantAppend(lep);
}
} else {
// 直线
lep = link.getAssistantAppend<PolylineEditPlugin>(
PolylineEditPlugin.Name
);
if (!lep) {
lep = new PolylineEditPlugin(link, { onEditPointCreate });
link.addAssistantAppend(lep);
}
}
lep.showAll();
}
onUnselected(g: DisplayObject): void {
const link = g as Link;
let lep;
if (link.datas.curve) {
// 曲线
lep = link.getAssistantAppend<BezierCurveEditPlugin>(
BezierCurveEditPlugin.Name
);
} else {
// 直线
lep = link.getAssistantAppend<PolylineEditPlugin>(
PolylineEditPlugin.Name
);
}
if (lep) {
lep.hideAll();
}
}
}

View File

@ -0,0 +1,159 @@
import { JlGraphic, GraphicData, JlGraphicTemplate } from 'src/jl-graphic';
import { Graphics, IPointData, Point } from 'pixi.js';
import { RunLine } from '../runLine/RunLine';
import { getDrawApp } from 'src/drawApp';
// calculateDistanceFromPointToLine
import { calculateFootPointFromPointToLine, distance } from 'src/jl-graphic';
import { StationLine } from '../stationLine/StationLine';
export interface KilometerPoint {
get point(): IPointData;
set point(point: IPointData);
get kilometer(): number;
set kilometer(kilometer: number);
get stName(): string;
set stName(stName: string);
}
export interface IPathLineData extends GraphicData {
get code(): string;
set code(v: string);
get points(): IPointData[]; // 线坐标点
set points(points: IPointData[]);
get isUp(): boolean;
set isUp(v: boolean);
get kilometerPoints(): KilometerPoint[];
set kilometerPoints(kilometerPoints: KilometerPoint[]);
clone(): IPathLineData;
copyFrom(data: IPathLineData): void;
eq(other: IPathLineData): boolean;
}
export const pathLineConsts = {
pathLineWidth: 1,
pathLineColor: '0Xff0000',
};
export class PathLine extends JlGraphic {
static Type = 'PathLine';
pathLine: Graphics = new Graphics();
constructor() {
super(PathLine.Type);
this.addChild(this.pathLine);
}
get datas(): IPathLineData {
return this.getDatas<IPathLineData>();
}
doRepaint(): void {
if (this.datas.points.length < 2) {
throw new Error('RunLine坐标数据异常');
}
this.pathLine.clear();
this.pathLine.lineStyle({
width: pathLineConsts.pathLineWidth,
color: pathLineConsts.pathLineColor,
});
const start = this.getStartPoint();
this.pathLine.moveTo(start.x, start.y);
for (let i = 0; i < this.datas.points.length; i++) {
const p = this.datas.points[i];
this.pathLine.lineTo(p.x, p.y);
}
}
get linePoints(): IPointData[] {
return this.datas.points;
}
set linePoints(points: IPointData[]) {
const old = this.datas.clone();
old.points = points;
this.updateData(old);
}
getStartPoint(): IPointData {
return this.datas.points[0];
}
getEndPoint(): IPointData {
return this.datas.points[this.datas.points.length - 1];
}
onDelete(): void {
super.onDelete();
const pathLineId = this.datas.id;
const app = getDrawApp();
if (!app) return;
const runLineList = app.queryStore.queryByType(RunLine.Type) as RunLine[];
runLineList.find((runLine) => {
if (runLine.datas.linkPathLines.includes(pathLineId)) {
const list = [...runLine.datas.linkPathLines];
const index = list.findIndex((item) => item === pathLineId);
list.splice(index, 1);
runLine.datas.linkPathLines = list;
return true;
}
});
}
/**
* stationLine在轨迹线上的垂足距离stationLine与轨迹点距离线
* @param stas stationLine列表
*/
generatePathLineKilometerPoints(stas: string[]) {
const kilometerPoints: KilometerPoint[] = [];
stas.forEach((stasId) => {
const sta = this.queryStore.queryById(stasId) as StationLine;
let fp: IPointData | null = null;
const sp = sta.position;
let minDistance = Number.MAX_SAFE_INTEGER;
this.datas.points.forEach((p, index) => {
if (index) {
const prep = this.datas.points[index - 1];
const fpn = calculateFootPointFromPointToLine(prep, p, sp);
const distanceS = distance(prep.x, prep.y, p.x, p.y);
const fln = distance(fpn.x, fpn.y, sp.x, sp.y);
const distanceP = distance(p.x, p.y, sp.x, sp.y);
if (
distance(fpn.x, fpn.y, prep.x, prep.y) <= distanceS &&
distance(fpn.x, fpn.y, p.x, p.y) <= distanceS &&
fln <= minDistance
) {
minDistance = fln;
fp = fpn;
}
if (distanceP < minDistance) {
if (distanceP < minDistance) {
minDistance = distanceP;
fp = p;
}
}
} else {
const distanceP = distance(p.x, p.y, sp.x, sp.y);
if (distanceP < minDistance) {
minDistance = distanceP;
fp = p;
}
}
});
if (sta.datas.code === '延平门') {
console.log(fp);
}
if (fp) {
kilometerPoints.push({
point: new Point(fp?.['x'], fp?.['y']),
kilometer: 0,
stName: sta.datas.code,
});
}
});
this.datas.kilometerPoints = kilometerPoints;
}
}
export class PathLineTemplate extends JlGraphicTemplate<PathLine> {
pathLineColor: string;
pathLineWidth: number;
constructor(dataTemplate: IPathLineData) {
super(PathLine.Type, { dataTemplate });
this.pathLineColor = pathLineConsts.pathLineColor;
this.pathLineWidth = pathLineConsts.pathLineWidth;
}
new(): PathLine {
return new PathLine();
}
}

View File

@ -0,0 +1,219 @@
import {
GraphicDrawAssistant,
JlDrawApp,
linePoint,
GraphicInteractionPlugin,
GraphicApp,
JlGraphic,
DraggablePoint,
GraphicTransformEvent,
calculateFootPointFromPointToLine,
calculateDistanceFromPointToLine,
distance,
} from 'src/jl-graphic';
import {
IPathLineData,
PathLineTemplate,
PathLine,
pathLineConsts,
} from './PathLine';
import {
Point,
Graphics,
LINE_JOIN,
IHitArea,
DisplayObject,
IPointData,
} from 'pixi.js';
export interface IPathLineDrawOptions {
newData: () => IPathLineData;
}
export class PathLineDraw extends GraphicDrawAssistant<
PathLineTemplate,
IPathLineData
> {
points: Point[] = [];
code = '';
graphic: Graphics = new Graphics();
constructor(app: JlDrawApp, template: PathLineTemplate) {
super(app, template, 'sym_o_horizontal_rule', '不展示');
this.container.addChild(this.graphic);
PathLinePointsEditPlugin.init(app);
}
clearCache(): void {
this.points = [];
this.code = '';
this.graphic.clear();
}
// onRightClick(): void {
// this.createAndStore(true);
// }
// onLeftDown(e: FederatedPointerEvent): void {
// const { x, y } = this.toCanvasCoordinates(e.global);
// const p = new Point(x, y);
// this.points.push(p);
// }
quickCreate(points: Point[], lineId: string) {
this.points = [...points];
this.code = lineId;
return this.createAndStore(true);
}
redraw(p: Point): void {
if (this.points.length < 1) return;
this.graphic.clear();
const template = this.graphicTemplate;
this.graphic.lineStyle({
width: template.pathLineWidth,
color: template.pathLineColor,
join: LINE_JOIN.ROUND,
});
const ps = [...this.points];
ps.push(p);
// 直线
this.graphic.moveTo(ps[0].x, ps[0].y);
for (let i = 1; i < ps.length; i++) {
const p = ps[i];
this.graphic.lineTo(p.x, p.y);
}
}
prepareData(data: IPathLineData): boolean {
data.points = this.points;
return true;
}
}
export class PathLineGraphicHitArea implements IHitArea {
pathLine: PathLine;
constructor(pathLine: PathLine) {
this.pathLine = pathLine;
}
contains(x: number, y: number): boolean {
const p = new Point(x, y);
for (let i = 1; i < this.pathLine.datas.points.length; i++) {
const p1 = this.pathLine.datas.points[i - 1];
const p2 = this.pathLine.datas.points[i];
if (linePoint(p1, p2, p, pathLineConsts.pathLineWidth)) {
return true;
}
}
return false;
}
}
export class PathLinePointsEditPlugin extends GraphicInteractionPlugin<PathLine> {
static Name = 'LinkPointsDrag';
constructor(app: GraphicApp) {
super(PathLinePointsEditPlugin.Name, app);
}
static init(app: GraphicApp): PathLinePointsEditPlugin {
return new PathLinePointsEditPlugin(app);
}
filter(...grahpics: JlGraphic[]): PathLine[] | undefined {
return grahpics.filter((g) => g.type == PathLine.Type) as PathLine[];
}
bind(g: PathLine): void {
g.pathLine.eventMode = 'static';
g.pathLine.cursor = 'pointer';
g.pathLine.draggable = false;
g.pathLine.scalable = false;
g.pathLine.rotatable = false;
g.pathLine.hitArea = new PathLineGraphicHitArea(g);
g.on('selected', this.onSelected, this);
g.on('unselected', this.onUnselected, this);
}
unbind(g: PathLine): void {
g.off('selected', this.onSelected, this);
g.off('unselected', this.onUnselected, this);
}
onSelected(g: DisplayObject): void {
const pathLine = g as PathLine;
pathLine.draggable = false;
pathLine.scalable = false;
pathLine.rotatable = false;
pathLine.datas.kilometerPoints.forEach((kp, index) => {
const dp = new DraggablePoint(kp.point);
dp.on(
'transforming',
(e: GraphicTransformEvent) => {
this.movePointOnLine(pathLine.datas.points, e);
},
this
);
dp.on(
'transformend',
(e: GraphicTransformEvent) => {
const kilometerPoints = [...pathLine.datas.kilometerPoints];
kilometerPoints[index] = {
point: new Point(e.target.position.x, e.target.position.y),
kilometer: kilometerPoints[index].kilometer,
stName: kilometerPoints[index].stName,
};
pathLine.datas.kilometerPoints = kilometerPoints;
},
this
);
pathLine.addChild(dp);
});
}
onUnselected(g: DisplayObject): void {
const pathLine = g as PathLine;
if (pathLine.children.length > 1) {
pathLine.removeChildren(1);
}
}
kpTransforming(e: GraphicTransformEvent) {
console.log(e, '----');
}
movePointOnLine(points: IPointData[], e: GraphicTransformEvent) {
let minDistance = Number.MAX_SAFE_INTEGER;
let fp = null;
const np = e.target.position;
for (let i = 0; i < points.length; i++) {
const distancep = distance(np.x, np.y, points[i].x, points[i].y);
if (i !== 0) {
const distanceF = calculateDistanceFromPointToLine(
points[i - 1],
points[i],
np
);
const fpn = calculateFootPointFromPointToLine(
points[i - 1],
points[i],
np
);
const distance1 = distance(
fpn.x,
fpn.y,
points[i - 1].x,
points[i - 1].y
);
const distance2 = distance(fpn.x, fpn.y, points[i].x, points[i].y);
const distance3 = distance(
points[i - 1].x,
points[i - 1].y,
points[i].x,
points[i].y
);
if (distancep < minDistance) {
minDistance = distancep;
fp = points[i];
}
if (distanceF < minDistance && distance1 + distance2 <= distance3 + 1) {
minDistance = distanceF;
fp = fpn;
}
} else {
minDistance = distancep;
fp = points[i];
}
}
if (fp) {
e.target.position.set(fp.x, fp.y);
}
}
}

View File

@ -0,0 +1,429 @@
import { Color, Container, Graphics, Rectangle } from 'pixi.js';
import {
GraphicData,
GraphicState,
JlGraphic,
JlGraphicTemplate,
VectorText,
getRectangleCenter,
} from 'src/jl-graphic';
export interface IPlatformData extends GraphicData {
get code(): string; // 编号
set code(v: string);
get hasdoor(): boolean; // 是否有屏蔽门
set hasdoor(v: boolean);
get direction(): string; // 屏蔽门上下
set direction(v: string);
clone(): IPlatformData;
copyFrom(data: IPlatformData): void;
eq(other: IPlatformData): boolean;
}
export interface IPlatformState extends GraphicState {
get emergstop(): boolean;
set emergstop(v: boolean);
get trainberth(): boolean;
set trainberth(v: boolean);
get close(): boolean;
set close(v: boolean);
get upHold(): boolean;
set upHold(v: boolean);
get downHold(): boolean;
set downHold(v: boolean);
get upOccHold(): boolean;
set upOccHold(v: boolean);
get downOccHold(): boolean;
set downOccHold(v: boolean);
get psdOpen(): boolean;
set psdOpen(v: boolean);
get psdCut(): boolean;
set psdCut(v: boolean);
get upSkipstop(): boolean;
set upSkipstop(v: boolean);
get downSkipstop(): boolean;
set downSkipstop(v: boolean);
get upTrainSkipstop(): boolean;
set upTrainSkipstop(v: boolean);
get downTrainSkipstop(): boolean;
set downTrainSkipstop(v: boolean);
get nextSectionRunTime(): number; //下一区间运行时间
set nextSectionRunTime(v: number);
get nextSectionRunLevel(): number; //下一区间运行等级
set nextSectionRunLevel(v: number);
get stopTime(): number; //停站时间
set stopTime(v: number);
}
//站台颜色
export enum PlatformColorEnum {
grey = '0x7F7F7F', //站台没有列车停站
yellow = '0xfbff00', //列车在站台停站
blue = '0xC0C0FE', //列车在站台跳停
lozengeRed = '0xff0000', //站台旁的菱形图标
whiteNumbers = '0xffffff', //站台旁白色数字
whiteCircle = '0xffffff', //H字符旁的圆圈
HCharYellow = '0xfbff00', //站台旁的H字符
HCharWhite = '0xffffff',
HCharRed = '0xff0000',
doorGreen = '0x00FF00', //屏蔽门的颜色
doorRed = '0xff0000',
doorBlue = '0x4048C4',
}
const platformConsts = {
width: 90,
height: 20,
lineWidth: 3,
besideFontSize: 12,
doorOpenSpacing: 15,
doorPlatformSpacing: 10,
besideSpacing: 10,
circleRadius: 1,
};
//子元素--矩形
export class rectGraphic extends Container {
static Type = 'RectPlatForm';
rectGraphic: Graphics;
constructor() {
super();
this.rectGraphic = new Graphics();
this.addChild(this.rectGraphic);
}
draw(state: IPlatformState): void {
const rectGraphic = this.rectGraphic;
rectGraphic.clear();
let fillColor = PlatformColorEnum.grey;
if (state.trainberth) {
fillColor = PlatformColorEnum.yellow;
}
if (state.upSkipstop || state.downSkipstop) {
fillColor = PlatformColorEnum.blue;
}
rectGraphic.lineStyle(platformConsts.lineWidth, new Color(fillColor));
rectGraphic.beginFill(fillColor, 1);
rectGraphic.drawRect(0, 0, platformConsts.width, platformConsts.height);
rectGraphic.endFill;
const rectP = new Rectangle(
0,
0,
platformConsts.width,
platformConsts.height
);
rectGraphic.pivot = getRectangleCenter(rectP);
}
clear(): void {
this.rectGraphic.clear();
}
}
//子元素--门
export class doorGraphic extends Container {
static Type = 'Door';
doorGraphic: Graphics;
doorCloseGraphic: Graphics;
constructor() {
super();
this.doorGraphic = new Graphics();
this.doorCloseGraphic = new Graphics();
this.addChild(this.doorGraphic);
this.addChild(this.doorCloseGraphic);
}
draw(stateData: IPlatformState): void {
const doorGraphic = this.doorGraphic;
const doorCloseGraphic = this.doorCloseGraphic;
doorGraphic.clear();
doorCloseGraphic.clear();
let lineColor = PlatformColorEnum.doorGreen;
if (stateData.psdCut) {
lineColor = PlatformColorEnum.doorRed;
}
doorGraphic.lineStyle(platformConsts.lineWidth, new Color(lineColor));
doorGraphic.moveTo(
-platformConsts.width / 2 - platformConsts.lineWidth / 2,
0
);
doorGraphic.lineTo(-platformConsts.doorOpenSpacing, 0);
doorGraphic.moveTo(platformConsts.doorOpenSpacing, 0);
doorGraphic.lineTo(
platformConsts.width / 2 + platformConsts.lineWidth / 2,
0
);
//屏蔽门闭合
doorCloseGraphic.lineStyle(platformConsts.lineWidth, new Color(lineColor));
doorCloseGraphic.moveTo(-platformConsts.doorOpenSpacing, 0);
doorCloseGraphic.lineTo(platformConsts.doorOpenSpacing, 0);
doorGraphic.position.set(
0,
-platformConsts.height / 2 - platformConsts.doorPlatformSpacing
);
doorCloseGraphic.position.set(
0,
-platformConsts.height / 2 - platformConsts.doorPlatformSpacing
);
}
clear(): void {
this.doorGraphic.clear();
this.doorCloseGraphic.clear();
}
changeState(stateData: IPlatformState): void {
if (stateData.psdOpen) {
this.doorCloseGraphic.visible = false;
} else {
this.doorCloseGraphic.visible = true;
}
}
}
//子元素--字符
class codeGraph extends Container {
static Type = 'Code';
character: VectorText = new VectorText(''); //扣车H
runTime: VectorText = new VectorText(''); //运行时间
stopTime: VectorText = new VectorText(''); //停站时间
circle: Graphics = new Graphics();
constructor() {
super();
this.addChild(this.character);
this.addChild(this.runTime);
this.addChild(this.circle);
this.addChild(this.stopTime);
this.character.setVectorFontSize(platformConsts.besideFontSize);
this.runTime.setVectorFontSize(platformConsts.besideFontSize);
this.stopTime.setVectorFontSize(platformConsts.besideFontSize);
}
draw(): void {
//扣车
const character = this.character;
character.text = 'H';
character.anchor.set(0.5);
character.position.set(
-platformConsts.width / 2 -
platformConsts.lineWidth / 2 -
(platformConsts.besideSpacing * 2) / 3,
(platformConsts.height * 3) / 4
);
character.style.fill = PlatformColorEnum.whiteNumbers;
const circle = this.circle;
circle.clear();
circle.lineStyle(0.5, PlatformColorEnum.whiteCircle);
circle.drawCircle(0, 0, platformConsts.circleRadius);
circle.position.set(
-platformConsts.width / 2 -
platformConsts.lineWidth / 2 -
(platformConsts.besideSpacing * 4) / 3,
(platformConsts.height * 3) / 5
);
//区间运行等级状态
const runTime = this.runTime;
runTime.anchor.set(0.5);
runTime.position.set(
platformConsts.width / 2 +
platformConsts.lineWidth / 2 +
platformConsts.besideSpacing,
-platformConsts.besideSpacing
);
runTime.style.fill = PlatformColorEnum.whiteNumbers;
//停站时间
const stopTime = this.stopTime;
stopTime.anchor.set(0.5);
stopTime.position.set(
platformConsts.width / 2 +
platformConsts.lineWidth / 2 +
platformConsts.besideSpacing,
platformConsts.besideSpacing
);
stopTime.style.fill = PlatformColorEnum.whiteNumbers;
character.visible = false;
circle.visible = false;
runTime.visible = false;
stopTime.visible = false;
}
clear(): void {
this.character.destroy();
}
changeState(stateData: IPlatformState): void {
if (
stateData.upHold ||
stateData.upOccHold ||
stateData.downHold ||
stateData.downOccHold
) {
this.character.text = 'H';
this.character.visible = true;
this.circle.visible = true;
//上行扣车
if (stateData.upHold) {
this.character.style.fill = PlatformColorEnum.HCharYellow;
}
if (stateData.upOccHold) {
this.character.style.fill = PlatformColorEnum.HCharWhite;
}
if (stateData.upHold && stateData.upOccHold) {
this.character.style.fill = PlatformColorEnum.HCharRed;
}
//下行扣车
if (stateData.downHold) {
this.character.style.fill = PlatformColorEnum.HCharYellow;
}
if (stateData.downOccHold) {
this.character.style.fill = PlatformColorEnum.HCharWhite;
}
if (stateData.downHold && stateData.downOccHold) {
this.character.style.fill = PlatformColorEnum.HCharRed;
}
//运行等级
if (stateData.nextSectionRunTime) {
this.runTime.visible = true;
this.runTime.text = stateData.nextSectionRunTime;
}
//停站时间
if (stateData.stopTime) {
this.stopTime.visible = true;
this.stopTime.text = stateData.stopTime;
}
}
}
}
//子元素--站台旁菱形图标
class besideGraphic extends Container {
static Type = 'BesideGraphic';
besideGraphic: Graphics;
constructor() {
super();
this.besideGraphic = new Graphics();
this.addChild(this.besideGraphic);
}
draw(): void {
const besideGraphic = this.besideGraphic;
besideGraphic.clear();
besideGraphic.lineStyle(1, new Color(PlatformColorEnum.lozengeRed));
besideGraphic.beginFill(PlatformColorEnum.lozengeRed, 1);
besideGraphic.drawRect(
0,
0,
platformConsts.height / 4,
platformConsts.height / 4
);
besideGraphic.endFill();
const rect = new Rectangle(
0,
0,
platformConsts.height / 4,
platformConsts.height / 4
);
besideGraphic.pivot = getRectangleCenter(rect);
besideGraphic.rotation = Math.PI / 4;
besideGraphic.position.set(
0,
-platformConsts.height / 2 -
platformConsts.doorPlatformSpacing -
platformConsts.height / 3
);
besideGraphic.visible = false;
}
clear(): void {
this.besideGraphic.clear();
}
changeState(stateData: IPlatformState): void {
if (stateData.emergstop) {
this.besideGraphic.visible = true;
} else {
this.besideGraphic.visible = false;
}
}
}
export class Platform extends JlGraphic {
static Type = 'Platform';
platformGraphic: rectGraphic = new rectGraphic();
doorGraphic: doorGraphic = new doorGraphic();
besideGraphic: besideGraphic = new besideGraphic();
codeGraph: codeGraph = new codeGraph();
constructor() {
super(Platform.Type);
this.addChild(this.platformGraphic);
this.addChild(this.doorGraphic);
this.addChild(this.besideGraphic);
this.addChild(this.codeGraph);
}
get datas(): IPlatformData {
return this.getDatas<IPlatformData>();
}
get states(): IPlatformState {
return this.getStates<IPlatformState>();
}
doRepaint(): void {
this.doorGraphic.clear();
if (this.datas.hasdoor) {
this.doorGraphic.draw(this.states);
}
this.platformGraphic.draw(this.states);
this.besideGraphic.draw();
this.codeGraph.draw();
this.doorGraphic.position.set(0, 0);
this.besideGraphic.position.set(0, 0);
this.codeGraph.position.set(0, 0);
//站台方向
if (this.datas.direction == 'down') {
this.doorGraphic.position.set(
0,
platformConsts.height + platformConsts.doorPlatformSpacing * 2
);
this.besideGraphic.position.set(
0,
platformConsts.height +
platformConsts.doorPlatformSpacing * 2 +
(platformConsts.height * 2) / 3
);
this.codeGraph.children[0].position.set(
platformConsts.width / 2 +
platformConsts.lineWidth / 2 +
(platformConsts.besideSpacing * 2) / 3,
-(platformConsts.height * 3) / 4
);
this.codeGraph.children[1].position.set(
-platformConsts.width / 2 -
platformConsts.lineWidth / 2 -
platformConsts.besideSpacing,
platformConsts.besideSpacing
);
this.codeGraph.children[2].position.set(
platformConsts.width / 2 +
platformConsts.lineWidth / 2 +
(platformConsts.besideSpacing * 4) / 3,
(-platformConsts.height * 10) / 11
);
this.codeGraph.children[3].position.set(
-platformConsts.width / 2 -
platformConsts.lineWidth / 2 -
platformConsts.besideSpacing,
-platformConsts.besideSpacing
);
}
this.changeState();
}
changeState(): void {
this.doorGraphic.changeState(this.states);
this.besideGraphic.changeState(this.states);
this.codeGraph.changeState(this.states);
}
}
export class PlatformTemplate extends JlGraphicTemplate<Platform> {
hasdoor: boolean;
direction: string;
constructor(dataTemplate: IPlatformData, stateTemplate: IPlatformState) {
super(Platform.Type, {
dataTemplate,
stateTemplate,
});
this.hasdoor = true;
this.direction = 'up';
}
new(): Platform {
const platform = new Platform();
platform.loadData(this.datas);
platform.loadState(this.states);
return platform;
}
}

View File

@ -0,0 +1,119 @@
import { FederatedPointerEvent, Point } from 'pixi.js';
import {
AbsorbableLine,
AbsorbablePosition,
GraphicDrawAssistant,
GraphicInteractionPlugin,
JlDrawApp,
JlGraphic,
} from 'src/jl-graphic';
import {
IPlatformData,
Platform,
PlatformTemplate,
rectGraphic,
doorGraphic,
IPlatformState,
} from './Platform';
export interface IPlatformDrawOptions {
newData: () => IPlatformData;
}
export class PlatformDraw extends GraphicDrawAssistant<
PlatformTemplate,
IPlatformData
> {
platformGraphic: rectGraphic = new rectGraphic();
doorGraphic: doorGraphic = new doorGraphic();
constructor(app: JlDrawApp, template: PlatformTemplate) {
super(
app,
template,
'svguse:../../drawIcon.svg#icon-platform',
'站台Platform'
);
this.container.addChild(this.platformGraphic);
this.container.addChild(this.doorGraphic);
platformInteraction.init(app);
}
bind(): void {
super.bind();
this.platformGraphic.draw(this.graphicTemplate.states as IPlatformState);
this.doorGraphic.draw(this.graphicTemplate.states as IPlatformState);
}
clearCache(): void {
this.platformGraphic.clear();
this.doorGraphic.clear();
}
onLeftDown(e: FederatedPointerEvent): void {
this.container.position.copyFrom(this.toCanvasCoordinates(e.global));
this.createAndStore(true);
}
redraw(p: Point): void {
this.container.position.copyFrom(p);
}
prepareData(data: IPlatformData): boolean {
const template = this.graphicTemplate;
data.hasdoor = template.hasdoor;
data.direction = template.direction;
data.transform = this.container.saveTransform();
return true;
}
}
function buildAbsorbablePositions(platform: Platform): AbsorbablePosition[] {
const aps: AbsorbablePosition[] = [];
const platforms = platform.queryStore.queryByType<Platform>(Platform.Type);
const { width, height } = platform.getGraphicApp().canvas;
platforms.forEach((other) => {
if (other.id == platform.id) {
return;
}
const ps = other.datas.transform.position;
const xs = new AbsorbableLine({ x: 0, y: ps.y }, { x: width, y: ps.y });
const ys = new AbsorbableLine({ x: ps.x, y: 0 }, { x: ps.x, y: height });
aps.push(xs, ys);
});
return aps;
}
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;
g.on('selected', this.onSelected, this);
}
unbind(g: Platform): void {
g.eventMode = 'none';
g.scalable = false;
g.rotatable = false;
g.off('selected', this.onSelected, this);
}
onSelected(): void {
const platform = this.app.selectedGraphics[0] as Platform;
this.app.setOptions({
absorbablePositions: buildAbsorbablePositions(platform),
});
}
}

View File

@ -0,0 +1,72 @@
import { Color, Graphics, IPointData } from 'pixi.js';
import { GraphicData, JlGraphic, JlGraphicTemplate } from 'src/jl-graphic';
export interface IPolygonData extends GraphicData {
get code(): string; // 编号
set code(v: string);
get lineWidth(): number; // 线宽
set lineWidth(v: number);
get lineColor(): string; // 线色
set lineColor(v: string);
get points(): IPointData[]; // 多边形坐标点
set points(points: IPointData[]);
clone(): IPolygonData;
copyFrom(data: IPolygonData): void;
eq(other: IPolygonData): boolean;
}
const polygonConsts = {
lineWidth: 2,
lineColor: '0xff0000',
};
export class Polygon extends JlGraphic {
static Type = 'Polygon';
polygonGraphic: Graphics;
constructor() {
super(Polygon.Type);
this.polygonGraphic = new Graphics();
this.addChild(this.polygonGraphic);
}
get datas(): IPolygonData {
return this.getDatas<IPolygonData>();
}
doRepaint(): void {
const polygonGraphic = this.polygonGraphic;
polygonGraphic.clear();
polygonGraphic.lineStyle(
this.datas.lineWidth,
new Color(this.datas.lineColor)
);
polygonGraphic.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);
}
addOnePoints(): IPointData[] {
const ps = [...this.datas.points];
ps.push(this.datas.points[0]);
return ps;
}
}
export class PolygonTemplate extends JlGraphicTemplate<Polygon> {
lineWidth: number;
lineColor: string;
constructor(dataTemplate: IPolygonData) {
super(Polygon.Type, {
dataTemplate,
});
this.lineWidth = polygonConsts.lineWidth;
this.lineColor = polygonConsts.lineColor;
}
new(): Polygon {
return new Polygon();
}
}

View File

@ -0,0 +1,195 @@
import {
FederatedPointerEvent,
Graphics,
Point,
IHitArea,
DisplayObject,
} from 'pixi.js';
import {
DraggablePoint,
GraphicApp,
GraphicDrawAssistant,
GraphicInteractionPlugin,
GraphicTransformEvent,
JlDrawApp,
JlGraphic,
linePoint,
} from 'src/jl-graphic';
import AbsorbablePoint, {
AbsorbablePosition,
} from 'src/jl-graphic/graphic/AbsorbablePosition';
import {
ILineGraphic,
PolylineEditPlugin,
} from 'src/jl-graphic/plugins/GraphicEditPlugin';
import { IPolygonData, Polygon, PolygonTemplate } from './Polygon';
export interface IPolygonDrawOptions {
newData: () => IPolygonData;
}
export class PolygonDraw extends GraphicDrawAssistant<
PolygonTemplate,
IPolygonData
> {
points: Point[] = [];
polygonGraphic: Graphics = new Graphics();
constructor(app: JlDrawApp, template: PolygonTemplate) {
super(app, template, 'sym_o_square', '多边形Polygon');
this.container.addChild(this.polygonGraphic);
PolygonPointsEditPlugin.init(app);
}
clearCache(): void {
this.points = [];
this.polygonGraphic.clear();
}
onLeftDown(e: FederatedPointerEvent): void {
const { x, y } = this.toCanvasCoordinates(e.global);
const p = new Point(x, y);
this.points.push(p);
}
onRightClick(): void {
this.createAndStore(true);
}
redraw(p: Point): void {
if (this.points.length < 1) return;
const polygonGraphic = this.polygonGraphic;
const template = this.graphicTemplate;
const ps = [...this.points];
ps.push(p);
polygonGraphic.clear();
polygonGraphic.lineStyle(template.lineWidth, template.lineColor);
polygonGraphic.drawPolygon(ps);
}
prepareData(data: IPolygonData): boolean {
if (this.points.length < 2) {
console.log('Polygon绘制因点不够取消绘制');
return false;
}
const template = this.graphicTemplate;
data.lineWidth = template.lineWidth;
data.lineColor = template.lineColor;
data.points = this.points;
return true;
}
}
//碰撞检测
export class PolygonGraphicHitArea implements IHitArea {
polygon: Polygon;
constructor(polygon: Polygon) {
this.polygon = polygon;
}
contains(x: number, y: number): boolean {
let contains = false;
const p = new Point(x, y);
const polygonData = this.polygon.datas;
//contains = pointPolygon(p, polygonData.points, polygonData.lineWidth);是否包含多边形内部
const ps = this.polygon.addOnePoints();
const tolerance = polygonData.lineWidth;
for (let i = 0; i < ps.length - 1; i++) {
const p1 = ps[i];
const p2 = ps[i + 1];
contains = contains || linePoint(p1, p2, p, tolerance);
if (contains) {
break;
}
}
return contains;
}
}
/**
*
* @param polygon
* @returns
*/
function buildAbsorbablePositions(polygon: Polygon): AbsorbablePosition[] {
const aps: AbsorbablePosition[] = [];
const polygons = polygon.queryStore.queryByType<Polygon>(Polygon.Type);
polygons.forEach((other) => {
if (other.id == polygon.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 polygon = g as Polygon;
// 端点
dp.on('transformstart', (e: GraphicTransformEvent) => {
if (e.isShift()) {
polygon.getGraphicApp().setOptions({
absorbablePositions: buildAbsorbablePositions(polygon),
});
}
});
}
/**
* polygon路径编辑
*/
export class PolygonPointsEditPlugin extends GraphicInteractionPlugin<Polygon> {
static Name = 'PolygonPointsDrag';
constructor(app: GraphicApp) {
super(PolygonPointsEditPlugin.Name, app);
}
static init(app: GraphicApp): PolygonPointsEditPlugin {
return new PolygonPointsEditPlugin(app);
}
filter(...grahpics: JlGraphic[]): Polygon[] | undefined {
return grahpics.filter((g) => g.type == Polygon.Type) as Polygon[];
}
bind(g: Polygon): void {
g.polygonGraphic.eventMode = 'static';
g.polygonGraphic.cursor = 'pointer';
g.polygonGraphic.hitArea = new PolygonGraphicHitArea(g);
g.on('selected', this.onSelected, this);
g.on('unselected', this.onUnselected, this);
}
unbind(g: Polygon): void {
g.off('selected', this.onSelected, this);
g.off('unselected', this.onUnselected, this);
}
onSelected(g: DisplayObject): void {
const polygon = g as Polygon;
let lep = polygon.getAssistantAppend<PolylineEditPlugin>(
PolylineEditPlugin.Name
);
if (!lep) {
lep = new PolylineEditPlugin(polygon, { onEditPointCreate });
polygon.addAssistantAppend(lep);
}
lep.showAll();
}
onUnselected(g: DisplayObject): void {
const polygon = g as Polygon;
const lep = polygon.getAssistantAppend<PolylineEditPlugin>(
PolylineEditPlugin.Name
);
if (lep) {
lep.hideAll();
}
}
}

View File

@ -0,0 +1,90 @@
import { IPointData, Point } from 'pixi.js';
import {
calculateDistanceFromPointToLine,
calculateFootPointFromPointToLine,
} from 'src/jl-graphic';
import { Polygon } from './Polygon';
//计算线段细分坐标--线段分成几份
export function getLineSegmentPoints(
startPoint: IPointData,
endPoint: IPointData,
knife: number
) {
const segmentLength = Math.sqrt(
Math.pow(endPoint.x - startPoint.x, 2) +
Math.pow(endPoint.y - startPoint.y, 2)
);
const segmentIncrement = segmentLength / knife;
const segmentAngle = Math.atan2(
endPoint.y - startPoint.y,
endPoint.x - startPoint.x
);
const points: IPointData[] = [];
for (let i = 1; i < knife; i++) {
const segmentPosition = i * segmentIncrement;
const x = startPoint.x + segmentPosition * Math.cos(segmentAngle);
const y = startPoint.y + segmentPosition * Math.sin(segmentAngle);
points.push(new Point(x, y));
}
return points;
}
//获取所选线段的索引
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 addPolygonSegmentingPoint(
graphic: Polygon,
start: number,
end: number,
knife = 2
) {
const linePoints = graphic.addOnePoints();
const points = linePoints.slice(0, start + 1);
points.push(
...getLineSegmentPoints(linePoints[start], linePoints[end], knife)
);
points.push(...linePoints.slice(end));
points.pop();
graphic.linePoints = points;
}

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

@ -0,0 +1,99 @@
import { Color, Graphics, IPointData, Point, Rectangle } from 'pixi.js';
import {
GraphicData,
JlGraphic,
JlGraphicTemplate,
getRectangleCenter,
} from 'src/jl-graphic';
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 radius(): number; // 圆角半径
set radius(v: number);
clone(): IRectData;
copyFrom(data: IRectData): void;
eq(other: IRectData): boolean;
}
const rectConsts = {
lineWidth: 2,
lineColor: '0xff0000',
};
export class Rect extends JlGraphic {
static Type = 'Rect';
rectGraphic: Graphics = new Graphics();
constructor() {
super(Rect.Type);
this.addChild(this.rectGraphic);
}
get datas(): IRectData {
return this.getDatas<IRectData>();
}
doRepaint(): void {
const rectGraphic = this.rectGraphic;
rectGraphic.clear();
rectGraphic.lineStyle(
this.datas.lineWidth,
new Color(this.datas.lineColor)
);
const radius = this.datas?.radius || 0;
rectGraphic.drawRoundedRect(
0,
0,
this.datas.width,
this.datas.height,
radius
);
rectGraphic.pivot = getRectangleCenter(
new Rectangle(0, 0, this.datas.width, this.datas.height)
);
const transformPos = this.datas.transform.position;
if (transformPos.x == 0 && transformPos.y == 0) {
this.position.set(
this.datas.point.x + this.datas.width / 2,
this.datas.point.y + this.datas.height / 2
);
} else {
this.position.set(
this.datas.transform.position.x,
this.datas.transform.position.y
);
}
}
rectPoints(): IPointData[] {
const r1 = new Point(this.datas.point.x, this.datas.point.y);
const r2 = new Point(r1.x + this.datas.width, r1.y);
const r3 = new Point(r1.x + this.datas.width, r1.y + this.datas.height);
const r4 = new Point(r1.x, r1.y + this.datas.height);
const rectPoints = [r1, r2, r3, r4, r1];
return rectPoints;
}
}
export class RectTemplate extends JlGraphicTemplate<Rect> {
lineWidth: number;
lineColor: string;
constructor(dataTemplate: IRectData) {
super(Rect.Type, {
dataTemplate,
});
this.lineWidth = rectConsts.lineWidth;
this.lineColor = rectConsts.lineColor;
}
new(): Rect {
return new Rect();
}
}

Some files were not shown because too many files have changed in this diff Show More