Browse Source

考勤点名

master
yxq 2 months ago
parent
commit
1829086860
14 changed files with 1408 additions and 66 deletions
  1. +1
    -0
      SafeCampus.WEB/src/api/interface/sys/attendance/index.ts
  2. +1
    -1
      SafeCampus.WEB/src/api/interface/sys/attendance/passenger.ts
  3. +31
    -0
      SafeCampus.WEB/src/api/interface/sys/attendance/studentsReturn.ts
  4. +4
    -4
      SafeCampus.WEB/src/api/modules/attendance/studentsReturn.ts
  5. +191
    -0
      SafeCampus.WEB/src/views/attendance/behaviorTrace/components/form/form_basic.vue
  6. +120
    -0
      SafeCampus.WEB/src/views/attendance/behaviorTrace/components/form/index.vue
  7. +107
    -0
      SafeCampus.WEB/src/views/attendance/behaviorTrace/components/formClass/index.vue
  8. +116
    -0
      SafeCampus.WEB/src/views/attendance/behaviorTrace/components/formTeacher/index.vue
  9. +206
    -0
      SafeCampus.WEB/src/views/attendance/behaviorTrace/components/nofaceTable/index.vue
  10. +352
    -12
      SafeCampus.WEB/src/views/attendance/behaviorTrace/index.vue
  11. +20
    -43
      SafeCampus.WEB/src/views/attendance/passenger/components/form/index.vue
  12. +1
    -1
      SafeCampus.WEB/src/views/attendance/passenger/index.vue
  13. +198
    -0
      SafeCampus.WEB/src/views/attendance/studentsReturn/components/nofaceTable/index.vue
  14. +60
    -5
      SafeCampus.WEB/src/views/attendance/studentsReturn/index.vue

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

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

+ 1
- 1
SafeCampus.WEB/src/api/interface/sys/attendance/passenger.ts View File

@@ -14,7 +14,7 @@

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


+ 31
- 0
SafeCampus.WEB/src/api/interface/sys/attendance/studentsReturn.ts View 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 AttendanceStudentsReturn {
export interface Page extends ReqPage {}

/** 表单 */
export interface studentsReturnInfo {
fenpianleixing:String,
shijianduan:any,
shexiangtou:any,
}
}

+ 4
- 4
SafeCampus.WEB/src/api/modules/attendance/studentsReturn.ts View File

@@ -24,19 +24,19 @@ const http = moduleRequest("/business/dfieldApi/");
const attendanceStudentsReturn = {
/** 查询底库列表 */
page(params: SysUserPersonnel.ClassPage) {
return http.get("page", params);
return http.get("test", params);
},
/** 删除底库 */
delete(params: ReqId) {
return http.delete("deleteDfieldD", params);
return http.delete("test", params);
},
/** 创建底库 */
add(params: SysUserPersonnel.ClassPage) {
return http.post("createDfieldA", params);
return http.post("test", params);
},
/** 更新底库 */
update(params: SysUserPersonnel.ClassPage) {
return http.put("updateDfieldU", params);
return http.put("test", params);
}
};



+ 191
- 0
SafeCampus.WEB/src/views/attendance/behaviorTrace/components/form/form_basic.vue View File

