Browse Source

first commit

master
chendingwei 2 years ago
commit
0e2b29a2c0
  1. 24
      .gitignore
  2. 3
      .vscode/extensions.json
  3. 16
      README.md
  4. 28
      components.d.ts
  5. 14
      index.html
  6. 4361
      package-lock.json
  7. 29
      package.json
  8. BIN
      public.zip
  9. 1
      public/vite.svg
  10. 58
      src/App.vue
  11. BIN
      src/assets/audio/clickMouse.mp3
  12. BIN
      src/assets/audio/clickMouse2.mp3
  13. BIN
      src/assets/audio/excessive.mp3
  14. BIN
      src/assets/audio/notify.wav
  15. BIN
      src/assets/audio/police.wav
  16. BIN
      src/assets/audio/pop.wav
  17. BIN
      src/assets/images/dialog.png
  18. 1
      src/assets/images/vite.svg
  19. 43
      src/axios/action.ts
  20. 436
      src/axios/api.ts
  21. 26
      src/axios/cameraBase/cameraApi.ts
  22. 26
      src/axios/cameraMark/markGroupApi.ts
  23. 29
      src/axios/cameraMark/markLabelApi.ts
  24. 29
      src/axios/core/markSearchApi.ts
  25. 6
      src/axios/index.ts
  26. 1
      src/axios/onvif/onvif.ts
  27. 114
      src/axios/request.ts
  28. 0
      src/axios/webrtcStreamer/webrtcStreamer.ts
  29. 34
      src/components/aside/index.vue
  30. 124
      src/components/aside/leftMenu/index.vue
  31. 77
      src/components/aside/leftMenu/leftMenuItem/basic.vue
  32. 128
      src/components/aside/leftMenu/leftMenuItem/models3d.vue
  33. 118
      src/components/aside/leftMenu/leftMenuItem/modelsimages.vue
  34. 89
      src/components/aside/leftMenu/leftMenuItem/sytemcomponent.vue
  35. 100
      src/components/aside/leftMenu/leftMenuItem/tools.vue
  36. 40
      src/components/aside/rightMenu/index.vue
  37. 16
      src/main.ts
  38. 7
      src/router/index.ts
  39. 8
      src/router/routes.ts
  40. 35
      src/store/index.ts
  41. 15
      src/style.css
  42. 2
      src/types/index.d.ts
  43. 39
      src/utils/audio.ts
  44. 10
      src/utils/axios.ts
  45. 9
      src/utils/message.ts
  46. 68
      src/utils/mqttunit.ts
  47. 317
      src/utils/webrtc-streamer/webrtcstreamer.js
  48. 34
      src/views/index.vue
  49. 297
      src/views/page/cameraCenter.vue
  50. 7
      src/vite-env.d.ts
  51. 22
      tsconfig.json
  52. 9
      tsconfig.node.json
  53. 57
      vite.config.ts
  54. 1411
      yarn.lock

24
.gitignore

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

16
README.md

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

28
components.d.ts

@ -0,0 +1,28 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ACol: typeof import('ant-design-vue/es')['Col']
ACollapse: typeof import('ant-design-vue/es')['Collapse']
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
ARow: typeof import('ant-design-vue/es')['Row']
Aside: typeof import('./src/components/aside/index.vue')['default']
ATabPane: typeof import('ant-design-vue/es')['TabPane']
ATabs: typeof import('ant-design-vue/es')['Tabs']
ATree: typeof import('ant-design-vue/es')['Tree']
Basic: typeof import('./src/components/aside/leftMenu/leftMenuItem/basic.vue')['default']
LeftMenu: typeof import('./src/components/aside/leftMenu/index.vue')['default']
Models3d: typeof import('./src/components/aside/leftMenu/leftMenuItem/models3d.vue')['default']
Modelsimages: typeof import('./src/components/aside/leftMenu/leftMenuItem/modelsimages.vue')['default']
RightMenu: typeof import('./src/components/aside/rightMenu/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sytemcomponent: typeof import('./src/components/aside/leftMenu/leftMenuItem/sytemcomponent.vue')['default']
Tools: typeof import('./src/components/aside/leftMenu/leftMenuItem/tools.vue')['default']
}
}

14
index.html

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

4361
package-lock.json

File diff suppressed because it is too large

29
package.json

@ -0,0 +1,29 @@
{
"name": "hq-web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"serve": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"ant-design-vue": "^3.3.0-beta.3",
"axios": "^1.2.0",
"mqtt": "^4.3.7",
"pinia": "^2.0.25",
"qs": "^6.11.0",
"vue": "^3.2.41",
"vue-router": "^4.1.6",
"vue3-colorpicker": "^2.0.11"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"less": "^4.1.3",
"typescript": "^4.6.4",
"unplugin-vue-components": "^0.22.11",
"vite": "^3.2.3",
"vue-tsc": "^1.0.9"
}
}

BIN
public.zip

Binary file not shown.

1
public/vite.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

58
src/App.vue

@ -0,0 +1,58 @@
<template>
<router-view></router-view>
<audio
class="audio"
src=""
@timeupdate="timeupdatefn"
style="display:none;"
></audio>
</template>
<script setup lang="ts">
// @ts-nocheck
import { useStore } from '@/store';
import { storeToRefs } from 'pinia';
import MqttUnit from "@/utils/mqttunit";
import { onMounted } from 'vue';
import {WebRtcStreamer} from '@/utils/webrtc-streamer/webrtcstreamer.js'
let pinia = useStore()
onMounted(() => {
let client = new MqttUnit().mqttInit();
let WebRtcStreamerRt=WebRtcStreamer()
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
window.WebRtcStreamer = WebRtcStreamerRt;
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = WebRtcStreamerRt;
}
client.on('message', (topic: string, message: Uint8Array) => {
const utf8decoder = new TextDecoder();
const msgString = utf8decoder.decode(message);
if (topic.startsWith("/server/warning")) {
// $mitt.emit("warnInfo", msgString);
console.log(msgString);
}
})
})
function timeupdatefn(e:any) {
let {audioCount,audioCurrentCount}=storeToRefs(pinia)
if (e.target.currentTime >= e.target.duration) {
let count_ = audioCurrentCount.value + 1;
pinia.updateaudioCurrentCount(count_)
if (audioCurrentCount.value < audioCount.value) {
e.target.currentTime = 0;
e.target.play();
}
}
}
</script>
<style lang="less">
body{
height: 100vh;
overflow: hidden;
}
</style>

BIN
src/assets/audio/clickMouse.mp3

Binary file not shown.

BIN
src/assets/audio/clickMouse2.mp3

Binary file not shown.

BIN
src/assets/audio/excessive.mp3

Binary file not shown.

BIN
src/assets/audio/notify.wav

Binary file not shown.

BIN
src/assets/audio/police.wav

Binary file not shown.

BIN
src/assets/audio/pop.wav

Binary file not shown.

BIN
src/assets/images/dialog.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

1
src/assets/images/vite.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

43
src/axios/action.ts

@ -0,0 +1,43 @@
import { axios } from "@/axios/request";
import { AxiosPromise } from "axios";
//get
export function getAction (url: string, parameter?: unknown): AxiosPromise
{
return axios({
url: url,
method: 'get',
params: parameter
})
}
//post
export function postAction (url: string, parameter?: unknown): AxiosPromise
{
return axios({
url: url,
method: 'post',
data: parameter
})
}
//put
export function putAction (url: string, parameter?: unknown): AxiosPromise
{
return axios({
url: url,
method: 'put',
data: parameter
})
}
//delete
export function deleteAction (url: string, parameter?: unknown): AxiosPromise
{
return axios({
url: url,
method: 'delete',
params: parameter
})
}

436
src/axios/api.ts

