12 changed files with 660 additions and 35 deletions
@ -0,0 +1,114 @@
|
||||
import { HttpClient } from '@angular/common/http'; |
||||
import { Injectable } from '@angular/core'; |
||||
import { Observable } from 'rxjs'; |
||||
|
||||
@Injectable({ |
||||
providedIn: 'root', |
||||
}) |
||||
export class ObjectsSimpleService { |
||||
static readonly c_apiRoot = '/api/'; //普通上传的API根路径
|
||||
static readonly c_apiRoot_Multipart = '/api/'; //分块上传的API根路径
|
||||
constructor(private http: HttpClient) {} |
||||
|
||||
static getBucketName() { |
||||
let bucket = '/test/'; |
||||
return bucket; |
||||
} |
||||
|
||||
//普通上传,单个文件上限5M
|
||||
static baseUrl = |
||||
ObjectsSimpleService.c_apiRoot + |
||||
'Objects' + |
||||
ObjectsSimpleService.getBucketName(); |
||||
postFile(extensionPath: string, file: File): Observable<Object> { |
||||
let formData = new FormData(); |
||||
formData.append('file', file, file.name); |
||||
let data = { keepOriginalName: 'true' }; |
||||
return this.http.post( |
||||
ObjectsSimpleService.baseUrl + extensionPath, |
||||
formData, |
||||
{ params: data } |
||||
); |
||||
} |
||||
|
||||
//分块上传
|
||||
static baseUrl_MultipartUpload = |
||||
ObjectsSimpleService.c_apiRoot_Multipart + |
||||
'NewMultipartUpload' + |
||||
ObjectsSimpleService.getBucketName(); |
||||
postFile_MultipartUpload(extensionPath: string, file: File): Promise<Object> { |
||||
// let formData = new FormData()
|
||||
// formData.append("file", file, file.name)
|
||||
// return this.http.post(ObjectsSimpleService.baseUrl + extensionPath, formData);
|
||||
let data = { keepOriginalName: 'true', filename: file.name }; |
||||
return new Promise((resolve, reject) => { |
||||
this.http |
||||
.post( |
||||
ObjectsSimpleService.baseUrl_MultipartUpload + extensionPath, |
||||
{}, |
||||
{ params: data } |
||||
) |
||||
.subscribe(async (data: any) => { |
||||
//初始化分段上传
|
||||
let objectName = data.objectName; |
||||
let uploadId = data.uploadId; |
||||
let PartNumberETag = []; //每次返回需要保存的信息
|
||||
//分块 处理
|
||||
let fileSize = file.size || null; //上传文件的总大小
|
||||
let shardSize = 5 * 1024 * 1024; //5MB一个分片
|
||||
let allSlice = Math.ceil(fileSize / shardSize); //总文件/5MB===共分多少段
|
||||
console.log('共分多少段' + allSlice); |
||||
for (let i = 0; i < allSlice; i++) { |
||||
//循环分段上传
|
||||
let start = i * shardSize; //切割文件开始位置
|
||||
let end = Math.min(fileSize, start + shardSize); //切割文件结束位置
|
||||
let formData = new FormData(); |
||||
formData.append('file', file.slice(start, end)); |
||||
|
||||
//同步写法实现异步调用
|
||||
let result = await new Promise((resolve, reject) => { |
||||
// await 需要后面返回一个 promise 对象
|
||||
this.http |
||||
.post( |
||||
ObjectsSimpleService.c_apiRoot_Multipart + |
||||
`MultipartUpload` + |
||||
+ObjectsSimpleService.getBucketName() + |
||||
`${objectName}?uploadId=${uploadId}&partNumber=${i + 1}`, |
||||
formData |
||||
) |
||||
.subscribe((data: any) => { |
||||
let msg = { |
||||
partNumber: data.partNumber || null, |
||||
eTag: data.eTag || null, |
||||
}; |
||||
resolve(msg); // 调用 promise 内置方法处理成功
|
||||
}); |
||||
}); |
||||
PartNumberETag.push(result); |
||||
|
||||
if (PartNumberETag.length === allSlice) { |
||||
//分块上传完成
|
||||
|
||||
let data = PartNumberETag; |
||||
let paramsData = { uploadId: uploadId }; |
||||
let path = |
||||
ObjectsSimpleService.c_apiRoot_Multipart + |
||||
'CompleteMultipartUpload' + |
||||
ObjectsSimpleService.getBucketName() + |
||||
objectName; |
||||
this.http |
||||
.post(path, data, { params: paramsData }) |
||||
.subscribe((data) => { |
||||
let objData: any = new Object(); |
||||
objData.fileName = file.name; |
||||
objData.filePath = ObjectsSimpleService.baseUrl + objectName; |
||||
resolve(objData); |
||||
}); |
||||
} |
||||
} //for循环
|
||||
|
||||
//分块 处理
|
||||
}); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,82 @@
|
||||
<div class="box"> |
||||
<form nz-form [formGroup]="validateForm" [nzLayout]="'inline'"> |
||||
<nz-form-item> |
||||
<nz-form-label nzRequired nzFor="HostIPAddress">主机地址</nz-form-label> |
||||
<nz-form-control> |
||||
<nz-input-group> |
||||
<input nz-input type="text" formControlName="HostIPAddress" placeholder="请输入主机地址" /> |
||||
</nz-input-group> |
||||
</nz-form-control> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label nzRequired nzFor="EventSystemName">预警事件</nz-form-label> |
||||
<nz-form-control> |
||||
<nz-select formControlName="EventSystemName" nzPlaceHolder="请选择预警事件"> |
||||
<nz-option *ngFor="let item of eventList" [nzValue]="item" [nzLabel]="item"></nz-option> |
||||
</nz-select> |
||||
</nz-form-control> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label nzRequired nzFor="ViolateArea">预警区域</nz-form-label> |
||||
<nz-form-control> |
||||
<nz-select formControlName="ViolateArea" nzPlaceHolder="请选择预警区域"> |
||||
<nz-option *ngFor="let item of areaList" [nzValue]="item" [nzLabel]="item"></nz-option> |
||||
</nz-select> |
||||
</nz-form-control> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label nzRequired nzFor="CameraNo">摄像头编号</nz-form-label> |
||||
<nz-form-control> |
||||
<nz-input-group> |
||||
<input nz-input type="text" formControlName="CameraNo" placeholder="请输入摄像头编号" /> |
||||
</nz-input-group> |
||||
</nz-form-control> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label nzRequired nzFor="ViolateTime">选择日期</nz-form-label> |
||||
<nz-form-control> |
||||
<nz-date-picker nzShowTime formControlName="ViolateTime"></nz-date-picker> |
||||
</nz-form-control> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label nzRequired nzFor="ViolateImage">图片地址</nz-form-label> |
||||
<nz-form-control> |
||||
<nz-input-group> |
||||
<input nz-input type="text" formControlName="ViolateImage" placeholder="请输入图片地址" /> |
||||
</nz-input-group> |
||||
</nz-form-control> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label nzRequired nzFor="violateVideo">视频地址</nz-form-label> |
||||
<nz-form-control> |
||||
<nz-input-group> |
||||
<input nz-input type="text" formControlName="violateVideo" placeholder="请输入图片地址" /> |
||||
</nz-input-group> |
||||
</nz-form-control> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label>第一步</nz-form-label> |
||||
<button nz-button (click)="getImg()">捕获图片</button> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<button nz-button (click)="getVideo()">捕获视频</button> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<nz-form-label>第二步</nz-form-label> |
||||
<button nz-button (click)="updateImg()">更新底图</button> |
||||
</nz-form-item> |
||||
<nz-form-item> |
||||
<button nz-button (click)="updateVideo()">更新视频</button> |
||||
</nz-form-item> |
||||
</form> |
||||
<div class="img_box"> |
||||
<canvas #canvas (mousedown)="onMouseDown($event)" (mousemove)="onMouseMove($event)" (mouseup)="onMouseUp()" |
||||
style="border: 1px solid #ccc;"></canvas> |
||||
<video style="width: 500px;height: auto;" [src]="videoUrl" controls></video> |
||||
<div> |
||||
<button style="margin-bottom: 10px;" nz-button nzType="primary" (click)="uploadImg()">上传预警图片</button> |
||||
<button nz-button nzType="primary" (click)="uploadVideo()">上传预警视频</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
@ -0,0 +1,16 @@
|
||||
.ant-form-item { |
||||
margin-bottom: 10px; |
||||
} |
||||
.img_box { |
||||
height: auto; |
||||
display: flex; |
||||
canvas { |
||||
max-width: 1000px; |
||||
height: auto; |
||||
} |
||||
button { |
||||
margin: 0; |
||||
margin-left: 10px; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
||||
import { SimulateDataComponent } from './simulate-data.component'; |
||||
|
||||
describe('SimulateDataComponent', () => { |
||||
let component: SimulateDataComponent; |
||||
let fixture: ComponentFixture<SimulateDataComponent>; |
||||
|
||||
beforeEach(async () => { |
||||
await TestBed.configureTestingModule({ |
||||
declarations: [ SimulateDataComponent ] |
||||
}) |
||||
.compileComponents(); |
||||
}); |
||||
|
||||
beforeEach(() => { |
||||
fixture = TestBed.createComponent(SimulateDataComponent); |
||||
component = fixture.componentInstance; |
||||
fixture.detectChanges(); |
||||
}); |
||||
|
||||
it('should create', () => { |
||||
expect(component).toBeTruthy(); |
||||
}); |
||||
}); |
@ -0,0 +1,327 @@
|
||||
import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core'; |
||||
import { NzModalRef } from 'ng-zorro-antd/modal'; |
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
import { HttpClient } from '@angular/common/http'; |
||||
import { DomSanitizer } from '@angular/platform-browser'; |
||||
import { ObjectsSimpleService } from 'src/app/service/objectsSimple.service'; |
||||
import { NzMessageService } from 'ng-zorro-antd/message'; |
||||
@Component({ |
||||
selector: 'app-simulate-data', |
||||
templateUrl: './simulate-data.component.html', |
||||
styleUrls: ['./simulate-data.component.scss'], |
||||
}) |
||||
export class SimulateDataComponent implements OnInit { |
||||
@Input() data: any; |
||||
validateForm!: FormGroup; |
||||
constructor( |
||||
private fb: FormBuilder, |
||||
private http: HttpClient, |
||||
private objectsSrv: ObjectsSimpleService, |
||||
private message: NzMessageService, |
||||
private sanitizer: DomSanitizer |
||||
) {} |
||||
|
||||
currentTime = null; |
||||
arr = ['进出口', '加油区', '卸油区', '便利店']; |
||||
ngOnInit(): void { |
||||
this.currentTime = new Date().getTime(); |
||||
|
||||
let datacopy = JSON.parse(JSON.stringify(this.data)); |
||||
let HostIPAddress = sessionStorage.getItem('hostIPAddress'); |
||||
this.validateForm = this.fb.group({ |
||||
HostIPAddress: [HostIPAddress, [Validators.required]], |
||||
EventSystemName: [null, [Validators.required]], |
||||
ViolateArea: [this.arr[datacopy.type], [Validators.required]], |
||||
CameraNo: [datacopy.name, [Validators.required]], |
||||
ViolateTime: [null, [Validators.required]], |
||||
ViolateImage: [ |
||||
`/api/Objects/test/${this.currentTime}/image.png`, |
||||
[Validators.required], |
||||
], |
||||
violateVideo: [ |
||||
`/api/Objects/test/${this.currentTime}/video.mp4`, |
||||
[Validators.required], |
||||
], |
||||
}); |
||||
this.getImgMarkData(); |
||||
this.updateVideo(); |
||||
} |
||||
|
||||
@ViewChild('canvas') canvasRef: ElementRef<HTMLCanvasElement>; |
||||
private ctx: CanvasRenderingContext2D; |
||||
imgUrl: string; |
||||
isDrawing = false; |
||||
startX: number; |
||||
startY: number; |
||||
endX: number; |
||||
endY: number; |
||||
img: HTMLImageElement; |
||||
|
||||
//获取 摄像头图片/标注点位
|
||||
getImgMarkData() { |
||||
const httpOptions = { |
||||
responseType: 'blob' as 'json', |
||||
params: { cameraId: this.data.id }, |
||||
}; |
||||
let date = new Date().getTime(); |
||||
this.http.get(`/api/Cameras/Images?v=${date}`, httpOptions).subscribe({ |
||||
next: (data: any) => { |
||||
setTimeout(() => { |
||||
this.initCanvas(data); |
||||
}, 0); |
||||
}, |
||||
error: (error) => { |
||||
console.error('获取图片数据失败', error); |
||||
setTimeout(() => { |
||||
this.initCanvas(null); |
||||
}, 0); |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
canvasObj = null; |
||||
initCanvas(data) { |
||||
if (!data) { |
||||
// 如果没有获取到图片数据,使用本地默认图片
|
||||
this.message.warning('未获取到照片数据,使用默认图片'); |
||||
this.imgUrl = '../../../../assets/images/bgImg.png'; // 请确保这个路径下有默认图片
|
||||
} else { |
||||
this.imgUrl = window.URL.createObjectURL(data); |
||||
} |
||||
|
||||
this.img = new Image(); |
||||
this.img.src = this.imgUrl; |
||||
this.img.onerror = () => { |
||||
// 如果图片加载失败,也使用默认图片
|
||||
this.message.warning('图片加载失败,使用默认图片'); |
||||
this.img.src = '../../../../assets/images/bgImg.png'; |
||||
}; |
||||
|
||||
this.img.onload = () => { |
||||
this.canvasObj = this.canvasRef.nativeElement; |
||||
this.ctx = this.canvasObj.getContext('2d'); |
||||
|
||||
// 设置 canvas 宽度为 1000px,保持图片的宽高比
|
||||
const targetWidth = 1000; |
||||
this.canvasObj.width = targetWidth; |
||||
this.canvasObj.height = (this.img.height / this.img.width) * targetWidth; |
||||
|
||||
// 在 canvas 上绘制图片
|
||||
this.ctx.drawImage( |
||||
this.img, |
||||
0, |
||||
0, |
||||
this.canvasObj.width, |
||||
this.canvasObj.height |
||||
); |
||||
}; |
||||
} |
||||
|
||||
// 开始绘制红框
|
||||
onMouseDown(event: MouseEvent): void { |
||||
this.isDrawing = true; |
||||
this.startX = event.offsetX; |
||||
this.startY = event.offsetY; |
||||
} |
||||
|
||||
// 实时绘制红框
|
||||
onMouseMove(event: MouseEvent): void { |
||||
if (!this.isDrawing) return; |
||||
|
||||
this.endX = event.offsetX; |
||||
this.endY = event.offsetY; |
||||
|
||||
const canvas = this.canvasRef.nativeElement; |
||||
this.ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除 canvas
|
||||
this.ctx.drawImage(this.img, 0, 0, canvas.width, canvas.height); // 重新绘制图片
|
||||
this.ctx.strokeStyle = 'red'; |
||||
this.ctx.lineWidth = 2; |
||||
this.ctx.strokeRect( |
||||
this.startX, |
||||
this.startY, |
||||
this.endX - this.startX, |
||||
this.endY - this.startY |
||||
); |
||||
} |
||||
|
||||
// 停止绘制红框
|
||||
onMouseUp(): void { |
||||
this.isDrawing = false; |
||||
} |
||||
|
||||
async uploadImg() { |
||||
const canvas = this.canvasRef.nativeElement; |
||||
canvas.toBlob(async (blob: Blob) => { |
||||
// 创建 File 对象,'image.png' 为文件名,'image/png' 为文件类型
|
||||
const file = new File([blob], 'image.png', { type: 'image/png' }); |
||||
console.log(file); |
||||
let url = await this.postFile(file); |
||||
console.log(url); |
||||
this.validateForm.patchValue({ |
||||
ViolateImage: url, // 替换为你想要的 ViolateImage 值
|
||||
}); |
||||
}, 'image/png'); |
||||
} |
||||
|
||||
async uploadVideo() { |
||||
// 创建 File 对象,'image.png' 为文件名,'image/png' 为文件类型
|
||||
const file = new File([this.videoBlob], 'video.mp4', { type: 'video/mp4' }); |
||||
console.log(file); |
||||
let url = await this.postFile(file); |
||||
console.log(url); |
||||
this.validateForm.patchValue({ |
||||
ViolateImage: url, // 替换为你想要的 ViolateImage 值
|
||||
}); |
||||
} |
||||
|
||||
//上传文件
|
||||
async postFile(file: File) { |
||||
return new Promise((resolve, reject) => { |
||||
this.objectsSrv |
||||
.postFile(`${this.currentTime}/`, file) |
||||
.subscribe((data) => { |
||||
let dataObj = data as any; |
||||
let filePath: string = |
||||
ObjectsSimpleService.baseUrl + dataObj.objectName; |
||||
this.message.create('success', '上传成功'); |
||||
resolve(filePath); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
getImg() { |
||||
let params = { |
||||
cameraId: this.data.id, |
||||
provider: 1, |
||||
}; |
||||
this.http |
||||
.put('/api/Cameras/Commands/CaptureImages', '', { params: params }) |
||||
.subscribe({ |
||||
next: (value: Object) => { |
||||
this.message.create( |
||||
'success', |
||||
'发送指令: 捕获图片成功,请过一段时间手动更新底图!' |
||||
); |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
getVideo() { |
||||
let params = { |
||||
cameraId: this.data.id, |
||||
provider: 1, |
||||
}; |
||||
this.http |
||||
.put('/api/Cameras/Commands/CaptureVideos', '', { params: params }) |
||||
.subscribe({ |
||||
next: (value: Object) => { |
||||
this.message.create( |
||||
'success', |
||||
'发送指令: 捕获视频成功,请过一段时间手动更新视频!' |
||||
); |
||||
}, |
||||
}); |
||||
} |
||||
updateImg() { |
||||
const httpOptions = { |
||||
responseType: 'blob' as 'json', |
||||
params: { cameraId: this.data.id }, |
||||
}; |
||||
let date = new Date().getTime(); |
||||
this.http.get(`/api/Cameras/Images?v=${date}`, httpOptions).subscribe({ |
||||
next: (data: any) => { |
||||
this.replaceBackground(data); |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
replaceBackground(newData: Blob) { |
||||
console.log('newData', newData); |
||||
const newImgUrl = window.URL.createObjectURL(newData); |
||||
|
||||
this.img = new Image(); |
||||
this.img.src = newImgUrl; |
||||
|
||||
this.img.onload = () => { |
||||
// 清空 canvas 内容
|
||||
this.ctx.clearRect(0, 0, this.canvasObj.width, this.canvasObj.height); |
||||
|
||||
// 更新 canvas 大小和图片宽高比
|
||||
const targetWidth = 1000; |
||||
this.canvasObj.width = targetWidth; |
||||
this.canvasObj.height = (this.img.height / this.img.width) * targetWidth; |
||||
|
||||
// 绘制新的图片
|
||||
this.ctx.drawImage( |
||||
this.img, |
||||
0, |
||||
0, |
||||
this.canvasObj.width, |
||||
this.canvasObj.height |
||||
); |
||||
}; |
||||
} |
||||
|
||||
videoBlob = null; |
||||
videoUrl = null; |
||||
updateVideo() { |
||||
const httpOptions = { |
||||
responseType: 'blob' as 'json', |
||||
params: { cameraId: this.data.id }, |
||||
}; |
||||
let date = new Date().getTime(); |
||||
this.http.get(`/api/Cameras/Videos?v=${date}`, httpOptions).subscribe({ |
||||
next: (data: any) => { |
||||
console.log('视频数据', data); |
||||
this.videoBlob = data; |
||||
// 获取图片数据 (data 是 Blob 类型)
|
||||
const unsafeUrl = window.URL.createObjectURL(data); |
||||
|
||||
// 使用 DomSanitizer 处理 URL
|
||||
this.videoUrl = this.sanitizer.bypassSecurityTrustUrl(unsafeUrl); |
||||
}, |
||||
}); |
||||
} |
||||
eventList = [ |
||||
'油罐区入侵', |
||||
'进出口停车', |
||||
'站内吸烟', |
||||
'前庭接打电话', |
||||
'设置卸油隔离区', |
||||
'拆除卸油管', |
||||
'卸油连接静电接地', |
||||
'卸油设置消防器材', |
||||
'卸油现场清理', |
||||
'油罐车无人卸油', |
||||
'连接卸油管', |
||||
'卸油中无人监卸', |
||||
'证照年检逾期报警', |
||||
'证照年检临期提醒', |
||||
'证照年检办理提醒', |
||||
'普通证照有效期逾期报警', |
||||
'证照有效期办理提醒', |
||||
'烟雾预警', |
||||
'火灾报警', |
||||
'设备报废逾期报警', |
||||
'设备报废临期提醒', |
||||
'收银员着装', |
||||
'关键证照有效期逾期报警', |
||||
'证照有效期临期提醒', |
||||
'设备维保临期提醒', |
||||
'设备维保逾期报警', |
||||
]; |
||||
|
||||
areaList = [ |
||||
'进出口', |
||||
'卸油区', |
||||
'加油区', |
||||
'收银区', |
||||
'发油区', |
||||
'油库区', |
||||
'监控区', |
||||
'消防通道', |
||||
'泵房区', |
||||
'油罐区', |
||||
'停车区', |
||||
]; |
||||
} |
Loading…
Reference in new issue