王晓寒 4 months ago
parent
commit
9dfe619d49
32 changed files with 2330 additions and 508 deletions
  1. +7
    -0
      SafeCampus.WEB/src/api/interface/index.ts
  2. +1
    -0
      SafeCampus.WEB/src/api/interface/sys/index.ts
  3. +60
    -0
      SafeCampus.WEB/src/api/interface/sys/monitor/camera.ts
  4. +1
    -0
      SafeCampus.WEB/src/api/interface/sys/monitor/index.ts
  5. +39
    -0
      SafeCampus.WEB/src/api/interface/sys/usermanage/clothing.ts
  6. +1
    -0
      SafeCampus.WEB/src/api/interface/sys/usermanage/index.ts
  7. +7
    -5
      SafeCampus.WEB/src/api/interface/sys/usermanage/personnel.ts
  8. +34
    -9
      SafeCampus.WEB/src/api/modules/monitor/live.ts
  9. +2
    -2
      SafeCampus.WEB/src/api/modules/usermanage/classManage.ts
  10. +68
    -0
      SafeCampus.WEB/src/api/modules/usermanage/clothing.ts
  11. +3
    -0
      SafeCampus.WEB/src/api/modules/usermanage/index.ts
  12. +63
    -0
      SafeCampus.WEB/src/api/modules/usermanage/keyPersonnel.ts
  13. +54
    -0
      SafeCampus.WEB/src/api/modules/usermanage/teacher.ts
  14. +2
    -1
      SafeCampus.WEB/src/components/TreeFilter/index.scss
  15. +12
    -6
      SafeCampus.WEB/src/components/TreeFilter/index.vue
  16. +2
    -0
      SafeCampus.WEB/src/typings/props.d.ts
  17. +15
    -17
      SafeCampus.WEB/src/views/monitor/live/components/moveForm.vue
  18. +21
    -16
      SafeCampus.WEB/src/views/monitor/live/index.scss
  19. +336
    -349
      SafeCampus.WEB/src/views/monitor/live/index.vue
  20. +175
    -0
      SafeCampus.WEB/src/views/practicalTraining/model/index.vue
  21. +104
    -73
      SafeCampus.WEB/src/views/sysconfig/ability/index.vue
  22. +128
    -0
      SafeCampus.WEB/src/views/sysconfig/push/index.vue
  23. +156
    -0
      SafeCampus.WEB/src/views/userManage/clothing/components/form/index.vue
  24. +105
    -0
      SafeCampus.WEB/src/views/userManage/clothing/components/form1/index.vue
  25. +195
    -0
      SafeCampus.WEB/src/views/userManage/clothing/index.vue
  26. +181
    -0
      SafeCampus.WEB/src/views/userManage/keyPersonnel/components/form/form_basic.vue
  27. +117
    -0
      SafeCampus.WEB/src/views/userManage/keyPersonnel/components/form/index.vue
  28. +220
    -0
      SafeCampus.WEB/src/views/userManage/keyPersonnel/index.vue
  29. +6
    -7
      SafeCampus.WEB/src/views/userManage/personnel/components/form/form_basic.vue
  30. +116
    -0
      SafeCampus.WEB/src/views/userManage/personnel/components/formTeacher/index.vue
  31. +82
    -16
      SafeCampus.WEB/src/views/userManage/personnel/index.vue
  32. +17
    -7
      SafeCampus.WEB/src/views/warn/zjrq/index.vue

+ 7
- 0
SafeCampus.WEB/src/api/interface/index.ts View File

@@ -51,6 +51,13 @@ export interface ReqId {
/** id */
id: number | string;
}

/** ReqClothId请求参数 */
export interface ReqClothId {
/** ReqClothId */
clothSetId: number | string;
}