@ -0,0 +1,436 @@
import { deleteAction, getAction, postAction, putAction } from '@/axios/action'
import { AxiosPromise } from 'axios';
// import {
// DEVICE_STREAM_TYPE,
// NET_PROTO_TYPE,
// PTZ_CONTROL_ACTION,
// STREAM_TRANS_TYPE,
// STREAM_TYPE
// } from '@/utils/emun/videoControlEnum';
const url = {
camera: {
getCameraList: '/area/api/getCameraList',
// getCameraList: '/area/api/cameraByToken?token=Bearer eyJ' +
// 'hbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pb' +
// 'iIsInN1YiI6InNvbWVib2R5IiwiYXVkIjpbInNvbWVib2R5X2Vs' +
// 'c2UiXSwiZXhwIjoxNjYyNzE1MDYyLCJuYmYiOjE2NjI2Mjg2NjI' +
// 'sImlhdCI6MTY2MjYyODY2MiwianRpIjoiMSJ9.0GyQ1qZZPcVOT' +
// 'jNYe7NgsZYwKaMOGRkb-itK9fqC-Ug',
},
/* 监控点模块 */
label: {
addLabel: '/military/msAreaLabel/add',
getLabel: '/military/msAreaLabel/list',
deleteCamera: '/military/msAreaLabel/deleteCamera',
editLabel: '/military/msAreaLabel/edit'
},
areaFence: {
add: "/military/msAreaFence/add",
//添加防区及相机
addAndDevice: "/military/msAreaFence/addFenceAndRelation",
//获取预置点接口
selectPreset: "area/api/selectPreset",
fenceList: "/military/msAreaFence/list",
queryFenceById: "/military/msAreaFence/queryFenceAndeRelationById",
deleteFence: "/military/msAreaFence/deleteFenceAndRelation",
editFence: "/military/msAreaFence/editFenceAndRelation",
},
cameraVideo: {
addCameraVideo: '/military/msAreaCameraVideo/add',
getCameraVideoList: '/military/msAreaCameraVideo/list',
getCameraVideoById: '/military/msAreaCameraVideo/queryByLabelId',
editCameraVideo: '/military/msAreaCameraVideo/edit',
},
dict: {
getBuilding: '/sys/dict/getDictItems/ms_building_info',
},
path: {
addRoamPath: '/military/msAreaRoamPath/add',
RoamPathList: '/military/msAreaRoamPath/list',
deleteRoamPath: '/military/msAreaRoamPath/delete',
editRoamPath: '/military/msAreaRoamPath/edit',
msAreaCustomPrimitiveList: '/military/msAreaCustomPrimitive/list',
msAreaCustomPrimitiveDel: '/military/msAreaCustomPrimitive/delete',
msAreaCustomPrimitiveEdit: '/military/msAreaCustomPrimitive/edit'
},
patrolVideo: {
addPatrolVideo: '/military/msAreaCustomPrimitive/add',
},
videoControl: {
getRealPlayUrl: 'area/api/getRealPlayUrl',
ptzControl: 'area/api/ptzControl',
},
device: {
list: '/military/msDeviceInfo/list',
},
warn: {
//监控报警信息(从中光学获取)
getAlarmInfos: '/area/api/qryAlarmInfos',
getAlarmCount: '/area/api/countAlarm',
//报警信息(本地获取)
getWarnList: '/warn/list',
getWarnCount: '/warn/getWarnCount',
getFenceWarn: "/military/msDeviceInfo/downCmd"
},
haplochromatization: {
list: '/military/msAreaHaplochromatization/list',
add: '/military/msAreaHaplochromatization/add',
edit: '/military/msAreaHaplochromatization/edit',
delete: '/military/msAreaHaplochromatization/delete',
deleteBatch: '/military/msAreaHaplochromatization/deleteBatch'
},
delCache: '/area/api/delCache',
// 添加模型
models: {
addModel: "/models/msModels/add",
selectModel: "/models/msModels/list",
editModel: "/models/msModels/edit",
//批量删除
deleteModels: "/models/msModels/deleteBatch",
deleteModel: "/models/msModels/delete",
},
// 测试登录
login: 'http://127.0.0.1:8088/',
loginChecked: 'http://127.0.0.1:8088/checked',
/* 看板模块 */
board: {
//看板
getBoard: '/api/rpt/sensorbydevicecode2',
//看板编号
getBoardType: "/api/rpt/boardtype",
//看板信息
getBoardInfo: "/models/msModelsBoardApi/list",
//看板数据接口
getBoardData: '/api/rpt/boarddata',
},
/* iot设备 */
iotDevice: {
getNode: "/api/iot/nodelist",
getSensor: "/api/iot/sensorlist",
getSensorInfo: "/api/iot/sensorbyid",
},
modelsinfo: {
getModels3DGroup: '/api/models/models3dgroup',
getModels3D: '/api/models/modelsinfo3d',
getModelsImageGroup: '/api/models/modelsimagegroup',
getModelsImage: '/api/models/modelsinfoimage',
}
}
interface LabelEntity {
labelAttr: number,
labelName: string,
labelLon: number,
labelLat: number,
labelHeight: number,
labelStatus: number,
labelImgUrl: string,
deviceUid: string,
cameraVideoFlag: number,
belongTo: string,
}
interface LabelQueryList {
labelStatus: number,
id?: string,
belongTo?: string,
pageNo?: number,
pageSize?: number,
// selectArea?: string,
}
interface CameraVideoEntity {
lon: number,
lat: number,
height: number,
yaw: number,
pitch: number,
roll: number,
fovh: number,
fovv: number,
near: number,
far: number,
videourl: string,
maskurl: string,
// 相机id
labelId: string,
}
interface CameraVideoQueryList {
labelId: string,
}
type PatrolVideo = {
/**经度*/
lon: number,
/**纬度*/
lat: number;
/**高度*/
height: number;
/**偏航角*/
yaw: number;
/**俯仰角*/
pitch: number;
/**翻转角*/
roll: number;
/**漫游路径id*/
pathId: string;
/**相机设备Id*/
deviceUid: string;
}
// 云台控制
// 获取摄像头实时视频播放地址 参数
// interface GetRealPlayUrlParams {
// // 摄像机UID
// deviceId: string,
// // 传输协议
// streamTransType: STREAM_TRANS_TYPE,
// // 码流
// streamType: STREAM_TYPE,
// // 是否转hls 默认false
// enableHls: boolean,
// // 是否mp4录制 默认false
// enableMp4: boolean,
// // 拉流时,拉流方式 默认 0:tcp
// netProto: NET_PROTO_TYPE,
// // 拉流超时时间,单位秒 默认10
// timeoutSec: number,
// // 失败时重试的次数
// retryCount: number,
// // 传输协议 (添加海康私有流时使用该字段:6-TRANS_PROTO_PRIVATE)
// deviceStreamType: DEVICE_STREAM_TYPE,
// }
// // 云台控制 参数
// interface PtzControlParams {
// deviceId: string,
// // 云台控制命令 (0-199)
// ptzCommand: PTZ_CONTROL_ACTION,
// // 云台控制参数(云台速度)
// arguments: number,
// }
interface AlarmInfoParams {
deviceId: string,
startTime: string,
stopTime: string,
eventType?: number,
targetType?: number,
pageIndex: number,
pageSize: number,
alarmLevel?: number,
alarmMode?: number
}
interface AlarmCountParams {
deviceId: string,
startTime: string,
stopTime: string,
eventType: number,
targetType: number,
dateUnit: number
}
interface DeviceParams {
deviceType: number,
}
interface WarnInfoParams {
cameraId: string,
column?: string,
order?: string,
pageNo?: number,
pageSize?: number
}
interface WarnCountParams {
cameraId: string,
startTime: string,
endTime: string,
warnType?: number,
warnLevel?: number
}
interface HaplochromatizationParams {
id: string,
name: string,
positions: string,
height: number,
extrudedHeight: number,
color: string,
showobj: number,
showhelper: number,
belongTo: string | null,
}
interface Models {
"createBy"?: string,
"createTime"?: string,
"height"?: number,
"id"?: string,
"isShow"?: number,
"lat"?: number,
"lon"?: number,
"modelCode"?: string,
"modelGroup"?: string,
"modelName"?: string,
"modelSceneCode"?: string,
"modelType"?: string,
"modelUrl1"?: string,
"modelUrl2"?: string,
"modelX"?: number,
"modelY"?: number,
"modelZ"?: number,
"pitch"?: number,
"roll"?: number,
"sysOrgCode"?: string,
"template"?: string,
"updateBy"?: string,
"updateTime"?: string,
"url"?: string,
"urlcondition"?: string,
"yaw"?: number,
"pageNo"?: number,
"pageSize"?: number
"boardNo"?: string
"boardType"?: string
}
class Api {
/* 相机模块 */
public static camera = {
getCameraList: (params?: { token: string }): AxiosPromise => getAction(url.camera.getCameraList, params),
}
/* 监控点模块 */
public static label = {
addLabel: (params: LabelEntity | any): AxiosPromise => postAction(url.label.addLabel, params),
getLabel: (params: LabelQueryList): AxiosPromise => getAction(url.label.getLabel, params),
deleteLabel: (params: { id: string }): AxiosPromise => deleteAction(url.label.deleteCamera, params),
editLabel: (params: LabelEntity): AxiosPromise => putAction(url.label.editLabel, params),
}
/* 视频融合模块 */
public static cameraVideo = {
addCameraVideo: (params?: CameraVideoEntity): AxiosPromise => postAction(url.cameraVideo.addCameraVideo, params),
getCameraVideoById: (params?: { labelId: string }): AxiosPromise => getAction(url.cameraVideo.getCameraVideoById, params),
getCameraVideoList: (params?: CameraVideoQueryList): AxiosPromise => getAction(url.cameraVideo.getCameraVideoList, params),
editCameraVideo: (params?: CameraVideoEntity): AxiosPromise => putAction(url.cameraVideo.editCameraVideo, params),
deleteCameraVideo: (params?: { id: string }): AxiosPromise => deleteAction(url.cameraVideo.editCameraVideo, params),
}
/* 数据字典 */
public static dict = {
getBuilding: (): AxiosPromise => getAction(url.dict.getBuilding),
}
/* 巡检模块 */
public static path = {
addRoamPath: (params?: unknown): AxiosPromise => postAction(url.path.addRoamPath, params),
RoamPathList: (params?: { pageNo: number, pageSize: number, selectArea: string }): AxiosPromise => getAction(url.path.RoamPathList, params),
deleteRoamPath: (params?: unknown): AxiosPromise => deleteAction(url.path.deleteRoamPath, params),
editRoamPath: (params?: unknown): AxiosPromise => putAction(url.path.editRoamPath, params),
msAreaCustomPrimitiveList: (params?: unknown): AxiosPromise => getAction(url.path.msAreaCustomPrimitiveList, params),
msAreaCustomPrimitiveDel: (params?: unknown): AxiosPromise => deleteAction(url.path.msAreaCustomPrimitiveDel, params),
msAreaCustomPrimitiveEdit: (params?: unknown): AxiosPromise => putAction(url.path.msAreaCustomPrimitiveEdit, params),
}
/*巡检视频模块*/
public static patrolVideo = {
addPatrolVideo: (params?: PatrolVideo): AxiosPromise => postAction(url.patrolVideo.addPatrolVideo, params),
}
/* 云台控制模块 */
// public static videoControl = {
// // 获取摄像头实时视频播放地址
// getRealPlayUrl: (params?: GetRealPlayUrlParams): AxiosPromise => getAction(url.videoControl.getRealPlayUrl, params),
// // 云台控制
// ptzControl: (params?: PtzControlParams): AxiosPromise => getAction(url.videoControl.ptzControl, params),
// }
/* 设备信息模块*/
public static device = {
getDeviceList: (params?: DeviceParams): AxiosPromise => getAction(url.device.list, params)
}
/* 警示模块*/
public static warn = {
getAlarmInfos: (params?: AlarmInfoParams): AxiosPromise => getAction(url.warn.getAlarmInfos, params),
getAlarmCount: (params?: AlarmCountParams): AxiosPromise => getAction(url.warn.getAlarmCount, params),
getWarnList: (params?: WarnInfoParams): AxiosPromise => getAction(url.warn.getWarnList, params),
getWarnCount: (params?: WarnCountParams): AxiosPromise => getAction(url.warn.getWarnCount, params),
getFenceWarn: (params?: WarnCountParams): AxiosPromise => getAction(url.warn.getFenceWarn, params),
}
/* 缓存 */
public static cache = {
delCache: (): AxiosPromise => deleteAction(url.delCache),
}
/* 单体化 */
public static haplochromatization = {
list: (params?: unknown): AxiosPromise => getAction(url.haplochromatization.list, params),
add: (params?: HaplochromatizationParams): AxiosPromise => postAction(url.haplochromatization.add, params),
edit: (params?: HaplochromatizationParams): AxiosPromise => putAction(url.haplochromatization.edit, params),
delete: (params: { id: string }): AxiosPromise => deleteAction(url.haplochromatization.delete, params),
deleteBatch: (params: { ids: string }): AxiosPromise => deleteAction(url.haplochromatization.delete, params),
}
/* 模型 */
public static models = {
add: (params: Models): AxiosPromise => postAction(url.models.addModel, params),
list: (params: Models): AxiosPromise => getAction(url.models.selectModel, params),
edit: (params: Models): AxiosPromise => postAction(url.models.editModel, params),
deleteBatch: (params: { ids: string }): AxiosPromise => deleteAction(url.models.deleteModels, params),
delete: (params: { id: string }): AxiosPromise => deleteAction(url.models.deleteModel, params),
}
/* 用户登录 */
public static Login = {
login: (params: any): AxiosPromise => getAction(url.login, params),
loginChecked: (params: any): AxiosPromise => getAction(url.loginChecked, params),
}
/* 看板模块 */
public static board = {
getBoardList: (params?: any): AxiosPromise => getAction(url.board.getBoard, params),
getBoardTypeList: (params?: any): AxiosPromise => getAction(url.board.getBoardType, params),
getBoardInfo: (params?: any): AxiosPromise => getAction(url.board.getBoardInfo, params),
getBoardDataList: (params: { boardtype: string }): AxiosPromise => getAction(url.board.getBoardData, params),
}
/* 防区 */
public static areaFence = {
add: (params?: any): AxiosPromise => postAction(url.areaFence.add, params),
addAndDevice: (params?: any): AxiosPromise => postAction(url.areaFence.addAndDevice, params),
selectPreset: (params?: any): AxiosPromise => getAction(url.areaFence.selectPreset, params),
getFenceList: (params?: any): AxiosPromise => getAction(url.areaFence.fenceList, params),
queryFenceById: (params?: { id: string }): AxiosPromise => getAction(url.areaFence.queryFenceById, params),
deleteFence: (params: { id: string }): AxiosPromise => deleteAction(url.areaFence.deleteFence, params),
editFence: (params: any): AxiosPromise => putAction(url.areaFence.editFence, params),
}
/* 相机模块 */
public static modelsinfo = {
getModels3DGroup: (params?: { token: string }): AxiosPromise => getAction(url.modelsinfo.getModels3DGroup, params),
getModels3D: (params?: { token: string }): AxiosPromise => getAction(url.modelsinfo.getModels3D, params),
getModelsImageGroup: (params?: { token: string }): AxiosPromise => getAction(url.modelsinfo.getModelsImageGroup, params),
getModelsImage: (params?: { token: string }): AxiosPromise => getAction(url.modelsinfo.getModelsImage, params),
}
/*iot设备*/
public static iotDevice = {
//设备列表
getNodeList: (params?: any): AxiosPromise => getAction(url.iotDevice.getNode, params),
//传感器列表信息
getSensorList: (params?: { nodeid: string }): AxiosPromise => getAction(url.iotDevice.getSensor, params),
//传感器
getSensorInfo: (params?: { id: string }): AxiosPromise => getAction(url.iotDevice.getSensorInfo, params),
}
}
export default Api
export {
Api,
// LabelEntity,
// LabelQueryList,
// CameraVideoEntity,
// CameraVideoQueryList,
// GetRealPlayUrlParams,
// PtzControlParams,
// HaplochromatizationParams,
// AlarmInfoParams,
// AlarmCountParams,
}

