王晓寒 2 mesi fa
parent
commit
895c1895ca
42 ha cambiato i file con 2225 aggiunte e 99 eliminazioni
  1. +1
    -1
      SafeCampus.WEB/.env
  2. +6
    -2
      SafeCampus.WEB/public/static/rtsPlayer.html
  3. +28
    -0
      SafeCampus.WEB/src/api/interface/sys/attendance/behaviorTrace.ts
  4. +17
    -0
      SafeCampus.WEB/src/api/interface/sys/attendance/index.ts
  5. +31
    -0
      SafeCampus.WEB/src/api/interface/sys/attendance/passenger.ts
  6. +28
    -0
      SafeCampus.WEB/src/api/interface/sys/attendance/studentsReturn.ts
  7. +1
    -0
      SafeCampus.WEB/src/api/interface/sys/index.ts
  8. +7
    -0
      SafeCampus.WEB/src/api/interface/sys/monitor/camera.ts
  9. +47
    -0
      SafeCampus.WEB/src/api/modules/attendance/behaviorTrace.ts
  10. +19
    -0
      SafeCampus.WEB/src/api/modules/attendance/index.ts
  11. +50
    -0
      SafeCampus.WEB/src/api/modules/attendance/passenger.ts
  12. +49
    -0
      SafeCampus.WEB/src/api/modules/attendance/roolcall.ts
  13. +43
    -0
      SafeCampus.WEB/src/api/modules/attendance/studentsReturn.ts
  14. +1
    -0
      SafeCampus.WEB/src/api/modules/index.ts
  15. +12
    -0
      SafeCampus.WEB/src/api/modules/monitor/live.ts
  16. +2
    -1
      SafeCampus.WEB/src/components/Form/SButton/index.vue
  17. +1
    -1
      SafeCampus.WEB/src/components/Selectors/OrgSelector/index.vue
  18. +9
    -1
      SafeCampus.WEB/src/enums/formEnum.ts
  19. +10
    -1
      SafeCampus.WEB/src/routers/modules/staticRouter.ts
  20. +1
    -0
      SafeCampus.WEB/src/typings/props.d.ts
  21. +16
    -0
      SafeCampus.WEB/src/utils/index.ts
  22. +148
    -0
      SafeCampus.WEB/src/views/attendance/behaviorTrace/components/traceShow/index.vue
  23. +139
    -0
      SafeCampus.WEB/src/views/attendance/behaviorTrace/index.vue
  24. +79
    -0
      SafeCampus.WEB/src/views/attendance/passenger/components/detailTable/index.vue
  25. +91
    -0
      SafeCampus.WEB/src/views/attendance/passenger/components/form/form_basic.vue
  26. +112
    -0
      SafeCampus.WEB/src/views/attendance/passenger/components/form/index.vue
  27. +110
    -0
      SafeCampus.WEB/src/views/attendance/passenger/index.vue
  28. +160
    -0
      SafeCampus.WEB/src/views/attendance/roolcall/components/form/index.vue
  29. +191
    -0
      SafeCampus.WEB/src/views/attendance/roolcall/detail.vue
  30. +159
    -0
      SafeCampus.WEB/src/views/attendance/roolcall/index.vue
  31. +184
    -0
      SafeCampus.WEB/src/views/attendance/studentsReturn/components/returnConfirm/index.vue
  32. +96
    -0
      SafeCampus.WEB/src/views/attendance/studentsReturn/index.vue
  33. +20
    -14
      SafeCampus.WEB/src/views/monitor/live/components/form.vue
  34. +2
    -2
      SafeCampus.WEB/src/views/monitor/live/components/moveForm.vue
  35. +59
    -31
      SafeCampus.WEB/src/views/monitor/live/components/userForm.vue
  36. +95
    -21
      SafeCampus.WEB/src/views/monitor/live/index.vue
  37. +138
    -0
      SafeCampus.WEB/src/views/sysconfig/ability/components/userForm.vue
  38. +59
    -20
      SafeCampus.WEB/src/views/sysconfig/ability/index.vue
  39. +1
    -1
      SafeCampus.WEB/src/views/userManage/clothing/components/form/index.vue
  40. +1
    -1
      SafeCampus.WEB/src/views/userManage/clothing/index.vue
  41. +1
    -1
      SafeCampus.WEB/src/views/userManage/keyPersonnel/index.vue
  42. +1
    -1
      SafeCampus.WEB/src/views/userManage/personnel/index.vue

+ 1
- 1
SafeCampus.WEB/.env Vedi File

@@ -1,5 +1,5 @@
# title
VITE_GLOB_APP_TITLE = 校园监控智能分析平台
VITE_GLOB_APP_TITLE = AI监控预警分析平台

# 本地运行端口号
VITE_PORT = 8848


+ 6
- 2
SafeCampus.WEB/public/static/rtsPlayer.html Vedi File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html style="overflow: hidden;">

<head>
<meta charset="utf-8">
@@ -27,12 +27,13 @@

// 更多播放器配置请参考 https://player.alicdn.com/aliplayer/index.html
let rtsUrl = getUrlParams(location.href)['rtsUrl'] || ''
let height = getUrlParams(location.href)['height'] || "500px"
var options = {
"id": "player-con",
"source": rtsUrl,
"rtsFallbackSource": "降级地址,如HLS",
"width": "100%",
"height": "500px",
"height": height,
"autoplay": true,
"isLive": true,
"playsinline": true,
@@ -65,6 +66,9 @@
// 当RTS拉流成功时触发,通过订阅该事件,可以获取到RTS TraceId
player.on('rtsTraceId', function (data) {
console.log('[EVENT]rtsTraceId', data.paramData);
player.setVolume(0)
let fullscreenBtn = document.querySelector('.prism-controlbar .prism-fullscreen-btn')
if(fullscreenBtn)fullscreenBtn.style.display = 'none'
// event.paramData.traceId 拉流的TraceId
// event.paramData.source 当前RTS流的播放地址
})


+ 28
- 0
SafeCampus.WEB/src/api/interface/sys/attendance/behaviorTrace.ts Vedi File

@@ -0,0 +1,28 @@
/**
* @description 学生归寝
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
*/

import { ReqPage } from "@/api";
/**
* @Description: 教师点名
* @Author: yxq
* @Date: 2023-12-17 15:34:54
*/

export namespace AttendanceBehaviorTrace {
export interface Page extends ReqPage {}

/** 表单 */
export interface behaviorTraceInfo {
}
}

+ 17
- 0
SafeCampus.WEB/src/api/interface/sys/attendance/index.ts Vedi File

@@ -0,0 +1,17 @@
/**
* @description
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
export * from "./passenger";
export * from "./studentsReturn";
export * from "./behaviorTrace";

+ 31
- 0
SafeCampus.WEB/src/api/interface/sys/attendance/passenger.ts Vedi File

@@ -0,0 +1,31 @@
/**
* @description 用户管理接口
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
*/

import { ReqPage } from "@/api";
/**
* @Description: 客流查询
* @Author: yxq
* @Date: 2023-12-17 15:34:54
*/

export namespace AttendancePassenger {
export interface Page extends ReqPage {}

/** 客流查询表单 */
export interface PassengerInfo {
fenpianleixing:String,
shijianduan:any,
shexiangtou:any,
}
}

+ 28
- 0
SafeCampus.WEB/src/api/interface/sys/attendance/studentsReturn.ts Vedi File

@@ -0,0 +1,28 @@
/**
* @description 学生归寝
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
*/

import { ReqPage } from "@/api";
/**
* @Description: 教师点名
* @Author: yxq
* @Date: 2023-12-17 15:34:54
*/

export namespace AttendanceStudentsReturn {
export interface Page extends ReqPage {}

/** 表单 */
export interface studentsReturnInfo {
}
}

+ 1
- 0
SafeCampus.WEB/src/api/interface/sys/index.ts Vedi File

@@ -22,3 +22,4 @@ export * from "./auth";
export * from "./warn";
export * from "./usermanage";
export * from "./monitor";
export * from "./attendance";

+ 7
- 0
SafeCampus.WEB/src/api/interface/sys/monitor/camera.ts Vedi File

@@ -57,4 +57,11 @@ export namespace sysCamera {
id: number | string;
name: string;
}
// 设置推送人
export interface setGroup {
/** groupId */
groupId: number | string;
userId: number | string;
ids: number | string[];
}
}

+ 47
- 0
SafeCampus.WEB/src/api/modules/attendance/behaviorTrace.ts Vedi File

@@ -0,0 +1,47 @@
/**
* @description 单页管理接口
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
import { moduleRequest } from "@/api/request";
import { ReqId, SysUserPersonnel } from "@/api/interface";
const http = moduleRequest("/business/dfieldApi/");

/**
* @Description: 单页管理
* @Author: SYY
* @Date: 2023-12-15 15:34:54
*/
const attendanceBehaviorTrace = {
/** 查询底库列表 */
page(params: SysUserPersonnel.ClassPage) {
return http.get("test", params);
},
/** 删除底库 */
delete(params: ReqId) {
return http.delete("test", params);
},
/** 创建底库 */
add(params: SysUserPersonnel.ClassPage) {
return http.post("test", params);
},
/** 更新底库 */
update(params: SysUserPersonnel.ClassPage) {
return http.put("test", params);
},
/** 轨迹详情 */
detail(params: SysUserPersonnel.ClassPage) {
return http.get("test", params);
}
};

export { attendanceBehaviorTrace };