@@ -0,0 +1,191 @@
<!--
* @Description: 人员表单
* @Author: syy
* @Date: 2023-12-15 15:45:50
-->
<template>
<div class="userManageForm">
<div>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="人员姓名" prop="name">
<s-input v-model="userInfo.name"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="所属班级" prop="personSetId">
<s-select v-model="userInfo.personSetId" :options="treeData" label="personSetName" value="personSetId"></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
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, userManagePersonnelApi, userManageClassManageApi } 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 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);
}
userInfo.value.faces = faces.value;
if (uploadFile.personId) {
userManagePersonnelApi.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"
}
]);
const getRequestData = async () => {
const { data } = await userManageClassManageApi.page();
treeData.value = data;
};
onMounted(() => {
// 初始化
userInfo.value.gender = userInfo.value.gender ? userInfo.value.gender : genderOptions.value[0].value;
getRequestData();
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>

+ 120
- 0
SafeCampus.WEB/src/views/attendance/behaviorTrace/components/form/index.vue View File

@@ -0,0 +1,120 @@
<!--
* @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="SysUserPersonnelForm">
import { SysUserPersonnel, userManagePersonnelApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
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({
// personId: [required("请输入人员ID")],
name: [required("请输入姓名")],
gender: [required("请选择性别")],
faces: [required("请上传人脸图片")],
phone: [required("请输入手机号")]
// extData: [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
userManagePersonnelApi.detail({ id: props.record.personId }).then(res => {
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 userManagePersonnelApi
.update(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
await userManagePersonnelApi
.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>

+ 107
- 0
SafeCampus.WEB/src/views/attendance/behaviorTrace/components/formClass/index.vue View File

@@ -0,0 +1,107 @@
<!--
* @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="personSetName">
<s-input v-model="sysUserProps.record.personSetName"></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="SysUserPerformClass">
import { ref } from "vue";
import { SysUserPersonnel, userManageClassManageApi } 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<SysUserPersonnel.ClassPage>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
personSetName: [required("请输入班级名称")]
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysUserPersonnel.ClassPage>) {
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; //表单验证失败
sysUserProps.record.name = sysUserProps.record.personSetName;
//提交表单
if (sysUserProps.record.personSetId) {
sysUserProps.record.id = sysUserProps.record.personSetId;
await userManageClassManageApi
.update(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
await userManageClassManageApi
.add(sysUserProps.record)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}

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

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

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

+ 116
- 0
SafeCampus.WEB/src/views/attendance/behaviorTrace/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>

+ 206
- 0
SafeCampus.WEB/src/views/attendance/behaviorTrace/components/nofaceTable/index.vue View File

@@ -0,0 +1,206 @@
<!--
* @Description: 人脸识别失败表格
* @Author: yxq
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`行为轨迹查看`" form-size="1000px" @close="onClose">
<ProTable ref="proTable" title="教师点名" height="600px" :data="tableData" :columns="columns">
<!-- 表格 header 按钮 -->
<template #tableHeader>
<!-- <s-button type="primary" icon="none" prefix="人工" suffix="确认" @click="handleSubmit" /> -->
</template>
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link :opt="FormOptEnum.EDIT" prefix="人工" suffix="确认" @click="onOpen(FormOptEnum.VIEW, scope.row)" />
</el-space>
</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="attendancePassenger">
import { AttendancePassenger, attendancePassenger } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { FormOptEnum } from "@/enums";

const visible = ref(false); //是否显示
// 弹框参数
const propsInfo = reactive<FormProps.Base<AttendancePassenger.PassengerInfo>>({
opt: FormOptEnum.ADD, //操作类型
record: {}, //弹框数据
disabled: false
});
/**
* 打开弹框
* @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 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;"
alt=""
/>
);
}
}
// { prop: "operation", label: "操作", width: 250, fixed: "right" }
];
// 图片预览
const imgVisible = ref(false);
const faceUrl = ref('');
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.faces[0].faceUrl;
imgVisible.value = true
};
// 刷新表格
const RefreshTable = () => {
proTable.value?.refresh();
};

// 提交数据(新增/编辑)
const formRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
formRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
// shijianduan时间段
propsInfo.record.startTime = formatDate(propsInfo.record.shijianduan[0]);
propsInfo.record.endTime = formatDate(propsInfo.record.shijianduan[1]);
delete propsInfo.record.shijianduan;
// shexiangtou摄像头
propsInfo.record.shexiangtou = propsInfo.record.shexiangtou.toString();
//提交表单
console.log(propsInfo);
if (propsInfo.record.id) {
await attendancePassenger
.update(propsInfo.record)
.then(() => {
propsInfo.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
console.log(propsInfo.record);
await attendancePassenger
.add(propsInfo.record)
.then(() => {
propsInfo.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}

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

+ 352
- 12
SafeCampus.WEB/src/views/attendance/behaviorTrace/index.vue View File

@@ -1,34 +1,374 @@
<!--
* @Description: 班级管理
* @Author: yxq
* @Date: 2024-7-16
* @Description: 人员管理
* @Author: syy
* @Date: 2024-7-15
-->
<template>
<div class="table-box">
<ProTable ref="proTable" title="班级管理" :columns="columns" :request-api="userManageClassManageApi.page"> </ProTable>
<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>
<!-- <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.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>
</TreeFilter>
<div class="table-box">
<ProTable ref="proTable" title="人员管理" :columns="columns" rowKey="personId" :request-api="userManagePersonnelApi.page">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<!-- <s-button v-auth="userPerButtonCode.add" suffix="人员" @click="onOpen(FormOptEnum.ADD, { personSetId: personSetId })" />
<s-button
v-auth="userPerButtonCode.delete"
type="danger"
:opt="FormOptEnum.DELETE"
plain
suffix="人员"
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选人员')"
/> -->
</template>
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link prefix="轨迹" suffix="查看" :opt="FormOptEnum.VIEW" @click="onOpenDetail(FormOptEnum.VIEW, scope.row)" />
<!-- <s-button v-auth="userPerButtonCode.edit" link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
<s-button v-auth="userPerButtonCode.delete" 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-item :command="command(scope.row, cmdEnum.UnderpantsUnBinding)">
{{ cmdEnum.UnderpantsUnBinding }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown> -->
</el-space>
</template>
</ProTable>
</div>
<!-- 人员新增/编辑表单 -->
<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">
<img class="detailpic" :src="faceUrl" alt="" />
</div>
</el-dialog>
<!-- 轨迹详情 -->
<TraceDetail ref="detialRef" />
</div>
</template>
<script setup lang="ts">
import { userManageClassManageApi } from "@/api";
<script setup lang="tsx" name="SysUserPersonnel">
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 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";
import { TokenEnum } from "@/enums";
import type { UploadProps } from "element-plus";
import TraceDetail from "./components/nofaceTable/index.vue";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const faceUrl = ref('');
const visible = ref(false); //是否显示人员表单
const proTable = ref<ProTableInstance>();
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
const userStore = useUserStore();
const { accessToken } = userStore;
// 表格配置项
const columns: ColumnProps[] = [
const columns: ColumnProps<SysUserPersonnel.SysUserPerInfo>[] = [
{ type: "selection", fixed: "left", width: 50 },
{
prop: "personSetName",
label: "班级名称"
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: "personSetId",
label: "班级ID"
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
console.log(faceUrl);
};
const handleClose = () => {
visible.value = false;
};


// 人员表单引用
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 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | SysUserPersonnel.SysUserPerInfo = {}) {
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}

/**
* 打开班级表单
* @param opt 操作类型
* @param record 记录
*/

function addClass(opt: FormOptEnum, record: {} | SysUserPersonnel.ClassPage = {}) {
formRefC.value?.onOpen({ opt: opt, record: JSON.parse(JSON.stringify(record)), successful: RefreshTable });
}
/**
* 班级删除
* @param ids id数组
*/
async function addDelete(id: string[],msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(userManageClassManageApi.delete, { id }, msg);
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();
treeFilter.value?.refresh(); //刷新树形筛选器
}


/** 更多下拉菜单命令枚举 */
enum cmdEnum {
AddFace = "添加人脸",
UnderpantsUnBinding = "底库解绑"
}
/** 下拉菜单参数接口 */
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
case cmdEnum.UnderpantsUnBinding:
userManagePersonnelApi.personUnBindDfie({
personId:command.row.personId,
personSetId: command.row.personSets[0].personSetId
}).then(res=>{
ElMessage.success('底库解绑成功');
RefreshTable()
})
break;
}
}



/** 更多下拉菜单命令枚举 */
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,
faceUrl: response.data
}).then(res=>{
RefreshTable()
})
} else {
ElMessage.error(response.msg);
}
};

/** 部门切换 */
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 TraceDetail> | 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>

+ 20
- 43
SafeCampus.WEB/src/views/attendance/passenger/components/form/index.vue View File

@@ -5,23 +5,23 @@
-->
<template>
<div>
<form-container v-model="visible" :title="`${formProps.opt}查询`" form-size="600px" @close="onClose">
<form-container v-model="visible" :title="`${propsInfo.opt}查询`" form-size="600px" @close="onClose">
<el-form
ref="formRef"
:rules="rules"
:disabled="formProps.disabled"
:model="formProps.record"
:hide-required-asterisk="formProps.disabled"
:disabled="propsInfo.disabled"
:model="propsInfo.record"
:hide-required-asterisk="propsInfo.disabled"
label-width="auto"
label-suffix=" :"
>
<el-tabs v-model="activeName">
<Basic v-model="formProps.record"></Basic>
<Basic v-model="propsInfo.record"></Basic>
</el-tabs>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!formProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
<el-button v-show="!propsInfo.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</div>
@@ -37,8 +37,8 @@ import { formatDate } from "@/utils";

const visible = ref(false); //是否显示表单
const activeName = ref("basic");
// 表单参数
const formProps = reactive<FormProps.Base<AttendancePassenger.PassengerInfo>>({
// 弹框参数
const propsInfo = reactive<FormProps.Base<AttendancePassenger.PassengerInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
@@ -68,17 +68,17 @@ const rules = reactive({
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<AttendancePassenger.PassengerInfo>) {
Object.assign(formProps, props); //合并参数
Object.assign(propsInfo, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
formProps.record.shijianduan = [];
formProps.record.shexiangtou = [];
propsInfo.record.shijianduan = [];
propsInfo.record.shexiangtou = [];
}
visible.value = true; //显示表单
if (props.record.id) {
//如果传了id,就去请求api获取record
attendancePassenger.detail({ id: props.record.id }).then(res => {
formProps.record = res.data;
propsInfo.record = res.data;
});
}
}
@@ -87,37 +87,14 @@ function onOpen(props: FormProps.Base<AttendancePassenger.PassengerInfo>) {
const formRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
formRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
// shijianduan时间段
formProps.record.startTime = formatDate(formProps.record.shijianduan[0]);
formProps.record.endTime = formatDate(formProps.record.shijianduan[1]);
delete formProps.record.shijianduan;
// shexiangtou摄像头
formProps.record.shexiangtou = formProps.record.shexiangtou.toString();
//提交表单
console.log(formProps);
if (formProps.record.id) {
await attendancePassenger
.update(formProps.record)
.then(() => {
formProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
console.log(formProps.record);
await attendancePassenger
.add(formProps.record)
.then(() => {
formProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
await attendancePassenger
.update(propsInfo.record)
.then(() => {
propsInfo.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}

/** 关闭表单*/


+ 1
- 1
SafeCampus.WEB/src/views/attendance/passenger/index.vue View File

@@ -5,7 +5,7 @@
-->
<template>
<div class="table-box">
<ProTable ref="proTable" title="查询管理" :columns="columns" :data="data" :request-api="attendancePassenger.page">
<ProTable ref="proTable" title="查询管理" :columns="columns" :data="data">
<!-- 表格 header 按钮 -->
<template #tableHeader>
<s-button v-auth="attendancePassengerBtnCode.add" type="primary" suffix="查询" @click="onOpen(FormOptEnum.ADD)" />


+ 198
- 0
SafeCampus.WEB/src/views/attendance/studentsReturn/components/nofaceTable/index.vue View File

@@ -0,0 +1,198 @@
<!--
* @Description: 人脸识别失败表格
* @Author: yxq
* @Date: 2023-12-15 15:45:59
-->
<template>
<div>
<form-container v-model="visible" :title="`${propsInfo.record.classname}人脸未识别确认`" form-size="1000px" @close="onClose">
<ProTable ref="proTable" title="教师点名" height="600px" :data="tableData" :columns="columns">
<!-- 表格 header 按钮 -->
<template #tableHeader>
<s-button type="primary" icon="check" prefix="人工" suffix="确认" @click="handleSubmit" />
</template>
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link :opt="FormOptEnum.EDIT" prefix="人工" suffix="确认" @click="onOpen(FormOptEnum.VIEW, scope.row)" />
</el-space>
</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="attendancePassenger">
import { AttendancePassenger, attendancePassenger } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { FormOptEnum } from "@/enums";

const visible = ref(false); //是否显示
// 弹框参数
const propsInfo = reactive<FormProps.Base<AttendancePassenger.PassengerInfo>>({
opt: FormOptEnum.ADD, //操作类型
record: {}, //弹框数据
disabled: false
});
/**
* 打开弹框
* @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 tableData = ref([
{
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"
},
],
},
{
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"
},
],
},
{
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"
},
],
},
{
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"
},
],
},
{
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"
},
],
}
]);
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps[] = [
{ type: "selection" },
{
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=""
/>
);
}
}
// { prop: "operation", label: "操作", width: 250, fixed: "right" }
];
// 图片预览
const imgVisible = ref(false);
const faceUrl = ref('');
const viewHeadImage = (scope: any) => {
faceUrl.value = scope.row.faces[0].faceUrl;
imgVisible.value = true
};
// 刷新表格
const RefreshTable = () => {
proTable.value?.refresh();
};

// 提交数据(新增/编辑)
const formRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
formRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
// shijianduan时间段
propsInfo.record.startTime = formatDate(propsInfo.record.shijianduan[0]);
propsInfo.record.endTime = formatDate(propsInfo.record.shijianduan[1]);
delete propsInfo.record.shijianduan;
// shexiangtou摄像头
propsInfo.record.shexiangtou = propsInfo.record.shexiangtou.toString();
//提交表单
console.log(propsInfo);
if (propsInfo.record.id) {
await attendancePassenger
.update(propsInfo.record)
.then(() => {
propsInfo.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
} else {
console.log(propsInfo.record);
await attendancePassenger
.add(propsInfo.record)
.then(() => {
propsInfo.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
}
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}

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

+ 60
- 5
SafeCampus.WEB/src/views/attendance/studentsReturn/index.vue View File

@@ -1,30 +1,85 @@
<!--
* @Description: 班级管理
* @Description: 教师点名
* @Author: yxq
* @Date: 2024-7-16
-->
<template>
<div class="table-box">
<ProTable ref="proTable" title="班级管理" :columns="columns" :request-api="userManageClassManageApi.page"> </ProTable>
<ProTable ref="proTable" title="教师点名" :columns="columns" :data="data">
<!-- 表格操作栏 -->
<template #operation="scope">
<el-space>
<s-button link prefix="人工" :opt="FormOptEnum.EDIT" suffix="确认" @click="onOpen(FormOptEnum.VIEW, scope.row)" />
</el-space>
</template>
</ProTable>
<FaceConfirm ref="FaceConfirmRef" />
</div>
</template>
<script setup lang="ts">
import { userManageClassManageApi } from "@/api";
import { attendanceStudentsReturn, AttendanceStudentsReturn } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { FormOptEnum } from "@/enums";
import FaceConfirm from "./components/nofaceTable/index.vue";
const data = ref([
{
classname: "XXXXX班级",
classId: "544165",
studentNum: "43",
guiqinNum: "38",
noFaceNum: "5"
},
{
classname: "XX班级",
classId: "555333",
studentNum: "42",
guiqinNum: "37",
noFaceNum: "5"
}
]);
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps[] = [
{
prop: "personSetName",
prop: "classname",
label: "班级名称"
},
{
prop: "personSetId",
prop: "classId",
label: "班级ID"
},
{
prop: "studentNum",
label: "学生人数"
},
{
prop: "guiqinNum",
label: "归寝人数"
},
{
prop: "noFaceNum",
label: "人脸无法识别人数"
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
const FaceConfirmRef = ref<InstanceType<typeof FaceConfirm> | null>(null);
/**
* 打开弹框
* @param opt 操作类型
* @param record 弹框数据
*/
function onOpen(opt: FormOptEnum, record: {} | AttendanceStudentsReturn.studentsReturnInfo = {}) {
switch (opt) {
case FormOptEnum.VIEW:
FaceConfirmRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
break;
}
}
// 刷新表格
const RefreshTable = () => {
proTable.value?.refresh();
};
</script>
<style scoped lang="scss">
.table-box {


Loading…
Cancel
Save