26
src/axios/cameraBase/cameraApi.ts

@ -0,0 +1,26 @@
import {axios} from '@/utils/axios';
import {apiUrl} from "@/axios";
import qs from "qs";
enum Api {
Add = '/cbCamera/add',
Update = '/cbCamera/update',
Delete = '/cbCamera/delete',
Get = '/cbCamera/get',
GetList = '/cbCamera/getList',
GetPageList = '/cbCamera/getPageList',
}
const CisApiUrl = apiUrl.CisApiUrl;
export const Add = (params?: any) => axios.post(CisApiUrl + Api.Add, qs.stringify(params))
export const Update = (params?: any) => axios.post(CisApiUrl + Api.Update, qs.stringify(params))
export const Delete = (params?: any) => axios.post(CisApiUrl + Api.Delete, qs.stringify(params))
export const Get = (params?: any) => axios.get(CisApiUrl + Api.Get, {params: params})
export const GetList = (params?: any) => axios.get(CisApiUrl + Api.GetList, {params: params})
export const GetPageList = (params?: any) => axios.get(CisApiUrl + Api.GetPageList, {params: params})

26
src/axios/cameraMark/markGroupApi.ts

@ -0,0 +1,26 @@
import {axios} from '@/utils/axios';
import {apiUrl} from "@/axios";
import qs from "qs";
enum Api {
Add = '/cmMarkGroup/add',
Update = '/cmMarkGroup/update',
Delete = '/cmMarkGroup/delete',
Get = '/cmMarkGroup/get',
GetList = '/cmMarkGroup/getList',
GetPageList = '/cmMarkGroup/getPageList',
}
const CisApiUrl = apiUrl.CisApiUrl;
export const Add = (params?: any) => axios.post(CisApiUrl + Api.Add, qs.stringify(params))
export const Update = (params?: any) => axios.post(CisApiUrl + Api.Update, qs.stringify(params))
export const Delete = (params?: any) => axios.post(CisApiUrl + Api.Delete, qs.stringify(params))
export const Get = (params?: any) => axios.get(CisApiUrl + Api.Get, {params: params})
export const GetList = (params?: any) => axios.get(CisApiUrl + Api.GetList, {params: params})
export const GetPageList = (params?: any) => axios.get(CisApiUrl + Api.GetPageList, {params: params})

29
src/axios/cameraMark/markLabelApi.ts

@ -0,0 +1,29 @@
import {axios} from '@/utils/axios';
import {apiUrl} from "@/axios";
import qs from 'qs';
enum Api {
Add = '/cmMarkLabel/add',
AddReturnId = '/cmMarkLabel/addReturnId',
Update = '/cmMarkLabel/update',
Delete = '/cmMarkLabel/delete',
Get = '/cmMarkLabel/get',
GetList = '/cmMarkLabel/getList',
GetPageList = '/cmMarkLabel/getPageList',
}
const CisApiUrl = apiUrl.CisApiUrl;
export const Add = (params?: any) => axios.post(CisApiUrl + Api.Add, qs.stringify(params))
export const AddReturnId = (params?: any) => axios.post(CisApiUrl + Api.AddReturnId, qs.stringify(params))
export const Update = (params?: any) => axios.post(CisApiUrl + Api.Update, qs.stringify(params))
export const Delete = (params?: any) => axios.post(CisApiUrl + Api.Delete+"?"+qs.stringify(params))
export const Get = (params?: any) => axios.get(CisApiUrl + Api.Get, {params: params})
export const GetList = (params?: any) => axios.get(CisApiUrl + Api.GetList, {params: params})
export const GetPageList = (params?: any) => axios.get(CisApiUrl + Api.GetPageList, {params: params})