+ 19
- 0
SafeCampus.WEB/src/api/modules/attendance/index.ts Vedi File

@@ -0,0 +1,19 @@
/**
* @description
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
export * from "./behaviorTrace";
export * from "./passenger";
export * from "./roolcall";
export * from "./studentsReturn";


+ 50
- 0
SafeCampus.WEB/src/api/modules/attendance/passenger.ts Vedi File

@@ -0,0 +1,50 @@
/**
* @description 单页管理接口
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
import { moduleRequest } from "@/api/request";
import { ReqId, AttendancePassenger } from "@/api/interface";
const http = moduleRequest("/business/dfieldApi/");

/**
* @Description: 单页管理
* @Author: SYY
* @Date: 2023-12-15 15:34:54
*/
const attendancePassenger = {
/** 查询列表 */
page(params: AttendancePassenger.PassengerInfo) {
return http.get("test", params);
},
/** 删除 */
delete(params: ReqId) {
return http.delete("test", params);
},
/** 创建 */
add(params: AttendancePassenger.PassengerInfo) {
return http.post("test", params);
},
/** 详情 */
detail(params: ReqId) {
return http.post("test", params);
},
};

const attendancePassengerBtnCode = {
/** 新增 */
add: "attendancePassengerBtnCodeAdd",
/** 删除 */
delete: "attendancePassengerBtnCodeDel"
};

export { attendancePassenger,attendancePassengerBtnCode };

+ 49
- 0
SafeCampus.WEB/src/api/modules/attendance/roolcall.ts Vedi File

@@ -0,0 +1,49 @@
/**
* @description 单页管理接口
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
import { moduleRequest } from "@/api/request";
const http = moduleRequest("/business/classRoomCallApi/");
/**
* @Description: 教师点名
* @Author: SYY
* @Date: 2023-12-15 15:34:54
*/
const attendanceRoolcallApi = {
// 获取列表
page(params: any) {
return http.get("getPageList", params);
},
/** 查询点名任务列表 */
getTaskPageList(params: any) {
return http.get("getTaskPageList", params);
},
/** 删除点名任务(删除关联点名数据) */
delete(params: any) {
return http.delete("delete", params);
},
/** 删除点名任务(只可删除待处理的任务) */
remove(params: any) {
return http.delete("remove", params);
},
/** 点名任务下发 */
add(params: any) {
return http.post("taskSubmit", params);
},
/** 修改点名任务 */
update(params: any) {
return http.post("update", params);
}
};

export { attendanceRoolcallApi };

+ 43
- 0
SafeCampus.WEB/src/api/modules/attendance/studentsReturn.ts Vedi File

@@ -0,0 +1,43 @@
/**
* @description 学生归寝
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
import { moduleRequest } from "@/api/request";
import { ReqId, SysUserPersonnel } from "@/api/interface";
const http = moduleRequest("/business/dfieldApi/");

/**
* @Description: 单页管理
* @Author: SYY
* @Date: 2023-12-15 15:34:54
*/
const attendanceStudentsReturn = {
/** 查询底库列表 */
page(params: SysUserPersonnel.ClassPage) {
return http.get("test", params);
},
/** 删除底库 */
delete(params: ReqId) {
return http.delete("test", params);
},
/** 创建底库 */
add(params: SysUserPersonnel.ClassPage) {
return http.post("test", params);
},
/** 更新底库 */
update(params: SysUserPersonnel.ClassPage) {
return http.put("test", params);
}
};

export { attendanceStudentsReturn };

+ 1
- 0
SafeCampus.WEB/src/api/modules/index.ts Vedi File

@@ -20,3 +20,4 @@ export * from "./monitor";
export * from "./sysconfig";
export * from "./statistion";
export * from "./usermanage";
export * from "./attendance";

+ 12
- 0
SafeCampus.WEB/src/api/modules/monitor/live.ts Vedi File

@@ -44,6 +44,18 @@ const monitorLIVEApi = {
setGroup(params: sysCamera.setGroup) {
return http2.post<ResPage<sysCamera.MonitorInfo>>("batchSetGroup", params);
},
// 根据分组批量设置推送人
setPushPerson(params: sysCamera.setGroup) {
return http2.post<ResPage<sysCamera.MonitorInfo>>("batchSetPushPersonByGroup", params);
},
// 对摄像头批量设置推送人
setVideoPushPerson(params: sysCamera.setGroup) {
return http2.post<ResPage<sysCamera.MonitorInfo>>("batchSetPushPerson", params);
},
// 对摄像头批量设置推送人
setWarningPushPerson(params: sysCamera.setGroup) {
return http2.post<ResPage<sysCamera.MonitorInfo>>("batchSetPushPersonByWarn", params);
},
// 获取摄像头分组树
groupList(params: sysCamera.Tree) {
return http3.get<ResPage<sysCamera.MonitorInfo>>("getNoPageList");


+ 2
- 1
SafeCampus.WEB/src/components/Form/SButton/index.vue Vedi File

@@ -18,7 +18,8 @@ import { FormOptEnum } from "@/enums";
//接口
interface Props {
/** 操作 */
opt?: FormOptEnum;
// opt?: FormOptEnum;
opt?: any;
/** 前缀 */
prefix?: string;
/** 标题 */


+ 1
- 1
SafeCampus.WEB/src/components/Selectors/OrgSelector/index.vue Vedi File

@@ -82,7 +82,7 @@ function getOrgTree() {
function changeOrg(value: number | string) {
valueOrg.value = value; //更新当前选中的组织名称
emit("update:orgValue", value); //更新父组件数据
emit("change", treeRef.value.getCurrentNode().path); //触发change事件
emit("change", treeRef.value.getCurrentNode().path, treeRef.value.getCurrentNode()); //触发change事件
}
</script>



+ 9
- 1
SafeCampus.WEB/src/enums/formEnum.ts Vedi File

@@ -22,5 +22,13 @@ export enum FormOptEnum {
/** 查看 */
VIEW = "查看",
/** 删除 */
DELETE = "删除"
DELETE = "删除",
// 分组推送人
GroupPushPerson = "分组推送人",
// 视频推送人
VideoPushPerson = "视频推送人",
// 移动
MOVE = "移动",
// 分组
GROUP = "分组"
}

+ 10
- 1
SafeCampus.WEB/src/routers/modules/staticRouter.ts Vedi File

@@ -37,7 +37,16 @@ export const staticRouter: RouteRecordRaw[] = [
component: () => import("@/layouts/index.vue"),
// component: () => import("@/layouts/indexAsync.vue"),
redirect: HOME_URL,
children: []
children: [
{
meta: {
title: "学生点名详情"
},
name: "学生点名详情",
path: "/roolcall/detail",
component: () => import("@/views/attendance/roolcall/detail.vue")
}
]
}
];



+ 1
- 0
SafeCampus.WEB/src/typings/props.d.ts Vedi File

@@ -29,6 +29,7 @@ declare namespace FormProps {
inline?: boolean;
// 树数据
treeAllData?: Array<any>;
records: Array<any>;
/** 表单布局 */
successful?: () => void;
}


+ 16
- 0
SafeCampus.WEB/src/utils/index.ts Vedi File

@@ -323,3 +323,19 @@ export function findItemNested(enumData: any, callValue: any, value: string, chi
if (current[children]) return findItemNested(current[children], callValue, value, children);
}, null);
}

/**
* @description 时间戳转化为日期
* */
export function formatDate(timestamp:number) {
let date = new Date(timestamp);
let year = date.getFullYear();
let month = "0" + (date.getMonth() + 1); // getMonth返回的月份是从0开始的
let day = "0" + date.getDate();
let hours = "0" + date.getHours();
let minutes = "0" + date.getMinutes();
let seconds = "0" + date.getSeconds();

return year + "-" + month.substr(-2) + "-" + day.substr(-2)
+ " " + hours.substr(-2) + ":" + minutes.substr(-2) + ":" + seconds.substr(-2);
}

+ 148
- 0
SafeCampus.WEB/src/views/attendance/behaviorTrace/components/traceShow/index.vue Vedi File

@@ -0,0 +1,148 @@
<!--
* @Description: 行为轨迹
* @Author: yxq
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`行为轨迹查看`" form-size="800px" @close="onClose">
<ProTable ref="proTable" title="行为轨迹" height="500px" :data="tableData" :columns="columns"></ProTable>
<template #footer>
<el-button @click="onClose"> 关闭 </el-button>
</template>
</form-container>
<!-- 预览头像 -->
<el-dialog v-model="imgVisible" title="查看头像" width="830px">
<div style="display: flex; align-items: center; justify-content: center">
<img class="detailpic" :src="faceUrl" alt="" />
</div>
</el-dialog>
</div>
</template>
<script setup lang="tsx" name="attendanceBehaviorTrace">
import { AttendanceBehaviorTrace, attendanceBehaviorTrace } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { FormOptEnum } from "@/enums";

const visible = ref(false); //是否显示
// 弹框参数
const propsInfo = reactive<FormProps.Base<AttendanceBehaviorTrace.behaviorTraceInfo>>({
opt: FormOptEnum.VIEW, //操作类型
record: {}, //弹框数据
disabled: false
});
/**
* 打开弹框
* @param props 弹框参数
*/
function onOpen(props: FormProps.Base<AttendanceBehaviorTrace.PassengerInfo>) {
Object.assign(propsInfo, props); //合并参数
visible.value = true; //显示弹框
if (props.record.id) {
//如果传了id,就去请求api获取record
attendanceBehaviorTrace.detail({ id: props.record.id }).then(res => {
propsInfo.record = res.data;
});
}
}

const tableData = ref([
{
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
time:'2024-07-16 18:20:12',
name:'大厅(魔豆)',
},
{
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
time:'2024-07-16 18:20:12',
name:'教室(海康)',
},
{
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
time:'2024-07-16 18:20:12',
name:'大厅(魔豆)',
},
{
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
time:'2024-07-16 18:20:12',
name:'教室(海康)',
},
{
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
time:'2024-07-16 18:20:12',
name:'大厅(魔豆)',
}
]);
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps[] = [
{
prop: "time",
label: "经过摄像头时间"
},
{
prop: "name",
label: "摄像头名称",
},
{
prop: "faces",
label: "快照",
render: scope => {
return (
<img
src={scope.row.faces.length > 0 ? scope.row.faces[0].faceUrl : ""}
onClick={() => viewHeadImage(scope)}
style="width:50px;height:50px;cursor:pointer"
alt=""
/>
);
}
}
];
// 图片预览
const imgVisible = ref(false);
const faceUrl = ref('');
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.faces[0].faceUrl;
imgVisible.value = true
};
/** 关闭表单*/
function onClose() {
visible.value = false;
}

// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>

+ 139
- 0
SafeCampus.WEB/src/views/attendance/behaviorTrace/index.vue Vedi File

@@ -0,0 +1,139 @@
<!--
* @Description: 行为轨迹
* @Author: yxq
* @Date: 2024-7-15
-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="personSetName"
id="personSetId"
width="300px"
:show-all="true"
:request-api="userManageClassManageApi.page"
@change="changeTreeFilter"
>
<template v-slot:header>
<h4 style="margin: 0 0 15px; font-size: 18px; font-weight: bold; color: var(--el-color-info-dark-2); letter-spacing: 0.5px">所属班级</h4>
</template>
<template v-slot:label="{ row }">
<span class="custom-tree-node">
<span>{{ row.node.label }}</span>
</span>
</template>
</TreeFilter>
<div class="table-box">
<ProTable ref="proTable" title="人员管理" :columns="columns" rowKey="personId" :request-api="userManagePersonnelApi.page">
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link prefix="轨迹" suffix="查看" :opt="FormOptEnum.VIEW" @click="onOpenDetail(FormOptEnum.VIEW, scope.row)" />
</el-space>
</template>
</ProTable>
</div>
<!-- 预览头像 -->
<el-dialog v-model="visible" title="查看快照" width="830px" :before-close="handleClose">
<div style="display: flex; align-items: center; justify-content: center">
<img class="detailpic" :src="faceUrl" alt="" />
</div>
</el-dialog>
<!-- 轨迹详情 -->
<TraceShow ref="detialRef" />
</div>
</template>
<script setup lang="tsx" name="SysUserPersonnel">
import { userManagePersonnelApi,SysUserPersonnel,userManageClassManageApi,userManageTeacherApi } from "@/api";
import { FormOptEnum } from "@/enums";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { useUserStore } from "@/stores/modules";
import TraceShow from "./components/traceShow/index.vue";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const faceUrl = ref('');
const visible = ref(false); //预览头像
const proTable = ref<ProTableInstance>();
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
// 表格配置项
const columns: ColumnProps<SysUserPersonnel.SysUserPerInfo>[] = [
{
prop: "faceUrl",
label: "人脸",
render: scope => {
return (
<img src={scope.row.faces.length > 0 ? scope.row.faces[0].faceUrl : ''} onClick={() => viewHeadImage(scope)} style='width:50px;height:50px;' alt=''/>
);
}
},
{
prop: "name",
label: "姓名"
},
{
prop: "personId",
label: "人员ID"
},
{
prop: "phone",
label: "手机号"
},
{
prop: "personSets",
label: "所属班级",
render: scope => {
return scope.row.personSets.length > 0 ? scope.row.personSets[0].personSetName : ''
}
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.faces[0].faceUrl;
visible.value = true
};
const handleClose = () => {
visible.value = false;
};

// 刷新表格
const RefreshTable = () => {
proTable.value?.refresh();
treeFilter.value?.refresh(); //刷新树形筛选器
}

/** 部门切换 */
const personSetId = ref<number | string>()
function changeTreeFilter(val: number | string) {
personSetId.value = val
proTable.value!.pageable.pageNum = 1;
proTable.value!.searchParam.personSetId = val;
proTable.value!.search();
}
const detialRef = ref<InstanceType<typeof TraceShow> | null>(null);
/**
* 详情
* @param opt 操作类型
* @param record 弹框数据
*/
function onOpenDetail(opt: FormOptEnum, record: {} | AttendanceStudentsReturn.studentsReturnInfo = {}) {
switch (opt) {
case FormOptEnum.VIEW:
detialRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
break;
}
}
</script>
<style scoped lang="scss">
.table-box {
width: 100%;
height: 100%;
}
.custom-tree-node {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
padding-right: 8px;
font-size: 14px;
}
</style>

+ 79
- 0
SafeCampus.WEB/src/views/attendance/passenger/components/detailTable/index.vue Vedi File

@@ -0,0 +1,79 @@
<!--
* @Description: 行为轨迹
* @Author: yxq
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`分片详情`" form-size="800px" @close="onClose">
<ProTable ref="proTable" title="分片详情" height="500px" :data="tableData" :columns="columns"></ProTable>
<template #footer>
<el-button @click="onClose"> 关闭 </el-button>
</template>
</form-container>
</div>
</template>
<script setup lang="tsx" name="attendanceBehaviorTrace">
import { AttendanceBehaviorTrace, attendanceBehaviorTrace } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { FormOptEnum } from "@/enums";

const visible = ref(false); //是否显示
// 弹框参数
const propsInfo = reactive<FormProps.Base<AttendanceBehaviorTrace.behaviorTraceInfo>>({
opt: FormOptEnum.VIEW, //操作类型
record: {}, //弹框数据
disabled: false
});
/**
* 打开弹框
* @param props 弹框参数
*/
function onOpen(props: FormProps.Base<AttendanceBehaviorTrace.PassengerInfo>) {
Object.assign(propsInfo, props); //合并参数
visible.value = true; //显示弹框
if (props.record.id) {
//如果传了id,就去请求api获取record
attendanceBehaviorTrace.detail({ id: props.record.id }).then(res => {
propsInfo.record = res.data;
});
}
}

const tableData = ref([
{
time:'2024-07-16 00:00:00',
name:'60',
},
{
time:'2024-07-17 00:00:00',
name:'40',
},
]);
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps[] = [
{
prop: "time",
label: "时间"
},
{
prop: "name",
label: "人数",
},
];
/** 关闭表单*/
function onClose() {
visible.value = false;
}

// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>

+ 91
- 0
SafeCampus.WEB/src/views/attendance/passenger/components/form/form_basic.vue Vedi File

@@ -0,0 +1,91 @@
<!--
* @Description: 客流查询
* @Author: yxq
* @Date: 2023-12-17 15:45:50
-->
<template>
<div>
<div>
<el-row :gutter="16">
<el-col :span="24">
<s-form-item label="查询时间段" prop="shijianduan">
<el-date-picker
v-model="formInfo.shijianduan"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
>
</el-date-picker>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<s-form-item label="摄像头" prop="shexiangtou">
<el-select v-model="formInfo.shexiangtou" multiple placeholder="摄像头">
<el-option v-for="item in shexiangtouOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<s-form-item label="分片类型(天/小时)" prop="fenpianleixing">
<s-radio-group v-model="formInfo.fenpianleixing" :options="fenpianleixingOptions" button />
</s-form-item>
</el-col>
</el-row>
</div>
</div>
</template>

<script setup lang="ts">
import { AttendancePassenger, monitorLIVEApi } from "@/api";
// props
interface FormProps {
modelValue: Partial<AttendancePassenger.PassengerInfo>;
}
const emit = defineEmits(["update:modelValue"]); //定义emit
const props = defineProps<FormProps>(); //定义props
// 客流查询表单
const formInfo = computed({
get: () => props.modelValue,
set: val => emit("update:modelValue", val)
});
console.log(props.modelValue);
// 分片类型
const fenpianleixingOptions = ref([
{
label: "天",
value: "0"
},
{
label: "小时",
value: "1"
}
]);
// 摄像头
let shexiangtouOptions = ref([]);
monitorLIVEApi.page({ pageSize: 100, pageNum: 1 }).then(res => {
if (res.code == 200) {
shexiangtouOptions.value = res.data.list.map(e => {
return {
value: e.sensorId,
label: e.sensorName
};
});
}
});

onMounted(() => {
// 初始化
formInfo.value.fenpianleixing = formInfo.value.fenpianleixing ? formInfo.value.fenpianleixing : fenpianleixingOptions.value[0].value;
});
</script>

<style lang="scss" scoped>
:deep(.el-input__wrapper) {
width: 100% !important;
}
</style>

+ 112
- 0
SafeCampus.WEB/src/views/attendance/passenger/components/form/index.vue Vedi File

@@ -0,0 +1,112 @@
<!--
* @Description: 表单
* @Author: syy
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`${propsInfo.opt}查询`" form-size="600px" @close="onClose">
<el-form
ref="formRef"
:rules="rules"
:disabled="propsInfo.disabled"
:model="propsInfo.record"
:hide-required-asterisk="propsInfo.disabled"
label-width="auto"
label-suffix=" :"
>
<el-tabs v-model="activeName">
<Basic v-model="propsInfo.record"></Basic>
</el-tabs>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!propsInfo.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</div>
</template>

<script setup lang="ts">
import { AttendancePassenger, attendancePassenger } from "@/api";
import { FormOptEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import Basic from "./form_basic.vue";
import { formatDate } from "@/utils";

const visible = ref(false); //是否显示表单
const activeName = ref("basic");
// 弹框参数
const propsInfo = reactive<FormProps.Base<AttendancePassenger.PassengerInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});