/** id请求参数 */
export interface ReqPersonId {
/** id */


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

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

+ 60
- 0
SafeCampus.WEB/src/api/interface/sys/monitor/camera.ts View File

@@ -0,0 +1,60 @@
/**
* @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/interface";
/**
* @Description: 单页管理接口
* @Author: wangwenpei
* @Date: 2023-12-15 15:34:54
*/
export namespace sysCamera {
/**单页分页查询 */
export interface Page extends ReqPage {
alarmType: "visual_fence";
}

/** 单页信息 */
export interface MonitorInfo {
/** id */
id: number | string;
deviceStatus: boolean;
directUrlIp: string;
fieldId: number | string;
fieldName: string;
groupId: number;
lastTime: string;
pushUserId: number;
resHeight: number;
resWidth: number;
sensorId: string;
sensorName: string;
/** 摄像头快照 */
snapshotUrl: string;
}
/**摄像头分页查询 */
export interface List extends ReqPage {
sensorName?: string;
sensorId?: number | string;
groupId?: number | string;
}
// 摄像头分组
export interface Tree extends MonitorInfo {
children: Tree[];
}
// 摄像头分组添加编辑删除信息
export interface MonitorGroupInfo {
/** id */
id: number | string;
name: string;
}
}

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

@@ -0,0 +1 @@
export * from "./camera";

+ 39
- 0
SafeCampus.WEB/src/api/interface/sys/usermanage/clothing.ts View File

@@ -0,0 +1,39 @@
/**
* @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.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
*/
/**
* @Description: 服装底库管理接口
* @Author: syy
* @Date: 2023-12-15 15:34:54
*/

export namespace SysUserCloth {
// 服装底库树
export interface Page {
clothSetId?: number | string;
clothSetName?: number | string;
}

export interface list {
clothUrl: string;
clothId: number | string;
clothSetId: string;
}
/** 用户信息 */
export interface SysUserClothInfo {
clothSetId: number | string;
clothSetName: number | string;
/** 列表 */
clothes?: Array<list>;
}
}

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

@@ -13,3 +13,4 @@
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
export * from "./personnel";
export * from "./clothing";

+ 7
- 5
SafeCampus.WEB/src/api/interface/sys/usermanage/personnel.ts View File

@@ -26,9 +26,11 @@ export namespace SysUserPersonnel {
export interface ClassPage {
personSetId?: string | number | undefined;
personSetName?: string | undefined;
id?: string | undefined;
name?: string | undefined;
personId?: string | undefined | number;
id?: string | number | undefined;
name?: string | number | undefined;
userId?: string | number | undefined;
personId?: string | number | undefined;
userName?: string | number | undefined;
}
/** 人脸信息 */
export interface SysUserAvatar {
@@ -59,9 +61,9 @@ export namespace SysUserPersonnel {
/** 扩展字段 */
extData?: string;
/** 人脸 */
faces: Array<SysUserAvatar>;
faces: any;
/** 分组 */
personSets: Array<ClassPage>;
personSets: any;
personSetId?: number | string;
}
}

+ 34
- 9
SafeCampus.WEB/src/api/modules/monitor/live.ts View File

@@ -13,28 +13,53 @@
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
import { moduleRequest } from "@/api/request";
import { ReqstartId, ResPage, ZJRQ, ReqstopId } from "@/api/interface";
import { ReqstartId, ResPage, sysCamera, ReqstopId } from "@/api/interface";
const http = moduleRequest("/business/deviceApi/");
const http2 = moduleRequest("/business/cameraInfo/");
const http3 = moduleRequest("/business/cameraGroup/");

/**
* @Description: 单页管理
* @Author: huguodong
* @Author: wangwenpei
* @Date: 2023-12-15 15:34:54
*/
const monitorLIVEApi = {
/** 获取单页分页 */
page(params: ZJRQ.Page) {
return http.post<ResPage<ZJRQ.WarnInfo>>("brief", params);
page(params: sysCamera.Page) {
return http.post<ResPage<sysCamera.MonitorInfo>>("brief", params);
},
/** 获取单页详情 */
detail(params: ReqstartId) {
return http.get<ZJRQ.WarnInfo>("getStartVideoLive", params);
return http.get<sysCamera.MonitorInfo>("getStartVideoLive", params);
},
/** 停止视频流获取 */
stopUrl(params: ReqstopId) {
return http.get<sysCamera.MonitorInfo>("getStopVideoLive", params);
},
/** 获取监控列表 */
list(params: sysCamera.List) {
return http2.get<ResPage<sysCamera.MonitorInfo>>("getPageList", params);
},
// 批量设置分组
setGroup(params: sysCamera.setGroup) {
return http2.post<ResPage<sysCamera.MonitorInfo>>("batchSetGroup", params);
},
// 获取摄像头分组树
groupList(params: sysCamera.Tree) {
return http3.get<ResPage<sysCamera.MonitorInfo>>("getNoPageList");
},
// 添加摄像头分组树
addGroup(params: sysCamera.MonitorGroupInfo) {
return http3.post<ResPage<sysCamera.MonitorInfo>>("add", params);
},
/** 停止视频流获取 */
stopUrl(params: ReqstopId) {
return http.get<ZJRQ.WarnInfo>("getStopVideoLive", params);
// 编辑摄像头分组树
updateGroup(params: sysCamera.MonitorGroupInfo) {
return http3.put<ResPage<sysCamera.MonitorInfo>>("update", params);
},
// 删除摄像头分组树
deleteGroup(params: sysCamera.MonitorGroupInfo) {
return http3.delete<ResPage<sysCamera.MonitorInfo>>("delete", params);
}
};
/**
* @Description: 监控管理按钮权限码


+ 2
- 2
SafeCampus.WEB/src/api/modules/usermanage/classManage.ts View File

@@ -23,8 +23,8 @@ const http = moduleRequest("/business/dfieldApi/");
*/
const userManageClassManageApi = {
/** 查询底库列表 */
page(params: SysUserPersonnel.ClassPage) {
return http.get("queryAll", params);
page() {
return http.get("queryAll");
},
/** 删除底库 */
delete(params: ReqId) {


+ 68
- 0
SafeCampus.WEB/src/api/modules/usermanage/clothing.ts View File

@@ -0,0 +1,68 @@
/**
* @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, SysUserCloth, ReqClothId } from "@/api/interface";
const http = moduleRequest("/business/clothApi/");
/**
* @Description: 单页管理
* @Author: SYY
* @Date: 2023-12-15 15:34:54
*/
const userManageClothApi = {
/** 查询服装底库列表 */
getList(params: SysUserCloth.Page) {
return http.get("getList", params);
},
/** 查询服装底库详情 */
page(params: ReqClothId) {
return http.get("getInfo", params);
},
/** 删除服装底库 */
deleteClothDataBaseD(params: ReqClothId) {
return http.delete("deleteClothDataBaseD", params);
},
/** 新增服装底库 */
addClothDataBaseA(params: SysUserCloth.Page) {
return http.post("addClothDataBaseA", params);
},
/** 更新服装底库 */
update(params: SysUserCloth.Page) {
return http.put("updateClothU", params);
},

/** 服装图片上传 */
uploadFile(params: any) {
return http.post("uploadFile", params);
},
/** 新增服装 */
add(params: any) {
return http.post("addClothA", params);
},
/** 删除服装 */
delete(params: SysUserCloth.list) {
return http.post("deleteClothD", params);
}
};

const userClothButtonCode = {
/** 新增人员 */
add: "userManageClothAdd",
/** 删除人员 */
edit: "userManageClothEdit",
/** 删除人员 */
delete: "userManageClothDelete"
};

export { userManageClothApi, userClothButtonCode };

+ 3
- 0
SafeCampus.WEB/src/api/modules/usermanage/index.ts View File

@@ -14,3 +14,6 @@
*/
export * from "./personnel";
export * from "./classManage";
export * from "./clothing";
export * from "./teacher";
export * from "./keyPersonnel";

+ 63
- 0
SafeCampus.WEB/src/api/modules/usermanage/keyPersonnel.ts View File

@@ -0,0 +1,63 @@
/**
* @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/keyPersonnel/");
/**
* @Description: 单页管理
* @Author: SYY
* @Date: 2024-7-19 15:34:54
*/
const userManageKeyPersonApi = {
/** 获取单页分页 */
page(params: SysUserPersonnel.Page) {
return http.post("pageQuery", params);
},
/** 获取单页详情 */
detail(params: ReqId) {
return http.get("getPersionById", params);
},
/** 删除人员 */
delete(params: ReqId) {
return http.delete("deletePersonD", params);
},
/** 新增人员 */
add(params: any) {
return http.post("createPersonA", params);
},
/** 修改人员 */
update(params: any) {
return http.put("updatePersionU", params);
},
/** 添加人脸 */
addFace(params: SysUserPersonnel.SysUserAvatar) {
return http.post("addFaceA", params);
},
/** 删除人脸 */
deleteFace(params: SysUserPersonnel.SysUserFace) {
return http.post("deleteFaceD", params);
}
};

const userKeyPerButtonCode = {
/** 新增人员 */
add: "userManageKeyPersonAdd",
/** 删除人员 */
edit: "userManageKeyPersonEdit",
/** 删除人员 */
delete: "userManageKeyPersonDelete"
};

export { userManageKeyPersonApi, userKeyPerButtonCode };

+ 54
- 0
SafeCampus.WEB/src/api/modules/usermanage/teacher.ts View File

@@ -0,0 +1,54 @@
/**
* @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/classTeacher/");
/**
* @Description: 单页管理
* @Author: SYY
* @Date: 2023-12-15 15:34:54
*/
const userManageTeacherApi = {
/** 查询班主任列表 */
page() {
return http.get("getNoPageList");
},
/** 获取单页详情 */
detail(params: any) {
return http.get("getInfo", params);
},
/** 删除班主任 */
delete(params: any) {
return http.delete("delete", params);
},
/** 创建班主任 */
add(params: any) {
return http.post("add", params);
},
/** 更新班主任 */
update(params: any) {
return http.put("update", params);
}
};

const userTeacherButtonCode = {
/** 新增人员 */
add: "userManageClassManageAdd",
/** 删除人员 */
edit: "userManageClassManageEdit",
/** 删除人员 */
delete: "userManageClassManageDelete"
};

export { userManageTeacherApi, userTeacherButtonCode };

+ 2
- 1
SafeCampus.WEB/src/components/TreeFilter/index.scss View File

@@ -26,7 +26,8 @@
.el-tree-node.is-current > .el-tree-node__content {
background-color: var(--el-color-primary);
.el-tree-node__label,
.el-tree-node__expand-icon {
.el-tree-node__expand-icon,
.el-dropdown .el-link {
color: white;
}
.is-leaf {


+ 12
- 6
SafeCampus.WEB/src/components/TreeFilter/index.vue View File

@@ -56,6 +56,7 @@ interface TreeFilterProps {
topName?: string; // 顶级分类名称 ==> 非必传,默认为 “全部”
showAll?: boolean; // 是否显示全部选项 ==> 非必传,默认为 true
width: string;
isData?: boolean;
}
const props = withDefaults(defineProps<TreeFilterProps>(), {
id: "id",
@@ -65,7 +66,8 @@ const props = withDefaults(defineProps<TreeFilterProps>(), {
defaultExpandLevel: 1,
checkStrictly: false,
topName: "全部",
showAll: true
showAll: true,
isData: false
});

const defaultProps = {
@@ -98,7 +100,7 @@ watch(
const setTreeAllData = (data: any) => {
//如果需要显示全部选项就加上全部,否则就拿到什么输出什么
if (props.showAll) {
treeAllData.value = [{ id: "", [props.label]: props.topName }, ...data];
treeAllData.value = [{ [props.id]: "", [props.label]: props.topName }, ...data];
} else {
treeAllData.value = data;
}
@@ -156,13 +158,17 @@ const refresh = async () => {
treeRef.value?.setCheckedKeys([]);
treeRef.value?.setCurrentKey("");
setSelected();
await getRequestData();
if (!props.isData) {
await getRequestData();
}
};

const getRequestData = async () => {
const { data } = await props.requestApi!();
treeData.value = data;
setTreeAllData(data);
if (!props.isData) {
const { data } = await props.requestApi!();
treeData.value = data;
setTreeAllData(data);
}
};

/** 获取默认展开层级 */


+ 2
- 0
SafeCampus.WEB/src/typings/props.d.ts View File

@@ -27,6 +27,8 @@ declare namespace FormProps {
disabled?: boolean;
/** 行内表单模式 */
inline?: boolean;
// 树数据
treeAllData?: Array<any>;
/** 表单布局 */
successful?: () => void;
}


+ 15
- 17
SafeCampus.WEB/src/views/monitor/live/components/moveForm.vue View File

@@ -15,8 +15,8 @@
label-width="auto"
label-suffix=" :"
>
<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="id">
<org-selector v-model:org-value="orgProps.record.id" :org-tree-api="monitorLIVEApi.groupList" :show-all="false" />
</s-form-item>
</el-form>
<template #footer>
@@ -28,7 +28,7 @@
</template>

<script setup lang="ts">
import { SysOrg, SysUser, bizOrgApi, bizPositionApi, sysRoleApi, bizUserApi } from "@/api";
import { SysOrg, monitorLIVEApi, SysUser, bizOrgApi, bizPositionApi, sysRoleApi, bizUserApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
@@ -44,12 +44,13 @@ const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
const orgProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
disabled: false,
records: []
});

// 表单验证规则
const rules = reactive({
parentId: [required("请选择所属学校")]
id: [required("请选择摄像头分组")]
});

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

@@ -79,10 +74,13 @@ async function handleSubmit() {
liveFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
console.log(orgProps);
return;
let params: any = {
id: orgProps.record.id,
ids: orgProps.records
};
//提交表单
await bizOrgApi
.submitForm(orgProps.record, orgProps.record.id != undefined)
await monitorLIVEApi
.setGroup(params)
.then(() => {
orgProps.successful!(); //调用父组件的successful方法
})


+ 21
- 16
SafeCampus.WEB/src/views/monitor/live/index.scss View File

@@ -1,48 +1,53 @@
ul,li {
list-style: none;
padding: 0;
margin: 0;
}

.treeBox {
box-sizing: border-box;
width: 280px;
flex-shrink: 1;
width: 320px;
height: 100%;
padding: 14px;
margin-right: 10px;
flex-shrink: 1;
.title {
margin: 0 0 15px;
font-size: 18px;
font-weight: bold;
color: var(--el-color-info-dark-2);
letter-spacing: 0.5px;
}
.btn {

}
.treeContent {
padding: 10px 0;

// height: calc(100% - 100px);
// overflow: auto;
.el-tree-node__content {
height: 33px;
}
.custom-tree-node {
flex: 1;
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
font-size: 14px;
.node-label {
width: 100px;
overflow: hidden;
font-size: 16px;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
}
}
:deep(.el-tree--highlight-current) {
.el-tree-node.is-current > .el-tree-node__content {
color: white;
background-color: var(--el-color-primary);

// .el-tree-node__label,
// .el-tree-node__expand-icon {
// color: white;
// }
// .is-leaf {
// color: transparent;
// }
}
}
:deep(.el-tree-node__content) {
height: 50px;
@@ -74,5 +79,5 @@ ul,li {
}
}
.table-box {
width: calc(100% - 280px);
width: calc(100% - 320px);
}

+ 336
- 349
SafeCampus.WEB/src/views/monitor/live/index.vue View File

@@ -1,44 +1,39 @@
<!--
* @Description: 告警管理
* @Author: huguodong
* @Date: 2023-12-15 15:44:05
* @Description: 监控管理
* @Author: wangwenpei
* @Date: 2024-07-18 15:44:05
!-->
<template>
<div class="main-box">
<div class="card treeBox">
<p class="title">摄像头分组管理</p>
<div class="btn">
<el-button @click="append('add', {})" type="primary">添加分组</el-button>
<el-button icon="CirclePlus" @click="append('add', {})" type="primary">添加分组</el-button>
</div>
<!-- <div class="treeContent">
<ul class="treeList" v-for="(item, i) in treeData" :key="i">
<li class="treeItem">
<div class="treeLabel">{{ item.label }}</div>
<div class="treeBtn">
<el-icon><Edit /></el-icon>
<el-icon><Delete /></el-icon>
</div>
</li>
</ul>
</div> -->
<div class="treeContent">
<el-input v-model="filterText" style="width: 240px" placeholder="请输入关键字" />
<el-tree
style="max-width: 600px"
ref="treeRef"
:data="treeData"
node-key="id"
default-expand-all
:props="defaultProps"
:expand-on-click-node="false"
:check-on-click-node="true"
:highlight-current="true"
:filter-node-method="filterNode"
:current-node-key="defaultHighlightKey"
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<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" @click.stop="append('edit', data)"><Edit /></el-icon>
<el-icon size="16" @click.stop="pushPerson('push', data)" style="margin-left: 8px"><UserFilled /></el-icon>
<el-icon size="16" @click.stop="remove(node, data)" style="margin-left: 8px"><Delete /></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="remove(data.id, '删除该分组')" style="margin-left: 8px"><Delete /></el-icon>
</span>
</span>
</template>
@@ -46,7 +41,7 @@
</div>
</div>
<div class="table-box">
<ProTable ref="proTable" title="视频列表" :columns="columns" :request-api="monitorLIVEApi.page">
<ProTable ref="proTable" title="视频列表" :columns="columns" :request-api="monitorLIVEApi.list">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button v-auth="monitorLiveButtonCode.add" suffix="摄像头" @click="onOpen(FormOptEnum.ADD)" />
@@ -58,21 +53,9 @@
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选数据')"
/>
<el-button plain @click="omMove(FormOptEnum.ADD)" type="success">移动至分组</el-button>
<el-button :disabled="!scope.isSelected" plain @click="omMove(FormOptEnum.ADD, scope.selectedListIds)" type="success">移动至分组</el-button>
</template>
<!-- 表格 菜单类型 按钮 -->
<!-- <template #menuType="scope">
<el-space wrap>
<el-tag v-if="scope.row.menuType === MenuTypeDictEnum.MENU" type="success">{{
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.MENU)
}}</el-tag>
<el-tag v-else-if="scope.row.menuType === MenuTypeDictEnum.LINK" type="warning">{{
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.LINK)
}}</el-tag>
<el-tag v-else type="info">{{ dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType) }}</el-tag>
<el-tag v-if="scope.row.isHome === true" type="danger">首页</el-tag>
</el-space>
</template> -->
<!-- 操作 -->
<template #operation="scope">
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)">编辑</s-button>
@@ -103,7 +86,7 @@
<!-- 移动至分组 -->
<moveForm ref="moveFormRef" />
<!-- 视频详情 -->
<el-dialog v-model="visible" :title="detailData.title" width="830px" :before-close="closeGroup">
<el-dialog v-model="visible" :title="detailData.title" width="830px" :before-close="handleClose">
<div>
<div class="dialogHeader">
<div></div>
@@ -128,187 +111,195 @@
</div>
</div>
</template>
<script setup lang="tsx" name="sysSpa">
import VideoPlay from "@/components/VideoPlay/videoplay.vue";
import { ElMessage,ElMessageBox } from "element-plus";
import type Node from 'element-plus/es/components/tree/src/model/node'
import { monitorLIVEApi, monitorLiveButtonCode } from "@/api";
import { ZJRQ } 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 './aliyun-rts-sdk.js'
import './ali.js'
const groupVisible = ref(false); //是否显示表单
const groupTitle = ref('新增分组');
const groupForm = reactive({
name: ''
});
const groupRules = ref({
name: [{ required: true, message: '请输入分组名称', trigger: 'blur' }]
<script setup lang="tsx" name="sysSpa">
import VideoPlay from "@/components/VideoPlay/videoplay.vue";
import { ElMessage, ElMessageBox, ElTree } from "element-plus";
import type Node from "element-plus/es/components/tree/src/model/node";
import { monitorLIVEApi, monitorLiveButtonCode } from "@/api";
import { ZJRQ } 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 './aliyun-rts-sdk.js'
import "./ali.js";
// 分组字段配置
const defaultProps = {
children: "children",
label: "name"
};
// 默认高亮
const defaultHighlightKey = ref("");

let treeDada1 = ref([
{
id: "",
name: "全部"
}
]);
const treeData = ref<any>([]);
var treeRef = ref<InstanceType<typeof ElTree>>();
// 获取摄像头分组
function getGroupList() {
monitorLIVEApi.groupList({}).then((res:any) => {
if (res.code == 200) {
treeData.value = [...treeDada1.value, ...res.data];
}
});
const groupFormRef = ref(null);
const closeGroup = () => {
groupVisible.value = false;
groupFormRef.value.resetFields();
groupForm.name = ''
}

const groupVisible = ref(false); //是否显示表单
const groupTitle = ref("新增分组");
const groupForm = reactive({
name: "",
id: ""
});
const groupRules = ref({
name: [{ required: true, message: "请输入分组名称", trigger: "blur" }]
});
const groupFormRef = ref<any>(null);
const closeGroup = () => {
groupVisible.value = false;
groupFormRef.value.resetFields();
groupForm.name = "";
};
// 新增编辑分组
const append = (type: string, data: Tree) => {
groupVisible.value = true;
if (type == "edit") {
groupForm.name = data.name;
groupForm.id = data.id;
}
const onSubmit = () => {
groupFormRef.value.validate((valid:any) => {
if (valid) {
closeGroup()
ElMessage.success('提交成功');
};
// 删除分组
async function remove(id: string[], msg: string) {
await useHandleData(monitorLIVEApi.deleteGroup, { id }, msg);
getGroupList();
}
// 提交分组
const onSubmit = () => {
groupFormRef.value.validate((valid: any) => {
if (valid) {
let params: any = reactive({
id: "",
name: groupForm.name
});
if (!groupForm.id) {
monitorLIVEApi.addGroup(params).then(res => {
if (res.code == 200) {
getGroupList();
closeGroup();
}
});
} else {
return false;
params.id = groupForm.id;
monitorLIVEApi.updateGroup(params).then(res => {
if (res.code == 200) {
getGroupList();
closeGroup();
}
});
}
});
};
// 摄像头分组
closeGroup();
} else {
return false;
}
});
};
// 摄像头分组过滤
interface Tree {
id: any
label: string
children?: Tree[]
[key: string]: any;
id: any;
label: string;
children?: Tree[];
}
let id = 1000
const filterText = ref("");

watch(filterText, val => {
treeRef.value!.filter(val);
});

const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.name.includes(value);
};

const handleNodeClick = (data: Tree) => {
console.log(data)
}
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 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
const dictStore = useDictStore();

const treeData = ref<Tree[]>([
// 表格配置项
const columns: ColumnProps<ZJRQ.WarnInfo>[] = [
{ type: "selection", fixed: "left", width: 80 },
{
id: '',
label: '全部',
prop: "sensorId",
label: "摄像头ID"
},
{
id: '-1',
label: '无分组',
prop: "sensorName",
label: "摄像头名称"
},
{
id: 1,
label: '走廊',
prop: "fieldName",
label: "所属学校"
},
{
id: 2,
label: '大厅',
prop: "directUrlIp",
label: "设备ip"
},
{
id: 3,
label: '厨房',
prop: "resWidth",
label: "分辨率",
render: row => {
return row.row.resWidth + "*" + row.row.resHeight;
}
},
])
// 新增分组
const append = (type:string,data: Tree) => {
console.log(type,data)
groupVisible.value = true;
if(type == 'edit') {
groupForm.name = treeData.value.find(item => item.id ==data.id).label;
}
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
// 移动分组禁用
const moveFormRef = ref<InstanceType<typeof Form> | null>(null);
function omMove(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
console.log(record,"record")

// const newChild = { id: id++, label: 'testtest', children: [] }
// if (!data.children) {
// data.children = []
// }
// data.children.push(newChild)
// treeData.value = [...treeData.value]
moveFormRef.value?.omMove({ opt: opt, records: record, successful: RefreshTable });
}
// 删除分组
const remove = (node: Node, data: Tree) => {
const parent = node.parent
const children: Tree[] = parent.data.children || parent.data
const index = children.findIndex((d) => d.id === data.id)
children.splice(index, 1)
treeData.value = [...treeData.value]
/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
return;
// 二次确认 => 请求api => 刷新表格
await useHandleData(monitorLIVEApi.delete, { ids }, msg);
RefreshTable();
}
// 设置分组推送人
// async function pushPerson(type:string,data: Tree) {
// }
const visible = ref(false); //是否显示表单
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
const dictStore = useDictStore();
// 表格配置项
const columns: ColumnProps<ZJRQ.WarnInfo>[] = [
{ type: "selection", fixed: "left", width: 80 },
// { prop: "searchKey", label: "关键字", search: { el: "input" }, isShow: false },
// {
// prop: "poiId",
// label: "所属学校",
// render: () => {
// return "演示学校";
// }
// },
{
prop: "sensorId",
label: "摄像头ID",
// render: () => {
// return "楼道";
// }
},
{
prop: "sensorName",
label: "摄像头名称",
// render: () => {
// return "楼道";
// }
},
{
prop: "fieldName",
label: "所属学校",
},
{
prop: "directUrlIp",
label: "设备ip",
},
{
prop: "resWidth",
label: "分辨率",
render: (row) => {
return row.row.resWidth + '*' + row.row.resHeight;
}
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];

/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
return
// 二次确认 => 请求api => 刷新表格
await useHandleData(monitorLIVEApi.delete, { ids }, msg);
RefreshTable();
}



const moveFormRef = ref<InstanceType<typeof Form> | null>(null);
function omMove(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
moveFormRef.value?.omMove({ opt: opt, record: record, successful: RefreshTable });
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh();
}
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh();
}
// 表单引用
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
/**
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
@@ -318,9 +309,9 @@ function onOpen(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
}

// 人员选择
// 表单引用
const userFormRef = ref<InstanceType<typeof Form> | null>(null);
/**
// 表单引用
const userFormRef = ref<InstanceType<typeof Form> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
@@ -328,183 +319,179 @@ function onOpen(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
function pushPerson(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
userFormRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}
// 详情数据
// let detailData: globalThis.Ref<{}>
let detailData = reactive({
title: '',
videoUrl: '',
sensorId: '',
streamId: '',
videoToken:'',
videoType: ''
});
function onDetail(row: any) {
visible.value = true;
detailData.sensorId = row.sensorId;
detailData.title = row.sensorName + '('+ row.sensorId + ')';
getUrl()
}
// 刷新
// var showVideo = false
const showVideo = ref(false)
function refreshUrl() {
stopUrl();
showVideo.value = false;
setTimeout(()=> {
showVideo.value = true;
getUrl()
},1000)
}
let num = 1;
function getUrl() {
detailData.videoType = 'm3u8'
setTimeout(async ()=> {
await monitorLIVEApi.detail({ sensorId: detailData.sensorId}).then(res => {
let { code, data } = res;
if (code == 200) {
// window.location.origin+
detailData.videoUrl = '';
detailData.videoUrl= data.rtsPullStreamUrls[0].url;
// detailData.videoUrl = data.pullStreamUrls[2].url;
detailData.streamId = data.streamId;
detailData.videoToken = data.videoToken;
// getvideo1()
getvideo2()
}
});
})
}
function getvideo1() {
let aliRts = new AliRTS()
// let url = detailData.videoUrl.replace('http://rts-pull-live.deepeleph.com', '/Files')
// console.log(url,888)
let pullStreamUrl = detailData.videoUrl;
const mediaEle = document.querySelector('video');
aliRts.on("onError", (err) => {
console.log(`errorCode: ${err.errorCode}`);
console.log(`message: ${err.message}`);
})
// 详情数据
// let detailData: globalThis.Ref<{}>
let detailData = reactive({
title: "",
videoUrl: "",
sensorId: "",
streamId: "",
videoToken: "",
videoType: ""
});
function onDetail(row: any) {
visible.value = true;
detailData.sensorId = row.sensorId;
detailData.title = row.sensorName + "(" + row.sensorId + ")";
getUrl();
}
// 刷新
const showVideo = ref(false);
function refreshUrl() {
stopUrl();
showVideo.value = false;
setTimeout(() => {
showVideo.value = true;
getUrl();
}, 1000);
}
let num = 1;
function getUrl() {
detailData.videoType = "m3u8";
setTimeout(async () => {
await monitorLIVEApi.detail({ sensorId: detailData.sensorId }).then((res:any) => {
let { code, data } = res;

const PLAY_EVENT = {
CANPLAY: "canplay",
WAITING: "waiting",
PLAYING: "playing"
}
if (code == '200') {
// window.location.origin+
detailData.videoUrl = "";
detailData.videoUrl = data.rtsPullStreamUrls[0].url;
// detailData.videoUrl = data.pullStreamUrls[2].url;
detailData.streamId = data.streamId;
detailData.videoToken = data.videoToken;
// getvideo1()
getvideo2();
}
});
});
}
function getvideo1() {
let aliRts = new AliRTS();
let pullStreamUrl = detailData.videoUrl;
const mediaEle = document.querySelector("video");
aliRts.on("onError", err => {
console.log(`errorCode: ${err.errorCode}`);
console.log(`message: ${err.message}`);
});

aliRts.on('onPlayEvent', (play) => {
console.log(">>play.event:" + play.event);
if (play.event === PLAY_EVENT.CANPLAY) {
// 拉流可以播放
console.log('可以播放')
} else if (play.event === PLAY_EVENT.WAITING) {
// 拉流卡顿等待缓冲中 (仅Chrome)
console.log('拉流卡顿等待缓冲中')
const PLAY_EVENT = {
CANPLAY: "canplay",
WAITING: "waiting",
PLAYING: "playing"
};

} else if (play.event === PLAY_EVENT.PLAYING) {
// 拉流卡顿结束恢复播放 (仅Chrome)
console.log('拉流卡顿结束恢复播放')
}
});
aliRts.startLiveStream(pullStreamUrl, mediaEle);
// aliRts.startLiveStream(pullStreamUrl, mediaEle);
}
let player = ref(null);
function getvideo2() {
var options = {
"id": "player-con",
"source": detailData.videoUrl + '&subaudio=no&jitterbuffer=6000',
// "rtsFallbackSource": "降级地址,如HLS",
"width": "100%",
"height": "600px",
"autoplay": true,
"isLive": true,
"playsinline": true,
"skipRtsSupportCheck": false, // 对于不在 https://help.aliyun.com/document_detail/397569.html 中的浏览器,可以传 true 跳过检查,强制使用 RTS(有风险,需要自测保证)
aliRts.on("onPlayEvent", play => {
console.log(">>play.event:" + play.event);
if (play.event === PLAY_EVENT.CANPLAY) {
// 拉流可以播放
console.log("可以播放");
} else if (play.event === PLAY_EVENT.WAITING) {
// 拉流卡顿等待缓冲中 (仅Chrome)
console.log("拉流卡顿等待缓冲中");
} else if (play.event === PLAY_EVENT.PLAYING) {
// 拉流卡顿结束恢复播放 (仅Chrome)
console.log("拉流卡顿结束恢复播放");
}
});
aliRts.startLiveStream(pullStreamUrl, mediaEle);
// aliRts.startLiveStream(pullStreamUrl, mediaEle);
}
let player = ref<any>(null);
function getvideo2() {
var options = {
id: "player-con",
source: detailData.videoUrl + "&subaudio=no&jitterbuffer=6000",
// "rtsFallbackSource": "降级地址,如HLS",
width: "100%",
height: "600px",
autoplay: true,
isLive: true,
playsinline: true,
skipRtsSupportCheck: false, // 对于不在 https://help.aliyun.com/document_detail/397569.html 中的浏览器,可以传 true 跳过检查,强制使用 RTS(有风险,需要自测保证)

/**
* RTS 拉流超时会默认重试
* 以下两个参数用来控制降级之前的重试策略,比如 3000 毫秒超时,重试一次,如果再拉不到流就降级,那么总共等待 6000 毫秒降级
**/
// RTS 多久拉不到流会重试,默认 3000 ms
rtsLoadDataTimeout: 1500,
/**
* RTS 拉流超时会默认重试
* 以下两个参数用来控制降级之前的重试策略,比如 3000 毫秒超时,重试一次,如果再拉不到流就降级,那么总共等待 6000 毫秒降级
**/
// RTS 多久拉不到流会重试,默认 3000 ms
rtsLoadDataTimeout: 1500,

// RTS 拉不到流重试的次数,默认 5,此参数建议设为 1,即重试 1 次后降级,可以减少降级等待时间
liveRetry: 1,
};
// RTS 拉不到流重试的次数,默认 5,此参数建议设为 1,即重试 1 次后降级,可以减少降级等待时间
liveRetry: 1
};

player.value = new Aliplayer(options, function () {/* player ready */ })
player.value = new Aliplayer(options, function () {
/* player ready */
});

// 降级时会触发此事件
player.value.on('rtsFallback', function (event) {
console.log('[EVENT]rtsFallback', event.paramData);
// event.paramData.reason 降级的原因
// event.paramData.fallbackUrl 降级到的地址
})
// 降级时会触发此事件
player.value.on("rtsFallback", function (event:any) {
console.log("[EVENT]rtsFallback", event.paramData);
// event.paramData.reason 降级的原因
// event.paramData.fallbackUrl 降级到的地址
});

player.value.on('error', function (event) {
console.log('[EVENT]error', event.paramData);
})
player.value.on("error", function (event:any) {
console.log("[EVENT]error", event.paramData);
});

// 当RTS拉流成功时触发,通过订阅该事件,可以获取到RTS TraceId
player.value.on('rtsTraceId', function (data) {
console.log('[EVENT]rtsTraceId', data.paramData);
// event.paramData.traceId 拉流的TraceId
// event.paramData.source 当前RTS流的播放地址
})
// 当RTS拉流成功时触发,通过订阅该事件,可以获取到RTS TraceId
player.value.on("rtsTraceId", function (data:any) {
console.log("[EVENT]rtsTraceId", data.paramData);
// event.paramData.traceId 拉流的TraceId
// event.paramData.source 当前RTS流的播放地址
});
}
const handleClose = () => {
visible.value = false;
detailData.videoUrl = "";
detailData.videoType = "";
stopUrl();
if (player.value) {
player.value.dispose();
}
const handleClose = () => {
visible.value = false;
detailData.videoUrl = ''
detailData.videoType = ''
stopUrl();
if(player.value) {
player.value.dispose();
}
};
function stopUrl() {
detailData.videoUrl = ''
if(player.value) {
player.value.dispose();
}
let params = {
sensorId: detailData.sensorId,
streamId: detailData.streamId,
videoToken: detailData.videoToken
}
setTimeout(async ()=> {
await monitorLIVEApi.stopUrl(params).then(res => {
let { code, data, msg } = res;
if (code == 200) {
// ElMessage.success(msg);
}
});
})
};
function stopUrl() {
detailData.videoUrl = "";
if (player.value) {
player.value.dispose();
}
</script>
<style lang="scss" scoped>
@import url("https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/skins/default/aliplayer-min.css");
let params = {
sensorId: detailData.sensorId,
streamId: detailData.streamId,
videoToken: detailData.videoToken
};
setTimeout(async () => {
await monitorLIVEApi.stopUrl(params).then((res:any) => {
let { code, data, msg } = res;

if (code == 200) {
// ElMessage.success(msg);
}
});
});
}

onMounted(() => {
getGroupList();
});
</script>

<style lang="scss" scoped>
@import "https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/skins/default/aliplayer-min.css";
@import "./index.scss";
.dialogHeader {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
margin: 20px;
color: #409efc;
font-size: 18px;
color: #409efc;
cursor: pointer;
.dialogBtn {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
}
}
</style>

+ 175
- 0
SafeCampus.WEB/src/views/practicalTraining/model/index.vue View File

@@ -0,0 +1,175 @@
<!--
* @Description: 告警管理
* @Author: huguodong
* @Date: 2023-12-15 15:44:05
!-->
<template>
<div class="table-box">
<ProTable ref="proTable" title="告警列表" :columns="columns" :data="tableData">
<!-- 表格 header 按钮 -->

<!-- 表格 菜单类型 按钮 -->
<!-- 状态 -->
<template #status="scope">
<el-tag v-if="scope.row.status === 1" type="success">已部署</el-tag>
<el-tag v-else type="danger">未部署</el-tag>
</template>
<template #menuType="scope">
<el-space wrap>
<el-tag v-if="scope.row.menuType === MenuTypeDictEnum.MENU" type="success">{{
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.MENU)
}}</el-tag>
<el-tag v-else-if="scope.row.menuType === MenuTypeDictEnum.LINK" type="warning">{{
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.LINK)
}}</el-tag>
<el-tag v-else type="info">{{ dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType) }}</el-tag>
<el-tag v-if="scope.row.isHome === true" type="danger">首页</el-tag>
</el-space>
</template>
</ProTable>
</div>
</template>
<script setup lang="tsx" name="sysSpa">
import { warnZJRQApi } from "@/api";
import { ZJRQ } from "@/api/interface";
import { useHandleData } from "@/hooks/useHandleData";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { useDictStore } from "@/stores/modules";
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
import { dividerDark } from "naive-ui";
const visible = ref(false); //是否显示表单

// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
const dictStore = useDictStore();
// 表格配置项
const columns: ColumnProps<ZJRQ.WarnInfo>[] = [
{
prop: "name",
label: "模型名称",
// render: () => {
// return "演示学校";
// }
},
{
prop: "type",
label: "模型类型",
// render: () => {
// return "楼道";
// }
},
{
prop: "core",
label: "分数",
},
{
prop: "time",
label: "训练时间",
},
{
prop: "status",
label: "部署状态",
},
{
prop: "isshow",
label: "训练模型开关",
render: scope => {
return (
<el-switch
v-model={scope.row.isshow}
onChange={() => {
switchChange(scope.row)
}}
/>
);
}
},
{
prop: "ishandle",
label: "实训业务开关",
render: scope => {
return (
<el-switch
v-model={scope.row.ishandle}
onChange={() => {
switchChange(scope.row)
}}
/>
);
}
},
];
function switchChange(row: object) {
console.log(row)
RefreshTable();
}
const tableData = ref([
{
name: '无人机巡检算法',
type: "物体分类",
core: "95",
time: "2024-02-10 15:00:25",
status: 1,
isshow: true,
ishandle: true
},
{
name: '越界检测',
type: "物体分类",
core: "89",
time: "2024-03-10 16:00:25",
status: 1,
isshow: true,
ishandle: false
},
{
name: '口罩识别',
type: "物体分类",
core: "80",
time: "2024-05-26 15:10:00",
status: 1,
isshow: true,
ishandle: true
},
{
name: '监控室脱岗检测',
type: "边界检测",
core: "80",
time: "2024-08-10 16:20:25",
status: 0,
isshow: true,
ishandle: true
},

{
name: '反光衣识别',
type: "物体分类",
core: "70",
time: "2024-06-02 18:00:25",
status: 1,
isshow: true,
ishandle: true
},
]);
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh();
}

</script>
<style lang="scss" scoped>
.detailpic {
width: 800px;
object-fit: cover;
}
.linebox {
margin-top: 20px;
}
</style>

+ 104
- 73
SafeCampus.WEB/src/views/sysconfig/ability/index.vue View File

@@ -4,52 +4,89 @@
* @Date: 2023-12-15 15:44:05
!-->
<template>
<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="btns">
<el-switch v-model="item.state" @change="stateChange" />
<div class="abilityBox">
<TreeFilter ref="treeFilter" 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>

<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="btns">
<el-switch v-model="item.state" @change="stateChange" />
</div>
</div>
</div>
</template>
<div class="collapse-content">
<!-- <div class="contentinfo" v-for="(v, index) in item.subset" :key="v.code">
</template>
<div class="collapse-content">
<!-- <div class="contentinfo" v-for="(v, index) in item.subset" :key="v.code">
<div class="modellabel">{{ v.name }}</div>

<div class="btns">
<el-switch v-model="v.state" active-text="开启" inactive-text="关闭" />
</div>
</div> -->
<el-row :gutter="20">
<el-col :span="6" v-for="(v, index) in item.subset" :key="v.code">
<div class="contentinfo">
<div class="modellabel">{{ v.name }}</div>
<el-row :gutter="20">
<el-col :span="6" v-for="(v, index) 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" />
<div class="btns">
<el-switch v-model="v.state" @change="stateChange" />
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script setup lang="tsx" name="sysSpa">
import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { ElMessage } from "element-plus";
import { abilityApi } from "@/api";
import { abilityApi, userManageClassManageApi, monitorLIVEApi } from "@/api";

const value = ref(true);
onMounted(() => {
// 在这里执行其他需要在组件挂载后运行的代码
getwarnGroup();
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)
}
const videoList = ref<any>();
function getVideoList() {
setTimeout(async () => {
await monitorLIVEApi.list({ pageNum: 1, pageSize: 1000 }).then(res => {
videoList.value = res.data.list;
});
});
}
function changeVideo(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)
}
// 获取配置树

// 获取配置树
let warnGroupList = ref([]);
function getwarnGroup() {
setTimeout(async () => {
@@ -62,7 +99,7 @@ function getwarnGroup() {
});
});
}
// 开关
function stateChange() {
let params: string = JSON.stringify(warnGroupList.value);
setTimeout(async () => {
@@ -77,60 +114,54 @@ function stateChange() {
});
}
</script>
<style lang="scss" scoped>
.content-main {
::v-deep(.el-collapse-item__header) {
flex: 1 0 auto;
order: -1;
height: 50px;
line-height: 50px;
}

.collapse-title {
flex: 1 0 90%;
order: 1;
display: flex;
align-items: center;
justify-content: space-between;
// padding: 30px 0;

.titlemodel {
// display: inline-block;
font-size: 18px;
margin-right: 40px;
}
<style lang="scss" scoped>
.abilityBox {
display: flex;
width: 100%;
height: 100%;

.btns {
margin-left: 40px;
// 配置列表
.content-main {
width: calc(100% - 630px);
::v-deep(.el-collapse-item__header) {
flex: 1 0 auto;
order: -1;
height: 50px;
line-height: 50px;
}
}
.collapse-content {
padding: 20px;
.contentinfo {
margin-top: 20px;
// display: flex;
// justify-content: space-between;
// align-items: center;
padding: 20px 20px;
box-sizing: border-box;
// border-bottom: 1px solid #dcdfe6;
// background: red;
border-radius: 10px;
// box-shadow: 3px 3px 3px #00000014, 3px -3px 3px #00000014, -3px 3px 3px #00000014, -3px -3px 3px #00000014;
// box-shadow: 0 1px 1px hsl(0deg 0% 0% / 0.075), 0 2px 2px hsl(0deg 0% 0% / 0.075), 0 4px 4px hsl(0deg 0% 0% / 0.075),
// 0 8px 8px hsl(0deg 0% 0% / 0.075), 0 16px 16px hsl(0deg 0% 0% / 0.075);
box-shadow: 0px 2px 1px rgba(0, 0, 0, 0.1), 0 0px 8px rgba(0, 0, 0, 0.1), 0 -1px 1px #fff, 0px 0 0px #fff, 0 0 16px #fff;
.modellabel {
font-size: 16px;
.collapse-title {
display: flex;
flex: 1 0 90%;
align-items: center;
justify-content: space-between;
order: 1;
.titlemodel {
margin-right: 40px;
font-size: 18px;
}
.btns {
margin-left: 40px;
}
}
.collapse-content {
padding: 20px;
.contentinfo {
box-sizing: border-box;
padding: 20px;
margin-top: 20px;
margin-right: 0;
text-align: right;
border-radius: 10px;
box-shadow: 0 2px 1px rgb(0 0 0 / 10%), 0 0 8px rgb(0 0 0 / 10%), 0 -1px 1px #ffffff, 0 0 0 #ffffff, 0 0 16px #ffffff;
.modellabel {
font-size: 16px;
}
.btns {
margin-top: 20px;
margin-right: 0;
text-align: right;
}
}
}
}
}
</style>

+ 128
- 0
SafeCampus.WEB/src/views/sysconfig/push/index.vue View File

@@ -0,0 +1,128 @@
<!--
* @Description: 推送配置
* @Author: 王文沛
* @Date: 2023-12-15 15:44:05
!-->
<template>
<div class="card content-main">
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
class="demo-ruleForm"
:size="formSize"
status-icon
>
<s-form-item label="推送设置:" prop="resource">
<s-radio-group v-model="ruleForm.resource" :options="pushOptions" />
</s-form-item>

<div class="ruleTitle">推送规则</div>
<div class="ruleContent">
<span>当日预警每达到</span>
<el-input-number v-model="ruleForm.num" class="mx-4" :min="1" controls-position="right" @change="handleChange" />
<span>条时进行推送</span>
<div class="tips">当日预警数据每达到设置的条数后,会进行消息推送。</div>
</div>
<el-form-item>
<el-button type="primary" @click="submitForm(ruleFormRef)"> 保 存 </el-button>
<!-- <el-button @click="resetForm(ruleFormRef)">取消</el-button> -->
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="tsx" name="sysconfigPush">
import { reactive, ref } from 'vue'
import type { ComponentSize, FormInstance, FormRules } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
interface RuleForm {

resource: string,
num: number
}

const formSize = ref<ComponentSize>('default')
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
resource: '0',
num: 1
})
const pushOptions = [{
label: '开启',
value: '1',
},{
label: '关闭',
value: '0',
}]

const rules = reactive<FormRules<RuleForm>>({

resource: [
{
required: true,
message: '请选择推送设置',
trigger: 'change',
},
],
})
const handleChange = (value: number) => {
console.log(value)
}
function isPositiveInteger(value:any) {
return /^[1-9]\d*$/.test(value);
}
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
if(!isPositiveInteger(ruleForm.num)) {
ElMessage({
message: '推送规则条数必须是大于零的整数',
type: 'warning',
})
return
}
ElMessage({
message: '操作成功',
type: 'success',
})
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}

const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
</script>
<style lang="scss" scoped>
.content-main {
height: 100%;

.ruleTitle {
font-size: 14px;
font-weight: 600;
margin: 30px 0 20px 0;
}
.ruleContent {
font-size: 14px;
color: #666666;
margin: 20px 0;
.tips {
font-size: 14px;
color: red;
margin-top: 10px;
}
}
}
</style>

+ 156
- 0
SafeCampus.WEB/src/views/userManage/clothing/components/form/index.vue View File

@@ -0,0 +1,156 @@
<!--
* @Description: 表单
* @Author: syy
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`${sysUserProps.opt}服装`" form-size="500px" @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="24">
<s-form-item label="所属服装库" prop="clothSetId">
<s-select
v-model="sysUserProps.record.clothSetId"
:options="sysUserProps.treeAllData"
label="clothSetName"
value="clothSetId"
></s-select>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<s-form-item label="上传服装" prop="faces">
<el-upload
class="avatar-uploader"
action="/api/business/clothApi/uploadFile"
:show-file-list="false"
:on-success="handleAvatarSuccess"
accept=".jpg, .jpeg, .png"
:headers="{
Authorization: `${TokenEnum.TOKEN_PREFIX} ${accessToken}`
}"
>
<img v-if="sysUserProps.record.clothUrl" :src="sysUserProps.record.clothUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</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="SysUserClothForm">
import { SysUserCloth, userManageClothApi } from "@/api";
import { FormOptEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useUserStore } from "@/stores/modules";
import type { UploadProps } from "element-plus";
import { TokenEnum } from "@/enums";
const visible = ref(false); //是否显示表单
const activeName = ref("basic");
const userStore = useUserStore();
const { accessToken } = userStore;
// 表单参数
const sysUserProps = reactive<FormProps.Base<SysUserCloth.list>>({
opt: FormOptEnum.ADD,
record: {},
treeAllData: [],
disabled: false
});

// 表单验证规则
const rules = reactive({
clothSetId: [required("请选择所属服装库")],
clothUrl: [required("请上传服装")]
});

const handleAvatarSuccess: UploadProps["onSuccess"] = (response: any) => {
if (response.code === 200) {
sysUserProps.record.clothUrl = response.data;
}
};
/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysUserCloth.list>) {
Object.assign(sysUserProps, props); //合并参数
visible.value = true; //显示表单
sysUserProps.record = props.record;
}

// 提交数据(新增/编辑)
const sysUserFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
sysUserFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
await userManageClothApi
.add(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
});
}

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

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

<style lang="scss" scoped>
.avatar-uploader .avatar {
display: block;
width: 178px;
height: 178px;
}
</style>
<style>
.avatar-uploader .el-upload {
position: relative;
overflow: hidden;
cursor: pointer;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
width: 178px;
height: 178px;
font-size: 28px;
color: #8c939d;
text-align: center;
}
</style>

+ 105
- 0
SafeCampus.WEB/src/views/userManage/clothing/components/form1/index.vue View File

@@ -0,0 +1,105 @@
<!--
* @Description: 表单
* @Author: syy
* @Date: 2023-12-18 15:45:59
-->
<template>
<div>
<form-container v-model="visibleClass" :title="`${sysUserProps.opt}服装库`" form-size="400px" @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="22">
<s-form-item label="服装库名称" prop="clothSetName">
<s-input v-model="sysUserProps.record.clothSetName"></s-input>
</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="SysUserClothDk">
import { ref } from "vue";
import { userManageClothApi, SysUserCloth } from "@/api";
import { FormOptEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";

const visibleClass = ref(false); //是否显示表单
// 表单参数
const sysUserProps = reactive<FormProps.Base<SysUserCloth.Page>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
clothSetName: [required("请输入服装库名称")]
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysUserCloth.Page>) {
Object.assign(sysUserProps, props); //合并参数
visibleClass.value = true; //显示表单
sysUserProps.record = props.record;
}

// 提交数据(新增/编辑)
const sysUserFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
sysUserFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
if (sysUserProps.record.clothSetId) {
await userManageClothApi
.update(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
await userManageClothApi
.addClothDataBaseA(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}

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

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

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

+ 195
- 0
SafeCampus.WEB/src/views/userManage/clothing/index.vue View File

@@ -0,0 +1,195 @@
<!--
* @Description: 服装库管理
* @Author: syy
* @Date: 2024-7-18
-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="clothSetName"
id="clothSetId"
width="300px"
:show-all="true"
:request-api="userManageClothApi.getList"
@change="changeTreeFilter"
>
<template v-slot:header>
<s-button suffix="服装库" @click="addClass(FormOptEnum.ADD)" style="margin-bottom: 15px" />
</template>
<template v-slot:label="{ row }">
<span class="custom-tree-node">
<span>{{ row.node.label }}</span>
<span v-if="row.node.label != '全部'">
<a @click.stop="addClass(FormOptEnum.EDIT, row.node.data)">
<el-icon><Edit /></el-icon>
</a>
<a style="margin-left: 8px" @click.stop="addDelete(row.node.data.clothSetId, '删除服装库')">
<el-icon><Delete /></el-icon>
</a>
</span>
</span>
</template>
</TreeFilter>
<div class="table-box">
<ProTable ref="proTable" title="服装库管理" :columns="columns" rowKey="clothId" :data="tableData.clothes">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button suffix="服装" @click="onOpen(FormOptEnum.ADD, { clothSetId: clothSetId }, 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.DELETE" @click="onDelete([scope.row.clothId], `删除服装`)" />
</el-space>
</template>
</ProTable>
</div>
<!-- 人员新增/编辑表单 -->
<Form ref="formRef"></Form>
<!-- 班级新增/编辑表单 -->
<Form1 ref="formRefC" />
<!-- 预览头像 -->
<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="" />
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="tsx" name="SysUserClothing">
import { userManageClothApi,userClothButtonCode,SysUserCloth } from "@/api";
import { useHandleData } from "@/hooks/useHandleData";
import { FormOptEnum } from "@/enums";
import Form from "./components/form/index.vue";
import Form1 from "./components/form1/index.vue";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { useUserStore } from "@/stores/modules";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const faceUrl = ref('');
const visible = ref(false); //是否显示人员表单
const proTable = ref<ProTableInstance>();
const treeFilter = ref<any>({});
const userStore = useUserStore();
const { accessToken } = userStore;
const tableData = ref<any>([])
// 表格配置项
const columns: ColumnProps<SysUserCloth.list>[] = [
{ type: "selection", fixed: "left", width: 50 },
{
prop: "clothUrl",
label: "服装图",
render: scope => {
return (
<img src={scope.row.clothUrl} onClick={() => viewHeadImage(scope)} style='width:50px;height:50px' alt=''/>
);
}
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.clothUrl;
visible.value = true
};
const handleClose = () => {
visible.value = false;
};
// 人员表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
// 班级表单引用
const formRefC = ref<InstanceType<typeof Form1> | null>(null);
/**
* 打开服装表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {},treeAllData:any) {
formRef.value?.onOpen({ opt: opt, record: record,treeAllData:treeAllData, successful: RefreshTable });
}

/**
* 打开服装库表单
* @param opt 操作类型
* @param record 记录
*/

function addClass(opt: FormOptEnum, record: {} | SysUserCloth.list = {}) {
formRefC.value?.onOpen({ opt: opt, record: JSON.parse(JSON.stringify(record)), successful: RefreshTree });
}
/**
* 服装库删除
* @param ids id数组
*/
async function addDelete(clothSetId: string[],msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(userManageClothApi.deleteClothDataBaseD, { clothSetId }, msg);
RefreshTree(); //刷新表格
}

/**
* 服装删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(userManageClothApi.delete, {clothId: ids.join(","), clothSetId:tableData.value.clothSetId}, msg);
RefreshTable(); //刷新表格
}

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



/** 服装库切换切换 */
const clothSetId = ref<number | string>()
function changeTreeFilter(val: number | string) {
clothSetId.value = val
proTable.value!.pageable.pageNum = 1;
getList(val)
}
// 获取列表
const getList = (clothSetId:any)=>{
if(!clothSetId) return false
userManageClothApi.page({clothSetId:clothSetId}).then((resp:any)=>{
if(resp.code == 200){
tableData.value = resp.data
}
})
}
</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>

+ 181
- 0
SafeCampus.WEB/src/views/userManage/keyPersonnel/components/form/form_basic.vue View File

@@ -0,0 +1,181 @@
<!--
* @Description: 人员表单
* @Author: syy
* @Date: 2023-12-15 15:45:50
-->
<template>
<div class="userManageForm">
<div>
<el-row :gutter="16">
<el-col :span="24">
<s-form-item label="人员姓名" prop="name">
<s-input v-model="userInfo.name"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<s-form-item label="上传人脸" prop="faces">
<el-upload
v-model:file-list="fileList"
action="/api/business/personApi/uploadFile"
list-type="picture-card"
:on-success="handleAvatarSuccess"
:on-error="handleAvatarError"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
accept=".jpg, .jpeg, .png"
:headers="{
Authorization: `${TokenEnum.TOKEN_PREFIX} ${accessToken}`
}"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible" title="查看图片">
<img w-full :src="dialogImageUrl" alt="Preview Image" style="width: 100%" />
</el-dialog>
</s-form-item>
</el-col>
</el-row>

<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="性别" prop="gender">
<s-radio-group v-model="userInfo.gender" :options="genderOptions" />
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="年龄" prop="age">
<s-input v-model="userInfo.age"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="手机号" prop="phone">
<s-input v-model="userInfo.phone"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="扩展字段" prop="extData">
<s-input v-model="userInfo.extData"></s-input>
</s-form-item>
</el-col>
</el-row>
</div>
</div>
</template>

<script setup lang="ts">
import { SysUserPersonnel, userManageKeyPersonApi } from "@/api";
import { Plus } from "@element-plus/icons-vue";
import { useUserStore } from "@/stores/modules";
import type { UploadProps } from "element-plus";
import { ElMessage } from "element-plus";
import { TokenEnum } from "@/enums";
// props
interface FormProps {
modelValue: Partial<SysUserPersonnel.SysUserPerInfo>;
}
const emit = defineEmits(["update:modelValue"]); //定义emit
const props = defineProps<FormProps>(); //定义props
// 人员信息
const userInfo = computed({
get: () => props.modelValue,
set: val => emit("update:modelValue", val)
});
/* */
const userStore = useUserStore();
const { accessToken } = userStore;
const fileList = ref<any>([]);
const faces = ref<SysUserPersonnel.SysUserAvatar[]>([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const handleRemove: UploadProps["onRemove"] = (uploadFile: any) => {
const index = faces.value.findIndex(item => item.uid === uploadFile.uid);
if (index > -1) {
faces.value.splice(index, 1);
}
userInfo.value.faces = faces.value;
if (uploadFile.personId) {
userManageKeyPersonApi.deleteFace({ personId: uploadFile.personId, faceIds: [uploadFile.uid] }).then(res => {});
}
};

const handlePictureCardPreview: UploadProps["onPreview"] = uploadFile => {
dialogImageUrl.value = uploadFile.url!;
dialogVisible.value = true;
};
const handleAvatarSuccess: UploadProps["onSuccess"] = (response, uploadFile) => {
if (response.code === 200) {
faces.value.push({ faceUrl: response.data, uid: uploadFile.uid });
userInfo.value.faces = faces.value;
ElMessage.success(response.msg);
} else {
ElMessage.error(response.msg);
fileList.value = fileList.value.splice(0, fileList.value.length - 1);
}
};
const handleAvatarError: UploadProps["onError"] = (error, uploadFile, uploadFiles) => {
console.log(error, uploadFile, uploadFiles, "err");
};
// 通用状态选项
const genderOptions = ref([
{
label: "未知",
value: "GENDER_UNKNOWN"
},
{
label: "男",
value: "GENDER_MALE"
},
{
label: "女",
value: "GENDER_FEMALE"
}
]);

onMounted(() => {
// 初始化
userInfo.value.gender = userInfo.value.gender ? userInfo.value.gender : genderOptions.value[0].value;
if (userInfo.value.personId) {
if (userInfo.value.faces?.length > 0) {
fileList.value = [
...JSON.parse(JSON.stringify(userInfo.value.faces)).map((item: any) => {
return {
url: item.faceUrl,
uid: item.faceId,
personId: userInfo.value.personId
};
})
];
faces.value = [
...JSON.parse(JSON.stringify(userInfo.value.faces)).map((item: any) => {
return {
faceUrl: item.faceUrl,
uid: item.faceId,
personId: userInfo.value.personId
};
})
];
}
}
});
</script>

<style lang="scss" scoped>
:deep(.el-input__wrapper) {
width: 100% !important;
}
:deep(.el-date-editor.el-input) {
width: 92% !important;
}
:deep(.el-upload-list--picture-card .el-upload-list__item) {
width: 100px !important;
height: 100px !important;
}
:deep(.el-upload--picture-card) {
width: 100px !important;
height: 100px !important;
}
</style>

+ 117
- 0
SafeCampus.WEB/src/views/userManage/keyPersonnel/components/form/index.vue View File

@@ -0,0 +1,117 @@
<!--
* @Description: 表单
* @Author: syy
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`${sysUserProps.opt}人员`" form-size="800px" @close="onClose">
<el-form
ref="sysUserFormRef"
:rules="rules"
:disabled="sysUserProps.disabled"
:model="sysUserProps.record"
:hide-required-asterisk="sysUserProps.disabled"
label-width="auto"
label-suffix=" :"
>
<el-tabs v-model="activeName">
<Basic v-model="sysUserProps.record"></Basic>
</el-tabs>
</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="SysUserKeyPersonnelForm">
import { SysUserPersonnel, userManageKeyPersonApi } from "@/api";
import { FormOptEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import Basic from "./form_basic.vue";

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

// 表单验证规则
const rules = reactive({
name: [required("请输入姓名")],
gender: [required("请选择性别")],
faces: [required("请上传人脸图片")],
phone: [required("请输入手机号")]
});

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

// 提交数据(新增/编辑)
const sysUserFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
sysUserFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
if (sysUserProps.record.faces.length === 0) {
return ElMessage.error("请上传人脸图片");
}
if (sysUserProps.record.personId) {
await userManageKeyPersonApi
.update(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
await userManageKeyPersonApi
.add(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}

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

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

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

+ 220
- 0
SafeCampus.WEB/src/views/userManage/keyPersonnel/index.vue View File

@@ -0,0 +1,220 @@
<!--
* @Description: 人员管理
* @Author: syy
* @Date: 2024-7-15
-->
<template>
<div class="main-box">
<div class="table-box">
<ProTable ref="proTable" title="人员管理" :columns="columns" rowKey="personId" :request-api="userManageKeyPersonApi.page">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button suffix="人员" @click="onOpen(FormOptEnum.ADD)" />
<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.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.personId], `删除人员`)" />
<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.AddFace)"
><el-upload
ref="upload"
class="upload-demo"
action="/api/business/personApi/uploadFile"
:show-file-list="false"
:on-success="handleAvatarSuccess"
accept=".jpg, .jpeg, .png"
:headers="{
Authorization: `${TokenEnum.TOKEN_PREFIX} ${accessToken}`
}"
>
<template #trigger>
{{ cmdEnum.AddFace }}
</template>
</el-upload>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-space>
</template>
</ProTable>
</div>
<!-- 人员新增/编辑表单 -->
<Form ref="formRef"></Form>
<!-- 预览头像 -->
<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%" />
</div>
</el-dialog>
</div>
</template>
<script setup lang="tsx" name="SysUserKeyPersonnel">
import { userManageKeyPersonApi,SysUserPersonnel } from "@/api";
import { useHandleData } from "@/hooks/useHandleData";
import { FormOptEnum } from "@/enums";
import Form from "./components/form/index.vue";
import { ArrowDown } from "@element-plus/icons-vue";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { ElMessage } from "element-plus";
import { useUserStore } from "@/stores/modules";
import { TokenEnum } from "@/enums";
import type { UploadProps } from "element-plus";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const faceUrl = ref('');
const visible = ref(false); //是否显示人员表单
const proTable = ref<ProTableInstance>();
const userStore = useUserStore();
const { accessToken } = userStore;
// 表格配置项
const columns: ColumnProps<SysUserPersonnel.SysUserPerInfo>[] = [
{ type: "selection", fixed: "left", width: 50 },
{
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: "age",
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
console.log(faceUrl);
};
const handleClose = () => {
visible.value = false;
};


// 人员表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);

/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | SysUserPersonnel.SysUserPerInfo = {}) {
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}

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

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


/** 更多下拉菜单命令枚举 */
enum cmdEnum {
AddFace = "添加人脸",
}
/** 下拉菜单参数接口 */
interface Command {
row: SysUserPersonnel.SysUserPerInfo;
command: cmdEnum;
}

/**配置command的参数 */
function command(row: SysUserPersonnel.SysUserPerInfo, command: cmdEnum): Command {
return {
row: row,
command: command
};
}
/**
* 列表更多下拉菜单点击事件
* @param command
*/
const personId = ref<number | string>(); //人员id
function handleCommand(command: Command) {
switch (command.command) {
case cmdEnum.AddFace:
personId.value = command.row.personId; //获取人员id
break
}
}





const handleAvatarSuccess: UploadProps["onSuccess"] = (response) => {
if (response.code === 200) {
userManageKeyPersonApi.addFace({
personId: personId.value,
faceUrl: response.data
}).then(res=>{
RefreshTable()
})
} else {
ElMessage.error(response.msg);
}
};
</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>

+ 6
- 7
SafeCampus.WEB/src/views/userManage/personnel/components/form/form_basic.vue View File

@@ -75,7 +75,7 @@
import { SysUserPersonnel, userManagePersonnelApi, userManageClassManageApi } from "@/api";
import { Plus } from "@element-plus/icons-vue";
import { useUserStore } from "@/stores/modules";
import type { UploadProps, UploadUserFile } from "element-plus";
import type { UploadProps } from "element-plus";
import { ElMessage } from "element-plus";
import { TokenEnum } from "@/enums";
// props
@@ -92,12 +92,12 @@ const userInfo = computed({
/* */
const userStore = useUserStore();
const { accessToken } = userStore;
const fileList = ref([]);
const fileList = ref<any>([]);
const faces = ref<SysUserPersonnel.SysUserAvatar[]>([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const treeData = ref<{ [key: string]: any }[]>([]);
const handleRemove: UploadProps["onRemove"] = uploadFile => {
const treeData = ref<any>([]);
const handleRemove: UploadProps["onRemove"] = (uploadFile: any) => {
const index = faces.value.findIndex(item => item.uid === uploadFile.uid);
if (index > -1) {
faces.value.splice(index, 1);
@@ -143,7 +143,6 @@ const genderOptions = ref([
const getRequestData = async () => {
const { data } = await userManageClassManageApi.page();
treeData.value = data;
console.log(treeData.value, "treeData");
};
onMounted(() => {
// 初始化
@@ -152,7 +151,7 @@ onMounted(() => {
if (userInfo.value.personId) {
if (userInfo.value.faces?.length > 0) {
fileList.value = [
...JSON.parse(JSON.stringify(userInfo.value.faces)).map(item => {
...JSON.parse(JSON.stringify(userInfo.value.faces)).map((item: any) => {
return {
url: item.faceUrl,
uid: item.faceId,
@@ -161,7 +160,7 @@ onMounted(() => {
})
];
faces.value = [
...JSON.parse(JSON.stringify(userInfo.value.faces)).map(item => {
...JSON.parse(JSON.stringify(userInfo.value.faces)).map((item: any) => {
return {
faceUrl: item.faceUrl,
uid: item.faceId,


+ 116
- 0
SafeCampus.WEB/src/views/userManage/personnel/components/formTeacher/index.vue View File

@@ -0,0 +1,116 @@
<!--
* @Description: 表单
* @Author: syy
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visibleClass" :title="`${sysUserProps.opt}`" form-size="400px" @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="22">
<s-form-item label="班主任" prop="userId">
<s-select v-model="sysUserProps.record.userId" :options="teacherData" label="name" value="userId"></s-select>
</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="SysUserPerformClass">
import { ref } from "vue";
import { SysUserPersonnel, userManageTeacherApi } from "@/api";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
const teacherData = ref<any>([]);
const visibleClass = ref(false); //是否显示表单
// 表单参数
enum FormOptEnum {
/** 新增 */
AddTeacher = "绑定班主任",
/** 编辑 */
UpdateTeacher = "修改班主任"
}
const sysUserProps = reactive<FormProps.Base<SysUserPersonnel.ClassPage>>({
opt: FormOptEnum.AddTeacher,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
userId: [required("请选择班主任名称")]
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysUserPersonnel.ClassPage>) {
getRequestData();
Object.assign(sysUserProps, props); //合并参数
visibleClass.value = true; //显示表单
sysUserProps.record = props.record;
}

const getRequestData = async () => {
const { data } = await userManageTeacherApi.page();
teacherData.value = data;
};

// 提交数据(新增/编辑)
const sysUserFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
sysUserFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
console.log(sysUserProps);
//提交表单
if (sysUserProps.opt === FormOptEnum.UpdateTeacher) {
await userManageTeacherApi
.update(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
await userManageTeacherApi
.add(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}

/** 关闭表单*/
function onClose() {
visibleClass.value = false;
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>

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

+ 82
- 16
SafeCampus.WEB/src/views/userManage/personnel/index.vue View File

@@ -10,7 +10,7 @@
label="personSetName"
id="personSetId"
width="300px"
:show-all="false"
:show-all="true"
:request-api="userManageClassManageApi.page"
@change="changeTreeFilter"
>
@@ -20,13 +20,31 @@
<template v-slot:label="{ row }">
<span class="custom-tree-node">
<span>{{ row.node.label }}</span>
<span>
<a @click="addClass(FormOptEnum.EDIT, row.node.data)">
<span v-if="row.node.label != '全部'">
<a @click.stop="addClass(FormOptEnum.EDIT, row.node.data)">
<el-icon><Edit /></el-icon>
</a>
<a style="margin-left: 8px" @click="addDelete(row.node.data.personSetId, '删除班级')">
<a style="margin-left: 8px" @click.stop="addDelete(row.node.data.personSetId, '删除班级')">
<el-icon><Delete /></el-icon>
</a>
<a style="margin-left: 8px" @click.stop>
<el-dropdown @command="handleCommandTree">
<el-link :underline="false" :icon="More"> </el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="!row.node.data.userId" :command="commander(row.node.data, cmdEnumTree.AddTeacher)">
{{ cmdEnumTree.AddTeacher }}
</el-dropdown-item>
<el-dropdown-item v-if="row.node.data.userId" :command="commander(row.node.data, cmdEnumTree.UpdateTeacher)">
{{ cmdEnumTree.UpdateTeacher }}
</el-dropdown-item>
<el-dropdown-item v-if="row.node.data.userId" :command="commander(row.node.data, cmdEnumTree.DeleteTeacher)">
{{ cmdEnumTree.DeleteTeacher }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</a>
</span>
</span>
</template>
@@ -86,6 +104,8 @@
<Form ref="formRef"></Form>
<!-- 班级新增/编辑表单 -->
<FormClass ref="formRefC" />
<!-- 班主任绑定/修改 -->
<FormTeacher ref="formRefT" />
<!-- 预览头像 -->
<el-dialog v-model="visible" title="查看头像" width="830px" :before-close="handleClose">
<div style="display: flex; align-items: center; justify-content: center">
@@ -95,12 +115,13 @@
</div>
</template>
<script setup lang="tsx" name="SysUserPersonnel">
import { userManagePersonnelApi,userPerButtonCode,SysUserPersonnel,userManageClassManageApi } from "@/api";
import { userManagePersonnelApi,userPerButtonCode,SysUserPersonnel,userManageClassManageApi,userManageTeacherApi } from "@/api";
import { useHandleData } from "@/hooks/useHandleData";
import { FormOptEnum } from "@/enums";
import Form from "./components/form/index.vue";
import FormClass from "./components/formClass/index.vue";
import { ArrowDown } from "@element-plus/icons-vue";
import FormTeacher from "./components/formTeacher/index.vue";
import { ArrowDown,More } from "@element-plus/icons-vue";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { useUserStore } from "@/stores/modules";
@@ -134,8 +155,8 @@ const columns: ColumnProps<SysUserPersonnel.SysUserPerInfo>[] = [
label: "人员ID"
},
{
prop: "age",
label: "年龄"
prop: "phone",
label: "手机号"
},
{
prop: "personSets",
@@ -158,8 +179,11 @@ const handleClose = () => {

// 人员表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
// 班级表单引用
// 班级表单引用
const formRefC = ref<InstanceType<typeof FormClass> | null>(null);
// 班级表单引用
const formRefT = ref<InstanceType<typeof FormTeacher> | null>(null);

/**
* 打开表单
* @param opt 操作类型
@@ -176,7 +200,7 @@ function onOpen(opt: FormOptEnum, record: {} | SysUserPersonnel.SysUserPerInfo =
*/

function addClass(opt: FormOptEnum, record: {} | SysUserPersonnel.ClassPage = {}) {
formRefC.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
formRefC.value?.onOpen({ opt: opt, record: JSON.parse(JSON.stringify(record)), successful: RefreshTable });
}
/**
* 班级删除
@@ -231,11 +255,11 @@ function command(row: SysUserPersonnel.SysUserPerInfo, command: cmdEnum): Comman
};
}
/**
* 更多下拉菜单点击事件
* 列表更多下拉菜单点击事件
* @param command
*/
const personId = ref<number | string>(); //人员id
function handleCommand(command: Command) {
const personId = ref<number | string>(); //人员id
function handleCommand(command: Command) {
switch (command.command) {
case cmdEnum.AddFace:
personId.value = command.row.personId; //获取人员id
@@ -245,15 +269,57 @@ function command(row: SysUserPersonnel.SysUserPerInfo, command: cmdEnum): Comman
personId:command.row.personId,
personSetId: command.row.personSets[0].personSetId
}).then(res=>{
if(res.code == 200){
ElMessage.success('底库解绑成功');
RefreshTable()
}
})
break;
}
}
const handleAvatarSuccess: UploadProps["onSuccess"] = (response, uploadFile) => {



/** 更多下拉菜单命令枚举 */
enum cmdEnumTree {
AddTeacher = "绑定班主任",
UpdateTeacher = "修改班主任",
DeleteTeacher = "解绑班主任",
}

/** 树下拉菜单参数接口 */
interface CommandTree {
row: SysUserPersonnel.ClassPage;
commander: cmdEnumTree;
}

/**配置command的参数 */
function commander(row: SysUserPersonnel.ClassPage, commander: cmdEnumTree): CommandTree {
return {
row: row,
commander: commander,
};
}
/**
* 树更多下拉菜单点击事件
* @param commandtree
*/
async function handleCommandTree(commander: CommandTree) {
switch (commander.commander) {
case cmdEnumTree.AddTeacher:
formRefT.value?.onOpen({ opt: commander.commander, record: commander.row, successful: RefreshTable });
break
case cmdEnumTree.UpdateTeacher:
formRefT.value?.onOpen({ opt: commander.commander, record: commander.row, successful: RefreshTable });
break;
case cmdEnumTree.DeleteTeacher:
// 二次确认 => 请求api => 刷新表格
await useHandleData(userManageTeacherApi.delete, {id: commander.row.id}, '解绑教师');
RefreshTable(); //刷新表格
break;
}
}

const handleAvatarSuccess: UploadProps["onSuccess"] = (response) => {
if (response.code === 200) {
userManagePersonnelApi.addFace({
personId: personId.value,


+ 17
- 7
SafeCampus.WEB/src/views/warn/zjrq/index.vue View File

@@ -98,6 +98,12 @@
</div>
</template>
</el-dialog>
<!-- 预览头像 -->
<el-dialog v-model="preViewvisible" title="查看图片" width="830px" :before-close="previewhandleClose">
<div style="display: flex; align-items: center; justify-content: center">
<img class="detailpic" :src="faceUrl" alt="" />
</div>
</el-dialog>
</div>
</template>

@@ -109,6 +115,16 @@ import { useHandleData } from "@/hooks/useHandleData";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { useDictStore } from "@/stores/modules";
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
const faceUrl = ref('');
const preViewvisible = ref(false); //是否显示人员表单
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.snapshotUrl;
preViewvisible.value = true
console.log(faceUrl);
};
const previewhandleClose = () => {
preViewvisible.value = false;
};
const visible = ref(false); //是否显示表单
onMounted(() => {
getWarnTypeList();
@@ -161,13 +177,7 @@ const columns: ColumnProps<ZJRQ.WarnInfo>[] = [
label: "告警快照",
render: scope => {
return (
<img
src={scope.row.snapshotUrl}
class="h-6 w-6"
onClick={() => {
window.open(scope.row.snapshotUrl, "_blank");
}}
/>
<img src={scope.row.snapshotUrl ? scope.row.snapshotUrl : ''} onClick={() => viewHeadImage(scope)} style='width:50px;height:50px;' alt=''/>
);
}
},


Loading…
Cancel
Save