29
src/axios/core/markSearchApi.ts

@ -0,0 +1,29 @@
import {axios} from '@/utils/axios';
import {apiUrl} from "@/axios";
import qs from 'qs';
enum Api {
ActivateSearcher = '/markSearch/activateSearcher',
DeActiveSearcher = '/markSearch/deActiveSearcher',
IsExistsSearcher = '/markSearch/isExistsSearcher',
ActivateMarkLabel = '/markSearch/activateMarkLabel',
DeactivateMarkLabel = '/markSearch/deactivateMarkLabel',
IsExistsMarkLabel = '/markSearch/isExistsMarkLabel',
GetMarkLabelCalcResultList = '/markSearch/getMarkLabelCalcResultList',
}
const CisApiUrl = apiUrl.CisApiUrl;
export const ActiveSearcher = (params?: any) => axios.post(CisApiUrl + Api.ActivateSearcher, qs.stringify(params))
export const DeActiveSearcher = (params?: any) => axios.post(CisApiUrl + Api.DeActiveSearcher, qs.stringify(params))
export const IsExistsSearcher = (params?: any) => axios.get(CisApiUrl + Api.IsExistsSearcher, {params: params})
export const ActivateMarkLabel = (params?: any) => axios.post(CisApiUrl + Api.ActivateMarkLabel, qs.stringify(params))
export const DeactivateMarkLabel = (params?: any) => axios.post(CisApiUrl + Api.DeactivateMarkLabel, qs.stringify(params))
export const IsExistsMarkLabel = (params?: any) => axios.get(CisApiUrl + Api.IsExistsMarkLabel, {params: params})
export const GetMarkLabelCalcResultList = (params?: any) => axios.get(CisApiUrl + Api.GetMarkLabelCalcResultList, {params: params})

6
src/axios/index.ts

@ -0,0 +1,6 @@
export const apiUrl = {
CisApiUrl: 'http://192.168.1.119:800/api',
WebRtcUrl: 'http://192.168.1.119:8000',
OnvifApiUrl: ''
// CisApiUrl: 'https://192.168.1.119:5001/api'
}

1
src/axios/onvif/onvif.ts

@ -0,0 +1 @@
export default{}

114
src/axios/request.ts

@ -0,0 +1,114 @@
import axios, { AxiosResponse, AxiosRequestConfig, Canceler } from 'axios'
import { message, notification } from 'ant-design-vue'
/**
* http请求工具类
*
* token值存储并放置在头部提交给服务端
*
*/
// 开启跨域
axios.defaults.withCredentials = true
// 创建 axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL, // api base_url
timeout: 30 * 1000, // 请求超时时间
// withCredentials: true, // 跨域请求时发送 cookies
// headers: {
// "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
// },
});
const ACCESS_TOKEN = 'Access-Token'
//请求失败后的错误统一处理
const errorHandle = (error: any) => {
if (error.response) {
const data: any = error.response.data
const token: string | null = localStorage.getItem(ACCESS_TOKEN)
console.log("------异常响应------", token)
console.log("------异常响应------", error.response.status)
switch (error.response.status) {
case 401:
message.error("登录状态失效,请重新登录");
break
case 403:
message.error("拒绝访问");
break
case 404:
message.error("资源不存在");
break
case 405:
message.error("请求类型有误");
break
case 408:
message.error("请求超时");
break
case 415:
message.error("服务器无法处理请求附带的媒体格式");
break
case 500:
message.error("服务器内部错误");
break
case 502:
message.error("网关错误");
break;
case 503:
message.error("服务不可用");
break;
case 504:
message.error("网关超时");
break
default:
notification.error({
message: '系统提示',
description: data.message,
duration: 4
})
break
}
}
return Promise.reject(error)
};
// 取消请求 cancelToken
const CancelToken = axios.CancelToken;
// 请求拦截
service.interceptors.request.use((config: AxiosRequestConfig) => {
// 设置 token 到请求头
const token: string | null = localStorage.getItem(ACCESS_TOKEN)
if (token) {
axios.defaults.headers.post['X-Access-Token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
}
// 设置取消请求
config.cancelToken = new CancelToken((cancel: Canceler) => {
const url = config.url;
let str = url?.split('/');
str = Array.from(new Set(str));
const index = str.length - 1;
const funName = str[index];
// store.dispatch('setCancel', { cancel, funName: funName }).catch(err => {
// console.log('存储 CancelToken 出错:', err);
// });
})
return config;
}, err => {
Promise.reject(err).catch(r => {
console.log('请求拦截器出错:', r);
});
});
// 响应拦截
service.interceptors.response.use((response: AxiosResponse) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// store.dispatch("response", response).catch(err => {
// console.log('移除 CancelToken 出错:', err);
// });
return response.data
}, errorHandle)
export {
service as axios
}

0
src/axios/webrtcStreamer/webrtcStreamer.ts

34
src/components/aside/index.vue

@ -0,0 +1,34 @@
<!--
*@描述: admin主页构建
*@作者
*@日期 9/27
*@版本1.0
*/
-->
<template>
<a-row>
<a-col :span="3" class="left-com" >
<comLeftMenu></comLeftMenu>
</a-col>
<a-col :span="18" class=" com-setup-center ">
<slot></slot>
</a-col>
<a-col :span="3" class="right-com" ><comRightMenu></comRightMenu></a-col>
</a-row>
</template>
<script setup lang="ts">
import comLeftMenu from './leftMenu/index.vue';
import comRightMenu from './rightMenu/index.vue';
</script>
<style lang="less" scoped>
.com-setup-center {
height: 100vh;
width: 100%;
flex-wrap: nowrap;
}
.left-com,.right-com{
position:relative;
z-index:8;
}
</style>

124
src/components/aside/leftMenu/index.vue

@ -0,0 +1,124 @@
<!--
*@描述: 左侧组件
*@作者
*@日期 9/27
*@版本1.0
*/
-->
<template>
<a-row class="com-left-menu-header">
<span>工具栏</span>
</a-row>
<hr style="border:1px solid #00ffe4;" width="100%" />
<a-row class="com-left-menu">
<a-tabs v-model:activeKey="activeKey" :tab-position="'left'">
<a-tab-pane key="1">
<template #tab>
<!-- <span class="icon iconfont icon-gongneng iconfontsize"></span> -->
<div class="menu-title">
功能
</div>
</template>
<Basic />
</a-tab-pane>
<a-tab-pane key="2">
<template #tab>
<!-- <span class="icon iconfont icon-chanpinmoxing iconfontsize"></span> -->
<div class="menu-title">
3D模型
</div>
</template>
<Models3D />
</a-tab-pane>
<a-tab-pane key="3">
<template #tab>
<!-- <span class="icon iconfont icon-chanpinmoxing iconfontsize"></span> -->
<div class="menu-title">
图片<br />模型
</div>
</template>
<ModelsImages />
</a-tab-pane>
<a-tab-pane key="4">
<template #tab>
<!-- <span class="icon iconfont icon-shengchan iconfontsize"></span> -->
<div class="menu-title">
工具
</div>
</template>
<Tools />
</a-tab-pane>
<a-tab-pane key="5">
<template #tab>
<!-- <span class="icon iconfont icon-shezhi1 iconfontsize"></span> -->
<div class="menu-title">
系统<br />功能
</div>
</template>
<SytemComponent />
</a-tab-pane>
</a-tabs>
</a-row>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import Basic from "./leftMenuItem/basic.vue";
import ModelsImages from "./leftMenuItem/modelsimages.vue";
import Models3D from "./leftMenuItem/models3d.vue";
import SytemComponent from "./leftMenuItem/sytemcomponent.vue";
import Tools from "./leftMenuItem/tools.vue";
let activeKey = ref('1')
</script>
<style lang="less" scoped>
.com-left-menu {
user-select: none;
height: 100vh;
background-color: #001529;
>div,:deep(.ant-tabs-tab-btn) {
width: 100%;
}
.menu-title{
text-align: center;
}
}
.com-left-menu-header {
padding-top: 10px;
user-select: none;
height: 40px;
background-color: #001529;
text-align: center;
width: 100%;
border-width: 2px;
border-bottom: #fff;
span {
margin-left: 40px;
width: 100%;
color: #fff;
font-size: 18px;
text-align: left;
}
}
:deep(.ant-tabs-tab) {
text-align: center !important;
padding: 12px 0 !important;
}
:deep(.ant-tabs-left-bar) {
width: calc(6 / 24 * 100%);
}
:deep(.ant-tabs-left-content.ant-tabs-content.ant-tabs-content-animated),
:deep(.ant-tabs-tabpane) {
padding-left: 0 !important;
}
.iconfontsize {
font-size: 28px;
}
</style>

