프론트 백엔드 코드 회사 래파지토리에서 가져오기
Signed-off-by: bsh <bsh01111@naver.com>
Showing
176 changed files
with
10569 additions
and
0 deletions
Backend/.eslintrc.js
0 → 100644
1 | +module.exports = { | ||
2 | + parser: '@typescript-eslint/parser', | ||
3 | + parserOptions: { | ||
4 | + project: 'tsconfig.json', | ||
5 | + sourceType: 'module', | ||
6 | + }, | ||
7 | + plugins: ['@typescript-eslint/eslint-plugin'], | ||
8 | + extends: [ | ||
9 | + 'plugin:@typescript-eslint/recommended', | ||
10 | + 'plugin:prettier/recommended', | ||
11 | + ], | ||
12 | + root: true, | ||
13 | + env: { | ||
14 | + node: true, | ||
15 | + jest: true, | ||
16 | + }, | ||
17 | + ignorePatterns: ['.eslintrc.js'], | ||
18 | + rules: { | ||
19 | + '@typescript-eslint/interface-name-prefix': 'off', | ||
20 | + '@typescript-eslint/explicit-function-return-type': 'off', | ||
21 | + '@typescript-eslint/explicit-module-boundary-types': 'off', | ||
22 | + '@typescript-eslint/no-explicit-any': 'off', | ||
23 | + }, | ||
24 | +}; |
Backend/.gitignore
0 → 100644
1 | +# compiled output | ||
2 | +/dist | ||
3 | +/node_modules | ||
4 | +/output | ||
5 | + | ||
6 | +# config env | ||
7 | +.env | ||
8 | +.envrc | ||
9 | + | ||
10 | +# Logs | ||
11 | +logs | ||
12 | +*.log | ||
13 | +npm-debug.log* | ||
14 | +yarn-debug.log* | ||
15 | +yarn-error.log* | ||
16 | +lerna-debug.log* | ||
17 | + | ||
18 | +# OS | ||
19 | +.DS_Store | ||
20 | + | ||
21 | +# Tests | ||
22 | +/coverage | ||
23 | +/.nyc_output | ||
24 | + | ||
25 | +# IDEs and editors | ||
26 | +/.idea | ||
27 | +.project | ||
28 | +.classpath | ||
29 | +.c9/ | ||
30 | +*.launch | ||
31 | +.settings/ | ||
32 | +*.sublime-workspace | ||
33 | +builds/* | ||
34 | +nohup.out | ||
35 | + | ||
36 | +# IDE - VSCode | ||
37 | +.vscode/* | ||
38 | +!.vscode/settings.json | ||
39 | +!.vscode/tasks.json | ||
40 | +!.vscode/launch.json | ||
41 | +!.vscode/extensions.json | ||
42 | + | ||
43 | +ormconfig.json | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
Backend/.gitlab-ci.yml
0 → 100644
1 | +cache: | ||
2 | + untracked: true | ||
3 | + key: '$CI_BUILD_REF_NAME' | ||
4 | + paths: | ||
5 | + - node_modules/ | ||
6 | + | ||
7 | +build: | ||
8 | + stage: build | ||
9 | + script: | ||
10 | + - npm install | ||
11 | + - npm run build | ||
12 | + only: | ||
13 | + - master | ||
14 | + tags: | ||
15 | + - build | ||
16 | + | ||
17 | +deploy: | ||
18 | + stage: deploy | ||
19 | + script: | ||
20 | + - source ./env/production.sh | ||
21 | + - pm2 start --exp-backoff-restart-delay=200 | ||
22 | + only: | ||
23 | + - master | ||
24 | + tags: | ||
25 | + - deploy |
Backend/.prettierrc
0 → 100644
Backend/README.md
0 → 100644
1 | +## Description | ||
2 | + | ||
3 | +드.론. | ||
4 | + | ||
5 | +## Installation | ||
6 | + | ||
7 | +```bash | ||
8 | +$ npm install | ||
9 | +``` | ||
10 | + | ||
11 | +## Running the app | ||
12 | + | ||
13 | +```bash | ||
14 | +# development | ||
15 | +$ npm run start | ||
16 | + | ||
17 | +# watch mode | ||
18 | +$ npm run start:dev | ||
19 | + | ||
20 | +# production mode | ||
21 | +$ npm run start:prod | ||
22 | +``` | ||
23 | + | ||
24 | +## Test | ||
25 | + | ||
26 | +```bash | ||
27 | +# unit tests | ||
28 | +$ npm run test | ||
29 | + | ||
30 | +# e2e tests | ||
31 | +$ npm run test:e2e | ||
32 | + | ||
33 | +# test coverage | ||
34 | +$ npm run test:cov | ||
35 | +``` |
Backend/backend
0 → 100644
File mode changed
Backend/ecosystem.config.js
0 → 100644
Backend/env/production.sh
0 → 100755
Backend/nest-cli.json
0 → 100644
Backend/package-lock.json
0 → 100644
This diff could not be displayed because it is too large.
Backend/package.json
0 → 100644
1 | +{ | ||
2 | + "name": "web-api", | ||
3 | + "version": "0.0.1", | ||
4 | + "description": "", | ||
5 | + "author": "", | ||
6 | + "private": true, | ||
7 | + "license": "UNLICENSED", | ||
8 | + "scripts": { | ||
9 | + "prebuild": "rimraf dist", | ||
10 | + "build": "nest build", | ||
11 | + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", | ||
12 | + "start": "nest start", | ||
13 | + "start:dev": "nest start --watch", | ||
14 | + "start:debug": "nest start --debug --watch", | ||
15 | + "start:prod": "node dist/main", | ||
16 | + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", | ||
17 | + "test": "jest", | ||
18 | + "test:watch": "jest --watch", | ||
19 | + "test:cov": "jest --coverage", | ||
20 | + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", | ||
21 | + "test:e2e": "jest --config ./test/jest-e2e.json", | ||
22 | + "typeorm": "ts-node ./node_modules/typeorm/cli.js" | ||
23 | + }, | ||
24 | + "dependencies": { | ||
25 | + "@nestjs/common": "^7.6.13", | ||
26 | + "@nestjs/config": "^0.6.3", | ||
27 | + "@nestjs/core": "^7.6.13", | ||
28 | + "@nestjs/platform-express": "^7.6.13", | ||
29 | + "@nestjs/platform-socket.io": "^7.6.15", | ||
30 | + "@nestjs/platform-ws": "^7.6.15", | ||
31 | + "@nestjs/typeorm": "^7.1.5", | ||
32 | + "@nestjs/websockets": "^7.6.15", | ||
33 | + "class-transformer": "^0.4.0", | ||
34 | + "class-validator": "^0.13.1", | ||
35 | + "pg": "^8.5.1", | ||
36 | + "reflect-metadata": "^0.1.13", | ||
37 | + "rimraf": "^3.0.2", | ||
38 | + "rxjs": "^6.6.6", | ||
39 | + "typeorm": "^0.2.32" | ||
40 | + }, | ||
41 | + "devDependencies": { | ||
42 | + "@nestjs/cli": "^7.5.6", | ||
43 | + "@nestjs/schematics": "^7.2.7", | ||
44 | + "@nestjs/testing": "^7.6.13", | ||
45 | + "@types/express": "^4.17.11", | ||
46 | + "@types/jest": "^26.0.20", | ||
47 | + "@types/node": "^14.14.31", | ||
48 | + "@types/socket.io": "^2.1.13", | ||
49 | + "@types/supertest": "^2.0.10", | ||
50 | + "@typescript-eslint/eslint-plugin": "^4.15.2", | ||
51 | + "@typescript-eslint/parser": "^4.15.2", | ||
52 | + "eslint": "^7.20.0", | ||
53 | + "eslint-config-prettier": "^8.1.0", | ||
54 | + "eslint-plugin-prettier": "^3.3.1", | ||
55 | + "jest": "^26.6.3", | ||
56 | + "prettier": "^2.2.1", | ||
57 | + "supertest": "^6.1.3", | ||
58 | + "ts-jest": "^26.5.2", | ||
59 | + "ts-loader": "^8.0.17", | ||
60 | + "ts-node": "^9.1.1", | ||
61 | + "tsconfig-paths": "^3.9.0", | ||
62 | + "typescript": "^4.1.5" | ||
63 | + }, | ||
64 | + "jest": { | ||
65 | + "moduleFileExtensions": [ | ||
66 | + "js", | ||
67 | + "json", | ||
68 | + "ts" | ||
69 | + ], | ||
70 | + "rootDir": "src", | ||
71 | + "testRegex": ".*\\.spec\\.ts$", | ||
72 | + "transform": { | ||
73 | + "^.+\\.(t|j)s$": "ts-jest" | ||
74 | + }, | ||
75 | + "collectCoverageFrom": [ | ||
76 | + "**/*.(t|j)s" | ||
77 | + ], | ||
78 | + "coverageDirectory": "../coverage", | ||
79 | + "testEnvironment": "node" | ||
80 | + } | ||
81 | +} |
Backend/src/app.module.ts
0 → 100644
1 | +import { Module } from '@nestjs/common'; | ||
2 | +import { ConfigModule, ConfigService } from '@nestjs/config'; | ||
3 | +import { TypeOrmModule } from '@nestjs/typeorm'; | ||
4 | +import configuration from './config/configuration'; | ||
5 | +import { DroneModule } from './drone/drone.module'; | ||
6 | +import { DroneEntity } from './entities/drone.entity'; | ||
7 | +import { DroneLogEntity } from './entities/drone.log.entity'; | ||
8 | +import { ScheduleEntity } from './entities/schedule.entity'; | ||
9 | +import { MemberEntity } from './entities/member.entity'; | ||
10 | +import { CodeEntity } from './entities/code.entity'; | ||
11 | +import { DroneScheduleMappingEntity } from './entities/drone.schedule.mapping.entity'; | ||
12 | + | ||
13 | +@Module({ | ||
14 | + imports: [ | ||
15 | + ConfigModule.forRoot({ | ||
16 | + isGlobal: true, | ||
17 | + load: [configuration], | ||
18 | + }), | ||
19 | + TypeOrmModule.forRootAsync({ | ||
20 | + imports: [ConfigModule], | ||
21 | + inject: [ConfigService], | ||
22 | + useFactory: (configService: ConfigService) => ({ | ||
23 | + type: 'postgres', | ||
24 | + host: configService.get('database.host'), | ||
25 | + username: configService.get('database.user'), | ||
26 | + password: configService.get('database.password'), | ||
27 | + database: configService.get('database.name'), | ||
28 | + port: configService.get('database.port'), | ||
29 | + entities: [`${__dirname}/**/*.entity.{ts,js}`], | ||
30 | + synchronize: false, | ||
31 | + }), | ||
32 | + }), | ||
33 | + DroneModule, | ||
34 | + ], | ||
35 | + controllers: [], | ||
36 | + providers: [], | ||
37 | +}) | ||
38 | +export class AppModule {} |
Backend/src/config/configuration.ts
0 → 100644
1 | +import 'dotenv/config'; | ||
2 | + | ||
3 | +export default () => ({ | ||
4 | + database: { | ||
5 | + host: process.env.DATABASE_HOST || '', | ||
6 | + user: process.env.DATABASE_USER || '', | ||
7 | + name: process.env.DATABASE_NAME || '', | ||
8 | + password: process.env.DATABASE_PASSWORD || '', | ||
9 | + port: process.env.DATABASE_PORT || '', | ||
10 | + }, | ||
11 | +}); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
Backend/src/drone/drone.controller.ts
0 → 100644
1 | +import { | ||
2 | + Body, | ||
3 | + Controller, | ||
4 | + Delete, | ||
5 | + Get, | ||
6 | + Param, | ||
7 | + Post, | ||
8 | + Put, | ||
9 | + Query, | ||
10 | + UsePipes, | ||
11 | + ValidationPipe, | ||
12 | +} from '@nestjs/common'; | ||
13 | +import { DroneService } from './drone.service'; | ||
14 | +import { DroneEntity } from 'src/entities/drone.entity'; | ||
15 | +import { MemberEntity } from 'src/entities/member.entity'; | ||
16 | +import { DroneLogEntity } from 'src/entities/drone.log.entity'; | ||
17 | +import { ScheduleEntity } from 'src/entities/schedule.entity'; | ||
18 | +import { CodeEntity } from 'src/entities/code.entity'; | ||
19 | +import { DroneApiDto } from 'src/drone/dto/drone.api.dto'; | ||
20 | + | ||
21 | +@Controller() | ||
22 | +export class DroneController { | ||
23 | + constructor(private droneService: DroneService) { | ||
24 | + this.droneService = droneService; | ||
25 | + } | ||
26 | + | ||
27 | + @Get('/member/list') | ||
28 | + async findMemberAll(): Promise<MemberEntity[]> { | ||
29 | + const memberList = await this.droneService.findMemberAll(); | ||
30 | + return Object.assign({ | ||
31 | + data: { | ||
32 | + members: memberList, | ||
33 | + }, | ||
34 | + statusCode: 200, | ||
35 | + statusMsg: '완료', | ||
36 | + }); | ||
37 | + } | ||
38 | + | ||
39 | + @Get('/member/page') | ||
40 | + async findMemberPagination(@Query() queries): Promise<MemberEntity[]> { | ||
41 | + const pageNo = queries.pageNo; | ||
42 | + const pageSize = queries.pageSize; | ||
43 | + | ||
44 | + const begin = (pageNo - 1) * pageSize; | ||
45 | + const end = pageNo * pageSize; | ||
46 | + | ||
47 | + const memberList = await (await this.droneService.findMemberAll()).slice( | ||
48 | + begin, | ||
49 | + end, | ||
50 | + ); | ||
51 | + const totalElement = (await this.droneService.findMemberAll()).length; | ||
52 | + return Object.assign({ | ||
53 | + data: { | ||
54 | + pageNo: pageNo, | ||
55 | + pageSize: pageSize, | ||
56 | + totalElement: totalElement, | ||
57 | + members: memberList, | ||
58 | + }, | ||
59 | + statusCode: 200, | ||
60 | + statusMsg: '완료', | ||
61 | + }); | ||
62 | + } | ||
63 | + | ||
64 | + @Get('/member/:id') | ||
65 | + async findMemberOne(@Param('id') id: number): Promise<MemberEntity> { | ||
66 | + const foundMember = await this.droneService.findMemberOne(id); | ||
67 | + return Object.assign({ | ||
68 | + data: foundMember, | ||
69 | + statusCode: 200, | ||
70 | + statusMsg: '완료', | ||
71 | + }); | ||
72 | + } | ||
73 | + | ||
74 | + @Get('/drone/list') | ||
75 | + async findDroneAll(@Query() queries): Promise<DroneEntity[]> { | ||
76 | + // Params 변수 | ||
77 | + const modelName = queries.modelName; | ||
78 | + const maker = queries.maker; | ||
79 | + const usageName = queries.usageName; | ||
80 | + const minWeight = queries.minWeight; | ||
81 | + const maxWeight = queries.maxWeight; | ||
82 | + | ||
83 | + const droneList = await this.droneService.findDroneAll({ | ||
84 | + modelName, | ||
85 | + maker, | ||
86 | + usageName, | ||
87 | + minWeight, | ||
88 | + maxWeight, | ||
89 | + }); | ||
90 | + | ||
91 | + return Object.assign({ | ||
92 | + data: { | ||
93 | + drones: droneList, | ||
94 | + }, | ||
95 | + statusCode: 200, | ||
96 | + statusMsg: '완료', | ||
97 | + }); | ||
98 | + } | ||
99 | + | ||
100 | + @Get('/drone/page') | ||
101 | + async findDronePagination(@Query() queries): Promise<DroneEntity[]> { | ||
102 | + const modelName = queries.modelName; | ||
103 | + const maker = queries.maker; | ||
104 | + const usageName = queries.usageName; | ||
105 | + const minWeight = queries.minWeight; | ||
106 | + const maxWeight = queries.maxWeight; | ||
107 | + const pageNo = queries.pageNo; | ||
108 | + const pageSize = queries.pageSize; | ||
109 | + | ||
110 | + const droneList = await this.droneService.findDroneAll({ | ||
111 | + modelName, | ||
112 | + maker, | ||
113 | + usageName, | ||
114 | + minWeight, | ||
115 | + maxWeight, | ||
116 | + pageNo, | ||
117 | + pageSize, | ||
118 | + }); | ||
119 | + const totalElement = await this.droneService.findDroneTotalElement({ | ||
120 | + modelName, | ||
121 | + maker, | ||
122 | + usageName, | ||
123 | + minWeight, | ||
124 | + maxWeight, | ||
125 | + }); | ||
126 | + | ||
127 | + return Object.assign({ | ||
128 | + data: { | ||
129 | + pageNo: pageNo, | ||
130 | + pageSize: pageSize, | ||
131 | + totalElement: totalElement, | ||
132 | + drones: droneList, | ||
133 | + }, | ||
134 | + statusCode: 200, | ||
135 | + statusMsg: '완료', | ||
136 | + }); | ||
137 | + } | ||
138 | + | ||
139 | + @Get('/drone/:id') | ||
140 | + async findDroneOne(@Param('id') id: number): Promise<DroneEntity> { | ||
141 | + const foundDrone = await this.droneService.findDroneOne(id); | ||
142 | + return Object.assign({ | ||
143 | + data: foundDrone, | ||
144 | + statusCode: 200, | ||
145 | + statusMsg: '완료', | ||
146 | + }); | ||
147 | + } | ||
148 | + | ||
149 | + @Get('/map/drone/:id') | ||
150 | + async findMapDroneOne(@Param('id') id: number) { | ||
151 | + const foundDrone = await this.droneService.findDroneOne(id); | ||
152 | + const foundSchedule = await this.droneService.findScheduleByDroneId(id); | ||
153 | + | ||
154 | + if (!foundSchedule) { | ||
155 | + return Object.assign({ | ||
156 | + data: { | ||
157 | + foundDrone, | ||
158 | + foundSchedule: null, | ||
159 | + droneLogs: [], | ||
160 | + }, | ||
161 | + statusCode: 200, | ||
162 | + statusMsg: '완료', | ||
163 | + }); | ||
164 | + } | ||
165 | + | ||
166 | + const logList = await this.droneService.findLogListByScheduleId( | ||
167 | + foundSchedule.scheduleId, | ||
168 | + ); | ||
169 | + | ||
170 | + return Object.assign({ | ||
171 | + data: { | ||
172 | + foundDrone, | ||
173 | + foundSchedule, | ||
174 | + droneLogs: logList, | ||
175 | + }, | ||
176 | + statusCode: 200, | ||
177 | + statusMsg: '완료', | ||
178 | + }); | ||
179 | + } | ||
180 | + | ||
181 | + @Get('/log/list') | ||
182 | + async findLogAll(@Query() queries): Promise<DroneLogEntity[]> { | ||
183 | + const scheduleId = queries.scheduleId; | ||
184 | + const latitude = queries.latitude; | ||
185 | + const longitude = queries.longitude; | ||
186 | + const minVerticalSpeed = queries.minVerticalSpeed; | ||
187 | + const maxVerticalSpeed = queries.maxVerticalSpeed; | ||
188 | + const minHorizontalSpeed = queries.minHorizontalSpeed; | ||
189 | + const maxHorizontalSpeed = queries.maxHorizontalSpeed; | ||
190 | + const minAboveSeaLevel = queries.minAboveSeaLevel; | ||
191 | + const maxAboveSeaLevel = queries.maxAboveSeaLevel; | ||
192 | + const minAboveGroundLevel = queries.minAboveGroundLevel; | ||
193 | + const maxAboveGroundLevel = queries.maxAboveGroundLevel; | ||
194 | + | ||
195 | + const logList = await this.droneService.findLogAll({ | ||
196 | + scheduleId, | ||
197 | + latitude, | ||
198 | + longitude, | ||
199 | + minVerticalSpeed, | ||
200 | + maxVerticalSpeed, | ||
201 | + minHorizontalSpeed, | ||
202 | + maxHorizontalSpeed, | ||
203 | + minAboveSeaLevel, | ||
204 | + maxAboveSeaLevel, | ||
205 | + minAboveGroundLevel, | ||
206 | + maxAboveGroundLevel, | ||
207 | + }); | ||
208 | + | ||
209 | + return Object.assign({ | ||
210 | + data: { | ||
211 | + droneLogs: logList, | ||
212 | + }, | ||
213 | + statusCode: 200, | ||
214 | + statusMsg: '완료', | ||
215 | + }); | ||
216 | + } | ||
217 | + | ||
218 | + @Get('/log/page') | ||
219 | + async findLogPagination(@Query() queries): Promise<DroneLogEntity[]> { | ||
220 | + const scheduleId = queries.scheduleId; | ||
221 | + const latitude = queries.latitude; | ||
222 | + const longitude = queries.longitude; | ||
223 | + const minVerticalSpeed = queries.minVerticalSpeed; | ||
224 | + const maxVerticalSpeed = queries.maxVerticalSpeed; | ||
225 | + const minHorizontalSpeed = queries.minHorizontalSpeed; | ||
226 | + const maxHorizontalSpeed = queries.maxHorizontalSpeed; | ||
227 | + const minAboveSeaLevel = queries.minAboveSeaLevel; | ||
228 | + const maxAboveSeaLevel = queries.maxAboveSeaLevel; | ||
229 | + const minAboveGroundLevel = queries.minAboveGroundLevel; | ||
230 | + const maxAboveGroundLevel = queries.maxAboveGroundLevel; | ||
231 | + const pageNo = queries.pageNo; | ||
232 | + const pageSize = queries.pageSize; | ||
233 | + | ||
234 | + const logList = await this.droneService.findLogAll({ | ||
235 | + scheduleId, | ||
236 | + latitude, | ||
237 | + longitude, | ||
238 | + minVerticalSpeed, | ||
239 | + maxVerticalSpeed, | ||
240 | + minHorizontalSpeed, | ||
241 | + maxHorizontalSpeed, | ||
242 | + minAboveSeaLevel, | ||
243 | + maxAboveSeaLevel, | ||
244 | + minAboveGroundLevel, | ||
245 | + maxAboveGroundLevel, | ||
246 | + pageNo, | ||
247 | + pageSize, | ||
248 | + }); | ||
249 | + const totalElement = await this.droneService.findLogTotalElement({ | ||
250 | + scheduleId, | ||
251 | + latitude, | ||
252 | + longitude, | ||
253 | + minVerticalSpeed, | ||
254 | + maxVerticalSpeed, | ||
255 | + minHorizontalSpeed, | ||
256 | + maxHorizontalSpeed, | ||
257 | + minAboveSeaLevel, | ||
258 | + maxAboveSeaLevel, | ||
259 | + minAboveGroundLevel, | ||
260 | + maxAboveGroundLevel, | ||
261 | + }); | ||
262 | + | ||
263 | + return Object.assign({ | ||
264 | + data: { | ||
265 | + pageNo: pageNo, | ||
266 | + pageSize: pageSize, | ||
267 | + totalElement: totalElement, | ||
268 | + droneLogs: logList, | ||
269 | + }, | ||
270 | + statusCode: 200, | ||
271 | + statusMsg: '완료', | ||
272 | + }); | ||
273 | + } | ||
274 | + | ||
275 | + @Get('/log/:id') | ||
276 | + async findLogOne(@Param('id') id: number): Promise<DroneLogEntity> { | ||
277 | + const foundLog = await this.droneService.findLogOne(id); | ||
278 | + return Object.assign({ | ||
279 | + data: foundLog, | ||
280 | + statusCode: 200, | ||
281 | + statusMsg: '완료', | ||
282 | + }); | ||
283 | + } | ||
284 | + | ||
285 | + @Get('/schedule/list') | ||
286 | + async findScheduleAll(@Query() queries): Promise<ScheduleEntity[]> { | ||
287 | + const droneId = queries.droneId; | ||
288 | + const startTime = queries.startTime; | ||
289 | + const terminateTime = queries.terminateTime; | ||
290 | + | ||
291 | + const scheduleList = await this.droneService.findScheduleAll({ | ||
292 | + droneId, | ||
293 | + startTime, | ||
294 | + terminateTime, | ||
295 | + }); | ||
296 | + return Object.assign({ | ||
297 | + data: { | ||
298 | + schedules: scheduleList, | ||
299 | + }, | ||
300 | + statusCode: 200, | ||
301 | + statusMsg: '완료', | ||
302 | + }); | ||
303 | + } | ||
304 | + | ||
305 | + @Get('/schedule/page') | ||
306 | + async findSchedulePagination(@Query() queries): Promise<ScheduleEntity[]> { | ||
307 | + const droneId = queries.droneId; | ||
308 | + const startTime = queries.startTime; | ||
309 | + const terminateTime = queries.terminateTime; | ||
310 | + const pageNo = queries.pageNo; | ||
311 | + const pageSize = queries.pageSize; | ||
312 | + | ||
313 | + const scheduleList = await this.droneService.findScheduleAll({ | ||
314 | + droneId, | ||
315 | + startTime, | ||
316 | + terminateTime, | ||
317 | + pageNo, | ||
318 | + pageSize, | ||
319 | + }); | ||
320 | + const totalElement = await this.droneService.findScheduleTotalElement({ | ||
321 | + droneId, | ||
322 | + startTime, | ||
323 | + terminateTime, | ||
324 | + }); | ||
325 | + | ||
326 | + return Object.assign({ | ||
327 | + data: { | ||
328 | + pageNo: pageNo, | ||
329 | + pageSize: pageSize, | ||
330 | + totalElement: totalElement, | ||
331 | + schedules: scheduleList, | ||
332 | + }, | ||
333 | + statusCode: 200, | ||
334 | + statusMsg: '완료', | ||
335 | + }); | ||
336 | + } | ||
337 | + | ||
338 | + @Get('/schedule/:id') | ||
339 | + async findScheduleOne(@Param('id') id: number): Promise<ScheduleEntity> { | ||
340 | + const foundSchedule = await this.droneService.findScheduleOne(id); | ||
341 | + return Object.assign({ | ||
342 | + data: foundSchedule, | ||
343 | + statusCode: 200, | ||
344 | + statusMsg: '완료', | ||
345 | + }); | ||
346 | + } | ||
347 | + | ||
348 | + @Get('/code/list') | ||
349 | + async findCodeAll(): Promise<CodeEntity[]> { | ||
350 | + const codeList = await this.droneService.findCodeAll(); | ||
351 | + return Object.assign({ | ||
352 | + data: { | ||
353 | + codes: codeList, | ||
354 | + }, | ||
355 | + statusCode: 200, | ||
356 | + statusMsg: '완료', | ||
357 | + }); | ||
358 | + } | ||
359 | + | ||
360 | + @Get('/code/page') | ||
361 | + async findCodePagination(@Query() queries): Promise<CodeEntity[]> { | ||
362 | + const pageNo = queries.pageNo; | ||
363 | + const pageSize = queries.pageSize; | ||
364 | + | ||
365 | + const begin = (pageNo - 1) * pageSize; | ||
366 | + const end = pageNo * pageSize; | ||
367 | + | ||
368 | + const codeList = await (await this.droneService.findCodeAll()).slice( | ||
369 | + begin, | ||
370 | + end, | ||
371 | + ); | ||
372 | + | ||
373 | + const totalElement = (await this.droneService.findCodeAll()).length; | ||
374 | + return Object.assign({ | ||
375 | + data: { | ||
376 | + pageNo: pageNo, | ||
377 | + pageSize: pageSize, | ||
378 | + totalElement: totalElement, | ||
379 | + codes: codeList, | ||
380 | + }, | ||
381 | + statusCode: 200, | ||
382 | + statusMsg: '완료', | ||
383 | + }); | ||
384 | + } | ||
385 | + | ||
386 | + @Get('/code/:id') | ||
387 | + async findCodeOne(@Param('id') id: number): Promise<CodeEntity> { | ||
388 | + const foundCode = await this.droneService.findCodeOne(id); | ||
389 | + return Object.assign({ | ||
390 | + data: foundCode, | ||
391 | + statusCode: 200, | ||
392 | + statusMsg: '완료', | ||
393 | + }); | ||
394 | + } | ||
395 | + | ||
396 | + @Post() | ||
397 | + async saveMember(@Body() memberEntity: MemberEntity): Promise<string> { | ||
398 | + await this.droneService.saveMember(memberEntity); | ||
399 | + return Object.assign({ | ||
400 | + statusCode: 201, | ||
401 | + data: { ...memberEntity }, | ||
402 | + statusMsg: '완료', | ||
403 | + }); | ||
404 | + } | ||
405 | + | ||
406 | + @Post() | ||
407 | + async saveDrone(@Body() droneEntity: DroneEntity): Promise<string> { | ||
408 | + await this.droneService.saveDrone(droneEntity); | ||
409 | + return Object.assign({ | ||
410 | + statusCode: 201, | ||
411 | + data: { ...droneEntity }, | ||
412 | + statusMsg: '완료', | ||
413 | + }); | ||
414 | + } | ||
415 | + | ||
416 | + @Post('drone/list') | ||
417 | + @UsePipes(new ValidationPipe({ transform: true })) | ||
418 | + async saveDroneList(@Body() saveDroneListDto: DroneApiDto.SaveDroneListDto) { | ||
419 | + const affectedRows = await this.droneService.saveDroneList( | ||
420 | + saveDroneListDto.droneList, | ||
421 | + ); | ||
422 | + return { | ||
423 | + statusCode: 201, | ||
424 | + data: { affectedRows }, | ||
425 | + statusMessage: '완료', | ||
426 | + }; | ||
427 | + } | ||
428 | + | ||
429 | + @Put('drone/list') | ||
430 | + @UsePipes(new ValidationPipe({ transform: true })) | ||
431 | + async updateDroneList( | ||
432 | + @Body() updateDroneListDto: DroneApiDto.UpdateDroneListDto, | ||
433 | + ) { | ||
434 | + const affectedRows = await this.droneService.updateDroneList( | ||
435 | + updateDroneListDto.droneList, | ||
436 | + ); | ||
437 | + return { | ||
438 | + statusCode: 201, | ||
439 | + data: { affectedRows }, | ||
440 | + statusMessage: '완료', | ||
441 | + }; | ||
442 | + } | ||
443 | + | ||
444 | + @Post() | ||
445 | + async saveLog(@Body() dronelogEntity: DroneLogEntity): Promise<string> { | ||
446 | + await this.droneService.saveLog(dronelogEntity); | ||
447 | + return Object.assign({ | ||
448 | + statusCode: 201, | ||
449 | + data: { ...dronelogEntity }, | ||
450 | + statusMsg: '완료', | ||
451 | + }); | ||
452 | + } | ||
453 | + | ||
454 | + @Post() | ||
455 | + async saveSchedule(@Body() ScheduleEntity: ScheduleEntity): Promise<string> { | ||
456 | + await this.droneService.saveSchedule(ScheduleEntity); | ||
457 | + return Object.assign({ | ||
458 | + statusCode: 201, | ||
459 | + data: { ...ScheduleEntity }, | ||
460 | + statusMsg: '완료', | ||
461 | + }); | ||
462 | + } | ||
463 | + | ||
464 | + @Post('schdule/list') | ||
465 | + @UsePipes(new ValidationPipe({ transform: true })) | ||
466 | + async saveSchduleList( | ||
467 | + @Body() saveSchduleListDto: DroneApiDto.SaveSchduleListDto, | ||
468 | + ) { | ||
469 | + const affectedRows = await this.droneService.saveScheduleList( | ||
470 | + saveSchduleListDto.schduleList, | ||
471 | + ); | ||
472 | + return { | ||
473 | + statusCode: 200, | ||
474 | + data: { affectedRows }, | ||
475 | + statusMessage: '완료', | ||
476 | + }; | ||
477 | + } | ||
478 | + | ||
479 | + @Put('schdule/list') | ||
480 | + @UsePipes(new ValidationPipe({ transform: true })) | ||
481 | + async updateSchduleList( | ||
482 | + @Body() updateSchduleListDto: DroneApiDto.UpdateSchduleListDto, | ||
483 | + ) { | ||
484 | + const affectedRows = await this.droneService.updateSchduleList( | ||
485 | + updateSchduleListDto.schduleList, | ||
486 | + ); | ||
487 | + return { | ||
488 | + statusCode: 201, | ||
489 | + data: { affectedRows }, | ||
490 | + statusMessage: '완료', | ||
491 | + }; | ||
492 | + } | ||
493 | + | ||
494 | + @Post() | ||
495 | + async saveCode(@Body() CodeEntity: CodeEntity): Promise<string> { | ||
496 | + await this.droneService.saveCode(CodeEntity); | ||
497 | + return Object.assign({ | ||
498 | + statusCode: 201, | ||
499 | + data: { ...CodeEntity }, | ||
500 | + statusMsg: '완료', | ||
501 | + }); | ||
502 | + } | ||
503 | + | ||
504 | + @Delete('/member/:id') | ||
505 | + async deleteMember(@Param('id') id: number): Promise<string> { | ||
506 | + await this.droneService.deleteMember(id); | ||
507 | + return Object.assign({ | ||
508 | + statusCode: 201, | ||
509 | + data: { | ||
510 | + id: id, | ||
511 | + }, | ||
512 | + statusMsg: '완료', | ||
513 | + }); | ||
514 | + } | ||
515 | + | ||
516 | + @Delete('/drone/:id') | ||
517 | + async deleteDrone(@Param('id') id: number): Promise<string> { | ||
518 | + await this.droneService.deleteDrone(id); | ||
519 | + return Object.assign({ | ||
520 | + statusCode: 201, | ||
521 | + data: { | ||
522 | + id: id, | ||
523 | + }, | ||
524 | + statusMsg: '완료', | ||
525 | + }); | ||
526 | + } | ||
527 | + | ||
528 | + @Delete('/log/:id') | ||
529 | + async deleteLog(@Param('id') id: number): Promise<string> { | ||
530 | + await this.droneService.deleteLog(id); | ||
531 | + return Object.assign({ | ||
532 | + statusCode: 201, | ||
533 | + data: { | ||
534 | + id: id, | ||
535 | + }, | ||
536 | + statusMsg: '완료', | ||
537 | + }); | ||
538 | + } | ||
539 | + | ||
540 | + @Delete('/schedule/:id') | ||
541 | + async deleteSchedule(@Param('id') id: number): Promise<string> { | ||
542 | + await this.droneService.deleteSchedule(id); | ||
543 | + return Object.assign({ | ||
544 | + statusCode: 201, | ||
545 | + data: { | ||
546 | + id: id, | ||
547 | + }, | ||
548 | + statusMsg: '완료', | ||
549 | + }); | ||
550 | + } | ||
551 | + | ||
552 | + @Delete('/code/:id') | ||
553 | + async deleteCode(@Param('id') id: number): Promise<string> { | ||
554 | + await this.droneService.deleteCode(id); | ||
555 | + return Object.assign({ | ||
556 | + statusCode: 201, | ||
557 | + data: { | ||
558 | + id: id, | ||
559 | + }, | ||
560 | + statusMsg: '완료', | ||
561 | + }); | ||
562 | + } | ||
563 | +} |
Backend/src/drone/drone.data.controller.ts
0 → 100644
1 | +import { | ||
2 | + Body, | ||
3 | + Controller, | ||
4 | + Post, | ||
5 | + UsePipes, | ||
6 | + ValidationPipe, | ||
7 | +} from '@nestjs/common'; | ||
8 | +import { DroneDataService } from 'src/drone/drone.data.service'; | ||
9 | +import { DroneApiDto } from 'src/drone/dto/drone.api.dto'; | ||
10 | + | ||
11 | +@Controller() | ||
12 | +export class DroneDataController { | ||
13 | + constructor(private droneDataService: DroneDataService) { | ||
14 | + this.droneDataService = droneDataService; | ||
15 | + } | ||
16 | + | ||
17 | + @Post('/droneLog/list') | ||
18 | + @UsePipes(new ValidationPipe({ transform: true })) | ||
19 | + async saveDroneLogList( | ||
20 | + @Body() saveDroneLogListDto: DroneApiDto.SaveDroneLogListDto, | ||
21 | + ) { | ||
22 | + await this.droneDataService.saveDroneLogList( | ||
23 | + saveDroneLogListDto.droneLogList, | ||
24 | + ); | ||
25 | + return { | ||
26 | + statusCode: 200, | ||
27 | + statusMsg: '완료', | ||
28 | + }; | ||
29 | + } | ||
30 | +} |
Backend/src/drone/drone.data.service.ts
0 → 100644
1 | +import { Injectable } from '@nestjs/common'; | ||
2 | +import { InjectRepository } from '@nestjs/typeorm'; | ||
3 | +import { DroneLogEntity } from 'src/entities/drone.log.entity'; | ||
4 | +import { DroneGateway } from 'src/drone/drone.gateway'; | ||
5 | +import { Repository } from 'typeorm/index'; | ||
6 | +import { DroneApiDto } from 'src/drone/dto/drone.api.dto'; | ||
7 | + | ||
8 | +@Injectable() | ||
9 | +export class DroneDataService { | ||
10 | + constructor( | ||
11 | + @InjectRepository(DroneLogEntity) | ||
12 | + private dronelogRepository: Repository<DroneLogEntity>, | ||
13 | + private droneGateway: DroneGateway, | ||
14 | + ) { | ||
15 | + this.dronelogRepository = dronelogRepository; | ||
16 | + this.droneGateway = droneGateway; | ||
17 | + } | ||
18 | + | ||
19 | + async saveDroneLogList(droneLogList: DroneApiDto.SaveDroneLogDto[]) { | ||
20 | + // 드론 데이터 전송 | ||
21 | + this.sendDroneLogList(droneLogList.map((log) => new DroneLogEntity(log))); | ||
22 | + | ||
23 | + // 드론로그 생성 | ||
24 | + for (const droneLog of droneLogList) { | ||
25 | + this.dronelogRepository.save({ | ||
26 | + droneId: droneLog.droneId, | ||
27 | + scheduleId: droneLog.scheduleId, | ||
28 | + latitude: droneLog.latitude, | ||
29 | + longitude: droneLog.longitude, | ||
30 | + verticalSpeed: droneLog.verticalSpeed, | ||
31 | + horizontalSpeed: droneLog.horizontalSpeed, | ||
32 | + aboveSeaLevel: droneLog.aboveSeaLevel, | ||
33 | + aboveGroundLevel: droneLog.aboveGroundLevel, | ||
34 | + }); | ||
35 | + } | ||
36 | + } | ||
37 | + | ||
38 | + sendDroneLogList(droneLogList) { | ||
39 | + // 드론데이터 전송 | ||
40 | + this.droneGateway.sendToClientsDroneLogList(droneLogList); | ||
41 | + for (const droneLog of droneLogList) { | ||
42 | + let droneLogEntity = new DroneLogEntity(droneLog); | ||
43 | + this.dronelogRepository.save(droneLogEntity); | ||
44 | + } | ||
45 | + } | ||
46 | + | ||
47 | + async saveLog(droneLogEntity: DroneLogEntity): Promise<void> { | ||
48 | + await this.dronelogRepository.save(droneLogEntity); | ||
49 | + } | ||
50 | +} |
Backend/src/drone/drone.gateway.ts
0 → 100644
1 | +import { | ||
2 | + SubscribeMessage, | ||
3 | + WebSocketGateway, | ||
4 | + WebSocketServer, | ||
5 | +} from '@nestjs/websockets'; | ||
6 | +import { Server, Socket } from 'ws'; | ||
7 | +import { DroneService } from './drone.service'; | ||
8 | +import { DroneLogEntity } from 'src/entities/drone.log.entity'; | ||
9 | + | ||
10 | +async function sleep(ms) { | ||
11 | + return new Promise((resolve) => { | ||
12 | + setTimeout(resolve, ms); | ||
13 | + }); | ||
14 | +} | ||
15 | + | ||
16 | +@WebSocketGateway(20206, { path: '/drone' }) | ||
17 | +export class DroneGateway { | ||
18 | + constructor(private readonly droneService: DroneService) {} | ||
19 | + | ||
20 | + @WebSocketServer() | ||
21 | + server: Server; | ||
22 | + | ||
23 | + private wsClients = []; | ||
24 | + | ||
25 | + async afterInit() { | ||
26 | + let globalCircleStep = 0; | ||
27 | + while (1) { | ||
28 | + if (globalCircleStep === 3600) { | ||
29 | + globalCircleStep = 0; | ||
30 | + } else { | ||
31 | + globalCircleStep += 1; | ||
32 | + } | ||
33 | + this.wsClients.forEach((client) => { | ||
34 | + const droneTestData = this.droneService.getDroneTestData({ | ||
35 | + globalCircleStep, | ||
36 | + }); | ||
37 | + client.send( | ||
38 | + JSON.stringify( | ||
39 | + Object.assign({ | ||
40 | + data: { droneLog: droneTestData }, | ||
41 | + statusCode: 200, | ||
42 | + }), | ||
43 | + ), | ||
44 | + ); | ||
45 | + }); | ||
46 | + await sleep(1000); | ||
47 | + } | ||
48 | + } | ||
49 | + | ||
50 | + async handleConnection(client: Socket) { | ||
51 | + let clientId = this.wsClients.push(client); | ||
52 | + } | ||
53 | + | ||
54 | + handleDisconnect(client: Socket) { | ||
55 | + let clientIndex = this.wsClients.find((wsClient) => wsClient === client); | ||
56 | + if (clientIndex !== -1) { | ||
57 | + this.wsClients.splice(clientIndex, 1); | ||
58 | + } | ||
59 | + } | ||
60 | + | ||
61 | + @SubscribeMessage('') | ||
62 | + handleMessages(client: Socket, payload: { name: string; text: string }) { | ||
63 | + client.send( | ||
64 | + JSON.stringify({ | ||
65 | + number: 10, | ||
66 | + member: 'tony', | ||
67 | + }), | ||
68 | + ); | ||
69 | + } | ||
70 | + | ||
71 | + sendToClientsDroneLogList(droneLogList: Array<DroneLogEntity>) { | ||
72 | + this.wsClients.forEach((client) => { | ||
73 | + client.send( | ||
74 | + JSON.stringify( | ||
75 | + Object.assign({ | ||
76 | + data: { droneLog: droneLogList }, | ||
77 | + statusCode: 200, | ||
78 | + }), | ||
79 | + ), | ||
80 | + ); | ||
81 | + }); | ||
82 | + } | ||
83 | +} |
Backend/src/drone/drone.module.ts
0 → 100644
1 | +import { Module } from '@nestjs/common'; | ||
2 | +import { TypeOrmModule } from '@nestjs/typeorm'; | ||
3 | +import { DroneController } from './drone.controller'; | ||
4 | +import { DroneService } from './drone.service'; | ||
5 | +import { DroneDataController } from 'src/drone/drone.data.controller'; | ||
6 | +import { DroneDataService } from './drone.data.service'; | ||
7 | +import { DroneGateway } from './drone.gateway'; | ||
8 | +import { DroneEntity } from 'src/entities/drone.entity'; | ||
9 | +import { ScheduleEntity } from 'src/entities/schedule.entity'; | ||
10 | +import { DroneLogEntity } from 'src/entities/drone.log.entity'; | ||
11 | +import { MemberEntity } from 'src/entities/member.entity'; | ||
12 | +import { CodeEntity } from 'src/entities/code.entity'; | ||
13 | +import { DroneScheduleMappingEntity } from 'src/entities/drone.schedule.mapping.entity'; | ||
14 | + | ||
15 | +@Module({ | ||
16 | + imports: [ | ||
17 | + TypeOrmModule.forFeature([ | ||
18 | + MemberEntity, | ||
19 | + DroneEntity, | ||
20 | + DroneLogEntity, | ||
21 | + ScheduleEntity, | ||
22 | + CodeEntity, | ||
23 | + DroneScheduleMappingEntity, | ||
24 | + ]), | ||
25 | + ], | ||
26 | + controllers: [DroneController, DroneDataController], | ||
27 | + providers: [DroneGateway, DroneService, DroneDataService], | ||
28 | +}) | ||
29 | +export class DroneModule {} |
Backend/src/drone/drone.service.ts
0 → 100644
1 | +import { Injectable, HttpException } from '@nestjs/common'; | ||
2 | +import { InjectRepository } from '@nestjs/typeorm'; | ||
3 | +import { DroneEntity } from 'src/entities/drone.entity'; | ||
4 | +import { DroneLogEntity } from 'src/entities/drone.log.entity'; | ||
5 | +import { ScheduleEntity } from 'src/entities/schedule.entity'; | ||
6 | +import { MemberEntity } from 'src/entities/member.entity'; | ||
7 | +import { DroneApiDto } from 'src/drone/dto/drone.api.dto'; | ||
8 | +import { DroneLogDto } from 'src/drone/dto/droneLog.dto'; | ||
9 | +import { getRepository, Repository } from 'typeorm/index'; | ||
10 | +import { DroneScheduleMappingEntity } from 'src/entities/drone.schedule.mapping.entity'; | ||
11 | +import { CodeEntity } from 'src/entities/code.entity'; | ||
12 | + | ||
13 | +@Injectable() | ||
14 | +export class DroneService { | ||
15 | + constructor( | ||
16 | + @InjectRepository(MemberEntity) | ||
17 | + private memberRepository: Repository<MemberEntity>, | ||
18 | + @InjectRepository(DroneEntity) | ||
19 | + private droneRepository: Repository<DroneEntity>, | ||
20 | + @InjectRepository(DroneLogEntity) | ||
21 | + private dronelogRepository: Repository<DroneLogEntity>, | ||
22 | + @InjectRepository(ScheduleEntity) | ||
23 | + private scheduleRepository: Repository<ScheduleEntity>, | ||
24 | + @InjectRepository(CodeEntity) | ||
25 | + private codeRepository: Repository<CodeEntity>, | ||
26 | + @InjectRepository(DroneScheduleMappingEntity) | ||
27 | + private droneschedulemappingRepository: Repository<DroneScheduleMappingEntity>, | ||
28 | + ) { | ||
29 | + this.memberRepository = memberRepository; | ||
30 | + this.droneRepository = droneRepository; | ||
31 | + this.dronelogRepository = dronelogRepository; | ||
32 | + this.scheduleRepository = scheduleRepository; | ||
33 | + this.codeRepository = codeRepository; | ||
34 | + this.droneschedulemappingRepository = droneschedulemappingRepository; | ||
35 | + } | ||
36 | + | ||
37 | + droneMap = {}; | ||
38 | + | ||
39 | + // test Drone Data | ||
40 | + droneCount: number = 1200; | ||
41 | + | ||
42 | + getRandomArbitrary(min: number, max: number): number { | ||
43 | + return Math.random() * (max - min) + min; | ||
44 | + } | ||
45 | + | ||
46 | + circleMove( | ||
47 | + x: number, | ||
48 | + y: number, | ||
49 | + radius: number, | ||
50 | + max: number, | ||
51 | + circleStep: number, | ||
52 | + ) { | ||
53 | + return { | ||
54 | + x: x + radius * Math.cos((2 * Math.PI * circleStep) / max), | ||
55 | + y: y + radius * Math.sin((2 * Math.PI * circleStep) / max), | ||
56 | + }; | ||
57 | + } | ||
58 | + | ||
59 | + logData: Array<{ x: number; y: number; id: number }> = Array.from( | ||
60 | + { length: this.droneCount }, | ||
61 | + (v, i) => { | ||
62 | + return { | ||
63 | + x: | ||
64 | + this.getRandomArbitrary(37200000000000, 37300000000000) / | ||
65 | + 1000000000000, | ||
66 | + y: | ||
67 | + this.getRandomArbitrary(126900000000000, 127100000000000) / | ||
68 | + 1000000000000, | ||
69 | + id: i, | ||
70 | + }; | ||
71 | + }, | ||
72 | + ); | ||
73 | + | ||
74 | + getDroneTestData({ globalCircleStep }) { | ||
75 | + const cordData = []; | ||
76 | + for (let i = 0; i < this.droneCount; i += 1) { | ||
77 | + const circleCord: { x: number; y: number } = this.circleMove( | ||
78 | + this.logData[i].x, | ||
79 | + this.logData[i].y, | ||
80 | + 0.05, | ||
81 | + 3600, | ||
82 | + globalCircleStep, | ||
83 | + ); | ||
84 | + cordData.push({ | ||
85 | + droneId: this.logData[i].id, | ||
86 | + scheduleId: this.logData[i].id, | ||
87 | + latitude: circleCord.x, | ||
88 | + longitude: circleCord.y, | ||
89 | + verticalSpeed: Math.round(Math.random() * 100), | ||
90 | + horizontalSpeed: Math.round(Math.random() * 100), | ||
91 | + aboveSeaLevel: 22, | ||
92 | + aboveGroundLevel: 33, | ||
93 | + }); | ||
94 | + } | ||
95 | + | ||
96 | + return cordData; | ||
97 | + } | ||
98 | + | ||
99 | + // 드론ID로 스케줄 찾기 | ||
100 | + async findScheduleByDroneId(droneId: number) { | ||
101 | + const scheduleList = getRepository(DroneScheduleMappingEntity) | ||
102 | + .createQueryBuilder('droneSchedule') | ||
103 | + .select('droneSchedule.scheduleId', 'scheduleId') | ||
104 | + .addSelect('schedule.startTime', 'startTime') | ||
105 | + .addSelect('schedule.terminateTime', 'terminateTime') | ||
106 | + .addSelect('schedule.startLatitude', 'startLatitude') | ||
107 | + .addSelect('schedule.startLongitude', 'startLongitude') | ||
108 | + .addSelect('schedule.terminateLatitude', 'terminateLatitude') | ||
109 | + .addSelect('schedule.terminateLongitude', 'terminateLongitude') | ||
110 | + .innerJoin( | ||
111 | + ScheduleEntity, | ||
112 | + 'schedule', | ||
113 | + 'droneSchedule.scheduleId = schedule.id', | ||
114 | + ) | ||
115 | + .where('droneSchedule.droneId = :droneId', { | ||
116 | + droneId: droneId, | ||
117 | + }) | ||
118 | + .andWhere('schedule.startTime <= NOW()') | ||
119 | + .andWhere('schedule.terminateTime >= NOW()') | ||
120 | + .orderBy('droneSchedule.scheduleId', 'DESC') | ||
121 | + .getRawOne(); | ||
122 | + | ||
123 | + return scheduleList; | ||
124 | + } | ||
125 | + | ||
126 | + // 스케줄ID로 로그 찾기 | ||
127 | + async findLogListByScheduleId(scheduleId: number) { | ||
128 | + const logList = getRepository(DroneLogEntity) | ||
129 | + .createQueryBuilder('droneLog') | ||
130 | + .where('droneLog.scheduleId = :scheduleId', { | ||
131 | + scheduleId: scheduleId, | ||
132 | + }) | ||
133 | + .orderBy('droneLog.id', 'DESC') | ||
134 | + .getMany(); | ||
135 | + | ||
136 | + return logList; | ||
137 | + } | ||
138 | + | ||
139 | + findMemberAll(): Promise<MemberEntity[]> { | ||
140 | + return this.memberRepository.find(); | ||
141 | + } | ||
142 | + | ||
143 | + async findDroneAll({ | ||
144 | + modelName, | ||
145 | + maker, | ||
146 | + usageName, | ||
147 | + minWeight, | ||
148 | + maxWeight, | ||
149 | + pageNo, | ||
150 | + pageSize, | ||
151 | + }: { | ||
152 | + modelName?: string; | ||
153 | + maker?: string; | ||
154 | + usageName?: string; | ||
155 | + minWeight?: number; | ||
156 | + maxWeight?: number; | ||
157 | + pageNo?: number; | ||
158 | + pageSize?: number; | ||
159 | + }): Promise<DroneEntity[]> { | ||
160 | + const droneList = getRepository(DroneEntity).createQueryBuilder('drone'); | ||
161 | + | ||
162 | + if (modelName) { | ||
163 | + droneList.where('drone.modelName like :modelName', { | ||
164 | + modelName: `%${modelName}%`, | ||
165 | + }); | ||
166 | + } | ||
167 | + | ||
168 | + if (maker) { | ||
169 | + droneList.andWhere('drone.maker like :maker', { maker: `%${maker}%` }); | ||
170 | + } | ||
171 | + | ||
172 | + if (usageName) { | ||
173 | + droneList.andWhere('drone.usageName = :usageName', { | ||
174 | + usageName: usageName, | ||
175 | + }); | ||
176 | + } | ||
177 | + | ||
178 | + if (minWeight) { | ||
179 | + droneList.andWhere('drone.weight >= :minWeight', { | ||
180 | + minWeight: minWeight, | ||
181 | + }); | ||
182 | + } | ||
183 | + | ||
184 | + if (maxWeight) { | ||
185 | + droneList.andWhere('drone.weight <= :maxWeight', { | ||
186 | + maxWeight: maxWeight, | ||
187 | + }); | ||
188 | + } | ||
189 | + | ||
190 | + droneList.orderBy('drone.id', 'DESC'); | ||
191 | + | ||
192 | + if (pageNo && pageSize) { | ||
193 | + droneList.offset((pageNo - 1) * pageSize).limit(pageSize); | ||
194 | + } | ||
195 | + return droneList.getMany(); | ||
196 | + } | ||
197 | + | ||
198 | + async findDroneTotalElement({ | ||
199 | + modelName, | ||
200 | + maker, | ||
201 | + usageName, | ||
202 | + minWeight, | ||
203 | + maxWeight, | ||
204 | + }: { | ||
205 | + modelName?: string; | ||
206 | + maker?: string; | ||
207 | + usageName?: string; | ||
208 | + minWeight?: number; | ||
209 | + maxWeight?: number; | ||
210 | + }) { | ||
211 | + const droneList = getRepository(DroneEntity).createQueryBuilder('drone'); | ||
212 | + | ||
213 | + if (modelName) { | ||
214 | + droneList.where('drone.modelName like :modelName', { | ||
215 | + modelName: `%${modelName}%`, | ||
216 | + }); | ||
217 | + } | ||
218 | + | ||
219 | + if (maker) { | ||
220 | + droneList.andWhere('drone.maker like :maker', { maker: `%${maker}%` }); | ||
221 | + } | ||
222 | + | ||
223 | + if (usageName) { | ||
224 | + droneList.andWhere('drone.usageName = :usageName', { | ||
225 | + usageName: usageName, | ||
226 | + }); | ||
227 | + } | ||
228 | + | ||
229 | + if (minWeight) { | ||
230 | + droneList.andWhere('drone.weight >= :minWeight', { | ||
231 | + minWeight: minWeight, | ||
232 | + }); | ||
233 | + } | ||
234 | + | ||
235 | + if (maxWeight) { | ||
236 | + droneList.andWhere('drone.weight <= :maxWeight', { | ||
237 | + maxWeight: maxWeight, | ||
238 | + }); | ||
239 | + } | ||
240 | + | ||
241 | + return droneList.getCount(); | ||
242 | + } | ||
243 | + | ||
244 | + async findLogAll({ | ||
245 | + scheduleId, | ||
246 | + latitude, | ||
247 | + longitude, | ||
248 | + minVerticalSpeed, | ||
249 | + maxVerticalSpeed, | ||
250 | + minHorizontalSpeed, | ||
251 | + maxHorizontalSpeed, | ||
252 | + minAboveSeaLevel, | ||
253 | + maxAboveSeaLevel, | ||
254 | + minAboveGroundLevel, | ||
255 | + maxAboveGroundLevel, | ||
256 | + pageNo, | ||
257 | + pageSize, | ||
258 | + }: { | ||
259 | + scheduleId?: number; | ||
260 | + latitude?: number; | ||
261 | + longitude?: string; | ||
262 | + minVerticalSpeed?: number; | ||
263 | + maxVerticalSpeed?: number; | ||
264 | + minHorizontalSpeed?: number; | ||
265 | + maxHorizontalSpeed?: number; | ||
266 | + minAboveSeaLevel?: number; | ||
267 | + maxAboveSeaLevel?: number; | ||
268 | + minAboveGroundLevel?: number; | ||
269 | + maxAboveGroundLevel?: number; | ||
270 | + pageNo?: number; | ||
271 | + pageSize?: number; | ||
272 | + }): Promise<any[]> { | ||
273 | + const logList = getRepository(DroneLogEntity) | ||
274 | + .createQueryBuilder('droneLog') | ||
275 | + .select('droneLog.id', 'id') | ||
276 | + .addSelect('drone.modelName', 'modelName') | ||
277 | + .addSelect('droneLog.droneId', 'droneId') | ||
278 | + .addSelect('droneLog.scheduleId', 'scheduleId') | ||
279 | + .addSelect('droneLog.latitude', 'latitude') | ||
280 | + .addSelect('droneLog.longitude', 'longitude') | ||
281 | + .addSelect('droneLog.verticalSpeed', 'verticalSpeed') | ||
282 | + .addSelect('droneLog.horizontalSpeed', 'horizontalSpeed') | ||
283 | + .addSelect('droneLog.aboveSeaLevel', 'aboveSeaLevel') | ||
284 | + .addSelect('droneLog.aboveGroundLevel', 'aboveGroundLevel') | ||
285 | + .addSelect('droneLog.createdAt', 'createdAt') | ||
286 | + .innerJoin(DroneEntity, 'drone', 'drone.id = droneLog.droneId'); | ||
287 | + | ||
288 | + if (scheduleId) { | ||
289 | + logList.where('droneLog.scheduleId = :scheduleId', { | ||
290 | + scheduleId: scheduleId, | ||
291 | + }); | ||
292 | + } | ||
293 | + | ||
294 | + if (latitude) { | ||
295 | + logList.andWhere('cast(droneLog.latitude as varchar) like :latitude', { | ||
296 | + latitude: `%${latitude}%`, | ||
297 | + }); | ||
298 | + } | ||
299 | + | ||
300 | + if (longitude) { | ||
301 | + logList.andWhere('cast(droneLog.longitude as varchar) like :longitude', { | ||
302 | + longitude: `%${longitude}%`, | ||
303 | + }); | ||
304 | + } | ||
305 | + | ||
306 | + if (minVerticalSpeed) { | ||
307 | + logList.andWhere('droneLog.verticalSpeed >= :minVerticalSpeed', { | ||
308 | + minVerticalSpeed: minVerticalSpeed, | ||
309 | + }); | ||
310 | + } | ||
311 | + | ||
312 | + if (maxVerticalSpeed) { | ||
313 | + logList.andWhere('droneLog.verticalSpeed <= :maxVerticalSpeed', { | ||
314 | + maxVerticalSpeed: maxVerticalSpeed, | ||
315 | + }); | ||
316 | + } | ||
317 | + | ||
318 | + if (minHorizontalSpeed) { | ||
319 | + logList.andWhere('droneLog.horizontalSpeed >= :minHorizontalSpeed', { | ||
320 | + minHorizontalSpeed: minHorizontalSpeed, | ||
321 | + }); | ||
322 | + } | ||
323 | + | ||
324 | + if (maxHorizontalSpeed) { | ||
325 | + logList.andWhere('droneLog.horizontalSpeed <= :maxHorizontalSpeed', { | ||
326 | + maxHorizontalSpeed: maxHorizontalSpeed, | ||
327 | + }); | ||
328 | + } | ||
329 | + | ||
330 | + if (minAboveSeaLevel) { | ||
331 | + logList.andWhere('droneLog.aboveSeaLevel >= :minAboveSeaLevel', { | ||
332 | + minAboveSeaLevel: minAboveSeaLevel, | ||
333 | + }); | ||
334 | + } | ||
335 | + | ||
336 | + if (maxAboveSeaLevel) { | ||
337 | + logList.andWhere('droneLog.aboveSeaLevel <= :maxAboveSeaLevel', { | ||
338 | + maxAboveSeaLevel: maxAboveSeaLevel, | ||
339 | + }); | ||
340 | + } | ||
341 | + | ||
342 | + if (minAboveGroundLevel) { | ||
343 | + logList.andWhere('droneLog.aboveGroundLevel >= :minAboveGroundLevel', { | ||
344 | + minAboveGroundLevel: minAboveGroundLevel, | ||
345 | + }); | ||
346 | + } | ||
347 | + | ||
348 | + if (maxAboveGroundLevel) { | ||
349 | + logList.andWhere('droneLog.aboveGroundLevel <= :maxAboveGroundLevel', { | ||
350 | + maxAboveGroundLevel: maxAboveGroundLevel, | ||
351 | + }); | ||
352 | + } | ||
353 | + | ||
354 | + logList.orderBy('id', 'DESC'); | ||
355 | + | ||
356 | + if (pageNo && pageSize) { | ||
357 | + logList.offset((pageNo - 1) * pageSize).limit(pageSize); | ||
358 | + } | ||
359 | + return logList.getRawMany(); | ||
360 | + } | ||
361 | + | ||
362 | + async findLogTotalElement({ | ||
363 | + scheduleId, | ||
364 | + latitude, | ||
365 | + longitude, | ||
366 | + minVerticalSpeed, | ||
367 | + maxVerticalSpeed, | ||
368 | + minHorizontalSpeed, | ||
369 | + maxHorizontalSpeed, | ||
370 | + minAboveSeaLevel, | ||
371 | + maxAboveSeaLevel, | ||
372 | + minAboveGroundLevel, | ||
373 | + maxAboveGroundLevel, | ||
374 | + }: { | ||
375 | + scheduleId?: number; | ||
376 | + latitude?: string; | ||
377 | + longitude?: string; | ||
378 | + minVerticalSpeed?: number; | ||
379 | + maxVerticalSpeed?: number; | ||
380 | + minHorizontalSpeed?: number; | ||
381 | + maxHorizontalSpeed?: number; | ||
382 | + minAboveSeaLevel?: number; | ||
383 | + maxAboveSeaLevel?: number; | ||
384 | + minAboveGroundLevel?: number; | ||
385 | + maxAboveGroundLevel?: number; | ||
386 | + }) { | ||
387 | + const logList = getRepository(DroneLogEntity) | ||
388 | + .createQueryBuilder('droneLog') | ||
389 | + .select('droneLog.id', 'id') | ||
390 | + .addSelect('drone.modelName', 'modelName') | ||
391 | + .addSelect('droneLog.droneId', 'droneId') | ||
392 | + .addSelect('droneLog.scheduleId', 'scheduleId') | ||
393 | + .addSelect('droneLog.latitude', 'latitude') | ||
394 | + .addSelect('droneLog.longitude', 'longitude') | ||
395 | + .addSelect('droneLog.verticalSpeed', 'verticalSpeed') | ||
396 | + .addSelect('droneLog.horizontalSpeed', 'horizontalSpeed') | ||
397 | + .addSelect('droneLog.aboveSeaLevel', 'aboveSeaLevel') | ||
398 | + .addSelect('droneLog.aboveGroundLevel', 'aboveGroundLevel') | ||
399 | + .addSelect('droneLog.createdAt', 'createdAt') | ||
400 | + .innerJoin(DroneEntity, 'drone', 'drone.id = droneLog.droneId'); | ||
401 | + | ||
402 | + if (scheduleId) { | ||
403 | + logList.where('droneLog.scheduleId = :scheduleId', { | ||
404 | + scheduleId: scheduleId, | ||
405 | + }); | ||
406 | + } | ||
407 | + | ||
408 | + if (latitude) { | ||
409 | + logList.andWhere('cast(droneLog.latitude as varchar) like :latitude', { | ||
410 | + latitude: `%${latitude}%`, | ||
411 | + }); | ||
412 | + } | ||
413 | + | ||
414 | + if (longitude) { | ||
415 | + logList.andWhere('cast(droneLog.longitude as varchar) like :longitude', { | ||
416 | + longitude: `%${longitude}%`, | ||
417 | + }); | ||
418 | + } | ||
419 | + | ||
420 | + if (minVerticalSpeed) { | ||
421 | + logList.andWhere('droneLog.verticalSpeed >= :minVerticalSpeed', { | ||
422 | + minVerticalSpeed: minVerticalSpeed, | ||
423 | + }); | ||
424 | + } | ||
425 | + | ||
426 | + if (maxVerticalSpeed) { | ||
427 | + logList.andWhere('droneLog.verticalSpeed <= :maxVerticalSpeed', { | ||
428 | + maxVerticalSpeed: maxVerticalSpeed, | ||
429 | + }); | ||
430 | + } | ||
431 | + | ||
432 | + if (minHorizontalSpeed) { | ||
433 | + logList.andWhere('droneLog.horizontalSpeed >= :minHorizontalSpeed', { | ||
434 | + minHorizontalSpeed: minHorizontalSpeed, | ||
435 | + }); | ||
436 | + } | ||
437 | + | ||
438 | + if (maxHorizontalSpeed) { | ||
439 | + logList.andWhere('droneLog.horizontalSpeed <= :maxHorizontalSpeed', { | ||
440 | + maxHorizontalSpeed: maxHorizontalSpeed, | ||
441 | + }); | ||
442 | + } | ||
443 | + | ||
444 | + if (minAboveSeaLevel) { | ||
445 | + logList.andWhere('droneLog.aboveSeaLevel >= :minAboveSeaLevel', { | ||
446 | + minAboveSeaLevel: minAboveSeaLevel, | ||
447 | + }); | ||
448 | + } | ||
449 | + | ||
450 | + if (maxAboveSeaLevel) { | ||
451 | + logList.andWhere('droneLog.aboveSeaLevel <= :maxAboveSeaLevel', { | ||
452 | + maxAboveSeaLevel: maxAboveSeaLevel, | ||
453 | + }); | ||
454 | + } | ||
455 | + | ||
456 | + if (minAboveGroundLevel) { | ||
457 | + logList.andWhere('droneLog.aboveGroundLevel >= :minAboveGroundLevel', { | ||
458 | + minAboveGroundLevel: minAboveGroundLevel, | ||
459 | + }); | ||
460 | + } | ||
461 | + | ||
462 | + if (maxAboveGroundLevel) { | ||
463 | + logList.andWhere('droneLog.aboveGroundLevel <= :maxAboveGroundLevel', { | ||
464 | + maxAboveGroundLevel: maxAboveGroundLevel, | ||
465 | + }); | ||
466 | + } | ||
467 | + | ||
468 | + return logList.getCount(); | ||
469 | + } | ||
470 | + | ||
471 | + async findScheduleAll({ | ||
472 | + droneId, | ||
473 | + startTime, | ||
474 | + terminateTime, | ||
475 | + pageNo, | ||
476 | + pageSize, | ||
477 | + }: { | ||
478 | + droneId?: number; | ||
479 | + startTime?: Date; | ||
480 | + terminateTime?: Date; | ||
481 | + pageNo?: number; | ||
482 | + pageSize?: number; | ||
483 | + }): Promise<ScheduleEntity[]> { | ||
484 | + const scheduleList = getRepository(ScheduleEntity) | ||
485 | + .createQueryBuilder('schedule') | ||
486 | + .select('schedule.id', 'id') | ||
487 | + .addSelect('drone.id', 'droneId') | ||
488 | + .addSelect('drone.modelName', 'modelName') | ||
489 | + .addSelect('schedule.startTime', 'startTime') | ||
490 | + .addSelect('schedule.terminateTime', 'terminateTime') | ||
491 | + .addSelect('schedule.startLatitude', 'startLatitude') | ||
492 | + .addSelect('schedule.terminateLatitude', 'terminateLatitude') | ||
493 | + .addSelect('schedule.startLongitude', 'startLongitude') | ||
494 | + .addSelect('schedule.terminateLongitude', 'terminateLongitude') | ||
495 | + .innerJoin( | ||
496 | + DroneScheduleMappingEntity, | ||
497 | + 'mapping', | ||
498 | + 'schedule.id = mapping.scheduleId', | ||
499 | + ) | ||
500 | + .innerJoin(DroneEntity, 'drone', 'drone.id = mapping.droneId'); | ||
501 | + | ||
502 | + if (droneId) { | ||
503 | + scheduleList.where('mapping.droneId = :droneId', { droneId: droneId }); | ||
504 | + } | ||
505 | + | ||
506 | + if (startTime) { | ||
507 | + scheduleList.andWhere('schedule.startTime >= :startTime', { | ||
508 | + startTime: startTime, | ||
509 | + }); | ||
510 | + } | ||
511 | + if (terminateTime) { | ||
512 | + scheduleList.andWhere('schedule.terminateTime <= :terminateTime', { | ||
513 | + terminateTime: terminateTime, | ||
514 | + }); | ||
515 | + } | ||
516 | + | ||
517 | + scheduleList.orderBy('id', 'DESC'); | ||
518 | + | ||
519 | + if (pageNo && pageSize) { | ||
520 | + scheduleList.offset((pageNo - 1) * pageSize).limit(pageSize); | ||
521 | + } | ||
522 | + | ||
523 | + return scheduleList.getRawMany(); | ||
524 | + } | ||
525 | + | ||
526 | + async findScheduleTotalElement({ | ||
527 | + droneId, | ||
528 | + startTime, | ||
529 | + terminateTime, | ||
530 | + }: { | ||
531 | + droneId?: number; | ||
532 | + startTime?: string; | ||
533 | + terminateTime?: string; | ||
534 | + }) { | ||
535 | + const scheduleList = getRepository(ScheduleEntity) | ||
536 | + .createQueryBuilder('schedule') | ||
537 | + .select('schedule.id', 'id') | ||
538 | + .addSelect('drone.id', 'droneId') | ||
539 | + .addSelect('drone.modelName', 'modelName') | ||
540 | + .addSelect('schedule.startTime', 'startTime') | ||
541 | + .addSelect('schedule.terminateTime', 'terminateTime') | ||
542 | + .addSelect('schedule.startLatitude', 'startLatitude') | ||
543 | + .addSelect('schedule.terminateLatitude', 'terminateLatitude') | ||
544 | + .addSelect('schedule.startLongitude', 'startLongitude') | ||
545 | + .addSelect('schedule.terminateLongitude', 'terminateLongitude') | ||
546 | + .innerJoin( | ||
547 | + DroneScheduleMappingEntity, | ||
548 | + 'mapping', | ||
549 | + 'schedule.id = mapping.scheduleId', | ||
550 | + ) | ||
551 | + .innerJoin(DroneEntity, 'drone', 'drone.id = mapping.droneId'); | ||
552 | + | ||
553 | + if (droneId) { | ||
554 | + scheduleList.where('mapping.droneId = :droneId', { droneId: droneId }); | ||
555 | + } | ||
556 | + | ||
557 | + if (startTime) { | ||
558 | + scheduleList.andWhere('schedule.startTime >= :startTime', { | ||
559 | + startTime: startTime, | ||
560 | + }); | ||
561 | + } | ||
562 | + | ||
563 | + if (terminateTime) { | ||
564 | + scheduleList.andWhere('schedule.terminateTime <= :terminateTime', { | ||
565 | + terminateTime: terminateTime, | ||
566 | + }); | ||
567 | + } | ||
568 | + | ||
569 | + return scheduleList.getCount(); | ||
570 | + } | ||
571 | + | ||
572 | + findCodeAll(): Promise<CodeEntity[]> { | ||
573 | + return this.codeRepository.find(); | ||
574 | + } | ||
575 | + | ||
576 | + // 각 테이블별 detail 처리 | ||
577 | + findMemberOne(id: number): Promise<MemberEntity> { | ||
578 | + return this.memberRepository.findOne({ id: id }); | ||
579 | + } | ||
580 | + | ||
581 | + findDroneOne(id: number): Promise<DroneEntity> { | ||
582 | + return this.droneRepository.findOne({ id: id }); | ||
583 | + } | ||
584 | + | ||
585 | + findLogOne(id: number): Promise<DroneLogEntity> { | ||
586 | + return this.dronelogRepository.findOne({ id: id }); | ||
587 | + } | ||
588 | + | ||
589 | + findScheduleOne(id: number): Promise<ScheduleEntity> { | ||
590 | + const foundschedule = getRepository(ScheduleEntity) | ||
591 | + .createQueryBuilder('schedule') | ||
592 | + .select('schedule.id', 'id') | ||
593 | + .addSelect('mapping.droneId', 'droneId') | ||
594 | + .addSelect('schedule.startTime', 'startTime') | ||
595 | + .addSelect('schedule.terminateTime', 'terminateTime') | ||
596 | + .addSelect('schedule.startLatitude', 'startLatitude') | ||
597 | + .addSelect('schedule.terminateLatitude', 'terminateLatitude') | ||
598 | + .addSelect('schedule.startLongitude', 'startLongitude') | ||
599 | + .addSelect('schedule.terminateLongitude', 'terminateLongitude') | ||
600 | + .innerJoin( | ||
601 | + DroneScheduleMappingEntity, | ||
602 | + 'mapping', | ||
603 | + 'schedule.id = mapping.scheduleId', | ||
604 | + ) | ||
605 | + .where('schedule.id = :id', { id: id }); | ||
606 | + | ||
607 | + return foundschedule.getRawOne(); | ||
608 | + } | ||
609 | + | ||
610 | + findCodeOne(id: number): Promise<CodeEntity> { | ||
611 | + return this.codeRepository.findOne({ id: id }); | ||
612 | + } | ||
613 | + | ||
614 | + // 각 테이블별 insert | ||
615 | + async saveMember(memberEntity: MemberEntity): Promise<void> { | ||
616 | + await this.memberRepository.save(memberEntity); | ||
617 | + } | ||
618 | + | ||
619 | + async saveDrone(droneEntity: DroneEntity): Promise<void> { | ||
620 | + await this.droneRepository.save(droneEntity); | ||
621 | + } | ||
622 | + | ||
623 | + async saveDroneList(droneEntityList: DroneEntity[]): Promise<number> { | ||
624 | + const ret = await this.droneRepository.save(droneEntityList); | ||
625 | + return ret.length; | ||
626 | + } | ||
627 | + | ||
628 | + async updateDroneList( | ||
629 | + droneList: DroneApiDto.UpdateDroneDto[], | ||
630 | + ): Promise<number> { | ||
631 | + let affectedRows = 0; | ||
632 | + for (const drone of droneList) { | ||
633 | + const ret = await this.droneRepository | ||
634 | + .createQueryBuilder() | ||
635 | + .update('drone') | ||
636 | + .set({ | ||
637 | + maker: drone.maker, | ||
638 | + usage: drone.usage, | ||
639 | + specification: drone.specification, | ||
640 | + weight: drone.weight, | ||
641 | + }) | ||
642 | + .where('id = :id', { id: drone.id }) | ||
643 | + .execute(); | ||
644 | + | ||
645 | + affectedRows += ret.affected || 0; | ||
646 | + } | ||
647 | + return affectedRows; | ||
648 | + } | ||
649 | + | ||
650 | + async saveLog(droneLogEntity: DroneLogEntity): Promise<void> { | ||
651 | + await this.dronelogRepository.save(droneLogEntity); | ||
652 | + } | ||
653 | + | ||
654 | + async saveSchedule(ScheduleEntity: ScheduleEntity): Promise<void> { | ||
655 | + await this.scheduleRepository.save(ScheduleEntity); | ||
656 | + } | ||
657 | + | ||
658 | + async saveScheduleList( | ||
659 | + scheduleList: DroneApiDto.SaveSchduleDto[], | ||
660 | + ): Promise<number> { | ||
661 | + // 스케쥴 중복여부 확인 | ||
662 | + for (const schedule of scheduleList) { | ||
663 | + const ret = await this.droneRepository | ||
664 | + .createQueryBuilder('drone') | ||
665 | + .select('drone.id', 'droneId') | ||
666 | + .addSelect('schedule.id', 'scheduleId') | ||
667 | + .innerJoin( | ||
668 | + DroneScheduleMappingEntity, | ||
669 | + 'mapping', | ||
670 | + 'drone.id = mapping.droneId', | ||
671 | + ) | ||
672 | + .innerJoin( | ||
673 | + ScheduleEntity, | ||
674 | + 'schedule', | ||
675 | + 'schedule.id = mapping.scheduleId', | ||
676 | + ) | ||
677 | + .where('drone.id = :id', { id: schedule.droneId }) | ||
678 | + .andWhere('schedule.start_time <= :startTime', { | ||
679 | + startTime: schedule.startTime, | ||
680 | + }) | ||
681 | + .andWhere('schedule.terminate_time >= :terminateTime', { | ||
682 | + terminateTime: schedule.terminateTime, | ||
683 | + }) | ||
684 | + .getRawOne(); | ||
685 | + | ||
686 | + if (ret) { | ||
687 | + // 시간이 중복된 스케쥴이 존제함. | ||
688 | + throw new HttpException( | ||
689 | + `해당 기간에 설정된 스케쥴이 이미 존제합니다. droneId: ${ret.droneId}, scheduleId: ${ret.scheduleId}`, | ||
690 | + 400, | ||
691 | + ); | ||
692 | + } | ||
693 | + } | ||
694 | + | ||
695 | + let affectedRows = 0; | ||
696 | + | ||
697 | + // 스케쥴 생성 | ||
698 | + for (const schedule of scheduleList) { | ||
699 | + const ret = await this.scheduleRepository.save({ | ||
700 | + startTime: schedule.startTime, | ||
701 | + terminateTime: schedule.terminateTime, | ||
702 | + startLatitude: schedule.startLatitude, | ||
703 | + startLongitude: schedule.startLongitude, | ||
704 | + terminateLatitude: schedule.terminateLatitude, | ||
705 | + terminateLongitude: schedule.terminateLongitude, | ||
706 | + }); | ||
707 | + | ||
708 | + const scheduleId = ret.id; | ||
709 | + | ||
710 | + await this.droneschedulemappingRepository.save({ | ||
711 | + droneId: schedule.droneId, | ||
712 | + scheduleId, | ||
713 | + }); | ||
714 | + | ||
715 | + affectedRows += 1; | ||
716 | + } | ||
717 | + return affectedRows; | ||
718 | + } | ||
719 | + | ||
720 | + async updateSchduleList( | ||
721 | + scheduleList: DroneApiDto.UpdateSchduleDto[], | ||
722 | + ): Promise<number> { | ||
723 | + // 스케쥴 중복여부 확인 | ||
724 | + for (const schedule of scheduleList) { | ||
725 | + const ret = await this.droneschedulemappingRepository | ||
726 | + .createQueryBuilder('mapping') | ||
727 | + .select('drone.id', 'droneId') | ||
728 | + .addSelect('schedule.id', 'scheduleId') | ||
729 | + .innerJoin(DroneEntity, 'drone', 'drone.id = mapping.droneId') | ||
730 | + .innerJoin( | ||
731 | + DroneScheduleMappingEntity, | ||
732 | + 'mapping2', | ||
733 | + 'drone.id = mapping2.droneId', | ||
734 | + ) | ||
735 | + .innerJoin( | ||
736 | + ScheduleEntity, | ||
737 | + 'schedule', | ||
738 | + 'schedule.id = mapping2.scheduleId', | ||
739 | + ) | ||
740 | + .where('mapping.schedule_id = :id', { | ||
741 | + id: schedule.id, | ||
742 | + }) | ||
743 | + .andWhere('mapping2.schedule_id != :id', { | ||
744 | + id: schedule.id, | ||
745 | + }) | ||
746 | + .andWhere('schedule.start_time <= :startTime', { | ||
747 | + startTime: schedule.startTime, | ||
748 | + }) | ||
749 | + .andWhere('schedule.terminate_time >= :terminateTime', { | ||
750 | + terminateTime: schedule.terminateTime, | ||
751 | + }) | ||
752 | + .getRawOne(); | ||
753 | + | ||
754 | + if (ret) { | ||
755 | + // 시간이 중복된 스케쥴이 존제함. | ||
756 | + throw new HttpException( | ||
757 | + `해당 기간에 설정된 스케쥴이 이미 존제합니다. droneId: ${ret.droneId}, scheduleId: ${ret.scheduleId}`, | ||
758 | + 400, | ||
759 | + ); | ||
760 | + } | ||
761 | + } | ||
762 | + | ||
763 | + let affectedRows = 0; | ||
764 | + | ||
765 | + for (const schedule of scheduleList) { | ||
766 | + const ret = await this.scheduleRepository | ||
767 | + .createQueryBuilder() | ||
768 | + .update('schedule') | ||
769 | + .set({ | ||
770 | + startTime: schedule.startTime, | ||
771 | + terminateTime: schedule.terminateTime, | ||
772 | + startLatitude: schedule.startLatitude, | ||
773 | + startLongitude: schedule.startLongitude, | ||
774 | + terminateLatitude: schedule.terminateLatitude, | ||
775 | + terminateLongitude: schedule.terminateLongitude, | ||
776 | + }) | ||
777 | + .where('id = :id', { id: schedule.id }) | ||
778 | + .execute(); | ||
779 | + | ||
780 | + affectedRows += ret.affected || 0; | ||
781 | + } | ||
782 | + return affectedRows; | ||
783 | + } | ||
784 | + | ||
785 | + updateDroneMap(droneLogList: DroneLogDto[]) { | ||
786 | + droneLogList.forEach((droneLog) => { | ||
787 | + this.droneMap[`${droneLog.droneId}`] = droneLog; | ||
788 | + }); | ||
789 | + } | ||
790 | + | ||
791 | + async saveCode(CodeEntity: CodeEntity): Promise<void> { | ||
792 | + await this.codeRepository.save(CodeEntity); | ||
793 | + } | ||
794 | + | ||
795 | + // 각 테이블별 Delete | ||
796 | + async deleteMember(id: number): Promise<void> { | ||
797 | + await this.memberRepository.delete({ id: id }); | ||
798 | + } | ||
799 | + | ||
800 | + async deleteDrone(id: number): Promise<void> { | ||
801 | + await this.droneRepository.delete({ id: id }); | ||
802 | + } | ||
803 | + | ||
804 | + async deleteLog(id: number): Promise<void> { | ||
805 | + await this.dronelogRepository.delete({ id: id }); | ||
806 | + } | ||
807 | + | ||
808 | + async deleteSchedule(id: number): Promise<void> { | ||
809 | + await this.scheduleRepository.delete({ id: id }); | ||
810 | + } | ||
811 | + | ||
812 | + async deleteCode(id: number): Promise<void> { | ||
813 | + await this.codeRepository.delete({ id: id }); | ||
814 | + } | ||
815 | + | ||
816 | + async findFirstDroneLogList() { | ||
817 | + const droneList = await getRepository(DroneEntity) | ||
818 | + .createQueryBuilder('drone') | ||
819 | + .select('drone.id', 'droneId') | ||
820 | + .addSelect('schedule.id', 'scheduleId') | ||
821 | + .addSelect('log.latitude') | ||
822 | + .addSelect('log.longitude') | ||
823 | + .addSelect('log.vertical_speed', 'verticalSpeed') | ||
824 | + .addSelect('log.horizontal_speed', 'horizontalSpeed') | ||
825 | + .addSelect('log.above_sea_level', 'aboveSeaLevel') | ||
826 | + .addSelect('log.above_sea_level', 'aboveSeaLevel') | ||
827 | + .addSelect('log.above_ground_level', 'above_GroundLevel') | ||
828 | + .leftJoin( | ||
829 | + DroneScheduleMappingEntity, | ||
830 | + 'mapping', | ||
831 | + 'drone.id = mapping.droneId', | ||
832 | + ) | ||
833 | + .leftJoin( | ||
834 | + ScheduleEntity, | ||
835 | + 'schedule', | ||
836 | + 'mapping.scheduleId = schedule.id AND NOW() between schedule.start_time AND schedule.terminate_time', | ||
837 | + ) | ||
838 | + .leftJoin(DroneLogEntity, 'log', 'log.scheduleId = schedule.id') | ||
839 | + .where('log.id IS NULL') | ||
840 | + .orWhere( | ||
841 | + `log.id IN ( | ||
842 | + SELECT MAX(log2.id) FROM drone_log log2 WHERE log2.schedule_id = schedule.id GROUP BY log2.schedule_id | ||
843 | + )`, | ||
844 | + ) | ||
845 | + .orderBy('drone.id', 'DESC') | ||
846 | + .getRawMany(); | ||
847 | + return droneList; | ||
848 | + } | ||
849 | +} |
Backend/src/drone/dto/drone.api.dto.ts
0 → 100644
1 | +import { | ||
2 | + IsArray, | ||
3 | + IsNotEmpty, | ||
4 | + IsOptional, | ||
5 | + IsNumber, | ||
6 | + IsString, | ||
7 | + ValidateNested, | ||
8 | + IsDateString, | ||
9 | +} from 'class-validator'; | ||
10 | +import { Type } from 'class-transformer'; | ||
11 | +import { DroneEntity } from 'src/entities/drone.entity'; | ||
12 | + | ||
13 | +export namespace DroneApiDto { | ||
14 | + export class SaveDroneListDto { | ||
15 | + @IsArray() | ||
16 | + @ValidateNested({ each: true }) | ||
17 | + @Type(() => DroneEntity) | ||
18 | + droneList: DroneEntity[]; | ||
19 | + } | ||
20 | + | ||
21 | + export class UpdateDroneListDto { | ||
22 | + @IsArray() | ||
23 | + @ValidateNested({ each: true }) | ||
24 | + @Type(() => UpdateDroneDto) | ||
25 | + droneList: UpdateDroneDto[]; | ||
26 | + } | ||
27 | + | ||
28 | + export class UpdateDroneDto { | ||
29 | + @IsNotEmpty() | ||
30 | + @IsNumber() | ||
31 | + id: number; | ||
32 | + | ||
33 | + @IsString() | ||
34 | + @IsOptional() | ||
35 | + maker: string; | ||
36 | + | ||
37 | + @IsString() | ||
38 | + @IsOptional() | ||
39 | + usage: string; | ||
40 | + | ||
41 | + @IsNumber() | ||
42 | + @IsOptional() | ||
43 | + specification: number; | ||
44 | + | ||
45 | + @IsNumber() | ||
46 | + @IsOptional() | ||
47 | + weight: number; | ||
48 | + } | ||
49 | + | ||
50 | + export class SaveSchduleListDto { | ||
51 | + @IsArray() | ||
52 | + @ValidateNested({ each: true }) | ||
53 | + @Type(() => SaveSchduleDto) | ||
54 | + schduleList: SaveSchduleDto[]; | ||
55 | + } | ||
56 | + | ||
57 | + export class SaveSchduleDto { | ||
58 | + @IsNotEmpty() | ||
59 | + @IsNumber() | ||
60 | + droneId: number; | ||
61 | + | ||
62 | + @IsDateString() | ||
63 | + @IsNotEmpty() | ||
64 | + startTime: string; | ||
65 | + | ||
66 | + @IsDateString() | ||
67 | + @IsNotEmpty() | ||
68 | + terminateTime: string; | ||
69 | + | ||
70 | + @IsNumber() | ||
71 | + @IsNotEmpty() | ||
72 | + startLatitude: number; | ||
73 | + | ||
74 | + @IsNumber() | ||
75 | + @IsNotEmpty() | ||
76 | + startLongitude: number; | ||
77 | + | ||
78 | + @IsNumber() | ||
79 | + @IsNotEmpty() | ||
80 | + terminateLatitude: number; | ||
81 | + | ||
82 | + @IsNumber() | ||
83 | + @IsNotEmpty() | ||
84 | + terminateLongitude: number; | ||
85 | + } | ||
86 | + | ||
87 | + export class UpdateSchduleListDto { | ||
88 | + @IsArray() | ||
89 | + @ValidateNested({ each: true }) | ||
90 | + @Type(() => UpdateSchduleDto) | ||
91 | + schduleList: UpdateSchduleDto[]; | ||
92 | + } | ||
93 | + | ||
94 | + export class UpdateSchduleDto { | ||
95 | + @IsNotEmpty() | ||
96 | + @IsNumber() | ||
97 | + id: number; | ||
98 | + | ||
99 | + @IsDateString() | ||
100 | + @IsNotEmpty() | ||
101 | + startTime: string; | ||
102 | + | ||
103 | + @IsDateString() | ||
104 | + @IsNotEmpty() | ||
105 | + terminateTime: string; | ||
106 | + | ||
107 | + @IsNumber() | ||
108 | + @IsNotEmpty() | ||
109 | + startLatitude: number; | ||
110 | + | ||
111 | + @IsNumber() | ||
112 | + @IsNotEmpty() | ||
113 | + startLongitude: number; | ||
114 | + | ||
115 | + @IsNumber() | ||
116 | + @IsNotEmpty() | ||
117 | + terminateLatitude: number; | ||
118 | + | ||
119 | + @IsNumber() | ||
120 | + @IsNotEmpty() | ||
121 | + terminateLongitude: number; | ||
122 | + } | ||
123 | + | ||
124 | + export class SaveDroneLogListDto { | ||
125 | + @IsArray() | ||
126 | + @ValidateNested({ each: true }) | ||
127 | + @Type(() => SaveDroneLogDto) | ||
128 | + droneLogList: SaveDroneLogDto[]; | ||
129 | + } | ||
130 | + | ||
131 | + export class SaveDroneLogDto { | ||
132 | + @IsNotEmpty() | ||
133 | + @IsNumber() | ||
134 | + droneId: number; | ||
135 | + | ||
136 | + @IsNotEmpty() | ||
137 | + @IsNumber() | ||
138 | + scheduleId: number; | ||
139 | + | ||
140 | + @IsNotEmpty() | ||
141 | + @IsNumber() | ||
142 | + latitude: number; | ||
143 | + | ||
144 | + @IsNotEmpty() | ||
145 | + @IsNumber() | ||
146 | + longitude: number; | ||
147 | + | ||
148 | + @IsNotEmpty() | ||
149 | + @IsNumber() | ||
150 | + verticalSpeed: number; | ||
151 | + | ||
152 | + @IsNotEmpty() | ||
153 | + @IsNumber() | ||
154 | + horizontalSpeed: number; | ||
155 | + | ||
156 | + @IsNotEmpty() | ||
157 | + @IsNumber() | ||
158 | + aboveSeaLevel: number; | ||
159 | + | ||
160 | + @IsNotEmpty() | ||
161 | + @IsNumber() | ||
162 | + aboveGroundLevel: number; | ||
163 | + } | ||
164 | +} |
Backend/src/drone/dto/drone.dto.ts
0 → 100644
1 | +export class DroneDto { | ||
2 | + private _id: number; | ||
3 | + private _model_name: string; | ||
4 | + private _maker: string; | ||
5 | + private _usage: string; | ||
6 | + private _picture: string; | ||
7 | + private _specification: number; | ||
8 | + private _weight: number; | ||
9 | + | ||
10 | + constructor( | ||
11 | + id: number, | ||
12 | + model_name: string, | ||
13 | + maker: string, | ||
14 | + usage: string, | ||
15 | + picture: string, | ||
16 | + specification: number, | ||
17 | + weight: number, | ||
18 | + ) { | ||
19 | + this._id = id; | ||
20 | + this._model_name = model_name; | ||
21 | + this._maker = maker; | ||
22 | + this._usage = usage; | ||
23 | + this._picture = picture; | ||
24 | + this._specification = specification; | ||
25 | + this._weight = weight; | ||
26 | + } | ||
27 | + | ||
28 | + get id(): number { | ||
29 | + return this._id; | ||
30 | + } | ||
31 | + set id(value: number) { | ||
32 | + this._id = value; | ||
33 | + } | ||
34 | + | ||
35 | + get model_name(): string { | ||
36 | + return this._model_name; | ||
37 | + } | ||
38 | + set model_name(value: string) { | ||
39 | + this._model_name = value; | ||
40 | + } | ||
41 | + | ||
42 | + get maker(): string { | ||
43 | + return this._maker; | ||
44 | + } | ||
45 | + set maker(value: string) { | ||
46 | + this._maker = value; | ||
47 | + } | ||
48 | + | ||
49 | + get usage(): string { | ||
50 | + return this._usage; | ||
51 | + } | ||
52 | + set usage(value: string) { | ||
53 | + this._usage = value; | ||
54 | + } | ||
55 | + | ||
56 | + get picture(): string { | ||
57 | + return this._picture; | ||
58 | + } | ||
59 | + set picture(value: string) { | ||
60 | + this._picture = value; | ||
61 | + } | ||
62 | + | ||
63 | + get specification(): number { | ||
64 | + return this._specification; | ||
65 | + } | ||
66 | + set specification(value: number) { | ||
67 | + this._specification = value; | ||
68 | + } | ||
69 | + | ||
70 | + get weight(): number { | ||
71 | + return this._weight; | ||
72 | + } | ||
73 | + set weight(value: number) { | ||
74 | + this._weight = value; | ||
75 | + } | ||
76 | +} |
Backend/src/drone/dto/droneLog.dto.ts
0 → 100644
1 | +export class DroneLogDto { | ||
2 | + constructor(props) { | ||
3 | + // props.id = props.id ? parseInt(props.id) : props.id; | ||
4 | + props.droneId = props.droneId ? parseInt(props.droneId) : props.droneId; | ||
5 | + props.scheduleId = props.scheduleId | ||
6 | + ? parseInt(props.scheduleId) | ||
7 | + : props.scheduleId; | ||
8 | + Object.assign(this, props); | ||
9 | + } | ||
10 | + id: number; | ||
11 | + droneId: number; | ||
12 | + scheduleId: number; | ||
13 | + latitude: number; | ||
14 | + longitude: number; | ||
15 | + verticalSpeed: number; | ||
16 | + horizontalSpeed: number; | ||
17 | + aboveSeaLevel: number; | ||
18 | + aboveGroundLevel: number; | ||
19 | +} |
Backend/src/entities/code.entity.ts
0 → 100644
1 | +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index'; | ||
2 | + | ||
3 | +@Entity({ name: 'code', schema: 'public' }) | ||
4 | +export class CodeEntity { | ||
5 | + @PrimaryGeneratedColumn({ name: 'id' }) | ||
6 | + id: number; | ||
7 | + | ||
8 | + @Column({ length: 20, name: 'code_group' }) | ||
9 | + code_Group: string; | ||
10 | + | ||
11 | + @Column({ length: 20, name: 'code_group_name' }) | ||
12 | + codeGroupName: string; | ||
13 | + | ||
14 | + @Column({ length: 20, name: 'code_text' }) | ||
15 | + codeText: string; | ||
16 | + | ||
17 | + @Column({ length: 20, name: 'code_value' }) | ||
18 | + codeValue: string; | ||
19 | + | ||
20 | + @Column({ length: 20, name: 'code_value_name' }) | ||
21 | + codeValueName: string; | ||
22 | +} |
Backend/src/entities/drone.entity.ts
0 → 100644
1 | +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index'; | ||
2 | + | ||
3 | +@Entity({ name: 'drone', schema: 'public' }) | ||
4 | +export class DroneEntity { | ||
5 | + @PrimaryGeneratedColumn({ name: 'id' }) | ||
6 | + id: number; | ||
7 | + | ||
8 | + @Column({ length: 20, name: 'model_name' }) | ||
9 | + modelName: string; | ||
10 | + | ||
11 | + @Column({ length: 20 }) | ||
12 | + maker: string; | ||
13 | + | ||
14 | + @Column({ length: 20 }) | ||
15 | + usage: string; | ||
16 | + | ||
17 | + @Column({ length: 20, name: 'usagename' }) | ||
18 | + usageName: string; | ||
19 | + | ||
20 | + @Column({ length: 20 }) | ||
21 | + picture: string; | ||
22 | + | ||
23 | + @Column() | ||
24 | + specification: number; | ||
25 | + | ||
26 | + @Column() | ||
27 | + weight: number; | ||
28 | +} |
Backend/src/entities/drone.log.entity.ts
0 → 100644
1 | +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index'; | ||
2 | + | ||
3 | +@Entity({ name: 'drone_log', schema: 'public' }) | ||
4 | +export class DroneLogEntity { | ||
5 | + @PrimaryGeneratedColumn({ name: 'id' }) | ||
6 | + id: number; | ||
7 | + | ||
8 | + @Column({ name: 'drone_id' }) | ||
9 | + droneId: number; | ||
10 | + | ||
11 | + @Column({ name: 'schedule_id' }) | ||
12 | + scheduleId: number; | ||
13 | + | ||
14 | + @Column() | ||
15 | + latitude: number; | ||
16 | + | ||
17 | + @Column() | ||
18 | + longitude: number; | ||
19 | + | ||
20 | + @Column({ name: 'vertical_speed' }) | ||
21 | + verticalSpeed: number; | ||
22 | + | ||
23 | + @Column({ name: 'horizontal_speed' }) | ||
24 | + horizontalSpeed: number; | ||
25 | + | ||
26 | + @Column({ name: 'above_sea_level' }) | ||
27 | + aboveSeaLevel: number; | ||
28 | + | ||
29 | + @Column({ name: 'above_ground_level' }) | ||
30 | + aboveGroundLevel: number; | ||
31 | + | ||
32 | + @Column({ type: 'timestamp', name: 'created_at' }) | ||
33 | + createdAt: Date; | ||
34 | + | ||
35 | + constructor(props) { | ||
36 | + Object.assign(this, props); | ||
37 | + } | ||
38 | +} |
1 | +import { Entity, PrimaryGeneratedColumn } from 'typeorm/index'; | ||
2 | + | ||
3 | +@Entity({ name: 'drone_schedule_mapping', schema: 'public' }) | ||
4 | +export class DroneScheduleMappingEntity { | ||
5 | + @PrimaryGeneratedColumn({ name: 'drone_id' }) | ||
6 | + droneId: number; | ||
7 | + | ||
8 | + @PrimaryGeneratedColumn({ name: 'schedule_id' }) | ||
9 | + scheduleId: number; | ||
10 | +} |
Backend/src/entities/member.entity.ts
0 → 100644
1 | +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index'; | ||
2 | + | ||
3 | +@Entity({ name: 'member', schema: 'public' }) | ||
4 | +export class MemberEntity { | ||
5 | + @PrimaryGeneratedColumn({ name: 'id' }) | ||
6 | + id: number; | ||
7 | + | ||
8 | + @Column({ length: 15 }) | ||
9 | + name: string; | ||
10 | + | ||
11 | + @Column({ length: 20, name:'tel_number' }) | ||
12 | + telNumber: string; | ||
13 | + | ||
14 | + @Column({ length: 20 }) | ||
15 | + affiliation: string; | ||
16 | +} |
Backend/src/entities/schedule.entity.ts
0 → 100644
1 | +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index'; | ||
2 | + | ||
3 | +@Entity({ name: 'schedule', schema: 'public' }) | ||
4 | +export class ScheduleEntity { | ||
5 | + @PrimaryGeneratedColumn({ name: 'id' }) | ||
6 | + id: number; | ||
7 | + | ||
8 | + @Column({ type: 'timestamp', name: 'start_time' }) | ||
9 | + startTime: Date; | ||
10 | + | ||
11 | + @Column({ type: 'timestamp', name: 'terminate_time' }) | ||
12 | + terminateTime: Date; | ||
13 | + | ||
14 | + @Column({ name: 'start_latitude' }) | ||
15 | + startLatitude: number; | ||
16 | + | ||
17 | + @Column({ name: 'start_longitude' }) | ||
18 | + startLongitude: number; | ||
19 | + | ||
20 | + @Column({ name: 'terminate_latitude' }) | ||
21 | + terminateLatitude: number; | ||
22 | + | ||
23 | + @Column({ name: 'terminate_longitude' }) | ||
24 | + terminateLongitude: number; | ||
25 | +} |
Backend/src/main.ts
0 → 100644
1 | +import { NestFactory } from '@nestjs/core'; | ||
2 | +import { WsAdapter } from '@nestjs/platform-ws'; | ||
3 | +import { AppModule } from './app.module'; | ||
4 | + | ||
5 | +async function bootstrap() { | ||
6 | + const app = await NestFactory.create(AppModule); | ||
7 | + app.useWebSocketAdapter(new WsAdapter(app)); | ||
8 | + await app.listen(20205); | ||
9 | +} | ||
10 | +bootstrap(); |
Backend/test/app.e2e-spec.ts
0 → 100644
1 | +import { Test, TestingModule } from '@nestjs/testing'; | ||
2 | +import { INestApplication } from '@nestjs/common'; | ||
3 | +import * as request from 'supertest'; | ||
4 | +import { AppModule } from './../src/app.module'; | ||
5 | + | ||
6 | +describe('AppController (e2e)', () => { | ||
7 | + let app: INestApplication; | ||
8 | + | ||
9 | + beforeEach(async () => { | ||
10 | + const moduleFixture: TestingModule = await Test.createTestingModule({ | ||
11 | + imports: [AppModule], | ||
12 | + }).compile(); | ||
13 | + | ||
14 | + app = moduleFixture.createNestApplication(); | ||
15 | + await app.init(); | ||
16 | + }); | ||
17 | + | ||
18 | + it('/ (GET)', () => { | ||
19 | + return request(app.getHttpServer()) | ||
20 | + .get('/') | ||
21 | + .expect(200) | ||
22 | + .expect('Hello World!'); | ||
23 | + }); | ||
24 | +}); |
Backend/test/jest-e2e.json
0 → 100644
Backend/tsconfig.build.json
0 → 100644
Backend/tsconfig.json
0 → 100644
1 | +{ | ||
2 | + "compilerOptions": { | ||
3 | + "module": "commonjs", | ||
4 | + "declaration": true, | ||
5 | + "removeComments": true, | ||
6 | + "emitDecoratorMetadata": true, | ||
7 | + "experimentalDecorators": true, | ||
8 | + "allowSyntheticDefaultImports": true, | ||
9 | + "target": "es2017", | ||
10 | + "sourceMap": true, | ||
11 | + "outDir": "./dist", | ||
12 | + "baseUrl": "./", | ||
13 | + "incremental": true | ||
14 | + } | ||
15 | +} |
Frontend/.editorconfig
0 → 100644
Frontend/.env
0 → 100644
Frontend/.env.production
0 → 100644
Frontend/.eslintignore
0 → 100644
Frontend/.eslintrc.js
0 → 100644
1 | +module.exports = { | ||
2 | + root: true, | ||
3 | + env: { | ||
4 | + browser: true, | ||
5 | + node: true, | ||
6 | + }, | ||
7 | + parserOptions: { | ||
8 | + parser: 'babel-eslint', | ||
9 | + }, | ||
10 | + extends: [ | ||
11 | + 'plugin:vue/essential', | ||
12 | + '@vue/airbnb', | ||
13 | + ], | ||
14 | + plugins: [ | ||
15 | + ], | ||
16 | + // add your custom rules here | ||
17 | + rules: { | ||
18 | + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', | ||
19 | + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', | ||
20 | + 'no-unused-vars': 'warn', | ||
21 | + 'comma-dangle': ['error', 'always-multiline'], | ||
22 | + 'linebreak-style': 0, | ||
23 | + 'import/no-extraneous-dependencies': 0, | ||
24 | + 'no-shadow': 0, | ||
25 | + 'import/prefer-default-export': 0, | ||
26 | + 'max-len': ['warn', { code: 200 }], | ||
27 | + 'import/extensions': ['error', 'always', { | ||
28 | + js: 'never', | ||
29 | + jsx: 'never', | ||
30 | + vue: 'never', | ||
31 | + }], | ||
32 | + indent: [2, 2], | ||
33 | + }, | ||
34 | + settings: { | ||
35 | + 'import/extensions': ['.js', '.jsx', '.vue'], | ||
36 | + 'import/resolver': { | ||
37 | + alias: { | ||
38 | + map: [ | ||
39 | + ['@', './src'], | ||
40 | + ], | ||
41 | + extensions: ['.js', '.vue', '.jsx'], | ||
42 | + }, | ||
43 | + node: { | ||
44 | + extensions: ['.js', '.vue', '.jsx'], | ||
45 | + }, | ||
46 | + }, | ||
47 | + }, | ||
48 | +}; |
Frontend/.gitignore
0 → 100644
1 | +# Created by .ignore support plugin (hsz.mobi) | ||
2 | +### Node template | ||
3 | +# Logs | ||
4 | +/logs | ||
5 | +*.log | ||
6 | +npm-debug.log* | ||
7 | +yarn-debug.log* | ||
8 | +yarn-error.log* | ||
9 | + | ||
10 | +# Runtime data | ||
11 | +pids | ||
12 | +*.pid | ||
13 | +*.seed | ||
14 | +*.pid.lock | ||
15 | + | ||
16 | +# Directory for instrumented libs generated by jscoverage/JSCover | ||
17 | +lib-cov | ||
18 | + | ||
19 | +# Coverage directory used by tools like istanbul | ||
20 | +coverage | ||
21 | + | ||
22 | +# nyc test coverage | ||
23 | +.nyc_output | ||
24 | + | ||
25 | +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
26 | +.grunt | ||
27 | + | ||
28 | +# Bower dependency directory (https://bower.io/) | ||
29 | +bower_components | ||
30 | + | ||
31 | +# node-waf configuration | ||
32 | +.lock-wscript | ||
33 | + | ||
34 | +# Compiled binary addons (https://nodejs.org/api/addons.html) | ||
35 | +build/Release | ||
36 | + | ||
37 | +# Dependency directories | ||
38 | +node_modules/ | ||
39 | +jspm_packages/ | ||
40 | + | ||
41 | +# TypeScript v1 declaration files | ||
42 | +typings/ | ||
43 | + | ||
44 | +# Optional npm cache directory | ||
45 | +.npm | ||
46 | + | ||
47 | +# Optional eslint cache | ||
48 | +.eslintcache | ||
49 | + | ||
50 | +# Optional REPL history | ||
51 | +.node_repl_history | ||
52 | + | ||
53 | +# Output of 'npm pack' | ||
54 | +*.tgz | ||
55 | + | ||
56 | +# Yarn Integrity file | ||
57 | +.yarn-integrity | ||
58 | + | ||
59 | +# parcel-bundler cache (https://parceljs.org/) | ||
60 | +.cache | ||
61 | + | ||
62 | +# next.js build output | ||
63 | +.next | ||
64 | + | ||
65 | +# nuxt.js build output | ||
66 | +.nuxt | ||
67 | + | ||
68 | +# Nuxt generate | ||
69 | +dist | ||
70 | + | ||
71 | +# vuepress build output | ||
72 | +.vuepress/dist | ||
73 | + | ||
74 | +# Serverless directories | ||
75 | +.serverless | ||
76 | + | ||
77 | +# IDE / Editor | ||
78 | +.idea | ||
79 | + | ||
80 | +# Service worker | ||
81 | +sw.* | ||
82 | + | ||
83 | +# macOS | ||
84 | +.DS_Store | ||
85 | + | ||
86 | +# Vim swap files | ||
87 | +*.swp | ||
88 | +.DS_Store |
Frontend/.gitlab-ci.yml
0 → 100755
1 | +stages: | ||
2 | + - others | ||
3 | + - build | ||
4 | + - deploy | ||
5 | + | ||
6 | +.build: | ||
7 | + cache: | ||
8 | + paths: | ||
9 | + - node_modules/ | ||
10 | + | ||
11 | +.deploy: | ||
12 | + variables: | ||
13 | + GIT_STRATEGY: none | ||
14 | + | ||
15 | +dev_build: | ||
16 | + stage: build | ||
17 | + extends: .build | ||
18 | + script: | ||
19 | + - yarn install | ||
20 | + - yarn build | ||
21 | + only: | ||
22 | + - develop | ||
23 | + tags: | ||
24 | + - front | ||
25 | + | ||
26 | +dev_deploy: | ||
27 | + stage: deploy | ||
28 | + extends: .deploy | ||
29 | + script: | ||
30 | + - pm2 start --exp-backoff-restart-delay=100 | ||
31 | + only: | ||
32 | + - develop | ||
33 | + tags: | ||
34 | + - front | ||
35 | +#(DEV) mockserver: | ||
36 | +# stage: deploy | ||
37 | +# extends: .deploy | ||
38 | +# image: node:15 | ||
39 | +# script: yarn serve | ||
40 | +# only: | ||
41 | +# - develop | ||
42 | +# artifacts: | ||
43 | +# paths: | ||
44 | +# - ./ | ||
45 | +# tags: | ||
46 | +# - ws-server | ||
47 | +# | ||
48 | +#(DEV) websocket: | ||
49 | +# stage: deploy | ||
50 | +# extends: .deploy | ||
51 | +# image: docker:latest | ||
52 | +# script: yarn ws | ||
53 | +# only: | ||
54 | +# - develop | ||
55 | +# artifacts: | ||
56 | +# paths: | ||
57 | +# - ./ | ||
58 | +# tags: | ||
59 | +# - api-server |
Frontend/.prettierrc.json
0 → 100644
Frontend/Readme.md
0 → 100644
1 | +# drone-web-nuxt | ||
2 | + | ||
3 | +## Build Setup | ||
4 | + | ||
5 | +```bash | ||
6 | +# install dependencies | ||
7 | +$ yarn install | ||
8 | + | ||
9 | +# serve with hot reload at localhost:3000 | ||
10 | +$ yarn dev | ||
11 | + | ||
12 | +# build for production and launch server | ||
13 | +$ yarn build | ||
14 | +$ yarn start | ||
15 | + | ||
16 | +# generate static project | ||
17 | +$ yarn generate | ||
18 | +``` | ||
19 | + | ||
20 | +For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). |
Frontend/deploy.sh
0 → 100644
Frontend/ecosystem.config.js
0 → 100644
1 | +module.exports = { | ||
2 | + apps: [ | ||
3 | + { | ||
4 | + name: 'drone-front', | ||
5 | + // package.json에 정의된 npm run start를 실행하게 해서 PM2로 관리하게 한다. | ||
6 | + script: 'yarn', | ||
7 | + args: 'run start', | ||
8 | + instances: '1', | ||
9 | + max_memory_restart: '1G', | ||
10 | + error_file: 'err.log', | ||
11 | + out_file: 'out.log', | ||
12 | + log_file: 'combined.log', | ||
13 | + }, | ||
14 | + ], | ||
15 | +}; |
Frontend/front.service
0 → 100644
1 | +[Unit] | ||
2 | +Description=Drone front service | ||
3 | +After=network.target | ||
4 | +StartLimitIntervalSec=0 | ||
5 | + | ||
6 | +[Service] | ||
7 | +Type=simple | ||
8 | +Restart=always | ||
9 | +RestartSec=1 | ||
10 | +User=gitlab-runner | ||
11 | +ExecStart=/home/gitlab-runner/builds/4hhEfxWU/0/khu-oz-wizard/drone-monitoring-web-ui yarn start | ||
12 | + | ||
13 | +[Install] | ||
14 | +WantedBy=multi-user.target |
Frontend/lib/db.json
0 → 100644
This diff could not be displayed because it is too large.
Frontend/nginx
0 → 100644
1 | +map $sent_http_content_type $expires { | ||
2 | + "text/html" epoch; | ||
3 | + "text/html; charset=utf-8" epoch; | ||
4 | + default off; | ||
5 | +} | ||
6 | + | ||
7 | +server { | ||
8 | + listen 20205; | ||
9 | + server_name localhost; | ||
10 | + | ||
11 | + gzip on; | ||
12 | + gzip_types text/plain application/xml text/css application/javascript; | ||
13 | + gzip_min_length 1000; | ||
14 | + | ||
15 | + location / { | ||
16 | + expires $expires; | ||
17 | + | ||
18 | + proxy_redirect off; | ||
19 | + proxy_set_header Host $host; | ||
20 | + proxy_set_header X-Real-IP $remote_addr; | ||
21 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
22 | + proxy_set_header X-Forwarded-Proto $scheme; | ||
23 | + proxy_read_timeout 1m; | ||
24 | + proxy_connect_timeout 1m; | ||
25 | + proxy_pass http://127.0.0.1:3000; | ||
26 | + } | ||
27 | +} |
Frontend/nuxt.config.js
0 → 100644
1 | +/* eslint-disable no-unused-vars */ | ||
2 | +import api from './nuxtConfig/api'; | ||
3 | +import build from './nuxtConfig/build'; | ||
4 | +import theme from './nuxtConfig/theme'; | ||
5 | +import nuxtConfigModule from './nuxtConfig/module'; | ||
6 | +import io from './nuxtConfig/ioConfig'; | ||
7 | +import extendRouter from './nuxtConfig/extendRouter'; | ||
8 | + | ||
9 | +// 설정 내용이 짧은 것 및 구조화 하기 애매한 것은 별도 파일로 관리하지 않음. | ||
10 | +export default { | ||
11 | + // Global page headers: https://go.nuxtjs.dev/config-head | ||
12 | + head: { | ||
13 | + title: 'drone-web-nuxt', | ||
14 | + htmlAttrs: { | ||
15 | + lang: 'en', | ||
16 | + }, | ||
17 | + meta: [ | ||
18 | + { charset: 'utf-8' }, | ||
19 | + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, | ||
20 | + { hid: 'description', name: 'description', content: '' }, | ||
21 | + ], | ||
22 | + link: [ | ||
23 | + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, | ||
24 | + ], | ||
25 | + }, | ||
26 | + | ||
27 | + // Auto import components: https://go.nuxtjs.dev/config-components | ||
28 | + components: false, | ||
29 | + // source Directory | ||
30 | + srcDir: 'src/', | ||
31 | + | ||
32 | + /* middleware */ | ||
33 | + serverMiddleware: [ | ||
34 | + './serverMiddleWare/index', | ||
35 | + ], | ||
36 | + router: { | ||
37 | + // router middleware | ||
38 | + middleware: 'router', | ||
39 | + // router extend | ||
40 | + // extendRoutes: extendRouter, | ||
41 | + }, | ||
42 | + | ||
43 | + // module, plugin, alias, robots | ||
44 | + ...nuxtConfigModule, | ||
45 | + // axios, proxy, auth | ||
46 | + ...api, | ||
47 | + // env, runtimeConfig, build | ||
48 | + ...build, | ||
49 | + // loading, transition, css | ||
50 | + ...theme, | ||
51 | + | ||
52 | + // vue Global Config | ||
53 | + vue: { | ||
54 | + config: { | ||
55 | + productionTip: true, | ||
56 | + devtools: process.env.NODE_ENV === 'development', | ||
57 | + // silent: process.env.NODE_ENV !== 'development', | ||
58 | + // performance: process.env.NODE_ENV === 'development', | ||
59 | + }, | ||
60 | + }, | ||
61 | + | ||
62 | + // robots Setting | ||
63 | + robots: { | ||
64 | + UserAgent: '*', | ||
65 | + Disallow: '/', | ||
66 | + }, | ||
67 | + // socket io Setting | ||
68 | + io, | ||
69 | +}; |
Frontend/nuxtConfig/api.js
0 → 100644
1 | +import { version } from '../package.json'; | ||
2 | + | ||
3 | +/** | ||
4 | + * api 관련된 nuxt 옵션을 정리합니다. | ||
5 | + * 해당 옵션은 아래와 같습니다. | ||
6 | + * auth, axios, proxy, | ||
7 | + */ | ||
8 | +export default { | ||
9 | + // Axios module configuration: https://go.nuxtjs.dev/config-axios | ||
10 | + axios: { | ||
11 | + proxy: true, | ||
12 | + retry: { retries: 3 }, | ||
13 | + // baseUrl: 'http://localhost:5555', | ||
14 | + headers: { | ||
15 | + common: { | ||
16 | + Accept: 'application/json, text/plain, */*', | ||
17 | + AppVersion: version, | ||
18 | + }, | ||
19 | + delete: {}, | ||
20 | + get: {}, | ||
21 | + head: {}, | ||
22 | + post: {}, | ||
23 | + put: {}, | ||
24 | + patch: {}, | ||
25 | + }, | ||
26 | + }, | ||
27 | + proxy: { | ||
28 | + '/api': { | ||
29 | + target: process.env.BASE_API_URL || 'http://14.33.35.148:20205', | ||
30 | + pathRewrite: { | ||
31 | + '^/api': '', | ||
32 | + }, | ||
33 | + changeOrigin: true, | ||
34 | + }, | ||
35 | + }, | ||
36 | + auth: { | ||
37 | + // Options | ||
38 | + }, | ||
39 | +}; |
Frontend/nuxtConfig/build.js
0 → 100644
1 | +/** | ||
2 | + * 빌드에 관련된 nuxt 옵션을 정리합니다. | ||
3 | + * 해당 옵션은 아래와 같습니다. | ||
4 | + * env, build, | ||
5 | + */ | ||
6 | +export default { | ||
7 | + // modern property https://ko.nuxtjs.org/docs/2.x/configuration-glossary/configuration-modern | ||
8 | + modern: false, | ||
9 | + /* env Setting */ | ||
10 | + env: { | ||
11 | + BASE_API_URL: process.env.BASE_API_URL, | ||
12 | + BASE_APP_URL: process.env.BASE_APP_URL, | ||
13 | + BASE_I18N_LOCALE: process.env.BASE_I18N_LOCALE, | ||
14 | + BASE_I18N_FALLBACK_LOCALE: process.env.BASE_I18N_FALLBACK_LOCALE, | ||
15 | + }, | ||
16 | + // public nuxt.context config variables | ||
17 | + publicRuntimeConfig: { | ||
18 | + BASE_API_URL: process.env.BASE_API_URL, | ||
19 | + BASE_APP_URL: process.env.BASE_APP_URL, | ||
20 | + BASE_I18N_LOCALE: process.env.BASE_I18N_LOCALE, | ||
21 | + BASE_I18N_FALLBACK_LOCALE: process.env.BASE_I18N_FALLBACK_LOCALE, | ||
22 | + }, | ||
23 | + // private nuxt.context config variables | ||
24 | + privateRuntimeConfig: { | ||
25 | + | ||
26 | + }, | ||
27 | + | ||
28 | + // Build Configuration: https://go.nuxtjs.dev/config-build | ||
29 | + build: { | ||
30 | + loaders: { | ||
31 | + vue: { | ||
32 | + transformAssetUrls: { | ||
33 | + 'vl-style-icon': 'src', | ||
34 | + }, | ||
35 | + }, | ||
36 | + // for Antdv CustomTheme Setting | ||
37 | + less: { | ||
38 | + lessOptions: { | ||
39 | + javascriptEnabled: true, | ||
40 | + math: 'always', | ||
41 | + }, | ||
42 | + }, | ||
43 | + }, | ||
44 | + devtool: true, | ||
45 | + analyze: true, | ||
46 | + }, | ||
47 | + // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules | ||
48 | + buildModules: [ | ||
49 | + // https://go.nuxtjs.dev/eslint | ||
50 | + '@nuxt/image', | ||
51 | + ], | ||
52 | + image: { | ||
53 | + staticFilename: '[publicPath]/images/[name]-[hash][ext]', | ||
54 | + presets: { | ||
55 | + avatar: { | ||
56 | + modifiers: { | ||
57 | + format: 'jpg', | ||
58 | + width: 50, | ||
59 | + height: 50, | ||
60 | + }, | ||
61 | + }, | ||
62 | + }, | ||
63 | + }, | ||
64 | +}; |
Frontend/nuxtConfig/extendRouter.js
0 → 100644
Frontend/nuxtConfig/ioConfig.js
0 → 100644
Frontend/nuxtConfig/module.js
0 → 100644
1 | +/** | ||
2 | + * 모듈에 관련된 nuxt 옵션을 정리합니다. | ||
3 | + * 해당 옵션은 아래와 같습니다. | ||
4 | + * module, plugin, alias | ||
5 | + */ | ||
6 | +import { resolve } from 'path'; | ||
7 | + | ||
8 | +export default { | ||
9 | +// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins | ||
10 | + plugins: [ | ||
11 | + resolve(__dirname, '../src/plugins/ApiClient/index'), | ||
12 | + resolve(__dirname, '../src/plugins/antDesign'), | ||
13 | + resolve(__dirname, '../src/plugins/Dayjs/index'), | ||
14 | + { src: '@/plugins/client-only.client.js' }, | ||
15 | + { src: '@/plugins/globalMixins' }, | ||
16 | + { src: '@/plugins/vuelayers.js', ssr: false }, | ||
17 | + ], | ||
18 | + | ||
19 | + // Modules: https://go.nuxtjs.dev/config-modules | ||
20 | + modules: [ | ||
21 | + // https://go.nuxtjs.dev/axios | ||
22 | + '@nuxtjs/axios', | ||
23 | + '@nuxtjs/style-resources', | ||
24 | + '@nuxtjs/auth-next', | ||
25 | + '@nuxtjs/sitemap', | ||
26 | + '@nuxtjs/robots', | ||
27 | + 'nuxt-socket-io', | ||
28 | + 'nuxt-leaflet', | ||
29 | + resolve(__dirname, '../src/modules/vuelayers.js'), | ||
30 | + ], | ||
31 | + // alias | ||
32 | + alias: { | ||
33 | + '@': resolve(__dirname, '../src/'), | ||
34 | + images: resolve(__dirname, '../src/assets/images'), | ||
35 | + styles: resolve(__dirname, '../src/assets/styles'), | ||
36 | + }, | ||
37 | +}; |
Frontend/nuxtConfig/theme.js
0 → 100644
1 | +/** | ||
2 | + * 테마에 관련된 nuxt 옵션을 정리합니다. | ||
3 | + * 해당 옵션은 아래와 같습니다. | ||
4 | + * trainsition, css, loading | ||
5 | + */ | ||
6 | +import { resolve } from 'path'; | ||
7 | + | ||
8 | +export default { | ||
9 | +// Theme Animation | ||
10 | + loading: { | ||
11 | + color: '#1890ff', | ||
12 | + height: '4px', | ||
13 | + }, | ||
14 | + layoutTransition: { | ||
15 | + name: 'default-layout', | ||
16 | + mode: 'out-in', | ||
17 | + }, | ||
18 | + pageTransition: { | ||
19 | + name: 'default-page', | ||
20 | + mode: 'out-in', | ||
21 | + }, | ||
22 | + // Global CSS: https://go.nuxtjs.dev/config-css | ||
23 | + css: [ | ||
24 | + resolve(__dirname, '../src/assets/styles/less/index'), | ||
25 | + resolve(__dirname, '../src/assets/styles/scss/index'), | ||
26 | + ], | ||
27 | +}; |
Frontend/package.json
0 → 100644
1 | +{ | ||
2 | + "name": "drone-web-nuxt", | ||
3 | + "version": "1.0.3", | ||
4 | + "private": true, | ||
5 | + "scripts": { | ||
6 | + "dev": "nuxt", | ||
7 | + "analyze-dev": "nuxt --analyze", | ||
8 | + "build": "nuxt build", | ||
9 | + "start": "nuxt start", | ||
10 | + "build-mo": "nuxt build --modern=server", | ||
11 | + "analyze-build": "nuxt build --analyze", | ||
12 | + "start-mo": "nuxt start --modern=server", | ||
13 | + "generate": "nuxt generate", | ||
14 | + "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .", | ||
15 | + "lint": "yarn lint:js", | ||
16 | + "serve": "json-server --watch lib/db.json --port 5555", | ||
17 | + "socket": "node ./serverMiddleWare/socket.js", | ||
18 | + "ws": "node ./serverMiddleWare/ws.js", | ||
19 | + "wsm": "node ./serverMiddleWare/multWs.js", | ||
20 | + "wsmp": "node ./serverMiddleWare/multPWs.js", | ||
21 | + "k6": "k6 run ./serverMiddleWare/k6.js", | ||
22 | + "k6m": "k6 run ./serverMiddleWare/k6mult.js" | ||
23 | + }, | ||
24 | + "dependencies": { | ||
25 | + "@nuxtjs/auth-next": "5.0.0-1613647907.37b1156", | ||
26 | + "@nuxtjs/axios": "^5.13.1", | ||
27 | + "@nuxtjs/robots": "^2.5.0", | ||
28 | + "@nuxtjs/sitemap": "^2.4.0", | ||
29 | + "ant-design-vue": "^1.7.2", | ||
30 | + "core-js": "^3.8.3", | ||
31 | + "cors": "^2.8.5", | ||
32 | + "dayjs": "^1.10.4", | ||
33 | + "express": "^4.17.1", | ||
34 | + "highcharts": "^9.0.1", | ||
35 | + "highcharts-vue": "^1.3.5", | ||
36 | + "nuxt": "^2.14.12", | ||
37 | + "nuxt-leaflet": "^0.0.25", | ||
38 | + "nuxt-socket-io": "^1.1.14", | ||
39 | + "socket.io": "^4.0.0", | ||
40 | + "store2": "^2.12.0", | ||
41 | + "vuelayers": "^0.11.35", | ||
42 | + "vuex": "^3.6.2", | ||
43 | + "ws": "^7.4.4" | ||
44 | + }, | ||
45 | + "devDependencies": { | ||
46 | + "@nuxt/image": "^0.4.13", | ||
47 | + "@nuxtjs/eslint-config": "^5.0.0", | ||
48 | + "@nuxtjs/eslint-module": "^3.0.2", | ||
49 | + "@nuxtjs/style-resources": "^1.0.0", | ||
50 | + "@vue/cli-service": "^4.5.11", | ||
51 | + "@vue/eslint-config-airbnb": "^5.3.0", | ||
52 | + "babel-eslint": "^10.1.0", | ||
53 | + "eslint": "^7.18.0", | ||
54 | + "eslint-import-resolver-alias": "^1.1.2", | ||
55 | + "eslint-plugin-nuxt": "^2.0.0", | ||
56 | + "eslint-plugin-vue": "^7.5.0", | ||
57 | + "fibers": "^5.0.0", | ||
58 | + "json-server": "^0.16.3", | ||
59 | + "less": "^4.1.1", | ||
60 | + "less-loader": "7", | ||
61 | + "sass": "^1.32.8", | ||
62 | + "sass-loader": "10" | ||
63 | + } | ||
64 | +} |
Frontend/serverMiddleWare/index.js
0 → 100644
1 | +// <project root>/api/index.js | ||
2 | +// const express = require('express'); | ||
3 | +const app = require('express')(); | ||
4 | +const http = require('http').createServer(app); | ||
5 | +const io = require('socket.io')(http); | ||
6 | + | ||
7 | +// 실제로는 /api 라우트를 처리하는 메소드가 된다. | ||
8 | +app.get('/', (req, res) => { | ||
9 | + console.log('hi'); | ||
10 | + io.emit('connection'); | ||
11 | + res.send('API root'); | ||
12 | +}); | ||
13 | + | ||
14 | +io.of('/analytics').on('connect', (socket) => { | ||
15 | + console.log('클라이언트 접속'); | ||
16 | + | ||
17 | + socket.on('disconnect', () => { | ||
18 | + console.log('클라이언트 접속 종료'); | ||
19 | + }); | ||
20 | + setInterval(() => { | ||
21 | + socket.emit('message', '메세지'); | ||
22 | + }, 3000); | ||
23 | +}); | ||
24 | + | ||
25 | +// 모듈로 사용할 수 있도록 export | ||
26 | +// 앱의 /api/* 라우트로 접근하는 모든 요청은 모두 app 인스턴스에게 전달된다. | ||
27 | +module.exports = { | ||
28 | + path: '/socket', | ||
29 | + handler: app, | ||
30 | +}; |
Frontend/serverMiddleWare/k6.js
0 → 100644
1 | +import ws from 'k6/ws'; | ||
2 | +import { check } from 'k6'; | ||
3 | + | ||
4 | +export const options = { | ||
5 | + // vus: 100, | ||
6 | + // duration: '30s', | ||
7 | + stages: [ | ||
8 | + { duration: '60s', target: 10 }, | ||
9 | + ], | ||
10 | +}; | ||
11 | + | ||
12 | +export default function () { | ||
13 | + const url = 'ws://localhost:8080'; | ||
14 | + const params = { tags: { my_tag: 'hello' } }; | ||
15 | + | ||
16 | + const res = ws.connect(url, params, (socket) => { | ||
17 | + socket.on('open', () => console.log('connected')); | ||
18 | + socket.on('message', (data) => console.log('Message received: ')); | ||
19 | + socket.on('close', () => console.log('disconnected')); | ||
20 | + socket.setTimeout(() => { | ||
21 | + console.log('60 seconds passed, closing the socket'); | ||
22 | + socket.close(); | ||
23 | + }, 30000); | ||
24 | + }); | ||
25 | + | ||
26 | + check(res, { 'status is 101': (r) => r && r.status === 101 }); | ||
27 | +} |
Frontend/serverMiddleWare/k6mult.js
0 → 100644
1 | +import ws from 'k6/ws'; | ||
2 | +import { check } from 'k6'; | ||
3 | + | ||
4 | +export const options = { | ||
5 | + // vus: 100, | ||
6 | + // duration: '30s', | ||
7 | + stages: [ | ||
8 | + { duration: '60s', target: 10 }, | ||
9 | + // { duration: '10s', target: 30 }, | ||
10 | + // { duration: '20s', target: 50 }, | ||
11 | + ], | ||
12 | +}; | ||
13 | + | ||
14 | +export default function () { | ||
15 | + const url = 'ws://localhost:8080'; | ||
16 | + const params = { tags: { my_tag: 'hello' } }; | ||
17 | + const res = {}; | ||
18 | + | ||
19 | + for (let i = 0; i < 10; i += 1) { | ||
20 | + res[i] = ws.connect(`${url}/${i + 1}`, params, (socket) => { | ||
21 | + socket.on('open', () => console.log('connected', i)); | ||
22 | + socket.on('message', (data) => console.log('Message received: ', i)); | ||
23 | + socket.on('close', () => console.log('disconnected')); | ||
24 | + socket.setTimeout(() => { | ||
25 | + console.log('60 seconds passed, closing the socket'); | ||
26 | + socket.close(); | ||
27 | + }, 60000); | ||
28 | + }); | ||
29 | + } | ||
30 | + console.log(res); | ||
31 | + check(res, { | ||
32 | + 'status1 is 101': (r) => r && r[0].status === 101, | ||
33 | + 'status2 is 101': (r) => r && r[1].status === 101, | ||
34 | + 'status3 is 101': (r) => r && r[2].status === 101, | ||
35 | + 'status4 is 101': (r) => r && r[3].status === 101, | ||
36 | + }); | ||
37 | +} | ||
38 | +// | ||
39 | +// const res1 = ws.connect(`${url}/${1}`, params, (socket) => { | ||
40 | +// socket.on('open', () => console.log('connected')); | ||
41 | +// socket.on('message', (data) => console.log('Message received: 1')); | ||
42 | +// socket.on('close', () => console.log('disconnected')); | ||
43 | +// socket.setTimeout(() => { | ||
44 | +// console.log('60 seconds passed, closing the socket'); | ||
45 | +// socket.close(); | ||
46 | +// }, 60000); | ||
47 | +// }); | ||
48 | +// | ||
49 | +// const res2 = ws.connect(`${url}/${2}`, params, (socket) => { | ||
50 | +// socket.on('open', () => console.log('connected')); | ||
51 | +// socket.on('message', (data) => console.log('Message received: 2')); | ||
52 | +// socket.on('close', () => console.log('disconnected')); | ||
53 | +// socket.setTimeout(() => { | ||
54 | +// console.log('60 seconds passed, closing the socket'); | ||
55 | +// socket.close(); | ||
56 | +// }, 60000); | ||
57 | +// }); | ||
58 | +// | ||
59 | +// const res3 = ws.connect(`${url}/${3}`, params, (socket) => { | ||
60 | +// socket.on('open', () => console.log('connected')); | ||
61 | +// socket.on('message', (data) => console.log('Message received: 3')); | ||
62 | +// socket.on('close', () => console.log('disconnected')); | ||
63 | +// socket.setTimeout(() => { | ||
64 | +// console.log('60 seconds passed, closing the socket'); | ||
65 | +// socket.close(); | ||
66 | +// }, 60000); | ||
67 | +// }); | ||
68 | +// | ||
69 | +// const res4 = ws.connect(`${url}/${4}`, params, (socket) => { | ||
70 | +// socket.on('open', () => console.log('connected')); | ||
71 | +// socket.on('message', (data) => console.log('Message received: 4')); | ||
72 | +// socket.on('close', () => console.log('disconnected')); | ||
73 | +// socket.setTimeout(() => { | ||
74 | +// console.log('60 seconds passed, closing the socket'); | ||
75 | +// socket.close(); | ||
76 | +// }, 60000); | ||
77 | +// }); | ||
78 | +// | ||
79 | +// const res5 = ws.connect(`${url}/${5}`, params, (socket) => { | ||
80 | +// socket.on('open', () => console.log('connected')); | ||
81 | +// socket.on('message', (data) => console.log('Message received: 5')); | ||
82 | +// socket.on('close', () => console.log('disconnected')); | ||
83 | +// socket.setTimeout(() => { | ||
84 | +// console.log('60 seconds passed, closing the socket'); | ||
85 | +// socket.close(); | ||
86 | +// }, 60000); | ||
87 | +// }); | ||
88 | +// | ||
89 | +// const res6 = ws.connect(`${url}/${6}`, params, (socket) => { | ||
90 | +// socket.on('open', () => console.log('connected')); | ||
91 | +// socket.on('message', (data) => console.log('Message received: 6')); | ||
92 | +// socket.on('close', () => console.log('disconnected')); | ||
93 | +// socket.setTimeout(() => { | ||
94 | +// console.log('60 seconds passed, closing the socket'); | ||
95 | +// socket.close(); | ||
96 | +// }, 60000); | ||
97 | +// }); | ||
98 | +// | ||
99 | +// const res7 = ws.connect(`${url}/${7}`, params, (socket) => { | ||
100 | +// socket.on('open', () => console.log('connected')); | ||
101 | +// socket.on('message', (data) => console.log('Message received: 7')); | ||
102 | +// socket.on('close', () => console.log('disconnected')); | ||
103 | +// socket.setTimeout(() => { | ||
104 | +// console.log('60 seconds passed, closing the socket'); | ||
105 | +// socket.close(); | ||
106 | +// }, 60000); | ||
107 | +// }); | ||
108 | +// | ||
109 | +// const res8 = ws.connect(`${url}/${8}`, params, (socket) => { | ||
110 | +// socket.on('open', () => console.log('connected')); | ||
111 | +// socket.on('message', (data) => console.log('Message received: 8')); | ||
112 | +// socket.on('close', () => console.log('disconnected')); | ||
113 | +// socket.setTimeout(() => { | ||
114 | +// console.log('60 seconds passed, closing the socket'); | ||
115 | +// socket.close(); | ||
116 | +// }, 60000); | ||
117 | +// }); | ||
118 | +// | ||
119 | +// const res9 = ws.connect(`${url}/${9}`, params, (socket) => { | ||
120 | +// socket.on('open', () => console.log('connected')); | ||
121 | +// socket.on('message', (data) => console.log('Message received: 9')); | ||
122 | +// socket.on('close', () => console.log('disconnected')); | ||
123 | +// socket.setTimeout(() => { | ||
124 | +// console.log('60 seconds passed, closing the socket'); | ||
125 | +// socket.close(); | ||
126 | +// }, 60000); | ||
127 | +// }); | ||
128 | +// | ||
129 | +// const res10 = ws.connect(`${url}/${10}`, params, (socket) => { | ||
130 | +// socket.on('open', () => console.log('connected')); | ||
131 | +// socket.on('message', (data) => console.log('Message received: 10')); | ||
132 | +// socket.on('close', () => console.log('disconnected')); | ||
133 | +// socket.setTimeout(() => { | ||
134 | +// console.log('60 seconds passed, closing the socket'); | ||
135 | +// socket.close(); | ||
136 | +// }, 60000); | ||
137 | +// }); | ||
138 | +// check([res1, res2, res3, res10], { 'status is 101': (r) => r && r.status === 101 }); |
Frontend/serverMiddleWare/multPWs.js
0 → 100644
1 | +/* eslint-disable prefer-arrow-callback,consistent-return,no-param-reassign,no-mixed-operators,no-use-before-define */ | ||
2 | +const http = require('http'); | ||
3 | +const WebSocket = require('ws'); | ||
4 | +const url = require('url'); | ||
5 | + | ||
6 | +const server = http.createServer(); | ||
7 | +const wss = {}; | ||
8 | + | ||
9 | +const dataNum = process.argv[2]; | ||
10 | +const wsServerCnt = process.argv[3]; | ||
11 | +const port = process.argv[4]; | ||
12 | +const dataPerWsServer = dataNum / wsServerCnt; | ||
13 | + | ||
14 | +console.log(process.argv); | ||
15 | + | ||
16 | +let pingInterval = null; | ||
17 | +let sendInterval = null; | ||
18 | + | ||
19 | +const logData = []; | ||
20 | +for (let i = 0; i < dataPerWsServer; i += 1) { | ||
21 | + logData.push({ | ||
22 | + latitude: getRandomArbitrary(37200000000000, 37300000000000) / 1000000000000, | ||
23 | + longitude: getRandomArbitrary(126900000000000, 127100000000000) / 1000000000000, | ||
24 | + id: i, | ||
25 | + }); | ||
26 | +} | ||
27 | + | ||
28 | +function getRandomArbitrary(min, max) { | ||
29 | + return parseInt((Math.random() * (max - min) + min), 10); | ||
30 | +} | ||
31 | +function circleMove(x, y, radius, max, circleStep) { | ||
32 | + return { | ||
33 | + latitude: x + radius * Math.cos(2 * Math.PI * circleStep / max), | ||
34 | + longitude: y + radius * Math.sin(2 * Math.PI * circleStep / max), | ||
35 | + }; | ||
36 | +} | ||
37 | +function makeCoordData(log, circleStep) { | ||
38 | + if (circleStep == null) { | ||
39 | + circleStep = 0; | ||
40 | + } | ||
41 | + if (circleStep === 3600) { | ||
42 | + circleStep = 0; | ||
43 | + } else circleStep += 1; | ||
44 | + | ||
45 | + // console.log('step', circleStep); | ||
46 | + | ||
47 | + return Array.from( | ||
48 | + { length: dataPerWsServer }, | ||
49 | + (v, i) => ({ | ||
50 | + id: i, | ||
51 | + ...circleMove(log[i].latitude, log[i].longitude, 0.05, 3600, circleStep), | ||
52 | + time: new Date(), | ||
53 | + }), | ||
54 | + ); | ||
55 | +} | ||
56 | +function heartbeat() { | ||
57 | + this.isAlive = true; | ||
58 | + console.log('client Heartbeat'); | ||
59 | +} | ||
60 | +function noop() {} | ||
61 | + | ||
62 | +for (let i = 0; i < wsServerCnt; i += 1) { | ||
63 | + wss[i + 1] = new WebSocket.Server({ noServer: true }); | ||
64 | +} | ||
65 | + | ||
66 | +Object.entries(wss).forEach(([key, wss]) => { | ||
67 | + wss.on('connection', (ws) => { | ||
68 | + console.log('connected', key); | ||
69 | + ws.isAlive = true; | ||
70 | + ws.on('pong', heartbeat); | ||
71 | + ws.on('message', function incoming(message) { | ||
72 | + console.log('received: %s', message); | ||
73 | + }); | ||
74 | + let circleStep = 0; | ||
75 | + sendInterval = setInterval(() => { | ||
76 | + if (ws.readyState === WebSocket.OPEN) { | ||
77 | + const coordData = makeCoordData(logData, circleStep); | ||
78 | + ws.send(JSON.stringify(coordData)); | ||
79 | + } | ||
80 | + circleStep += 1; | ||
81 | + }, 1000); | ||
82 | + | ||
83 | + ws.on('close', function close() { | ||
84 | + console.log('websocket Closed'); | ||
85 | + clearInterval(pingInterval); | ||
86 | + clearInterval(sendInterval); | ||
87 | + // sendInterval = null; | ||
88 | + }); | ||
89 | + | ||
90 | + /* ping check */ | ||
91 | + pingInterval = setInterval(function ping() { | ||
92 | + wss.clients.forEach(function each(ws) { | ||
93 | + if (ws.isAlive === false) return ws.terminate(); | ||
94 | + ws.isAlive = false; | ||
95 | + ws.ping(noop); | ||
96 | + }); | ||
97 | + }, 30000); | ||
98 | + | ||
99 | + wss.on('close', function close() { | ||
100 | + console.log('server closed'); | ||
101 | + clearInterval(pingInterval); | ||
102 | + clearInterval(sendInterval); | ||
103 | + }); | ||
104 | + }); | ||
105 | +}); | ||
106 | + | ||
107 | +server.on('upgrade', (request, socket, head) => { | ||
108 | + const { pathname } = url.parse(request.url); | ||
109 | + | ||
110 | + Object.entries(wss).forEach(([key, wss]) => { | ||
111 | + if (`/${key}` === pathname) { | ||
112 | + wss.handleUpgrade(request, socket, head, (ws) => { | ||
113 | + wss.emit('connection', ws, request); | ||
114 | + }); | ||
115 | + } | ||
116 | + }); | ||
117 | +}); | ||
118 | + | ||
119 | +server.listen(port); |
Frontend/serverMiddleWare/multWs.js
0 → 100644
1 | +/* eslint-disable prefer-arrow-callback,consistent-return,no-param-reassign,no-mixed-operators,no-use-before-define */ | ||
2 | +const http = require('http'); | ||
3 | +const WebSocket = require('ws'); | ||
4 | +const url = require('url'); | ||
5 | + | ||
6 | +const server = http.createServer(); | ||
7 | +const wss = {}; | ||
8 | + | ||
9 | +const dataNum = 20000; | ||
10 | +const wsServerCnt = process.argv[2]; | ||
11 | +console.log(wsServerCnt); | ||
12 | +const dataPerWsServer = dataNum / wsServerCnt; | ||
13 | + | ||
14 | +let pingInterval = null; | ||
15 | +let sendInterval = null; | ||
16 | + | ||
17 | +const logData = []; | ||
18 | +for (let i = 0; i < dataPerWsServer; i += 1) { | ||
19 | + logData.push({ | ||
20 | + latitude: getRandomArbitrary(37200000000000, 37300000000000) / 1000000000000, | ||
21 | + longitude: getRandomArbitrary(126900000000000, 127100000000000) / 1000000000000, | ||
22 | + id: i, | ||
23 | + }); | ||
24 | +} | ||
25 | + | ||
26 | +function getRandomArbitrary(min, max) { | ||
27 | + return parseInt((Math.random() * (max - min) + min), 10); | ||
28 | +} | ||
29 | +function circleMove(x, y, radius, max, circleStep) { | ||
30 | + return { | ||
31 | + latitude: x + radius * Math.cos(2 * Math.PI * circleStep / max), | ||
32 | + longitude: y + radius * Math.sin(2 * Math.PI * circleStep / max), | ||
33 | + }; | ||
34 | +} | ||
35 | +function makeCoordData(log, circleStep) { | ||
36 | + if (circleStep == null) { | ||
37 | + circleStep = 0; | ||
38 | + } | ||
39 | + if (circleStep === 3600) { | ||
40 | + circleStep = 0; | ||
41 | + } else circleStep += 1; | ||
42 | + | ||
43 | + // console.log('step', circleStep); | ||
44 | + | ||
45 | + return Array.from( | ||
46 | + { length: dataPerWsServer }, | ||
47 | + (v, i) => ({ | ||
48 | + id: i, | ||
49 | + ...circleMove(log[i].latitude, log[i].longitude, 0.05, 3600, circleStep), | ||
50 | + time: new Date(), | ||
51 | + }), | ||
52 | + ); | ||
53 | +} | ||
54 | +function heartbeat() { | ||
55 | + this.isAlive = true; | ||
56 | + console.log('client Heartbeat'); | ||
57 | +} | ||
58 | +function noop() {} | ||
59 | + | ||
60 | +for (let i = 0; i < wsServerCnt; i += 1) { | ||
61 | + wss[i + 1] = new WebSocket.Server({ noServer: true }); | ||
62 | +} | ||
63 | + | ||
64 | +Object.entries(wss).forEach(([key, wss]) => { | ||
65 | + wss.on('connection', (ws) => { | ||
66 | + console.log('connected', key); | ||
67 | + ws.isAlive = true; | ||
68 | + ws.on('pong', heartbeat); | ||
69 | + ws.on('message', function incoming(message) { | ||
70 | + console.log('received: %s', message); | ||
71 | + }); | ||
72 | + let circleStep = 0; | ||
73 | + sendInterval = setInterval(() => { | ||
74 | + if (ws.readyState === WebSocket.OPEN) { | ||
75 | + const coordData = makeCoordData(logData, circleStep); | ||
76 | + ws.send(JSON.stringify(coordData)); | ||
77 | + } | ||
78 | + circleStep += 1; | ||
79 | + }, 1000); | ||
80 | + | ||
81 | + ws.on('close', function close() { | ||
82 | + console.log('websocket Closed'); | ||
83 | + clearInterval(pingInterval); | ||
84 | + clearInterval(sendInterval); | ||
85 | + // sendInterval = null; | ||
86 | + }); | ||
87 | + | ||
88 | + /* ping check */ | ||
89 | + pingInterval = setInterval(function ping() { | ||
90 | + wss.clients.forEach(function each(ws) { | ||
91 | + if (ws.isAlive === false) return ws.terminate(); | ||
92 | + ws.isAlive = false; | ||
93 | + ws.ping(noop); | ||
94 | + }); | ||
95 | + }, 30000); | ||
96 | + | ||
97 | + wss.on('close', function close() { | ||
98 | + console.log('server closed'); | ||
99 | + clearInterval(pingInterval); | ||
100 | + clearInterval(sendInterval); | ||
101 | + }); | ||
102 | + }); | ||
103 | +}); | ||
104 | + | ||
105 | +server.on('upgrade', (request, socket, head) => { | ||
106 | + const { pathname } = url.parse(request.url); | ||
107 | + | ||
108 | + Object.entries(wss).forEach(([key, wss]) => { | ||
109 | + if (`/${key}` === pathname) { | ||
110 | + wss.handleUpgrade(request, socket, head, (ws) => { | ||
111 | + wss.emit('connection', ws, request); | ||
112 | + }); | ||
113 | + } | ||
114 | + }); | ||
115 | +}); | ||
116 | + | ||
117 | +server.listen(20203); |
Frontend/serverMiddleWare/socket.js
0 → 100644
1 | +/* eslint-disable prefer-arrow-callback */ | ||
2 | +// <project root>/api/index.js | ||
3 | +// const express = require('express'); | ||
4 | + | ||
5 | +function getRandomArbitrary(min, max) { | ||
6 | + return parseInt((Math.random() * (max - min) + min), 10); | ||
7 | +} | ||
8 | +const app = require('express')(); | ||
9 | +const http = require('http').createServer(app); | ||
10 | +const io = require('socket.io')(http, { | ||
11 | + cors: true, | ||
12 | + origins: ['http://127.0.0.1:3000', 'http://127.0.0.1:8888', 'http://localhost:3000'], | ||
13 | +}); | ||
14 | +const cors = require('cors'); | ||
15 | + | ||
16 | +app.use(cors()); | ||
17 | + | ||
18 | +// 실제로는 /api 라우트를 처리하는 메소드가 된다. | ||
19 | +app.get('/', (req, res) => { | ||
20 | + console.log('hi'); | ||
21 | + io.of('/testSoc').emit('connection', { data: '1234' }); | ||
22 | + res.send('API root'); | ||
23 | +}); | ||
24 | + | ||
25 | +io.of('/testSoc').on('connect', (socket) => { | ||
26 | + console.log('클라이언트 접속'); | ||
27 | + | ||
28 | + socket.on('getMessage', (data) => { | ||
29 | + console.log('fromClient', data); | ||
30 | + }); | ||
31 | + socket.on('disconnect', () => { | ||
32 | + console.log('클라이언트 접속 종료'); | ||
33 | + }); | ||
34 | + setInterval(() => { | ||
35 | + socket.emit('receiveLog', { num: getRandomArbitrary(10, 100), time: new Date() }); | ||
36 | + }, 1000); | ||
37 | +}); | ||
38 | + | ||
39 | +http.listen(8888, () => { | ||
40 | + console.log('Socket IO server listening on port 8888'); | ||
41 | +}); |
Frontend/serverMiddleWare/test.js
0 → 100644
1 | +import ws from 'k6/ws'; | ||
2 | +import { check } from 'k6'; | ||
3 | + | ||
4 | +export default function () { | ||
5 | + const url = 'ws://echo.websocket.org'; | ||
6 | + const params = { tags: { my_tag: 'hello' } }; | ||
7 | + | ||
8 | + const res = ws.connect(url, params, (socket) => { | ||
9 | + socket.on('open', () => { | ||
10 | + console.log('connected'); | ||
11 | + | ||
12 | + socket.setInterval(() => { | ||
13 | + socket.ping(); | ||
14 | + console.log('Pinging every 1sec (setInterval test)'); | ||
15 | + }, 1000); | ||
16 | + }); | ||
17 | + | ||
18 | + socket.on('ping', () => { | ||
19 | + console.log('PING!'); | ||
20 | + }); | ||
21 | + | ||
22 | + socket.on('pong', () => { | ||
23 | + console.log('PONG!'); | ||
24 | + }); | ||
25 | + | ||
26 | + socket.on('close', () => { | ||
27 | + console.log('disconnected'); | ||
28 | + }); | ||
29 | + | ||
30 | + socket.setTimeout(() => { | ||
31 | + console.log('2 seconds passed, closing the socket'); | ||
32 | + socket.close(); | ||
33 | + }, 2000); | ||
34 | + }); | ||
35 | + | ||
36 | + check(res, { | ||
37 | + 'status is 101': (r) => r && r.status === 101, | ||
38 | + 'Homepage body size is 11026 bytes': (r) => r.body && r.body.length === 11026, | ||
39 | + test: (r) => r, | ||
40 | + }); | ||
41 | +} |
Frontend/serverMiddleWare/ws.js
0 → 100644
1 | +/* eslint-disable prefer-arrow-callback,consistent-return,no-param-reassign,no-mixed-operators,no-use-before-define */ | ||
2 | +let pingInterval = null; | ||
3 | +let sendInterval = null; | ||
4 | +const logData = []; | ||
5 | +const dataNum = 20000; | ||
6 | +for (let i = 0; i < dataNum; i += 1) { | ||
7 | + logData.push({ | ||
8 | + latitude: getRandomArbitrary(37200000000000, 37300000000000) / 1000000000000, | ||
9 | + longitude: getRandomArbitrary(126900000000000, 127100000000000) / 1000000000000, | ||
10 | + id: i, | ||
11 | + }); | ||
12 | +} | ||
13 | + | ||
14 | +function getRandomArbitrary(min, max) { | ||
15 | + return parseInt((Math.random() * (max - min) + min), 10); | ||
16 | +} | ||
17 | +function circleMove(x, y, radius, max, circleStep) { | ||
18 | + return { | ||
19 | + latitude: x + radius * Math.cos(2 * Math.PI * circleStep / max), | ||
20 | + longitude: y + radius * Math.sin(2 * Math.PI * circleStep / max), | ||
21 | + }; | ||
22 | +} | ||
23 | +function makeCoordData(log, circleStep) { | ||
24 | + if (circleStep == null) { | ||
25 | + circleStep = 0; | ||
26 | + } | ||
27 | + if (circleStep === 3600) { | ||
28 | + circleStep = 0; | ||
29 | + } else circleStep += 1; | ||
30 | + | ||
31 | + console.log('step', circleStep); | ||
32 | + | ||
33 | + return Array.from( | ||
34 | + { length: dataNum }, | ||
35 | + (v, i) => ({ | ||
36 | + id: i, | ||
37 | + ...circleMove(log[i].latitude, log[i].longitude, 0.05, 3600, circleStep), | ||
38 | + time: new Date(), | ||
39 | + }), | ||
40 | + ); | ||
41 | +} | ||
42 | +function heartbeat() { | ||
43 | + this.isAlive = true; | ||
44 | + console.log('client Heartbeat'); | ||
45 | +} | ||
46 | +function noop() {} | ||
47 | + | ||
48 | +const WebSocket = require('ws'); | ||
49 | + | ||
50 | +const wss = new WebSocket.Server({ | ||
51 | + port: 20202, | ||
52 | + perMessageDeflate: { | ||
53 | + zlibDeflateOptions: { | ||
54 | + // See zlib defaults. | ||
55 | + chunkSize: 1024, | ||
56 | + memLevel: 7, | ||
57 | + level: 3, | ||
58 | + }, | ||
59 | + zlibInflateOptions: { | ||
60 | + chunkSize: 10 * 1024, | ||
61 | + }, | ||
62 | + // Other options settable: | ||
63 | + clientNoContextTakeover: true, // Defaults to negotiated value. | ||
64 | + serverNoContextTakeover: true, // Defaults to negotiated value. | ||
65 | + serverMaxWindowBits: 10, // Defaults to negotiated value. | ||
66 | + // Below options specified as default values. | ||
67 | + concurrencyLimit: 10, // Limits zlib concurrency for perf. | ||
68 | + threshold: 1024, // Size (in bytes) below which messages | ||
69 | + // should not be compressed. | ||
70 | + }, | ||
71 | +}); | ||
72 | + | ||
73 | +wss.on('connection', function connection(ws) { | ||
74 | + ws.isAlive = true; | ||
75 | + ws.on('pong', heartbeat); | ||
76 | + ws.on('message', function incoming(message) { | ||
77 | + console.log('received: %s', message); | ||
78 | + }); | ||
79 | + let circleStep = 0; | ||
80 | + sendInterval = setInterval(() => { | ||
81 | + if (ws.readyState === WebSocket.OPEN) { | ||
82 | + const coordData = makeCoordData(logData, circleStep); | ||
83 | + ws.send(JSON.stringify(coordData)); | ||
84 | + } | ||
85 | + circleStep += 1; | ||
86 | + }, 1000); | ||
87 | + | ||
88 | + ws.on('close', function close() { | ||
89 | + console.log('websocket Closed'); | ||
90 | + clearInterval(pingInterval); | ||
91 | + clearInterval(sendInterval); | ||
92 | + // sendInterval = null; | ||
93 | + }); | ||
94 | +}); | ||
95 | + | ||
96 | +/* ping check */ | ||
97 | +pingInterval = setInterval(function ping() { | ||
98 | + wss.clients.forEach(function each(ws) { | ||
99 | + if (ws.isAlive === false) return ws.terminate(); | ||
100 | + ws.isAlive = false; | ||
101 | + ws.ping(noop); | ||
102 | + }); | ||
103 | +}, 30000); | ||
104 | + | ||
105 | +wss.on('close', function close() { | ||
106 | + console.log('server closed'); | ||
107 | + clearInterval(pingInterval); | ||
108 | + clearInterval(sendInterval); | ||
109 | +}); |
Frontend/src/assets/README.md
0 → 100644
1 | +# ASSETS | ||
2 | + | ||
3 | +**This directory is not required, you can delete it if you don't want to use it.** | ||
4 | + | ||
5 | +This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. | ||
6 | + | ||
7 | +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). |
Frontend/src/assets/images/54763744_p0.png
0 → 100755
2.55 MB
Frontend/src/assets/images/drone-image.jpg
0 → 100644
25.5 KB
Frontend/src/assets/images/drone.png
0 → 100644
80.2 KB
Frontend/src/assets/styles/less/index.less
0 → 100644
1 | +/* default Variable */ | ||
2 | +@primary-color: #1890ff; // primary color for all components | ||
3 | +@link-color: #1890ff; // link color | ||
4 | +@success-color: #52c41a; // success state color | ||
5 | +@warning-color: #faad14; // warning state color | ||
6 | +@error-color: #f5222d; // error state color | ||
7 | +@font-size-base: 14px; // major text font size | ||
8 | +@heading-color: rgba(0, 0, 0, 0.85); // heading text color | ||
9 | +@text-color: rgba(0, 0, 0, 0.65); // major text color | ||
10 | +@text-color-secondary: rgba(0, 0, 0, 0.45); // secondary text color | ||
11 | +@disabled-color: rgba(0, 0, 0, 0.25); // disable state color | ||
12 | +@border-radius-base: 4px; // major border radius | ||
13 | +@border-color-base: #d9d9d9; // major border color | ||
14 | +@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // major shadow for layers |
Frontend/src/assets/styles/mixins.scss
0 → 100644
1 | +// Colors | ||
2 | +$white: #fff; | ||
3 | +$black: #001529; | ||
4 | +$blue: var(--kit-color-primary); | ||
5 | +$blue-light: #3d6ee7; | ||
6 | +$blue-dark: #103daf; | ||
7 | +$gray-1: #f2f4f8; | ||
8 | +$gray-2: #e4e9f0; | ||
9 | +$gray-3: #dde2ec; | ||
10 | +$gray-4: #c3bedc; | ||
11 | +$gray-5: #aca6cc; | ||
12 | +$gray-6: #786fa4; | ||
13 | +$yellow: #ff0; | ||
14 | +$orange: #f2a654; | ||
15 | +$red: #b52427; | ||
16 | +$pink: #fd3995; | ||
17 | +$purple: #652eff; | ||
18 | +$green: #41b883; | ||
19 | +$kdis-color: #0c7037; | ||
20 | +$antblue: #1890ff; | ||
21 | + | ||
22 | +$text: $gray-6; | ||
23 | +$border: $gray-2; | ||
24 | + | ||
25 | +// Accent colors | ||
26 | +$default: $gray-4; | ||
27 | +$primary: $kdis-color; | ||
28 | +$secondary: $gray-5; | ||
29 | +$success: $green; | ||
30 | +$info: $blue-light; | ||
31 | +$warning: $orange; | ||
32 | +$danger: $red; | ||
33 | +$light: $gray-1; | ||
34 | +$dark: $black; | ||
35 | + | ||
36 | +// dark theme | ||
37 | +$dark-gray-1: #aeaee0; | ||
38 | +$dark-gray-2: #7575a3; | ||
39 | +$dark-gray-3: #4f4f7a; | ||
40 | +$dark-gray-4: #2a274d; | ||
41 | +$dark-gray-5: #161537; | ||
42 | +$dark-gray-6: #100f28; | ||
43 | + | ||
44 | +// Font Family | ||
45 | +$base-font-family: 'Noto Sans KR', sans-serif; | ||
46 | + | ||
47 | +// Font Size | ||
48 | +$base-font-size: 15 !default; |
Frontend/src/assets/styles/scss/index.scss
0 → 100644
1 | +@import '@/assets/styles/scss/partials/transition.scss'; | ||
2 | +@import '@/assets/styles/scss/partials/page.scss'; | ||
3 | +@import '@/assets/styles/scss/partials/layouts.scss'; | ||
4 | +@import '@/assets/styles/scss/partials/description.scss'; | ||
5 | +@import '@/assets/styles/scss/partials/box.scss'; | ||
6 | +@import '@/assets/styles/scss/partials/test.scss'; | ||
7 | +@import '@/assets/styles/scss/partials/pagination.scss'; | ||
8 | +@import '@/assets/styles/scss/partials/alert.scss'; |
1 | + | ||
2 | +.mapBox { | ||
3 | + ::-webkit-scrollbar { | ||
4 | + width: 7px; | ||
5 | + border-radius: 10px; | ||
6 | + } | ||
7 | + | ||
8 | + ::-webkit-scrollbar-thumb { | ||
9 | + background-clip: padding-box; | ||
10 | + background-color: #47749E; | ||
11 | + border: 2px solid transparent; | ||
12 | + width: 5px; | ||
13 | + border-radius: 2px; | ||
14 | + } | ||
15 | + ::-webkit-scrollbar-track { | ||
16 | + background-color: rgb(236, 236, 236); | ||
17 | + border-radius: 0px 2px 2px 0px; | ||
18 | + } | ||
19 | + | ||
20 | + | ||
21 | + .search-box { | ||
22 | + position: absolute; | ||
23 | + right: 10px; | ||
24 | + top: 10px; | ||
25 | + | ||
26 | + .searchBtn{ | ||
27 | + width: 50px; | ||
28 | + height: 50px; | ||
29 | + padding: 7px; | ||
30 | + background: white; | ||
31 | + border: 2px solid rgba(0,0,0,0.2); | ||
32 | + border-radius: 4px; | ||
33 | + cursor: pointer; | ||
34 | + } | ||
35 | + | ||
36 | + .ant-modal-content { | ||
37 | + position: absolute; | ||
38 | + right: 60px; | ||
39 | + top: 0px; | ||
40 | + width: 400px; | ||
41 | + max-height: calc(100vh - 120px); | ||
42 | + | ||
43 | + .ant-modal-header { | ||
44 | + background: #47749e; | ||
45 | + .ant-modal-title { | ||
46 | + color: white; | ||
47 | + text-align: center; | ||
48 | + } | ||
49 | + } | ||
50 | + | ||
51 | + .ant-modal-body { | ||
52 | + padding: 20px; | ||
53 | + } | ||
54 | + | ||
55 | + .ant-input:hover { | ||
56 | + border-color: #47749e; | ||
57 | + border-right-width: 1px !important; | ||
58 | + } | ||
59 | + | ||
60 | + .ant-input:focus { | ||
61 | + border-color: #47749e; | ||
62 | + } | ||
63 | + | ||
64 | + .ant-btn-primary { | ||
65 | + background-color: #47749e; | ||
66 | + border-color: #47749e; | ||
67 | + } | ||
68 | + | ||
69 | + .ant-list-header { | ||
70 | + color: white; | ||
71 | + background: #47749e; | ||
72 | + } | ||
73 | + | ||
74 | + .ant-input-search { | ||
75 | + width: 100%; | ||
76 | + margin-bottom: 10px; | ||
77 | + } | ||
78 | + | ||
79 | + .ant-list-items { | ||
80 | + max-height: calc(100vh - 300px); | ||
81 | + overflow-y: scroll; | ||
82 | + } | ||
83 | + | ||
84 | + .ant-list-item { | ||
85 | + padding: 10px; | ||
86 | + background: white; | ||
87 | + width: 100%; | ||
88 | + } | ||
89 | + | ||
90 | + .ant-list-item:hover { | ||
91 | + color: #47749e; | ||
92 | + font-weight: 600; | ||
93 | + } | ||
94 | + } | ||
95 | + } | ||
96 | +} | ||
97 | +.filter-feature-box { | ||
98 | + .ant-modal-content { | ||
99 | + position: absolute; | ||
100 | + right: 70px; | ||
101 | + top: 10px; | ||
102 | + width: 300px; | ||
103 | + | ||
104 | + .ant-modal-header { | ||
105 | + background: #47749e; | ||
106 | + .ant-modal-title { | ||
107 | + color: white; | ||
108 | + text-align: center; | ||
109 | + } | ||
110 | + } | ||
111 | + } | ||
112 | + | ||
113 | + .label { | ||
114 | + font-size: 13px; | ||
115 | + font-weight: 700; | ||
116 | + margin-bottom: 10px; | ||
117 | + margin-top: 20px; | ||
118 | + | ||
119 | + span { | ||
120 | + display: inline-block; | ||
121 | + border: 2px solid #47749e; | ||
122 | + border-radius: 3px; | ||
123 | + padding: 2px 5px; | ||
124 | + } | ||
125 | + | ||
126 | + } | ||
127 | +} | ||
128 | + | ||
129 | +.boxBtn { | ||
130 | + display: flex; | ||
131 | + justify-content: center; | ||
132 | + align-items: center; | ||
133 | + width: 50px; | ||
134 | + height: 50px; | ||
135 | + padding: 7px; | ||
136 | + background: white; | ||
137 | + border: 2px solid rgba(0,0,0,0.2); | ||
138 | + border-radius: 4px; | ||
139 | + cursor: pointer; | ||
140 | + font-size: 30px; | ||
141 | +} | ||
142 | + | ||
143 | +.bottom-tool-box { | ||
144 | + display: flex; | ||
145 | + gap: 10px; | ||
146 | + position: absolute; | ||
147 | + bottom: 10px; | ||
148 | + right: calc(50% - 25px); | ||
149 | + .filterBox { | ||
150 | + .filterBtn { | ||
151 | + width: 50px; | ||
152 | + height: 50px; | ||
153 | + padding: 7px; | ||
154 | + background: white; | ||
155 | + border: 2px solid rgba(0,0,0,0.2); | ||
156 | + border-radius: 4px; | ||
157 | + cursor: pointer; | ||
158 | + } | ||
159 | + } | ||
160 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&family=Roboto:wght@500&display=swap'); | ||
2 | + | ||
3 | +.ant-descriptions-bordered .ant-descriptions-item-label { | ||
4 | + background-color: #47749e; | ||
5 | + color: white; | ||
6 | +} | ||
7 | + | ||
8 | +.ant-descriptions-item { | ||
9 | + display: inline-flex; | ||
10 | + margin-right: 10px; | ||
11 | +} | ||
12 | + | ||
13 | +.ant-descriptions-row { | ||
14 | + font-family: 'Noto Sans KR', sans-serif; | ||
15 | + .ant-descriptions-item-content { | ||
16 | + min-width: 150px; | ||
17 | + font-size: 13px; | ||
18 | + background: white; | ||
19 | + } | ||
20 | +} | ||
21 | + | ||
22 | +.ant-descriptions-item > span { | ||
23 | + align-self: center; | ||
24 | +} | ||
25 | + | ||
26 | +.ant-descriptions-bordered .ant-descriptions-item-label, | ||
27 | +.ant-descriptions-bordered .ant-descriptions-item-content { | ||
28 | + padding: 10px 10px; | ||
29 | +} | ||
30 | +.description-box .ant-descriptions-item { | ||
31 | + display: table-cell; | ||
32 | +} | ||
33 | + | ||
34 | +.drone-detail { | ||
35 | + .ant-descriptions-view { | ||
36 | + max-height: calc(100vh - 120px); | ||
37 | + overflow: auto; | ||
38 | + } | ||
39 | + | ||
40 | + .ant-descriptions-item-label.ant-descriptions-item-colon { | ||
41 | + font-size: 16px; | ||
42 | + } | ||
43 | +} | ||
44 | + | ||
45 | +.ant-page-header-content { | ||
46 | + .search-box .ant-descriptions-row .ant-descriptions-item-content { | ||
47 | + min-width: 0; | ||
48 | + } | ||
49 | + .search-box .ant-descriptions-row { | ||
50 | + font-family: none; | ||
51 | + } | ||
52 | +} |
1 | + | ||
2 | +.mt-size-default{ | ||
3 | + margin-top: 8px; | ||
4 | +} | ||
5 | +.mt-size-sm { | ||
6 | + margin-top: 4px; | ||
7 | +} | ||
8 | +.mt-size-md { | ||
9 | + margin-top: 12px; | ||
10 | +} | ||
11 | +.mt-size-lg { | ||
12 | + margin-top: 20px; | ||
13 | +} | ||
14 | + | ||
15 | +.ml-size-default{ | ||
16 | + margin-left: 8px; | ||
17 | +} | ||
18 | +.ml-size-sm { | ||
19 | + margin-left: 4px; | ||
20 | +} | ||
21 | +.ml-size-md { | ||
22 | + margin-left: 12px; | ||
23 | +} | ||
24 | +.ml-size-lg { | ||
25 | + margin-left: 20px; | ||
26 | +} | ||
27 | + | ||
28 | +.mr-size-default{ | ||
29 | + margin-right: 8px; | ||
30 | +} | ||
31 | +.mr-size-sm { | ||
32 | + margin-right: 4px; | ||
33 | +} | ||
34 | +.mr-size-md { | ||
35 | + margin-right: 12px; | ||
36 | +} | ||
37 | +.mr-size-lg { | ||
38 | + margin-right: 20px; | ||
39 | +} | ||
40 | + | ||
41 | +.mb-size-default{ | ||
42 | + margin-bottom: 8px; | ||
43 | +} | ||
44 | +.mb-size-sm { | ||
45 | + margin-bottom: 4px; | ||
46 | +} | ||
47 | +.mb-size-md { | ||
48 | + margin-bottom: 12px; | ||
49 | +} | ||
50 | +.mb-size-lg { | ||
51 | + margin-bottom: 20px; | ||
52 | +} | ||
53 | + | ||
54 | +.margin-size-default{ | ||
55 | + margin: 8px; | ||
56 | +} | ||
57 | +.margin-size-sm { | ||
58 | + margin: 4px; | ||
59 | +} | ||
60 | +.margin-size-md { | ||
61 | + margin: 12px; | ||
62 | +} | ||
63 | +.margin-size-lg { | ||
64 | + margin: 20px; | ||
65 | +} | ||
66 | + | ||
67 | + | ||
68 | +.pt-size-default{ | ||
69 | + padding-top: 8px; | ||
70 | +} | ||
71 | +.pt-size-sm { | ||
72 | + padding-top: 4px; | ||
73 | +} | ||
74 | +.pt-size-md { | ||
75 | + padding-top: 12px; | ||
76 | +} | ||
77 | +.pt-size-lg { | ||
78 | + padding-top: 20px; | ||
79 | +} | ||
80 | + | ||
81 | +.pl-size-default{ | ||
82 | + padding-left: 8px; | ||
83 | +} | ||
84 | +.pl-size-sm { | ||
85 | + padding-left: 4px; | ||
86 | +} | ||
87 | +.pl-size-md { | ||
88 | + padding-left: 12px; | ||
89 | +} | ||
90 | +.pl-size-lg { | ||
91 | + padding-left: 20px; | ||
92 | +} | ||
93 | + | ||
94 | +.pr-size-default{ | ||
95 | + padding-right: 8px; | ||
96 | +} | ||
97 | +.pr-size-sm { | ||
98 | + padding-right: 4px; | ||
99 | +} | ||
100 | +.pr-size-md { | ||
101 | + padding-right: 12px; | ||
102 | +} | ||
103 | +.pr-size-lg { | ||
104 | + padding-right: 20px; | ||
105 | +} | ||
106 | + | ||
107 | +.pb-size-default{ | ||
108 | + padding-bottom: 8px; | ||
109 | +} | ||
110 | +.pb-size-sm { | ||
111 | + padding-bottom: 4px; | ||
112 | +} | ||
113 | +.pb-size-md { | ||
114 | + padding-bottom: 12px; | ||
115 | +} | ||
116 | +.pb-size-lg { | ||
117 | + padding-bottom: 20px; | ||
118 | +} | ||
119 | + | ||
120 | +.padding-size-default{ | ||
121 | + padding: 8px; | ||
122 | +} | ||
123 | +.padding-size-sm { | ||
124 | + padding: 4px; | ||
125 | +} | ||
126 | +.padding-size-md { | ||
127 | + padding: 12px; | ||
128 | +} | ||
129 | +.padding-size-lg { | ||
130 | + padding: 20px; | ||
131 | +} | ||
132 | +.r-flex{ | ||
133 | + display: flex; | ||
134 | + &.center{ | ||
135 | + justify-content: center; | ||
136 | + align-items: center; | ||
137 | + } | ||
138 | + &.space-between{ | ||
139 | + justify-content: space-between; | ||
140 | + align-items: center; | ||
141 | + } | ||
142 | + &.space-around{ | ||
143 | + justify-content: space-around; | ||
144 | + align-items: center; | ||
145 | + } | ||
146 | + &.space-evenly{ | ||
147 | + justify-content: space-evenly; | ||
148 | + align-items: center; | ||
149 | + } | ||
150 | + &.start{ | ||
151 | + justify-content: start; | ||
152 | + align-items: center; | ||
153 | + } | ||
154 | + &.end{ | ||
155 | + justify-content: flex-end; | ||
156 | + align-items: center; | ||
157 | + } | ||
158 | + &.gap-1 { | ||
159 | + gap: 4px | ||
160 | + } | ||
161 | + &.gap-2 { | ||
162 | + gap: 8px | ||
163 | + } | ||
164 | + &.gap-3 { | ||
165 | + gap: 12px | ||
166 | + } | ||
167 | + &.gap-4 { | ||
168 | + gap: 16px | ||
169 | + } | ||
170 | + &.gap-5 { | ||
171 | + gap: 20px | ||
172 | + } | ||
173 | + &.gap-6 { | ||
174 | + gap: 24px | ||
175 | + } | ||
176 | + &.gap-default{ | ||
177 | + gap: 8px | ||
178 | + } | ||
179 | + &.gap-sm { | ||
180 | + gap: 6px | ||
181 | + } | ||
182 | + &.gap-md { | ||
183 | + gap: 12px | ||
184 | + } | ||
185 | + &.gap-lg { | ||
186 | + gap: 20px | ||
187 | + } | ||
188 | +} |
1 | +@import '@/assets/styles/mixins.scss'; | ||
2 | + | ||
3 | +.page-header { | ||
4 | + background-color: white; | ||
5 | + border: $gray-3 1px solid; | ||
6 | + border-radius: 6px; | ||
7 | +} | ||
8 | + | ||
9 | +.search-input { | ||
10 | + width: 50%; | ||
11 | + min-width: 200px; | ||
12 | +} | ||
13 | + | ||
14 | +.page-main { | ||
15 | + margin-top: 20px; | ||
16 | + padding: 20px 20px 20px 20px; | ||
17 | + background-color: white; | ||
18 | + border-radius: 6px; | ||
19 | + border: $gray-2 1px solid; | ||
20 | +} | ||
21 | + | ||
22 | +.page-main-without-header { | ||
23 | + padding: 20px 20px 20px 20px; | ||
24 | + background-color: white; | ||
25 | + border-radius: 6px; | ||
26 | + border: $gray-2 1px solid; | ||
27 | +} |
1 | +.ant-table-pagination.ant-pagination { | ||
2 | + float: none; | ||
3 | + text-align: center; | ||
4 | +} | ||
5 | + | ||
6 | +.ant-pagination-prev .ant-pagination-item-link, | ||
7 | +.ant-pagination-next .ant-pagination-item-link, | ||
8 | +.ant-pagination-item { | ||
9 | + border: none; | ||
10 | + outline: none; | ||
11 | +} | ||
12 | + | ||
13 | +.ant-pagination-item { | ||
14 | + font-size: 1rem; | ||
15 | +} | ||
16 | + | ||
17 | +.ant-pagination-item-active a { | ||
18 | + font-weight: bolder; | ||
19 | +} |
1 | +@import '@/assets/styles/mixins.scss'; |
1 | +.default-layout-enter-active, | ||
2 | +.default-layout-leave-active { | ||
3 | + transition: opacity 0.5s; | ||
4 | +} | ||
5 | +.default-layout-enter, | ||
6 | +.default-layout-leave-active { | ||
7 | + opacity: 0; | ||
8 | +} | ||
9 | +// | ||
10 | +.default-page-enter-active, | ||
11 | +.default-page-leave-active { | ||
12 | + transition: opacity 0.3s; | ||
13 | +} | ||
14 | +.default-page-enter, | ||
15 | +.default-page-leave-active { | ||
16 | + opacity: 0; | ||
17 | +} |
1 | +<template> | ||
2 | + <pie-chart :chart-data="chartData" | ||
3 | + :chart-settings="chartSettings" | ||
4 | + /> | ||
5 | +</template> | ||
6 | + | ||
7 | +<script> | ||
8 | +import PieChart from '@/components/_Common/Chart/pieChart'; | ||
9 | + | ||
10 | +export default { | ||
11 | + name: 'droneCategoryPieChart', | ||
12 | + components: { | ||
13 | + PieChart, | ||
14 | + }, | ||
15 | + props: { | ||
16 | + chartData: { | ||
17 | + type: Array, | ||
18 | + default: null, | ||
19 | + }, | ||
20 | + }, | ||
21 | + data() { | ||
22 | + return { | ||
23 | + chartSettings: { | ||
24 | + chart: { | ||
25 | + height: 400, | ||
26 | + }, | ||
27 | + title: { | ||
28 | + text: '카테고리별 드론 기체 수', | ||
29 | + style: { | ||
30 | + fontSize: '20px', | ||
31 | + }, | ||
32 | + }, | ||
33 | + width: '50%', | ||
34 | + plotOptions: { | ||
35 | + pie: { | ||
36 | + shadow: true, | ||
37 | + allowPointSelect: true, | ||
38 | + cursor: 'pointer', | ||
39 | + dataLabels: { | ||
40 | + useHTML: true, | ||
41 | + distance: -50, | ||
42 | + formatter() { | ||
43 | + if (this.percentage.toFixed(0) < 6) return ''; | ||
44 | + return `<div style="padding: 6px 4px 4px 6px; | ||
45 | + background-color: rgba(0, 0, 0, 0.5); | ||
46 | + border: 2px solid #f2f4f8; | ||
47 | + border-radius: 6px; | ||
48 | + ">${this.percentage.toFixed(1)}%</div>`; | ||
49 | + }, | ||
50 | + style: { | ||
51 | + fontSize: '16px', | ||
52 | + color: 'white', | ||
53 | + }, | ||
54 | + }, | ||
55 | + showInLegend: true, | ||
56 | + }, | ||
57 | + }, | ||
58 | + tooltip: { | ||
59 | + formatter() { | ||
60 | + return ` | ||
61 | + <span style="font-size:16px; color:${this.color}">${this.key}</span> | ||
62 | + <table> | ||
63 | + <tr> | ||
64 | + <td style="padding:0">${this.series.name}</td> | ||
65 | + <td style="padding:0">: <b>${this.y}</b></td> | ||
66 | + </tr> | ||
67 | + <tr> | ||
68 | + <td style="padding:0">점유율</td> | ||
69 | + <td style="color:{series.color};padding:0">: <b>${this.percentage.toFixed(1)}%</b></td> | ||
70 | + </tr> | ||
71 | + </table> | ||
72 | + `; | ||
73 | + }, | ||
74 | + }, | ||
75 | + }, | ||
76 | + }; | ||
77 | + }, | ||
78 | +}; | ||
79 | +</script> | ||
80 | + | ||
81 | +<style scoped> | ||
82 | + | ||
83 | +</style> |
1 | +<template> | ||
2 | + <live-line-chart :chart-data="chartData" | ||
3 | + :chart-settings="chartSettings" | ||
4 | + /> | ||
5 | +</template> | ||
6 | + | ||
7 | +<script> | ||
8 | +import LiveLineChart from '@/components/_Common/Chart/liveLineChart'; | ||
9 | +import dayjs from 'dayjs'; | ||
10 | + | ||
11 | +export default { | ||
12 | + components: { | ||
13 | + LiveLineChart, | ||
14 | + }, | ||
15 | + props: { | ||
16 | + chartData: { | ||
17 | + type: Array, | ||
18 | + default: null, | ||
19 | + }, | ||
20 | + }, | ||
21 | + data() { | ||
22 | + return { | ||
23 | + chartSettings: { | ||
24 | + chart: { | ||
25 | + height: 400, | ||
26 | + }, | ||
27 | + title: { | ||
28 | + text: '시간별 드론 기체 수', | ||
29 | + style: { | ||
30 | + fontSize: '20px', | ||
31 | + }, | ||
32 | + }, | ||
33 | + width: '100%', | ||
34 | + xAxis: { | ||
35 | + type: 'datetime', | ||
36 | + }, | ||
37 | + yAxis: { | ||
38 | + title: { | ||
39 | + text: null, | ||
40 | + }, | ||
41 | + min: 0, | ||
42 | + }, | ||
43 | + tooltip: { | ||
44 | + formatter() { | ||
45 | + const trList = this.points.map((elem) => ` | ||
46 | + <tr> | ||
47 | + <td style="font-size: 16px; padding:0; color:${elem.color}">${elem.series.name}</td> | ||
48 | + <td style="font-size: 16px; padding:0; color:${elem.color}">: <b>${elem.y}</b></td> | ||
49 | + </tr> | ||
50 | + `); | ||
51 | + return ` | ||
52 | + <span style="font-size:12px; color:${this.color}">${dayjs(this.x).format('YYYY-MM-DD HH:mm:ss')}</span> | ||
53 | + <table> | ||
54 | + ${trList} | ||
55 | + </table> | ||
56 | + `; | ||
57 | + }, | ||
58 | + }, | ||
59 | + data: { | ||
60 | + enablePolling: true, | ||
61 | + dataRefreshRate: 2, | ||
62 | + }, | ||
63 | + }, | ||
64 | + }; | ||
65 | + }, | ||
66 | +}; | ||
67 | +</script> | ||
68 | + | ||
69 | +<style scoped lang="scss"> | ||
70 | + | ||
71 | +</style> |
1 | +<template> | ||
2 | + <pie-chart :chart-data="chartData" | ||
3 | + :chart-settings="chartSettings" | ||
4 | + /> | ||
5 | +</template> | ||
6 | + | ||
7 | +<script> | ||
8 | +import PieChart from '@/components/_Common/Chart/pieChart'; | ||
9 | + | ||
10 | +export default { | ||
11 | + name: 'makerPieChart', | ||
12 | + components: { | ||
13 | + PieChart, | ||
14 | + }, | ||
15 | + props: { | ||
16 | + chartData: { | ||
17 | + type: Array, | ||
18 | + default: null, | ||
19 | + }, | ||
20 | + }, | ||
21 | + data() { | ||
22 | + return { | ||
23 | + chartSettings: { | ||
24 | + chart: { | ||
25 | + height: 400, | ||
26 | + }, | ||
27 | + title: { | ||
28 | + text: '제조사별 드론 기체 수', | ||
29 | + style: { | ||
30 | + fontSize: '20px', | ||
31 | + }, | ||
32 | + }, | ||
33 | + width: '50%', | ||
34 | + plotOptions: { | ||
35 | + pie: { | ||
36 | + shadow: true, | ||
37 | + allowPointSelect: true, | ||
38 | + cursor: 'pointer', | ||
39 | + dataLabels: { | ||
40 | + useHTML: true, | ||
41 | + distance: -50, | ||
42 | + formatter() { | ||
43 | + if (this.percentage.toFixed(0) < 6) return ''; | ||
44 | + return `<div style="padding: 6px 4px 4px 6px; | ||
45 | + background-color: rgba(0, 0, 0, 0.5); | ||
46 | + border: 2px solid #f2f4f8; | ||
47 | + border-radius: 6px; | ||
48 | + ">${this.percentage.toFixed(1)}%</div>`; | ||
49 | + }, | ||
50 | + style: { | ||
51 | + fontSize: '16px', | ||
52 | + color: 'white', | ||
53 | + }, | ||
54 | + }, | ||
55 | + showInLegend: true, | ||
56 | + }, | ||
57 | + }, | ||
58 | + tooltip: { | ||
59 | + formatter() { | ||
60 | + return ` | ||
61 | + <span style="font-size:16px; color:${this.color}">${this.key}</span> | ||
62 | + <table> | ||
63 | + <tr> | ||
64 | + <td style="padding:0">${this.series.name}</td> | ||
65 | + <td style="padding:0">: <b>${this.y}</b></td> | ||
66 | + </tr> | ||
67 | + <tr> | ||
68 | + <td style="padding:0">점유율</td> | ||
69 | + <td style="color:{series.color};padding:0">: <b>${this.percentage.toFixed(1)}%</b></td> | ||
70 | + </tr> | ||
71 | + </table> | ||
72 | + `; | ||
73 | + }, | ||
74 | + }, | ||
75 | + }, | ||
76 | + }; | ||
77 | + }, | ||
78 | +}; | ||
79 | +</script> | ||
80 | + | ||
81 | +<style scoped> | ||
82 | + | ||
83 | +</style> |
1 | +<template> | ||
2 | + <column-chart :chart-data="chartData" | ||
3 | + :chart-settings="chartSettings"/> | ||
4 | +</template> | ||
5 | + | ||
6 | +<script> | ||
7 | +import ColumnChart from '@/components/_Common/Chart/columnChart'; | ||
8 | + | ||
9 | +export default { | ||
10 | + name: 'timeCategoryColumnChart', | ||
11 | + components: { | ||
12 | + ColumnChart, | ||
13 | + | ||
14 | + }, | ||
15 | + props: { | ||
16 | + chartData: { | ||
17 | + type: Array, | ||
18 | + default: () => [], | ||
19 | + }, | ||
20 | + }, | ||
21 | + data() { | ||
22 | + return { | ||
23 | + chartSettings: { | ||
24 | + chart: { | ||
25 | + height: 400, | ||
26 | + }, | ||
27 | + title: { | ||
28 | + text: '시간-드론 타입별 드론 기체 수', | ||
29 | + style: { | ||
30 | + fontSize: '20px', | ||
31 | + }, | ||
32 | + }, | ||
33 | + width: '100%', | ||
34 | + xAxis: { | ||
35 | + categories: [ | ||
36 | + '00:00 ~ 04:00', | ||
37 | + '04:00 ~ 08:00', | ||
38 | + '08:00 ~ 12:00', | ||
39 | + '12:00 ~ 16:00', | ||
40 | + '16:00 ~ 20:00', | ||
41 | + '20:00 ~ 24:00', | ||
42 | + ], | ||
43 | + }, | ||
44 | + yAxis: { | ||
45 | + title: { | ||
46 | + text: null, | ||
47 | + }, | ||
48 | + min: 0, | ||
49 | + }, | ||
50 | + }, | ||
51 | + }; | ||
52 | + }, | ||
53 | +}; | ||
54 | +</script> | ||
55 | + | ||
56 | +<style scoped lang="scss"> | ||
57 | + | ||
58 | +</style> |
Frontend/src/components/Analytics/header.vue
0 → 100644
1 | +<template> | ||
2 | + <a-page-header | ||
3 | + class="page-header" | ||
4 | + title="Analytics" | ||
5 | + sub-title="Chart for Realtime Drone Data" | ||
6 | + > | ||
7 | + <template slot="extra"> | ||
8 | + <a-button key="1" type="primary"> | ||
9 | + action | ||
10 | + </a-button> | ||
11 | + </template> | ||
12 | + </a-page-header> | ||
13 | +</template> | ||
14 | + | ||
15 | +<script> | ||
16 | + | ||
17 | +export default { | ||
18 | + name: 'AnalyticsHeader', | ||
19 | + components: {}, | ||
20 | + data() { | ||
21 | + return { | ||
22 | + }; | ||
23 | + }, | ||
24 | +}; | ||
25 | +</script> | ||
26 | + | ||
27 | +<style scoped lang="scss"> | ||
28 | + | ||
29 | +</style> |
1 | +<template> | ||
2 | + <a-page-header | ||
3 | + class="page-header" | ||
4 | + title="드론 정보" | ||
5 | + sub-title="Drone-Schedule" | ||
6 | + @back="$router.go(-1)" | ||
7 | + > | ||
8 | + <div :style="{display: 'flex'}"> | ||
9 | + <div :style="{width: '30%'}"> | ||
10 | + <img :src="droneInfo.picture" :style="{width: '90%'}" /> | ||
11 | + </div> | ||
12 | + | ||
13 | + <div :style="{width: '70%', marginLeft: '20px'}"> | ||
14 | + <div class="label-modelName">{{ droneInfo.modelName }}</div> | ||
15 | + <div :style="{display: 'flex', marginBottom: '15px'}"> | ||
16 | + <div class="label-info">info</div> | ||
17 | + | ||
18 | + <a-descriptions | ||
19 | + :column="{xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1}" | ||
20 | + class="description-box" | ||
21 | + > | ||
22 | + <a-descriptions-item label="모델명"> | ||
23 | + {{ droneInfo.modelName }} | ||
24 | + </a-descriptions-item> | ||
25 | + <a-descriptions-item label="제조사"> | ||
26 | + {{ droneInfo.maker == null ? 'None' : droneInfo.maker }} | ||
27 | + </a-descriptions-item> | ||
28 | + <a-descriptions-item label="종류"> | ||
29 | + {{ droneInfo.usage == null ? 'None' : droneInfo.usage }} | ||
30 | + </a-descriptions-item> | ||
31 | + <a-descriptions-item label="제원"> | ||
32 | + {{ | ||
33 | + droneInfo.specification == null | ||
34 | + ? 'None' | ||
35 | + : droneInfo.specification | ||
36 | + }} | ||
37 | + </a-descriptions-item> | ||
38 | + <a-descriptions-item label="무게"> | ||
39 | + {{ droneInfo.weight == null ? '?' : droneInfo.weight }} kg | ||
40 | + </a-descriptions-item> | ||
41 | + <a-descriptions-item label="No"> | ||
42 | + {{ droneInfo.id }} | ||
43 | + </a-descriptions-item> | ||
44 | + </a-descriptions> | ||
45 | + </div> | ||
46 | + </div> | ||
47 | + </div> | ||
48 | + <a-divider /> | ||
49 | + </a-page-header> | ||
50 | +</template> | ||
51 | + | ||
52 | +<script> | ||
53 | +import {mapGetters} from 'vuex'; | ||
54 | + | ||
55 | +export default { | ||
56 | + name: 'DatabaseDetailHeader', | ||
57 | + components: {}, | ||
58 | + data() { | ||
59 | + return { | ||
60 | + droneInfo: {}, | ||
61 | + }; | ||
62 | + }, | ||
63 | + computed: { | ||
64 | + ...mapGetters('Drone/detail', { | ||
65 | + getDetailData: 'getDetailData', | ||
66 | + }), | ||
67 | + ...mapGetters('Code', { | ||
68 | + getCodes: 'getCodes', | ||
69 | + }), | ||
70 | + droneCategory() { | ||
71 | + return (data) => { | ||
72 | + switch (parseInt(data, 10)) { | ||
73 | + case 1: | ||
74 | + return '촬영용'; | ||
75 | + case 2: | ||
76 | + return '레이싱용'; | ||
77 | + case 3: | ||
78 | + return '완구용'; | ||
79 | + default: | ||
80 | + return null; | ||
81 | + } | ||
82 | + }; | ||
83 | + }, | ||
84 | + }, | ||
85 | + created() { | ||
86 | + console.log(this.getDetailData); | ||
87 | + this.droneInfo = this.getDetailData; | ||
88 | + }, | ||
89 | +}; | ||
90 | +</script> | ||
91 | + | ||
92 | +<style scoped lang="scss"> | ||
93 | +@import '@/assets/styles/mixins.scss'; | ||
94 | + | ||
95 | +.label-modelName { | ||
96 | + font-size: 30px; | ||
97 | + color: $antblue; | ||
98 | +} | ||
99 | +.label-info { | ||
100 | + padding: 0 10px; | ||
101 | + border-right: 1px solid #777777; | ||
102 | + min-width: 20%; | ||
103 | +} | ||
104 | +.description-box { | ||
105 | + padding-left: 10px; | ||
106 | +} | ||
107 | +</style> |
1 | +<template> | ||
2 | + <a-page-header | ||
3 | + class="page-header" | ||
4 | + title="Database" | ||
5 | + sub-title="Table - Drone list" | ||
6 | + > | ||
7 | + <template slot="extra"> | ||
8 | + <a-button key="1" type="primary" form="form" html-type="submit"> | ||
9 | + 검색 | ||
10 | + </a-button> | ||
11 | + </template> | ||
12 | + | ||
13 | + <a-form id="form" @submit.prevent="searchData" class="form-wrapper"> | ||
14 | + <a-form-item label="모델명" class="form-item"> | ||
15 | + <a-input v-model="searchParams.modelName" class="search-input" /> | ||
16 | + </a-form-item> | ||
17 | + <a-form-item label="제조사" class="form-item"> | ||
18 | + <a-input v-model="searchParams.maker" class="search-input" /> | ||
19 | + </a-form-item> | ||
20 | + <a-form-item label="무게" class="form-item"> | ||
21 | + <a-button | ||
22 | + v-if="searchOpenFlag" | ||
23 | + class="search-input" | ||
24 | + @click="searchOpenFlag = !searchOpenFlag" | ||
25 | + >{{ sliderBegin }} - {{ sliderEnd == 50 ? '50+' : sliderEnd }} kg | ||
26 | + <a-icon type="up" /> | ||
27 | + </a-button> | ||
28 | + <a-button | ||
29 | + v-else | ||
30 | + class="search-input" | ||
31 | + @click="searchOpenFlag = !searchOpenFlag" | ||
32 | + >{{ sliderBegin }} - {{ sliderEnd == 50 ? '50+' : sliderEnd }} kg | ||
33 | + <a-icon type="down" /> | ||
34 | + </a-button> | ||
35 | + <div v-if="searchOpenFlag" class="slider-box"> | ||
36 | + <a-slider | ||
37 | + range | ||
38 | + :marks="marks" | ||
39 | + :max="50" | ||
40 | + :step="1" | ||
41 | + :default-value="[sliderBegin, sliderEnd]" | ||
42 | + class="search-input" | ||
43 | + @change="onSliderChange" | ||
44 | + @afterChange="onSliderAfterChange" | ||
45 | + /> | ||
46 | + </div> | ||
47 | + </a-form-item> | ||
48 | + <a-form-item label="종류" has-feedback class="form-item"> | ||
49 | + <a-select | ||
50 | + class="search-input" | ||
51 | + default-value="" | ||
52 | + @change="handleSelectChange" | ||
53 | + > | ||
54 | + <a-select-option value=""> | ||
55 | + 선택 안 함 | ||
56 | + </a-select-option> | ||
57 | + <a-select-option value="촬영용"> | ||
58 | + 촬영용 | ||
59 | + </a-select-option> | ||
60 | + <a-select-option value="산업용"> | ||
61 | + 산업용 | ||
62 | + </a-select-option> | ||
63 | + <a-select-option value="군사용"> | ||
64 | + 군사용 | ||
65 | + </a-select-option> | ||
66 | + <a-select-option value="레이싱용"> | ||
67 | + 레이싱용 | ||
68 | + </a-select-option> | ||
69 | + </a-select> | ||
70 | + </a-form-item> | ||
71 | + </a-form> | ||
72 | + </a-page-header> | ||
73 | +</template> | ||
74 | + | ||
75 | +<script> | ||
76 | +import {droneCategory} from '@/utils/CommonData/selectOptions'; | ||
77 | +import {mapActions, mapGetters} from 'vuex'; | ||
78 | + | ||
79 | +export default { | ||
80 | + name: 'DatabaseSearchFilter', | ||
81 | + components: {}, | ||
82 | + data() { | ||
83 | + return { | ||
84 | + searchOpenFlag: false, | ||
85 | + categoryOptions: droneCategory, | ||
86 | + searchParams: {}, | ||
87 | + marks: { | ||
88 | + 0: '0kg', | ||
89 | + 50: '50+kg', | ||
90 | + }, | ||
91 | + sliderBegin: 0, | ||
92 | + sliderEnd: 50, | ||
93 | + }; | ||
94 | + }, | ||
95 | + computed: { | ||
96 | + ...mapGetters('Drone/page', { | ||
97 | + getPageParams: 'getPageParams', | ||
98 | + }), | ||
99 | + }, | ||
100 | + created() { | ||
101 | + this.searchParams = JSON.parse(JSON.stringify(this.getPageParams)); | ||
102 | + }, | ||
103 | + methods: { | ||
104 | + ...mapActions('Drone/page', { | ||
105 | + setPageParams: 'setPageParams', | ||
106 | + }), | ||
107 | + searchData() { | ||
108 | + console.log(this.searchParams); | ||
109 | + this.setPageParams(this.searchParams); | ||
110 | + this.$emit('loadData'); | ||
111 | + }, | ||
112 | + handleSelectChange(value) { | ||
113 | + this.searchParams.usageName = value; | ||
114 | + this.searchData(); | ||
115 | + }, | ||
116 | + onSliderChange(value) { | ||
117 | + this.sliderBegin = value[0]; | ||
118 | + this.sliderEnd = value[1]; | ||
119 | + }, | ||
120 | + onSliderAfterChange(value) { | ||
121 | + this.searchParams.minWeight = value[0]; | ||
122 | + this.searchParams.maxWeight = value[1]; | ||
123 | + if (value[1] == 50) { | ||
124 | + this.searchParams.maxWeight = null; | ||
125 | + } | ||
126 | + this.searchOpenFlag = !this.searchOpenFlag; | ||
127 | + }, | ||
128 | + }, | ||
129 | +}; | ||
130 | +</script> | ||
131 | + | ||
132 | +<style scoped lang="scss"> | ||
133 | +@import '@/assets/styles/mixins.scss'; | ||
134 | + | ||
135 | +.form-wrapper { | ||
136 | + height: 40px; | ||
137 | +} | ||
138 | +.form-wrapper, | ||
139 | +.form-item { | ||
140 | + display: flex; | ||
141 | + margin-left: 10px; | ||
142 | + margin-bottom: 0; | ||
143 | +} | ||
144 | + | ||
145 | +.slider-box { | ||
146 | + background: white; | ||
147 | + padding: 10px 20px; | ||
148 | + border-radius: 20px; | ||
149 | + border: solid 1px $antblue; | ||
150 | + position: fixed; | ||
151 | + z-index: 10; | ||
152 | +} | ||
153 | +</style> |
1 | +<template> | ||
2 | + <a-table | ||
3 | + rowKey="id" | ||
4 | + bordered | ||
5 | + :loading="childLoading" | ||
6 | + :columns="columns" | ||
7 | + :data-source="getPageData" | ||
8 | + :scroll="{x: 1000}" | ||
9 | + :pagination="pagination" | ||
10 | + @change="changePage" | ||
11 | + > | ||
12 | + <a slot="modelName" slot-scope="data, row" @click="goDetail(row)"> | ||
13 | + {{ data }} | ||
14 | + </a> | ||
15 | + <div slot="usageName" slot-scope="data"> | ||
16 | + {{ data == null ? '?' : data }} | ||
17 | + </div> | ||
18 | + <div slot="weight" slot-scope="data"> | ||
19 | + {{ data == null ? '?' : data + 'kg' }} | ||
20 | + </div> | ||
21 | + <div slot="specification" slot-scope="data"> | ||
22 | + {{ data == null ? '?' : data }} | ||
23 | + </div> | ||
24 | + <div slot="droneCategory" slot-scope="data"> | ||
25 | + {{ data }} | ||
26 | + </div> | ||
27 | + </a-table> | ||
28 | +</template> | ||
29 | + | ||
30 | +<script> | ||
31 | +import databaseColumn from '@/utils/ColumnData/databaseColumn'; | ||
32 | +import { mapActions, mapGetters } from 'vuex'; | ||
33 | + | ||
34 | +export default { | ||
35 | + name: 'DatabaseTable', | ||
36 | + props: { | ||
37 | + childLoading: { | ||
38 | + type: Boolean, | ||
39 | + default: false, | ||
40 | + }, | ||
41 | + }, | ||
42 | + data() { | ||
43 | + return { | ||
44 | + columns: databaseColumn, | ||
45 | + }; | ||
46 | + }, | ||
47 | + computed: { | ||
48 | + ...mapGetters('Drone/page', { | ||
49 | + getPageData: 'getPageData', | ||
50 | + getPagination: 'getPagination', | ||
51 | + getPageParams: 'getPageParams', | ||
52 | + }), | ||
53 | + pagination: { | ||
54 | + get() { | ||
55 | + return this.getPagination; | ||
56 | + }, | ||
57 | + set(e) { | ||
58 | + this.setPagination({ | ||
59 | + size: e.size, | ||
60 | + page: e.page, | ||
61 | + }); | ||
62 | + }, | ||
63 | + }, | ||
64 | + }, | ||
65 | + methods: { | ||
66 | + ...mapActions('Drone/page', { | ||
67 | + setPagination: 'setPagination', | ||
68 | + fetchPageData: 'fetchPageData', | ||
69 | + }), | ||
70 | + changePage(e) { | ||
71 | + this.pagination = { | ||
72 | + size: e.pageSize, | ||
73 | + page: e.current, | ||
74 | + }; | ||
75 | + this.fetchPageData(this.getPageParams); | ||
76 | + }, | ||
77 | + goDetail(row) { | ||
78 | + this.$router.push({ | ||
79 | + path: `/database/drone/${row.id}`, | ||
80 | + }); | ||
81 | + }, | ||
82 | + }, | ||
83 | +}; | ||
84 | +</script> | ||
85 | + | ||
86 | +<style scoped lang="scss"></style> |
1 | +<template> | ||
2 | + <a-page-header | ||
3 | + class="page-header" | ||
4 | + title="Database" | ||
5 | + sub-title="Table - Drone Log" | ||
6 | + > | ||
7 | + <template slot="extra"> | ||
8 | + <a-button | ||
9 | + key="1" | ||
10 | + type="primary" | ||
11 | + form="form" | ||
12 | + html-type="submit" | ||
13 | + :style="{padding: '0 15px'}" | ||
14 | + > | ||
15 | + 검색 | ||
16 | + </a-button> | ||
17 | + </template> | ||
18 | + | ||
19 | + <a-form id="form" @submit.prevent="searchData" class="form-wrapper"> | ||
20 | + <a-descriptions | ||
21 | + :column="{xs: 1, sm: 3, md: 3, lg: 6, xl: 7, xxl: 7}" | ||
22 | + class="search-box" | ||
23 | + > | ||
24 | + <a-descriptions-item label="Schedule ID" class="form-item"> | ||
25 | + <a-input v-model="searchParams.scheduleId" class="search-input-log" /> | ||
26 | + </a-descriptions-item> | ||
27 | + <a-descriptions-item label="수평 속도" class="form-item"> | ||
28 | + <a-button v-if="hsOpenFlag" @click="hsOpenFlag = !hsOpenFlag" | ||
29 | + >{{ minHorizontalSpeed }} - | ||
30 | + {{ maxHorizontalSpeed == 100 ? '100+' : maxHorizontalSpeed }} km/s | ||
31 | + <a-icon type="up" /> | ||
32 | + </a-button> | ||
33 | + <a-button v-else @click="hsOpenFlag = !hsOpenFlag" | ||
34 | + >{{ minHorizontalSpeed }} - | ||
35 | + {{ maxHorizontalSpeed == 100 ? '100+' : maxHorizontalSpeed }} km/s | ||
36 | + <a-icon type="down" /> | ||
37 | + </a-button> | ||
38 | + <div v-if="hsOpenFlag" class="slider-box"> | ||
39 | + <a-slider | ||
40 | + range | ||
41 | + :marks="speedMarks" | ||
42 | + :max="100" | ||
43 | + :step="1" | ||
44 | + :default-value="[minHorizontalSpeed, maxHorizontalSpeed]" | ||
45 | + class="search-input" | ||
46 | + @change="onHorizontalSpeedChange" | ||
47 | + @afterChange="onHorizontalSpeedAfterChange" | ||
48 | + /> | ||
49 | + </div> | ||
50 | + </a-descriptions-item> | ||
51 | + <a-descriptions-item label="수직 속도" class="form-item"> | ||
52 | + <a-button v-if="vsOpenFlag" @click="vsOpenFlag = !vsOpenFlag" | ||
53 | + >{{ minVerticalSpeed }} - | ||
54 | + {{ maxVerticalSpeed == 100 ? '100+' : maxVerticalSpeed }} km/s | ||
55 | + <a-icon type="up" /> | ||
56 | + </a-button> | ||
57 | + <a-button v-else @click="vsOpenFlag = !vsOpenFlag" | ||
58 | + >{{ minVerticalSpeed }} - | ||
59 | + {{ maxVerticalSpeed == 100 ? '100+' : maxVerticalSpeed }} km/s | ||
60 | + <a-icon type="down" /> | ||
61 | + </a-button> | ||
62 | + <div v-if="vsOpenFlag" class="slider-box"> | ||
63 | + <a-slider | ||
64 | + range | ||
65 | + :marks="speedMarks" | ||
66 | + :max="100" | ||
67 | + :step="1" | ||
68 | + :default-value="[minVerticalSpeed, maxVerticalSpeed]" | ||
69 | + class="search-input" | ||
70 | + @change="onVerticalSpeedChange" | ||
71 | + @afterChange="onVerticalSpeedAfterChange" | ||
72 | + /> | ||
73 | + </div> | ||
74 | + </a-descriptions-item> | ||
75 | + <a-descriptions-item label="지면 고도" class="form-item"> | ||
76 | + <a-button v-if="aglOpenFlag" @click="aglOpenFlag = !aglOpenFlag" | ||
77 | + >{{ minAboveGroundLevel }} - | ||
78 | + {{ maxAboveGroundLevel == 200 ? '200+' : maxAboveGroundLevel }} m | ||
79 | + <a-icon type="up" /> | ||
80 | + </a-button> | ||
81 | + <a-button v-else @click="aglOpenFlag = !aglOpenFlag" | ||
82 | + >{{ minAboveGroundLevel }} - | ||
83 | + {{ maxAboveGroundLevel == 200 ? '200+' : maxAboveGroundLevel }} m | ||
84 | + <a-icon type="down" /> | ||
85 | + </a-button> | ||
86 | + <div v-if="aglOpenFlag" class="slider-box"> | ||
87 | + <a-slider | ||
88 | + range | ||
89 | + :marks="levelMarks" | ||
90 | + :max="200" | ||
91 | + :step="1" | ||
92 | + :default-value="[minAboveGroundLevel, maxAboveGroundLevel]" | ||
93 | + class="search-input" | ||
94 | + @change="onAboveGroundLevelChange" | ||
95 | + @afterChange="onAboveGroundLevelAfterChange" | ||
96 | + /> | ||
97 | + </div> | ||
98 | + </a-descriptions-item> | ||
99 | + <a-descriptions-item label="해발 고도" class="form-item"> | ||
100 | + <a-button v-if="aslOpenFlag" @click="aslOpenFlag = !aslOpenFlag" | ||
101 | + >{{ minAboveSeaLevel }} - | ||
102 | + {{ maxAboveSeaLevel == 200 ? '200+' : maxAboveSeaLevel }} m | ||
103 | + <a-icon type="up" /> | ||
104 | + </a-button> | ||
105 | + <a-button v-else @click="aslOpenFlag = !aslOpenFlag" | ||
106 | + >{{ minAboveSeaLevel }} - | ||
107 | + {{ maxAboveSeaLevel == 200 ? '200+' : maxAboveSeaLevel }} m | ||
108 | + <a-icon type="down" /> | ||
109 | + </a-button> | ||
110 | + <div v-if="aslOpenFlag" class="slider-box"> | ||
111 | + <a-slider | ||
112 | + range | ||
113 | + :marks="levelMarks" | ||
114 | + :max="200" | ||
115 | + :step="1" | ||
116 | + :default-value="[minAboveSeaLevel, maxAboveSeaLevel]" | ||
117 | + class="search-input" | ||
118 | + @change="onAboveSeaLevelChange" | ||
119 | + @afterChange="onAboveSeaLevelAfterChange" | ||
120 | + /> | ||
121 | + </div> | ||
122 | + </a-descriptions-item> | ||
123 | + <a-descriptions-item label="위도" class="form-item"> | ||
124 | + <a-input v-model="searchParams.latitude" class="search-input-log" /> | ||
125 | + </a-descriptions-item> | ||
126 | + <a-descriptions-item label="경도" class="form-item"> | ||
127 | + <a-input v-model="searchParams.longitude" class="search-input-log" /> | ||
128 | + </a-descriptions-item> | ||
129 | + </a-descriptions> | ||
130 | + </a-form> | ||
131 | + </a-page-header> | ||
132 | +</template> | ||
133 | + | ||
134 | +<script> | ||
135 | +/* eslint-disable prefer-destructuring */ | ||
136 | +import {mapActions, mapGetters} from 'vuex'; | ||
137 | + | ||
138 | +export default { | ||
139 | + name: 'LogSearchFilter', | ||
140 | + components: {}, | ||
141 | + data() { | ||
142 | + return { | ||
143 | + hsOpenFlag: false, | ||
144 | + vsOpenFlag: false, | ||
145 | + aslOpenFlag: false, | ||
146 | + aglOpenFlag: false, | ||
147 | + searchParams: {}, | ||
148 | + speedMarks: { | ||
149 | + 0: '0km/h', | ||
150 | + 100: '100+km/h', | ||
151 | + }, | ||
152 | + levelMarks: { | ||
153 | + 0: '0m', | ||
154 | + 200: '200+m', | ||
155 | + }, | ||
156 | + minVerticalSpeed: 0, | ||
157 | + maxVerticalSpeed: 100, | ||
158 | + minHorizontalSpeed: 0, | ||
159 | + maxHorizontalSpeed: 100, | ||
160 | + minAboveSeaLevel: 0, | ||
161 | + maxAboveSeaLevel: 200, | ||
162 | + minAboveGroundLevel: 0, | ||
163 | + maxAboveGroundLevel: 200, | ||
164 | + }; | ||
165 | + }, | ||
166 | + computed: { | ||
167 | + ...mapGetters('Log/page', { | ||
168 | + getPageParams: 'getPageParams', | ||
169 | + }), | ||
170 | + }, | ||
171 | + created() { | ||
172 | + this.searchParams = JSON.parse(JSON.stringify(this.getPageParams)); | ||
173 | + }, | ||
174 | + methods: { | ||
175 | + ...mapActions('Log/page', { | ||
176 | + setPageParams: 'setPageParams', | ||
177 | + }), | ||
178 | + searchData() { | ||
179 | + console.log(this.searchParams); | ||
180 | + this.setPageParams(this.searchParams); | ||
181 | + this.$emit('loadData'); | ||
182 | + }, | ||
183 | + onHorizontalSpeedChange(value) { | ||
184 | + this.minHorizontalSpeed = value[0]; | ||
185 | + this.maxHorizontalSpeed = value[1]; | ||
186 | + }, | ||
187 | + onHorizontalSpeedAfterChange(value) { | ||
188 | + this.hsOpenFlag = !this.hsOpenFlag; | ||
189 | + this.searchParams.minHorizontalSpeed = this.minHorizontalSpeed; | ||
190 | + this.searchParams.maxHorizontalSpeed = this.maxHorizontalSpeed; | ||
191 | + if (this.maxHorizontalSpeed == 100) { | ||
192 | + this.searchParams.maxHorizontalSpeed = null; | ||
193 | + } | ||
194 | + }, | ||
195 | + onVerticalSpeedChange(value) { | ||
196 | + this.minVerticalSpeed = value[0]; | ||
197 | + this.maxVerticalSpeed = value[1]; | ||
198 | + }, | ||
199 | + onVerticalSpeedAfterChange(value) { | ||
200 | + this.vsOpenFlag = !this.vsOpenFlag; | ||
201 | + this.searchParams.minVerticalSpeed = this.minVerticalSpeed; | ||
202 | + this.searchParams.maxVerticalSpeed = this.maxVerticalSpeed; | ||
203 | + if (this.maxVerticalSpeed == 100) { | ||
204 | + this.searchParams.maxVerticalSpeed = null; | ||
205 | + } | ||
206 | + }, | ||
207 | + onAboveSeaLevelChange(value) { | ||
208 | + this.minAboveSeaLevel = value[0]; | ||
209 | + this.maxAboveSeaLevel = value[1]; | ||
210 | + }, | ||
211 | + onAboveSeaLevelAfterChange(value) { | ||
212 | + this.aslOpenFlag = !this.aslOpenFlag; | ||
213 | + this.searchParams.minAboveSeaLevel = this.minAboveSeaLevel; | ||
214 | + this.searchParams.maxAboveSeaLevel = this.maxAboveSeaLevel; | ||
215 | + if (this.maxAboveSeaLevel == 200) { | ||
216 | + this.searchParams.maxAboveSeaLevel = null; | ||
217 | + } | ||
218 | + }, | ||
219 | + onAboveGroundLevelChange(value) { | ||
220 | + this.minAboveGroundLevel = value[0]; | ||
221 | + this.maxAboveGroundLevel = value[1]; | ||
222 | + }, | ||
223 | + onAboveGroundLevelAfterChange(value) { | ||
224 | + this.aglOpenFlag = !this.aglOpenFlag; | ||
225 | + this.searchParams.minAboveGroundLevel = this.minAboveGroundLevel; | ||
226 | + this.searchParams.maxAboveGroundLevel = this.maxAboveGroundLevel; | ||
227 | + if (this.maxAboveGroundLevel == 200) { | ||
228 | + this.searchParams.maxAboveGroundLevel = null; | ||
229 | + } | ||
230 | + }, | ||
231 | + }, | ||
232 | +}; | ||
233 | +</script> | ||
234 | + | ||
235 | +<style scoped lang="scss"> | ||
236 | +@import '@/assets/styles/mixins.scss'; | ||
237 | +.form-wrapper { | ||
238 | + //height: 40px; | ||
239 | + justify-content: space-between; | ||
240 | +} | ||
241 | +.form-wrapper, | ||
242 | +.form-item { | ||
243 | + display: flex; | ||
244 | + margin-right: 10px; | ||
245 | + margin-bottom: 0; | ||
246 | +} | ||
247 | + | ||
248 | +.slider-box { | ||
249 | + background: white; | ||
250 | + padding: 10px 23px; | ||
251 | + border-radius: 20px; | ||
252 | + border: solid 1px $antblue; | ||
253 | + position: fixed; | ||
254 | + z-index: 9; | ||
255 | +} | ||
256 | + | ||
257 | +.search-input-log { | ||
258 | + width: 100px; | ||
259 | +} | ||
260 | + | ||
261 | +.ant-btn { | ||
262 | + padding: 0 10px; | ||
263 | +} | ||
264 | +</style> |
1 | +<template> | ||
2 | + <a-table | ||
3 | + rowKey="id" | ||
4 | + bordered | ||
5 | + :loading="childLoading" | ||
6 | + :columns="columns" | ||
7 | + :data-source="getPageData" | ||
8 | + :scroll="{x: 1000}" | ||
9 | + :pagination="pagination" | ||
10 | + @change="changePage" | ||
11 | + > | ||
12 | + <a slot="modelName" slot-scope="data, row" @click="goDetail(row)"> | ||
13 | + {{ data }} | ||
14 | + </a> | ||
15 | + | ||
16 | + <div slot="horizontalSpeed" slot-scope="data">{{ data }} km/h</div> | ||
17 | + <div slot="verticalSpeed" slot-scope="data">{{ data }} km/h</div> | ||
18 | + <div slot="aboveGroundLevel" slot-scope="data">{{ data }} m</div> | ||
19 | + <div slot="aboveSeaLevel" slot-scope="data">{{ data }} m</div> | ||
20 | + </a-table> | ||
21 | +</template> | ||
22 | + | ||
23 | +<script> | ||
24 | +import databaseLogColumn from '@/utils/ColumnData/databaseLogColumn'; | ||
25 | +import { mapActions, mapGetters } from 'vuex'; | ||
26 | + | ||
27 | +export default { | ||
28 | + name: 'LogTable', | ||
29 | + props: { | ||
30 | + childLoading: { | ||
31 | + type: Boolean, | ||
32 | + default: false, | ||
33 | + }, | ||
34 | + }, | ||
35 | + data() { | ||
36 | + return { | ||
37 | + columns: databaseLogColumn, | ||
38 | + }; | ||
39 | + }, | ||
40 | + computed: { | ||
41 | + ...mapGetters('Log/page', { | ||
42 | + getPageData: 'getPageData', | ||
43 | + getPagination: 'getPagination', | ||
44 | + getPageParams: 'getPageParams', | ||
45 | + }), | ||
46 | + pagination: { | ||
47 | + get() { | ||
48 | + return this.getPagination; | ||
49 | + }, | ||
50 | + set(e) { | ||
51 | + this.setPagination({ | ||
52 | + size: e.size, | ||
53 | + page: e.page, | ||
54 | + }); | ||
55 | + }, | ||
56 | + }, | ||
57 | + }, | ||
58 | + methods: { | ||
59 | + ...mapActions('Log/page', { | ||
60 | + setPagination: 'setPagination', | ||
61 | + fetchPageData: 'fetchPageData', | ||
62 | + }), | ||
63 | + ...mapActions('Drone/detail', { | ||
64 | + fetchDroneDetailData: 'fetchDetailData', | ||
65 | + }), | ||
66 | + ...mapActions('Schedule/detail', { | ||
67 | + fetchScheduleDetailData: 'fetchDetailData', | ||
68 | + }), | ||
69 | + changePage(e) { | ||
70 | + this.pagination = { | ||
71 | + size: e.pageSize, | ||
72 | + page: e.current, | ||
73 | + }; | ||
74 | + this.fetchPageData(this.getPageParams); | ||
75 | + }, | ||
76 | + goDetail(row) { | ||
77 | + this.fetchDroneDetailData(row.droneId); | ||
78 | + this.fetchScheduleDetailData(row.scheduleId); | ||
79 | + this.$router.push({ | ||
80 | + path: `/database/schedule/${row.scheduleId}`, | ||
81 | + }); | ||
82 | + }, | ||
83 | + }, | ||
84 | +}; | ||
85 | +</script> | ||
86 | + | ||
87 | +<style scoped lang="scss"></style> |
1 | +<template> | ||
2 | + <a-page-header | ||
3 | + class="page-header" | ||
4 | + title="드론 정보" | ||
5 | + sub-title="Drone-Schedule-Log" | ||
6 | + @back="$router.go(-1)" | ||
7 | + > | ||
8 | + <div :style="{display: 'flex'}"> | ||
9 | + <div :style="{width: '30%'}"> | ||
10 | + <img :src="droneInfo.picture" :style="{width: '90%'}" /> | ||
11 | + </div> | ||
12 | + | ||
13 | + <div :style="{width: '70%', marginLeft: '20px'}"> | ||
14 | + <div class="label-modelName">{{ droneInfo.modelName }}</div> | ||
15 | + <div :style="{display: 'flex', marginBottom: '15px'}"> | ||
16 | + <div class="label-info">info</div> | ||
17 | + | ||
18 | + <a-descriptions | ||
19 | + :column="{xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1}" | ||
20 | + class="description-box" | ||
21 | + > | ||
22 | + <a-descriptions-item label="모델명"> | ||
23 | + {{ droneInfo.modelName }} | ||
24 | + </a-descriptions-item> | ||
25 | + <a-descriptions-item label="제조사"> | ||
26 | + {{ droneInfo.maker == null ? 'None' : droneInfo.maker }} | ||
27 | + </a-descriptions-item> | ||
28 | + <a-descriptions-item label="종류"> | ||
29 | + {{ droneInfo.usage == null ? 'None' : droneInfo.usageName }} | ||
30 | + </a-descriptions-item> | ||
31 | + <a-descriptions-item label="제원"> | ||
32 | + {{ | ||
33 | + droneInfo.specification == null | ||
34 | + ? 'None' | ||
35 | + : droneInfo.specification | ||
36 | + }} | ||
37 | + </a-descriptions-item> | ||
38 | + <a-descriptions-item label="무게"> | ||
39 | + {{ droneInfo.weight == null ? '?' : droneInfo.weight }} kg | ||
40 | + </a-descriptions-item> | ||
41 | + <a-descriptions-item label="No"> | ||
42 | + {{ droneInfo.id }} | ||
43 | + </a-descriptions-item> | ||
44 | + </a-descriptions> | ||
45 | + </div> | ||
46 | + <div :style="{display: 'flex'}"> | ||
47 | + <div class="label-info">Schedule</div> | ||
48 | + | ||
49 | + <a-descriptions | ||
50 | + :column="{xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1}" | ||
51 | + class="description-box" | ||
52 | + > | ||
53 | + <a-descriptions-item label="시작 시간"> | ||
54 | + {{ mc_dateTime(ScheduleInfo.startTime) }} | ||
55 | + </a-descriptions-item> | ||
56 | + <a-descriptions-item label="시작 위도"> | ||
57 | + {{ ScheduleInfo.startLatitude }} | ||
58 | + </a-descriptions-item> | ||
59 | + <a-descriptions-item label="시작 경도"> | ||
60 | + {{ ScheduleInfo.startLongitude }} | ||
61 | + </a-descriptions-item> | ||
62 | + <a-descriptions-item label="종료 시간"> | ||
63 | + {{ mc_dateTime(ScheduleInfo.terminateTime) }} | ||
64 | + </a-descriptions-item> | ||
65 | + <a-descriptions-item label="종료 위도"> | ||
66 | + {{ ScheduleInfo.terminateLatitude }} | ||
67 | + </a-descriptions-item> | ||
68 | + <a-descriptions-item label="종료 경도"> | ||
69 | + {{ ScheduleInfo.terminateLongitude }} | ||
70 | + </a-descriptions-item> | ||
71 | + </a-descriptions> | ||
72 | + </div> | ||
73 | + </div> | ||
74 | + </div> | ||
75 | + <a-divider /> | ||
76 | + </a-page-header> | ||
77 | +</template> | ||
78 | + | ||
79 | +<script> | ||
80 | +import {mapGetters} from 'vuex'; | ||
81 | + | ||
82 | +export default { | ||
83 | + name: 'DatabaseDetailHeader', | ||
84 | + components: {}, | ||
85 | + data() { | ||
86 | + return { | ||
87 | + droneInfo: {}, | ||
88 | + ScheduleInfo: {}, | ||
89 | + }; | ||
90 | + }, | ||
91 | + created() { | ||
92 | + this.droneInfo = this.getDetailData; | ||
93 | + this.ScheduleInfo = this.getScheduleDetailData; | ||
94 | + }, | ||
95 | + computed: { | ||
96 | + ...mapGetters('Drone/detail', { | ||
97 | + getDetailData: 'getDetailData', | ||
98 | + }), | ||
99 | + ...mapGetters('Schedule/detail', { | ||
100 | + getScheduleDetailData: 'getDetailData', | ||
101 | + }), | ||
102 | + ...mapGetters('Code', { | ||
103 | + getCodes: 'getCodes', | ||
104 | + }), | ||
105 | + droneCategory() { | ||
106 | + return (data) => { | ||
107 | + switch (parseInt(data, 10)) { | ||
108 | + case 1: | ||
109 | + return '촬영용'; | ||
110 | + case 2: | ||
111 | + return '레이싱용'; | ||
112 | + case 3: | ||
113 | + return '완구용'; | ||
114 | + default: | ||
115 | + return null; | ||
116 | + } | ||
117 | + }; | ||
118 | + }, | ||
119 | + }, | ||
120 | +}; | ||
121 | +</script> | ||
122 | + | ||
123 | +<style scoped lang="scss"> | ||
124 | +@import '@/assets/styles/mixins.scss'; | ||
125 | + | ||
126 | +.label-modelName { | ||
127 | + font-size: 30px; | ||
128 | + color: $antblue; | ||
129 | +} | ||
130 | +.label-info { | ||
131 | + padding: 0 10px; | ||
132 | + border-right: 1px solid #777777; | ||
133 | + min-width: 20%; | ||
134 | +} | ||
135 | +.description-box { | ||
136 | + padding-left: 10px; | ||
137 | +} | ||
138 | +</style> |
1 | +<template> | ||
2 | + <a-page-header | ||
3 | + class="page-header" | ||
4 | + title="Database" | ||
5 | + sub-title="Table - DroneSchedule" | ||
6 | + > | ||
7 | + <template slot="extra"> | ||
8 | + <a-button key="1" type="primary" form="form" html-type="submit"> | ||
9 | + 검색 | ||
10 | + </a-button> | ||
11 | + </template> | ||
12 | + | ||
13 | + <a-form id="form" @submit.prevent="searchData" class="form-wrapper"> | ||
14 | + <a-form-item label="검색 범위" class="form-item"> | ||
15 | + <div> | ||
16 | + <a-date-picker | ||
17 | + v-model="startValue" | ||
18 | + :disabled-date="disabledStartDate" | ||
19 | + show-time | ||
20 | + format="YYYY-MM-DD HH:mm:ss" | ||
21 | + placeholder="Start Date" | ||
22 | + @openChange="handleStartOpenChange" | ||
23 | + /> | ||
24 | + <span :style="{padding: '0 10px'}">-</span> | ||
25 | + <a-date-picker | ||
26 | + v-model="endValue" | ||
27 | + :disabled-date="disabledEndDate" | ||
28 | + show-time | ||
29 | + format="YYYY-MM-DD HH:mm:ss" | ||
30 | + placeholder="End Date" | ||
31 | + :open="endOpen" | ||
32 | + @openChange="handleEndOpenChange" | ||
33 | + /> | ||
34 | + </div> | ||
35 | + </a-form-item> | ||
36 | + </a-form> | ||
37 | + </a-page-header> | ||
38 | +</template> | ||
39 | + | ||
40 | +<script> | ||
41 | +import {mapActions, mapGetters} from 'vuex'; | ||
42 | + | ||
43 | +export default { | ||
44 | + name: 'DatabaseSearchFilter', | ||
45 | + components: {}, | ||
46 | + data() { | ||
47 | + return { | ||
48 | + searchParams: {}, | ||
49 | + startValue: null, | ||
50 | + endValue: null, | ||
51 | + endOpen: false, | ||
52 | + }; | ||
53 | + }, | ||
54 | + watch: { | ||
55 | + startValue(val) { | ||
56 | + if (val != null) { | ||
57 | + this.searchParams.startTime = val.format('YYYY-MM-DD HH:mm:ss'); | ||
58 | + } else { | ||
59 | + this.searchParams.startTime = null; | ||
60 | + } | ||
61 | + }, | ||
62 | + endValue(val) { | ||
63 | + if (val !== null) { | ||
64 | + this.searchParams.terminateTime = val.format('YYYY-MM-DD HH:mm:ss'); | ||
65 | + } else { | ||
66 | + this.searchParams.terminateTime = null; | ||
67 | + } | ||
68 | + }, | ||
69 | + }, | ||
70 | + computed: { | ||
71 | + ...mapGetters('Schedule/page', { | ||
72 | + getPageParams: 'getPageParams', | ||
73 | + }), | ||
74 | + }, | ||
75 | + created() { | ||
76 | + this.searchParams = JSON.parse(JSON.stringify(this.getPageParams)); | ||
77 | + }, | ||
78 | + methods: { | ||
79 | + ...mapActions('Schedule/page', { | ||
80 | + setPageParams: 'setPageParams', | ||
81 | + }), | ||
82 | + searchData() { | ||
83 | + console.log(this.searchParams); | ||
84 | + this.setPageParams(this.searchParams); | ||
85 | + this.$emit('loadData'); | ||
86 | + }, | ||
87 | + disabledStartDate(startValue) { | ||
88 | + const endValue = this.endValue; | ||
89 | + if (!startValue || !endValue) { | ||
90 | + return false; | ||
91 | + } | ||
92 | + return startValue.valueOf() > endValue.valueOf(); | ||
93 | + }, | ||
94 | + disabledEndDate(endValue) { | ||
95 | + const startValue = this.startValue; | ||
96 | + if (!endValue || !startValue) { | ||
97 | + return false; | ||
98 | + } | ||
99 | + return startValue.valueOf() >= endValue.valueOf(); | ||
100 | + }, | ||
101 | + handleStartOpenChange(open) { | ||
102 | + if (!open) { | ||
103 | + this.endOpen = true; | ||
104 | + } | ||
105 | + }, | ||
106 | + handleEndOpenChange(open) { | ||
107 | + this.endOpen = open; | ||
108 | + }, | ||
109 | + }, | ||
110 | +}; | ||
111 | +</script> | ||
112 | + | ||
113 | +<style scoped lang="scss"> | ||
114 | +@import '@/assets/styles/mixins.scss'; | ||
115 | +.form-wrapper { | ||
116 | + height: 40px; | ||
117 | +} | ||
118 | +.form-wrapper, | ||
119 | +.form-item { | ||
120 | + display: flex; | ||
121 | + margin-left: 10px; | ||
122 | + margin-bottom: 0; | ||
123 | +} | ||
124 | +.slider-box { | ||
125 | + background: white; | ||
126 | + padding: 10px 15px; | ||
127 | + border-radius: 20px; | ||
128 | + border: solid 1px $antblue; | ||
129 | + position: fixed; | ||
130 | + z-index: 9; | ||
131 | +} | ||
132 | +</style> |
1 | +<template> | ||
2 | + <a-table | ||
3 | + rowKey="id" | ||
4 | + bordered | ||
5 | + :loading="childLoading" | ||
6 | + :columns="columns" | ||
7 | + :data-source="getPageData" | ||
8 | + :scroll="{x: 1000}" | ||
9 | + :pagination="pagination" | ||
10 | + @change="changePage" | ||
11 | + > | ||
12 | + <a slot="modelName" slot-scope="data, row" @click="goDetail(row)"> | ||
13 | + {{ data }} | ||
14 | + </a> | ||
15 | + <div slot="startTime" slot-scope="data"> | ||
16 | + {{ mc_dateTime(data) }} | ||
17 | + </div> | ||
18 | + <div slot="terminateTime" slot-scope="data"> | ||
19 | + {{ mc_dateTime(data) }} | ||
20 | + </div> | ||
21 | + </a-table> | ||
22 | +</template> | ||
23 | + | ||
24 | +<script> | ||
25 | +import databaseScheduleColumn from '@/utils/ColumnData/databaseScheduleColumn'; | ||
26 | +import {mapActions, mapGetters} from 'vuex'; | ||
27 | + | ||
28 | +export default { | ||
29 | + name: 'DatabaseTable', | ||
30 | + props: { | ||
31 | + childLoading: { | ||
32 | + type: Boolean, | ||
33 | + default: false, | ||
34 | + }, | ||
35 | + }, | ||
36 | + data() { | ||
37 | + return { | ||
38 | + columns: databaseScheduleColumn, | ||
39 | + }; | ||
40 | + }, | ||
41 | + computed: { | ||
42 | + ...mapGetters('Schedule/page', { | ||
43 | + getPageData: 'getPageData', | ||
44 | + getPagination: 'getPagination', | ||
45 | + getPageParams: 'getPageParams', | ||
46 | + }), | ||
47 | + pagination: { | ||
48 | + get() { | ||
49 | + return this.getPagination; | ||
50 | + }, | ||
51 | + set(e) { | ||
52 | + this.setPagination({ | ||
53 | + size: e.size, | ||
54 | + page: e.page, | ||
55 | + }); | ||
56 | + }, | ||
57 | + }, | ||
58 | + }, | ||
59 | + methods: { | ||
60 | + ...mapActions('Schedule/page', { | ||
61 | + setPagination: 'setPagination', | ||
62 | + fetchPageData: 'fetchPageData', | ||
63 | + }), | ||
64 | + ...mapActions('Drone/detail', { | ||
65 | + fetchDetailData: 'fetchDetailData', | ||
66 | + }), | ||
67 | + changePage(e) { | ||
68 | + this.pagination = { | ||
69 | + size: e.pageSize, | ||
70 | + page: e.current, | ||
71 | + }; | ||
72 | + this.fetchPageData(this.getPageParams); | ||
73 | + }, | ||
74 | + goDetail(row) { | ||
75 | + this.fetchDetailData(row.droneId); | ||
76 | + this.$router.push({ | ||
77 | + path: `/database/schedule/${row.id}`, | ||
78 | + }); | ||
79 | + }, | ||
80 | + }, | ||
81 | +}; | ||
82 | +</script> | ||
83 | + | ||
84 | +<style scoped lang="scss"></style> |
Frontend/src/components/Layout/footer.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <h4 class="footer-font"> | ||
4 | + Drone Simulation Map | ||
5 | + </h4> | ||
6 | + <h5 class="footer-font"> | ||
7 | + @2021 TwentyOz & KHU | ||
8 | + </h5> | ||
9 | + </div> | ||
10 | +</template> | ||
11 | + | ||
12 | +<script> | ||
13 | +export default { | ||
14 | + name: 'LayoutFooter', | ||
15 | + data() { | ||
16 | + return { | ||
17 | + | ||
18 | + }; | ||
19 | + }, | ||
20 | + computed: { | ||
21 | + | ||
22 | + }, | ||
23 | + watch: { | ||
24 | + | ||
25 | + }, | ||
26 | + created() { | ||
27 | + | ||
28 | + }, | ||
29 | + method: { | ||
30 | + | ||
31 | + }, | ||
32 | +}; | ||
33 | +</script> | ||
34 | + | ||
35 | +<style scoped lang="scss"> | ||
36 | +@import '@/assets/styles/mixins.scss'; | ||
37 | + | ||
38 | +.footer-font { | ||
39 | + color: $gray-3 | ||
40 | +} | ||
41 | +</style> |
1 | +<template> | ||
2 | + <div> | ||
3 | + <div class="bottom-tool-box"> | ||
4 | + <div class="boxBtn" @click="clickWeatherBtn"> | ||
5 | + <a-icon type="cloud"/> | ||
6 | + </div> | ||
7 | + <FilterBtnBox | ||
8 | + class="filterBox" | ||
9 | + @clickClose="clickFilterBoxClose" | ||
10 | + @changeFilterMode="e => this.$emit('changeFilterMode', e)" | ||
11 | + @changeSearchMode="e => this.$emit('changeSearchMode', e)" | ||
12 | + /> | ||
13 | + <div class="boxBtn" @click="clickBookMarkBtn"> | ||
14 | + <a-icon type="star"/> | ||
15 | + </div> | ||
16 | + </div> | ||
17 | + </div> | ||
18 | +</template> | ||
19 | +<script> | ||
20 | +import FilterBtnBox from '../FilterBox/filterBtnBox'; | ||
21 | + | ||
22 | +export default { | ||
23 | + head() { | ||
24 | + return { | ||
25 | + title: 'Drone', | ||
26 | + meta: [ | ||
27 | + { | ||
28 | + hid: 'database', | ||
29 | + name: 'Descriptions', | ||
30 | + content: 'DroneWeb-Content', | ||
31 | + }, | ||
32 | + ], | ||
33 | + }; | ||
34 | + }, | ||
35 | + components: { | ||
36 | + FilterBtnBox | ||
37 | + }, | ||
38 | + props: { | ||
39 | + }, | ||
40 | + data() { | ||
41 | + return { | ||
42 | + searchMode: false, | ||
43 | + filterMode: false, | ||
44 | + }; | ||
45 | + }, | ||
46 | + computed: { | ||
47 | + }, | ||
48 | + created() { | ||
49 | + }, | ||
50 | + methods: { | ||
51 | + clickWeatherBtn() { | ||
52 | + console.log('click') | ||
53 | + this.$notification['warning']({ | ||
54 | + message: '날씨 기능은 추후 추가될 예정입니다.', | ||
55 | + duration: 3, | ||
56 | + }) | ||
57 | + }, | ||
58 | + clickBookMarkBtn() { | ||
59 | + this.$notification['warning']({ | ||
60 | + message: '즐겨찾기 기능은 추후 추가될 예정입니다.', | ||
61 | + duration: 3, | ||
62 | + }) | ||
63 | + }, | ||
64 | + toggleFilterMode() { | ||
65 | + this.$emit('toggleFilterMode') | ||
66 | + }, | ||
67 | + toggleSearchMode() { | ||
68 | + this.$emit('toggleSearchMode') | ||
69 | + }, | ||
70 | + clickSearchBoxClose() { | ||
71 | + this.searchMode = false; | ||
72 | + }, | ||
73 | + clickFilterBoxClose() { | ||
74 | + this.filterMode = false; | ||
75 | + }, | ||
76 | + clickSearchBtn() { | ||
77 | + this.filterMode = false; | ||
78 | + this.searchMode = true; | ||
79 | + }, | ||
80 | + clickFilterBtn() { | ||
81 | + this.searchMode = false; | ||
82 | + this.filterMode = true; | ||
83 | + }, | ||
84 | + | ||
85 | + }, | ||
86 | +}; | ||
87 | +</script> | ||
88 | +<style lang="scss"> | ||
89 | +</style> |
1 | +<template> | ||
2 | + <div> | ||
3 | + <a-descriptions layout="vertical" bordered> | ||
4 | + <a-descriptions-item :span="4"> | ||
5 | + <template v-slot:label> | ||
6 | + <div>{{ foundDrone.modelName }}</div> | ||
7 | + </template> | ||
8 | + <img | ||
9 | + :src="foundDrone.picture || require('@/assets/images/drone-image.jpg')" | ||
10 | + :style="{width: '300px'}" | ||
11 | + /> | ||
12 | + </a-descriptions-item> | ||
13 | + <a-descriptions-item label="실시간 정보" :span="4"> | ||
14 | + <div class="des-sub-title">현재 위치</div> | ||
15 | + <div class="des-sub-cont-grid-4" style="margin-bottom: 10px;"> | ||
16 | + <div> | ||
17 | + <div>경도</div> | ||
18 | + <div>{{ selectedLastLog.longitude ? selectedLastLog.longitude.toFixed(3) : '?' }}</div> | ||
19 | + </div> | ||
20 | + <div> | ||
21 | + <div>위도</div> | ||
22 | + <div>{{ selectedLastLog.latitude ? selectedLastLog.latitude.toFixed(3) : '?' }}</div> | ||
23 | + </div> | ||
24 | + <div> | ||
25 | + <div>이동거리</div> | ||
26 | + <div>{{ Math.floor(getDetailData.distance + selectedLastLog.distance) }}m</div> | ||
27 | + </div> | ||
28 | + <div> | ||
29 | + <div>운용시간</div> | ||
30 | + <div>{{ getTimeDiff(foundSchedule.startTime) }}</div> | ||
31 | + </div> | ||
32 | + </div> | ||
33 | + <div class="des-sub-cont-grid-2" style="margin-bottom: 10px;"> | ||
34 | + <div> | ||
35 | + <div class="des-sub-title">현재 속도</div> | ||
36 | + <div class="des-sub-cont-grid-2"> | ||
37 | + <div> | ||
38 | + <div>수평 속도</div> | ||
39 | + <div>{{ selectedLastLog.horizontalSpeed }}km/h</div> | ||
40 | + </div> | ||
41 | + <div> | ||
42 | + <div>수직 속도</div> | ||
43 | + <div>{{ selectedLastLog.verticalSpeed }}km/h</div> | ||
44 | + </div> | ||
45 | + </div> | ||
46 | + </div> | ||
47 | + <div> | ||
48 | + <div class="des-sub-title">현재 고도</div> | ||
49 | + <div class="des-sub-cont-grid-2"> | ||
50 | + <div> | ||
51 | + <div>지면고도</div> | ||
52 | + <div>{{ selectedLastLog.aboveGroundLevel }}m</div> | ||
53 | + </div> | ||
54 | + <div> | ||
55 | + <div>해발고도</div> | ||
56 | + <div>{{ selectedLastLog.aboveSeaLevel }}m</div> | ||
57 | + </div> | ||
58 | + </div> | ||
59 | + </div> | ||
60 | + </div> | ||
61 | + </a-descriptions-item> | ||
62 | + <a-descriptions-item label="스케쥴" :span="4" :style="{padding: '0px'}"> | ||
63 | + <div class="des-sub-cont-grid-2" style="margin-bottom: 10px;"> | ||
64 | + <div> | ||
65 | + <div class="des-sub-title">시작 스케쥴</div> | ||
66 | + <div> | ||
67 | + <div>날짜/시간</div> | ||
68 | + <div style="margin-bottom: 5px;">{{ mc_dateTime(foundSchedule.startTime) || '?' }}</div> | ||
69 | + <div>경도</div> | ||
70 | + <div style="margin-bottom: 5px;">{{ foundSchedule.startLongitude || '?' }}</div> | ||
71 | + <div>위도</div> | ||
72 | + <div style="margin-bottom: 5px;">{{ foundSchedule.startLatitude || '?' }}</div> | ||
73 | + </div> | ||
74 | + </div> | ||
75 | + <div> | ||
76 | + <div class="des-sub-title">실제 시작</div> | ||
77 | + <div> | ||
78 | + <div>날짜/시간</div> | ||
79 | + <div style="margin-bottom: 5px;">{{ droneLogs.length !== 0 ? mc_dateTime(droneLogs[0].createdAt) : "?" }}</div> | ||
80 | + <div>경도</div> | ||
81 | + <div style="margin-bottom: 5px;">{{ droneLogs.length !== 0 ? droneLogs[0].longitude : "?" }}</div> | ||
82 | + <div>위도</div> | ||
83 | + <div style="margin-bottom: 5px;">{{ droneLogs.length !== 0 ? droneLogs[0].latitude : "?" }}</div> | ||
84 | + </div> | ||
85 | + </div> | ||
86 | + </div> | ||
87 | + <div class="des-sub-title">종료 스케쥴</div> | ||
88 | + <div style="display: grid; grid-template-columns: 2fr 1fr 1fr"> | ||
89 | + <div> | ||
90 | + <div>날짜/시간</div> | ||
91 | + <div>{{ mc_dateTime(foundSchedule.terminateTime) || '?' }}</div> | ||
92 | + </div> | ||
93 | + <div> | ||
94 | + <div>경도</div> | ||
95 | + <div>{{ foundSchedule.terminateLongitude || '?' }}</div> | ||
96 | + </div> | ||
97 | + <div> | ||
98 | + <div>위도</div> | ||
99 | + <div>{{ foundSchedule.terminateLatitude || '?' }}</div> | ||
100 | + </div> | ||
101 | + </div> | ||
102 | + </a-descriptions-item> | ||
103 | + <a-descriptions-item label="드론 정보" :span="6"> | ||
104 | + <div class="des-sub-cont-grid-4"> | ||
105 | + <div> | ||
106 | + <div class="des-sub-title">제조사</div> | ||
107 | + <div>{{ foundDrone.maker || '?' }}</div> | ||
108 | + </div> | ||
109 | + <div> | ||
110 | + <div class="des-sub-title">종류</div> | ||
111 | + <div>{{ foundDrone.usageName || '?' }}</div> | ||
112 | + </div> | ||
113 | + <div> | ||
114 | + <div class="des-sub-title">제원</div> | ||
115 | + <div>{{ foundDrone.specification || '?' }}</div> | ||
116 | + </div> | ||
117 | + <div> | ||
118 | + <div class="des-sub-title">무게</div> | ||
119 | + <div>{{ foundDrone.weight }}g</div> | ||
120 | + </div> | ||
121 | + </div> | ||
122 | + </a-descriptions-item> | ||
123 | + </a-descriptions> | ||
124 | + </div> | ||
125 | +</template> | ||
126 | + | ||
127 | +<script> | ||
128 | +import { mapActions, mapGetters } from 'vuex'; | ||
129 | + | ||
130 | +export default { | ||
131 | + head() { | ||
132 | + return { | ||
133 | + title: 'Drone', | ||
134 | + meta: [ | ||
135 | + { | ||
136 | + hid: 'database', | ||
137 | + name: 'Descriptions', | ||
138 | + content: 'DroneWeb-Content', | ||
139 | + }, | ||
140 | + ], | ||
141 | + }; | ||
142 | + }, | ||
143 | + watch: { | ||
144 | + getSelectedLastLog: { | ||
145 | + deep: true, | ||
146 | + handler(newVal) { | ||
147 | + this.selectedLastLog = newVal; | ||
148 | + }, | ||
149 | + }, | ||
150 | + }, | ||
151 | + data() { | ||
152 | + return { | ||
153 | + selectedLastLog: {}, | ||
154 | + }; | ||
155 | + }, | ||
156 | + computed: { | ||
157 | + ...mapGetters('Drone/drone', { | ||
158 | + getDetailData: 'getDetailData', | ||
159 | + getSelectedLastLog: 'getSelectedLastLog', | ||
160 | + }), | ||
161 | + foundSchedule() { | ||
162 | + return this.getDetailData.foundSchedule || {}; | ||
163 | + }, | ||
164 | + foundDrone() { | ||
165 | + return this.getDetailData.foundDrone || {}; | ||
166 | + }, | ||
167 | + droneLogs() { | ||
168 | + return this.getDetailData.droneLogs || []; | ||
169 | + }, | ||
170 | + selectedLastLog() { | ||
171 | + return this.getSelectedLastLog || {}; | ||
172 | + }, | ||
173 | + }, | ||
174 | + methods: { | ||
175 | + getTimeDiff(startTime) { | ||
176 | + const totalSeconds = this.$dayjs().diff(this.$dayjs(startTime), 's'); | ||
177 | + const seconds = totalSeconds % 60; | ||
178 | + const minutes = Math.floor(totalSeconds / 60) % 60; | ||
179 | + const hours = Math.floor(totalSeconds / 3600); | ||
180 | + | ||
181 | + return `${hours}:${minutes}:${seconds}`; | ||
182 | + }, | ||
183 | + }, | ||
184 | +}; | ||
185 | +</script> | ||
186 | +<style lang="scss"> | ||
187 | +.des-sub-title { | ||
188 | + font-size: 16px; | ||
189 | + font-weight: 900; | ||
190 | +} | ||
191 | + | ||
192 | +.des-sub-cont-grid-2 { | ||
193 | + display: grid; | ||
194 | + grid-template-columns: 1fr 1fr; | ||
195 | +} | ||
196 | + | ||
197 | +.des-sub-cont-grid-4 { | ||
198 | + display: grid; | ||
199 | + grid-template-columns: 1fr 1fr 1fr 1fr; | ||
200 | +} | ||
201 | + | ||
202 | +.des-sub-cont { | ||
203 | + display: flex; | ||
204 | + gap: 20px; | ||
205 | + margin-bottom: 20px; | ||
206 | +} | ||
207 | + | ||
208 | +</style> |
1 | +<template> | ||
2 | + <div> | ||
3 | + <img | ||
4 | + @click="clickFilterBtn" | ||
5 | + class="filterBtn" | ||
6 | + :src="require('@/static/img/filter.png')"/> | ||
7 | + </div> | ||
8 | +</template> | ||
9 | +<script> | ||
10 | + | ||
11 | +import CloseBox from '@/components/_Common/CloseBox/closeBox'; | ||
12 | +import { mapActions, mapGetters } from 'vuex'; | ||
13 | + | ||
14 | +export default { | ||
15 | + head() { | ||
16 | + return { | ||
17 | + title: 'Drone', | ||
18 | + meta: [ | ||
19 | + { | ||
20 | + hid: 'database', | ||
21 | + name: 'Descriptions', | ||
22 | + content: 'DroneWeb-Content', | ||
23 | + }, | ||
24 | + ], | ||
25 | + }; | ||
26 | + }, | ||
27 | + components: { | ||
28 | + CloseBox, | ||
29 | + }, | ||
30 | + props: {}, | ||
31 | + data() { | ||
32 | + return { | ||
33 | + filterMode: false, | ||
34 | + filteredDroneList: [], | ||
35 | + manufacturerValue: [], | ||
36 | + weightValue: [0, 50], | ||
37 | + weightMarks: { | ||
38 | + 0: '0kg', | ||
39 | + 50: '50kg+', | ||
40 | + }, | ||
41 | + altitudeValue: [0, 200], | ||
42 | + altitudeMarks: { | ||
43 | + 0: '0m', | ||
44 | + 200: '200m+', | ||
45 | + }, | ||
46 | + speedValue: [0, 100], | ||
47 | + speedMarks: { | ||
48 | + 0: '0km/h', | ||
49 | + 100: '100km/h+', | ||
50 | + }, | ||
51 | + filmingOptions: ['True', 'False'], | ||
52 | + filmingValue: 'True', | ||
53 | + }; | ||
54 | + }, | ||
55 | + computed: { | ||
56 | + ...mapGetters('Etc', { | ||
57 | + getMakers: 'getMakers', | ||
58 | + }), | ||
59 | + ...mapGetters('Drone', { | ||
60 | + getLogFilter: 'drone/getLogFilter', | ||
61 | + getFixedDroneList: 'list/getFixedDroneList', | ||
62 | + }), | ||
63 | + }, | ||
64 | + created() { | ||
65 | + }, | ||
66 | + methods: { | ||
67 | + ...mapActions('Drone/drone', { | ||
68 | + setLogFilter: 'setLogFilter', | ||
69 | + clearLogFilter: 'clearLogFilter', | ||
70 | + }), | ||
71 | + clickFilterBtn() { | ||
72 | + this.$emit('changeSearchMode', false) | ||
73 | + this.$emit('changeFilterMode', true) | ||
74 | + }, | ||
75 | + clickClose() { | ||
76 | + this.$emit('clickClose'); | ||
77 | + }, | ||
78 | + changeManufacturer(value) { | ||
79 | + this.manufacturerValue = value; | ||
80 | + this.filteredDroneList = this.getFixedDroneList.filter((v) => !!this.manufacturerValue.find((e) => v.maker === e)); | ||
81 | + }, | ||
82 | + changeWeight(weight) { | ||
83 | + this.weightValue = weight; | ||
84 | + }, | ||
85 | + changeAltitude(altitude) { | ||
86 | + this.altitudeValue = altitude; | ||
87 | + }, | ||
88 | + changeSpeed(speed) { | ||
89 | + this.speedValue = speed; | ||
90 | + }, | ||
91 | + applyFilter() { | ||
92 | + this.setLogFilter({ | ||
93 | + checkFilter: true, | ||
94 | + maker: this.manufacturerValue, | ||
95 | + filteredDroneList: this.filteredDroneList, | ||
96 | + weight: this.weightValue, | ||
97 | + altitude: this.altitudeValue, | ||
98 | + speed: this.speedValue, | ||
99 | + filming: this.filmingValue, | ||
100 | + }); | ||
101 | + }, | ||
102 | + clickReset() { | ||
103 | + this.clearLogFilter(); | ||
104 | + this.filteredDroneList = []; | ||
105 | + this.manufacturerValue = []; | ||
106 | + this.weightValue = [0, 50]; | ||
107 | + this.altitudeValue = [0, 200]; | ||
108 | + this.speedValue = [0, 100]; | ||
109 | + this.filmingValue = []; | ||
110 | + }, | ||
111 | + }, | ||
112 | +}; | ||
113 | +</script> | ||
114 | +<style lang="scss"> | ||
115 | +</style> |
1 | +<template> | ||
2 | + <div> | ||
3 | + <CloseBox | ||
4 | + @clickClose="clickClose" | ||
5 | + > | ||
6 | + <template v-slot:header> | ||
7 | + 드론 필터 | ||
8 | + </template> | ||
9 | + <template v-slot:body> | ||
10 | + <a-alert | ||
11 | + v-if="getLogFilter.checkFilter" | ||
12 | + message="필터가 적용된 상태입니다." | ||
13 | + type="info" show-icon | ||
14 | + class="filter-alert" | ||
15 | + /> | ||
16 | + <div :style="{padding: '0 10px 0 5px', marginTop: getLogFilter.checkFilter ? '-5px' : '-20px'}"> | ||
17 | + <div class="label"> | ||
18 | + <span>제조사</span> | ||
19 | + </div> | ||
20 | + <a-select | ||
21 | + :value="manufacturerValue" | ||
22 | + mode="tags" | ||
23 | + style="width: 100%;" | ||
24 | + placeholder="제조사" | ||
25 | + @change="changeManufacturer"> | ||
26 | + | ||
27 | + <a-select-option v-for="(maker,index) in getMakers" | ||
28 | + :key="index" | ||
29 | + :value="maker" | ||
30 | + > | ||
31 | + {{ maker }} | ||
32 | + </a-select-option> | ||
33 | + </a-select> | ||
34 | + <div class="label"><span>무게</span></div> | ||
35 | + <a-slider | ||
36 | + :value="weightValue" | ||
37 | + range | ||
38 | + :marks="weightMarks" | ||
39 | + :max="50" | ||
40 | + :min="0" | ||
41 | + @change="changeWeight" | ||
42 | + /> | ||
43 | + <div class="label"><span>고도</span></div> | ||
44 | + <a-slider | ||
45 | + :value="altitudeValue" | ||
46 | + range | ||
47 | + :marks="altitudeMarks" | ||
48 | + :max="200" | ||
49 | + :min="0" | ||
50 | + @change="changeAltitude"/> | ||
51 | + <div class="label"><span>속력</span></div> | ||
52 | + <a-slider | ||
53 | + :value="speedValue" | ||
54 | + range | ||
55 | + :marks="speedMarks" | ||
56 | + :max="100" | ||
57 | + :min="0" | ||
58 | + @change="changeSpeed"/> | ||
59 | + | ||
60 | + <div style="display: flex; justify-content: space-between; text-align: right; margin-top: 40px"> | ||
61 | + <a-button @click="applyFilter"> | ||
62 | + <a-icon type="check"/> | ||
63 | + 필터 적용 | ||
64 | + </a-button> | ||
65 | + <a-button @click="clickReset"> | ||
66 | + <a-icon type="reload"/> | ||
67 | + 필터 리셋 | ||
68 | + </a-button> | ||
69 | + </div> | ||
70 | + </div> | ||
71 | + </template> | ||
72 | + </CloseBox> | ||
73 | + </div> | ||
74 | +</template> | ||
75 | +<script> | ||
76 | + | ||
77 | +import CloseBox from '@/components/_Common/CloseBox/closeBox'; | ||
78 | +import { mapActions, mapGetters } from 'vuex'; | ||
79 | + | ||
80 | +export default { | ||
81 | + head() { | ||
82 | + return { | ||
83 | + title: 'Drone', | ||
84 | + meta: [ | ||
85 | + { | ||
86 | + hid: 'database', | ||
87 | + name: 'Descriptions', | ||
88 | + content: 'DroneWeb-Content', | ||
89 | + }, | ||
90 | + ], | ||
91 | + }; | ||
92 | + }, | ||
93 | + components: { | ||
94 | + CloseBox, | ||
95 | + }, | ||
96 | + props: {}, | ||
97 | + data() { | ||
98 | + return { | ||
99 | + filteredDroneList: [], | ||
100 | + manufacturerValue: [], | ||
101 | + weightValue: [0, 50], | ||
102 | + weightMarks: { | ||
103 | + 0: '0kg', | ||
104 | + 50: '50kg+', | ||
105 | + }, | ||
106 | + altitudeValue: [0, 200], | ||
107 | + altitudeMarks: { | ||
108 | + 0: '0m', | ||
109 | + 200: '200m+', | ||
110 | + }, | ||
111 | + speedValue: [0, 100], | ||
112 | + speedMarks: { | ||
113 | + 0: '0km/h', | ||
114 | + 100: '100km/h+', | ||
115 | + }, | ||
116 | + filmingOptions: ['True', 'False'], | ||
117 | + filmingValue: 'True', | ||
118 | + }; | ||
119 | + }, | ||
120 | + computed: { | ||
121 | + ...mapGetters('Etc', { | ||
122 | + getMakers: 'getMakers', | ||
123 | + }), | ||
124 | + ...mapGetters('Drone', { | ||
125 | + getLogFilter: 'drone/getLogFilter', | ||
126 | + getFixedDroneList: 'list/getFixedDroneList', | ||
127 | + }), | ||
128 | + }, | ||
129 | + created() { | ||
130 | + }, | ||
131 | + methods: { | ||
132 | + ...mapActions('Drone/drone', { | ||
133 | + setLogFilter: 'setLogFilter', | ||
134 | + clearLogFilter: 'clearLogFilter', | ||
135 | + }), | ||
136 | + clickClose() { | ||
137 | + this.$emit('clickClose'); | ||
138 | + }, | ||
139 | + changeManufacturer(value) { | ||
140 | + this.manufacturerValue = value; | ||
141 | + this.filteredDroneList = this.getFixedDroneList.filter((v) => !!this.manufacturerValue.find((e) => v.maker === e)); | ||
142 | + }, | ||
143 | + changeWeight(weight) { | ||
144 | + this.weightValue = weight; | ||
145 | + }, | ||
146 | + changeAltitude(altitude) { | ||
147 | + this.altitudeValue = altitude; | ||
148 | + }, | ||
149 | + changeSpeed(speed) { | ||
150 | + this.speedValue = speed; | ||
151 | + }, | ||
152 | + applyFilter() { | ||
153 | + this.setLogFilter({ | ||
154 | + checkFilter: true, | ||
155 | + maker: this.manufacturerValue, | ||
156 | + filteredDroneList: this.filteredDroneList, | ||
157 | + weight: this.weightValue, | ||
158 | + altitude: this.altitudeValue, | ||
159 | + speed: this.speedValue, | ||
160 | + filming: this.filmingValue, | ||
161 | + }); | ||
162 | + }, | ||
163 | + clickReset() { | ||
164 | + this.clearLogFilter(); | ||
165 | + this.filteredDroneList = []; | ||
166 | + this.manufacturerValue = []; | ||
167 | + this.weightValue = [0, 50]; | ||
168 | + this.altitudeValue = [0, 200]; | ||
169 | + this.speedValue = [0, 100]; | ||
170 | + this.filmingValue = []; | ||
171 | + }, | ||
172 | + }, | ||
173 | +}; | ||
174 | +</script> | ||
175 | +<style lang="scss"> | ||
176 | +</style> |
1 | +<template> | ||
2 | + <div> | ||
3 | + <img | ||
4 | + class="searchBtn" | ||
5 | + @click="clickSearchBtn" | ||
6 | + :src="require('@/static/img/search.png')"/> | ||
7 | + </div> | ||
8 | +</template> | ||
9 | +<script> | ||
10 | + | ||
11 | +import CloseBox from '@/components/_Common/CloseBox/closeBox'; | ||
12 | +import { mapGetters } from 'vuex'; | ||
13 | + | ||
14 | +export default { | ||
15 | + head() { | ||
16 | + return { | ||
17 | + title: 'Drone', | ||
18 | + meta: [ | ||
19 | + { | ||
20 | + hid: 'database', | ||
21 | + name: 'Descriptions', | ||
22 | + content: 'DroneWeb-Content', | ||
23 | + }, | ||
24 | + ], | ||
25 | + }; | ||
26 | + }, | ||
27 | + components: { | ||
28 | + CloseBox, | ||
29 | + }, | ||
30 | + props: {}, | ||
31 | + data() { | ||
32 | + return { | ||
33 | + searchValue: null, | ||
34 | + searchType: 'modelName', | ||
35 | + searchData: [], | ||
36 | + dataSource: [], | ||
37 | + searchMode: false, | ||
38 | + }; | ||
39 | + }, | ||
40 | + computed: { | ||
41 | + ...mapGetters('Etc', { | ||
42 | + getMakerDroneList: 'getMakerDroneList', | ||
43 | + }), | ||
44 | + ...mapGetters('Drone', { | ||
45 | + getLogFilter: 'drone/getLogFilter', | ||
46 | + getDroneLogs: 'drone/getDroneLogs', | ||
47 | + }), | ||
48 | + }, | ||
49 | + created() { | ||
50 | + }, | ||
51 | + methods: { | ||
52 | + async onSearch(value) { | ||
53 | + const params = {}; | ||
54 | + params[this.searchType] = value; | ||
55 | + const result = await this.$store.dispatch('Drone/list/fetchListContents', params); | ||
56 | + const objectDrone = {}; | ||
57 | + this.getDroneLogs.forEach((e) => { | ||
58 | + objectDrone[Number(e.droneId)] = true; | ||
59 | + }); | ||
60 | + this.searchData = [...result.drones.filter((v) => objectDrone[Number(v.id)])]; | ||
61 | + }, | ||
62 | + clickSearchBtn() { | ||
63 | + this.$emit('changeFilterMode', false) | ||
64 | + this.$emit('changeSearchMode', true) | ||
65 | + }, | ||
66 | + clickClose() { | ||
67 | + this.$emit('clickClose'); | ||
68 | + this.searchMode = false; | ||
69 | + }, | ||
70 | + clickListItem(item) { | ||
71 | + console.log('click', item); | ||
72 | + }, | ||
73 | + }, | ||
74 | +}; | ||
75 | +</script> | ||
76 | +<style lang="scss"> | ||
77 | +</style> |
1 | +<template> | ||
2 | + <div> | ||
3 | + <CloseBox | ||
4 | + @clickClose="clickClose" | ||
5 | + > | ||
6 | + <template v-slot:header> | ||
7 | + 드론 기체 검색 | ||
8 | + </template> | ||
9 | + <template v-slot:body> | ||
10 | + <a-alert | ||
11 | + v-if="getLogFilter.checkFilter" | ||
12 | + message="필터가 적용된 상태입니다." | ||
13 | + type="info" show-icon | ||
14 | + class="filter-alert" | ||
15 | + /> | ||
16 | + <div class="r-flex gap-default" | ||
17 | + :style="{marginTop: getLogFilter.checkFilter ? '10px' : '0px'}" | ||
18 | + > | ||
19 | + <a-select | ||
20 | + v-model="searchType" | ||
21 | + class="search-input" | ||
22 | + style="min-width: 80px" | ||
23 | + > | ||
24 | + <a-select-option value="modelName"> | ||
25 | + 모델명 | ||
26 | + </a-select-option> | ||
27 | + <a-select-option value="maker"> | ||
28 | + 제조사 | ||
29 | + </a-select-option> | ||
30 | + <a-select-option value="usageName"> | ||
31 | + 용도 | ||
32 | + </a-select-option> | ||
33 | + </a-select> | ||
34 | + <a-input-search | ||
35 | + placeholder="검색어" | ||
36 | + enter-button | ||
37 | + @search="onSearch" | ||
38 | + /> | ||
39 | + </div> | ||
40 | + | ||
41 | + <a-list | ||
42 | + v-if="searchData.length" | ||
43 | + bordered | ||
44 | + :data-source="searchData"> | ||
45 | + <a-list-item | ||
46 | + slot="renderItem" | ||
47 | + slot-scope="item" | ||
48 | + @click="() => clickListItem(item)" | ||
49 | + > | ||
50 | + {{ `${item.modelName} / ${item.maker} / ${item.usage || '*'}` }} | ||
51 | + </a-list-item> | ||
52 | + </a-list> | ||
53 | + </template> | ||
54 | + </CloseBox> | ||
55 | + </div> | ||
56 | +</template> | ||
57 | +<script> | ||
58 | + | ||
59 | +import CloseBox from '@/components/_Common/CloseBox/closeBox'; | ||
60 | +import { mapGetters } from 'vuex'; | ||
61 | + | ||
62 | +export default { | ||
63 | + head() { | ||
64 | + return { | ||
65 | + title: 'Drone', | ||
66 | + meta: [ | ||
67 | + { | ||
68 | + hid: 'database', | ||
69 | + name: 'Descriptions', | ||
70 | + content: 'DroneWeb-Content', | ||
71 | + }, | ||
72 | + ], | ||
73 | + }; | ||
74 | + }, | ||
75 | + components: { | ||
76 | + CloseBox, | ||
77 | + }, | ||
78 | + props: {}, | ||
79 | + data() { | ||
80 | + return { | ||
81 | + searchValue: null, | ||
82 | + searchType: 'modelName', | ||
83 | + searchData: [], | ||
84 | + dataSource: [], | ||
85 | + }; | ||
86 | + }, | ||
87 | + computed: { | ||
88 | + ...mapGetters('Etc', { | ||
89 | + getMakerDroneList: 'getMakerDroneList', | ||
90 | + }), | ||
91 | + ...mapGetters('Drone', { | ||
92 | + getLogFilter: 'drone/getLogFilter', | ||
93 | + getWholeDroneLog: 'drone/getWholeDroneLog', | ||
94 | + }), | ||
95 | + }, | ||
96 | + created() { | ||
97 | + | ||
98 | + }, | ||
99 | + methods: { | ||
100 | + onSearch(value) { | ||
101 | + const params = {}; | ||
102 | + params[this.searchType] = value; | ||
103 | + this.$store.dispatch('Drone/list/fetchListContents', params).then((r) => { | ||
104 | + this.searchData = r.drones; | ||
105 | + }); | ||
106 | + }, | ||
107 | + handleSearch(value) { | ||
108 | + | ||
109 | + }, | ||
110 | + clickClose() { | ||
111 | + this.$emit('clickClose'); | ||
112 | + }, | ||
113 | + clickListItem(item) { | ||
114 | + let checkValid = false; | ||
115 | + this.getWholeDroneLog.forEach((log) => { | ||
116 | + if (Number(log.droneId) === Number(item.id)) { | ||
117 | + this.$emit('focusChange', log.latitude, log.longitude); | ||
118 | + this.$emit('clickDrone', Number(item.id)); | ||
119 | + checkValid = true; | ||
120 | + } | ||
121 | + }); | ||
122 | + if (!checkValid) { | ||
123 | + this.$warning({ | ||
124 | + title: '선택하신 드론은 현재 가동 중이지 않은 드론입니다.', | ||
125 | + }); | ||
126 | + } | ||
127 | + }, | ||
128 | + }, | ||
129 | +}; | ||
130 | +</script> | ||
131 | +<style lang="scss"> | ||
132 | +</style> |
Frontend/src/components/Main/drone.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <l-marker | ||
4 | + ref="markersRef" | ||
5 | + :lat-lng="[latitude, longitude]" | ||
6 | + @click="clickDrone" | ||
7 | + > | ||
8 | + <l-icon | ||
9 | + :icon-size="iconSize" | ||
10 | + :icon-anchor="[20, 20]" | ||
11 | + :icon-url="icon" | ||
12 | + ></l-icon> | ||
13 | + </l-marker> | ||
14 | + <l-polyline :lat-lngs="drone.polyline.latlngs" :color="drone.polyline.color"></l-polyline> | ||
15 | + </div> | ||
16 | +</template> | ||
17 | + | ||
18 | +<script> | ||
19 | + | ||
20 | +import { mapGetters } from 'vuex'; | ||
21 | + | ||
22 | +export default { | ||
23 | + head() { | ||
24 | + return { | ||
25 | + title: 'Drone', | ||
26 | + meta: [ | ||
27 | + { | ||
28 | + hid: 'database', | ||
29 | + name: 'Descriptions', | ||
30 | + content: 'DroneWeb-Content', | ||
31 | + }, | ||
32 | + ], | ||
33 | + }; | ||
34 | + }, | ||
35 | + props: { | ||
36 | + latitude: { | ||
37 | + type: Number, | ||
38 | + default: 0, | ||
39 | + }, | ||
40 | + longitude: { | ||
41 | + type: Number, | ||
42 | + default: 0, | ||
43 | + }, | ||
44 | + icon: { | ||
45 | + default: null, | ||
46 | + }, | ||
47 | + }, | ||
48 | + data() { | ||
49 | + return { | ||
50 | + drone: { | ||
51 | + latitude: 37.2430125, | ||
52 | + longitude: 127.0811054, | ||
53 | + polyline: { | ||
54 | + latlngs: [], | ||
55 | + color: 'red', | ||
56 | + }, | ||
57 | + }, | ||
58 | + }; | ||
59 | + }, | ||
60 | + mounted() { | ||
61 | + this.$nextTick(() => { | ||
62 | + // console.log('hi', this.$refs.markersRef) | ||
63 | + // this.markerObjects = this.$refs.markersRef.map(ref => ref.mapObject); | ||
64 | + // console.log(this.markerObjects) | ||
65 | + }); | ||
66 | + }, | ||
67 | + computed: { | ||
68 | + ...mapGetters('Drone/Map', { | ||
69 | + getZoomLevel: 'getZoomLevel', | ||
70 | + }), | ||
71 | + iconSize() { | ||
72 | + if (this.getZoomLevel == null) return [40, 40]; | ||
73 | + return [this.getZoomLevel * 2.2, this.getZoomLevel * 2.2]; | ||
74 | + }, | ||
75 | + }, | ||
76 | + created() { | ||
77 | + // this.makeDronePath(); | ||
78 | + }, | ||
79 | + methods: { | ||
80 | + clickDrone() { | ||
81 | + this.$emit('clickDrone'); | ||
82 | + }, | ||
83 | + makeDronePath() { | ||
84 | + // setTimeout(() => { | ||
85 | + // this.drone.latitude += 0.00005; | ||
86 | + // // this.drone.longitude += 0.00005; | ||
87 | + // this.drone.polyline.latlngs.push([this.drone.latitude, this.drone.longitude]); | ||
88 | + // this.makeDronePath(); | ||
89 | + // }, 500); | ||
90 | + }, | ||
91 | + }, | ||
92 | +}; | ||
93 | +</script> |
Frontend/src/components/Main/header.vue
0 → 100644
1 | +<template> | ||
2 | + <a-page-header | ||
3 | + class="page-header" | ||
4 | + title="RT-Map" | ||
5 | + sub-title="Realtime Drone Simulation Map" | ||
6 | + > | ||
7 | + <template slot="extra"> | ||
8 | + <a-tooltip placement="bottom"> | ||
9 | + <template slot="title"> | ||
10 | + <div>맵이 중앙에 위치하도록 </div> | ||
11 | + <div>스크롤을 이동합니다.</div> | ||
12 | + </template> | ||
13 | + <a-button type="primary" icon="select" | ||
14 | + @click="moveScroll" | ||
15 | + > | ||
16 | + Move Scroll | ||
17 | + </a-button> | ||
18 | + </a-tooltip> | ||
19 | + </template> | ||
20 | + </a-page-header> | ||
21 | +</template> | ||
22 | + | ||
23 | +<script> | ||
24 | + | ||
25 | +export default { | ||
26 | + name: 'MainHeader', | ||
27 | + components: {}, | ||
28 | + data() { | ||
29 | + return { | ||
30 | + }; | ||
31 | + }, | ||
32 | + methods: { | ||
33 | + moveScroll() { | ||
34 | + window.scrollTo(0, 105); | ||
35 | + }, | ||
36 | + }, | ||
37 | +}; | ||
38 | +</script> | ||
39 | + | ||
40 | +<style scoped lang="scss"> | ||
41 | + | ||
42 | +</style> |
Frontend/src/components/README.md
0 → 100644
1 | +<template> | ||
2 | + <div v-if="chartData != null"> | ||
3 | + <highcharts :options="options" | ||
4 | + :callback="onChartLoaded" | ||
5 | + :class="chartClass" | ||
6 | + :style="borderStyle" | ||
7 | + /> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { Chart } from 'highcharts-vue'; | ||
13 | + | ||
14 | +export default { | ||
15 | + name: 'barChart', | ||
16 | + components: { | ||
17 | + highcharts: Chart, | ||
18 | + }, | ||
19 | + props: { | ||
20 | + chartData: { | ||
21 | + type: Array, | ||
22 | + default: null, | ||
23 | + }, | ||
24 | + chartSettings: { | ||
25 | + type: Object, | ||
26 | + default: null, | ||
27 | + }, | ||
28 | + chartClass: { | ||
29 | + type: String, | ||
30 | + default: null, | ||
31 | + }, | ||
32 | + bordered: { | ||
33 | + type: Boolean, | ||
34 | + default: null, | ||
35 | + }, | ||
36 | + }, | ||
37 | + data() { | ||
38 | + return { | ||
39 | + chart: null, | ||
40 | + options: null, | ||
41 | + borderStyle: {}, | ||
42 | + }; | ||
43 | + }, | ||
44 | + watch: { | ||
45 | + chartData: { | ||
46 | + deep: true, | ||
47 | + handler(val) { | ||
48 | + if (val == null) return; | ||
49 | + this.options.series = val; | ||
50 | + | ||
51 | + if (this.chart == null) return; | ||
52 | + this.chart.redraw(); | ||
53 | + this.chart.setSize(null); | ||
54 | + }, | ||
55 | + }, | ||
56 | + chartSettings: { | ||
57 | + deep: true, | ||
58 | + handler(val) { | ||
59 | + if (val == null) return; | ||
60 | + this.setCustomChartSetting(val); | ||
61 | + | ||
62 | + if (this.chart == null) return; | ||
63 | + this.chart.redraw(); | ||
64 | + this.chart.setSize(null); | ||
65 | + }, | ||
66 | + }, | ||
67 | + }, | ||
68 | + created() { | ||
69 | + this.options.series = this.chartData; | ||
70 | + this.setCustomChartSetting(this.chartSettings); | ||
71 | + | ||
72 | + if (this.bordered) { | ||
73 | + this.borderStyle = { | ||
74 | + border: '2px solid #dde2ec', | ||
75 | + }; | ||
76 | + } | ||
77 | + }, | ||
78 | + methods: { | ||
79 | + onChartLoaded(chart) { | ||
80 | + if (this.chart == null) { | ||
81 | + this.chart = chart; | ||
82 | + } | ||
83 | + | ||
84 | + this.chart.zoomOut(false); | ||
85 | + this.chart.redraw(); | ||
86 | + this.chart.setSize(null); | ||
87 | + }, | ||
88 | + /** | ||
89 | + * Label Formatter로 Html로 레이블 설정 가능 | ||
90 | + * 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions | ||
91 | + * @param settings | ||
92 | + */ | ||
93 | + setCustomChartSetting(settings) { | ||
94 | + this.options.chart = { | ||
95 | + type: 'bar', | ||
96 | + }; | ||
97 | + if (settings == null) return; | ||
98 | + this.options = { ...settings, series: this.chartData }; | ||
99 | + this.options.chart.type = 'bar'; | ||
100 | + /* | ||
101 | + * dataLabel 설정 | ||
102 | + */ | ||
103 | + // if (settings.dataLabelSuffix) { | ||
104 | + // this.options.plotOptions.column.dataLabels.formatter = function () { | ||
105 | + // return `${this.key}: ${this.y.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')}${settings.dataLabelSuffix}`; | ||
106 | + // }; | ||
107 | + // } | ||
108 | + }, | ||
109 | + }, | ||
110 | +}; | ||
111 | +</script> | ||
112 | + | ||
113 | +<style scoped> | ||
114 | + | ||
115 | +</style> |
1 | +<template> | ||
2 | + <div v-if="chartData != null"> | ||
3 | + <highcharts :options="options" | ||
4 | + :callback="onChartLoaded" | ||
5 | + :class="chartClass" | ||
6 | + :style="borderStyle" | ||
7 | + /> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { Chart } from 'highcharts-vue'; | ||
13 | + | ||
14 | +export default { | ||
15 | + name: 'columnChart', | ||
16 | + components: { | ||
17 | + highcharts: Chart, | ||
18 | + }, | ||
19 | + props: { | ||
20 | + chartData: { | ||
21 | + type: Array, | ||
22 | + default: null, | ||
23 | + }, | ||
24 | + chartSettings: { | ||
25 | + type: Object, | ||
26 | + default: null, | ||
27 | + }, | ||
28 | + chartClass: { | ||
29 | + type: String, | ||
30 | + default: null, | ||
31 | + }, | ||
32 | + bordered: { | ||
33 | + type: Boolean, | ||
34 | + default: null, | ||
35 | + }, | ||
36 | + }, | ||
37 | + data() { | ||
38 | + return { | ||
39 | + chart: null, | ||
40 | + options: {}, | ||
41 | + borderStyle: {}, | ||
42 | + }; | ||
43 | + }, | ||
44 | + watch: { | ||
45 | + chartData: { | ||
46 | + deep: true, | ||
47 | + handler(val) { | ||
48 | + if (val == null) return; | ||
49 | + this.options.series = val; | ||
50 | + | ||
51 | + if (this.chart == null) return; | ||
52 | + this.chart.redraw(); | ||
53 | + this.chart.setSize(null); | ||
54 | + }, | ||
55 | + }, | ||
56 | + chartSettings: { | ||
57 | + deep: true, | ||
58 | + handler(val) { | ||
59 | + if (val == null) return; | ||
60 | + this.setCustomChartSetting(val); | ||
61 | + | ||
62 | + if (this.chart == null) return; | ||
63 | + this.chart.redraw(); | ||
64 | + this.chart.setSize(null); | ||
65 | + }, | ||
66 | + }, | ||
67 | + }, | ||
68 | + created() { | ||
69 | + this.setCustomChartSetting(this.chartSettings); | ||
70 | + if (this.bordered) { | ||
71 | + this.borderStyle = { | ||
72 | + border: '2px solid #dde2ec', | ||
73 | + }; | ||
74 | + } | ||
75 | + }, | ||
76 | + methods: { | ||
77 | + onChartLoaded(chart) { | ||
78 | + if (this.chart == null) { this.chart = chart; } | ||
79 | + | ||
80 | + this.chart.zoomOut(false); | ||
81 | + this.chart.redraw(); | ||
82 | + this.chart.setSize(null); | ||
83 | + }, | ||
84 | + /** | ||
85 | + * Label Formatter로 Html로 레이블 설정 가능 | ||
86 | + * 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions | ||
87 | + * @param settings | ||
88 | + */ | ||
89 | + setCustomChartSetting(settings) { | ||
90 | + this.options.chart = { | ||
91 | + type: 'column', | ||
92 | + }; | ||
93 | + if (settings == null) return; | ||
94 | + this.options = { ...settings, series: this.chartData }; | ||
95 | + this.options.chart.type = 'column'; | ||
96 | + }, | ||
97 | + }, | ||
98 | +}; | ||
99 | +</script> | ||
100 | + | ||
101 | +<style scoped> | ||
102 | + | ||
103 | +</style> |
1 | +<template> | ||
2 | + <div v-if="chartData != null"> | ||
3 | + <highcharts :options="options" | ||
4 | + :callback="onChartLoaded" | ||
5 | + :class="chartClass" | ||
6 | + :style="borderStyle" | ||
7 | + /> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { Chart } from 'highcharts-vue'; | ||
13 | + | ||
14 | +export default { | ||
15 | + name: 'lineChart', | ||
16 | + components: { | ||
17 | + highcharts: Chart, | ||
18 | + }, | ||
19 | + props: { | ||
20 | + chartData: { | ||
21 | + type: Array, | ||
22 | + default: null, | ||
23 | + }, | ||
24 | + chartSettings: { | ||
25 | + type: Object, | ||
26 | + default: null, | ||
27 | + }, | ||
28 | + chartClass: { | ||
29 | + type: String, | ||
30 | + default: null, | ||
31 | + }, | ||
32 | + bordered: { | ||
33 | + type: Boolean, | ||
34 | + default: null, | ||
35 | + }, | ||
36 | + }, | ||
37 | + data() { | ||
38 | + return { | ||
39 | + chart: null, | ||
40 | + options: {}, | ||
41 | + borderStyle: {}, | ||
42 | + }; | ||
43 | + }, | ||
44 | + watch: { | ||
45 | + chartData: { | ||
46 | + deep: true, | ||
47 | + handler(val) { | ||
48 | + if (val == null) return; | ||
49 | + this.options.series = val; | ||
50 | + | ||
51 | + if (this.chart == null) return; | ||
52 | + this.chart.redraw(); | ||
53 | + this.chart.setSize(null); | ||
54 | + }, | ||
55 | + }, | ||
56 | + chartSettings: { | ||
57 | + deep: true, | ||
58 | + handler(val) { | ||
59 | + if (val == null) return; | ||
60 | + this.setCustomChartSetting(val); | ||
61 | + | ||
62 | + if (this.chart == null) return; | ||
63 | + this.chart.redraw(); | ||
64 | + this.chart.setSize(null); | ||
65 | + }, | ||
66 | + }, | ||
67 | + }, | ||
68 | + created() { | ||
69 | + this.options.series = this.chartData; | ||
70 | + this.setCustomChartSetting(this.chartSettings); | ||
71 | + if (this.bordered) { | ||
72 | + this.borderStyle = { | ||
73 | + border: '2px solid #dde2ec', | ||
74 | + }; | ||
75 | + } | ||
76 | + }, | ||
77 | + methods: { | ||
78 | + onChartLoaded(chart) { | ||
79 | + if (this.chart == null) { this.chart = chart; } | ||
80 | + | ||
81 | + this.chart.zoomOut(false); | ||
82 | + this.chart.redraw(); | ||
83 | + this.chart.setSize(null); | ||
84 | + }, | ||
85 | + /** | ||
86 | + * Label Formatter로 Html로 레이블 설정 가능 | ||
87 | + * 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions | ||
88 | + * @param settings | ||
89 | + */ | ||
90 | + setCustomChartSetting(settings) { | ||
91 | + this.options.chart = { | ||
92 | + type: 'line', | ||
93 | + }; | ||
94 | + if (settings == null) return; | ||
95 | + this.options = { ...settings, series: this.chartData }; | ||
96 | + this.options.chart.type = 'line'; | ||
97 | + }, | ||
98 | + }, | ||
99 | +}; | ||
100 | +</script> | ||
101 | + | ||
102 | +<style scoped> | ||
103 | + | ||
104 | +</style> |
1 | +<template> | ||
2 | + <div v-if="chartData != null"> | ||
3 | + <highcharts :options="options" | ||
4 | + :callback="onChartLoaded" | ||
5 | + :class="chartClass" | ||
6 | + :style="borderStyle" | ||
7 | + /> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { Chart } from 'highcharts-vue'; | ||
13 | + | ||
14 | +export default { | ||
15 | + name: 'liveLineChart', | ||
16 | + components: { | ||
17 | + highcharts: Chart, | ||
18 | + }, | ||
19 | + props: { | ||
20 | + chartData: { | ||
21 | + type: Array, | ||
22 | + default: null, | ||
23 | + }, | ||
24 | + chartSettings: { | ||
25 | + type: Object, | ||
26 | + default: null, | ||
27 | + }, | ||
28 | + chartClass: { | ||
29 | + type: String, | ||
30 | + default: null, | ||
31 | + }, | ||
32 | + bordered: { | ||
33 | + type: Boolean, | ||
34 | + default: null, | ||
35 | + }, | ||
36 | + }, | ||
37 | + data() { | ||
38 | + return { | ||
39 | + chart: null, | ||
40 | + options: {}, | ||
41 | + borderStyle: {}, | ||
42 | + }; | ||
43 | + }, | ||
44 | + watch: { | ||
45 | + chartSettings: { | ||
46 | + deep: true, | ||
47 | + handler(val) { | ||
48 | + if (val == null) return; | ||
49 | + this.setCustomChartSetting(val); | ||
50 | + | ||
51 | + if (this.chart == null) return; | ||
52 | + this.chart.redraw(); | ||
53 | + this.chart.setSize(null); | ||
54 | + }, | ||
55 | + }, | ||
56 | + }, | ||
57 | + created() { | ||
58 | + this.options.series = this.chartData; | ||
59 | + this.setCustomChartSetting(this.chartSettings); | ||
60 | + if (this.bordered) { | ||
61 | + this.borderStyle = { | ||
62 | + border: '2px solid #dde2ec', | ||
63 | + }; | ||
64 | + } | ||
65 | + }, | ||
66 | + methods: { | ||
67 | + onChartLoaded(chart) { | ||
68 | + if (this.chart == null) { this.chart = chart; } | ||
69 | + | ||
70 | + this.chart.zoomOut(false); | ||
71 | + this.chart.redraw(); | ||
72 | + this.chart.setSize(null); | ||
73 | + }, | ||
74 | + /** | ||
75 | + * Label Formatter로 Html로 레이블 설정 가능 | ||
76 | + * 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions | ||
77 | + * @param settings | ||
78 | + */ | ||
79 | + setCustomChartSetting(settings) { | ||
80 | + this.options.chart = { | ||
81 | + type: 'line', | ||
82 | + }; | ||
83 | + if (settings == null) return; | ||
84 | + this.options = { ...settings, series: this.chartData }; | ||
85 | + this.options.chart.type = 'line'; | ||
86 | + }, | ||
87 | + }, | ||
88 | +}; | ||
89 | +</script> | ||
90 | + | ||
91 | +<style scoped> | ||
92 | + | ||
93 | +</style> |
1 | +<template> | ||
2 | + <div v-if="chartData != null"> | ||
3 | + <highcharts :options="options" | ||
4 | + :callback="onChartLoaded" | ||
5 | + :class="chartClass" | ||
6 | + :style="borderStyle" | ||
7 | + /> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { Chart } from 'highcharts-vue'; | ||
13 | + | ||
14 | +export default { | ||
15 | + name: 'pieChart', | ||
16 | + components: { | ||
17 | + highcharts: Chart, | ||
18 | + }, | ||
19 | + props: { | ||
20 | + chartData: { | ||
21 | + type: Array, | ||
22 | + default: null, | ||
23 | + }, | ||
24 | + chartSettings: { | ||
25 | + type: Object, | ||
26 | + default: null, | ||
27 | + }, | ||
28 | + chartClass: { | ||
29 | + type: String, | ||
30 | + default: null, | ||
31 | + }, | ||
32 | + bordered: { | ||
33 | + type: Boolean, | ||
34 | + default: false, | ||
35 | + }, | ||
36 | + donut: { | ||
37 | + type: Boolean, | ||
38 | + default: false, | ||
39 | + }, | ||
40 | + }, | ||
41 | + data() { | ||
42 | + return { | ||
43 | + chart: null, | ||
44 | + options: {}, | ||
45 | + isDataLabelAdded: false, | ||
46 | + borderStyle: {}, | ||
47 | + }; | ||
48 | + }, | ||
49 | + watch: { | ||
50 | + chartData: { | ||
51 | + deep: true, | ||
52 | + handler(val) { | ||
53 | + if (val == null) return; | ||
54 | + this.options.series = val; | ||
55 | + if (this.donut && val.length > 0) { | ||
56 | + this.options.series[0].innerSize = '50%'; | ||
57 | + } | ||
58 | + | ||
59 | + if (this.chart == null) return; | ||
60 | + this.chart.redraw(); | ||
61 | + this.chart.setSize(null); | ||
62 | + }, | ||
63 | + }, | ||
64 | + chartSettings: { | ||
65 | + deep: true, | ||
66 | + handler(val) { | ||
67 | + if (val == null) return; | ||
68 | + this.setCustomChartSetting(val); | ||
69 | + | ||
70 | + if (this.chart == null) return; | ||
71 | + this.chart.redraw(); | ||
72 | + this.chart.setSize(null); | ||
73 | + }, | ||
74 | + }, | ||
75 | + 'chartSettings.subtitle': { | ||
76 | + deep: true, | ||
77 | + handler(val) { | ||
78 | + if (val == null) return; | ||
79 | + this.setCustomChartSetting(val); | ||
80 | + | ||
81 | + if (this.chart == null) return; | ||
82 | + this.chart.redraw(); | ||
83 | + this.chart.setSize(null); | ||
84 | + }, | ||
85 | + }, | ||
86 | + }, | ||
87 | + created() { | ||
88 | + this.setCustomChartSetting(this.chartSettings); | ||
89 | + if (this.bordered) { | ||
90 | + this.borderStyle = { | ||
91 | + border: '2px solid #dde2ec', | ||
92 | + }; | ||
93 | + } | ||
94 | + }, | ||
95 | + methods: { | ||
96 | + onChartLoaded(chart) { | ||
97 | + if (this.chart == null) { | ||
98 | + this.chart = chart; | ||
99 | + } | ||
100 | + this.chart.redraw(); | ||
101 | + this.chart.setSize(null); | ||
102 | + }, | ||
103 | + /** | ||
104 | + * Label Formatter로 Html로 레이블 설정 가능 | ||
105 | + * 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions | ||
106 | + * @param settings | ||
107 | + */ | ||
108 | + setCustomChartSetting(settings) { | ||
109 | + this.options.chart = { | ||
110 | + type: 'pie', | ||
111 | + }; | ||
112 | + if (settings == null) return; | ||
113 | + this.options = { ...settings, series: this.chartData }; | ||
114 | + this.options.chart.type = 'pie'; | ||
115 | + }, | ||
116 | + }, | ||
117 | +}; | ||
118 | +</script> | ||
119 | + | ||
120 | +<style scoped> | ||
121 | + | ||
122 | +</style> |
1 | +<template> | ||
2 | + <div class="ant-modal-content"> | ||
3 | + <button | ||
4 | + type="button" | ||
5 | + aria-label="Close" | ||
6 | + class="ant-modal-close" | ||
7 | + @click="clickClose" | ||
8 | + > | ||
9 | + <span class="ant-modal-close-x"> | ||
10 | + <a-icon style="color: white" type="close" /> | ||
11 | + </span> | ||
12 | + </button> | ||
13 | + <div class="ant-modal-header"> | ||
14 | + <div class="ant-modal-title"> | ||
15 | + <slot name="header"></slot> | ||
16 | + </div> | ||
17 | + </div> | ||
18 | + <div class="ant-modal-body"> | ||
19 | + <slot name="body"></slot> | ||
20 | + </div> | ||
21 | + </div> | ||
22 | +</template> | ||
23 | +<script> | ||
24 | + | ||
25 | +export default { | ||
26 | + head() { | ||
27 | + return { | ||
28 | + title: 'Drone', | ||
29 | + meta: [ | ||
30 | + { | ||
31 | + hid: 'database', | ||
32 | + name: 'Descriptions', | ||
33 | + content: 'DroneWeb-Content', | ||
34 | + }, | ||
35 | + ], | ||
36 | + }; | ||
37 | + }, | ||
38 | + props: { | ||
39 | + }, | ||
40 | + data() { | ||
41 | + return { | ||
42 | + }; | ||
43 | + }, | ||
44 | + computed: { | ||
45 | + }, | ||
46 | + created() { | ||
47 | + }, | ||
48 | + methods: { | ||
49 | + clickClose() { | ||
50 | + this.$emit('clickClose') | ||
51 | + }, | ||
52 | + }, | ||
53 | +}; | ||
54 | +</script> | ||
55 | +<style lang="scss"> | ||
56 | +</style> |
Frontend/src/components/_Common/README.md
0 → 100644
Frontend/src/components/logo.vue
0 → 100644
1 | +<template> | ||
2 | + <svg class="NuxtLogo" width="245" height="180" viewBox="0 0 452 342" xmlns="http://www.w3.org/2000/svg"> | ||
3 | + <path | ||
4 | + d="M139 330l-1-2c-2-4-2-8-1-13H29L189 31l67 121 22-16-67-121c-1-2-9-14-22-14-6 0-15 2-22 15L5 303c-1 3-8 16-2 27 4 6 10 12 24 12h136c-14 0-21-6-24-12z" | ||
5 | + fill="#00C58E" | ||
6 | + /> | ||
7 | + <path | ||
8 | + d="M447 304L317 70c-2-2-9-15-22-15-6 0-15 3-22 15l-17 28v54l39-67 129 230h-49a23 23 0 0 1-2 14l-1 1c-6 11-21 12-23 12h76c3 0 17-1 24-12 3-5 5-14-2-26z" | ||
9 | + fill="#108775" | ||
10 | + /> | ||
11 | + <path | ||
12 | + d="M376 330v-1l1-2c1-4 2-8 1-12l-4-12-102-178-15-27h-1l-15 27-102 178-4 12a24 24 0 0 0 2 15c4 6 10 12 24 12h190c3 0 18-1 25-12zM256 152l93 163H163l93-163z" | ||
13 | + fill="#2F495E" | ||
14 | + /> | ||
15 | + </svg> | ||
16 | +</template> | ||
17 | + | ||
18 | +<script> | ||
19 | +export default { | ||
20 | + name: 'Logo', | ||
21 | +}; | ||
22 | +</script> | ||
23 | + | ||
24 | +<style> | ||
25 | +.NuxtLogo { | ||
26 | + animation: 1s appear; | ||
27 | + margin: auto; | ||
28 | +} | ||
29 | + | ||
30 | +@keyframes appear { | ||
31 | + 0% { | ||
32 | + opacity: 0; | ||
33 | + } | ||
34 | +} | ||
35 | +</style> |
Frontend/src/layouts/README.md
0 → 100644
Frontend/src/layouts/auth.vue
0 → 100644
Frontend/src/layouts/default.vue
0 → 100644
1 | +<template> | ||
2 | + <a-config-provider> | ||
3 | + <a-layout :style="{height: '100%'}"> | ||
4 | + <a-layout-header class="header-style"> | ||
5 | + <div class="r-flex gap-2 space-between"> | ||
6 | + <div class="r-flex start"> | ||
7 | + <div style="margin-right: 20px"> | ||
8 | + <a-icon class="logo-title" type="compass" /> | ||
9 | + <span class="logo-title">Drone Web</span> | ||
10 | + </div> | ||
11 | + <a-menu | ||
12 | + theme="dark" | ||
13 | + mode="horizontal" | ||
14 | + v-model="tabKey" | ||
15 | + :style="{lineHeight: '40px'}" | ||
16 | + > | ||
17 | + <a-menu-item :key="1" class="router-menu-item"> | ||
18 | + <nuxt-link to="/"> | ||
19 | + <a-icon type="environment" /> | ||
20 | + RT-Map | ||
21 | + </nuxt-link> | ||
22 | + </a-menu-item> | ||
23 | + <a-menu-item :key="2" class="router-menu-item router-menu-margin"> | ||
24 | + <a-dropdown> | ||
25 | + <a | ||
26 | + class="ant-dropdown-link" | ||
27 | + @click="(e) => e.preventDefault()" | ||
28 | + > | ||
29 | + <a-icon type="database" /> | ||
30 | + DataBase | ||
31 | + </a> | ||
32 | + <a-menu | ||
33 | + v-model="dbMenu" | ||
34 | + slot="overlay" | ||
35 | + @click="hoverClick" | ||
36 | + :style="{textAlign: 'center'}" | ||
37 | + class="router-database" | ||
38 | + > | ||
39 | + <a-menu-item :key="1"> | ||
40 | + Drone | ||
41 | + </a-menu-item> | ||
42 | + <a-menu-item :key="2"> | ||
43 | + Schedule | ||
44 | + </a-menu-item> | ||
45 | + <a-menu-item :key="3"> | ||
46 | + Log | ||
47 | + </a-menu-item> | ||
48 | + </a-menu> | ||
49 | + </a-dropdown> | ||
50 | + </a-menu-item> | ||
51 | + <a-menu-item :key="3" class="router-menu-item" disabled> | ||
52 | + <!-- <nuxt-link to="/analytics"> | ||
53 | + <a-icon type="dashboard" /> | ||
54 | + Analytics | ||
55 | + </nuxt-link> --> | ||
56 | + <div @click="info()"> | ||
57 | + <a-icon type="dashboard" /> | ||
58 | + Analytics | ||
59 | + </div> | ||
60 | + </a-menu-item> | ||
61 | + <!-- <a-menu-item :key="4" class="router-menu-item"> | ||
62 | + <nuxt-link to="/websocket"> | ||
63 | + websocket | ||
64 | + </nuxt-link> | ||
65 | + </a-menu-item> --> | ||
66 | + </a-menu> | ||
67 | + </div> | ||
68 | + <div class="nav-etc"> | ||
69 | + <a-dropdown> | ||
70 | + <a-button | ||
71 | + type="dash" | ||
72 | + icon="setting" | ||
73 | + style="background-color: rgba(245, 245, 245, 0.9); border: 2px solid #e4e9f0; color: #001529" | ||
74 | + /> | ||
75 | + <a-menu | ||
76 | + slot="overlay" | ||
77 | + @click="clickEtc" | ||
78 | + class="router-database" | ||
79 | + > | ||
80 | + <a-menu-item-group style="text-align: center"> | ||
81 | + WebSocket | ||
82 | + </a-menu-item-group> | ||
83 | + <a-menu-divider style="margin-top: 8px" /> | ||
84 | + <a-menu-item key="connect"> | ||
85 | + <a-icon type="link"/>Connect | ||
86 | + </a-menu-item> | ||
87 | + <a-menu-item key="disconnect"> | ||
88 | + <a-icon type="disconnect"/>Disconnect | ||
89 | + </a-menu-item> | ||
90 | + </a-menu> | ||
91 | + </a-dropdown> | ||
92 | + </div> | ||
93 | + </div> | ||
94 | + </a-layout-header> | ||
95 | + <a-layout-content :style="{padding: '0 20px', marginTop: '84px'}"> | ||
96 | + <div :style="{minHeight: 'calc(100vh - 84px - 89px)', height: '100%'}"> | ||
97 | + <nuxt keep-alive /> | ||
98 | + </div> | ||
99 | + </a-layout-content> | ||
100 | + <a-layout-footer class="footer-style"> | ||
101 | + <layout-footer /> | ||
102 | + </a-layout-footer> | ||
103 | + </a-layout> | ||
104 | + </a-config-provider> | ||
105 | +</template> | ||
106 | + | ||
107 | +<script> | ||
108 | +import LayoutFooter from '@/components/Layout/footer'; | ||
109 | +import { mapActions, mapGetters } from 'vuex'; | ||
110 | + | ||
111 | +export default { | ||
112 | + components: { LayoutFooter }, | ||
113 | + /* vue-meta -> 각 페이지의 meta */ | ||
114 | + head() { | ||
115 | + return { | ||
116 | + titleTemplate: 'Drone Web - %s', | ||
117 | + meta: [ | ||
118 | + { | ||
119 | + hid: 'defaultLayout', | ||
120 | + name: 'Descriptions', | ||
121 | + content: 'Content', | ||
122 | + }, | ||
123 | + ], | ||
124 | + }; | ||
125 | + }, | ||
126 | + data() { | ||
127 | + return { | ||
128 | + tabKey: [1], | ||
129 | + dbMenu: [0], | ||
130 | + etcAction: 'connect', | ||
131 | + }; | ||
132 | + }, | ||
133 | + watch: { | ||
134 | + '$route.fullPath': { | ||
135 | + handler() { | ||
136 | + this.changeMenuRoute(); | ||
137 | + }, | ||
138 | + }, | ||
139 | + }, | ||
140 | + created() { | ||
141 | + this.changeMenuRoute(); | ||
142 | + this.$store.dispatch('Drone/list/fetchListContents').then((r) => { | ||
143 | + const makerList = {}; | ||
144 | + const { drones } = r; | ||
145 | + for (let i = 0; i < drones.length; i += 1) { | ||
146 | + if (makerList[drones[i].maker]) makerList[drones[i].maker].push(drones[i]); | ||
147 | + else makerList[drones[i].maker] = [drones[i]]; | ||
148 | + } | ||
149 | + this.$store.dispatch('Drone/list/setFixedDroneList', drones); | ||
150 | + this.$store.dispatch('Etc/setMakers', makerList); | ||
151 | + }); | ||
152 | + }, | ||
153 | + computed: { | ||
154 | + ...mapGetters('Schedule/page', { | ||
155 | + getSchdulePageParams: 'getPageParams', | ||
156 | + }), | ||
157 | + ...mapGetters('Log/page', { | ||
158 | + getLogPageParams: 'getPageParams', | ||
159 | + }), | ||
160 | + }, | ||
161 | + methods: { | ||
162 | + changeMenuRoute() { | ||
163 | + const pageRoute = this.$route.fullPath.split('/')[1]; | ||
164 | + switch (pageRoute) { | ||
165 | + case '': | ||
166 | + this.tabKey = [1]; | ||
167 | + this.dbMenu = [0]; | ||
168 | + break; | ||
169 | + case 'database': | ||
170 | + this.tabKey = [2]; | ||
171 | + break; | ||
172 | + case 'analytics': | ||
173 | + this.tabKey = [3]; | ||
174 | + this.dbMenu = [0]; | ||
175 | + break; | ||
176 | + default: | ||
177 | + this.tabKey = [999]; | ||
178 | + this.dbMenu = [0]; | ||
179 | + break; | ||
180 | + } | ||
181 | + }, | ||
182 | + hoverClick(data) { | ||
183 | + const { key } = data; | ||
184 | + this.tabKey = [2]; | ||
185 | + | ||
186 | + switch (key) { | ||
187 | + case 1: | ||
188 | + this.dbMenu = [1]; | ||
189 | + this.$router.push('/database/drone'); | ||
190 | + break; | ||
191 | + case 2: | ||
192 | + this.clearSchedulePageParams(); | ||
193 | + this.fetchSchedulePageData(this.getSchdulePageParams); | ||
194 | + this.dbMenu = [2]; | ||
195 | + this.$router.push('/database/schedule'); | ||
196 | + break; | ||
197 | + case 3: | ||
198 | + this.clearLogPageParams(); | ||
199 | + this.fetchLogPageData(this.getLogPageParams); | ||
200 | + this.dbMenu = [3]; | ||
201 | + this.$router.push('/database/log'); | ||
202 | + break; | ||
203 | + default: | ||
204 | + break; | ||
205 | + } | ||
206 | + }, | ||
207 | + ...mapActions('Schedule/page', { | ||
208 | + fetchSchedulePageData: 'fetchPageData', | ||
209 | + clearSchedulePageParams: 'clearPageParams', | ||
210 | + }), | ||
211 | + ...mapActions('Log/page', { | ||
212 | + fetchLogPageData: 'fetchPageData', | ||
213 | + clearLogPageParams: 'clearPageParams', | ||
214 | + }), | ||
215 | + info() { | ||
216 | + const h = this.$createElement; | ||
217 | + this.$info({ | ||
218 | + title: 'Notification', | ||
219 | + content: h('div', {}, [h('p', '개발 예정인 기능입니다.')]), | ||
220 | + onOk() {}, | ||
221 | + }); | ||
222 | + }, | ||
223 | + clickEtc(e) { | ||
224 | + if (e === 'connect') this.connect(); | ||
225 | + else this.disconnect(); | ||
226 | + }, | ||
227 | + }, | ||
228 | +}; | ||
229 | +</script> | ||
230 | + | ||
231 | +<style lang="scss" scoped> | ||
232 | +@import '@/assets/styles/mixins.scss'; | ||
233 | +.logo-title { | ||
234 | + color: white; | ||
235 | + font-size: 20px; | ||
236 | + margin-left: 20px; | ||
237 | +} | ||
238 | +.router-menu-item { | ||
239 | + border-radius: 8px; | ||
240 | +} | ||
241 | +.router-menu-margin { | ||
242 | + margin: 0 10px; | ||
243 | +} | ||
244 | +.header-style { | ||
245 | + position: fixed; | ||
246 | + z-index: 100; | ||
247 | + width: 100%; | ||
248 | + padding: 0px; | ||
249 | +} | ||
250 | +.nav-etc { | ||
251 | + margin-right: 20px; | ||
252 | + line-height: 30px; | ||
253 | +} | ||
254 | +.footer-style { | ||
255 | + background-color: #001529; | ||
256 | + text-align: center; | ||
257 | + margin: 20px 15px 0px 15px; | ||
258 | + border-radius: 6px 6px 0px 0px; | ||
259 | + padding: 12px 0px 6px 0px; | ||
260 | +} | ||
261 | + | ||
262 | +.ant-dropdown-menu-item-selected, | ||
263 | +.ant-dropdown-menu-submenu-title-selected, | ||
264 | +.ant-dropdown-menu-item-selected > a, | ||
265 | +.ant-dropdown-menu-submenu-title-selected > a { | ||
266 | + background: none; | ||
267 | +} | ||
268 | +</style> |
Frontend/src/layouts/error.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="error-main"> | ||
3 | + <a-result | ||
4 | + style="margin-top: -50px" | ||
5 | + :status="this.error.statusCode.toString()" | ||
6 | + :title="this.error.statusCode.toString()" | ||
7 | + :sub-title="this.error.message" | ||
8 | + > | ||
9 | + <template #extra> | ||
10 | + <a-button type="primary" @click="$router.push('/')"> | ||
11 | + Go to Main | ||
12 | + </a-button> | ||
13 | + </template> | ||
14 | + </a-result> | ||
15 | + </div> | ||
16 | +</template> | ||
17 | + | ||
18 | +<script> | ||
19 | +export default { | ||
20 | + props: { | ||
21 | + error: { | ||
22 | + type: Object, | ||
23 | + default: () => ({}), | ||
24 | + }, | ||
25 | + }, | ||
26 | +}; | ||
27 | +</script> | ||
28 | + | ||
29 | +<style lang="scss" scoped> | ||
30 | +.error-main { | ||
31 | + background-color: white; | ||
32 | + height: calc(100vh - 84px - 69px); | ||
33 | + display: flex; | ||
34 | + align-items: center; | ||
35 | + justify-content: center; | ||
36 | +} | ||
37 | +</style> |
Frontend/src/middleware/README.md
0 → 100644
1 | +# MIDDLEWARE | ||
2 | + | ||
3 | +**This directory is not required, you can delete it if you don't want to use it.** | ||
4 | + | ||
5 | +This directory contains your application middleware. | ||
6 | +Middleware let you define custom functions that can be run before rendering either a page or a group of pages. | ||
7 | + | ||
8 | +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). |
Frontend/src/middleware/router.js
0 → 100644
Frontend/src/modules/vuelayers.js
0 → 100644
Frontend/src/pages/README.md
0 → 100644
1 | +# PAGES | ||
2 | + | ||
3 | +This directory contains your Application Views and Routes. | ||
4 | +The framework reads all the `*.vue` files inside this directory and creates the router of your application. | ||
5 | + | ||
6 | +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). |
Frontend/src/pages/analytics/index.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <analytics-header/> | ||
4 | + <div class="chart-div"> | ||
5 | + <live-log-chart :chart-data="liveDroneData"/> | ||
6 | + </div> | ||
7 | + <div class="r-flex"> | ||
8 | + <div class="pie-chart-div" style="margin-right: 20px"> | ||
9 | + <maker-pie-chart :chart-data="makerData"/> | ||
10 | + </div> | ||
11 | + <div class="pie-chart-div"> | ||
12 | + <drone-category-pie-chart :chart-data="categoryData" /> | ||
13 | + </div> | ||
14 | + </div> | ||
15 | + <div class="chart-div"> | ||
16 | + <time-category-column-chart :chart-data="timeDroneData"/> | ||
17 | + </div> | ||
18 | + | ||
19 | + <div class="page-main"> | ||
20 | + <div> | ||
21 | + 소켓 테스트 | ||
22 | + </div> | ||
23 | + <a-button @click="getMessage">emit</a-button> | ||
24 | + <a-button @click="onM">on</a-button> | ||
25 | + </div> | ||
26 | + </div> | ||
27 | +</template> | ||
28 | + | ||
29 | +<script> | ||
30 | +import AnalyticsHeader from '@/components/Analytics/header'; | ||
31 | +import MakerPieChart from '@/components/Analytics/Chart/makerPieChart'; | ||
32 | +import DroneCategoryPieChart from '@/components/Analytics/Chart/droneCategoryPieChart'; | ||
33 | +import TimeCategoryColumnChart from '@/components/Analytics/Chart/timeCategoryColumnChart'; | ||
34 | +import LiveLogChart from '@/components/Analytics/Chart/liveLogChart'; | ||
35 | + | ||
36 | +export default { | ||
37 | + components: { | ||
38 | + LiveLogChart, | ||
39 | + TimeCategoryColumnChart, | ||
40 | + AnalyticsHeader, | ||
41 | + DroneCategoryPieChart, | ||
42 | + MakerPieChart, | ||
43 | + }, | ||
44 | + data() { | ||
45 | + return { | ||
46 | + socket: null, | ||
47 | + makerData: [], | ||
48 | + categoryData: [], | ||
49 | + timeDroneData: [], | ||
50 | + liveDroneData: [{ | ||
51 | + name: '드론 기체 수', | ||
52 | + data: [], | ||
53 | + }], | ||
54 | + }; | ||
55 | + }, | ||
56 | + computed: { | ||
57 | + ranNum() { | ||
58 | + return (min, max) => parseInt((Math.random() * (max - min) + min), 10); | ||
59 | + }, | ||
60 | + }, | ||
61 | + created() { | ||
62 | + this.makeChartData(); | ||
63 | + }, | ||
64 | + mounted() { | ||
65 | + this.socket = this.$nuxtSocket({ | ||
66 | + // nuxt-socket-io opts: | ||
67 | + name: 'main', // Use socket "home" | ||
68 | + channel: '/testSoc', | ||
69 | + // socket.io-client opts: | ||
70 | + reconnection: false, | ||
71 | + }); | ||
72 | + this.socket | ||
73 | + .on('receiveLog', (msg, cb) => { | ||
74 | + /* Handle event */ | ||
75 | + console.log(msg); | ||
76 | + this.liveDroneData[0].data.push({ | ||
77 | + x: this.$dayjs(msg.time).valueOf(), y: msg.num, | ||
78 | + }); | ||
79 | + }); | ||
80 | + this.socket | ||
81 | + .on('connection', (msg, cb) => { | ||
82 | + /* Handle event */ | ||
83 | + console.log(msg, cb); | ||
84 | + }); | ||
85 | + }, | ||
86 | + methods: { | ||
87 | + getMessage() { | ||
88 | + this.socket.emit('getMessage', { id: 'abc123' }, (resp) => { | ||
89 | + this.messageRxd = resp; | ||
90 | + }); | ||
91 | + }, | ||
92 | + onM() { | ||
93 | + this.$axios.get('http://localhost:8888') | ||
94 | + .then((r) => { | ||
95 | + console.log('apiCall'); | ||
96 | + }); | ||
97 | + }, | ||
98 | + makeChartData() { | ||
99 | + this.makerData = [{ | ||
100 | + name: '기체수', | ||
101 | + colorByPoint: true, | ||
102 | + data: [ | ||
103 | + { | ||
104 | + name: 'DJI', y: 6234, | ||
105 | + }, | ||
106 | + { | ||
107 | + name: '3DRobotics', y: 1248, | ||
108 | + }, | ||
109 | + { | ||
110 | + name: 'SYMA', y: 248, | ||
111 | + }, | ||
112 | + { | ||
113 | + name: 'Parrot', y: 783, | ||
114 | + }, | ||
115 | + { | ||
116 | + name: 'Cheerson', y: 563, | ||
117 | + }, | ||
118 | + ], | ||
119 | + }]; | ||
120 | + this.categoryData = [{ | ||
121 | + name: '기체수', | ||
122 | + colorByPoint: true, | ||
123 | + data: [ | ||
124 | + { | ||
125 | + name: 'TriCopter', y: 3452, | ||
126 | + }, | ||
127 | + { | ||
128 | + name: 'QuadCopter', y: 4032, | ||
129 | + }, | ||
130 | + { | ||
131 | + name: 'HexaCopter', y: 1395, | ||
132 | + }, | ||
133 | + { | ||
134 | + name: 'OctaCopter', y: 474, | ||
135 | + }, | ||
136 | + ], | ||
137 | + }]; | ||
138 | + | ||
139 | + this.timeDroneData = [ | ||
140 | + { | ||
141 | + name: 'Toy', | ||
142 | + data: [ | ||
143 | + this.ranNum(1, 100), | ||
144 | + this.ranNum(1, 100), | ||
145 | + this.ranNum(100, 300), | ||
146 | + this.ranNum(100, 300), | ||
147 | + this.ranNum(50, 200), | ||
148 | + this.ranNum(1, 100), | ||
149 | + ], | ||
150 | + }, | ||
151 | + { | ||
152 | + name: 'Racing', | ||
153 | + data: [ | ||
154 | + this.ranNum(1, 100), | ||
155 | + this.ranNum(1, 100), | ||
156 | + this.ranNum(100, 300), | ||
157 | + this.ranNum(100, 300), | ||
158 | + this.ranNum(50, 200), | ||
159 | + this.ranNum(1, 100), | ||
160 | + ], | ||
161 | + }, | ||
162 | + { | ||
163 | + name: 'Mission', | ||
164 | + data: [ | ||
165 | + this.ranNum(100, 1000), | ||
166 | + this.ranNum(100, 1000), | ||
167 | + this.ranNum(1, 200), | ||
168 | + this.ranNum(1, 200), | ||
169 | + this.ranNum(1, 200), | ||
170 | + this.ranNum(100, 1000), | ||
171 | + ], | ||
172 | + }, | ||
173 | + ]; | ||
174 | + }, | ||
175 | + }, | ||
176 | +}; | ||
177 | +</script> | ||
178 | + | ||
179 | +<style scoped lang="scss"> | ||
180 | +@import '@/assets/styles/mixins.scss'; | ||
181 | + | ||
182 | +.pie-chart-div { | ||
183 | + margin-top: 20px; | ||
184 | + height: 440px; | ||
185 | + width: calc(50% - 10px); | ||
186 | + padding: 20px 20px 20px 20px; | ||
187 | + background-color: white; | ||
188 | + border-radius: 6px; | ||
189 | + border: $gray-2 1px solid; | ||
190 | +} | ||
191 | +.chart-div { | ||
192 | + margin-top: 20px; | ||
193 | + height: 440px; | ||
194 | + padding: 20px 20px 20px 20px; | ||
195 | + background-color: white; | ||
196 | + border-radius: 6px; | ||
197 | + border: $gray-2 1px solid; | ||
198 | +} | ||
199 | +</style> |
Frontend/src/pages/auth/403.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + 403 Error Page | ||
4 | + </div> | ||
5 | +</template> | ||
6 | + | ||
7 | +<script> | ||
8 | +export default { | ||
9 | + layout: 'auth', | ||
10 | + data() { | ||
11 | + return { | ||
12 | + | ||
13 | + }; | ||
14 | + }, | ||
15 | + computed: { | ||
16 | + | ||
17 | + }, | ||
18 | + watch: { | ||
19 | + | ||
20 | + }, | ||
21 | + created() { | ||
22 | + | ||
23 | + }, | ||
24 | + method: { | ||
25 | + | ||
26 | + }, | ||
27 | +}; | ||
28 | +</script> |
Frontend/src/pages/auth/404.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + 404 Error Page | ||
4 | + </div> | ||
5 | +</template> | ||
6 | + | ||
7 | +<script> | ||
8 | +export default { | ||
9 | + layout: 'auth', | ||
10 | + data() { | ||
11 | + return { | ||
12 | + | ||
13 | + }; | ||
14 | + }, | ||
15 | + computed: { | ||
16 | + | ||
17 | + }, | ||
18 | + watch: { | ||
19 | + | ||
20 | + }, | ||
21 | + created() { | ||
22 | + | ||
23 | + }, | ||
24 | + method: { | ||
25 | + | ||
26 | + }, | ||
27 | +}; | ||
28 | +</script> |
Frontend/src/pages/auth/500.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + 500 Error Page | ||
4 | + </div> | ||
5 | +</template> | ||
6 | + | ||
7 | +<script> | ||
8 | +export default { | ||
9 | + layout: 'auth', | ||
10 | + data() { | ||
11 | + return { | ||
12 | + | ||
13 | + }; | ||
14 | + }, | ||
15 | + computed: { | ||
16 | + | ||
17 | + }, | ||
18 | + watch: { | ||
19 | + | ||
20 | + }, | ||
21 | + created() { | ||
22 | + | ||
23 | + }, | ||
24 | + method: { | ||
25 | + | ||
26 | + }, | ||
27 | +}; | ||
28 | +</script> |
Frontend/src/pages/database/drone/_id.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <database-detail-header /> | ||
4 | + <div class="page-main-without-header "> | ||
5 | + <div class="table-title">Schedule</div> | ||
6 | + <database-table :childLoading="parentLoading" @loadData="loadData" /> | ||
7 | + </div> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import DatabaseDetailHeader from '@/components/Database/Drone/Detail/header'; | ||
13 | +import DatabaseTable from '@/components/Database/Schedule/table'; | ||
14 | +import {mapActions, mapGetters} from 'vuex'; | ||
15 | + | ||
16 | +export default { | ||
17 | + components: { | ||
18 | + DatabaseDetailHeader, | ||
19 | + DatabaseTable, | ||
20 | + }, | ||
21 | + async asyncData({params, store}) { | ||
22 | + const {id} = params; | ||
23 | + await store.dispatch('Drone/detail/fetchDetailData', id); | ||
24 | + return {id}; | ||
25 | + }, | ||
26 | + data() { | ||
27 | + return { | ||
28 | + parentLoading: false, | ||
29 | + searchParams: {}, | ||
30 | + }; | ||
31 | + }, | ||
32 | + computed: { | ||
33 | + ...mapGetters('Drone/detail', { | ||
34 | + getDetailData: 'getDetailData', | ||
35 | + }), | ||
36 | + ...mapGetters('Schedule/page', { | ||
37 | + getPageParams: 'getPageParams', | ||
38 | + }), | ||
39 | + }, | ||
40 | + watch: {}, | ||
41 | + created() { | ||
42 | + this.searchParams = JSON.parse(JSON.stringify(this.getPageParams)); | ||
43 | + this.searchParams.droneId = this.getDetailData.id; | ||
44 | + this.setPageParams(this.searchParams); | ||
45 | + this.loadData(); | ||
46 | + }, | ||
47 | + beforeDestroy() { | ||
48 | + this.clearPageParams(); | ||
49 | + }, | ||
50 | + methods: { | ||
51 | + ...mapActions('Schedule/page', { | ||
52 | + fetchPageData: 'fetchPageData', | ||
53 | + clearPageParams: 'clearPageParams', | ||
54 | + setPageParams: 'setPageParams', | ||
55 | + }), | ||
56 | + loadData() { | ||
57 | + this.parentLoading = true; | ||
58 | + | ||
59 | + this.fetchPageData(this.getPageParams).finally(() => { | ||
60 | + this.parentLoading = false; | ||
61 | + }); | ||
62 | + }, | ||
63 | + }, | ||
64 | +}; | ||
65 | +</script> | ||
66 | + | ||
67 | +<style scoped lang="scss"> | ||
68 | +.table-title { | ||
69 | + font-size: 30px; | ||
70 | +} | ||
71 | +</style> |
Frontend/src/pages/database/drone/index.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <database-search-filter @loadData="loadData" /> | ||
4 | + | ||
5 | + <div class="page-main"> | ||
6 | + <database-table :childLoading="parentLoading" @loadData="loadData" /> | ||
7 | + </div> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { mapActions, mapGetters } from 'vuex'; | ||
13 | +import DatabaseSearchFilter from '@/components/Database/Drone/searchFilter'; | ||
14 | +import DatabaseTable from '@/components/Database/Drone/table'; | ||
15 | + | ||
16 | +export default { | ||
17 | + head() { | ||
18 | + return { | ||
19 | + title: 'Database-Drone', | ||
20 | + meta: [ | ||
21 | + { | ||
22 | + hid: 'database', | ||
23 | + name: 'Descriptions', | ||
24 | + content: 'db-Content', | ||
25 | + }, | ||
26 | + ], | ||
27 | + }; | ||
28 | + }, | ||
29 | + components: { | ||
30 | + DatabaseTable, | ||
31 | + DatabaseSearchFilter, | ||
32 | + }, | ||
33 | + data() { | ||
34 | + return { | ||
35 | + parentLoading: false, | ||
36 | + }; | ||
37 | + }, | ||
38 | + computed: { | ||
39 | + ...mapGetters('Drone/page', { | ||
40 | + getPageParams: 'getPageParams', | ||
41 | + }), | ||
42 | + }, | ||
43 | + created() { | ||
44 | + this.loadData(); | ||
45 | + }, | ||
46 | + beforeDestroy() { | ||
47 | + this.clearPageParams(); | ||
48 | + }, | ||
49 | + methods: { | ||
50 | + ...mapActions('Drone/page', { | ||
51 | + fetchPageData: 'fetchPageData', | ||
52 | + clearPageParams: 'clearPageParams', | ||
53 | + }), | ||
54 | + loadData() { | ||
55 | + this.parentLoading = true; | ||
56 | + this.fetchPageData(this.getPageParams).finally(() => { | ||
57 | + this.parentLoading = false; | ||
58 | + }); | ||
59 | + }, | ||
60 | + }, | ||
61 | +}; | ||
62 | +</script> | ||
63 | + | ||
64 | +<style scoped lang="scss"></style> |
Frontend/src/pages/database/log/index.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <log-search-filter @loadData="loadData"/> | ||
4 | + | ||
5 | + <div class="page-main"> | ||
6 | + <log-table :childLoading="parentLoading" @loadData="loadData"/> | ||
7 | + </div> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { mapActions, mapGetters } from 'vuex'; | ||
13 | +import LogSearchFilter from '@/components/Database/Log/SearchFilter'; | ||
14 | +import LogTable from '@/components/Database/Log/table'; | ||
15 | + | ||
16 | +export default { | ||
17 | + head() { | ||
18 | + return { | ||
19 | + title: 'Database-Log', | ||
20 | + meta: [ | ||
21 | + { | ||
22 | + hid: 'database', | ||
23 | + name: 'Descriptions', | ||
24 | + content: 'db-Content', | ||
25 | + }, | ||
26 | + ], | ||
27 | + }; | ||
28 | + }, | ||
29 | + components: { | ||
30 | + LogTable, | ||
31 | + LogSearchFilter, | ||
32 | + }, | ||
33 | + data() { | ||
34 | + return { | ||
35 | + parentLoading: false, | ||
36 | + }; | ||
37 | + }, | ||
38 | + computed: { | ||
39 | + ...mapGetters('Log/page', { | ||
40 | + getPageParams: 'getPageParams', | ||
41 | + }), | ||
42 | + }, | ||
43 | + created() { | ||
44 | + this.loadData(); | ||
45 | + }, | ||
46 | + beforeDestroy() { | ||
47 | + this.clearPageParams(); | ||
48 | + }, | ||
49 | + methods: { | ||
50 | + ...mapActions('Log/page', { | ||
51 | + fetchPageData: 'fetchPageData', | ||
52 | + clearPageParams: 'clearPageParams', | ||
53 | + }), | ||
54 | + loadData() { | ||
55 | + this.parentLoading = true; | ||
56 | + this.fetchPageData(this.getPageParams).finally(() => { | ||
57 | + this.parentLoading = false; | ||
58 | + }); | ||
59 | + }, | ||
60 | + }, | ||
61 | +}; | ||
62 | +</script> | ||
63 | + | ||
64 | +<style scoped lang="scss"></style> |
Frontend/src/pages/database/schedule/_id.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <database-detail-header /> | ||
4 | + <div class="page-main-without-header "> | ||
5 | + <div class="table-title">Log</div> | ||
6 | + <database-table :childLoading="parentLoading" @loadData="loadData" /> | ||
7 | + </div> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import DatabaseDetailHeader from '@/components/Database/Schedule/Detail/header'; | ||
13 | +import DatabaseTable from '@/components/Database/Log/table'; | ||
14 | +import {mapActions, mapGetters} from 'vuex'; | ||
15 | + | ||
16 | +export default { | ||
17 | + components: { | ||
18 | + DatabaseDetailHeader, | ||
19 | + DatabaseTable, | ||
20 | + }, | ||
21 | + async asyncData({params, store}) { | ||
22 | + const {id} = params; | ||
23 | + await store.dispatch('Schedule/detail/fetchDetailData', id); | ||
24 | + return {id}; | ||
25 | + }, | ||
26 | + data() { | ||
27 | + return { | ||
28 | + parentLoading: false, | ||
29 | + searchParams: {}, | ||
30 | + }; | ||
31 | + }, | ||
32 | + computed: { | ||
33 | + ...mapGetters('Schedule/detail', { | ||
34 | + getScheduleDetailData: 'getDetailData', | ||
35 | + }), | ||
36 | + ...mapGetters('Log/page', { | ||
37 | + getPageParams: 'getPageParams', | ||
38 | + }), | ||
39 | + }, | ||
40 | + watch: {}, | ||
41 | + created() { | ||
42 | + this.searchParams = JSON.parse(JSON.stringify(this.getPageParams)); | ||
43 | + this.searchParams.scheduleId = this.getScheduleDetailData.id; | ||
44 | + this.setPageParams(this.searchParams); | ||
45 | + this.loadData(); | ||
46 | + }, | ||
47 | + beforeDestroy() { | ||
48 | + this.clearPageParams(); | ||
49 | + }, | ||
50 | + methods: { | ||
51 | + ...mapActions('Log/page', { | ||
52 | + fetchPageData: 'fetchPageData', | ||
53 | + clearPageParams: 'clearPageParams', | ||
54 | + setPageParams: 'setPageParams', | ||
55 | + }), | ||
56 | + loadData() { | ||
57 | + this.parentLoading = true; | ||
58 | + | ||
59 | + this.fetchPageData(this.getPageParams).finally(() => { | ||
60 | + this.parentLoading = false; | ||
61 | + }); | ||
62 | + }, | ||
63 | + }, | ||
64 | +}; | ||
65 | +</script> | ||
66 | + | ||
67 | +<style scoped lang="scss"> | ||
68 | +.table-title { | ||
69 | + font-size: 30px; | ||
70 | +} | ||
71 | +</style> |
1 | +<template> | ||
2 | + <div> | ||
3 | + <database-search-filter @loadData="loadData" /> | ||
4 | + <div class="page-main"> | ||
5 | + <database-table :childLoading="parentLoading" @loadData="loadData" /> | ||
6 | + </div> | ||
7 | + </div> | ||
8 | +</template> | ||
9 | + | ||
10 | +<script> | ||
11 | +import {mapActions, mapGetters} from 'vuex'; | ||
12 | +import DatabaseSearchFilter from '@/components/Database/Schedule/searchFilter'; | ||
13 | +import DatabaseTable from '@/components/Database/Schedule/table'; | ||
14 | + | ||
15 | +export default { | ||
16 | + head() { | ||
17 | + return { | ||
18 | + title: 'Database', | ||
19 | + meta: [ | ||
20 | + { | ||
21 | + hid: 'database', | ||
22 | + name: 'Descriptions', | ||
23 | + content: 'db-Content', | ||
24 | + }, | ||
25 | + ], | ||
26 | + }; | ||
27 | + }, | ||
28 | + components: { | ||
29 | + DatabaseTable, | ||
30 | + DatabaseSearchFilter, | ||
31 | + }, | ||
32 | + data() { | ||
33 | + return { | ||
34 | + parentLoading: false, | ||
35 | + }; | ||
36 | + }, | ||
37 | + computed: { | ||
38 | + ...mapGetters('Schedule/page', { | ||
39 | + getPageParams: 'getPageParams', | ||
40 | + }), | ||
41 | + }, | ||
42 | + created() { | ||
43 | + this.loadData(); | ||
44 | + }, | ||
45 | + beforeDestroy() { | ||
46 | + this.clearPageParams(); | ||
47 | + }, | ||
48 | + methods: { | ||
49 | + ...mapActions('Schedule/page', { | ||
50 | + fetchPageData: 'fetchPageData', | ||
51 | + clearPageParams: 'clearPageParams', | ||
52 | + }), | ||
53 | + loadData() { | ||
54 | + this.parentLoading = true; | ||
55 | + this.fetchPageData(this.getPageParams).finally(() => { | ||
56 | + this.parentLoading = false; | ||
57 | + }); | ||
58 | + }, | ||
59 | + }, | ||
60 | +}; | ||
61 | +</script> | ||
62 | + | ||
63 | +<style scoped lang="scss"></style> |
Frontend/src/pages/index.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <main-header/> | ||
4 | + <div class="page-main"> | ||
5 | + <div id="map-wrap"> | ||
6 | + <client-only> | ||
7 | + <div class="mapBox"> | ||
8 | + <l-map | ||
9 | + ref="map" | ||
10 | + class="map" | ||
11 | + :center="[currentLatitude, currentLongitude]" | ||
12 | + :options="{zoomControl: false}" | ||
13 | + :zoom="zoom" | ||
14 | + :max-zoom="18" | ||
15 | + :min-zoom="8" | ||
16 | + @update:zoom="zoomUpdate" | ||
17 | + @update:bounds="boundsUpdate" | ||
18 | + @click="clickMap" | ||
19 | + > | ||
20 | + <l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></l-tile-layer> | ||
21 | + | ||
22 | + <l-control position="bottomright"> | ||
23 | + <img | ||
24 | + @click="clickGpsBtn" | ||
25 | + class="gpsBtn" | ||
26 | + :src="require('@/static/img/gps.png')" | ||
27 | + /> | ||
28 | + </l-control> | ||
29 | + | ||
30 | + <l-control-zoom position="bottomright"></l-control-zoom> | ||
31 | + <template v-for="(drone) in getDroneLogs"> | ||
32 | + <drone | ||
33 | + :key="drone.id" | ||
34 | + :latitude="drone.latitude" | ||
35 | + :longitude="drone.longitude" | ||
36 | + :icon="icon" | ||
37 | + @clickDrone=clickDrone(drone.droneId) | ||
38 | + /> | ||
39 | + </template> | ||
40 | + | ||
41 | + <l-polyline :lat-lngs="selectedDroneRoute" :color="'green'"></l-polyline> | ||
42 | + | ||
43 | + </l-map> | ||
44 | + <DroneDetail | ||
45 | + class="drone-detail" | ||
46 | + v-if="showDroneDetail" | ||
47 | + /> | ||
48 | + <SearchBtnBox | ||
49 | + class="search-box" | ||
50 | + @changeFilterMode="e => filterMode = e" | ||
51 | + @changeSearchMode="e => searchMode = e" | ||
52 | + /> | ||
53 | + <SearchFeatureBox | ||
54 | + v-if="searchMode" | ||
55 | + @clickClose="searchMode = false" | ||
56 | + @focusChange="(lat, lng) => focusChange(lat, lng)" | ||
57 | + @clickDrone="(id) => clickDrone(id)" | ||
58 | + class="search-box" | ||
59 | + /> | ||
60 | + <BottomToolBox | ||
61 | + class="bottom-tool-box" | ||
62 | + @changeFilterMode="e => filterMode = e" | ||
63 | + @changeSearchMode="e => searchMode = e" | ||
64 | + /> | ||
65 | + <FilterFeatureBox | ||
66 | + v-if="filterMode" | ||
67 | + @clickClose="filterMode = false" | ||
68 | + class="filter-feature-box" | ||
69 | + /> | ||
70 | + | ||
71 | + </div> | ||
72 | + </client-only> | ||
73 | + </div> | ||
74 | + </div> | ||
75 | + </div> | ||
76 | +</template> | ||
77 | + | ||
78 | +<script> | ||
79 | +/* eslint-disable no-underscore-dangle */ | ||
80 | +import { mapActions, mapGetters } from 'vuex'; | ||
81 | +import MainHeader from '@/components/Main/header'; | ||
82 | +import Drone from '@/components/Main/drone'; | ||
83 | +import DroneDetail from '@/components/Main/Box/DetailBox/detailBox'; | ||
84 | +import SearchBtnBox from '@/components/Main/Box/SearchBox/searchBtnBox'; | ||
85 | +import SearchFeatureBox from '@/components/Main/Box/SearchBox/searchFeatureBox'; | ||
86 | +import BottomToolBox from '@/components/Main/Box/BottomToolBox/bottomToolBox'; | ||
87 | +import FilterFeatureBox from '@/components/Main/Box/FilterBox/filterFeatureBox'; | ||
88 | + | ||
89 | +import droneImg from '@/static/img/drone.svg'; | ||
90 | + | ||
91 | +export default { | ||
92 | + components: { | ||
93 | + MainHeader, Drone, DroneDetail, SearchBtnBox, SearchFeatureBox, BottomToolBox, FilterFeatureBox, | ||
94 | + }, | ||
95 | + head() { | ||
96 | + return { | ||
97 | + title: 'DroneWeb', | ||
98 | + meta: [ | ||
99 | + { | ||
100 | + hid: 'database', | ||
101 | + name: 'Descriptions', | ||
102 | + content: 'DroneWeb-Content', | ||
103 | + }, | ||
104 | + ], | ||
105 | + }; | ||
106 | + }, | ||
107 | + data() { | ||
108 | + return { | ||
109 | + center: [0, 0], | ||
110 | + rotation: 0, | ||
111 | + geolocPosition: undefined, | ||
112 | + map: null, | ||
113 | + selectedDroneRoute: [], | ||
114 | + selectedDroneData: {}, | ||
115 | + selectedDroneId: null, | ||
116 | + currentLatitude: 37.2430125, | ||
117 | + currentLongitude: 127.0811054, | ||
118 | + showDroneDetail: false, | ||
119 | + filterMode: false, | ||
120 | + searchMode: false, | ||
121 | + icon: droneImg, | ||
122 | + | ||
123 | + zoom: 15, | ||
124 | + socketCheckInterval: null, | ||
125 | + }; | ||
126 | + }, | ||
127 | + computed: { | ||
128 | + ...mapGetters('Drone/drone', { | ||
129 | + getDetailData: 'getDetailData', | ||
130 | + getDroneLogs: 'getDroneLogs', | ||
131 | + getSelectedLogList: 'getSelectedLogList', | ||
132 | + getSelectedLastLog: 'getSelectedLastLog', | ||
133 | + getSelectedDroneId: 'getSelectedDroneId', | ||
134 | + }), | ||
135 | + }, | ||
136 | + watch: { | ||
137 | + getDroneLogs: { | ||
138 | + deep: true, | ||
139 | + handler(newVal) { | ||
140 | + // console.log('드론 소켓 로그 getDroneLogs', newVal); | ||
141 | + }, | ||
142 | + }, | ||
143 | + getSelectedLastLog: { | ||
144 | + deep: true, | ||
145 | + handler(newVal) { | ||
146 | + this.selectedDroneRoute.push([newVal.latitude, newVal.longitude]); | ||
147 | + // console.log('page 속도 등 계산한 정보 selectedDroneLog', newVal); | ||
148 | + }, | ||
149 | + }, | ||
150 | + }, | ||
151 | + created() { | ||
152 | + if (process.client) { | ||
153 | + // Websocket 연결 | ||
154 | + this.connect(); | ||
155 | + this.socketCheckInterval = setInterval(() => { | ||
156 | + console.log('socketState', window.clientSocket.readyState); | ||
157 | + this.connect(); | ||
158 | + }, 30000); | ||
159 | + setTimeout(() => { | ||
160 | + this.zoom = 16; | ||
161 | + }, 2000); | ||
162 | + | ||
163 | + navigator.geolocation.getCurrentPosition((position) => { | ||
164 | + this.currentLatitude = position.coords.latitude; | ||
165 | + this.currentLongitude = position.coords.longitude; | ||
166 | + }, (e) => { | ||
167 | + console.log('err', e); | ||
168 | + }, | ||
169 | + { enableHighAccuracy: true, maximumAge: 0, timeout: 2000 }); | ||
170 | + } | ||
171 | + }, | ||
172 | + methods: { | ||
173 | + ...mapActions('Drone/drone', { | ||
174 | + fetchDetailInfo: 'fetchDetailInfo', | ||
175 | + setSelectedDroneId: 'setSelectedDroneId', | ||
176 | + }), | ||
177 | + ...mapActions('Drone/Map', { | ||
178 | + setBoundary: 'setBoundary', | ||
179 | + setZoomLevel: 'setZoomLevel', | ||
180 | + }), | ||
181 | + zoomUpdate(zoom) { | ||
182 | + this.setZoomLevel(zoom); | ||
183 | + }, | ||
184 | + boundsUpdate(bounds) { | ||
185 | + this.setBoundary(bounds); | ||
186 | + }, | ||
187 | + focusChange(lat, lng) { | ||
188 | + this.map = this.$refs.map.mapObject.panTo([lat, lng]); | ||
189 | + }, | ||
190 | + clickGpsBtn() { | ||
191 | + if (process.client) { | ||
192 | + navigator.geolocation.getCurrentPosition((position) => { | ||
193 | + this.currentLatitude = position.coords.latitude; | ||
194 | + this.currentLongitude = position.coords.longitude; | ||
195 | + this.focusChange(this.currentLatitude, this.currentLongitude); | ||
196 | + }, (e) => { | ||
197 | + console.log('err', e); | ||
198 | + }, { enableHighAccuracy: true, maximumAge: 0, timeout: 2000 }); | ||
199 | + } | ||
200 | + }, | ||
201 | + clickDrone(droneId) { | ||
202 | + if (this.getSelectedDroneId === droneId) return; | ||
203 | + this.selectedDroneRoute = []; | ||
204 | + this.setSelectedDroneId(droneId); | ||
205 | + this.accumulatedDistance = 0; // 미터 | ||
206 | + | ||
207 | + this.fetchDetailInfo(this.getSelectedDroneId) | ||
208 | + .then((r) => { | ||
209 | + console.log('선택한 드론', r); | ||
210 | + r.droneLogs.forEach((droneLog) => { | ||
211 | + this.selectedDroneRoute.push([droneLog.latitude, droneLog.longitude]); | ||
212 | + }); | ||
213 | + this.selectedDroneData = this.getDetailData; | ||
214 | + this.showDroneDetail = true; | ||
215 | + }); | ||
216 | + }, | ||
217 | + clickMap() { | ||
218 | + this.showDroneDetail = false; | ||
219 | + this.selectedDroneRoute = []; | ||
220 | + this.setSelectedDroneId(null); | ||
221 | + }, | ||
222 | + clickSearchBtn() { | ||
223 | + console.log('click btn'); | ||
224 | + }, | ||
225 | + clickFilterBtn() { | ||
226 | + console.log('click btn'); | ||
227 | + }, | ||
228 | + }, | ||
229 | +}; | ||
230 | +</script> | ||
231 | +<style lang="scss" scoped> | ||
232 | +.mapBox { | ||
233 | + height: calc(100vh - 100px); | ||
234 | + position: relative; | ||
235 | +} | ||
236 | + | ||
237 | +.map { | ||
238 | + z-index: 0; | ||
239 | +} | ||
240 | + | ||
241 | +.drone-detail { | ||
242 | + position: absolute; | ||
243 | + z-index: 10; | ||
244 | + top: 10px; | ||
245 | + left: 10px; | ||
246 | +} | ||
247 | + | ||
248 | +.tool-box { | ||
249 | + position: absolute; | ||
250 | + z-index: 10; | ||
251 | + top: 10px; | ||
252 | + right: 10px; | ||
253 | +} | ||
254 | + | ||
255 | +.gpsBtn { | ||
256 | + width: 35px; | ||
257 | + height: 35px; | ||
258 | + padding: 5px; | ||
259 | + border: 2px solid rgba(0, 0, 0, 0.2); | ||
260 | + border-radius: 4px; | ||
261 | + background: white; | ||
262 | + cursor: pointer; | ||
263 | +} | ||
264 | + | ||
265 | +.gpsBtn:hover { | ||
266 | + background-color: #f4f4f4; | ||
267 | +} | ||
268 | + | ||
269 | +</style> |
Frontend/src/pages/indexVL.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <client-only> | ||
4 | + <vl-map :load-tiles-while-animating="true" :load-tiles-while-interacting="true" | ||
5 | + data-projection="EPSG:4326" style="height: 400px"> | ||
6 | + <vl-view :min-zoom="2" :zoom.sync="zoom" :center.sync="center" :rotation.sync="rotation"></vl-view> | ||
7 | + | ||
8 | + <vl-geoloc @update:position="geolocPosition = $event"> | ||
9 | + <template slot-scope="geoloc"> | ||
10 | + <!-- <vl-feature v-if="geoloc.position" id="position-feature"> | ||
11 | + {{ geoloc.position }} | ||
12 | + <vl-geom-point :coordinates="geoloc.position"></vl-geom-point> | ||
13 | + <vl-style-box> | ||
14 | + <vl-style-icon src="@/assets/images/drone.png" :scale="0.1" :anchor="[0.5, 1]"></vl-style-icon> | ||
15 | + </vl-style-box> | ||
16 | + </vl-feature> --> | ||
17 | + | ||
18 | + <vl-feature> | ||
19 | + <vl-geom-point :coordinates="[126.986606,37.310334]"></vl-geom-point> | ||
20 | + <vl-style-box> | ||
21 | + <vl-style-icon src="@/assets/images/drone.png" :scale="0.2" :anchor="[0.5, 1]"></vl-style-icon> | ||
22 | + </vl-style-box> | ||
23 | + </vl-feature> | ||
24 | + | ||
25 | + <vl-feature v-for="(drone) in droneLogs" :key="drone.id"> | ||
26 | + <vl-geom-point :coordinates="[drone.longitude,drone.latitude]"></vl-geom-point> | ||
27 | + <vl-style-box> | ||
28 | + <vl-style-icon src="@/assets/images/drone.png" :scale="0.1" :anchor="[0.5, 1]"></vl-style-icon> | ||
29 | + </vl-style-box> | ||
30 | + </vl-feature> | ||
31 | + </template> | ||
32 | + </vl-geoloc> | ||
33 | + | ||
34 | + <vl-layer-tile id="osm"> | ||
35 | + <vl-source-osm></vl-source-osm> | ||
36 | + </vl-layer-tile> | ||
37 | + </vl-map> | ||
38 | + <div style="padding: 20px"> | ||
39 | + Zoom: {{ zoom }}<br> | ||
40 | + Center: {{ center }}<br> | ||
41 | + Rotation: {{ rotation }}<br> | ||
42 | + My geolocation: {{ geolocPosition }} | ||
43 | + </div> | ||
44 | + <a-button @click="connect">connect</a-button> | ||
45 | +<!-- <a-button @click="connectMultiClient">connectMultiClient</a-button>--> | ||
46 | + <a-button @click="disconnect">disconnect</a-button> | ||
47 | + </client-only> | ||
48 | + </div> | ||
49 | +</template> | ||
50 | + | ||
51 | +<script> | ||
52 | +import { mapActions, mapGetters } from 'vuex'; | ||
53 | +import MainHeader from '@/components/Main/header'; | ||
54 | +import Drone from '@/components/Main/drone'; | ||
55 | +import DroneDetail from '@/components/Main/Box/DetailBox/detailBox'; | ||
56 | + | ||
57 | +export default { | ||
58 | + components: { | ||
59 | + MainHeader, Drone, DroneDetail, ToolBox, | ||
60 | + }, | ||
61 | + head() { | ||
62 | + return { | ||
63 | + title: 'DroneWeb', | ||
64 | + meta: [ | ||
65 | + { | ||
66 | + hid: 'database', | ||
67 | + name: 'Descriptions', | ||
68 | + content: 'DroneWeb-Content', | ||
69 | + }, | ||
70 | + ], | ||
71 | + }; | ||
72 | + }, | ||
73 | + data() { | ||
74 | + return { | ||
75 | + zoom: 2, | ||
76 | + center: [0, 0], | ||
77 | + rotation: 0, | ||
78 | + geolocPosition: undefined, | ||
79 | + map: null, | ||
80 | + drones: [], | ||
81 | + testDrone: { | ||
82 | + latitude: 37.2430125, | ||
83 | + longitude: 127.0811054, | ||
84 | + polyline: { | ||
85 | + latlngs: [], | ||
86 | + color: 'green', | ||
87 | + }, | ||
88 | + }, | ||
89 | + currentLatitude: 37.2430125, | ||
90 | + currentLongitude: 127.0811054, | ||
91 | + showDroneDetail: false, | ||
92 | + }; | ||
93 | + }, | ||
94 | + computed: { | ||
95 | + ...mapGetters('Drone/drone', { | ||
96 | + getDetailData: 'getDetailData', | ||
97 | + }), | ||
98 | + }, | ||
99 | + watch: { | ||
100 | + droneLogs: { | ||
101 | + deep: true, | ||
102 | + handler(newVal) { | ||
103 | + console.log('드론 소켓 로그 droneLogs', newVal); | ||
104 | + }, | ||
105 | + }, | ||
106 | + }, | ||
107 | + created() { | ||
108 | + /** | ||
109 | + * getDetailData와 fetchDetailInfo로 상세 정보 조회 | ||
110 | + * 속도, 이동거리, 방향은 모두 계산해서 출력함. | ||
111 | + */ | ||
112 | + if (process.browser) { | ||
113 | + navigator.geolocation.getCurrentPosition((position) => { | ||
114 | + this.currentLatitude = position.coords.latitude; | ||
115 | + this.currentLongitude = position.coords.longitude; | ||
116 | + }, () => { | ||
117 | + }, { enableHighAccuracy: true, maximumAge: 0 }); | ||
118 | + } | ||
119 | + }, | ||
120 | + methods: { | ||
121 | + ...mapActions('Drone/drone', { | ||
122 | + fetchDetailInfo: 'fetchDetailInfo', | ||
123 | + clearAccumulatedDistance: 'clearAccumulatedDistance', | ||
124 | + setSelectedDroneId: 'setSelectedDroneId', | ||
125 | + }), | ||
126 | + clickGpsBtn() { | ||
127 | + if (process.browser) { | ||
128 | + navigator.geolocation.getCurrentPosition((position) => { | ||
129 | + this.currentLatitude = position.coords.latitude; | ||
130 | + this.currentLongitude = position.coords.longitude; | ||
131 | + this.map = this.$refs.map.mapObject.panTo([this.currentLatitude, this.currentLongitude]); | ||
132 | + }, () => { | ||
133 | + }, { enableHighAccuracy: true, maximumAge: 0 }); | ||
134 | + } | ||
135 | + }, | ||
136 | + clickDrone() { | ||
137 | + /** | ||
138 | + * id가 1일때 가정하고 만듦. 추후에 수정 필. | ||
139 | + * distance 거리 단위는 미터 | ||
140 | + */ | ||
141 | + this.setSelectedDroneId(1); | ||
142 | + this.clearAccumulatedDistance(); | ||
143 | + this.fetchDetailInfo(1) | ||
144 | + .then((r) => { | ||
145 | + this.showDroneDetail = true; | ||
146 | + console.log('선택한 드론', r); | ||
147 | + }); | ||
148 | + console.log('drone click'); | ||
149 | + }, | ||
150 | + clickMap() { | ||
151 | + console.log('click Map'); | ||
152 | + this.showDroneDetail = false; | ||
153 | + }, | ||
154 | + | ||
155 | + clickSearchBtn() { | ||
156 | + console.log('click btn'); | ||
157 | + }, | ||
158 | + clickFilterBtn() { | ||
159 | + console.log('click btn'); | ||
160 | + }, | ||
161 | + }, | ||
162 | +}; | ||
163 | +</script> | ||
164 | +<style lang="scss" scoped> | ||
165 | +.map { | ||
166 | + z-index: 0; | ||
167 | +} | ||
168 | + | ||
169 | +.gpsBtn { | ||
170 | + width: 35px; | ||
171 | + height: 35px; | ||
172 | + padding: 5px; | ||
173 | + border: 2px solid rgba(0, 0, 0, 0.2); | ||
174 | + border-radius: 4px; | ||
175 | + background: white; | ||
176 | + cursor: pointer; | ||
177 | +} | ||
178 | + | ||
179 | +.gpsBtn:hover { | ||
180 | + background-color: #f4f4f4; | ||
181 | +} | ||
182 | + | ||
183 | +</style> |
Frontend/src/pages/websocket/index.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <analytics-header/> | ||
4 | + <div class="chart-div"> | ||
5 | + <live-log-chart :chart-data="liveDroneData"/> | ||
6 | + </div> | ||
7 | + | ||
8 | + <div class="page-main"> | ||
9 | + <div> | ||
10 | + 웹 소켓 테스트 | ||
11 | + </div> | ||
12 | + <a-input v-model="socketClientCnt"/> | ||
13 | + <a-button @click="multConnect">multConnect</a-button> | ||
14 | + <a-button @click="connect">connect</a-button> | ||
15 | + <a-button @click="sendMessage">sendMessage</a-button> | ||
16 | + <a-button @click="disconnectMult">disconnectMult</a-button> | ||
17 | + <a-button @click="disconnectSol">disconnectSol</a-button> | ||
18 | + <div> | ||
19 | + <a-button @click="multConnectDynamic">multConnectDynamic</a-button> | ||
20 | + </div> | ||
21 | + </div> | ||
22 | + </div> | ||
23 | +</template> | ||
24 | + | ||
25 | +<script> | ||
26 | +import AnalyticsHeader from '@/components/Analytics/header'; | ||
27 | +import LiveLogChart from '@/components/Analytics/Chart/liveLogChart'; | ||
28 | +import { mapActions, mapGetters } from 'vuex'; | ||
29 | +import calcGeoLocation from '@/utils/Mixins/Component/calcGeolocation'; | ||
30 | + | ||
31 | +export default { | ||
32 | + components: { | ||
33 | + LiveLogChart, | ||
34 | + AnalyticsHeader, | ||
35 | + }, | ||
36 | + mixins: [calcGeoLocation], | ||
37 | + data() { | ||
38 | + return { | ||
39 | + socketClientCnt: 10, | ||
40 | + liveDroneData: [ | ||
41 | + { | ||
42 | + name: '드론 기체 수', | ||
43 | + data: [], | ||
44 | + }, | ||
45 | + { | ||
46 | + name: '활성화된 드론 수', | ||
47 | + data: [], | ||
48 | + }, | ||
49 | + ], | ||
50 | + angle: {}, | ||
51 | + velocity: {}, | ||
52 | + }; | ||
53 | + }, | ||
54 | + computed: { | ||
55 | + ...mapGetters('GeoLocation', { | ||
56 | + getCoord: 'getCoord', | ||
57 | + getAngle: 'getAngle', | ||
58 | + getVelocity: 'getVelocity', | ||
59 | + }), | ||
60 | + ranNum() { | ||
61 | + return (min, max) => parseInt((Math.random() * (max - min) + min), 10); | ||
62 | + }, | ||
63 | + }, | ||
64 | + created() { | ||
65 | + }, | ||
66 | + mounted() { | ||
67 | + }, | ||
68 | + methods: { | ||
69 | + ...mapActions('GeoLocation', { | ||
70 | + saveCoord: 'saveCoord', | ||
71 | + }), | ||
72 | + connect() { | ||
73 | + // window.webSocketSol = new WebSocket('ws://localhost:20202'); | ||
74 | + window.webSocketSol = new WebSocket('ws://14.33.35.148:20202'); | ||
75 | + window.webSocketSol.onopen = () => { | ||
76 | + console.log('socket on open'); | ||
77 | + }; | ||
78 | + window.webSocketSol.onerror = () => { | ||
79 | + console.log('socket on open'); | ||
80 | + }; | ||
81 | + window.webSocketSol.onmessage = (data) => { | ||
82 | + // console.log(data); | ||
83 | + const logData = JSON.parse(data.data); | ||
84 | + // this.saveCoord(logData); | ||
85 | + console.log(`message ${this.$dayjs().format('HH:mm:ss')} : `, logData); | ||
86 | + }; | ||
87 | + window.webSocketSol.onclose = () => { | ||
88 | + console.log('socket on open'); | ||
89 | + }; | ||
90 | + }, | ||
91 | + multConnect() { | ||
92 | + for (let i = 0; i < 10; i += 1) { | ||
93 | + if (window[`wsSocket${i}`] == null || window[`wsSocket${i}`].readyState === 3) { | ||
94 | + // window[`wsSocket${i}`] = new WebSocket(`ws://localhost:20203/${i + 1}`); | ||
95 | + window[`wsSocket${i}`] = new WebSocket(`ws://14.33.35.148:20203/${i + 1}`); | ||
96 | + window[`wsSocket${i}`].onopen = () => { | ||
97 | + console.log(`socket${i} on open`); | ||
98 | + }; | ||
99 | + window[`wsSocket${i}`].onerror = () => { | ||
100 | + console.log(`socket${i} on error`); | ||
101 | + }; | ||
102 | + window[`wsSocket${i}`].onmessage = (data) => { | ||
103 | + // console.log(data); | ||
104 | + const logData = JSON.parse(data.data); | ||
105 | + // this.saveCoord(logData); | ||
106 | + console.log(`message${i} ${this.$dayjs().format('HH:mm:ss')} : `, logData); | ||
107 | + }; | ||
108 | + window[`wsSocket${i}`].onclose = () => { | ||
109 | + console.log(`socket${i} on close`); | ||
110 | + }; | ||
111 | + } | ||
112 | + } | ||
113 | + }, | ||
114 | + sendMessage() { | ||
115 | + window.wsSocket.send('client sent message'); | ||
116 | + }, | ||
117 | + multConnectDynamic() { | ||
118 | + for (let i = 0; i < this.socketClientCnt; i += 1) { | ||
119 | + if (window[`wsSocket${i}`] == null || window[`wsSocket${i}`].readyState === 3) { | ||
120 | + // window[`wsSocket${i}`] = new WebSocket(`ws://localhost:20203/${i + 1}`); | ||
121 | + window[`wsSocket${i}`] = new WebSocket(`ws://14.33.35.148:20204/${i + 1}`); | ||
122 | + window[`wsSocket${i}`].onopen = () => { | ||
123 | + console.log(`socket${i} on open`); | ||
124 | + }; | ||
125 | + window[`wsSocket${i}`].onerror = () => { | ||
126 | + console.log(`socket${i} on error`); | ||
127 | + }; | ||
128 | + window[`wsSocket${i}`].onmessage = (data) => { | ||
129 | + // console.log(data); | ||
130 | + const logData = JSON.parse(data.data); | ||
131 | + // this.saveCoord(logData); | ||
132 | + console.log(`message${i} ${this.$dayjs().format('HH:mm:ss')} : `, logData); | ||
133 | + }; | ||
134 | + window[`wsSocket${i}`].onclose = () => { | ||
135 | + console.log(`socket${i} on close`); | ||
136 | + }; | ||
137 | + } | ||
138 | + } | ||
139 | + }, | ||
140 | + disconnectMult() { | ||
141 | + for (let i = 0; i < this.socketClientCnt; i += 1) { | ||
142 | + if (window[`wsSocket${i}`].readyState === 1) { | ||
143 | + window[`wsSocket${i}`].close(); | ||
144 | + window[`wsSocket${i}`] = null; | ||
145 | + } | ||
146 | + } | ||
147 | + }, | ||
148 | + disconnectMult2() { | ||
149 | + for (let i = 0; i < this.socketClientCnt; i += 1) { | ||
150 | + if (window[`wsSocket${i}`].readyState === 1) { | ||
151 | + window[`wsSocket${i}`].close(); | ||
152 | + window[`wsSocket${i}`] = null; | ||
153 | + } | ||
154 | + } | ||
155 | + }, | ||
156 | + disconnectSol() { | ||
157 | + if (window.webSocketSol.readyState === 1) { | ||
158 | + window.webSocketSol.close(); | ||
159 | + window.webSocketSol = null; | ||
160 | + } | ||
161 | + }, | ||
162 | + }, | ||
163 | +}; | ||
164 | +</script> | ||
165 | + | ||
166 | +<style scoped lang="scss"> | ||
167 | +@import '@/assets/styles/mixins.scss'; | ||
168 | + | ||
169 | +.pie-chart-div { | ||
170 | + margin-top: 20px; | ||
171 | + height: 440px; | ||
172 | + width: calc(50% - 10px); | ||
173 | + padding: 20px 20px 20px 20px; | ||
174 | + background-color: white; | ||
175 | + border-radius: 6px; | ||
176 | + border: $gray-2 1px solid; | ||
177 | +} | ||
178 | + | ||
179 | +.chart-div { | ||
180 | + margin-top: 20px; | ||
181 | + height: 440px; | ||
182 | + padding: 20px 20px 20px 20px; | ||
183 | + background-color: white; | ||
184 | + border-radius: 6px; | ||
185 | + border: $gray-2 1px solid; | ||
186 | +} | ||
187 | +</style> |
Frontend/src/plugins/ApiClient/index.js
0 → 100644
1 | +/* eslint-disable func-names,no-param-reassign */ | ||
2 | +import store from 'store2'; | ||
3 | +import { Modal } from 'ant-design-vue'; | ||
4 | + | ||
5 | +export default function ({ $axios, redirect }) { | ||
6 | + $axios.onRequest((config) => { | ||
7 | + /* header AppVersion 체크 */ | ||
8 | + if (!store.get('appVersion')) { | ||
9 | + store.set('appVersion', config.headers.common.AppVersion); | ||
10 | + } else if (config.headers.common.AppVersion !== store.get('appVersion')) { | ||
11 | + store.set('appVersion', config.headers.common.AppVersion); | ||
12 | + Modal.info({ | ||
13 | + title: '새로운 콘텐츠가 감지되었습니다.', | ||
14 | + content: '확인을 눌러 새로고침 해주세요.', | ||
15 | + onOk: () => { | ||
16 | + window.location.reload(true); | ||
17 | + }, | ||
18 | + }); | ||
19 | + } | ||
20 | + return config; | ||
21 | + }); | ||
22 | + $axios.onResponse((response) => response.data); | ||
23 | + $axios.onError((error) => { | ||
24 | + if (error.response.status === 404) { | ||
25 | + redirect('/auth/404'); | ||
26 | + } else { | ||
27 | + Modal.error({ | ||
28 | + title: '서버와의 통신에 에러가 발생했습니다.', | ||
29 | + onOk: () => { | ||
30 | + redirect('/auth/500'); | ||
31 | + }, | ||
32 | + }); | ||
33 | + } | ||
34 | + }); | ||
35 | +} |
Frontend/src/plugins/Dayjs/index.js
0 → 100644
1 | +import Vue from 'vue'; | ||
2 | +import dayjs from './partials/implement'; | ||
3 | + | ||
4 | +const options = { | ||
5 | + // locale: process.env.VUE_APP_I18N_LOCALE || 'ko', | ||
6 | + locale: 'ko', | ||
7 | + localeObject: { | ||
8 | + name: 'ko', | ||
9 | + weekdays: ['월', '화', '수', '목', '금', '토', '일'], | ||
10 | + months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], | ||
11 | + formats: { | ||
12 | + L: 'YYYY-MM-DD', | ||
13 | + LL: 'YYYY-MM-DD(ddd)', | ||
14 | + LLL: 'YYYY-MM-DD HH:mm', | ||
15 | + LLLL: 'YYYY-MM-DD HH:mm:sss', | ||
16 | + }, | ||
17 | + }, | ||
18 | +}; | ||
19 | + | ||
20 | +Vue.use(dayjs, options); | ||
21 | +Vue.filter('dayjs', (value, format) => { | ||
22 | + if (value == null || format == null || value === 'Invalid Date') { | ||
23 | + return ''; | ||
24 | + } | ||
25 | + return dayjs.day(value).format(format); | ||
26 | +}); |
Frontend/src/plugins/README.md
0 → 100644
1 | +# PLUGINS | ||
2 | + | ||
3 | +**This directory is not required, you can delete it if you don't want to use it.** | ||
4 | + | ||
5 | +This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. | ||
6 | + | ||
7 | +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). |
Frontend/src/plugins/antDesign.js
0 → 100644
1 | +import Vue from 'vue'; | ||
2 | + | ||
3 | +import { | ||
4 | + Breadcrumb, Icon, PageHeader, Alert, Result, | ||
5 | + Button, Layout, Table, Radio, Dropdown, Menu, | ||
6 | + Input, Calendar, Form, Tooltip, Select, Switch, | ||
7 | + Spin, Checkbox, Tabs, Pagination, notification, | ||
8 | + DatePicker, TimePicker, Divider, Card, Avatar, | ||
9 | + Row, Col, Modal, ConfigProvider, Descriptions, Badge, | ||
10 | + List, Progress, Slider, AutoComplete, | ||
11 | +} from 'ant-design-vue'; | ||
12 | + | ||
13 | +Vue.use(notification); | ||
14 | +Vue.use(Badge); | ||
15 | +Vue.use(AutoComplete); | ||
16 | +Vue.use(List); | ||
17 | +Vue.use(PageHeader); | ||
18 | +Vue.use(Result); | ||
19 | +Vue.use(Alert); | ||
20 | +Vue.use(Modal); | ||
21 | +Vue.use(Icon); | ||
22 | +Vue.use(Divider); | ||
23 | +Vue.use(Row); | ||
24 | +Vue.use(Col); | ||
25 | +Vue.use(Card); | ||
26 | +Vue.use(Button); | ||
27 | +Vue.use(Breadcrumb); | ||
28 | +Vue.use(Layout); | ||
29 | +Vue.use(Table); | ||
30 | +Vue.use(Radio); | ||
31 | +Vue.use(Dropdown); | ||
32 | +Vue.use(Menu); | ||
33 | +Vue.use(Input); | ||
34 | +Vue.use(Calendar); | ||
35 | +Vue.use(Form); | ||
36 | +Vue.use(Tooltip); | ||
37 | +Vue.use(Select); | ||
38 | +Vue.use(Spin); | ||
39 | +Vue.use(Checkbox); | ||
40 | +Vue.use(Tabs); | ||
41 | +Vue.use(Pagination); | ||
42 | +Vue.use(Switch); | ||
43 | +Vue.use(DatePicker); | ||
44 | +Vue.use(TimePicker); | ||
45 | +Vue.use(ConfigProvider); | ||
46 | +Vue.use(Descriptions); | ||
47 | +Vue.use(Avatar); | ||
48 | +Vue.use(Progress); | ||
49 | +Vue.use(Slider); | ||
50 | + | ||
51 | +Vue.prototype.$notification = notification; | ||
52 | +Vue.prototype.$confirm = Modal.confirm; | ||
53 | +Vue.prototype.$info = Modal.info; | ||
54 | +Vue.prototype.$success = Modal.success; | ||
55 | +Vue.prototype.$error = Modal.error; | ||
56 | +Vue.prototype.$warning = Modal.warning; |
Frontend/src/plugins/client-only.client.js
0 → 100644
1 | +import './highcharts'; |
Frontend/src/plugins/globalMixins.js
0 → 100644
Frontend/src/plugins/highcharts.js
0 → 100644
1 | +import Vue from 'vue'; | ||
2 | +import Highcharts from 'highcharts'; | ||
3 | +import HighchartsVue from 'highcharts-vue'; | ||
4 | +import HighchartsBoost from 'highcharts/modules/boost'; | ||
5 | +import HighchartsBoostCanvas from 'highcharts/modules/boost-canvas'; | ||
6 | +// import HighchartsExporting from 'highcharts/modules/exporting'; | ||
7 | +// import HighchartsOfflineExporting from 'highcharts/modules/offline-exporting'; | ||
8 | +// import HighchartsExportData from 'highcharts/modules/export-data'; | ||
9 | +import HighchartsMore from 'highcharts/highcharts-more'; | ||
10 | + | ||
11 | +HighchartsBoost(Highcharts); | ||
12 | +HighchartsBoostCanvas(Highcharts); | ||
13 | +// HighchartsExporting(Highcharts); | ||
14 | +// HighchartsOfflineExporting(Highcharts); | ||
15 | +// HighchartsExportData(Highcharts); | ||
16 | +HighchartsMore(Highcharts); | ||
17 | + | ||
18 | +const options = { | ||
19 | + time: { | ||
20 | + timezoneOffset: -540, | ||
21 | + }, | ||
22 | + colors: [ | ||
23 | + '#C08B9C', '#F5D1B7', '#E99C65', '#7A9A82', | ||
24 | + '#28B3BE', '#A6A6A6', '#F4C96B', '#C04176', | ||
25 | + '#8885A4', '#454C88', '#94A4BE', '#E9DF84', | ||
26 | + '#7EAFB2', | ||
27 | + ], | ||
28 | + credits: { | ||
29 | + enabled: false, | ||
30 | + style: { | ||
31 | + color: '#FFFFFF', | ||
32 | + }, | ||
33 | + }, | ||
34 | + chart: { | ||
35 | + style: { | ||
36 | + fontFamily: 'Noto Sans Kr, sans-serif', | ||
37 | + fontWeight: 500, | ||
38 | + }, | ||
39 | + animation: true, | ||
40 | + }, | ||
41 | + title: { | ||
42 | + style: { | ||
43 | + color: '#2a274d', | ||
44 | + }, | ||
45 | + }, | ||
46 | + plotOptions: { | ||
47 | + column: { | ||
48 | + pointPadding: 0.2, | ||
49 | + borderWidth: 0, | ||
50 | + }, | ||
51 | + }, | ||
52 | + xAxis: { | ||
53 | + labels: { | ||
54 | + style: { | ||
55 | + color: '#4f4f7a', | ||
56 | + fontSize: '14px', | ||
57 | + }, | ||
58 | + shared: true, | ||
59 | + useHTML: true, | ||
60 | + }, | ||
61 | + }, | ||
62 | + yAxis: { | ||
63 | + labels: { | ||
64 | + style: { | ||
65 | + color: '#4f4f7a', | ||
66 | + fontSize: '14px', | ||
67 | + }, | ||
68 | + shared: true, | ||
69 | + useHTML: true, | ||
70 | + }, | ||
71 | + }, | ||
72 | + labels: { | ||
73 | + style: { | ||
74 | + color: '#4f4f7a', | ||
75 | + }, | ||
76 | + shared: true, | ||
77 | + useHTML: true, | ||
78 | + }, | ||
79 | + tooltip: { | ||
80 | + headerFormat: '<span style="font-size:16px;">{point.key}</span><table>', | ||
81 | + pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}</td>' | ||
82 | + + '<td style="padding:0"><b>: {point.y}</b></td></tr>', | ||
83 | + footerFormat: '</table>', | ||
84 | + shared: true, | ||
85 | + useHTML: true, | ||
86 | + style: { | ||
87 | + fontSize: '14px', | ||
88 | + }, | ||
89 | + borderWidth: 2, | ||
90 | + borderRadius: 6, | ||
91 | + }, | ||
92 | + legend: { | ||
93 | + useHTML: true, | ||
94 | + enabled: true, | ||
95 | + itemStyle: { | ||
96 | + color: '#4f4f7a', | ||
97 | + fontSize: '14px', | ||
98 | + }, | ||
99 | + itemHoverStyle: { | ||
100 | + color: '#100f28', | ||
101 | + fontSize: '14px', | ||
102 | + }, | ||
103 | + itemHiddenStyle: { | ||
104 | + color: '#dde2ec', | ||
105 | + fontSize: '14px', | ||
106 | + }, | ||
107 | + }, | ||
108 | + loading: { | ||
109 | + labelStyle: { | ||
110 | + color: '#013477', | ||
111 | + }, | ||
112 | + style: { | ||
113 | + backgroundColor: 'gray', | ||
114 | + }, | ||
115 | + }, | ||
116 | + exporting: { | ||
117 | + sourceWidth: 1000, | ||
118 | + sourceHeight: 500, | ||
119 | + buttons: { | ||
120 | + contextButton: { | ||
121 | + theme: { | ||
122 | + fill: 'transparent', | ||
123 | + }, | ||
124 | + x: -2, | ||
125 | + y: 0, | ||
126 | + menuItems: [ | ||
127 | + 'viewFullscreen', | ||
128 | + 'printChart', | ||
129 | + 'separator', | ||
130 | + 'downloadPDF', | ||
131 | + 'downloadPNG', | ||
132 | + 'downloadXLS', | ||
133 | + ], | ||
134 | + }, | ||
135 | + }, | ||
136 | + fallbackToExportServer: false, | ||
137 | + }, | ||
138 | + mapNavigation: { | ||
139 | + enableMouseWheelZoom: true, | ||
140 | + }, | ||
141 | +}; | ||
142 | + | ||
143 | +Highcharts.setOptions({ | ||
144 | + lang: { | ||
145 | + thousandsSep: ',', | ||
146 | + }, | ||
147 | + ...options, | ||
148 | +}); | ||
149 | + | ||
150 | +Vue.use(HighchartsVue, { | ||
151 | + highcharts: Highcharts, | ||
152 | +}); |
Frontend/src/plugins/vuelayers.js
0 → 100644
Frontend/src/static/README.md
0 → 100644
1 | +# STATIC | ||
2 | + | ||
3 | +**This directory is not required, you can delete it if you don't want to use it.** | ||
4 | + | ||
5 | +This directory contains your static files. | ||
6 | +Each file inside this directory is mapped to `/`. | ||
7 | +Thus you'd want to delete this README.md before deploying to production. | ||
8 | + | ||
9 | +Example: `/static/robots.txt` is mapped as `/robots.txt`. | ||
10 | + | ||
11 | +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). |
Frontend/src/static/favicon.ico
0 → 100644
No preview for this file type
Frontend/src/static/img/drone.jpeg
0 → 100644
76.2 KB
Frontend/src/static/img/drone.png
0 → 100644
80.2 KB
Frontend/src/static/img/drone.svg
0 → 100644
1 | +<?xml version="1.0" standalone="no"?> | ||
2 | +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | ||
3 | + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | ||
4 | +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" | ||
5 | + width="340.000000pt" height="340.000000pt" viewBox="0 0 340.000000 340.000000" | ||
6 | + preserveAspectRatio="xMidYMid meet"> | ||
7 | + | ||
8 | +<g transform="translate(0.000000,340.000000) scale(0.100000,-0.100000)" | ||
9 | +fill="#000000" stroke="none"> | ||
10 | +<path d="M610 3386 c-162 -36 -269 -95 -386 -211 -77 -76 -98 -105 -137 -185 | ||
11 | +-58 -118 -77 -189 -84 -310 -4 -76 -1 -113 16 -185 57 -237 200 -414 419 -517 | ||
12 | +116 -55 201 -71 342 -65 119 5 182 20 284 65 l59 26 39 -44 c53 -62 68 -119 | ||
13 | +68 -260 0 -141 -15 -198 -68 -260 l-39 -44 -61 27 c-116 53 -212 70 -347 64 | ||
14 | +-133 -6 -192 -20 -303 -75 -297 -147 -470 -505 -393 -812 52 -206 144 -347 | ||
15 | +298 -456 120 -86 293 -144 431 -144 74 0 216 34 304 73 192 85 343 261 409 | ||
16 | +477 45 145 25 377 -45 522 l-26 53 33 28 c17 16 57 40 87 55 51 25 64 27 190 | ||
17 | +27 127 0 138 -2 190 -28 30 -15 69 -39 87 -55 l32 -27 -29 -66 c-50 -111 -64 | ||
18 | +-182 -64 -309 0 -140 16 -210 80 -338 77 -154 230 -295 381 -352 172 -64 294 | ||
19 | +-74 438 -36 115 31 172 56 256 112 159 106 267 274 314 489 54 249 -62 542 | ||
20 | +-280 710 -216 166 -494 201 -749 95 l-77 -32 -23 21 c-77 72 -111 247 -81 411 | ||
21 | +8 44 50 122 81 151 l23 22 58 -26 c117 -51 169 -62 313 -62 154 0 234 19 350 | ||
22 | +82 202 110 324 273 381 507 26 110 24 203 -5 319 -38 148 -93 245 -201 353 | ||
23 | +-76 76 -105 97 -185 137 -227 111 -446 114 -665 7 -196 -95 -356 -298 -400 | ||
24 | +-508 -31 -146 -12 -331 48 -465 l31 -70 -44 -39 c-63 -54 -131 -73 -260 -73 | ||
25 | +-131 0 -200 20 -261 73 l-43 39 26 59 c44 100 59 165 65 280 7 140 -12 234 | ||
26 | +-72 359 -146 302 -495 480 -805 411z m268 -161 c62 -15 162 -60 215 -97 l28 | ||
27 | +-20 -43 -28 c-24 -15 -99 -84 -167 -153 -118 -119 -127 -126 -173 -132 -83 | ||
28 | +-12 -138 -78 -138 -166 0 -23 -5 -28 -37 -34 -21 -4 -49 -13 -63 -20 -60 -32 | ||
29 | +-146 -154 -177 -252 -9 -29 -19 -53 -22 -53 -8 0 -53 73 -85 137 -82 165 -71 | ||
30 | +377 29 549 69 119 220 230 365 268 72 19 189 19 268 1z m1963 -17 c98 -36 157 | ||
31 | +-74 229 -147 139 -142 197 -338 156 -532 -15 -68 -67 -184 -104 -229 l-20 -25 | ||
32 | +-10 25 c-5 14 -73 91 -149 170 -131 136 -141 148 -147 193 -5 35 -17 59 -40 | ||
33 | +84 -27 30 -40 36 -91 42 l-59 6 -18 62 c-15 50 -27 69 -65 102 -57 51 -135 96 | ||
34 | +-204 120 l-52 17 34 26 c50 39 150 86 216 103 86 22 240 14 324 -17z m-1573 | ||
35 | +-287 c48 -90 65 -170 60 -291 -4 -97 -32 -217 -57 -243 -4 -4 -46 22 -93 57 | ||
36 | +-48 36 -128 93 -180 126 -51 34 -96 69 -100 78 -15 39 -9 49 40 62 26 6 61 20 | ||
37 | +78 31 59 36 163 210 164 274 l1 30 29 -34 c16 -18 42 -59 58 -90z m948 74 c5 | ||
38 | +-13 72 -89 149 -167 173 -176 172 -170 38 -257 -50 -32 -131 -89 -179 -125 | ||
39 | +-48 -37 -91 -63 -95 -59 -3 5 -17 35 -29 68 -43 115 -38 308 11 423 23 54 78 | ||
40 | +142 88 142 4 0 11 -11 17 -25z m-1462 -495 c7 0 34 -34 61 -76 26 -42 83 -124 | ||
41 | +126 -182 43 -58 76 -108 74 -112 -3 -4 -37 -18 -77 -31 -61 -20 -94 -24 -193 | ||
42 | +-24 -138 0 -199 15 -298 75 -69 42 -79 57 -45 65 13 3 89 71 168 150 84 84 | ||
43 | +151 143 158 140 8 -3 19 -5 26 -5z m1952 -27 c3 -21 17 -56 29 -79 38 -66 210 | ||
44 | +-173 280 -174 l30 -1 -33 -28 c-47 -41 -146 -88 -222 -107 -88 -22 -251 -15 | ||
45 | +-335 15 -33 12 -63 25 -67 29 -5 5 21 47 56 94 36 48 93 128 127 180 66 100 | ||
46 | +73 108 106 108 18 0 23 -7 29 -37z m-1818 -1158 c35 -8 78 -23 97 -32 l34 -18 | ||
47 | +-45 -60 c-25 -33 -82 -114 -126 -180 -44 -66 -88 -123 -97 -127 -38 -16 -49 | ||
48 | +-8 -61 40 -6 26 -20 61 -31 78 -36 59 -210 163 -274 164 l-30 1 35 30 c43 37 | ||
49 | +140 86 206 104 66 18 215 18 292 0z m1964 -17 c66 -25 168 -84 168 -97 0 -4 | ||
50 | +-11 -11 -24 -17 -14 -5 -90 -72 -169 -149 -166 -162 -177 -166 -223 -87 -25 | ||
51 | +43 -141 210 -200 287 l-24 32 49 22 c81 34 120 40 241 37 93 -3 128 -8 182 | ||
52 | +-28z m319 -283 c100 -203 88 -413 -36 -595 -186 -274 -556 -340 -829 -147 | ||
53 | +l-28 20 28 15 c16 8 92 76 169 152 132 131 143 139 189 146 82 11 136 77 136 | ||
54 | +165 0 23 5 28 37 34 21 4 49 13 63 20 61 32 148 157 176 253 15 52 16 53 35 | ||
55 | +36 11 -10 38 -54 60 -99z m-2707 -95 c113 -112 136 -140 136 -164 0 -44 35 | ||
56 | +-109 70 -129 16 -10 52 -21 79 -24 47 -5 50 -7 56 -41 10 -54 29 -85 80 -130 | ||
57 | +51 -45 163 -106 213 -117 32 -7 33 -7 16 -26 -52 -58 -256 -129 -369 -129 | ||
58 | +-349 0 -628 325 -577 672 12 82 56 194 103 260 l22 31 18 -34 c10 -19 79 -95 | ||
59 | +153 -169z m852 -39 c23 -102 15 -242 -18 -333 -24 -65 -84 -168 -97 -168 -3 0 | ||
60 | +-13 14 -21 30 -8 16 -76 92 -151 168 -164 167 -164 168 -55 236 39 24 120 81 | ||
61 | +181 125 l110 81 17 -33 c9 -17 25 -65 34 -106z m956 36 c64 -47 142 -99 172 | ||
62 | +-117 36 -20 56 -38 56 -50 0 -10 2 -25 5 -32 4 -11 -9 -18 -52 -27 -48 -11 | ||
63 | +-65 -21 -103 -61 -56 -59 -130 -195 -130 -241 l-1 -34 -30 35 c-121 141 -155 | ||
64 | +398 -77 583 14 32 24 44 32 38 6 -5 64 -47 128 -94z"/> | ||
65 | +</g> | ||
66 | +</svg> |
Frontend/src/static/img/droneImage.jpeg
0 → 100644
1.04 MB
Frontend/src/static/img/filter.png
0 → 100644
4.1 KB
Frontend/src/static/img/gps.png
0 → 100644
13.2 KB
Frontend/src/static/img/search.png
0 → 100644
8.27 KB
Frontend/src/store/Code/index.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + codes: [], | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getCodes: (state) => state.codes, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + fetchCodes(context) { | ||
11 | + return new Promise((resolve, reject) => { | ||
12 | + context.commit('DEL_CODES'); | ||
13 | + this.$axios.get('/api/code/list') | ||
14 | + .then((r) => { | ||
15 | + const result = r.data; | ||
16 | + context.commit('SET_CODES', result); | ||
17 | + resolve(result); | ||
18 | + }) | ||
19 | + .catch((e) => { | ||
20 | + reject(e); | ||
21 | + }); | ||
22 | + }); | ||
23 | + }, | ||
24 | +}; | ||
25 | + | ||
26 | +export const mutations = { | ||
27 | + SET_CODES(state, payload) { | ||
28 | + state.pageData = payload; | ||
29 | + }, | ||
30 | + DEL_CODES(state) { | ||
31 | + state.pageData = null; | ||
32 | + }, | ||
33 | +}; |
Frontend/src/store/Drone/Map/index.js
0 → 100644
1 | +/* eslint-disable no-underscore-dangle */ | ||
2 | +export const state = () => ({ | ||
3 | + zoomLevel: 16, | ||
4 | + boundary: { | ||
5 | + northEast: {}, | ||
6 | + southWest: {}, | ||
7 | + }, | ||
8 | + stateChange: false, | ||
9 | +}); | ||
10 | + | ||
11 | +export const getters = { | ||
12 | + getZoomLevel: (state) => state.zoomLevel, | ||
13 | + getBoundary: (state) => state.boundary, | ||
14 | + getStateChange: (state) => state.stateChange, | ||
15 | + isBoundary: (state) => (drone) => { | ||
16 | + const checkLatBound = drone.latitude >= state.boundary.southWest.lat && drone.latitude <= state.boundary.northEast.lat; | ||
17 | + const checkLngBound = drone.longitude >= state.boundary.southWest.lng && drone.longitude <= state.boundary.northEast.lng; | ||
18 | + return checkLatBound && checkLngBound; | ||
19 | + }, | ||
20 | + zoomToActivateTime: (state) => { | ||
21 | + switch (state.zoomLevel) { | ||
22 | + case 8: | ||
23 | + return 30; | ||
24 | + case 9: | ||
25 | + return 15; | ||
26 | + case 10: | ||
27 | + return 10; | ||
28 | + case 11: | ||
29 | + return 6; | ||
30 | + case 12: | ||
31 | + return 4; | ||
32 | + case 13: | ||
33 | + return 3; | ||
34 | + case 14: | ||
35 | + return 2; | ||
36 | + default: | ||
37 | + return 1; | ||
38 | + } | ||
39 | + }, | ||
40 | +}; | ||
41 | + | ||
42 | +export const actions = { | ||
43 | + setZoomLevel(context, data) { | ||
44 | + context.commit('SET_ZOOM_LEVEL', data); | ||
45 | + }, | ||
46 | + setBoundary(context, data) { | ||
47 | + context.commit('SET_BOUNDARY', data); | ||
48 | + }, | ||
49 | + clearZoomLevel(context) { | ||
50 | + context.commit('DEL_ZOOM_LEVEL'); | ||
51 | + }, | ||
52 | + clearBoundary(context) { | ||
53 | + context.commit('DEL_BOUNDARY'); | ||
54 | + }, | ||
55 | + clearStateChange(context) { | ||
56 | + context.commit('RESET_STATE_CHANGE'); | ||
57 | + }, | ||
58 | +}; | ||
59 | + | ||
60 | +export const mutations = { | ||
61 | + SET_ZOOM_LEVEL(state, payload) { | ||
62 | + state.zoomLevel = payload; | ||
63 | + state.stateChange = true; | ||
64 | + }, | ||
65 | + SET_BOUNDARY(state, payload) { | ||
66 | + state.boundary = { northEast: payload?._northEast, southWest: payload?._southWest }; | ||
67 | + state.stateChange = true; | ||
68 | + }, | ||
69 | + DEL_ZOOM_LEVEL(state) { | ||
70 | + state.zoomLevel = 16; | ||
71 | + }, | ||
72 | + DEL_BOUNDARY(state) { | ||
73 | + state.boundary = { | ||
74 | + northEast: {}, | ||
75 | + southWest: {}, | ||
76 | + }; | ||
77 | + }, | ||
78 | + RESET_STATE_CHANGE(state) { | ||
79 | + state.stateChange = false; | ||
80 | + }, | ||
81 | +}; |
Frontend/src/store/Drone/detail.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + detailData: null, | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getDetailData: (state) => state.detailData, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + fetchDetailData(context, id) { | ||
11 | + return new Promise((resolve, reject) => { | ||
12 | + context.commit('CLEAR_DETAIL_DATA'); | ||
13 | + this.$axios.get(`/api/drone/${id}`) | ||
14 | + .then((r) => { | ||
15 | + context.commit('SET_DETAIL_DATA', r.data); | ||
16 | + resolve(r.data); | ||
17 | + }) | ||
18 | + .catch((e) => { | ||
19 | + reject(e); | ||
20 | + }); | ||
21 | + }); | ||
22 | + }, | ||
23 | +}; | ||
24 | + | ||
25 | +export const mutations = { | ||
26 | + SET_DETAIL_DATA(state, payload) { | ||
27 | + state.detailData = payload; | ||
28 | + }, | ||
29 | + CLEAR_DETAIL_DATA(state) { | ||
30 | + state.detailData = null; | ||
31 | + }, | ||
32 | +}; |
Frontend/src/store/Drone/drone.js
0 → 100644
1 | +import calcDistanceFromCoord from '@/utils/CommonFunction/calcDistanceFromCoord'; | ||
2 | + | ||
3 | +export const state = () => ({ | ||
4 | + detailData: {}, | ||
5 | + | ||
6 | + droneLogs: [], | ||
7 | + wholeDroneLogs: [], | ||
8 | + selectedLogList: [], | ||
9 | + selectedDroneId: null, | ||
10 | + accumulatedDistance: 0, | ||
11 | + logFilter: { | ||
12 | + checkFilter: false, | ||
13 | + maker: [], | ||
14 | + filteredDroneList: [], | ||
15 | + weight: [0, 50], | ||
16 | + altitude: [0, 200], | ||
17 | + speed: [0, 100], | ||
18 | + }, | ||
19 | +}); | ||
20 | + | ||
21 | +export const getters = { | ||
22 | + getDetailData: (state) => state.detailData, | ||
23 | + getDroneLogs: (state) => state.droneLogs, | ||
24 | + getWholeDroneLog: (state) => state.wholeDroneLogs, | ||
25 | + getSelectedLogList: (state) => state.selectedLogList, | ||
26 | + getSelectedLastLog: (state) => { | ||
27 | + if (state.selectedLogList.length === 0) return null; | ||
28 | + return state.selectedLogList[state.selectedLogList.length - 1]; | ||
29 | + }, | ||
30 | + getAccumulatedDistance: (state) => state.accumulatedDistance, | ||
31 | + getSelectedDroneId: (state) => state.selectedDroneId, | ||
32 | + getLogFilter: (state) => state.logFilter, | ||
33 | +}; | ||
34 | + | ||
35 | +export const actions = { | ||
36 | + fetchDetailInfo(context, id) { | ||
37 | + return new Promise((resolve, reject) => { | ||
38 | + // context.commit('DEL_DETAIL_DATA'); | ||
39 | + this.$axios.get(`/api/map/drone/${id}`) | ||
40 | + .then((r) => { | ||
41 | + const result = r.data; | ||
42 | + | ||
43 | + let tempLog = null; | ||
44 | + let distance = 0; | ||
45 | + for (let i = 0; i < result.droneLogs.length; i += 1) { | ||
46 | + const currentLog = result.droneLogs[i]; | ||
47 | + distance += calcDistanceFromCoord(tempLog, currentLog) || 0; | ||
48 | + tempLog = currentLog; | ||
49 | + } | ||
50 | + result.distance = distance; | ||
51 | + context.commit('SET_DETAIL_DATA', result); | ||
52 | + resolve(result); | ||
53 | + }) | ||
54 | + .catch((e) => { | ||
55 | + reject(e); | ||
56 | + }); | ||
57 | + }); | ||
58 | + }, | ||
59 | + setDroneLogs(context, data) { | ||
60 | + const filter = context.getters.getLogFilter; | ||
61 | + const filteredLogs = data.logs.filter((v) => { | ||
62 | + let isMaker = true; | ||
63 | + if (filter.maker.length !== 0) { | ||
64 | + isMaker = !!filter.filteredDroneList.find((e) => Number(e.id) === Number(v.droneId)); | ||
65 | + } | ||
66 | + const isSpeed = (v?.horizontalSpeed >= filter.speed[0] && (filter.speed[1] === 100 ? true : v?.horizontalSpeed <= filter.speed[1])) | ||
67 | + || (v?.verticalSpeed >= filter.speed[0] && (filter.speed[1] === 100 ? true : v?.verticalSpeed <= filter.speed[1])); | ||
68 | + const isAltitude = (v?.aboveGroundLevel >= filter.altitude[0] && (filter.altitude[1] === 200 ? true : v?.aboveGroundLevel <= filter.altitude[1])) | ||
69 | + || (v?.aboveSeaLevel >= filter.altitude[0] && (filter.altitude[1] === 200 ? true : v?.aboveSeaLevel <= filter.altitude[1])); | ||
70 | + const isWeight = v?.weight >= filter.weight[0] && (filter.weight[1] === 50 ? true : v?.weight <= filter.weight[1]); | ||
71 | + return isAltitude && isSpeed && isMaker; | ||
72 | + }); | ||
73 | + context.commit(data.mutation, filteredLogs); | ||
74 | + }, | ||
75 | + clearDroneLogs(context) { | ||
76 | + context.commit('CLEAR_DRONE_LOGS'); | ||
77 | + }, | ||
78 | + setSelectedLogList(context, data) { | ||
79 | + context.commit('SET_SELECTED_LOG_LIST', data); | ||
80 | + }, | ||
81 | + clearSelectedLogList(context) { | ||
82 | + context.commit('CLEAR_SELECTED_LOG_LIST'); | ||
83 | + }, | ||
84 | + setSelectedDroneId(context, data) { | ||
85 | + context.commit('SET_SELECTED_DRONE_ID', data); | ||
86 | + }, | ||
87 | + clearSelectedDroneId(context) { | ||
88 | + context.commit('CLEAR_SELECTED_DRONE_ID'); | ||
89 | + }, | ||
90 | + setAccumulatedDistance(context, data) { | ||
91 | + context.commit('SET_ACCUMULATED_DISTANCE', data); | ||
92 | + }, | ||
93 | + clearAccumulatedDistance(context) { | ||
94 | + context.commit('CLEAR_ACCUMULATED_DISTANCE'); | ||
95 | + }, | ||
96 | + setLogFilter(context, data) { | ||
97 | + context.commit('SET_LOG_FILTER', data); | ||
98 | + }, | ||
99 | + clearLogFilter(context) { | ||
100 | + context.commit('DEL_LOG_FILTER'); | ||
101 | + }, | ||
102 | +}; | ||
103 | + | ||
104 | +export const mutations = { | ||
105 | + SET_LOG_FILTER(state, payload) { | ||
106 | + state.logFilter = payload; | ||
107 | + }, | ||
108 | + DEL_LOG_FILTER(state) { | ||
109 | + state.logFilter = { | ||
110 | + checkFilter: false, | ||
111 | + filteredDroneList: [], | ||
112 | + maker: [], | ||
113 | + weight: [0, 50], | ||
114 | + altitude: [0, 200], | ||
115 | + speed: [0, 100], | ||
116 | + }; | ||
117 | + }, | ||
118 | + // | ||
119 | + SET_DETAIL_DATA(state, payload) { | ||
120 | + state.detailData = payload; | ||
121 | + }, | ||
122 | + DEL_DETAIL_DATA(state) { | ||
123 | + state.detailData = null; | ||
124 | + }, | ||
125 | + // | ||
126 | + SET_DRONE_LOGS(state, payload) { | ||
127 | + state.droneLogs = payload; | ||
128 | + }, | ||
129 | + CLEAR_DRONE_LOGS(state) { | ||
130 | + state.droneLogs = []; | ||
131 | + }, | ||
132 | + // | ||
133 | + SET_WHOLE_DRONE_LOGS(state, payload) { | ||
134 | + state.wholeDroneLogs = payload; | ||
135 | + }, | ||
136 | + CLEAR_WHOLE_DRONE_LOGS(state) { | ||
137 | + state.wholeDroneLogs = []; | ||
138 | + }, | ||
139 | + // | ||
140 | + SET_SELECTED_LOG_LIST(state, payload) { | ||
141 | + state.selectedLogList.push(payload); | ||
142 | + }, | ||
143 | + CLEAR_SELECTED_LOG_LIST(state) { | ||
144 | + state.selectedLogList = []; | ||
145 | + }, | ||
146 | + // | ||
147 | + SET_SELECTED_DRONE_ID(state, payload) { | ||
148 | + state.selectedDroneId = payload; | ||
149 | + }, | ||
150 | + CLEAR_SELECTED_DRONE_ID(state) { | ||
151 | + state.selectedDroneId = null; | ||
152 | + }, | ||
153 | + // | ||
154 | + SET_ACCUMULATED_DISTANCE(state, payload) { | ||
155 | + state.accumulatedDistance += payload; | ||
156 | + }, | ||
157 | + CLEAR_ACCUMULATED_DISTANCE(state) { | ||
158 | + state.accumulatedDistance = 0; | ||
159 | + }, | ||
160 | +}; |
Frontend/src/store/Drone/list.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + listContents: [], | ||
3 | + fixedDroneList: [], | ||
4 | +}); | ||
5 | + | ||
6 | +export const getters = { | ||
7 | + getListContents: (state) => state.listContents, | ||
8 | + getFixedDroneList: (state) => state.fixedDroneList, | ||
9 | +}; | ||
10 | + | ||
11 | +export const actions = { | ||
12 | + /* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */ | ||
13 | + fetchListContents(context, params) { | ||
14 | + return new Promise((resolve, reject) => { | ||
15 | + context.commit('CLEAR_LIST_DATA'); | ||
16 | + this.$axios.get('/api/drone/list', { | ||
17 | + params, | ||
18 | + }) | ||
19 | + .then((r) => { | ||
20 | + context.commit('SET_LIST_DATA', r.data); | ||
21 | + resolve(r.data); | ||
22 | + }) | ||
23 | + .catch((e) => { | ||
24 | + console.log(e.response); | ||
25 | + reject(e); | ||
26 | + }); | ||
27 | + }); | ||
28 | + }, | ||
29 | + setFixedDroneList(context, data) { | ||
30 | + context.commit('SET_FIXED_DRONE_LIST', data); | ||
31 | + }, | ||
32 | +}; | ||
33 | + | ||
34 | +export const mutations = { | ||
35 | + SET_FIXED_DRONE_LIST(state, payload) { | ||
36 | + state.fixedDroneList = payload; | ||
37 | + }, | ||
38 | + SET_LIST_DATA(state, payload) { | ||
39 | + state.listContents = payload.drones; | ||
40 | + }, | ||
41 | + CLEAR_LIST_DATA(state) { | ||
42 | + state.listContents = []; | ||
43 | + }, | ||
44 | +}; |
Frontend/src/store/Drone/page.js
0 → 100644
1 | +/* eslint-disable no-param-reassign */ | ||
2 | +export const state = () => ({ | ||
3 | + pageData: [], | ||
4 | + pageParams: { | ||
5 | + modelName: null, | ||
6 | + maker: null, | ||
7 | + minWeight: null, | ||
8 | + maxWeight: null, | ||
9 | + usageName: null, | ||
10 | + pageNo: 1, | ||
11 | + pageSize: 10, | ||
12 | + total: 0, | ||
13 | + }, | ||
14 | +}); | ||
15 | + | ||
16 | +export const getters = { | ||
17 | + getPageData: (state) => state.pageData, | ||
18 | + getPageParams: (state) => state.pageParams, | ||
19 | + getPagination: (state) => { | ||
20 | + const { total, pageSize } = state.pageParams; | ||
21 | + const page = state.pageParams.pageNo; | ||
22 | + return { total, pageSize, page }; | ||
23 | + }, | ||
24 | +}; | ||
25 | + | ||
26 | +export const actions = { | ||
27 | + fetchPageData(context, params) { | ||
28 | + return new Promise((resolve, reject) => { | ||
29 | + context.commit('DEL_PAGE_DATA'); | ||
30 | + this.$axios | ||
31 | + .get('/api/drone/page', { | ||
32 | + params, | ||
33 | + }) | ||
34 | + .then((r) => { | ||
35 | + const result = r.data; | ||
36 | + result.drones.forEach((elem, idx) => { | ||
37 | + elem.no = result.totalElement - (result.pageNo * result.pageSize + idx); | ||
38 | + }); | ||
39 | + context.commit('SET_PAGE_DATA', result); | ||
40 | + resolve(result); | ||
41 | + }) | ||
42 | + .catch((e) => { | ||
43 | + reject(e); | ||
44 | + }); | ||
45 | + }); | ||
46 | + }, | ||
47 | + setPageParams(context, data) { | ||
48 | + context.commit('SET_PAGE_PARAMS', data); | ||
49 | + }, | ||
50 | + clearPageParams(context) { | ||
51 | + context.commit('DEL_PAGE_PARAMS'); | ||
52 | + }, | ||
53 | + setPagination(context, params) { | ||
54 | + context.commit('SET_PAGINATION', params); | ||
55 | + }, | ||
56 | +}; | ||
57 | + | ||
58 | +export const mutations = { | ||
59 | + SET_PAGE_DATA(state, payload) { | ||
60 | + state.pageData = payload.drones; | ||
61 | + state.pageParams.total = Number(payload.totalElement); | ||
62 | + state.pageParams.pageNo = Number(payload.pageNo); | ||
63 | + state.pageParams.pageSize = Number(payload.pageSize); | ||
64 | + }, | ||
65 | + DEL_PAGE_DATA(state) { | ||
66 | + state.pageData = []; | ||
67 | + }, | ||
68 | + SET_PAGE_PARAMS(state, params) { | ||
69 | + state.pageParams = JSON.parse(JSON.stringify(params)); | ||
70 | + }, | ||
71 | + SET_PAGINATION: (state, data) => { | ||
72 | + state.pageParams.pageNo = data.page; | ||
73 | + state.pageParams.pageSize = data.size; | ||
74 | + }, | ||
75 | + DEL_PAGE_PARAMS(state) { | ||
76 | + state.pageParams = { | ||
77 | + modelName: null, | ||
78 | + maker: null, | ||
79 | + usage: null, | ||
80 | + pageNo: 1, | ||
81 | + pageSize: 20, | ||
82 | + total: 0, | ||
83 | + }; | ||
84 | + }, | ||
85 | +}; |
Frontend/src/store/Etc/index.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + makers: [], | ||
3 | + makerDroneList: [], | ||
4 | + // modelNameList: [], | ||
5 | +}); | ||
6 | + | ||
7 | +export const getters = { | ||
8 | + getMakers: (state) => state.makers, | ||
9 | + getMakerDroneList: (state) => state.makerDroneList, | ||
10 | + // getModelNameList: (state) => state.modelNameList, | ||
11 | +}; | ||
12 | + | ||
13 | +export const actions = { | ||
14 | + setMakers(context, data) { | ||
15 | + context.commit('DEL_MAKERS'); | ||
16 | + context.commit('SET_MAKERS', data); | ||
17 | + }, | ||
18 | +}; | ||
19 | + | ||
20 | +export const mutations = { | ||
21 | + SET_MAKERS(state, payload) { | ||
22 | + state.makers = Object.keys(payload); | ||
23 | + Object.entries(payload).forEach(([key, value]) => { | ||
24 | + state.makerDroneList.push({ maker: key, children: value }); | ||
25 | + // state.modelNameList = [...state.modelNameList, ...value]; | ||
26 | + }); | ||
27 | + }, | ||
28 | + DEL_MAKERS(state) { | ||
29 | + state.makers = []; | ||
30 | + state.makerDroneList = []; | ||
31 | + // state.modelNameList = []; | ||
32 | + }, | ||
33 | +}; |
Frontend/src/store/Log/detail.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + detailData: null, | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getDetailData: (state) => state.detailData, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + fetchDetailData(context, id) { | ||
11 | + return new Promise((resolve, reject) => { | ||
12 | + context.commit('CLEAR_DETAIL_DATA'); | ||
13 | + this.$axios.get(`/api/log/${id}`) | ||
14 | + .then((r) => { | ||
15 | + context.commit('SET_DETAIL_DATA', r.data); | ||
16 | + resolve(r.data); | ||
17 | + }) | ||
18 | + .catch((e) => { | ||
19 | + reject(e); | ||
20 | + }); | ||
21 | + }); | ||
22 | + }, | ||
23 | +}; | ||
24 | + | ||
25 | +export const mutations = { | ||
26 | + SET_DETAIL_DATA(state, payload) { | ||
27 | + state.detailData = payload; | ||
28 | + }, | ||
29 | + CLEAR_DETAIL_DATA(state) { | ||
30 | + state.detailData = null; | ||
31 | + }, | ||
32 | +}; |
Frontend/src/store/Log/list.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + listContents: [], | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getListContents: (state) => state.listContents, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + /* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */ | ||
11 | + fetchListContents(context, params) { | ||
12 | + return new Promise((resolve, reject) => { | ||
13 | + context.commit('CLEAR_LIST_DATA'); | ||
14 | + this.$axios.get('/api/log/list', { | ||
15 | + params, | ||
16 | + }) | ||
17 | + .then((r) => { | ||
18 | + context.commit('SET_LIST_DATA', r.data); | ||
19 | + resolve(r.data); | ||
20 | + }) | ||
21 | + .catch((e) => { | ||
22 | + console.log(e.response); | ||
23 | + reject(e); | ||
24 | + }); | ||
25 | + }); | ||
26 | + }, | ||
27 | +}; | ||
28 | + | ||
29 | +export const mutations = { | ||
30 | + SET_LIST_DATA(state, payload) { | ||
31 | + state.listContents = payload; | ||
32 | + }, | ||
33 | + CLEAR_LIST_DATA(state) { | ||
34 | + state.listContents = []; | ||
35 | + }, | ||
36 | + N_MGR_LIST(state) { | ||
37 | + }, | ||
38 | +}; |
Frontend/src/store/Log/page.js
0 → 100644
1 | +/* eslint-disable no-param-reassign */ | ||
2 | +export const state = () => ({ | ||
3 | + pageData: [], | ||
4 | + pageParams: { | ||
5 | + scheduleId: null, | ||
6 | + latitude: null, | ||
7 | + longitude: null, | ||
8 | + minVerticalSpeed: null, | ||
9 | + maxVerticalSpeed: null, | ||
10 | + minHorizontalSpeed: null, | ||
11 | + maxHorizontalSpeed: null, | ||
12 | + minAboveSeaLevel: null, | ||
13 | + maxAboveSeaLevel: null, | ||
14 | + minAboveGroundLevel: null, | ||
15 | + maxAboveGroundLevel: null, | ||
16 | + pageNo: 1, | ||
17 | + pageSize: 10, | ||
18 | + total: 0, | ||
19 | + }, | ||
20 | +}); | ||
21 | + | ||
22 | +export const getters = { | ||
23 | + getPageData: (state) => state.pageData, | ||
24 | + getPageParams: (state) => state.pageParams, | ||
25 | + getPagination: (state) => { | ||
26 | + const {total, pageSize} = state.pageParams; | ||
27 | + const page = state.pageParams.pageNo; | ||
28 | + return {total, pageSize, page}; | ||
29 | + }, | ||
30 | +}; | ||
31 | + | ||
32 | +export const actions = { | ||
33 | + fetchPageData(context, params) { | ||
34 | + return new Promise((resolve, reject) => { | ||
35 | + context.commit('DEL_PAGE_DATA'); | ||
36 | + this.$axios | ||
37 | + .get('/api/log/page', { | ||
38 | + params, | ||
39 | + }) | ||
40 | + .then((r) => { | ||
41 | + const result = r.data; | ||
42 | + result.droneLogs.forEach((elem, idx) => { | ||
43 | + elem.no = | ||
44 | + result.totalElement - (result.pageNo * result.pageSize + idx); | ||
45 | + }); | ||
46 | + context.commit('SET_PAGE_DATA', result); | ||
47 | + resolve(result); | ||
48 | + }) | ||
49 | + .catch((e) => { | ||
50 | + reject(e); | ||
51 | + }); | ||
52 | + }); | ||
53 | + }, | ||
54 | + setPageParams(context, data) { | ||
55 | + context.commit('SET_PAGE_PARAMS', data); | ||
56 | + }, | ||
57 | + clearPageParams(context) { | ||
58 | + context.commit('DEL_PAGE_PARAMS'); | ||
59 | + }, | ||
60 | + setPagination(context, params) { | ||
61 | + context.commit('SET_PAGINATION', params); | ||
62 | + }, | ||
63 | +}; | ||
64 | + | ||
65 | +export const mutations = { | ||
66 | + SET_PAGE_DATA(state, payload) { | ||
67 | + state.pageData = payload.droneLogs; | ||
68 | + state.pageParams.total = Number(payload.totalElement); | ||
69 | + state.pageParams.pageNo = Number(payload.pageNo); | ||
70 | + state.pageParams.pageSize = Number(payload.pageSize); | ||
71 | + }, | ||
72 | + DEL_PAGE_DATA(state) { | ||
73 | + state.pageData = []; | ||
74 | + }, | ||
75 | + SET_PAGE_PARAMS(state, params) { | ||
76 | + state.pageParams = JSON.parse(JSON.stringify(params)); | ||
77 | + }, | ||
78 | + SET_PAGINATION: (state, data) => { | ||
79 | + state.pageParams.pageNo = data.page; | ||
80 | + state.pageParams.pageSize = data.size; | ||
81 | + }, | ||
82 | + DEL_PAGE_PARAMS(state) { | ||
83 | + state.pageParams = { | ||
84 | + scheduleId: null, | ||
85 | + minSpeed: null, | ||
86 | + maxSpeed: null, | ||
87 | + minAltitude: null, | ||
88 | + maxAltitude: null, | ||
89 | + latitude: null, | ||
90 | + longitude: null, | ||
91 | + pageNo: 1, | ||
92 | + pageSize: 10, | ||
93 | + total: 0, | ||
94 | + }; | ||
95 | + }, | ||
96 | +}; |
Frontend/src/store/Member/detail.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + detailData: null, | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getDetailData: (state) => state.detailData, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + fetchDetailData(context, id) { | ||
11 | + return new Promise((resolve, reject) => { | ||
12 | + context.commit('CLEAR_DETAIL_DATA'); | ||
13 | + this.$axios.get(`/api/member/${id}`) | ||
14 | + .then((r) => { | ||
15 | + context.commit('SET_DETAIL_DATA', r.data); | ||
16 | + resolve(r.data); | ||
17 | + }) | ||
18 | + .catch((e) => { | ||
19 | + reject(e); | ||
20 | + }); | ||
21 | + }); | ||
22 | + }, | ||
23 | +}; | ||
24 | + | ||
25 | +export const mutations = { | ||
26 | + SET_DETAIL_DATA(state, payload) { | ||
27 | + state.detailData = payload; | ||
28 | + }, | ||
29 | + CLEAR_DETAIL_DATA(state) { | ||
30 | + state.detailData = null; | ||
31 | + }, | ||
32 | +}; |
Frontend/src/store/Member/list.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + listContents: [], | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getListContents: (state) => state.listContents, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + /* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */ | ||
11 | + fetchListContents(context, params) { | ||
12 | + return new Promise((resolve, reject) => { | ||
13 | + context.commit('CLEAR_LIST_DATA'); | ||
14 | + this.$axios.get('/api/member/list', { | ||
15 | + params, | ||
16 | + }) | ||
17 | + .then((r) => { | ||
18 | + context.commit('SET_LIST_DATA', r.data); | ||
19 | + resolve(r.data); | ||
20 | + }) | ||
21 | + .catch((e) => { | ||
22 | + console.log(e.response); | ||
23 | + reject(e); | ||
24 | + }); | ||
25 | + }); | ||
26 | + }, | ||
27 | +}; | ||
28 | + | ||
29 | +export const mutations = { | ||
30 | + SET_LIST_DATA(state, payload) { | ||
31 | + state.listContents = payload; | ||
32 | + }, | ||
33 | + CLEAR_LIST_DATA(state) { | ||
34 | + state.listContents = []; | ||
35 | + }, | ||
36 | + N_MGR_LIST(state) { | ||
37 | + }, | ||
38 | +}; |
Frontend/src/store/Member/page.js
0 → 100644
1 | +/* eslint-disable no-param-reassign */ | ||
2 | +export const state = () => ({ | ||
3 | + pageData: [], | ||
4 | + pageParams: { | ||
5 | + pageNo: 1, | ||
6 | + pageSize: 20, | ||
7 | + total: 0, | ||
8 | + }, | ||
9 | +}); | ||
10 | + | ||
11 | +export const getters = { | ||
12 | + getPageData: (state) => state.pageData, | ||
13 | + getPageParams: (state) => state.pageParams, | ||
14 | + getPagination: (state) => { | ||
15 | + const { total, pageSize } = state.pageParams; | ||
16 | + const page = state.pageParams.pageNo; | ||
17 | + return { total, pageSize, page }; | ||
18 | + }, | ||
19 | +}; | ||
20 | + | ||
21 | +export const actions = { | ||
22 | + fetchPageData(context, params) { | ||
23 | + return new Promise((resolve, reject) => { | ||
24 | + context.commit('DEL_PAGE_DATA'); | ||
25 | + this.$axios.get('/api/member/page', { | ||
26 | + params, | ||
27 | + }) | ||
28 | + .then((r) => { | ||
29 | + const result = r.data; | ||
30 | + result.members.forEach((elem, idx) => { | ||
31 | + elem.no = result.totalElement - (result.pageNo * result.pageSize + idx); | ||
32 | + }); | ||
33 | + context.commit('SET_PAGE_DATA', result); | ||
34 | + resolve(result); | ||
35 | + }) | ||
36 | + .catch((e) => { | ||
37 | + reject(e); | ||
38 | + }); | ||
39 | + }); | ||
40 | + }, | ||
41 | + setPageParams(context, data) { | ||
42 | + context.commit('SET_PAGE_PARAMS', data); | ||
43 | + }, | ||
44 | + clearPageParams(context) { | ||
45 | + context.commit('DEL_PAGE_PARAMS'); | ||
46 | + }, | ||
47 | + setPagination(context, params) { | ||
48 | + context.commit('SET_PAGINATION', params); | ||
49 | + }, | ||
50 | +}; | ||
51 | + | ||
52 | +export const mutations = { | ||
53 | + SET_PAGE_DATA(state, payload) { | ||
54 | + state.pageData = payload.members; | ||
55 | + state.pageParams.total = Number(payload.totalElement); | ||
56 | + state.pageParams.pageNo = Number(payload.pageNo); | ||
57 | + state.pageParams.pageSize = Number(payload.pageSize); | ||
58 | + }, | ||
59 | + DEL_PAGE_DATA(state) { | ||
60 | + state.pageData = []; | ||
61 | + }, | ||
62 | + SET_PAGE_PARAMS(state, params) { | ||
63 | + state.pageParams = JSON.parse(JSON.stringify(params)); | ||
64 | + }, | ||
65 | + SET_PAGINATION: (state, data) => { | ||
66 | + state.pageParams.pageNo = data.page; | ||
67 | + state.pageParams.pageSize = data.size; | ||
68 | + }, | ||
69 | + DEL_PAGE_PARAMS(state) { | ||
70 | + state.pageParams = { | ||
71 | + pageNo: 1, | ||
72 | + pageSize: 20, | ||
73 | + total: 0, | ||
74 | + }; | ||
75 | + }, | ||
76 | +}; |
Frontend/src/store/README.md
0 → 100644
1 | +# STORE | ||
2 | + | ||
3 | +**This directory is not required, you can delete it if you don't want to use it.** | ||
4 | + | ||
5 | +This directory contains your Vuex Store files. | ||
6 | +Vuex Store option is implemented in the Nuxt.js framework. | ||
7 | + | ||
8 | +Creating a file in this directory automatically activates the option in the framework. | ||
9 | + | ||
10 | +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). |
Frontend/src/store/Schedule/detail.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + detailData: null, | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getDetailData: (state) => state.detailData, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + fetchDetailData(context, id) { | ||
11 | + return new Promise((resolve, reject) => { | ||
12 | + context.commit('CLEAR_DETAIL_DATA'); | ||
13 | + this.$axios.get(`/api/schedule/${id}`) | ||
14 | + .then((r) => { | ||
15 | + context.commit('SET_DETAIL_DATA', r.data); | ||
16 | + resolve(r.data); | ||
17 | + }) | ||
18 | + .catch((e) => { | ||
19 | + reject(e); | ||
20 | + }); | ||
21 | + }); | ||
22 | + }, | ||
23 | +}; | ||
24 | + | ||
25 | +export const mutations = { | ||
26 | + SET_DETAIL_DATA(state, payload) { | ||
27 | + state.detailData = payload; | ||
28 | + }, | ||
29 | + CLEAR_DETAIL_DATA(state) { | ||
30 | + state.detailData = null; | ||
31 | + }, | ||
32 | +}; |
Frontend/src/store/Schedule/list.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + listContents: [], | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getListContents: (state) => state.listContents, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + /* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */ | ||
11 | + fetchListContents(context, params) { | ||
12 | + return new Promise((resolve, reject) => { | ||
13 | + context.commit('CLEAR_LIST_DATA'); | ||
14 | + this.$axios.get('/api/schedule/list', { | ||
15 | + params, | ||
16 | + }) | ||
17 | + .then((r) => { | ||
18 | + context.commit('SET_LIST_DATA', r.data); | ||
19 | + resolve(r.data); | ||
20 | + }) | ||
21 | + .catch((e) => { | ||
22 | + console.log(e.response); | ||
23 | + reject(e); | ||
24 | + }); | ||
25 | + }); | ||
26 | + }, | ||
27 | +}; | ||
28 | + | ||
29 | +export const mutations = { | ||
30 | + SET_LIST_DATA(state, payload) { | ||
31 | + state.listContents = payload; | ||
32 | + }, | ||
33 | + CLEAR_LIST_DATA(state) { | ||
34 | + state.listContents = []; | ||
35 | + }, | ||
36 | + N_MGR_LIST(state) { | ||
37 | + }, | ||
38 | +}; |
Frontend/src/store/Schedule/page.js
0 → 100644
1 | +/* eslint-disable no-param-reassign */ | ||
2 | +export const state = () => ({ | ||
3 | + pageData: [], | ||
4 | + pageParams: { | ||
5 | + droneId: null, | ||
6 | + startTime: null, | ||
7 | + terminateTime: null, | ||
8 | + pageNo: 1, | ||
9 | + pageSize: 10, | ||
10 | + total: 0, | ||
11 | + }, | ||
12 | +}); | ||
13 | + | ||
14 | +export const getters = { | ||
15 | + getPageData: (state) => state.pageData, | ||
16 | + getPageParams: (state) => state.pageParams, | ||
17 | + getPagination: (state) => { | ||
18 | + const {total, pageSize} = state.pageParams; | ||
19 | + const page = state.pageParams.pageNo; | ||
20 | + return {total, pageSize, page}; | ||
21 | + }, | ||
22 | +}; | ||
23 | + | ||
24 | +export const actions = { | ||
25 | + fetchPageData(context, params) { | ||
26 | + return new Promise((resolve, reject) => { | ||
27 | + context.commit('DEL_PAGE_DATA'); | ||
28 | + this.$axios | ||
29 | + .get('/api/schedule/page', { | ||
30 | + params, | ||
31 | + }) | ||
32 | + .then((r) => { | ||
33 | + const result = r.data; | ||
34 | + result.schedules.forEach((elem, idx) => { | ||
35 | + elem.no = | ||
36 | + result.totalElement - (result.pageNo * result.pageSize + idx); | ||
37 | + }); | ||
38 | + context.commit('SET_PAGE_DATA', result); | ||
39 | + resolve(result); | ||
40 | + }) | ||
41 | + .catch((e) => { | ||
42 | + reject(e); | ||
43 | + }); | ||
44 | + }); | ||
45 | + }, | ||
46 | + setPageParams(context, data) { | ||
47 | + context.commit('SET_PAGE_PARAMS', data); | ||
48 | + }, | ||
49 | + clearPageParams(context) { | ||
50 | + context.commit('DEL_PAGE_PARAMS'); | ||
51 | + }, | ||
52 | + setPagination(context, params) { | ||
53 | + context.commit('SET_PAGINATION', params); | ||
54 | + }, | ||
55 | +}; | ||
56 | + | ||
57 | +export const mutations = { | ||
58 | + SET_PAGE_DATA(state, payload) { | ||
59 | + state.pageData = payload.schedules; | ||
60 | + state.pageParams.total = Number(payload.totalElement); | ||
61 | + state.pageParams.pageNo = Number(payload.pageNo); | ||
62 | + state.pageParams.pageSize = Number(payload.pageSize); | ||
63 | + }, | ||
64 | + DEL_PAGE_DATA(state) { | ||
65 | + state.pageData = []; | ||
66 | + }, | ||
67 | + SET_PAGE_PARAMS(state, params) { | ||
68 | + state.pageParams = JSON.parse(JSON.stringify(params)); | ||
69 | + }, | ||
70 | + SET_PAGINATION: (state, data) => { | ||
71 | + state.pageParams.pageNo = data.page; | ||
72 | + state.pageParams.pageSize = data.size; | ||
73 | + }, | ||
74 | + DEL_PAGE_PARAMS(state) { | ||
75 | + state.pageParams = { | ||
76 | + droneId: null, | ||
77 | + startTime: null, | ||
78 | + terminateTime: null, | ||
79 | + pageNo: 1, | ||
80 | + pageSize: 10, | ||
81 | + total: 0, | ||
82 | + }; | ||
83 | + }, | ||
84 | +}; |
Frontend/src/store/index.js
0 → 100644
1 | +export const state = () => ({ | ||
2 | + settings: {}, | ||
3 | +}); | ||
4 | + | ||
5 | +export const getters = { | ||
6 | + getSettings: (state) => state.settings, | ||
7 | +}; | ||
8 | + | ||
9 | +export const actions = { | ||
10 | + setSettings(context, params) { | ||
11 | + context.commit('CHANGE_SETTINGS', params); | ||
12 | + }, | ||
13 | + async nuxtServerInit({ dispatch }, ctx) { | ||
14 | + dispatch('Code/fetchCodes'); | ||
15 | + }, | ||
16 | +}; | ||
17 | + | ||
18 | +export const mutations = { | ||
19 | + CHANGE_SETTINGS(state, payload) { | ||
20 | + state.settings = payload; | ||
21 | + }, | ||
22 | +}; |
1 | +export default [ | ||
2 | + { | ||
3 | + title: 'No', | ||
4 | + dataIndex: 'id', | ||
5 | + key: 'id', | ||
6 | + scopedSlots: { | ||
7 | + customRender: 'id', | ||
8 | + }, | ||
9 | + align: 'center', | ||
10 | + ellipsis: true, | ||
11 | + width: 50, | ||
12 | + }, | ||
13 | + { | ||
14 | + title: '모델명', | ||
15 | + dataIndex: 'modelName', | ||
16 | + key: 'modelName', | ||
17 | + scopedSlots: { | ||
18 | + customRender: 'modelName', | ||
19 | + }, | ||
20 | + align: 'center', | ||
21 | + ellipsis: true, | ||
22 | + width: 200, | ||
23 | + }, | ||
24 | + { | ||
25 | + title: '제조사', | ||
26 | + dataIndex: 'maker', | ||
27 | + key: 'maker', | ||
28 | + scopedSlots: { | ||
29 | + customRender: 'maker', | ||
30 | + }, | ||
31 | + align: 'center', | ||
32 | + ellipsis: true, | ||
33 | + width: 120, | ||
34 | + }, | ||
35 | + { | ||
36 | + title: '종류', | ||
37 | + dataIndex: 'usageName', | ||
38 | + key: 'usageName', | ||
39 | + scopedSlots: { | ||
40 | + customRender: 'usageName', | ||
41 | + }, | ||
42 | + align: 'center', | ||
43 | + ellipsis: true, | ||
44 | + width: 60, | ||
45 | + }, | ||
46 | + { | ||
47 | + title: '무게', | ||
48 | + dataIndex: 'weight', | ||
49 | + key: 'weight', | ||
50 | + scopedSlots: { | ||
51 | + customRender: 'weight', | ||
52 | + }, | ||
53 | + align: 'center', | ||
54 | + ellipsis: true, | ||
55 | + width: 60, | ||
56 | + }, | ||
57 | + { | ||
58 | + title: '제원', | ||
59 | + dataIndex: 'specification', | ||
60 | + key: 'specification', | ||
61 | + scopedSlots: { | ||
62 | + customRender: 'specification', | ||
63 | + }, | ||
64 | + align: 'center', | ||
65 | + ellipsis: true, | ||
66 | + width: 60, | ||
67 | + }, | ||
68 | +]; |
1 | +export default [ | ||
2 | + { | ||
3 | + title: 'No', | ||
4 | + dataIndex: 'id', | ||
5 | + key: 'id', | ||
6 | + scopedSlots: { | ||
7 | + customRender: 'id', | ||
8 | + }, | ||
9 | + align: 'center', | ||
10 | + ellipsis: true, | ||
11 | + width: 50, | ||
12 | + }, | ||
13 | + { | ||
14 | + title: '모델명', | ||
15 | + dataIndex: 'modelName', | ||
16 | + key: 'modelName', | ||
17 | + scopedSlots: { | ||
18 | + customRender: 'modelName', | ||
19 | + }, | ||
20 | + align: 'center', | ||
21 | + ellipsis: true, | ||
22 | + width: 150, | ||
23 | + }, | ||
24 | + { | ||
25 | + title: 'Schedule ID', | ||
26 | + dataIndex: 'scheduleId', | ||
27 | + key: 'scheduleId', | ||
28 | + scopedSlots: { | ||
29 | + customRender: 'scheduleId', | ||
30 | + }, | ||
31 | + align: 'center', | ||
32 | + ellipsis: true, | ||
33 | + width: 60, | ||
34 | + }, | ||
35 | + { | ||
36 | + title: '수평 속도', | ||
37 | + dataIndex: 'horizontalSpeed', | ||
38 | + key: 'horizontalSpeed', | ||
39 | + scopedSlots: { | ||
40 | + customRender: 'horizontalSpeed', | ||
41 | + }, | ||
42 | + align: 'center', | ||
43 | + ellipsis: true, | ||
44 | + width: 60, | ||
45 | + }, | ||
46 | + { | ||
47 | + title: '수직 속도', | ||
48 | + dataIndex: 'verticalSpeed', | ||
49 | + key: 'verticalSpeed', | ||
50 | + scopedSlots: { | ||
51 | + customRender: 'verticalSpeed', | ||
52 | + }, | ||
53 | + align: 'center', | ||
54 | + ellipsis: true, | ||
55 | + width: 60, | ||
56 | + }, | ||
57 | + { | ||
58 | + title: '지면 고도', | ||
59 | + dataIndex: 'aboveGroundLevel', | ||
60 | + key: 'aboveGroundLevel', | ||
61 | + scopedSlots: { | ||
62 | + customRender: 'aboveGroundLevel', | ||
63 | + }, | ||
64 | + align: 'center', | ||
65 | + ellipsis: true, | ||
66 | + width: 60, | ||
67 | + }, | ||
68 | + { | ||
69 | + title: '해발 고도', | ||
70 | + dataIndex: 'aboveSeaLevel', | ||
71 | + key: 'aboveSeaLevel', | ||
72 | + scopedSlots: { | ||
73 | + customRender: 'aboveSeaLevel', | ||
74 | + }, | ||
75 | + align: 'center', | ||
76 | + ellipsis: true, | ||
77 | + width: 60, | ||
78 | + }, | ||
79 | + { | ||
80 | + title: '위도', | ||
81 | + dataIndex: 'latitude', | ||
82 | + key: 'latitude', | ||
83 | + scopedSlots: { | ||
84 | + customRender: 'latitude', | ||
85 | + }, | ||
86 | + align: 'center', | ||
87 | + ellipsis: true, | ||
88 | + width: 60, | ||
89 | + }, | ||
90 | + { | ||
91 | + title: '경도', | ||
92 | + dataIndex: 'longitude', | ||
93 | + key: 'longitude', | ||
94 | + scopedSlots: { | ||
95 | + customRender: 'longitude', | ||
96 | + }, | ||
97 | + align: 'center', | ||
98 | + ellipsis: true, | ||
99 | + width: 60, | ||
100 | + }, | ||
101 | +]; |
1 | +export default [ | ||
2 | + { | ||
3 | + title: 'No', | ||
4 | + dataIndex: 'id', | ||
5 | + key: 'id', | ||
6 | + scopedSlots: { | ||
7 | + customRender: 'id', | ||
8 | + }, | ||
9 | + align: 'center', | ||
10 | + ellipsis: true, | ||
11 | + width: 50, | ||
12 | + }, | ||
13 | + { | ||
14 | + title: '모델명', | ||
15 | + dataIndex: 'modelName', | ||
16 | + key: 'modelName', | ||
17 | + scopedSlots: { | ||
18 | + customRender: 'modelName', | ||
19 | + }, | ||
20 | + align: 'center', | ||
21 | + ellipsis: true, | ||
22 | + width: 200, | ||
23 | + }, | ||
24 | + { | ||
25 | + title: '시작 날짜 / 시간', | ||
26 | + dataIndex: 'startTime', | ||
27 | + key: 'startTime', | ||
28 | + scopedSlots: { | ||
29 | + customRender: 'startTime', | ||
30 | + }, | ||
31 | + align: 'center', | ||
32 | + ellipsis: true, | ||
33 | + width: 120, | ||
34 | + }, | ||
35 | + { | ||
36 | + title: '시작 위도', | ||
37 | + dataIndex: 'startLatitude', | ||
38 | + key: 'startLatitude', | ||
39 | + scopedSlots: { | ||
40 | + customRender: 'startLatitude', | ||
41 | + }, | ||
42 | + align: 'center', | ||
43 | + ellipsis: true, | ||
44 | + width: 60, | ||
45 | + }, | ||
46 | + { | ||
47 | + title: '시작 경도', | ||
48 | + dataIndex: 'startLongitude', | ||
49 | + key: 'startLongitude', | ||
50 | + scopedSlots: { | ||
51 | + customRender: 'startLongitude', | ||
52 | + }, | ||
53 | + align: 'center', | ||
54 | + ellipsis: true, | ||
55 | + width: 60, | ||
56 | + }, | ||
57 | + { | ||
58 | + title: '종료 날짜 / 시간', | ||
59 | + dataIndex: 'terminateTime', | ||
60 | + key: 'terminateTime', | ||
61 | + scopedSlots: { | ||
62 | + customRender: 'terminateTime', | ||
63 | + }, | ||
64 | + align: 'center', | ||
65 | + ellipsis: true, | ||
66 | + width: 120, | ||
67 | + }, | ||
68 | + { | ||
69 | + title: '종료 위도', | ||
70 | + dataIndex: 'terminateLatitude', | ||
71 | + key: 'terminateLatitude', | ||
72 | + scopedSlots: { | ||
73 | + customRender: 'terminateLatitude', | ||
74 | + }, | ||
75 | + align: 'center', | ||
76 | + ellipsis: true, | ||
77 | + width: 60, | ||
78 | + }, | ||
79 | + { | ||
80 | + title: '종료 경도', | ||
81 | + dataIndex: 'terminateLongitude', | ||
82 | + key: 'terminateLongitude', | ||
83 | + scopedSlots: { | ||
84 | + customRender: 'terminateLongitude', | ||
85 | + }, | ||
86 | + align: 'center', | ||
87 | + ellipsis: true, | ||
88 | + width: 60, | ||
89 | + }, | ||
90 | +]; |
1 | +/* eslint-disable no-mixed-operators */ | ||
2 | +export default function calcAngleFromCoord(oldVal, newVal) { | ||
3 | + if ((oldVal == null || oldVal.latitude == null) || (newVal == null || newVal.latitude == null)) return undefined; | ||
4 | + | ||
5 | + const x = newVal.latitude - oldVal.latitude; | ||
6 | + const y = newVal.longitude - oldVal.longitude; | ||
7 | + | ||
8 | + const radian = Math.atan2(y, x); | ||
9 | + return radian * 180 / Math.PI; | ||
10 | +} |
1 | +function toRad(deg) { | ||
2 | + return deg * (Math.PI / 180); | ||
3 | +} | ||
4 | +// use Haversine Formula | ||
5 | +export default function calcDistanceFromCoord(oldVal, newVal) { | ||
6 | + if ((oldVal == null || oldVal.latitude == null) || (newVal == null || newVal.latitude == null)) return undefined; | ||
7 | + | ||
8 | + const R = 6371000; | ||
9 | + | ||
10 | + const φ1 = toRad(oldVal.latitude); | ||
11 | + const φ2 = toRad(newVal.latitude); | ||
12 | + const Δφ = toRad(Number(newVal.latitude) - Number(oldVal.latitude)); | ||
13 | + const Δλ = toRad(Number(newVal.longitude) - Number(oldVal.longitude)); | ||
14 | + | ||
15 | + const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) | ||
16 | + + Math.cos(φ1) * Math.cos(φ2) | ||
17 | + * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); | ||
18 | + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | ||
19 | + | ||
20 | + const result = R * c; | ||
21 | + | ||
22 | + return Number(result.toFixed(1)); // Distance in meter | ||
23 | +} |
1 | +import calcDistanceFromCoord from '@/utils/CommonFunction/calcDistanceFromCoord'; | ||
2 | +import calcAngleFromCoord from '@/utils/CommonFunction/calcAngleFromCoord'; | ||
3 | + | ||
4 | +export default { | ||
5 | + data() { | ||
6 | + return { | ||
7 | + }; | ||
8 | + }, | ||
9 | + computed: { | ||
10 | + calcAngle() { | ||
11 | + return (oldVal, newVal) => calcAngleFromCoord(oldVal, newVal); | ||
12 | + }, | ||
13 | + calcVelocity() { | ||
14 | + return (oldVal, newVal) => `${calcDistanceFromCoord(oldVal, newVal)}m/s`; | ||
15 | + }, | ||
16 | + calcDistance() { | ||
17 | + return (logs) => { | ||
18 | + let sum = 0; | ||
19 | + for (let i = 1; i < logs.length; i += 1) { | ||
20 | + const oldVal = logs[i - 1]; | ||
21 | + const newVal = logs[i]; | ||
22 | + sum += calcDistanceFromCoord(oldVal, newVal); | ||
23 | + } | ||
24 | + return sum; | ||
25 | + }; | ||
26 | + }, | ||
27 | + }, | ||
28 | + watch: { | ||
29 | + | ||
30 | + }, | ||
31 | + created() { | ||
32 | + | ||
33 | + }, | ||
34 | + mounted() { | ||
35 | + | ||
36 | + }, | ||
37 | + beforeDestroy() { | ||
38 | + | ||
39 | + }, | ||
40 | + methods: { | ||
41 | + }, | ||
42 | +}; |
Frontend/src/utils/Mixins/index.js
0 → 100644
1 | +/* | ||
2 | + * 전역으로 사용되는 mixin 지정입니다. | ||
3 | + */ | ||
4 | +import calcDistanceFromCoord from '@/utils/CommonFunction/calcDistanceFromCoord'; | ||
5 | +import calcAngleFromCoord from '@/utils/CommonFunction/calcAngleFromCoord'; | ||
6 | +import { mapActions, mapGetters } from 'vuex'; | ||
7 | + | ||
8 | +export default { | ||
9 | + data() { | ||
10 | + return { | ||
11 | + socketServerCnt: 5, | ||
12 | + droneCnt: 1000, | ||
13 | + selectedDroneLog: {}, | ||
14 | + accumulatedDistance: 0, | ||
15 | + socketOnGoing: 0, | ||
16 | + }; | ||
17 | + }, | ||
18 | + computed: { | ||
19 | + ...mapGetters('Drone/drone', { | ||
20 | + getDroneLogs: 'getDroneLogs', | ||
21 | + getSelectedLogList: 'getSelectedLogList', | ||
22 | + getSelectedDroneId: 'getSelectedDroneId', | ||
23 | + getSelectedLastLog: 'getSelectedLastLog', | ||
24 | + getAccumulatedDistance: 'getAccumulatedDistance', | ||
25 | + }), | ||
26 | + ...mapGetters('Drone/Map', { | ||
27 | + getZoomLevel: 'getZoomLevel', | ||
28 | + getBoundary: 'getBoundary', | ||
29 | + isBoundary: 'isBoundary', | ||
30 | + zoomToActivateTime: 'zoomToActivateTime', | ||
31 | + getStateChange: 'getStateChange', | ||
32 | + }), | ||
33 | + mc_dateTime() { | ||
34 | + return (dateTime) => { | ||
35 | + if (dateTime == null || dateTime === 'Invalid Date' || dateTime === '') return ''; | ||
36 | + return this.$dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss'); | ||
37 | + }; | ||
38 | + }, | ||
39 | + mc_date() { | ||
40 | + return (date) => { | ||
41 | + if (date == null || date === 'Invalid Date' || date === '') return ''; | ||
42 | + return this.$dayjs(date).format('YYYY-MM-DD'); | ||
43 | + }; | ||
44 | + }, | ||
45 | + zoomToSee() { | ||
46 | + return (index) => { | ||
47 | + const div = index % 10; | ||
48 | + switch (this.zoom) { | ||
49 | + case 8: | ||
50 | + return div < 2; | ||
51 | + case 9: | ||
52 | + return div < 4; | ||
53 | + case 10: | ||
54 | + return div < 6; | ||
55 | + case 11: | ||
56 | + return div < 8; | ||
57 | + default: | ||
58 | + return true; | ||
59 | + } | ||
60 | + }; | ||
61 | + }, | ||
62 | + }, | ||
63 | + methods: { | ||
64 | + ...mapActions('Drone/drone', { | ||
65 | + setDroneLogs: 'setDroneLogs', | ||
66 | + setSelectedLogList: 'setSelectedLogList', | ||
67 | + setAccumulatedDistance: 'setAccumulatedDistance', | ||
68 | + setSelectedDroneId: 'setSelectedDroneId', | ||
69 | + }), | ||
70 | + ...mapActions('Drone/Map', { | ||
71 | + clearStateChange: 'clearStateChange', | ||
72 | + }), | ||
73 | + connect() { | ||
74 | + if (window.clientSocket == null || window.clientSocket?.readyState === 3) { | ||
75 | + window.clientSocket = new WebSocket('ws://14.33.35.148:20206/drone'); | ||
76 | + | ||
77 | + window.clientSocket.onopen = () => { | ||
78 | + console.log('socket on open'); | ||
79 | + }; | ||
80 | + window.clientSocket.onerror = () => { | ||
81 | + console.log('socket on error'); | ||
82 | + }; | ||
83 | + window.clientSocket.onmessage = (data) => { | ||
84 | + const logData = JSON.parse(data.data); | ||
85 | + | ||
86 | + let logInfo = null; | ||
87 | + const filterDroneLogs = logData.data.droneLog.filter((v, idx) => { | ||
88 | + if (this.getSelectedDroneId && v.droneId === this.getSelectedDroneId) logInfo = v; | ||
89 | + return this.isBoundary(v) && this.zoomToSee(idx); | ||
90 | + }); | ||
91 | + | ||
92 | + if (this.socketOnGoing % this.zoomToActivateTime === 0 || this.getStateChange) { | ||
93 | + this.setDroneLogs({ logs: filterDroneLogs, mutation: 'SET_DRONE_LOGS' }); | ||
94 | + this.setDroneLogs({ logs: logData.data.droneLog, mutation: 'SET_WHOLE_DRONE_LOGS' }); | ||
95 | + this.clearStateChange(); | ||
96 | + } | ||
97 | + | ||
98 | + if (logInfo) { | ||
99 | + this.setAccumulatedDistance(calcDistanceFromCoord(this.getSelectedLastLog, logInfo) || 0); | ||
100 | + logInfo.angle = calcAngleFromCoord(this.getSelectedLastLog, logInfo) || 0; | ||
101 | + logInfo.distance = this.getAccumulatedDistance; | ||
102 | + this.setSelectedLogList(logInfo); | ||
103 | + } | ||
104 | + | ||
105 | + this.socketOnGoing += 1; | ||
106 | + }; | ||
107 | + window.clientSocket.onclose = () => { | ||
108 | + console.log('socket on close'); | ||
109 | + }; | ||
110 | + } | ||
111 | + }, | ||
112 | + // connectMultiClient() { | ||
113 | + // for (let j = 0; j < this.droneCnt * this.socketServerCnt; j += 1) { | ||
114 | + // this.drones.push({ x: 0, y: 0 }); | ||
115 | + // } | ||
116 | + // for (let i = 0; i < this.socketServerCnt; i += 1) { | ||
117 | + // if ( | ||
118 | + // window[`wsSocket${i}`] == null | ||
119 | + // || window[`wsSocket${i}`].readyState === 3 | ||
120 | + // ) { | ||
121 | + // window[`wsSocket${i}`] = new WebSocket( | ||
122 | + // 'ws://14.33.35.148:20206/drone', | ||
123 | + // ); | ||
124 | + // window[`wsSocket${i}`].onopen = () => { | ||
125 | + // console.log(`socket${i} on open`); | ||
126 | + // }; | ||
127 | + // window[`wsSocket${i}`].onerror = (e) => { | ||
128 | + // console.log(`socket${i} on error occur ${e}`); | ||
129 | + // }; | ||
130 | + // window[`wsSocket${i}`].onmessage = (data) => { | ||
131 | + // const logData = JSON.parse(data.data); | ||
132 | + // console.log( | ||
133 | + // `message${i} ${this.$dayjs().format('HH:mm:ss')} : `, | ||
134 | + // logData.droneData, | ||
135 | + // ); | ||
136 | + // setTimeout(() => { | ||
137 | + // for (let k = 0; k < this.droneCnt; k += 1) { | ||
138 | + // this.drones[i * (k + 1) + k].x = logData.droneData[k].x; | ||
139 | + // this.drones[i * (k + 1) + k].y = logData.droneData[k].y; | ||
140 | + // } | ||
141 | + // }, 0); | ||
142 | + // }; | ||
143 | + // window[`wsSocket${i}`].onclose = () => { | ||
144 | + // console.log(`socket${i} closed`); | ||
145 | + // }; | ||
146 | + // } | ||
147 | + // } | ||
148 | + // }, | ||
149 | + disconnect() { | ||
150 | + if (window.clientSocket && window.clientSocket.readyState === 1) { | ||
151 | + window.clientSocket.close(); | ||
152 | + window.clientSocket = null; | ||
153 | + } | ||
154 | + }, | ||
155 | + }, | ||
156 | +}; |
Frontend/test/websocket-load-test.yaml
0 → 100644
Frontend/yarn.lock
0 → 100644
This diff could not be displayed because it is too large.
-
Please register or login to post a comment