// 表单验证规则
const rules = reactive({
shijianduan: [
required("请选择查询时间段"),
{
validator: (rule, value, callback) => {
value = value.map(e => e.valueOf());
if (86400000 * 7 < value[1] - value[0]) {
callback(new Error("时间跨度不能大于七天"));
} else {
callback();
}
},
trigger: "change"
}
],
shexiangtou: [required("请选择摄像头")]
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<AttendancePassenger.PassengerInfo>) {
Object.assign(propsInfo, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
propsInfo.record.shijianduan = [];
propsInfo.record.shexiangtou = [];
}
visible.value = true; //显示表单
if (props.record.id) {
//如果传了id,就去请求api获取record
attendancePassenger.detail({ id: props.record.id }).then(res => {
propsInfo.record = res.data;
});
}
}

// 提交数据(新增/编辑)
const formRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
await attendancePassenger
.update(propsInfo.record)
.then(() => {
propsInfo.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}

/** 关闭表单*/
function onClose() {
visible.value = false;
activeName.value = "basic";
}

// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>

<style lang="scss" scoped></style>

+ 110
- 0
SafeCampus.WEB/src/views/attendance/passenger/index.vue Vedi File

@@ -0,0 +1,110 @@
<!--
* @Description: 客流查询
* @Author: yxq
* @Date: 2024-7-17
-->
<template>
<div class="table-box">
<ProTable ref="proTable" title="查询管理" :columns="columns" :data="data">
<!-- 表格 header 按钮 -->
<template #tableHeader>
<s-button v-auth="attendancePassengerBtnCode.add" type="primary" suffix="查询" @click="onOpen(FormOptEnum.ADD)" />
</template>
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link :opt="FormOptEnum.VIEW" prefix="分片" suffix="详情" @click="onOpen(FormOptEnum.VIEW, scope.row)" />
</el-space>
</template>
</ProTable>
<Form ref="formRef" />
<DetailTable ref="detailTableRef"></DetailTable>
</div>
</template>
<script setup lang="ts" name="AttendancePassenger">
import { attendancePassenger, attendancePassengerBtnCode, AttendancePassenger } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import Form from "./components/form/index.vue";
import DetailTable from "./components/detailTable/index.vue";
import { FormOptEnum } from "@/enums";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
const data = ref([
{
chaxunshijian: "2024-07-18 14:20:12",
kaishishijian: "2024-07-16 18:20:12",
jieshushijian: "2024-07-17 16:20:12",
shexiangtou: "大厅(魔豆)",
fenpianleiixng: "天",
tongjirenshu: "100",
fenpianshijian: "2024-07-17 16:20:12",
fenpianshuliang: "100"
},
{
chaxunshijian: "2024-06-19 14:20:12",
kaishishijian: "2024-05-16 18:20:12",
jieshushijian: "2024-05-17 16:20:12",
shexiangtou: "教室(海康)",
fenpianleiixng: "天",
tongjirenshu: "66",
fenpianshijian: "2024-07-17 16:20:12",
fenpianshuliang: "60"
}
]);
// 表格配置项
const columns: ColumnProps<any>[] = [
{
prop: "chaxunshijian",
label: "查询时间"
},
{
prop: "kaishishijian",
label: "开始时间"
},
{
prop: "jieshushijian",
label: "结束时间"
},
{
prop: "shexiangtou",
label: "摄像头"
},
{
prop: "fenpianleiixng",
label: "分片类型"
},
{
prop: "tongjirenshu",
label: "总人数"
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
const detailTableRef = ref<InstanceType<typeof DetailTable> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | AttendancePassenger.PassengerInfo = {}) {
switch (opt) {
case FormOptEnum.ADD:
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
break;
case FormOptEnum.VIEW:
detailTableRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}
}
// 刷新表格
const RefreshTable = () => {
proTable.value?.refresh();
};
</script>
<style scoped lang="scss">
.table-box {
width: 100%;
height: 100%;
}
</style>

+ 160
- 0
SafeCampus.WEB/src/views/attendance/roolcall/components/form/index.vue Vedi File

@@ -0,0 +1,160 @@
<!--
* @Description: 表单
* @Author: syy
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`${sysUserProps.opt}点名`" form-size="900px" @close="onClose">
<el-form
ref="sysUserFormRef"
:rules="rules"
:disabled="sysUserProps.disabled"
:model="sysUserProps.record"
:hide-required-asterisk="sysUserProps.disabled"
label-width="auto"
label-suffix=" :"
>
<div>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="摄像头分组" prop="cameraGroudId">
<org-selector
@change="handleChange"
v-model:org-value="sysUserProps.record.cameraGroudId"
:org-tree-api="monitorLIVEApi.groupList"
:show-all="false"
/></s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="摄像头" prop="cameraId">
<s-select v-model="sysUserProps.record.cameraId" :options="CameraList" label="sensorName" value="sensorId"></s-select>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="所属班级" prop="personSetId">
<s-select
v-model="sysUserProps.record.personSetId"
:options="sysUserProps.treeAllData"
label="personSetName"
value="personSetId"
></s-select>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="持续时间" prop="continueTime">
<el-slider v-model="sysUserProps.record.continueTime" />
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="相似度" prop="similarity">
<el-slider v-model="sysUserProps.record.similarity" :min="0.3" :max="0.8" :step="0.01" />
</s-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!sysUserProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</div>
</template>

<script setup lang="ts" name="SysUserPersonnelForm">
import { attendanceRoolcallApi, userManagePersonnelApi, monitorLIVEApi } from "@/api";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
const visible = ref(false); //是否显示表单
/** 表单操作类型枚举 */
enum FormOptEnums {
/** 发起 */
INITIATE = "发起"
}
// 表单参数
const sysUserProps = reactive<FormProps.Base<any>>({
opt: FormOptEnums.INITIATE,
record: {},
treeAllData: [],
disabled: false
});
const CameraList = ref<any>([]);

// 表单验证规则
const rules = reactive({
cameraGroudId: [required("请选择摄像头分组")],
similarity: [required("请选择相似度")],
cameraId: [required("请选择摄像头")],
continueTime: [required("请选择持续时间")],
personSetId: [required("请选择所属班级")]
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<any>) {
Object.assign(sysUserProps, props); //合并参数
if (props.opt == FormOptEnums.INITIATE) {
//如果是新增,设置默认值
// sysUserProps.record.sortCode = 99;
}
visible.value = true; //显示表单
if (props.record.personId) {
//如果传了id,就去请求api获取record
userManagePersonnelApi.detail({ id: props.record.personId }).then((res: any) => {
sysUserProps.record = res.data;
});
}
}

const handleChange = async (val: any, data: any) => {
let res = await monitorLIVEApi.list({ pageNum: 1, pageSize: 1000, groupId: data.id });
CameraList.value = res.data.list;
};
// 提交数据(新增/编辑)
const sysUserFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
sysUserFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
if (sysUserProps.record.personId) {
await attendanceRoolcallApi
.update(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
await attendanceRoolcallApi
.add(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}

/** 关闭表单*/
function onClose() {
visible.value = false;
}

// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>

<style lang="scss" scoped></style>

+ 191
- 0
SafeCampus.WEB/src/views/attendance/roolcall/detail.vue Vedi File

@@ -0,0 +1,191 @@
<template>
<div class="roolcallDetail">
<div class="header">
<el-row>
<el-col :span="18">
<el-row>
<el-col :span="6"><el-text class="mx-1" type="primary" size="large">应到:8人</el-text></el-col>
<el-col :span="6"><el-text class="mx-1" type="success" size="large">实到:4</el-text> </el-col>
<el-col :span="6"> <el-text class="mx-1" type="warning" size="large">趴桌子:2</el-text></el-col>
<el-col :span="6">
<el-text class="mx-1" type="info" size="large">未识别:{{ UnrecognizedD.length }}</el-text></el-col
>
</el-row>
</el-col>
</el-row>
</div>
<el-row :gutter="20">
<el-col :span="18"
><el-row class="rowbg">
<el-col :xl="4" :lg="4" :md="6" :sm="8" :xs="12" v-for="(item, index) in listData" :key="index"
><div class="grid-content">
<div class="imgBox" :class="{ active: item.status === 1 ? true : false }">
<img :src="item.faces[0].faceUrl" alt="" />
</div>
<div class="info">
<div class="desc">
<span class="label">姓名:</span>
{{ item.name }}
</div>
<div class="desc">
<span class="label">所属分组:</span>
{{ item.personSets[0].personSetName }}
</div>
</div>
</div></el-col
>
</el-row></el-col
>
<el-col :span="6">
<div class="unrecognized">
<div class="title">未识别人员</div>
<div class="imgBox" v-for="(item, index) in UnrecognizedD" :key="index">
<img :src="item.faceUrl" alt="" />
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts" name="SysUserPersonnel">
import { userManagePersonnelApi, attendanceRoolcallApi } from "@/api";
const route = useRoute();
const listData = ref<any>([]); //人员列表
const HighlightData = ref<any>([]); //点名人员列表
const UnrecognizedD = ref<any>([]); //未识别数据
const getList = () => {
userManagePersonnelApi
.page({
pageNum: 1,
pageSize: 1000,
personSetId: route.query.personSetId
})
.then((res: any) => {
listData.value = res.data.list;
Highlight();
});
};
const Highlight = () => {
attendanceRoolcallApi
.page({
pageNum: 1,
pageSize: 1000,
taskId: route.query.taskId
})
.then((res: any) => {
HighlightData.value = res.data.list;
listData.value
.map((item: any) => {
item.status = 0;
return item;
})
.forEach((item: any) => {
HighlightData.value.forEach((item1: any) => {
if (item.personId === item1.personId) {
item.status = 1;
}
});
});
// 未识别人员摘取
UnrecognizedD.value = [];
JSON.parse(JSON.stringify(HighlightData.value)).map((item: any) => {
if (!item.personId) {
UnrecognizedD.value.push(item);
}
});
console.log(UnrecognizedD.value);
});
};
onMounted(() => {
getList();
console.log(route.query);
});
</script>
<style lang="scss" scoped>
.roolcallDetail {
.el-row {
width: 100%;
}
.header {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 80px;
padding: 0 20px;
background: #ffffff;
.el-col {
text-align: center;
}
}
.rowbg {
width: 100%;
.el-col {
box-sizing: border-box;
height: 190px;
margin-bottom: 20px;
.grid-content {
box-sizing: border-box;
height: 100%;
padding: 15px;
margin: 10px;
background: #ffffff;
.imgBox {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100px;
background: #f2f2f2;
opacity: 0.5;
&.active {
background: #79bbff45;
opacity: 1;
}
img {
max-height: 100%;
object-fit: cover;
}
}
.info {
height: 80px;
.label {
margin-right: 8px;
font-size: 14px;
color: #41465f;
opacity: 0.6;
}
.desc {
max-width: 100%;
margin-top: 10px;
overflow: hidden;
font-size: 14px;
color: #41465f;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.btn {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
font-size: 14px;
color: #41465f;
border-top: 1px solid #f3f3f3;
}
}
}
}
.unrecognized {
.title {
font-size: 18px;
font-weight: bold;
line-height: 50px;
color: #41465f;
text-align: center;
}
}
}
</style>

+ 159
- 0
SafeCampus.WEB/src/views/attendance/roolcall/index.vue Vedi File

@@ -0,0 +1,159 @@
<!--
* @Description: 教师点名
* @Author: syy
* @Date: 2024-7-15
-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="personSetName"
id="personSetId"
width="300px"
title="班级管理"
:show-all="true"
:request-api="userManageClassManageApi.page"
@change="changeTreeFilter"
>
<template v-slot:label="{ row }">
<span class="custom-tree-node">
<span>{{ row.node.label }} {{ row.node.userName ? `(${row.node.userName})` : "" }}</span>
</span>
</template>
</TreeFilter>
<div class="table-box">
<ProTable ref="proTable" title="任务列表" :columns="columns" rowKey="id" :request-api="attendanceRoolcallApi.getTaskPageList">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button :opt="FormOptEnums.INITIATE" @click="onOpen(FormOptEnums.INITIATE, { personSetId: personSetId }, treeFilter.treeAllData)" />
<s-button
type="danger"
:opt="FormOptEnum.DELETE"
plain
suffix="点名任务"
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除点名任务')"
/>
</template>
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link :opt="FormOptEnum.VIEW" @click="onView(scope.row)" />
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.personId], `删除点名任务`)" />
</el-space>
</template>
</ProTable>
</div>
<!-- 发起新增/编辑表单 -->
<Form ref="formRef"></Form>
</div>
</template>
<script setup lang="tsx" name="SysUserPersonnel">
import { attendanceRoolcallApi,userManagePersonnelApi,userManageClassManageApi} from "@/api";
import { useHandleData } from "@/hooks/useHandleData";
import { FormOptEnum } from "@/enums";
import Form from "./components/form/index.vue";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { useRouter } from "vue-router";
const router = useRouter();

/** 表单操作类型枚举 */
enum FormOptEnums {
/** 发起 */
INITIATE = "发起",
/* 查看点名任务 */
VIEWTASK = "查看",
/* 修改点名记录 */
EDITTASK = "修改点名记录"
}

// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const visible = ref(false); //是否显示人员表单
const proTable = ref<ProTableInstance>();
const treeFilter = ref<any>();
// 表格配置项
const columns: ColumnProps[] = [
{ type: "selection", fixed: "left", width: 50 },
{
prop: "cameraName",
label: "摄像头名称"
},
{
prop: "continueTime",
label: "持续时间"
},
{
prop: "similarity",
label: "相似度"
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
const onView = (row: any) => {
router.push( {
path:'/roolcall/detail',
query: {
taskId: row.taskId,
personSetId:row.personSetId
}
});
};
// 发起任务表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);

/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnums, record: {},treeAllData:any) {
formRef.value?.onOpen({ opt: opt, record: record,treeAllData:treeAllData, successful: RefreshTable });
}



/**
* 任务删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
if(ids.length === 0){
ElMessage({
message: '请选择要删除的人员',
type: 'warning'
});
return
}
// 二次确认 => 请求api => 刷新表格
await useHandleData(userManagePersonnelApi.delete, {id: ids.join(",") }, msg);
RefreshTable(); //刷新表格
}

// 刷新表格
const RefreshTable = () => {
proTable.value?.refresh();
}

/** 班级切换 */
const personSetId = ref<number | string>()
function changeTreeFilter(val: number | string) {
personSetId.value = val
proTable.value!.pageable.pageNum = 1;
proTable.value!.searchParam.personSetId = val;
proTable.value!.search();
}
</script>
<style scoped lang="scss">
.table-box {
width: 100%;
height: 100%;
}
.custom-tree-node {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
padding-right: 8px;
font-size: 14px;
}
</style>

+ 184
- 0
SafeCampus.WEB/src/views/attendance/studentsReturn/components/returnConfirm/index.vue Vedi File

@@ -0,0 +1,184 @@
<!--
* @Description: 人脸识别失败表格
* @Author: yxq
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" row-key="id" :title="`${propsInfo.record.qinshibianhao}归寝确认`" form-size="800px" @close="onClose">
<ProTable ref="noFaceTableRef" title="归寝确认" height="500px" :data="tableData" :columns="columns">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button
type="primary"
:disabled="!scope.isSelected"
icon="check"
prefix="人工"
suffix="确认"
@click="personConfirm(scope.selectedListIds, '确认已归寝')"
/>
</template>
</ProTable>
<template #footer>
<el-button @click="onClose"> 关闭 </el-button>
</template>
</form-container>
<!-- 预览头像 -->
<el-dialog v-model="imgVisible" title="查看头像" width="830px">
<div style="display: flex; align-items: center; justify-content: center">
<img class="detailpic" :src="faceUrl" alt="" />
</div>
</el-dialog>
</div>
</template>
<script setup lang="tsx" name="attendanceStudentsReturn">
import { AttendanceStudentsReturn, attendanceStudentsReturn } from "@/api";
import { ColumnProps, noFaceTableInstance } from "@/components/noFaceTableRef/interface";
import { FormOptEnum } from "@/enums";
import { useHandleData } from "@/hooks";

const visible = ref(false); //是否显示
// 弹框参数
const propsInfo = reactive<FormProps.Base<AttendanceStudentsReturn.PassengerInfo>>({
opt: FormOptEnum.VIEW, //操作类型
record: {}, //弹框数据
disabled: false
});
/**
* 打开弹框
* @param props 弹框参数
*/
function onOpen(props: FormProps.Base<AttendanceStudentsReturn.PassengerInfo>) {
Object.assign(propsInfo, props); //合并参数
visible.value = true; //显示弹框
if (props.record.id) {
//如果传了id,就去请求api获取record
attendanceStudentsReturn.detail({ id: props.record.id }).then(res => {
propsInfo.record = res.data;
});
}
}

const tableData = ref([
{
id:'1',
studentname: "张三",
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
louceng:'3',
qinshi:'301',
},
{
id:'2',
studentname: "李四",
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
louceng:'3',
qinshi:'302',
},
{
id:'3',
studentname: "张三",
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
louceng:'4',
qinshi:'401',
},
{
id:'4',
studentname: "李四",
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
louceng:'3',
qinshi:'301',
},
{
id:'5',
studentname: "张三",
faces: [
{
faceUrl:
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D"
},
],
louceng:'5',
qinshi:'502',
}
]);
// 获取 noFaceTableRef 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const noFaceTableRef = ref<noFaceTableInstance>();
// 表格配置项
const columns: ColumnProps[] = [
{ type: "selection" },
{
prop: "louceng",
label: "楼层"
},
{
prop: "qinshi",
label: "寝室编号"
},
{
prop: "studentname",
label: "学生姓名"
},
{
prop: "faces",
label: "人脸",
render: scope => {
return (
<img
src={scope.row.faces.length > 0 ? scope.row.faces[0].faceUrl : ""}
onClick={() => viewHeadImage(scope)}
style="width:50px;height:50px;cursor:pointer"
alt=""
/>
);
}
}
];
// 图片预览
const imgVisible = ref(false);
const faceUrl = ref('');
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.faces[0].faceUrl;
imgVisible.value = true
};
/** 人工确认 */
async function personConfirm(ids: string[], msg: string) {
await useHandleData(attendanceStudentsReturn.delete, {id: ids.toString() }, msg);
RefreshTable(); //刷新表格
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}
// 刷新表格
const RefreshTable = () => {
noFaceTableRef.value?.refresh();
};
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>

+ 96
- 0
SafeCampus.WEB/src/views/attendance/studentsReturn/index.vue Vedi File

@@ -0,0 +1,96 @@
<!--
* @Description: 教师点名
* @Author: yxq
* @Date: 2024-7-16
-->
<template>
<div class="table-box">
<ProTable ref="proTable" title="教师点名" :columns="columns" :data="data">
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button
:disabled="!scope.row.noFaceNum"
link
prefix="人工"
:opt="FormOptEnum.EDIT"
suffix="确认"
@click="onOpen(FormOptEnum.VIEW, scope.row)"
/>
</el-space>
</template>
</ProTable>
<ReturnConfirm ref="ReturnConfirmRef" />
</div>
</template>
<script setup lang="ts">
import { attendanceStudentsReturn, AttendanceStudentsReturn } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { FormOptEnum } from "@/enums";
import ReturnConfirm from "./components/returnConfirm/index.vue";
const data = ref([
{
louceng: "3",
qinshibianhao: "306",
qinshirenshu: "6",
guiqinNum: "5",
noFaceNum: "1"
},
{
louceng: "3",
qinshibianhao: "307",
qinshirenshu: "8",
guiqinNum: "6",
noFaceNum: "2"
}
]);
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps[] = [
{
prop: "louceng",
label: "楼层"
},
{
prop: "qinshibianhao",
label: "寝室编号"
},
{
prop: "qinshirenshu",
label: "寝室人数"
},
{
prop: "guiqinNum",
label: "归寝人数"
},
{
prop: "noFaceNum",
label: "未归寝人数"
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
const ReturnConfirmRef = ref<InstanceType<typeof ReturnConfirm> | null>(null);
/**
* 人工确认
* @param opt 操作类型
* @param record 弹框数据
*/
function onOpen(opt: FormOptEnum, record: {} | AttendanceStudentsReturn.studentsReturnInfo = {}) {
switch (opt) {
case FormOptEnum.VIEW:
ReturnConfirmRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
break;
}
}
// 刷新表格
const RefreshTable = () => {
proTable.value?.refresh();
};
</script>
<style scoped lang="scss">
.table-box {
width: 100%;
height: 100%;
}
</style>

+ 20
- 14
SafeCampus.WEB/src/views/monitor/live/components/form.vue Vedi File

@@ -15,17 +15,20 @@
label-width="auto"
label-suffix=" :"
>
<s-form-item label="摄像头名称" prop="name">
<s-input v-model="orgProps.record.name"></s-input>
<s-form-item label="摄像头名称" prop="sensorName">
<s-input v-model="orgProps.record.sensorName"></s-input>
</s-form-item>
<s-form-item label="所属学校" prop="parentId">
<org-selector v-model:org-value="orgProps.record.parentId" :org-tree-api="bizOrgApi.tree" :show-all="false" />
<s-form-item label="所属学校" prop="fieldName">
<org-selector v-model:org-value="orgProps.record.fieldName" :org-tree-api="bizOrgApi.tree" :show-all="false" />
</s-form-item>
<s-form-item label="设备IP" prop="codeip">
<s-input v-model="orgProps.record.codeip" clearable></s-input>
<s-form-item label="设备IP" prop="directUrlIp">
<s-input v-model="orgProps.record.directUrlIp" clearable></s-input>
</s-form-item>
<s-form-item label="分辨率" prop="status">
<s-input v-model="orgProps.record.status" clearable></s-input>
<s-form-item label="分辨率(width)" prop="resWidth">
<s-input v-model="orgProps.record.resWidth" clearable></s-input>
</s-form-item>
<s-form-item label="分辨率(height)" prop="resHeight">
<s-input v-model="orgProps.record.resHeight" clearable></s-input>
</s-form-item>
</el-form>
<template #footer>
@@ -50,7 +53,7 @@ const dictStore = useDictStore(); //字典仓库
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);

// 表单参数
const orgProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({
const orgProps = reactive<FormProps.Base<any>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
@@ -58,10 +61,11 @@ const orgProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({

// 表单验证规则
const rules = reactive({
name: [required("请输入摄像头名称")],
parentId: [required("请选择所属学校")],
codeip: [required("请选择设备IP")],
status: [required("请输入分辨率")]
sensorName: [required("请输入摄像头名称")],
fieldName: [required("请选择所属学校")],
directUrlIp: [required("请选择设备IP")],
resWidth: [required("请输入分辨率(width)")],
resHeight: [required("请输入分辨率(height)")]
});

/**
@@ -72,12 +76,14 @@ function onOpen(props: FormProps.Base<SysOrg.SysOrgInfo>) {
Object.assign(orgProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
orgProps.record.sortCode = 99;
// orgProps.record.sortCode = 99;
// orgProps.record.status = statusOptions[0].value;
}
visible.value = true; //显示表单
if (props.record.id) {
orgProps.record = props.record;
//如果传了id,就去请求api获取record
return;
bizOrgApi.detail({ id: props.record.id }).then(res => {
orgProps.record = res.data;
});


+ 2
- 2
SafeCampus.WEB/src/views/monitor/live/components/moveForm.vue Vedi File

@@ -41,7 +41,7 @@ const dictStore = useDictStore(); //字典仓库
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);

// 表单参数
const orgProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({
const orgProps = reactive<FormProps.Base<any>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false,
@@ -57,7 +57,7 @@ const rules = reactive({
* 打开表单
* @param props 表单参数
*/
function omMove(props: FormProps.Base<SysOrg.SysOrgInfo>) {
function omMove(props: FormProps.Base<any>) {
// 合并参数
Object.assign(orgProps, props); //合并参数
if (orgProps.opt == FormOptEnum.ADD) {


+ 59
- 31
SafeCampus.WEB/src/views/monitor/live/components/userForm.vue Vedi File

@@ -15,10 +15,10 @@
label-width="auto"
label-suffix=" :"
>
<s-form-item label="指定分组推送人" prop="directorId">
<s-form-item label="指定推送人" prop="userId">
<el-button link type="primary" @click="showSelector">选择</el-button>
<el-tag v-if="liveUserProps.record.directorId" class="ml-3px" type="warning" closable @close="removeDirector">{{
liveUserProps.record.directorInfo?.name
<el-tag v-if="liveUserProps.record.userId" class="ml-3px" type="warning" closable @close="removeDirector">{{
liveUserProps.record.userInfo?.name
}}</el-tag>
</s-form-item>
</el-form>
@@ -32,7 +32,7 @@
</template>

<script setup lang="ts">
import { SysOrg, SysUser, sysOrgApi, sysPositionApi, sysRoleApi, sysUserApi } from "@/api";
import { SysOrg, SysUser, sysOrgApi, sysPositionApi, sysRoleApi, sysUserApi, monitorLIVEApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
@@ -43,7 +43,7 @@ const visible = ref(false); //是否显示表单
const dictStore = useDictStore(); //字典仓库

// 表单参数
const liveUserProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({
const liveUserProps = reactive<FormProps.Base<any>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
@@ -51,25 +51,27 @@ const liveUserProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({

// 表单验证规则
const rules = reactive({
directorId: [required("请选择指定分组推送人")]
userId: [required("请选择指定推送人")]
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysOrg.SysOrgInfo>) {
function onOpen(props: FormProps.Base<any>) {
Object.assign(liveUserProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
}
visible.value = true; //显示表单
// if (props.record.id) {
// //如果传了id,就去请求api获取record
// sysOrgApi.detail({ id: props.record.id }).then(res => {
// liveUserProps.record = res.data;
// });
// }
if (props.record.pushUserId) {
//如果传了id,就去请求api获取record
liveUserProps.record.userId = props.record.pushUserId;
liveUserProps.record.userInfo = props.record.sysUserItem;
// sysOrgApi.detail({ id: props.record.id }).then(res => {
// liveUserProps.record.userId = res.data;
// });
}
}

// 提交数据(新增/编辑)
@@ -78,45 +80,71 @@ const userFormRef = ref<FormInstance>();
async function handleSubmit() {
userFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
return;
//提交表单
await sysOrgApi
.submitForm(liveUserProps.record, liveUserProps.record.id != undefined)
.then(() => {
liveUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
console.log(liveUserProps, 888);
let params: any = {
groupId: "",
userId: "",
ids: []
};
if (liveUserProps.opt == "分组推送人") {
params.groupId = liveUserProps.record.id;
params.userId = liveUserProps.record.userId;
//提交表单
await monitorLIVEApi
.setPushPerson(params)
.then(() => {
liveUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
params.userId = liveUserProps.record.userId;
params.ids.push(liveUserProps.record.id);
//提交表单
await monitorLIVEApi
.setVideoPushPerson(params)
.then(() => {
liveUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
// delete params.
}
// console.log(params);
// return;
});
}

/** 关闭表单*/
function onClose() {
visible.value = false;
liveUserProps.record.userId = null;
liveUserProps.record.userInfo = null;
}

const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用
/** 显示用户选择器 */
function showSelector() {
//将liveUserProps.record.directorInfo转为 SysUser.SysUserInfo[]类型
const directorInfo = liveUserProps.record.directorInfo ? [liveUserProps.record.directorInfo] : [];
userSelectorRef.value?.showSelector(directorInfo);
//将liveUserProps.record.userInfo转为 SysUser.SysUserInfo[]类型
const userInfo = liveUserProps.record.userInfo ? [liveUserProps.record.userInfo] : [];
userSelectorRef.value?.showSelector(userInfo);
}

/** 选择用户 */
function handleChooseUser(data: SysUser.SysUserInfo[]) {
// 选择用户后,将用户id赋值给liveUserProps.record.directorId
// 选择用户后,将用户id赋值给liveUserProps.record.userId
if (data.length > 0) {
liveUserProps.record.directorId = data[0].id;
liveUserProps.record.directorInfo = data[0];
liveUserProps.record.userId = data[0].id;
liveUserProps.record.userInfo = data[0];
}
}

/** 移除主管 */
function removeDirector() {
liveUserProps.record.directorId = null;
liveUserProps.record.directorInfo = null;
liveUserProps.record.userId = null;
liveUserProps.record.userInfo = null;
}
// 暴露给父组件的方法
defineExpose({


+ 95
- 21
SafeCampus.WEB/src/views/monitor/live/index.vue Vedi File

@@ -11,7 +11,7 @@
<el-button icon="CirclePlus" @click="append('add', {})" type="primary">添加分组</el-button>
</div>
<div class="treeContent">
<el-input v-model="filterText" style="width: 240px" placeholder="请输入关键字" />
<el-input clearable v-model="filterText" style="width: 100%" placeholder="请输入关键字" />
<el-tree
style="max-width: 600px"
ref="treeRef"
@@ -30,9 +30,11 @@
<span class="custom-tree-node">
<span class="node-label" :title="node.label">{{ node.label }}</span>
<span v-if="data.id && data.id != '-1'">
<el-icon size="16" title="添加" @click.stop="append('add', data)"><CirclePlus /></el-icon>
<!-- <el-icon size="16" title="添加" @click.stop="append('add', data)"><CirclePlus /></el-icon> -->
<el-icon size="16" title="编辑" @click.stop="append('edit', data)" style="margin-left: 8px"><Edit /></el-icon>
<el-icon size="16" title="设置推送人" @click.stop="pushPerson('push', data)" style="margin-left: 8px"><UserFilled /></el-icon>
<el-icon size="16" title="设置推送人" @click.stop="pushPerson(FormOptEnum.GroupPushPerson, data)" style="margin-left: 8px"
><UserFilled
/></el-icon>
<el-icon size="16" title="删除" @click.stop="remove(data.id, '删除该分组')" style="margin-left: 8px"><Delete /></el-icon>
</span>
</span>
@@ -58,10 +60,25 @@
<!-- 表格 菜单类型 按钮 -->
<!-- 操作 -->
<template #operation="scope">
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)">编辑</s-button>
<s-button link :opt="FormOptEnum.VIEW" @click="onDetail(scope.row)"> 查看 </s-button>
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `确定删除该摄像头吗?`)" />
<s-button link :opt="FormOptEnum.VIEW" @click="pushPerson(scope.row)"> 推送人 </s-button>
<el-space>
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)">编辑</s-button>
<s-button link :opt="FormOptEnum.VIEW" @click="onDetail(scope.row)"> 查看 </s-button>
<!-- <s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `确定删除该摄像头吗?`)" />
<s-button link :opt="FormOptEnum.VIEW" @click="pushPerson(FormOptEnum.VideoPushPerson, scope.row)"> 推送人 </s-button> -->
<el-dropdown @command="handleCommand">
<el-link type="primary" :underline="false" :icon="ArrowDown"> 更多 </el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="command(scope.row, cmdEnum.Delete)">
{{ cmdEnum.Delete }}
</el-dropdown-item>
<el-dropdown-item :command="command(scope.row, cmdEnum.pushPerson)">
{{ cmdEnum.pushPerson }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-space>
</template>
</ProTable>
<!-- 添加分组弹框 -->
@@ -111,21 +128,84 @@
</div>
</div>
</template>
<script setup lang="tsx" name="sysSpa">
<script setup lang="tsx" name="live">
import VideoPlay from "@/components/VideoPlay/videoplay.vue";
import { ElMessage, ElMessageBox, ElTree } from "element-plus";
import { ArrowDown,More } from "@element-plus/icons-vue";
import type Node from "element-plus/es/components/tree/src/model/node";
import { monitorLIVEApi, monitorLiveButtonCode } from "@/api";
import { ZJRQ } from "@/api/interface";
import { monitorLIVEApi, monitorLiveButtonCode,SysOrg,SysUserPersonnel } from "@/api";
import { sysCamera } from "@/api/interface";
import { useHandleData } from "@/hooks/useHandleData";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { useDictStore } from "@/stores/modules";
import Form from "./components/form.vue";
import userForm from "./components/userForm.vue";
import moveForm from "./components/moveForm.vue";
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
// import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
// import './aliyun-rts-sdk.js'
import "./ali.js";
enum FormOptEnum {
/** 新增 */
ADD = "新增",
/** 编辑 */
EDIT = "编辑",
/** 查看 */
VIEW = "查看",
/** 删除 */
DELETE = "删除",
// 分组推送人
GroupPushPerson = "分组推送人",
// 视频推送人
VideoPushPerson = "视频推送人",
// 移动
MOVE = "移动",
// 分组
GROUP = "分组"
}
/** 更多下拉菜单命令枚举 */
enum cmdEnum {
Delete = "删除",
pushPerson = "推送人"
}
/** 下拉菜单参数接口 */
interface Command {
row: SysUserPersonnel.SysUserPerInfo;
command: cmdEnum;
}

/**配置command的参数 */
function command(row: SysUserPersonnel.SysUserPerInfo, command: cmdEnum): Command {
return {
row: row,
command: command
};
}
function handleCommand(command: Command) {
switch (command.command) {
case cmdEnum.Delete:
ElMessageBox.confirm(`确定要删除此数据吗?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
onDelete([command.row.id])
})
.catch(() => {
})
break
case cmdEnum.pushPerson:
pushPerson(FormOptEnum.VideoPushPerson, command.row)
// userManagePersonnelApi.personUnBindDfie({
// personId:command.row.personId,
// personSetId: command.row.personSets[0].personSetId
// }).then(res=>{
// ElMessage.success('底库解绑成功');
// RefreshTable()
// })
break;
}
}
// 分组字段配置
const defaultProps = {
children: "children",
@@ -228,17 +308,10 @@ const filterNode = (value: string, data: Tree) => {
};

const handleNodeClick = (data: Tree) => {
console.log(data);
// personSetId.value = val
proTable.value!.pageable.pageNum = 1;
proTable.value!.searchParam.groupId = data.id;
proTable.value!.search();
};

// 设置分组推送人
// async function pushPerson(type:string,data: Tree) {

// }
const visible = ref(false); //是否显示表单

// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
@@ -246,7 +319,7 @@ const proTable = ref<ProTableInstance>();
const dictStore = useDictStore();

// 表格配置项
const columns: ColumnProps<ZJRQ.WarnInfo>[] = [
const columns: ColumnProps<sysCamera.MonitorInfo>[] = [
{ type: "selection", fixed: "left", width: 80 },
{
prop: "sensorId",
@@ -285,6 +358,7 @@ function omMove(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
console.log(1)
return;
// 二次确认 => 请求api => 刷新表格
await useHandleData(monitorLIVEApi.delete, { ids }, msg);
@@ -403,7 +477,7 @@ function getvideo2() {
source: detailData.videoUrl + "&subaudio=no&jitterbuffer=6000",
// "rtsFallbackSource": "降级地址,如HLS",
width: "100%",
height: "600px",
height: "500px",
autoplay: true,
isLive: true,
playsinline: true,
@@ -434,7 +508,7 @@ function getvideo2() {
player.value.on("error", function (event:any) {
console.log("[EVENT]error", event.paramData);
});
player.value.setVolume(0)
// 当RTS拉流成功时触发,通过订阅该事件,可以获取到RTS TraceId
player.value.on("rtsTraceId", function (data:any) {
console.log("[EVENT]rtsTraceId", data.paramData);


+ 138
- 0
SafeCampus.WEB/src/views/sysconfig/ability/components/userForm.vue Vedi File

@@ -0,0 +1,138 @@
<!--
* @Description: 表单
* @Author: huguodong
* @Date: 2023-12-15 15:45:28
!-->
<template>
<div>
<form-container v-model="visible" title="人员选择" form-size="600px">
<el-form
ref="userFormRef"
:rules="rules"
:disabled="liveUserProps.disabled"
:model="liveUserProps.record"
:hide-required-asterisk="liveUserProps.disabled"
label-width="auto"
label-suffix=" :"
>
<s-form-item label="指定推送人" prop="userId">
<el-button link type="primary" @click="showSelector">选择</el-button>
<el-tag v-if="liveUserProps.record.userId" class="ml-3px" type="warning" closable @close="removeDirector">{{
liveUserProps.record.userInfo?.name
}}</el-tag>
</s-form-item>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!liveUserProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
<user-selector ref="userSelectorRef" :org-tree-api="sysOrgApi.tree" :user-selector-api="sysUserApi.selector" @successful="handleChooseUser" />
</div>
</template>

<script setup lang="ts">
import { SysOrg, SysUser, sysOrgApi, sysPositionApi, sysRoleApi, sysUserApi, monitorLIVEApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";

const visible = ref(false); //是否显示表单
const dictStore = useDictStore(); //字典仓库

// 表单参数
const liveUserProps = reactive<FormProps.Base<any>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});

// 表单验证规则
const rules = reactive({
userId: [required("请选择指定推送人")]
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<any>) {
Object.assign(liveUserProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
}
visible.value = true; //显示表单
if (props.record.pushUserId) {
//如果传了id,就去请求api获取record
liveUserProps.record.userId = props.record.pushUserId;
liveUserProps.record.userInfo = props.record.sysUserItem;
// sysOrgApi.detail({ id: props.record.id }).then(res => {
// liveUserProps.record.userId = res.data;
// });
}
}

// 提交数据(新增/编辑)
const userFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
userFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
let params: any = {
warnCode: "",
userId: ""
};
if (liveUserProps.opt == "预警推送人") {
params.warnCode = liveUserProps.record.warnCode;
params.userId = liveUserProps.record.userId;
//提交表单
await monitorLIVEApi
.setWarningPushPerson(params)
.then(() => {
liveUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}

/** 关闭表单*/
function onClose() {
visible.value = false;
liveUserProps.record.userId = null;
liveUserProps.record.userInfo = null;
}

const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用
/** 显示用户选择器 */
function showSelector() {
//将liveUserProps.record.userInfo转为 SysUser.SysUserInfo[]类型
const userInfo = liveUserProps.record.userInfo ? [liveUserProps.record.userInfo] : [];
userSelectorRef.value?.showSelector(userInfo);
}

/** 选择用户 */
function handleChooseUser(data: SysUser.SysUserInfo[]) {
// 选择用户后,将用户id赋值给liveUserProps.record.userId
if (data.length > 0) {
liveUserProps.record.userId = data[0].id;
liveUserProps.record.userInfo = data[0];
}
}

/** 移除主管 */
function removeDirector() {
liveUserProps.record.userId = null;
liveUserProps.record.userInfo = null;
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>

<style lang="scss" scoped></style>

+ 59
- 20
SafeCampus.WEB/src/views/sysconfig/ability/index.vue Vedi File

@@ -5,16 +5,32 @@
!-->
<template>
<div class="abilityBox">
<TreeFilter ref="treeFilter" label="name" id="id" width="300px" :request-api="monitorLIVEApi.groupList" @change="changeGroup"> </TreeFilter>
<TreeFilter ref="treeFilter" title="摄像头分组" label="name" id="id" width="300px" :request-api="monitorLIVEApi.groupList" @change="changeGroup">
</TreeFilter>

<TreeFilter ref="treeFilters" label="sensorName" id="sensorId" :isData="true" width="300px" :data="videoList" @change="changeVideo"> </TreeFilter>
<TreeFilter
ref="treeFilters"
title="摄像头列表"
label="sensorName"
id="sensorId"
:isData="true"
width="300px"
:data="videoList"
@change="changeVideo"
>
</TreeFilter>

<div class="card content-main">
<el-collapse accordion>
<el-collapse-item :name="ins" v-for="(item, ins) in warnGroupList" :key="item.cameraId[0]">
<template #title>
<div class="collapse-title" @click.stop="">
<div class="titlemodel">{{ item.name }} {{ item.cameraId[0] }}</div>
<div class="titlemodel">
<div style="width: 220px; text-align: left">{{ item.name }} {{ item.cameraId[0] }}</div>
<div style="margin-left: 20px">
<el-button @click="pushPerson(FormOptEnum.VideoPushPerson, item)" type="primary" size="small">设置推送人</el-button>
</div>
</div>
<div class="btns">
<el-switch v-model="item.state" @change="stateChange" />
</div>
@@ -29,12 +45,12 @@
</div>
</div> -->
<el-row :gutter="20">
<el-col :span="6" v-for="(v, index) in item.subset" :key="v.code">
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" v-for="v in item.subset" :key="v.code">
<div class="contentinfo">
<div class="modellabel">{{ v.name }}</div>

<div class="btns">
<el-switch v-model="v.state" @change="stateChange" />
<el-switch :disabled="item.state == false" v-model="v.state" @change="stateChange" />
</div>
</div>
</el-col>
@@ -43,35 +59,52 @@
</el-collapse-item>
</el-collapse>
</div>
<!-- 人员选择 -->
<userForm ref="userFormRef" />
</div>
</template>

<script setup lang="tsx" name="sysSpa">
<script setup lang="tsx" name="ability">
import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { ElMessage } from "element-plus";
import { abilityApi, userManageClassManageApi, monitorLIVEApi } from "@/api";
import { abilityApi, userManageClassManageApi, monitorLIVEApi,SysOrg } from "@/api";
// import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
import userForm from "./components/userForm.vue";

const value = ref(true);
enum FormOptEnum {
/** 新增 */
ADD = "新增",
/** 编辑 */
EDIT = "编辑",
/** 查看 */
VIEW = "查看",
/** 删除 */
DELETE = "删除",
// 分组推送人
GroupPushPerson = "分组推送人",
// 视频推送人
VideoPushPerson = "视频推送人",
// 移动
MOVE = "移动",
// 分组
GROUP = "分组"
}
onMounted(() => {
// 在这里执行其他需要在组件挂载后运行的代码
getwarnGroup();
getVideoList()
getVideoList('')
});
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
const treeFilters = ref<InstanceType<typeof TreeFilter> | null>(null);
function changeGroup(val: number | string) {
console.log(val)
// personSetId.value = val
// proTable.value!.pageable.pageNum = 1;
// proTable.value!.searchParam.personSetId = val;
// proTable.value!.search();
// getVideoList(val.id)
getVideoList(val)
}
const videoList = ref<any>();
function getVideoList() {
function getVideoList(id:any) {
console.log(id,11)
setTimeout(async () => {
await monitorLIVEApi.list({ pageNum: 1, pageSize: 1000 }).then(res => {
await monitorLIVEApi.list({ pageNum: 1, pageSize: 1000, groupId:id }).then(res => {
videoList.value = res.data.list;
});
});
@@ -87,10 +120,10 @@ function changeVideo(val: number | string) {
// 获取配置树

// 获取配置树
let warnGroupList = ref([]);
let warnGroupList = ref<any>([]);
function getwarnGroup() {
setTimeout(async () => {
await abilityApi.warnGroup({}).then(res => {
await abilityApi.warnGroup({}).then((res:any) => {
let { code, data } = res;
if (code == 200) {
warnGroupList.value = data;
@@ -99,11 +132,15 @@ function getwarnGroup() {
});
});
}
const userFormRef = ref<any>(null);
function pushPerson(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
userFormRef.value?.onOpen({ opt: opt, record: record, successful: getwarnGroup });
}
// 开关
function stateChange() {
let params: string = JSON.stringify(warnGroupList.value);
setTimeout(async () => {
await abilityApi.setWarnGroup({ configJson: params }).then(res => {
await abilityApi.setWarnGroup({ configJson: params }).then((res:any) => {
let { code, data, msg } = res;
if (code == 200 && data) {
// warnGroupList.value = data;
@@ -137,6 +174,8 @@ function stateChange() {
justify-content: space-between;
order: 1;
.titlemodel {
display: flex;
align-items: center;
margin-right: 40px;
font-size: 18px;
}


+ 1
- 1
SafeCampus.WEB/src/views/userManage/clothing/components/form/index.vue Vedi File

@@ -30,7 +30,7 @@
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<s-form-item label="上传服装" prop="faces">
<s-form-item label="上传服装" prop="clothUrl">
<el-upload
class="avatar-uploader"
action="/api/business/clothApi/uploadFile"


+ 1
- 1
SafeCampus.WEB/src/views/userManage/clothing/index.vue Vedi File

@@ -60,7 +60,7 @@
<!-- 预览头像 -->
<el-dialog v-model="visible" title="查看头像" width="830px" :before-close="handleClose">
<div style="display: flex; align-items: center; justify-content: center">
<img style="max-width: 100%" class="detailpic" :src="faceUrl" alt="" />
<img style="max-width: 100%; max-height: 600px" class="detailpic" :src="faceUrl" alt="" />
</div>
<template #footer>
<div class="dialog-footer">


+ 1
- 1
SafeCampus.WEB/src/views/userManage/keyPersonnel/index.vue Vedi File

@@ -57,7 +57,7 @@
<!-- 预览头像 -->
<el-dialog v-model="visible" title="查看头像" width="830px" :before-close="handleClose">
<div style="display: flex; align-items: center; justify-content: center">
<img class="detailpic" :src="faceUrl" alt="" style="max-width: 100%" />
<img class="detailpic" :src="faceUrl" alt="" style="max-width: 100%; max-height: 600px" />
</div>
</el-dialog>
</div>


+ 1
- 1
SafeCampus.WEB/src/views/userManage/personnel/index.vue Vedi File

@@ -109,7 +109,7 @@
<!-- 预览头像 -->
<el-dialog v-model="visible" title="查看头像" width="830px" :before-close="handleClose">
<div style="display: flex; align-items: center; justify-content: center">
<img class="detailpic" :src="faceUrl" alt="" />
<img style="max-width: 100%; max-height: 600px" class="detailpic" :src="faceUrl" alt="" />
</div>
</el-dialog>
</div>


Caricamento…
Annulla
Salva