77
src/components/aside/leftMenu/leftMenuItem/basic.vue

@ -0,0 +1,77 @@
<template>
<div class="left-com-content">
<a-row>
<a-col class="content-item" title="添加标签" @click="addLabel">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-biaoqian"></span>
</i>
</a>
<div class="item-text">添加标签</div>
</a-col>
<div style="background: aqua; height: 50%">
<a-tree style="background: aqua;" :tree-data="treeData" @select="selectTreeNode" />
</div>
</a-row>
</div>
</template>
<script setup lang='ts'>
import { audio } from "@/utils/audio";
import { useStore } from '@/store/index';
import {storeToRefs} from 'pinia';
import type { TreeProps } from 'ant-design-vue';
import * as cameraApi from '@/axios/cameraBase/cameraApi';
import { ref,onMounted } from 'vue';
let piniaStore = useStore();
let cameraMap = new Map<string, any>()
let treeData = ref<TreeProps['treeData']>([])
let curSelectKey=storeToRefs(piniaStore).curSelectKey
function loadTreeData() {
cameraApi.GetList().then((res: any) => {
let list: Array<any> = res.data.data;
console.log(list);
cameraMap = piniaStore.addCameraMap(list)
treeData.value=piniaStore.treeData
console.log(cameraMap);
});
}
onMounted(()=>{
loadTreeData()
})
function selectTreeNode(selectedKeys: any, e: any) {
if (!e.selected) return;
let key = selectedKeys[0];
if (curSelectKey.value !== key) {
piniaStore.updateCurSelectKey(key)
}
}
//
function playAudio(): void {
audio.click();
}
//
function addLabel(e: string) {
playAudio();
}
</script>
<style lang='less'>
.left-com-content {
.content-item {
cursor: pointer;
text-align: center;
padding: 5px 0;
margin: 1%;
width: 48%;
.fa span {
font-size: 25px;
}
}
}
</style>

128
src/components/aside/leftMenu/leftMenuItem/models3d.vue

@ -0,0 +1,128 @@
<template>
<div class="left-com-content">
<a-collapse v-model:activeKey="activeKey" accordion>
<a-collapse-panel v-for="item in header" :key="item.id" :header="item.group_name">
<div class="img-box">
<div class="img" v-for="ele in item.itemArr" @click="addModelFn(ele.url,ele.oldUrl)">
<img draggable="false" class="collapse-panel-img" width="36" height="36" :src="ele.dis_img"
:alt="ele.model_name">
</div>
</div>
</a-collapse-panel>
</a-collapse>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import api from "@/axios/api";
import { useStore } from '@/store/index';
let piniaStore = useStore();
const activeKey = ref([]);
let header: any = ref([])
//
let baseUrl = process.env.VUE_APP_API_BASE_URL + "/sys/common/static/";// api base_urll
let p1 = api.modelsinfo.getModels3DGroup( ).then(res =>
{
return res.data;
})
let p2 = api.modelsinfo.getModels3D().then(res =>
{
return res.data;
})
Promise.all([p1, p2]).then(res =>
{
let headerArr = res[0]
let itemArr = res[1]
//
headerArr.forEach((element: any, index: number) =>
{
//
element.itemArr = []
header.value.push(element)
itemArr?.forEach((item: any) =>
{
//
if (element.group_name == item.model_group_name)
{
item.oldUrl = item.url
item.url = baseUrl+item.url
item.dis_img = baseUrl + item.previewurl
header.value[index].itemArr.push(item)
}
})
});
})
let cus: any = null;
function addModelFn (url: string,img:string): void
{
cus.creating = true
cus.img = img
piniaStore.editType = 30;
}
</script>
<style scoped lang='less'>
.left-com-content {
.content-item {
cursor: pointer;
padding: 5px 0;
margin: 1%;
width: 48%;
.fa span {
font-size: 25px;
}
}
}
:deep(.ant-collapse-content-box) {
padding: 16px 10px;
}
.img-box {
text-align: left;
display: flex;
flex-wrap: wrap;
.img {
flex-basis: 1 / 3 * 100%;
text-align: center;
.collapse-panel-img {
padding: 2px;
&:hover {
border: 1px solid #001529;
cursor: pointer;
}
}
}
}
:deep(.ant-collapse) {
background-color: #001529;
border: #fff;
.ant-collapse-item {
.ant-collapse-header {
color: #fff !important;
span {
color: #fff;
}
}
}
}
</style>

118
src/components/aside/leftMenu/leftMenuItem/modelsimages.vue

@ -0,0 +1,118 @@
<template>
<div class="left-com-content">
<a-collapse v-model:activeKey="activeKey" accordion>
<a-collapse-panel v-for="item in header" :key="item.id" :header="item.group_name">
<div class="img-box">
<div class="img" v-for="ele in item.itemArr" @click="addImage(ele.dis_img,ele.url)">
<img draggable="false" class="collapse-panel-img" width="36" height="36" :src="ele.dis_img"
:alt="ele.model_name">
</div>
</div>
</a-collapse-panel>
</a-collapse>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import api from "@/axios/api";
import { useStore } from '@/store/index';
let piniaStore = useStore();
const activeKey = ref([]);
let header: any = ref([])
//
let baseUrl = process.env.VUE_APP_API_BASE_URL + "/sys/common/static/"; // api base_urll
let p1 = api.modelsinfo.getModelsImageGroup().then(res => {
return res.data;
})
let p2 = api.modelsinfo.getModelsImage().then(res => {
return res.data;
})
Promise.all([p1, p2]).then(res => {
let headerArr = res[0]
let itemArr = res[1]
//
headerArr.forEach((element: any, index: number) => {
//
element.itemArr = []
header.value.push(element)
itemArr?.forEach((item: any) => {
//
if (element.group_name == item.model_group_name) {
item.dis_img = baseUrl + item.url
header.value[index].itemArr.push(item)
}
})
});
})
let cus: any = null;
function addImage(imgstr: string,img:string): void {
cus.creating = true
cus.img = img
piniaStore.editType = 2;
}
</script>
<style scoped lang='less'>
.left-com-content {
.content-item {
cursor: pointer;
padding: 5px 0;
margin: 1%;
width: 48%;
.fa span {
font-size: 25px;
}
}
}
:deep(.ant-collapse-content-box) {
padding: 16px 10px;
}
.img-box {
text-align: left;
display: flex;
flex-wrap: wrap;
.img {
flex-basis: 1 / 3 * 100%;
text-align: center;
.collapse-panel-img {
padding: 2px;
&:hover {
border: 1px solid #001529;
cursor: pointer;
}
}
}
}
:deep(.ant-collapse) {
background-color: #001529;
border: #fff;
.ant-collapse-item {
.ant-collapse-header {
color: #fff !important;
span {
color: #fff;
}
}
}
}
</style>

89
src/components/aside/leftMenu/leftMenuItem/sytemcomponent.vue

@ -0,0 +1,89 @@
<template>
<div class="left-com-content">
<a-row>
<a-col class="content-item" @click="haplochromatizationListEvent" title="单体化列表">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-moxing-miaobian"></span>
</i>
</a>
<div class="item-text">单体化列表</div>
</a-col>
<a-col class="content-item" @click="addhaplochromatization" title="单体化">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-sanweimoxing"></span>
</i>
</a>
<div class="item-text">单体化</div>
</a-col>
<a-col class="content-item" title="防区列表" @click="fenceListFn">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-liebiao2"></span>
</i>
</a>
<div class="item-text">防区列表</div>
</a-col>
<a-col class="content-item" title="添加防区" @click="addDefenceArea">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-24gl-fence"></span>
</i>
</a>
<div class="item-text">添加防区</div>
</a-col>
</a-row>
</div>
</template>
<script setup lang='ts'>
import { useStore } from '@/store/index';
import { audio } from "@/utils/audio";
let piniaStore = useStore();
function haplochromatizationListEvent(): void {
playAudio();
}
function addhaplochromatization(): void {
piniaStore.editType = 501
}
function fenceListFn() {
playAudio();
}
//
function addDefenceArea(): void {
playAudio();
piniaStore.editType = 502
}
//
function playAudio(): void {
audio.click();
}
</script>
<style scoped lang='less'>
.left-com-content {
.content-item {
cursor: pointer;
padding: 5px 0;
margin: 1%;
width: 48%;
.fa span {
font-size: 25px;
}
}
}
</style>

100
src/components/aside/leftMenu/leftMenuItem/tools.vue

@ -0,0 +1,100 @@
<template>
<div class="left-com-content">
<a-row>
<a-col class="content-item" title="位置测量" @click="measureMent('POINT')">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-31dingwei"></span>
</i>
</a>
<div class="item-text">位置测量</div>
</a-col>
<a-col class="content-item" title="距离测量" @click="measureMent('SPACE_DISTANCE')">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-chizi"></span>
</i>
</a>
<div class="item-text">距离测量</div>
</a-col>
<a-col class="content-item" title="三角测量" @click="measureMent('TRIANGLE_DISTANCE')">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-celiangleixing"></span>
</i>
</a>
<div class="item-text">三角测量</div>
</a-col>
<a-col class="content-item" title="面积测量" @click="measureMent('SPACE_AREA')">
<a class="btn btnColor">
<i class="fa" style="font-size:35px">
<span class="icon iconfont icon-mianjiceliang"></span>
</i>
</a>
<div class="item-text">面积测量</div>
</a-col>
<a-col class="content-item" title="属性设置" @click="setProperties">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-shezhi"></span>
</i>
</a>
<div class="item-text">属性设置</div>
</a-col>
<a-col class="content-item" title="清空测量结果" @click="clearResults">
<a class="btn btnColor">
<i class="fa">
<span class="icon iconfont icon-Eliminate" style="color: #D35400;"></span>
</i>
</a>
<div class="item-text" style="color: #D35400;">清空测量</div>
</a-col>
</a-row>
</div>
</template>
<script setup lang='ts'>
import { ref, h, onMounted, getCurrentInstance, ComponentInternalInstance } from "vue";
import { audio } from "@/utils/audio";
let measureMentType = ref("NONE");
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
onMounted(() => {
})
//
function playAudio(): void {
audio.click();
}
//
function setProperties(): void {
playAudio();
}
//
function clearResults(): void {
measureMentType.value = "NONE";
}
function measureMent(Type: string) {
measureMentType.value = Type;
}
</script>
<style scoped lang='less'>
.left-com-content {
.content-item {
cursor: pointer;
padding: 5px 0;
margin: 1%;
width: 48%;
.fa span {
font-size: 25px;
}
}
}
</style>

40
src/components/aside/rightMenu/index.vue

@ -0,0 +1,40 @@
<!--
*@描述: 右侧面板 基本信息-数据绑定
*@作者
*@日期
*@版本1.0
*/
-->
<template>
<div class="rightMenu-class">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="首页">
暂无信息
</a-tab-pane>
<a-tab-pane key="2" tab="首页2">
暂无信息2
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
let activeKey = ref('1')
</script>
<style lang="less" scoped>
.rightMenu-class {
user-select: none;
overflow: auto;
height: 100vh;
background-color: #001529;
:deep(.ant-tabs-nav-list) {
margin: 0 auto;
padding: 0 20px;
}
:deep(.ant-tabs-tabpane){
text-align: center;
}
}
</style>

16
src/main.ts

@ -0,0 +1,16 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router/index"
import 'ant-design-vue/dist/antd.css';
import { createPinia } from 'pinia';
import Antd from 'ant-design-vue';
const pinia = createPinia()
const app=createApp(App)
app.use(router)
app.use(pinia)
app.use(Antd)
app.mount('#app')

7
src/router/index.ts

@ -0,0 +1,7 @@
import { createRouter, createWebHistory } from "vue-router"
import routes from "./routes"
var router=createRouter({
history:createWebHistory(),
routes
})
export default router

8
src/router/routes.ts

@ -0,0 +1,8 @@
const routes = [
{
name: "",
path: "/",
component: () => import("../views/index.vue")
}
]
export default routes;

35
src/store/index.ts

@ -0,0 +1,35 @@
import { defineStore } from 'pinia';
export const useStore = defineStore('Index', {
state: () => {
return {
curSelectKey:"",
cameraMap:new Map<string, any>(),
treeData:<any[]>[],
audioCount: 0,
audioCurrentCount: 0,
editType:0
}
},
// 也可以定义为
// state: () => ({ count: 0 })
actions: {
addCameraMap(list:any){
this.treeData=[]
for (let item of list) {
this.cameraMap.set(item.id, item);
this.treeData.push({title: item.ip, key: item.id});
}
return this.cameraMap
},
updateCurSelectKey(key:string){
this.curSelectKey=key
},
updateaudioCurrentCount(num: number) {
this.audioCurrentCount = num
},
updateaudioCount(num: number) {
this.audioCount = num
},
},
})

15
src/style.css

@ -0,0 +1,15 @@
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
hr{
margin: 0;
}

2
src/types/index.d.ts

@ -0,0 +1,2 @@
declare module 'mqtt/dist/mqtt.min'
declare module 'qs'

39
src/utils/audio.ts

@ -0,0 +1,39 @@
import { nextTick } from 'vue'
import { useStore } from '@/store/index';
import pop from '@/assets/audio/pop.wav'
import police from '@/assets/audio/police.wav'
import excessive from '@/assets/audio/excessive.mp3'
function audioPlay (url: any, count: number = 1)
{
let store = useStore()
store.updateaudioCurrentCount(0)
store.updateaudioCount(count)
nextTick(() =>
{
let audio_: any = document.querySelector(".audio");
audio_.src = url
audio_.play();
})
}
class AudioUnit
{
//播放音效
click (): void
{
audioPlay(pop, 1);
}
warning (): void
{
audioPlay(police, 1);
}
fly (): void
{
audioPlay(excessive, 1);
}
}
export const audio: AudioUnit = new AudioUnit();

10
src/utils/axios.ts

@ -0,0 +1,10 @@
import axios from "axios";
const service = axios.create({
timeout: 9000, // 请求超时时间
});
export {
service as axios,
}

9
src/utils/message.ts

@ -0,0 +1,9 @@
import { message } from "ant-design-vue";
const Msg = message;
//message全局配置
Msg.config({
top: '200px',
duration: 3,
maxCount: 3,
})
export default Msg;

68
src/utils/mqttunit.ts

@ -0,0 +1,68 @@
import * as mqtt from "mqtt/dist/mqtt.min";
const host = process.env.VUE_APP_MQTT_HOST || "127.0.0.1";
const port = process.env.VUE_APP_MQTT_PORT || 8083;
const options = {//mqtt配置
// port: 8083,//连接端口
protocolId: 'MQTT',
connectTimeout: 4000,//超时时间
clientID: "mqtt_" + Math.random().toString(16).substr(2, 8),
username: 'admin',
password: 'public',
};
// 订阅的频道和事件
const topics: string = process.env.VUE_APP_MQTT_TOPICS || "/server/warning/+/zdgl";
const topicList = topics.split(',')
export default class MqttUnit
{
connectUrl = `ws://${host}:${port}/mqtt`
client = mqtt.connect(this.connectUrl, options);
mqttInit ()
{
// const connectUrl = `mqtt://${host}:${port}/mqtt`
this.client.on('connect', () =>
{
console.log(`MQTT Connection ${this.connectUrl} succeeded!`)
// 不会订阅多个频道,所以遍历对象,循环监听多个频道
for (let key of topicList)
{
this.client.subscribe(key, (err: any) =>
{
if (!err)
{
console.log(`订阅'${key}'成功`);
}
});
}
})
this.client.on('error', (error: any) =>
{
console.log('Connection failed', error)
})
/**
* topic: 监听的频道
* message: 返回的数据
*/
return this.client
// this.client.on('message', (topic: string, message: Uint8Array) => {
// const utf8decoder = new TextDecoder();
// const msgString = utf8decoder.decode(message);
// if (topic.startsWith("/server/warning")) {
// // $mitt.emit("warnInfo", msgString);
// console.log(msgString);
// }
// })
}
}

317
src/utils/webrtc-streamer/webrtcstreamer.js

@ -0,0 +1,317 @@
export let WebRtcStreamer = function () {
/**
* Interface with WebRTC-streamer API
* @constructor
* @param {string} videoElement - id of the video element tag
* @param {string} srvurl - url of webrtc-streamer (default is current location)
*/
var WebRtcStreamer = function WebRtcStreamer(videoElement, srvurl) {
if (typeof videoElement === "string") {
this.videoElement = document.getElementById(videoElement);
} else {
this.videoElement = videoElement;
}
this.srvurl = srvurl || location.protocol + "//" + window.location.hostname + ":" + window.location.port;
this.pc = null;
this.mediaConstraints = {offerToReceiveAudio: true, offerToReceiveVideo: true};
this.iceServers = null;
this.earlyCandidates = [];
}
WebRtcStreamer.prototype._handleHttpErrors = function (response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
/**
* Connect a WebRTC Stream to videoElement
* @param {string} videourl - id of WebRTC video stream
* @param {string} audiourl - id of WebRTC audio stream
* @param {string} options - options of WebRTC call
* @param {string} stream - local stream to send
*/
WebRtcStreamer.prototype.connect = function (videourl, audiourl, options, localstream) {
this.disconnect();
// getIceServers is not already received
if (!this.iceServers) {
console.log("Get IceServers");
fetch(this.srvurl + "/api/getIceServers")
.then(this._handleHttpErrors)
.then((response) => (response.json()))
.then((response) => this.onReceiveGetIceServers.call(this, response, videourl, audiourl, options, localstream))
.catch((error) => this.onError("getIceServers " + error))
} else {
this.onReceiveGetIceServers(this.iceServers, videourl, audiourl, options, localstream);
}
}
/**
* Disconnect a WebRTC Stream and clear videoElement source
*/
WebRtcStreamer.prototype.disconnect = function () {
if (this.videoElement?.srcObject) {
this.videoElement.srcObject.getTracks().forEach(track => {
track.stop()
this.videoElement.srcObject.removeTrack(track);
});
}
if (this.pc) {
fetch(this.srvurl + "/api/hangup?peerid=" + this.pc.peerid)
.then(this._handleHttpErrors)
.catch((error) => this.onError("hangup " + error))
try {
this.pc.close();
} catch (e) {
console.log("Failure close peer connection:" + e);
}
this.pc = null;
}
}
/*
* GetIceServers callback
*/
WebRtcStreamer.prototype.onReceiveGetIceServers = function (iceServers, videourl, audiourl, options, stream) {
this.iceServers = iceServers;
this.pcConfig = iceServers || {"iceServers": []};
try {
this.createPeerConnection();
var callurl = this.srvurl + "/api/call?peerid=" + this.pc.peerid + "&url=" + encodeURIComponent(videourl);
if (audiourl) {
callurl += "&audiourl=" + encodeURIComponent(audiourl);
}
if (options) {
callurl += "&options=" + encodeURIComponent(options);
}
if (stream) {
this.pc.addStream(stream);
}
// clear early candidates
this.earlyCandidates.length = 0;
// create Offer
var bind = this;
this.pc.createOffer(this.mediaConstraints).then(function (sessionDescription) {
console.log("Create offer:" + JSON.stringify(sessionDescription));
bind.pc.setLocalDescription(sessionDescription
, function () {
fetch(callurl, {method: "POST", body: JSON.stringify(sessionDescription)})
.then(bind._handleHttpErrors)
.then((response) => (response.json()))
.catch((error) => bind.onError("call " + error))
.then((response) => bind.onReceiveCall.call(bind, response))
.catch((error) => bind.onError("call " + error))
}
, function (error) {
console.log("setLocalDescription error:" + JSON.stringify(error));
});
}, function (error) {
alert("Create offer error:" + JSON.stringify(error));
});
} catch (e) {
this.disconnect();
alert("connect error: " + e);
}
}
WebRtcStreamer.prototype.getIceCandidate = function () {
fetch(this.srvurl + "/api/getIceCandidate?peerid=" + this.pc.peerid)
.then(this._handleHttpErrors)
.then((response) => (response.json()))
.then((response) => this.onReceiveCandidate.call(this, response))
.catch((error) => bind.onError("getIceCandidate " + error))
}
/*
* create RTCPeerConnection
*/
WebRtcStreamer.prototype.createPeerConnection = function () {
console.log("createPeerConnection config: " + JSON.stringify(this.pcConfig));
this.pc = new RTCPeerConnection(this.pcConfig);
var pc = this.pc;
pc.peerid = Math.random();
var bind = this;
pc.onicecandidate = function (evt) {
bind.onIceCandidate.call(bind, evt);
};
pc.onaddstream = function (evt) {
bind.onAddStream.call(bind, evt);
};
pc.oniceconnectionstatechange = function (evt) {
console.log("oniceconnectionstatechange state: " + pc.iceConnectionState);
if (bind.videoElement) {
if (pc.iceConnectionState === "connected") {
bind.videoElement.style.opacity = "1.0";
} else if (pc.iceConnectionState === "disconnected") {
bind.videoElement.style.opacity = "0.25";
} else if ((pc.iceConnectionState === "failed") || (pc.iceConnectionState === "closed")) {
bind.videoElement.style.opacity = "0.5";
} else if (pc.iceConnectionState === "new") {
bind.getIceCandidate.call(bind)
}
}
}
pc.ondatachannel = function (evt) {
console.log("remote datachannel created:" + JSON.stringify(evt));
evt.channel.onopen = function () {
console.log("remote datachannel open");
this.send("remote channel openned");
}
evt.channel.onmessage = function (event) {
console.log("remote datachannel recv:" + JSON.stringify(event.data));
}
}
pc.onicegatheringstatechange = function () {
if (pc.iceGatheringState === "complete") {
const recvs = pc.getReceivers();
recvs.forEach((recv) => {
if (recv.track && recv.track.kind === "video") {
console.log("codecs:" + JSON.stringify(recv.getParameters().codecs))
}
});
}
}
try {
var dataChannel = pc.createDataChannel("ClientDataChannel");
dataChannel.onopen = function () {
console.log("local datachannel open");
this.send("local channel openned");
}
dataChannel.onmessage = function (evt) {
console.log("local datachannel recv:" + JSON.stringify(evt.data));
}
} catch (e) {
console.log("Cannor create datachannel error: " + e);
}
console.log("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig));
return pc;
}
/*
* RTCPeerConnection IceCandidate callback
*/
WebRtcStreamer.prototype.onIceCandidate = function (event) {
if (event.candidate) {
if (this.pc.currentRemoteDescription) {
this.addIceCandidate(this.pc.peerid, event.candidate);
} else {
this.earlyCandidates.push(event.candidate);
}
} else {
console.log("End of candidates.");
}
}
WebRtcStreamer.prototype.addIceCandidate = function (peerid, candidate) {
fetch(this.srvurl + "/api/addIceCandidate?peerid=" + peerid, {method: "POST", body: JSON.stringify(candidate)})
.then(this._handleHttpErrors)
.then((response) => (response.json()))
.then((response) => {
console.log("addIceCandidate ok:" + response)
})
.catch((error) => this.onError("addIceCandidate " + error))
}
/*
* RTCPeerConnection AddTrack callback
*/
WebRtcStreamer.prototype.onAddStream = function (event) {
console.log("Remote track added:" + JSON.stringify(event));
this.videoElement.srcObject = event.stream;
var promise = this.videoElement.play();
if (promise !== undefined) {
var bind = this;
promise.catch(function (error) {
console.warn("error:" + error);
bind.videoElement.setAttribute("controls", true);
});
}
}
/*
* AJAX /call callback
*/
WebRtcStreamer.prototype.onReceiveCall = function (dataJson) {
var bind = this;
console.log("offer: " + JSON.stringify(dataJson));
var descr = new RTCSessionDescription(dataJson);
this.pc.setRemoteDescription(descr
, function () {
console.log("setRemoteDescription ok");
while (bind.earlyCandidates.length) {
var candidate = bind.earlyCandidates.shift();
bind.addIceCandidate.call(bind, bind.pc.peerid, candidate);
}
bind.getIceCandidate.call(bind)
}
, function (error) {
console.log("setRemoteDescription error:" + JSON.stringify(error));
});
}
/*
* AJAX /getIceCandidate callback
*/
WebRtcStreamer.prototype.onReceiveCandidate = function (dataJson) {
console.log("candidate: " + JSON.stringify(dataJson));
if (dataJson) {
for (var i = 0; i < dataJson.length; i++) {
var candidate = new RTCIceCandidate(dataJson[i]);
console.log("Adding ICE candidate :" + JSON.stringify(candidate));
this.pc.addIceCandidate(candidate
, function () {
console.log("addIceCandidate OK");
}
, function (error) {
console.log("addIceCandidate error:" + JSON.stringify(error));
});
}
this.pc.addIceCandidate();
}
}
/*
* AJAX callback for Error
*/
WebRtcStreamer.prototype.onError = function (status) {
console.log("onError:" + status);
}
return WebRtcStreamer;
};
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
window.WebRtcStreamer = WebRtcStreamer;
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = WebRtcStreamer;
}

34
src/views/index.vue

@ -0,0 +1,34 @@
<!--
*@描述: 主页
*@作者
*@日期
*@版本1.0
*/
-->
<template>
<ComSetup>
<template v-slot:default>
<!-- 主体内容 -->
<div class="com-body">
<CameraCenter></CameraCenter>
</div>
</template>
</ComSetup>
</template>
<script lang="ts" setup>
import ComSetup from "@/components/aside/index.vue";
import CameraCenter from "@/views/page/cameraCenter.vue";
</script>
<style lang="less" scoped>
.com-body {
width: 100%;
height: 100%;
position: relative;
margin: auto;
color: black;
}
</style>

297
src/views/page/cameraCenter.vue

@ -0,0 +1,297 @@
<template>
<div id="videoWrapper">
<canvas ref="videoCanvas" id="videoCanvas" :width="canvasWidth" :height="canvasHeight">
</canvas>
<video ref="videoPlayer" id="videoPlayer" autoplay muted
@loadedmetadata="changeVideoCanvasSize" @mouseover="mouseOverVideo" @mouseout="mouseOutVideo"
@mousedown="mouseDownVideo">
</video>
<div v-for="item in labelList" class="labels" :key="item.id" :style="`top:${canvasHeight*item.canvasTopRatio}px;left:${canvasWidth*item.canvasLeftRatio}px`">
<div class="labels-item" @click="test(item.id)">
<!-- <div class="labels-item" v-if="item.inFlag"> -->
<img src="@/assets/images/dialog.png" width="80" height="50" alt="">
<span>{{item.name}}</span>
</div>
</div>
</div>
</template>
<script setup lang='ts'>
////@ts-nocheck
import { ref, h, onMounted, getCurrentInstance, ComponentInternalInstance, onUnmounted,watch } from "vue";
import * as markLabelApi from '@/axios/cameraMark/markLabelApi';
import * as markSearchApi from '@/axios/core/markSearchApi';
import { useStore } from '@/store/index';
import { apiUrl } from "@/axios";
import {storeToRefs} from 'pinia';
import Msg from "@/utils/message";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
let piniaStore = useStore();
let player = <HTMLVideoElement>document.querySelector('#videoPlayer')
let canvas = <HTMLCanvasElement>document.querySelector('#videoCanvas')
let canvasWidth = ref(0)
let canvasHeight = ref(0)
let curSelectKey = storeToRefs(piniaStore).curSelectKey
let cameraMap = storeToRefs(piniaStore).cameraMap
let webRtcServer: any = ref()
let isActiveChoose = ref(false)
let labelList=ref<any[]>([])
onMounted(() => {
{
player = <HTMLVideoElement>document.querySelector('#videoPlayer');
canvas = <HTMLCanvasElement>document.querySelector('#videoCanvas');
// loadTreeData();
loadVideoPlayer();
loadVideoCanvas();
}
})
watch(curSelectKey,(newVal,oldVal)=>{
switchCamera(newVal)
})
function test(id:number){
console.log(id,"id++++++++++++");
markLabelApi.Delete({id}).then((res:any)=>{
console.log(res.data);
if(res.data.code==200){
let msg=res.data.data==true?"删除成功":"删除失败"
if(res.data.data){
labelList.value=labelList.value.filter((element:any) => {
return element.id!=id
});
Msg.success("删除成功")
return
}
}
Msg.error("删除失败")
})
}
function loadVideoPlayer() {
let elmId = 'videoPlayer';
let url = apiUrl.WebRtcUrl;
webRtcServer.value = new WebRtcStreamer(elmId, url);
}
function loadVideoCanvas() {
window.addEventListener('resize', () => {
changeVideoCanvasSize();
});
}
function changeVideoCanvasSize() {
canvasWidth.value = player.clientWidth;
canvasHeight.value = player.clientHeight;
}
function mouseOverVideo() {
// console.log("");
if (!isActiveChoose.value) return;
player.classList.add('activeChoose');
}
function mouseOutVideo() {
// console.log("");
if (!isActiveChoose) return;
player.classList.remove('activeChoose');
}
function mouseDownVideo(e: MouseEvent) {
if (!isActiveChoose.value) return;
let cameraId = cameraMap.value.get(curSelectKey.value).id;
let name = 'markLabel-' + Date.now();
let videoWidth = player.videoWidth | 1920;
let videoHeight = player.videoHeight | 1080;
let canvasWidth = player.clientWidth;
let canvasHeight = player.clientHeight;
let canvasLeft = e.offsetX;
let canvasTop = e.offsetY;
let canvasLeftRatio = canvasLeft / canvasWidth;
let canvasTopRatio = canvasTop / canvasHeight;
let entity = {
CbCameraId: cameraId,
Name: name,
VideoWidth: videoWidth,
VideoHeight: videoHeight,
CanvasLeftRatio: canvasLeftRatio,
CanvasTopRatio: canvasTopRatio
}
console.log(entity);
markLabelApi.AddReturnId({
'entity': entity
}).then((res: any) => {
let markLabelId: number = res.data.data;
console.log(markLabelId);
if (markLabelId <= 0) return;
markSearchApi.ActivateMarkLabel({
'cameraId': cameraId,
'markLabelId': markLabelId
}).then((res: any) => {
let ret: boolean = res.data.data;
});
})
}
function activateChoose() {
if (curSelectKey.value.length == 0) return;
isActiveChoose.value = true;
}
function deactivateChoose() {
if (curSelectKey.value.length == 0) return;
isActiveChoose.value = false;
}
function switchCamera(cameraId: string) {
console.log('camera switch.');
// step1, get camera obj.
console.log('get camera obj.');
let cameraObj = cameraMap.value.get(cameraId);
if (!cameraObj) {
console.log('camera obj not found.');
return;
}
// object to proxy, ?
console.log(cameraObj, '111111');
// step2, get camera rtsp url.
console.log('get camera rtsp url.');
let rtspUrl = getRtspUrl(cameraObj);
// step3, connect webrtc-steamer.
console.log('connect webrtc-steamer.');
webRtcServer.value.disconnect();
webRtcServer.value.connect(rtspUrl);
// step4, active camera searcher.
markSearchApi.IsExistsSearcher({
'cameraId': cameraObj.id
}).then((res: any) => {
let flag: boolean = res.data.data;
if (!flag) {
console.log('not exist searcher.');
markSearchApi.ActiveSearcher({
'cameraId': cameraObj.id
}).then((res: any) => {
let flag: boolean = res.data.data;
console.log('activate searcher : ', flag);
if (flag) {
console.log('load camera labels');
getLabel(cameraObj.id)
loadMarkLabelsByLoop(cameraObj);
}
});
} else {
console.log('load camera labels');
getLabel(cameraObj.id)
loadMarkLabelsByLoop(cameraObj);
}
})
// step5, load camera labels.
}
function getRtspUrl(cameraObj: any): string {
// onvif
return "rtsp://admin:hk123456@192.168.1.65:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1";
}
function getLabel(cbCameraId:string|number){
markLabelApi.GetList({cbCameraId}).then((res:any)=>{
if(res.data.code==200){
console.log(res,'res');
labelList.value=res.data.data
}
})
}
function loadMarkLabelsByLoop(cameraObj: any, ms: number = 1000) {
if (curSelectKey.value !== cameraObj.id) {
console.log('load camera labels end.');
return;
}
loadMarkLabels(cameraObj);
setTimeout(() => {
loadMarkLabelsByLoop(cameraObj, ms);
}, ms);
}
function loadMarkLabels(cameraObj: any) {
markSearchApi.GetMarkLabelCalcResultList({
'cameraId': cameraObj.id
}).then((res: any) => {
let list: Array<any> = res.data.data;
list.forEach((element:any) => {
labelList.value.forEach((item:any,index:number) => {
if(element.id==item.id){
labelList.value[index].canvasLeftRatio=element.canvasLeftRatio
labelList.value[index].canvasTopRatio=element.canvasTopRatio
labelList.value[index].inFlag=element.inFlag
}
});
});
// console.log(labelList);
// drawMarkLabels(list);
});
}
function drawMarkLabels(markLabels: Array<any>) {
let ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
for (let markLabel of markLabels) {
if (!markLabel.inFlag) continue;
let x = Math.round(canvasWidth.value * markLabel.canvasLeftRatio);
let y = Math.round(canvasHeight.value * markLabel.canvasTopRatio);
x = canvasWidth.value * markLabel.canvasLeftRatio;
y = canvasHeight.value * markLabel.canvasTopRatio;
// console.log(markLabel);
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
}
onUnmounted(() => {
{
console.log('beforeUnmount.');
// webRtcServer
if (!webRtcServer.value) return;
console.log(123);
webRtcServer.value.disconnect();
webRtcServer.value = null;
}
})
</script>
<style scoped lang='less'>
#videoWrapper {
height: 100vh;
position: relative;
overflow: hidden;
#videoCanvas {
position: absolute;
}
#videoPlayer {
height: 100%;
width: 100%;
object-fit: fill;
}
.labels{
position: absolute;
top: 0;
left: 0;
transform: translate(-50%,-50%);
z-index: 2;
.labels-item{
position: relative;
span{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
}
}
}
</style>

7
src/vite-env.d.ts

@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

22
tsconfig.json

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": false,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/utils/webrtc-streamer/webrtcstreamer.js"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

57
vite.config.ts

@ -0,0 +1,57 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from 'path';
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [vue(),Components({
resolvers: [
AntDesignVueResolver({
importStyle: 'less', // 一定要开启这个配置项
}),
],
}),],
resolve:{
//设置路径别名
alias: {
'@': resolve(__dirname, './src'),
'*': resolve('')
},
},
define: {
'process.env': {
"VUE_APP_API_BASE_URL":"http://192.168.1.128:8080/military",
"VUE_APP_MQTT_HOST":"127.0.0.1",
"VUE_APP_MQTT_PORT":8083,
"VUE_APP_MQTT_TOPICS": "/server/warning/+/zdgl"
}
},
server: {
host: true, // 类型:string | boolean 指定服务器应该监听哪个 IP 地址
port: 3100, // 类型: number 指定服务器端口
cors: true, // 类型: boolean | CorsOptions 为开发服务器配置 CORS。默认启用并允许任何源
hmr : true,
// proxy: {
// '^/api': {
// target: 'http://192.168.1.119:800/api',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '')
// }
// }
},
css: {
preprocessorOptions: {
less: {
modifyVars: { // 在这里自定义主题色等样式
'primary-color': '#62f7e8a2',
'link-color': '#00ffe4',
'border-color-base': '#00ffe4',
'text-color': '#fff'
},
javascriptEnabled: true,
},
},
},
})

1411
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save