@@ -23,5 +23,5 @@ VITE_HTTP_PROXY = true | |||
# 开发环境跨域代理,支持配置多个 | |||
# VITE_PROXY = [["/api","http://192.168.10.186:5566","/Files"]] | |||
VITE_PROXY = [["/api","http://192.168.10.186:8003"],["/Files","artc://rts-pull-live.deepeleph.com"]] | |||
VITE_PROXY = [["/api","http://192.168.10.186:8003"],["/Files","http://192.168.10.186:8003/Files"]] | |||
@@ -2,6 +2,7 @@ | |||
"name": "simple-admin", | |||
"private": true, | |||
"version": "1.0.0", | |||
"node-version": "18.12.0 - 20.9.0", | |||
"type": "module", | |||
"description": "SimpleAdmin是ElementUI最好看的开源通用业务型后台管理系统", | |||
"author": { | |||
@@ -26,6 +26,8 @@ export namespace SysDormitory { | |||
name?: string | undefined; | |||
gender?: boolean | undefined; | |||
createTime?: string | undefined; | |||
insCameraId?: string | number | undefined; | |||
outCameraId?: string | number | undefined; | |||
} | |||
// 寝室列表传参 | |||
export interface Page extends ReqPage { | |||
@@ -41,5 +41,7 @@ export namespace ZJRQ { | |||
rects: string; | |||
/** 扩展字段 */ | |||
extend: string; | |||
// 处理意见 | |||
remark: string; | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
/** | |||
* @description 学生归寝 | |||
* @description 考勤事件管理 | |||
* @license Apache License Version 2.0 | |||
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 | |||
* @remarks | |||
@@ -13,31 +13,30 @@ | |||
* @see https://gitee.com/dotnetmoyu/SimpleAdmin | |||
*/ | |||
import { moduleRequest } from "@/api/request"; | |||
import { ReqId, SysUserPersonnel } from "@/api/interface"; | |||
const http = moduleRequest("/business/dfieldApi/"); | |||
const http = moduleRequest("/business/attendanceApi/"); | |||
/** | |||
* @Description: 单页管理 | |||
* @Author: SYY | |||
* @Author: yxq | |||
* @Date: 2023-12-15 15:34:54 | |||
*/ | |||
const attendanceStudentsReturn = { | |||
/** 查询底库列表 */ | |||
page(params: SysUserPersonnel.ClassPage) { | |||
return http.get("test", params); | |||
const attendanceApi = { | |||
/** 查询考勤记录列表 */ | |||
page(params: any) { | |||
return http.get("getPageList", params); | |||
}, | |||
/** 删除底库 */ | |||
delete(params: ReqId) { | |||
return http.delete("test", params); | |||
/** 查询所有列表 */ | |||
list(params: any) { | |||
return http.get("getNoPageList", params); | |||
}, | |||
/** 创建底库 */ | |||
add(params: SysUserPersonnel.ClassPage) { | |||
return http.post("test", params); | |||
/** 添加考勤记录 */ | |||
add(params: any) { | |||
return http.post("add", params); | |||
}, | |||
/** 根据楼栋查询出入记录 */ | |||
pageByBuild(params: any) { | |||
return http.get("getPageListByBuild", params); | |||
}, | |||
/** 更新底库 */ | |||
update(params: SysUserPersonnel.ClassPage) { | |||
return http.put("test", params); | |||
} | |||
}; | |||
export { attendanceStudentsReturn }; | |||
export { attendanceApi }; |
@@ -15,5 +15,4 @@ | |||
export * from "./behaviorTrace"; | |||
export * from "./passenger"; | |||
export * from "./roolcall"; | |||
export * from "./studentsReturn"; | |||
export * from "./attendanceApi"; |
@@ -20,4 +20,5 @@ export * from "./monitor"; | |||
export * from "./sysconfig"; | |||
export * from "./statistion"; | |||
export * from "./usermanage"; | |||
export * from "./attendance"; | |||
export * from "./attendance"; | |||
export * from "./violation"; |
@@ -13,11 +13,11 @@ | |||
* @see https://gitee.com/dotnetmoyu/SimpleAdmin | |||
*/ | |||
import { moduleRequest } from "@/api/request"; | |||
import { ReqId, ResPage, ZJRQ,setWarn } from "@/api/interface"; | |||
import { ReqId, ResPage, ZJRQ, setWarn } from "@/api/interface"; | |||
const http = moduleRequest("/business/warn/"); | |||
const http1 = moduleRequest("/violation/analysis/"); | |||
const abilityApi = { | |||
/** 获取告警分组 */ | |||
warnGroup(params: ReqId) { | |||
return http.get("getWarnGroup", params); | |||
@@ -26,6 +26,12 @@ const abilityApi = { | |||
setWarnGroup(params: setWarn) { | |||
return http.post("setWarnGroupDevice", params); | |||
}, | |||
/* 预警分析报告导出 */ | |||
reportExport(params: any) { | |||
return http1.download("reportExport", params, { | |||
showHeader: true | |||
}); | |||
} | |||
}; | |||
export { abilityApi }; |
@@ -53,7 +53,7 @@ const userManageDormitoryApi = { | |||
}, | |||
/** 删除寝室 */ | |||
delete(params: ReqId) { | |||
return httpChamber.delete("delete", params); | |||
return httpChamber.post("delete", params); | |||
}, | |||
/**寝室分配人员*/ | |||
setAssignPerson(params: SysDormitory.ChamberPersonnel) { | |||
@@ -68,7 +68,7 @@ const userManageDormitoryApi = { | |||
return httpChamber.get("getReturnInfo", params); | |||
}, | |||
/** 获取详情(带寝室人员) */ | |||
detail(params: ReqId) { | |||
detail(params: any) { | |||
return httpChamber.get("getInfo", params); | |||
}, | |||
/** 设置归寝时间 */ | |||
@@ -76,8 +76,8 @@ const userManageDormitoryApi = { | |||
return httpChamber.post("setReturnTime", params); | |||
}, | |||
/** 获取归寝时间 */ | |||
getReturnTime(params: ReqId) { | |||
return httpChamber.get("getReturnTime", params); | |||
getReturnTime() { | |||
return httpChamber.get("getReturnTime"); | |||
} | |||
}; | |||
@@ -27,7 +27,7 @@ const userManagePersonnelApi = { | |||
return http.post("pageQuery", params); | |||
}, | |||
/** 获取单页详情 */ | |||
detail(params: ReqId) { | |||
detail(params: any) { | |||
return http.get("getPersionById", params); | |||
}, | |||
/** 删除人员 */ | |||
@@ -0,0 +1,16 @@ | |||
/** | |||
* @description | |||
* @license Apache License Version 2.0 | |||
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 | |||
* @remarks | |||
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款: | |||
* 1.请不要删除和修改根目录下的LICENSE文件。 | |||
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。 | |||
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin | |||
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。 | |||
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。 | |||
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关 | |||
* @see https://gitee.com/dotnetmoyu/SimpleAdmin | |||
*/ | |||
export * from "./portraitSummary"; |
@@ -0,0 +1,45 @@ | |||
/** | |||
* @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("/violation/portraitSummary/"); | |||
/** | |||
* @Description: 学校画像 | |||
* @Author: SYY | |||
* @Date: 2024-7-30 15:34:54 | |||
*/ | |||
const portraitSummaryApi = { | |||
/** 获取学生性别 */ | |||
getStudentSex() { | |||
return http.get("getStudentSex"); | |||
}, | |||
/** 获取学生年龄 */ | |||
getStudentAge() { | |||
return http.get("getStudentAge"); | |||
}, | |||
/** 获取地理位置 */ | |||
getAddress() { | |||
return http.get("getAddress"); | |||
}, | |||
/** 获取属性标签 */ | |||
getStudentAttr() { | |||
return http.get("getStudentAttr"); | |||
}, | |||
/** 获取学生属性标签 */ | |||
getStudentDetail(params: any) { | |||
return http.get("getStudentDetail", params); | |||
} | |||
}; | |||
export { portraitSummaryApi }; |
@@ -42,6 +42,10 @@ const warnZJRQApi = { | |||
warnType(params: ReqId) { | |||
return http.get("getAlarmType", params); | |||
}, | |||
/** 处理告警 */ | |||
handWarn(params: ReqId) { | |||
return http.post("handWarn", params); | |||
} | |||
}; | |||
export { warnZJRQApi }; |
@@ -22,6 +22,7 @@ import { checkStatus } from "../helper/checkStatus"; | |||
import { useUserStore } from "@/stores/modules"; | |||
import { AxiosCanceler } from "../helper/axiosCancel"; | |||
import router from "@/routers"; | |||
import { i } from "vite/dist/node/types.d-jgA8ss1A"; | |||
// 自定义 AxiosRequestConfig 接口,增加 noLoading 属性 | |||
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig { | |||
@@ -111,7 +112,7 @@ export default class RequestHttp { | |||
(response: AxiosResponse) => { | |||
// 检查并存储授权信息 | |||
this.checkAndStoreAuthentication(response); | |||
const { data, config } = response; | |||
const { data, config }: any = response; | |||
const userStore = useUserStore(); | |||
axiosCanceler.removePending(config); | |||
tryHideFullScreenLoading(); | |||
@@ -140,7 +141,11 @@ export default class RequestHttp { | |||
}); | |||
} | |||
// 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑) | |||
return data; | |||
if (config.showHeader) { | |||
return response; //平时用在打印、导出、下载的时候使用 | |||
} else { | |||
return data; | |||
} | |||
}, | |||
async (error: AxiosError) => { | |||
const { response } = error; | |||
@@ -4,7 +4,7 @@ | |||
* @Date: 2023-12-15 15:38:32 | |||
!--> | |||
<template> | |||
<el-select :placeholder="placeholder" class="w-full" v-bind="$attrs" clearable> | |||
<el-select :placeholder="placeholder" :filterable="filterable" class="w-full" v-bind="$attrs" clearable> | |||
<el-option v-for="(item, index) in options" :key="index" :label="item[props.label]" :value="item[props.value]" /> | |||
</el-select> | |||
</template> | |||
@@ -20,7 +20,8 @@ const props = withDefaults(defineProps<SSelectProps>(), { | |||
options: [] as any, | |||
value: "value", | |||
label: "label", | |||
button: false | |||
button: false, | |||
filterable: false //是否可搜索 | |||
}); | |||
const placeholder = computed(() => { | |||
@@ -21,4 +21,5 @@ export interface SSelectProps { | |||
label?: string; | |||
/** 选项值 */ | |||
value?: string; | |||
filterable?: boolean; | |||
} |
@@ -20,7 +20,15 @@ | |||
</div> | |||
</div> | |||
<!-- 表格主体 --> | |||
<el-table ref="tableRef" v-bind="$attrs" :data="processTableData" :border="border" :row-key="rowKey" @selection-change="selectionChange"> | |||
<el-table | |||
ref="tableRef" | |||
v-bind="$attrs" | |||
:data="processTableData" | |||
:border="border" | |||
:max-height="maxHeight" | |||
:row-key="rowKey" | |||
@selection-change="selectionChange" | |||
> | |||
<!-- 默认插槽 --> | |||
<slot /> | |||
<template v-for="item in tableColumns" :key="item"> | |||
@@ -113,6 +121,7 @@ export interface ProTableProps { | |||
rowKey?: string; // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id) | |||
searchCol?: number | Record<BreakPoint, number>; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 } | |||
pagerCount?: number; // 分页组件显示最大页码按钮数 ==> 非必传(默认为7) | |||
maxHeight?: number | string; | |||
} | |||
// 接受父组件参数,配置默认值 | |||
@@ -52,7 +52,7 @@ | |||
</el-tabs> | |||
</el-col> | |||
<el-col :span="10"> | |||
<ProTable ref="userTable" :columns="columns" :tool-button="false" :init-param="initParam" :request-api="userSelectorApi"> | |||
<ProTable ref="userTable" :columns="columns" maxHeight="480" :tool-button="false" :init-param="initParam" :request-api="userSelectorApi"> | |||
<!-- 表格 header 按钮 --> | |||
<template #tableHeader="scope"> | |||
<el-button type="primary" @click="addRecords(userTable!.tableData)">添加当前</el-button> | |||
@@ -65,7 +65,15 @@ | |||
</ProTable> | |||
</el-col> | |||
<el-col :span="10"> | |||
<ProTable ref="chooseTable" :columns="columns" :tool-button="true" :data="chooseDataTmp" @search="searchRecords" @reset="resetRecords"> | |||
<ProTable | |||
ref="chooseTable" | |||
:columns="columns" | |||
maxHeight="480" | |||
:tool-button="true" | |||
:data="chooseDataTmp" | |||
@search="searchRecords" | |||
@reset="resetRecords" | |||
> | |||
<!-- 表格 header 按钮 --> | |||
<template #tableHeader="scope"> | |||
<el-button type="danger" @click="delRecords(chooseTable!.tableData)">删除当前</el-button> | |||
@@ -84,12 +92,18 @@ | |||
</el-row> | |||
</div> | |||
<template #footer> | |||
<div class="mt-20px"> | |||
<div> | |||
<el-button @click="onClose"> 取消 </el-button> | |||
<el-button type="primary" @click="handleOk"> 确定 </el-button> | |||
</div> | |||
</template> | |||
</form-container> | |||
<!-- 预览头像 --> | |||
<el-dialog v-model="imgVisible" title="查看头像" width="830px" :before-close="handleClose"> | |||
<div style="display: flex; align-items: center; justify-content: center"> | |||
<img style="max-width: 100%; max-height: 600px" class="detailpic" :src="faceUrl" alt="" /> | |||
</div> | |||
</el-dialog> | |||
</template> | |||
<script setup lang="tsx" name="UserSelector"> | |||
@@ -104,7 +118,16 @@ const emit = defineEmits({ successful: null }); // 自定义事件 | |||
// console.log(tab, event); | |||
// }; | |||
const visible = ref(false); //是否显示 | |||
const imgVisible = ref(false); //是否显示寝室表单 | |||
const faceUrl = ref(''); | |||
const viewHeadImage = (scope: any) => { | |||
faceUrl.value = scope.row.faces[0].faceUrl; | |||
imgVisible.value = true | |||
console.log(faceUrl); | |||
}; | |||
const handleClose = () => { | |||
imgVisible.value = false; | |||
}; | |||
// 定义组件props | |||
const props = withDefaults(defineProps<UserSelectProps>(), { | |||
multiple: false, | |||
@@ -112,7 +135,7 @@ const props = withDefaults(defineProps<UserSelectProps>(), { | |||
}); | |||
// 根据是否业务显示不同名称 | |||
const userName = props.biz ? "人员" : "用户"; | |||
const userName = props.biz ? "用户" : "人员"; | |||
const positionName = props.biz ? "职位" : "岗位"; | |||
const orgName = props.biz ? "机构" : "班级"; | |||
@@ -126,13 +149,12 @@ const chooseTable = ref<ProTableInstance>(); | |||
const columns: ColumnProps<SysUser.SysUserInfo>[] = [ | |||
{ type: "selection", fixed: "left", width: 50 }, | |||
{ prop: "operation", label: "操作", width: 80, fixed: "left" }, | |||
{ 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=''/> | |||
<img src={scope.row.faces.length > 0 ? scope.row.faces[0].faceUrl : ''} onClick={() => viewHeadImage(scope)} style='width:30px;height:30px;' alt=''/> | |||
); | |||
} | |||
}, | |||
@@ -143,7 +165,10 @@ const columns: ColumnProps<SysUser.SysUserInfo>[] = [ | |||
return row.row.name; | |||
}, | |||
search: { el: "input", span: 2 } | |||
} | |||
}, | |||
{ prop: "personSetName", label: "班级" }, | |||
{ prop: "dormitoryName", label: "寝室" }, | |||
]; | |||
/** 显示选择器 */ | |||
@@ -162,6 +187,10 @@ function onClose() { | |||
/** 提交数据 */ | |||
function handleOk() { | |||
if (chooseData.value.length == 0) { | |||
ElMessage.warning("请选择" + userName); | |||
return; | |||
} | |||
visible.value = false; | |||
console.log(chooseData.value,'提交得数据') | |||
emit("successful", chooseData.value); | |||
@@ -86,8 +86,8 @@ const setSelected = () => { | |||
}; | |||
onBeforeMount(async () => { | |||
setSelected(); | |||
await getRequestData(); | |||
setSelected(); | |||
}); | |||
// 使用 nextTick 防止打包后赋值不生效,开发环境是正常的 | |||
@@ -40,7 +40,8 @@ import hljsCommon from "highlight.js/lib/common"; | |||
import hljsVuePlugin from "@highlightjs/vue-plugin"; | |||
//解决谷歌浏览器 Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider markin... | |||
import "default-passive-events"; | |||
// import "default-passive-events"; | |||
import "@/utils/browser_patch"; | |||
// import "vue3-video-play/dist/style.css"; // 引入css | |||
const app = createApp(App); | |||
@@ -43,8 +43,16 @@ export const staticRouter: RouteRecordRaw[] = [ | |||
title: "学生点名详情" | |||
}, | |||
name: "学生点名详情", | |||
path: "/roolcall/detail", | |||
path: "/attendance/roolcall/detail", | |||
component: () => import("@/views/attendance/roolcall/detail.vue") | |||
}, | |||
{ | |||
meta: { | |||
title: "学生画像详情" | |||
}, | |||
name: "学生画像详情", | |||
path: "/violation/portrait/detail", | |||
component: () => import("@/views/violation/portrait/detail.vue") | |||
} | |||
] | |||
} | |||
@@ -0,0 +1,14 @@ | |||
//去除谷歌浏览器的scroll、wheel等事件警告 | |||
(function () { | |||
if (typeof EventTarget !== "undefined") { | |||
let func = EventTarget.prototype.addEventListener; | |||
EventTarget.prototype.addEventListener = function (type, fn, capture) { | |||
this.func = func; | |||
if (typeof capture !== "boolean") { | |||
capture = capture || {}; | |||
capture.passive = false; | |||
} | |||
this.func(type, fn, capture); | |||
}; | |||
} | |||
})(); |
@@ -6,7 +6,15 @@ | |||
<template> | |||
<div> | |||
<form-container v-model="visible" :title="`分片详情`" form-size="800px" @close="onClose"> | |||
<ProTable ref="proTable" title="分片详情" height="500px" :data="tableData" :columns="columns"></ProTable> | |||
<ProTable | |||
:toolButton="false" | |||
:pagination="false" | |||
ref="proTable" | |||
title="分片详情" | |||
height="500px" | |||
:data="tableData" | |||
:columns="columns" | |||
></ProTable> | |||
<template #footer> | |||
<el-button @click="onClose"> 关闭 </el-button> | |||
</template> | |||
@@ -67,7 +67,7 @@ const splitOptions = ref([ | |||
]); | |||
// 摄像头 | |||
let cameraldsOptions = ref([]); | |||
monitorLIVEApi.list({ pageSize: 1000, pageNum: 1 }).then(res => { | |||
monitorLIVEApi.list({ pageSize: 1000, pageNum: 1 }).then((res: any) => { | |||
if (res.code == 200) { | |||
cameraldsOptions.value = res.data.list.map(e => { | |||
return { | |||
@@ -88,7 +88,7 @@ function onOpen(opt: FormOptEnum, record: {} | AttendancePassenger.PassengerInfo | |||
const RefreshTable = () => { | |||
proTable.value?.refresh(); | |||
}; | |||
const haseExtJson = row => { | |||
const haseExtJson = (row: any) => { | |||
let extJson = JSON.parse(row.extJson); | |||
return extJson.length == 0; | |||
}; | |||
@@ -91,7 +91,7 @@ const columns: ColumnProps[] = [ | |||
]; | |||
const onView = (row: any) => { | |||
router.push( { | |||
path:'/roolcall/detail', | |||
path:'/attendance/roolcall/detail', | |||
query: { | |||
taskId: row.taskId, | |||
personSetId:row.personSetId | |||
@@ -1,21 +1,30 @@ | |||
<!-- | |||
* @Description: 人脸识别失败表格 | |||
* @Description: 未归寝 | |||
* @Author: yxq | |||
* @Date: 2023-12-15 15:45:59 | |||
--> | |||
<template> | |||
<div> | |||
<form-container v-model="visible" row-key="id" :title="`${propsInfo.record.qinshibianhao}归寝确认`" form-size="800px" @close="onClose"> | |||
<ProTable ref="noFaceTableRef" title="归寝确认" height="500px" :data="tableData" :columns="columns"> | |||
<form-container v-model="visible" row-key="id" :title="`寝室${propsInfo.record.name}归寝确认`" form-size="800px" @close="onClose"> | |||
<ProTable | |||
ref="noReturnTableRef" | |||
:request-auto="false" | |||
title="归寝确认" | |||
height="500px" | |||
:pagination="false" | |||
:columns="columns" | |||
:request-api="userManageDormitoryApi.returnDetail" | |||
:data-callback="dataCallback" | |||
> | |||
<!-- 表格 header 按钮 --> | |||
<template #tableHeader="scope"> | |||
<s-button | |||
type="primary" | |||
:disabled="!scope.isSelected" | |||
icon="check" | |||
prefix="人工" | |||
suffix="确认" | |||
@click="personConfirm(scope.selectedListIds, '确认已归寝')" | |||
prefix="确认" | |||
suffix="归寝" | |||
@click="personConfirm(scope.selectedList, '确认已归寝')" | |||
/> | |||
</template> | |||
</ProTable> | |||
@@ -26,118 +35,39 @@ | |||
<!-- 预览头像 --> | |||
<el-dialog v-model="imgVisible" title="查看头像" width="830px"> | |||
<div style="display: flex; align-items: center; justify-content: center"> | |||
<img class="detailpic" :src="faceUrl" alt="" /> | |||
<img class="detailpic" :src="'/api/' + faceUrl" alt="" /> | |||
</div> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script setup lang="tsx" name="attendanceStudentsReturn"> | |||
import { AttendanceStudentsReturn, attendanceStudentsReturn } from "@/api"; | |||
import { ColumnProps, noFaceTableInstance } from "@/components/noFaceTableRef/interface"; | |||
import { userManageDormitoryApi,attendanceApi } from "@/api"; | |||
import { ColumnProps,ProTableInstance } from "@/components/ProTable/interface"; | |||
import { FormOptEnum } from "@/enums"; | |||
import { useHandleData } from "@/hooks"; | |||
import { formatDate } from "@/utils"; | |||
const visible = ref(false); //是否显示 | |||
// 弹框参数 | |||
const propsInfo = reactive<FormProps.Base<AttendanceStudentsReturn.PassengerInfo>>({ | |||
const propsInfo = reactive<FormProps.Base<{}>>({ | |||
opt: FormOptEnum.VIEW, //操作类型 | |||
record: {}, //弹框数据 | |||
disabled: false | |||
}); | |||
/** | |||
* 打开弹框 | |||
* @param props 弹框参数 | |||
*/ | |||
function onOpen(props: FormProps.Base<AttendanceStudentsReturn.PassengerInfo>) { | |||
Object.assign(propsInfo, props); //合并参数 | |||
visible.value = true; //显示弹框 | |||
if (props.record.id) { | |||
//如果传了id,就去请求api获取record | |||
attendanceStudentsReturn.detail({ id: props.record.id }).then(res => { | |||
propsInfo.record = res.data; | |||
}); | |||
} | |||
} | |||
const tableData = ref([ | |||
{ | |||
id:'1', | |||
studentname: "张三", | |||
faces: [ | |||
{ | |||
faceUrl: | |||
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D" | |||
}, | |||
], | |||
louceng:'3', | |||
qinshi:'301', | |||
}, | |||
{ | |||
id:'2', | |||
studentname: "李四", | |||
faces: [ | |||
{ | |||
faceUrl: | |||
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D" | |||
}, | |||
], | |||
louceng:'3', | |||
qinshi:'302', | |||
}, | |||
{ | |||
id:'3', | |||
studentname: "张三", | |||
faces: [ | |||
{ | |||
faceUrl: | |||
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D" | |||
}, | |||
], | |||
louceng:'4', | |||
qinshi:'401', | |||
}, | |||
{ | |||
id:'4', | |||
studentname: "李四", | |||
faces: [ | |||
{ | |||
faceUrl: | |||
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D" | |||
}, | |||
], | |||
louceng:'3', | |||
qinshi:'301', | |||
}, | |||
{ | |||
id:'5', | |||
studentname: "张三", | |||
faces: [ | |||
{ | |||
faceUrl: | |||
"http://deepvision.oss-cn-zhangjiakou.aliyuncs.com/dfield-cruising/online/person-set/quanjiang/DEMO00001/8746a847ecd64c1696b37f4cdf617a18/5dc35e4c7af84b3fb5aab70156574873.jpg?Expires=1721375257&OSSAccessKeyId=STS.NTfeBHwzU7kivsSS2EypREace&Signature=c1aTbDc1hNISq%2BV05o0slEfoCTI%3D&security-token=CAIS0wN1q6Ft5B2yfSjIr5fTLvj8mqV0gKmCdFXitzIQdf9%2BqqTIhzz2IHFMf3huCeodsv8%2BlGxS5%2FgelrpqVpZDR03Na8RHwrly1lv5O9KY4x49TRng0s%2FLI3OaLjKm9hi7AYygPgK0GJqEb1TDiVUto9%2FTfimjWFqIKICAjYUdAP0cQgi%2Fa0gwZrJRPRAwh8IGEnHTOP2xUHvtmXGCNFd0nQB%2BhGhjk7TdpPeR8R3Dllb35%2FYIroDqWPieYtJrIY10XqWBvqx%2FfbGT1zVLuVoYtvV6gaFc5zbcv9abRFVf4hiCP6%2Ff6MBuNw5%2Fae94efZNp%2BOukuZj6K6B1db7xhtVI%2BBOUiPZA4mr2IzdBeqvNNcwc7m8F1no9YjXbsGs9EEGGStLaVgVI4F8dyAhWEd9FWjgR%2FX5qAyQUGCKULOY1aw6651xwmjz8MCCT1r1GOTBindGasVnMxh5Z0JMjDK9aNkKfgFUbVJ8BrGTCIh%2FYx0bsq7yowDIEyp71TRMo%2Bbu%2FDBhIifKpO4VN7AxMup1DPwu2wNCxORK3yyybb5sa3aTR6942p%2F%2FF5VxFNPqOx1rmCMf4znuW4yrvNkAVvNcuIqdR8aLhaLIEbHzoKxuEKRnrfsIeBqAAUB%2FCKIuI1UaCj4LYX8DyIYOI8tUKFsMguPHpPENaK9Lcz5ZhB%2BrX6BcQfNWOLhDx7KyRE28vtEs2uolAhLTRaZsZFjbAZE5Ngt09%2FbSXdD%2FR%2BOlGMpQdp752x5lOlvPsJp1skuFLbramyqY4oj4tVwv1OLWpcsEc3AEosPalpAuIAA%3D" | |||
}, | |||
], | |||
louceng:'5', | |||
qinshi:'502', | |||
} | |||
]); | |||
// 获取 noFaceTableRef 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) | |||
const noFaceTableRef = ref<noFaceTableInstance>(); | |||
// 获取 noReturnTableRef 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) | |||
const noReturnTableRef = ref<ProTableInstance>(); | |||
// 表格配置项 | |||
const columns: ColumnProps[] = [ | |||
{ type: "selection" }, | |||
{ | |||
prop: "louceng", | |||
label: "楼层" | |||
}, | |||
{ | |||
prop: "qinshi", | |||
label: "寝室编号" | |||
prop: "name", | |||
label: "学生姓名" | |||
}, | |||
{ | |||
prop: "studentname", | |||
label: "学生姓名" | |||
prop: "personSetName", | |||
label: "班级" | |||
}, | |||
{ | |||
prop: "faces", | |||
@@ -145,7 +75,7 @@ const columns: ColumnProps[] = [ | |||
render: scope => { | |||
return ( | |||
<img | |||
src={scope.row.faces.length > 0 ? scope.row.faces[0].faceUrl : ""} | |||
src={scope.row.faces.length > 0 ? '/api/'+scope.row.faces[0].faceUrl : ""} | |||
onClick={() => viewHeadImage(scope)} | |||
style="width:50px;height:50px;cursor:pointer" | |||
alt="" | |||
@@ -154,6 +84,25 @@ const columns: ColumnProps[] = [ | |||
} | |||
} | |||
]; | |||
/** | |||
* 打开弹框 | |||
* @param props 弹框参数 | |||
*/ | |||
function onOpen(props: FormProps.Base<{}>) { | |||
Object.assign(propsInfo, props); //合并参数 | |||
visible.value = true; //显示弹框 | |||
nextTick(()=>{ | |||
noReturnTableRef.value!.searchParam.id = props.record.id; | |||
noReturnTableRef.value!.searchParam.returnTime = props.record.ReturnTime; | |||
noReturnTableRef.value.search() | |||
}) | |||
} | |||
const dataCallback = (res:any)=>{ | |||
if(!res.attendanceDtos)return res.personInfos | |||
return res.personInfos.filter(e=>{ | |||
return !res.attendanceDtos.find(e1=>e1.personId == e.personId) | |||
}) | |||
} | |||
// 图片预览 | |||
const imgVisible = ref(false); | |||
const faceUrl = ref(''); | |||
@@ -162,8 +111,15 @@ const viewHeadImage = (scope: any) => { | |||
imgVisible.value = true | |||
}; | |||
/** 人工确认 */ | |||
async function personConfirm(ids: string[], msg: string) { | |||
await useHandleData(attendanceStudentsReturn.delete, {id: ids.toString() }, msg); | |||
async function personConfirm(list:any, msg: string) { | |||
list = list.map(e=>{ | |||
return { | |||
personSetId:e.personSetId, | |||
personId:e.personId, | |||
tick:formatDate(new Date().valueOf()) | |||
} | |||
}) | |||
await useHandleData(attendanceApi.add, list, msg); | |||
RefreshTable(); //刷新表格 | |||
} | |||
/** 关闭表单*/ | |||
@@ -172,7 +128,7 @@ function onClose() { | |||
} | |||
// 刷新表格 | |||
const RefreshTable = () => { | |||
noFaceTableRef.value?.refresh(); | |||
noReturnTableRef.value?.refresh(); | |||
}; | |||
// 暴露给父组件的方法 | |||
defineExpose({ | |||
@@ -1,72 +1,138 @@ | |||
<!-- | |||
* @Description: 教师点名 | |||
* @Description: 学生归寝 | |||
* @Author: yxq | |||
* @Date: 2024-7-16 | |||
* @Date: 2024-7-24 | |||
--> | |||
<template> | |||
<div class="table-box"> | |||
<ProTable ref="proTable" title="教师点名" :columns="columns" :data="data"> | |||
<!-- 表格操作栏 --> | |||
<template #operation="scope"> | |||
<el-space> | |||
<s-button | |||
:disabled="!scope.row.noFaceNum" | |||
link | |||
prefix="人工" | |||
:opt="FormOptEnum.EDIT" | |||
suffix="确认" | |||
@click="onOpen(FormOptEnum.VIEW, scope.row)" | |||
/> | |||
</el-space> | |||
<div class="main-box"> | |||
<TreeFilter | |||
ref="treeFilter" | |||
label="name" | |||
id="id" | |||
width="300px" | |||
:show-all="false" | |||
:isData="true" | |||
:data="treeData" | |||
:default-value="defaultValue" | |||
@change="changeTreeFilter" | |||
> | |||
<template v-slot:header> | |||
<h4 style="margin: 0 0 15px; font-size: 18px; font-weight: bold; color: var(--el-color-info-dark-2); letter-spacing: 0.5px">所属楼栋</h4> | |||
</template> | |||
</ProTable> | |||
<ReturnConfirm ref="ReturnConfirmRef" /> | |||
<template v-slot:label="{ row }"> | |||
<span class="custom-tree-node"> | |||
<span>{{ row.node.label }}</span> | |||
</span> | |||
</template> | |||
</TreeFilter> | |||
<div class="table-box"> | |||
<ProTable ref="proTable" title="教师点名" :request-auto="false" :columns="columns" :request-api="userManageDormitoryApi.returnList"> | |||
<!-- 表格 header 按钮 --> | |||
<template #tableHeader> | |||
<div style="display: flex; align-items: center"> | |||
<el-date-picker | |||
v-model="ReturnTime" | |||
:clearable="false" | |||
value-format="YYYY-MM-DD" | |||
type="date" | |||
placeholder="选择查询日期" | |||
@change="retrunTimeChange" | |||
> | |||
</el-date-picker> | |||
<div style="margin-left: 15px; color: #606266">归寝时间:{{ returnTime }}</div> | |||
</div> | |||
</template> | |||
<!-- 表格操作栏 --> | |||
<template #operation="scope"> | |||
<el-space> | |||
<!-- :disabled="!scope.row.noReturnNum" --> | |||
<s-button link prefix="归寝" :opt="FormOptEnum.EDIT" suffix="确认" @click="onOpen(FormOptEnum.VIEW, scope.row)" /> | |||
</el-space> | |||
</template> | |||
</ProTable> | |||
<ReturnConfirm ref="ReturnConfirmRef" /> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
import { attendanceStudentsReturn, AttendanceStudentsReturn } from "@/api"; | |||
import { userManageDormitoryApi } from "@/api"; | |||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface"; | |||
import { FormOptEnum } from "@/enums"; | |||
import ReturnConfirm from "./components/returnConfirm/index.vue"; | |||
const data = ref([ | |||
{ | |||
louceng: "3", | |||
qinshibianhao: "306", | |||
qinshirenshu: "6", | |||
guiqinNum: "5", | |||
noFaceNum: "1" | |||
}, | |||
{ | |||
louceng: "3", | |||
qinshibianhao: "307", | |||
qinshirenshu: "8", | |||
guiqinNum: "6", | |||
noFaceNum: "2" | |||
} | |||
]); | |||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) | |||
const proTable = ref<ProTableInstance>(); | |||
/**左侧楼栋**/ | |||
import TreeFilter from "@/components/TreeFilter/index.vue"; | |||
import { formatDate } from "@/utils"; | |||
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null); | |||
//楼栋切换 | |||
const buildId = ref<number | string>(); | |||
function changeTreeFilter(val: number | string) { | |||
console.log(treeFilter.value?.treeData); | |||
buildId.value = val; | |||
proTable.value!.pageable.pageNum = 1; | |||
proTable.value!.searchParam.buildId = val; | |||
proTable.value!.search(); | |||
} | |||
// 初始化数据 | |||
const treeData = ref([]); | |||
const defaultValue = ref(""); | |||
const ReturnTime = ref(""); | |||
let ReturnTimeDefault = ""; | |||
const returnTime = ref(); | |||
onMounted(() => { | |||
// 设置默认查询日期 | |||
let date = formatDate(new Date().valueOf()); | |||
ReturnTime.value = date.substring(0, 10); | |||
proTable.value!.searchParam.ReturnTime = ReturnTime.value; | |||
ReturnTimeDefault = ReturnTime.value; | |||
// 请求楼栋 | |||
userManageDormitoryApi.list().then(res => { | |||
if (res.code == 200) { | |||
if (res.data.length) { | |||
treeData.value = res.data; | |||
defaultValue.value = res.data[0].id.toString(); | |||
buildId.value = defaultValue.value; | |||
// 请求表格 | |||
proTable.value!.pageable.pageNum = 1; | |||
proTable.value!.searchParam.buildId = defaultValue.value; | |||
proTable.value!.search(); | |||
} | |||
} | |||
}); | |||
// 归寝时间 | |||
userManageDormitoryApi.getReturnTime().then(res => { | |||
if (res.code == 200) { | |||
returnTime.value = res.data.timeBegin + " ~ " + res.data.timeEnd; | |||
} | |||
}); | |||
}); | |||
// 查询日期修改 | |||
const retrunTimeChange = () => { | |||
proTable.value!.pageable.pageNum = 1; | |||
proTable.value!.searchParam.ReturnTime = ReturnTime.value || ReturnTimeDefault; | |||
proTable.value!.search(); | |||
}; | |||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) | |||
// 表格配置项 | |||
const columns: ColumnProps[] = [ | |||
{ | |||
prop: "louceng", | |||
label: "楼层" | |||
}, | |||
{ | |||
prop: "qinshibianhao", | |||
prop: "name", | |||
label: "寝室编号" | |||
}, | |||
{ | |||
prop: "qinshirenshu", | |||
prop: "personCount", | |||
label: "寝室人数" | |||
}, | |||
{ | |||
prop: "guiqinNum", | |||
prop: "dormitoryCount", | |||
label: "归寝人数" | |||
}, | |||
{ | |||
prop: "noFaceNum", | |||
label: "未归寝人数" | |||
prop: "noDormitoryCount", | |||
label: "未归寝人数", | |||
render: scope => { | |||
return scope.row.personCount - scope.row.dormitoryCount; | |||
} | |||
}, | |||
{ prop: "operation", label: "操作", width: 250, fixed: "right" } | |||
]; | |||
@@ -76,10 +142,14 @@ const ReturnConfirmRef = ref<InstanceType<typeof ReturnConfirm> | null>(null); | |||
* @param opt 操作类型 | |||
* @param record 弹框数据 | |||
*/ | |||
function onOpen(opt: FormOptEnum, record: {} | AttendanceStudentsReturn.studentsReturnInfo = {}) { | |||
function onOpen(opt: FormOptEnum, record = {}) { | |||
switch (opt) { | |||
case FormOptEnum.VIEW: | |||
ReturnConfirmRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable }); | |||
ReturnConfirmRef.value?.onOpen({ | |||
opt: opt, | |||
record: { ...record, ReturnTime: ReturnTime.value || ReturnTimeDefault }, | |||
successful: RefreshTable | |||
}); | |||
break; | |||
} | |||
} | |||
@@ -158,7 +158,7 @@ function getDataChart() { | |||
}); | |||
}); | |||
} | |||
function getCharts1(data) { | |||
function getCharts1(data: any) { | |||
const chart = echarts.init(chart1.value); | |||
const option = { | |||
tooltip: { | |||
@@ -208,7 +208,7 @@ function getCharts1(data) { | |||
chart.resize(); | |||
}); | |||
} | |||
function getCharts2(data) { | |||
function getCharts2(data: any) { | |||
const chartstation = echarts.init(chart2.value); | |||
const option = { | |||
tooltip: { | |||
@@ -261,22 +261,22 @@ function getCharts2(data) { | |||
function getWeekData() { | |||
setTimeout(async () => { | |||
await statistionApi.weekstatistion({}).then(res => { | |||
await statistionApi.weekstatistion({}).then((res: any) => { | |||
let { code, data } = res; | |||
if (code == 200) { | |||
// let chartData = data; | |||
let time = data.dataX; | |||
let chartData = data.dataY.map(item => { | |||
let chartData = data.dataY.map((item: any) => { | |||
return { | |||
data: item.data, | |||
name: item.name, | |||
type: "bar", | |||
barWidth: "12px", // 设置柱子粗细 | |||
itemStyle: { | |||
normal: { | |||
barBorderRadius: [30, 30, 0, 0] | |||
} | |||
// normal: { | |||
borderRadius: [30, 30, 0, 0] | |||
// } | |||
} | |||
}; | |||
}); | |||
@@ -285,7 +285,7 @@ function getWeekData() { | |||
}); | |||
}); | |||
} | |||
function getCharts3(time, data) { | |||
function getCharts3(time: any, data: any) { | |||
const chartstation3 = echarts.init(chart3.value); | |||
const option = { | |||
tooltip: { | |||
@@ -10,7 +10,7 @@ | |||
label="clothSetName" | |||
id="clothSetId" | |||
width="300px" | |||
:show-all="true" | |||
:show-all="false" | |||
:request-api="userManageClothApi.getList" | |||
@change="changeTreeFilter" | |||
> | |||
@@ -27,6 +27,28 @@ | |||
<s-radio-group v-model="sysDormitoryProps.record.gender" :options="genderOptions" /> | |||
</s-form-item> | |||
</el-col> | |||
<el-col :span="22"> | |||
<s-form-item label="进楼摄像头" prop="insCameraId"> | |||
<s-select | |||
v-model="sysDormitoryProps.record.insCameraId" | |||
:filterable="true" | |||
:options="creamaData" | |||
label="sensorName" | |||
value="sensorId" | |||
></s-select> | |||
</s-form-item> | |||
</el-col> | |||
<el-col :span="22"> | |||
<s-form-item label="出楼摄像头" prop="outCameraId"> | |||
<s-select | |||
v-model="sysDormitoryProps.record.outCameraId" | |||
:filterable="true" | |||
:options="creamaData" | |||
label="sensorName" | |||
value="sensorId" | |||
></s-select> | |||
</s-form-item> | |||
</el-col> | |||
</el-row> | |||
</div> | |||
</el-form> | |||
@@ -40,12 +62,13 @@ | |||
<script setup lang="ts" name="SysDormitoryformClass"> | |||
import { ref } from "vue"; | |||
import { SysDormitory, userManageDormitoryApi } from "@/api"; | |||
import { SysDormitory, userManageDormitoryApi, monitorLIVEApi } from "@/api"; | |||
import { FormOptEnum } from "@/enums"; | |||
import { required } from "@/utils/formRules"; | |||
import { FormInstance } from "element-plus"; | |||
const visibleDormitory = ref(false); //是否显示表单 | |||
const creamaData = ref<any>([]); | |||
const genderOptions = [ | |||
{ | |||
label: "男", | |||
@@ -65,7 +88,9 @@ const sysDormitoryProps = reactive<FormProps.Base<SysDormitory.DormitoryInfo>>({ | |||
// 表单验证规则 | |||
const rules = reactive({ | |||
name: [required("请输入宿舍楼名称")], | |||
gender: [required("请选择性别")] | |||
gender: [required("请选择性别")], | |||
insCameraId: [required("请选择进楼摄像头")], | |||
outCameraId: [required("请选择出楼摄像头")] | |||
}); | |||
/** | |||
@@ -113,7 +138,14 @@ async function handleSubmit() { | |||
function onClose() { | |||
visibleDormitory.value = false; | |||
} | |||
onMounted(() => { | |||
getCreamaList(); | |||
}); | |||
const getCreamaList = () => { | |||
monitorLIVEApi.list({ pageNum: 1, pageSize: 1000 }).then(res => { | |||
creamaData.value = res.data.list; | |||
}); | |||
}; | |||
// 暴露给父组件的方法 | |||
defineExpose({ | |||
onOpen | |||
@@ -1,144 +0,0 @@ | |||
<!-- | |||
* @Description: 表单 | |||
* @Author: huguodong | |||
* @Date: 2023-12-15 15:45:28 | |||
!--> | |||
<template> | |||
<div> | |||
<form-container v-model="visible" title="人员选择" form-size="600px"> | |||
<el-form | |||
ref="userFormRef" | |||
:rules="rules" | |||
:disabled="liveUserProps.disabled" | |||
:model="liveUserProps.record" | |||
:hide-required-asterisk="liveUserProps.disabled" | |||
label-width="auto" | |||
label-suffix=" :" | |||
> | |||
<s-form-item label="分配人员" prop="userId"> | |||
<el-button link type="primary" @click="showSelector">选择</el-button> | |||
<el-tag v-if="liveUserProps.record.userId" class="ml-3px" type="warning" closable @close="removeDirector">{{ | |||
liveUserProps.record.userInfo?.name | |||
}}</el-tag> | |||
</s-form-item> | |||
</el-form> | |||
<template #footer> | |||
<el-button @click="onClose"> 取消 </el-button> | |||
<el-button v-show="!liveUserProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button> | |||
</template> | |||
</form-container> | |||
<user-selector | |||
multiple | |||
ref="userSelectorRef" | |||
:org-tree-api="sysOrgApi.tree" | |||
:user-selector-api="sysUserApi.selector" | |||
@successful="handleChooseUser" | |||
/> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
import { SysOrg, SysUser, sysOrgApi, sysPositionApi, sysRoleApi, sysUserApi, monitorLIVEApi } from "@/api"; | |||
import { FormOptEnum, SysDictEnum } from "@/enums"; | |||
import { required } from "@/utils/formRules"; | |||
import { FormInstance } from "element-plus"; | |||
import { useDictStore } from "@/stores/modules"; | |||
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface"; | |||
const visible = ref(false); //是否显示表单 | |||
const dictStore = useDictStore(); //字典仓库 | |||
// 表单参数 | |||
const liveUserProps = reactive<FormProps.Base<any>>({ | |||
opt: FormOptEnum.ADD, | |||
record: {}, | |||
disabled: false | |||
}); | |||
// 表单验证规则 | |||
const rules = reactive({ | |||
userId: [required("请选择人员")] | |||
}); | |||
/** | |||
* 打开表单 | |||
* @param props 表单参数 | |||
*/ | |||
function onOpen(props: FormProps.Base<any>) { | |||
Object.assign(liveUserProps, props); //合并参数 | |||
if (props.opt == FormOptEnum.ADD) { | |||
//如果是新增,设置默认值 | |||
} | |||
visible.value = true; //显示表单 | |||
if (props.record.pushUserId) { | |||
//如果传了id,就去请求api获取record | |||
liveUserProps.record.userId = props.record.pushUserId; | |||
liveUserProps.record.userInfo = props.record.sysUserItem; | |||
// sysOrgApi.detail({ id: props.record.id }).then(res => { | |||
// liveUserProps.record.userId = res.data; | |||
// }); | |||
} | |||
} | |||
// 提交数据(新增/编辑) | |||
const userFormRef = ref<FormInstance>(); | |||
/** 提交表单 */ | |||
async function handleSubmit() { | |||
userFormRef.value?.validate(async valid => { | |||
if (!valid) return; //表单验证失败 | |||
let params: any = { | |||
warnCode: "", | |||
userId: "" | |||
}; | |||
if (liveUserProps.opt == "预警推送人") { | |||
params.warnCode = liveUserProps.record.warnCode; | |||
params.userId = liveUserProps.record.userId; | |||
//提交表单 | |||
await monitorLIVEApi | |||
.setWarningPushPerson(params) | |||
.then(() => { | |||
liveUserProps.successful!(); //调用父组件的successful方法 | |||
}) | |||
.finally(() => { | |||
onClose(); | |||
}); | |||
} | |||
}); | |||
} | |||
/** 关闭表单*/ | |||
function onClose() { | |||
visible.value = false; | |||
liveUserProps.record.userId = null; | |||
liveUserProps.record.userInfo = null; | |||
} | |||
const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用 | |||
/** 显示用户选择器 */ | |||
function showSelector() { | |||
//将liveUserProps.record.userInfo转为 SysUser.SysUserInfo[]类型 | |||
const userInfo = liveUserProps.record.userInfo ? [liveUserProps.record.userInfo] : []; | |||
userSelectorRef.value?.showSelector(userInfo); | |||
} | |||
/** 选择用户 */ | |||
function handleChooseUser(data: SysUser.SysUserInfo[]) { | |||
// 选择用户后,将用户id赋值给liveUserProps.record.userId | |||
if (data.length > 0) { | |||
liveUserProps.record.userId = data[0].id; | |||
liveUserProps.record.userInfo = data[0]; | |||
} | |||
} | |||
/** 移除主管 */ | |||
function removeDirector() { | |||
liveUserProps.record.userId = null; | |||
liveUserProps.record.userInfo = null; | |||
} | |||
// 暴露给父组件的方法 | |||
defineExpose({ | |||
onOpen | |||
}); | |||
</script> | |||
<style lang="scss" scoped></style> |
@@ -1,7 +1,7 @@ | |||
<!-- | |||
* @Description: 寝室管理 | |||
* @Author: syy | |||
* @Date: 2024-7-15 | |||
* @Author: wwp | |||
* @Date: 2024-7-25 | |||
--> | |||
<template> | |||
<div class="main-box"> | |||
@@ -17,6 +17,7 @@ | |||
> | |||
<template v-slot:header> | |||
<s-button suffix="宿舍楼" @click="addDormitory(FormOptEnum.ADD)" style="margin-bottom: 15px" /> | |||
<el-button @click="settingTime" style="margin-bottom: 15px">归寝时间设置</el-button> | |||
</template> | |||
<template v-slot:label="{ row }"> | |||
<span class="custom-tree-node"> | |||
@@ -65,38 +66,68 @@ | |||
</el-space> | |||
</template> | |||
</ProTable> | |||
<ClassUserselector | |||
ref="userSelectorRef" | |||
:org-tree-api="userManageClassManageApi.page" | |||
:user-selector-api="userManagePersonnelApi.page" | |||
multiple | |||
@successful="handleChooseUser" | |||
> | |||
</ClassUserselector> | |||
</div> | |||
<!-- 寝室新增/编辑表单 --> | |||
<Form ref="formRef"></Form> | |||
<!-- 宿舍楼新增/编辑表单 --> | |||
<FormDormitory ref="formRefD" /> | |||
<!-- 班主任绑定/修改 --> | |||
<!-- :user-selector-params="{ orgId: orgId }" --> | |||
<ClassUserselector | |||
ref="userSelectorRef" | |||
:org-tree-api="userManageClassManageApi.page" | |||
:org-tree-props="{ label: 'name', children: 'children' }" | |||
:user-selector-api="userManagePersonnelApi.page" | |||
multiple | |||
@successful="handleChooseUser" | |||
> | |||
</ClassUserselector> | |||
<!-- <formUser ref="formRefU" /> --> | |||
<!-- 预览头像 --> | |||
<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%; max-height: 600px" class="detailpic" :src="faceUrl" alt="" /> | |||
</div> | |||
<el-dialog v-model="timeVisible" title="归寝时间设置" :before-close="closeTime"> | |||
<el-form :model="timeForm" :rules="timeRules" ref="timeFormRef" label-width="100px"> | |||
<el-row> | |||
<el-col :span="16"> | |||
<el-form-item label="功能有效期:" prop="dateArr"> | |||
<el-date-picker | |||
v-model="timeForm.dateArr" | |||
type="daterange" | |||
format="YYYY-MM-DD" | |||
value-format="YYYY-MM-DD" | |||
range-separator="-" | |||
start-placeholder="开始日期" | |||
end-placeholder="结束日期" | |||
:size="size" | |||
/> | |||
</el-form-item> | |||
</el-col> | |||
<el-col :span="16"> | |||
<el-form-item label="布防时间:" prop="timeArr"> | |||
<el-time-picker | |||
v-model="timeForm.timeArr" | |||
format="HH:mm:ss" | |||
value-format="HH:mm:ss" | |||
is-range | |||
range-separator="-" | |||
start-placeholder="开始时间" | |||
end-placeholder="结束时间" | |||
/> | |||
</el-form-item> | |||
</el-col> | |||
</el-row> | |||
</el-form> | |||
<template #footer> | |||
<div class="dialog-footer"> | |||
<el-button type="primary" @click="onTimeSubmit">提交</el-button> | |||
<el-button @click="closeTime">取消</el-button> | |||
</div> | |||
</template> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script setup lang="tsx" name="SysDormitory"> | |||
import { userManageDormitoryApi,dormitoryButtonCode,SysDormitory,sysOrgApi,sysPositionApi,sysUserApi,sysRoleApi,userManagePersonnelApi,userManageClassManageApi } from "@/api"; | |||
import { userManageDormitoryApi,dormitoryButtonCode,SysDormitory,userManagePersonnelApi,userManageClassManageApi,SysUser } from "@/api"; | |||
import { useHandleData } from "@/hooks/useHandleData"; | |||
import { FormOptEnum } from "@/enums"; | |||
import Form from "./components/form/index.vue"; | |||
import FormDormitory from "./components/formDormitory/index.vue"; | |||
import formUser from "./components/formUser/index.vue"; | |||
import { ArrowDown,More } from "@element-plus/icons-vue"; | |||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface"; | |||
import TreeFilter from "@/components/TreeFilter/index.vue"; | |||
@@ -105,8 +136,7 @@ import { TokenEnum } from "@/enums"; | |||
import type { UploadProps } from "element-plus"; | |||
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface"; | |||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) | |||
const faceUrl = ref(''); | |||
const visible = ref(false); //是否显示寝室表单 | |||
const proTable = ref<ProTableInstance>(); | |||
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null); | |||
const userStore = useUserStore(); | |||
@@ -128,32 +158,84 @@ const columns: ColumnProps<SysDormitory.ChamberInfo>[] = [ | |||
}, | |||
{ 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 size = ref<'default' | 'large' | 'small'>('default') | |||
const timeVisible = ref(false); //是否显示时间设置 | |||
const timeForm = reactive<any>({ | |||
dateArr: [], | |||
timeArr: [], | |||
}); | |||
const timeRules = ref({ | |||
dateArr: [{ required: true, message: "请选择功能有效期", trigger: "change" }], | |||
timeArr: [{ required: true, message: "请选择布防时间", trigger: "change" }], | |||
}); | |||
const timeFormRef = ref<any>(null); | |||
const settingTime = () => { | |||
timeVisible.value = true; | |||
getTimeDetail() | |||
}; | |||
const getTimeDetail = () => { | |||
userManageDormitoryApi.getReturnTime().then((res:any) => { | |||
let {data} = res | |||
timeForm.dateArr = [data.funcStart,data.funcEnd] as never[]; | |||
timeForm.timeArr = [data.timeBegin,data.timeEnd] as never[]; | |||
}); | |||
} | |||
const closeTime = () => { | |||
timeForm.dateArr = []; | |||
timeForm.timeArr = []; | |||
timeVisible.value = false; | |||
}; | |||
const handleClose = () => { | |||
visible.value = false; | |||
// 提交分组 | |||
const onTimeSubmit = () => { | |||
timeFormRef.value.validate((valid: any) => { | |||
if (valid) { | |||
let params: any = reactive({ | |||
funcStart: timeForm.dateArr[0], | |||
funcEnd: timeForm.dateArr[1], | |||
timeBegin: timeForm.timeArr[0], | |||
timeEnd: timeForm.timeArr[1] | |||
}); | |||
userManageDormitoryApi.setReturnTime(params).then((res:any) => { | |||
if (res.code == 200) { | |||
// getGroupList(); | |||
ElMessage({ | |||
message: res.msg, | |||
type: 'success' | |||
}); | |||
closeTime(); | |||
} | |||
}); | |||
} else { | |||
return false; | |||
} | |||
}); | |||
}; | |||
// 确定人员 | |||
function handleChooseUser(data: SysUser.SysUserInfo[]) { | |||
console.log(data) | |||
//组装参数 | |||
const grantUser: SysRole.GrantUserReq = { | |||
const grantUser: SysDormitory.ChamberPersonnel = { | |||
// id: roleId.value, | |||
// grantInfoList: data.map(item => item.id) as number[] | string[], | |||
dormitoryId: chamberId.value, | |||
personIds: data.map(item => item.personId) as number[] | string[], | |||
personIds: data.map((item:any) => item.personId) as number[] | string[], | |||
}; | |||
userManageDormitoryApi.setAssignPerson(grantUser); | |||
userManageDormitoryApi.setAssignPerson(grantUser).then(res => { | |||
ElMessage({ | |||
message: res.msg, | |||
type: 'success' | |||
}); | |||
RefreshTable(); | |||
}) | |||
} | |||
// 寝室表单引用 | |||
const formRef = ref<InstanceType<typeof Form> | null>(null); | |||
// 宿舍楼表单引用 | |||
const formRefD = ref<InstanceType<typeof FormDormitory> | null>(null); | |||
// 人员引用 | |||
const formRefU = ref<InstanceType<typeof formUser> | null>(null); | |||
/** | |||
* 打开表单 | |||
@@ -161,13 +243,14 @@ const formRefU = ref<InstanceType<typeof formUser> | null>(null); | |||
* @param record 记录 | |||
*/ | |||
function onOpen(opt: FormOptEnum, record: {} | SysDormitory.ChamberInfo = {}) { | |||
if(buildId.value) { | |||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable }); | |||
} else { | |||
if(!buildId.value && opt == '新增') { | |||
ElMessage({ | |||
message: '请选择宿舍楼', | |||
type: 'warning' | |||
}); | |||
} else { | |||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable }); | |||
} | |||
} | |||
@@ -204,7 +287,7 @@ function onOpen(opt: FormOptEnum, record: {} | SysDormitory.ChamberInfo = {}) { | |||
return | |||
} | |||
// 二次确认 => 请求api => 刷新表格 | |||
await useHandleData(userManageDormitoryApi.delete, {id: ids.join(",") }, msg); | |||
await useHandleData(userManageDormitoryApi.delete, {ids: ids }, msg); | |||
RefreshTable(); //刷新表格 | |||
} | |||
@@ -248,16 +331,17 @@ const chamberId = ref<number | string>(); //寝室id | |||
function handleCommand(command: Command) { | |||
switch (command.command) { | |||
case cmdEnum.AddPerson: | |||
console.log(111) | |||
// addPerson(FormOptEnum.add, command.row) | |||
userSelectorRef.value?.showSelector(); //显示用户选择器 | |||
userManageDormitoryApi.detail({ id: command.row.id }).then((res:any) => { | |||
userSelectorRef.value?.showSelector(res.data.personInfos); //显示用户选择器 | |||
}); | |||
chamberId.value = command.row.id; //获取寝室id | |||
break; | |||
} | |||
} | |||
function addPerson(opt: FormOptEnum, record: {} | SysDormitory.DormitoryInfo = {}) { | |||
formRefU.value?.onOpen({ opt: opt, record: record, successful: RefreshTable }); | |||
} | |||
// function addPerson(opt: FormOptEnum, record: {} | SysDormitory.DormitoryInfo = {}) { | |||
// formRefU.value?.onOpen({ opt: opt, record: record, successful: RefreshTable }); | |||
// } | |||
/** 宿舍楼切换 */ | |||
const buildId = ref<number | string>() | |||
const buildGender = ref<boolean>() | |||
@@ -0,0 +1,143 @@ | |||
<!-- | |||
* @Description: 学生画像 | |||
* @Author: syy | |||
* @Date: 2024-7-24 | |||
--> | |||
<template> | |||
<div class="main-box"> | |||
<div class="table-box"> | |||
<ProTable ref="proTable" title="预警分析" :columns="columns" rowKey="id" :pagination="false" :request-api="abilityApi.warnGroup"> | |||
<!-- 表格 header 按钮 --> | |||
<template #operation="scope"> | |||
<s-button icon="Download" link :opt="FormOptEnums.EXPORT" @click="exportFn(scope.row)" /> | |||
</template> | |||
</ProTable> | |||
</div> | |||
<el-dialog v-model="timeForm.visible" title="选择导出时间范围" :before-close="closeTime" width="600px"> | |||
<el-form :model="timeForm" :rules="timeRules" ref="timeFormRef" label-width="100px"> | |||
<el-row> | |||
<el-col :span="21"> | |||
<el-form-item label="时间:" prop="dateArr"> | |||
<el-date-picker | |||
v-model="timeForm.dateArr" | |||
type="daterange" | |||
format="YYYY-MM-DD" | |||
value-format="YYYY-MM-DD" | |||
range-separator="-" | |||
start-placeholder="开始日期" | |||
end-placeholder="结束日期" | |||
/> | |||
</el-form-item> | |||
</el-col> | |||
</el-row> | |||
</el-form> | |||
<template #footer> | |||
<div class="dialog-footer"> | |||
<el-button type="primary" @click="onTimeSubmit">提交</el-button> | |||
<el-button @click="closeTime">取消</el-button> | |||
</div> | |||
</template> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script setup lang="tsx" name="SysUserPersonnel"> | |||
import { abilityApi} from "@/api"; | |||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface"; | |||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) | |||
const proTable = ref<ProTableInstance>(); | |||
const timeForm:any= reactive({ | |||
dateArr:[], | |||
rowItem:{}, | |||
visible:false | |||
}); | |||
const timeFormRef = ref(); | |||
const timeRules = ref({ | |||
dateArr: [ | |||
{ required: true, message: "请选择导出时间范围", trigger: "change" } | |||
] | |||
}); | |||
/** 表单操作类型枚举 */ | |||
enum FormOptEnums { | |||
/** 导出 */ | |||
EXPORT = "导出", | |||
} | |||
// 表格配置项 | |||
const columns: ColumnProps[] = [{ type: "index", label: "序号", width: 60, fixed: "left" }, | |||
{ | |||
prop: "cameraName", | |||
label: "摄像头", | |||
render: (scope: any) => { | |||
return scope.row.cameraName.join(", "); | |||
} | |||
}, | |||
{ | |||
prop: "name", | |||
label: "场景" | |||
}, | |||
{ | |||
prop: "state", | |||
label: "状态", | |||
render: (scope: any) => { | |||
return scope.row.state ? <el-tag type="success">启用</el-tag> : <el-tag type="danger">禁用</el-tag>; | |||
} | |||
}, | |||
{ prop: "operation", label: "操作", width: 250, fixed: "right" } | |||
]; | |||
/* 导出 */ | |||
const onTimeSubmit = ()=>{ | |||
timeFormRef.value.validate((valid: any) => { | |||
if (valid) { | |||
abilityApi.reportExport({ | |||
groupCode:timeForm.rowItem.code, | |||
startTime: timeForm.dateArr[0], | |||
endTime: timeForm.dateArr[1], | |||
}).then((res:any) => { | |||
console.log(res,"....UTF-8''"); | |||
const filename:any = ref() | |||
filename.value = window.decodeURI(res.headers["content-disposition"].split("=")[2]); | |||
filename.value = filename.value.slice(7,-1) | |||
let blobUrl = window.URL.createObjectURL(res.data); | |||
const a = document.createElement('a'); | |||
a.style.display = 'none'; | |||
a.setAttribute("target", "_blank"); | |||
a.download = filename.value | |||
a.href = blobUrl; | |||
a.click(); | |||
a.remove(); | |||
ElMessage({ | |||
message: res.msg, | |||
type: 'success' | |||
}); | |||
closeTime(); | |||
}); | |||
} else { | |||
return false; | |||
} | |||
}); | |||
}; | |||
const closeTime = () =>{ | |||
timeForm.visible = false | |||
} | |||
const exportFn = (row: any) => { | |||
timeForm.rowItem = JSON.parse(JSON.stringify(row)); | |||
timeForm.visible = true | |||
}; | |||
</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> |
@@ -0,0 +1,254 @@ | |||
<template> | |||
<div class="roolcallDetail"> | |||
<div class="header"> | |||
<div class="left"> | |||
<el-image | |||
style="width: 100px; height: 100px" | |||
src="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg" | |||
:zoom-rate="1.2" | |||
:max-scale="7" | |||
:min-scale="0.2" | |||
:preview-src-list="['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg']" | |||
:initial-index="4" | |||
fit="cover" | |||
/> | |||
</div> | |||
<div class="right"> | |||
<el-row> | |||
<el-col :span="6"> | |||
<span>人员编号:{{ formData.personId }}</span></el-col | |||
> | |||
<el-col :span="6" | |||
><span>姓名:{{ formData.name }}</span></el-col | |||
> | |||
<el-col :span="6" | |||
><span>性别:{{ genderOptions[formData.gender] }}</span></el-col | |||
> | |||
<el-col :span="6" | |||
><span>年龄:{{ formData.age }}</span></el-col | |||
> | |||
<el-col :span="6" | |||
><span>联系方式:{{ formData.phone }}</span> | |||
</el-col> | |||
<el-col :span="6"><span>班级:演示一班</span> </el-col> | |||
</el-row> | |||
</div> | |||
</div> | |||
<div class="content"> | |||
<div ref="chartEl" style="width: 100%; height: 100%"></div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup lang="ts" name="ViolationPortraitDetail"> | |||
import { userManagePersonnelApi, portraitSummaryApi } from "@/api"; | |||
const route = useRoute(); | |||
const formData = ref<any>({}); //班级人员列表 | |||
import * as echarts from "echarts"; | |||
const chartEl = ref(null); | |||
const genderOptions = ref<any>({ | |||
GENDER_UNKNOWN: "未知", | |||
GENDER_MALE: "男", | |||
GENDER_FEMALE: "女" | |||
}); | |||
const getDetail = () => { | |||
userManagePersonnelApi.detail({ id: route.query.personId }).then((res: any) => { | |||
formData.value = res.data; | |||
}); | |||
portraitSummaryApi.getStudentDetail({ id: route.query.personId }).then((res: any) => { | |||
getCharts(res.data.label, res.data.value); | |||
}); | |||
}; | |||
const getCharts = (nameArr: any, valueArr: any) => { | |||
const chart = echarts.init(chartEl.value); | |||
const option = { | |||
title: { | |||
text: "人员属性标签", | |||
left: "center", | |||
top: "5%", | |||
textStyle: { | |||
color: "#000", | |||
fontSize: 25 | |||
} | |||
}, | |||
grid: { | |||
top: "18%", | |||
left: "5%", | |||
right: "5%", | |||
bottom: "12%" | |||
}, | |||
tooltip: { | |||
trigger: "axis", | |||
backgroundColor: "#3A4667", | |||
borderColor: "#3A4667", | |||
textStyle: { | |||
color: "#fff" | |||
}, | |||
formatter: "{b} : {c}", | |||
axisPointer: { | |||
type: "cross", | |||
crossStyle: { | |||
color: "#999" | |||
} | |||
} | |||
}, | |||
legend: { | |||
icon: "rect", | |||
top: 10, | |||
right: 5, | |||
itemWidth: 10, | |||
itemHeight: 10, | |||
textStyle: { | |||
fontSize: 12, // 字体大小 | |||
// color: '#B3CFFF', // 字体颜色 | |||
color: "#FFEB3B" | |||
} | |||
}, | |||
xAxis: { | |||
type: "category", | |||
axisPointer: { | |||
type: "shadow" | |||
}, | |||
axisLine: { | |||
lineStyle: { | |||
color: "rgba(112, 138, 198, 1)" | |||
} | |||
}, | |||
axisLabel: { | |||
textStyle: { | |||
color: "#333", // x轴文本颜色 | |||
fontSize: 12 | |||
} | |||
// rotate:30, | |||
}, | |||
axisTick: { | |||
show: false | |||
}, | |||
data: nameArr | |||
}, | |||
yAxis: { | |||
name: "次", | |||
type: "value", | |||
splitLine: { | |||
show: true, | |||
lineStyle: { | |||
color: "#333", | |||
type: "dashed" | |||
} | |||
}, | |||
axisLabel: { | |||
formatter: "{value}", | |||
textStyle: { | |||
color: "#333", // x轴文本颜色 | |||
fontSize: 12 | |||
} | |||
}, | |||
nameTextStyle: { | |||
color: "#333", | |||
fontSize: 12 | |||
} | |||
}, | |||
series: [ | |||
{ | |||
type: "bar", | |||
name: "", | |||
barWidth: 27, | |||
emphasis: { | |||
itemStyle: { | |||
color: "#7fb7e9" | |||
}, | |||
label: { | |||
textStyle: { | |||
color: "#7fb7e9" | |||
} | |||
} | |||
}, | |||
label: { | |||
show: true, | |||
position: "top", | |||
textStyle: { | |||
color: "rgba(12, 245, 229, 1)", | |||
padding: [0, 10] | |||
} | |||
}, | |||
itemStyle: { | |||
normal: { | |||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ | |||
{ | |||
offset: 1, | |||
color: "rgba(12, 245, 229, 1)" | |||
// opacity: 0.6 | |||
}, | |||
{ | |||
offset: 0, | |||
color: "rgba(12, 245, 229, 0)" | |||
// opacity: 1 | |||
} | |||
]) | |||
} | |||
}, | |||
data: valueArr | |||
}, | |||
{ | |||
name: "", | |||
type: "pictorialBar", | |||
itemStyle: { | |||
normal: { | |||
color: "rgba(12, 245, 229, 1)" | |||
} | |||
}, | |||
symbol: "rect", // 图形类型,这里是矩形 | |||
symbolRotate: 0, | |||
symbolSize: ["27", "3"], | |||
symbolPosition: "end", | |||
data: valueArr, | |||
z: 3 | |||
} | |||
] | |||
}; | |||
chart.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chart.resize(); | |||
}); | |||
}; | |||
onMounted(() => { | |||
getDetail(); | |||
}); | |||
</script> | |||
<style lang="scss" scoped> | |||
.roolcallDetail { | |||
.header { | |||
box-sizing: border-box; | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
width: 100%; | |||
height: 180px; | |||
padding: 10px 20px; | |||
background: #ffffff; | |||
.left { | |||
display: flex; | |||
align-items: center; | |||
justify-content: flex-end; | |||
width: 200px; | |||
height: 100%; | |||
} | |||
.right { | |||
width: calc(100% - 200px); | |||
.el-row { | |||
height: 100%; | |||
.el-col { | |||
height: 50px; | |||
line-height: 50px; | |||
text-align: center; | |||
} | |||
} | |||
} | |||
} | |||
.content { | |||
height: 550px; | |||
margin-top: 20px; | |||
background: #ffffff; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,123 @@ | |||
<!-- | |||
* @Description: 学生画像 | |||
* @Author: syy | |||
* @Date: 2024-7-24 | |||
--> | |||
<template> | |||
<div class="main-box"> | |||
<TreeFilter | |||
ref="treeFilter" | |||
label="personSetName" | |||
id="personSetId" | |||
width="300px" | |||
title="班级管理" | |||
:show-all="true" | |||
:request-api="userManageClassManageApi.page" | |||
@change="changeTreeFilter" | |||
> | |||
<template v-slot:label="{ row }"> | |||
<span class="custom-tree-node"> | |||
<span>{{ row.node.label }} {{ row.node.userName ? `(${row.node.userName})` : "" }}</span> | |||
</span> | |||
</template> | |||
</TreeFilter> | |||
<div class="table-box"> | |||
<ProTable ref="proTable" title="人员列表" :columns="columns" rowKey="id" :request-api="userManagePersonnelApi.page"> | |||
<!-- 表格操作栏 --> | |||
<template #operation="scope"> | |||
<el-space> | |||
<s-button link :opt="FormOptEnum.VIEW" @click="onView(scope.row)" /> | |||
</el-space> | |||
</template> | |||
</ProTable> | |||
</div> | |||
<!-- 预览头像 --> | |||
<el-dialog v-model="visible" title="查看头像" width="830px" :before-close="handleClose"> | |||
<div style="display: flex; align-items: center; justify-content: center"> | |||
<img style="max-width: 100%; max-height: 600px" class="detailpic" :src="faceUrl" alt="" /> | |||
</div> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script setup lang="tsx" name="ViolationPortrait"> | |||
import { userManagePersonnelApi,userManageClassManageApi} from "@/api"; | |||
import { FormOptEnum } from "@/enums"; | |||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface"; | |||
import TreeFilter from "@/components/TreeFilter/index.vue"; | |||
import { useRouter } from "vue-router"; | |||
const visible = ref(false); //是否显示人员表单 | |||
const faceUrl = ref(''); | |||
const router = useRouter(); | |||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) | |||
const proTable = ref<ProTableInstance>(); | |||
const treeFilter = ref<any>(); | |||
// 表格配置项 | |||
const columns: ColumnProps[] = [ | |||
{ | |||
prop: "faceUrl", | |||
label: "人脸", | |||
render: scope => { | |||
return ( | |||
<img src={scope.row.faces.length > 0 ? scope.row.faces[0].faceUrl : ''} onClick={() => viewHeadImage(scope)} style='width:50px;height:50px;' alt=''/> | |||
); | |||
} | |||
}, | |||
{ | |||
prop: "name", | |||
label: "姓名" | |||
}, | |||
{ | |||
prop: "personId", | |||
label: "人员ID" | |||
}, | |||
{ | |||
prop: "phone", | |||
label: "手机号" | |||
}, | |||
{ | |||
prop: "personSetName", | |||
label: "所属班级", | |||
}, | |||
{ prop: "operation", label: "操作", width: 250, fixed: "right" } | |||
]; | |||
const viewHeadImage = (scope: any) => { | |||
faceUrl.value = scope.row.faces[0].faceUrl; | |||
visible.value = true | |||
}; | |||
const handleClose = () => { | |||
visible.value = false; | |||
}; | |||
const onView = (row: any) => { | |||
router.push({ | |||
path:'/violation/portrait/detail', | |||
query: { | |||
personId: row.personId | |||
} | |||
}); | |||
}; | |||
/** 部门切换 */ | |||
const personSetId = ref<number | string>() | |||
function changeTreeFilter(val: number | string) { | |||
personSetId.value = val | |||
proTable.value!.pageable.pageNum = 1; | |||
proTable.value!.searchParam.personSetId = val; | |||
proTable.value!.search(); | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.table-box { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
.custom-tree-node { | |||
display: flex; | |||
flex: 1; | |||
align-items: center; | |||
justify-content: space-between; | |||
padding-right: 8px; | |||
font-size: 14px; | |||
} | |||
</style> |
@@ -0,0 +1,539 @@ | |||
<template> | |||
<div class="home"> | |||
<el-row :gutter="20"> | |||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12"> | |||
<div class="home-bg card"> | |||
<div class="home-bg-title"> | |||
<div></div> | |||
<div>学生性别</div> | |||
</div> | |||
<div class="home-bg-content"> | |||
<div ref="chart1" style="width: 100%; height: 100%"></div> | |||
</div> | |||
</div> | |||
</el-col> | |||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12"> | |||
<div class="home-bg card"> | |||
<div class="home-bg-title"> | |||
<div></div> | |||
<div>学生年龄</div> | |||
</div> | |||
<div class="home-bg-content"> | |||
<div ref="chart2" style="width: 100%; height: 100%"></div> | |||
</div> | |||
</div> | |||
</el-col> | |||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12"> | |||
<div class="home-bg card"> | |||
<div class="home-bg-title"> | |||
<div></div> | |||
<div>地理位置</div> | |||
</div> | |||
<div class="home-bg-content"> | |||
<div ref="chart5" style="width: 100%; height: 100%"></div> | |||
</div> | |||
</div> | |||
</el-col> | |||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12"> | |||
<div class="home-bg card"> | |||
<div class="home-bg-title"> | |||
<div></div> | |||
<div>标签属性</div> | |||
</div> | |||
<div class="home-bg-content handleBox"> | |||
<div ref="chart4" style="width: 100%; height: 100%"></div> | |||
</div> | |||
</div> | |||
</el-col> | |||
</el-row> | |||
</div> | |||
</template> | |||
<script setup lang="ts" name="home"> | |||
import * as echarts from "echarts"; | |||
import { portraitSummaryApi } from "@/api"; | |||
const chart1 = ref(null); | |||
const chart2 = ref(null); | |||
const chart4 = ref(null); | |||
const chart5 = ref(null); | |||
onMounted(() => { | |||
getDataChart(); | |||
}); | |||
const getDataChart = async () => { | |||
/* 获取学生性别 */ | |||
let sex: any = await portraitSummaryApi.getStudentSex(); | |||
getCharts1(sex.data.label, sex.data.value); | |||
/* 获取学生年龄 */ | |||
let age: any = await portraitSummaryApi.getStudentAge(); | |||
getCharts2(age.data.label, age.data.value); | |||
/* 获取属性标签 */ | |||
let attr: any = await portraitSummaryApi.getStudentAttr(); | |||
getCharts4(attr.data.label, attr.data.value); | |||
/* 获取地理位置 */ | |||
let address: any = await portraitSummaryApi.getAddress(); | |||
getCharts5(address.data.label, address.data.value); | |||
}; | |||
const handleDate = (label: any, value: any, key: any = "") => { | |||
let data: any = []; | |||
label.forEach((item: any, index: number) => { | |||
data.push({ | |||
value: value[index], | |||
name: item + key | |||
}); | |||
}); | |||
return data; | |||
}; | |||
/* 性別 */ | |||
function getCharts1(label: any, value: any) { | |||
const chart = echarts.init(chart1.value); | |||
const data = handleDate(label, value); | |||
const option = { | |||
tooltip: { | |||
confine: true | |||
}, | |||
legend: { | |||
show: true, | |||
right: "0%", | |||
top: "center", | |||
orient: "vertical", | |||
data: label | |||
}, | |||
series: [ | |||
{ | |||
type: "pie", | |||
radius: "80%", | |||
center: ["50%", "50%"], | |||
roseType: "area", | |||
label: { | |||
normal: { | |||
show: true | |||
}, | |||
emphasis: { | |||
show: true | |||
} | |||
}, | |||
itemStyle: { | |||
normal: { | |||
label: { | |||
show: true, | |||
// position:'inside', | |||
formatter: "{b} : {c}人" | |||
} | |||
}, | |||
labelLine: { | |||
show: true | |||
} | |||
}, | |||
data: data | |||
} | |||
] | |||
}; | |||
chart.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chart.resize(); | |||
}); | |||
} | |||
/* 年齡 */ | |||
function getCharts2(label: any, value: any) { | |||
const chartstation = echarts.init(chart2.value); | |||
const data = handleDate(label, value, "岁"); | |||
const labelArr: any = []; | |||
label.map((item: any) => labelArr.push(item + "岁")); | |||
const option = { | |||
legend: { | |||
show: true, | |||
right: "0%", | |||
top: "center", | |||
orient: "vertical", | |||
data: labelArr | |||
}, | |||
tooltip: { | |||
trigger: "item", | |||
formatter: "{a} <br/>{b} : {c} ({d}%)" | |||
}, | |||
calculable: true, | |||
series: [ | |||
{ | |||
name: "年龄分布", | |||
type: "pie", | |||
radius: [50, 140], | |||
roseType: "area", | |||
data: data, | |||
itemStyle: { | |||
normal: { | |||
label: { | |||
show: true, | |||
// position:'inside', | |||
formatter: "{b} : {c}人" | |||
} | |||
}, | |||
labelLine: { | |||
show: true | |||
} | |||
} | |||
} | |||
] | |||
}; | |||
chartstation.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chartstation.resize(); | |||
}); | |||
} | |||
/* 兴趣爱好 */ | |||
function getCharts4(categoryData: any, dataArr: any) { | |||
const chartstation = echarts.init(chart4.value); | |||
var spaceLength = 5, | |||
fixedData = [], | |||
end = 0, | |||
max = 300; | |||
// var categoryData = ys.超市品牌; | |||
let xhao: any = []; | |||
for (var i = 0; i < dataArr.length; i++) { | |||
xhao.push(i + 1); | |||
} | |||
var data1 = dataArr.map((item: any) => { | |||
fixedData.push(spaceLength); | |||
return item - spaceLength; | |||
}); | |||
if (categoryData.length < 5) { | |||
end = categoryData.length - 1; | |||
} else { | |||
end = 5; | |||
} | |||
const option = { | |||
xAxis: [{ show: false }], | |||
yAxis: [ | |||
{ | |||
splitLine: { | |||
show: false | |||
}, | |||
axisLine: { | |||
//y轴 | |||
show: false | |||
}, | |||
type: "category", | |||
inverse: true, | |||
axisTick: { | |||
show: false | |||
}, | |||
data: categoryData, | |||
axisLabel: { | |||
align: "left", | |||
margin: 170, | |||
formatter: function (value: any, index: any) { | |||
const num = ref<any>(""); | |||
var str = ""; | |||
num.value = xhao[categoryData.indexOf(value)]; | |||
if (index === 0) { | |||
str = "{no1|" + "} {num1|" + num.value + "} {title| " + value + "}"; | |||
} else if (index === 1) { | |||
str = "{no2|" + "} {num2|" + num.value + "} {title| " + value + "}"; | |||
} else if (index === 2) { | |||
str = "{no3|" + "} {num3|" + num.value + "} {title| " + value + "}"; | |||
} else { | |||
str = " {num|" + num.value + "} {title| " + value + "}"; | |||
} | |||
return str; | |||
}, | |||
rich: { | |||
num: { | |||
color: "#387ec1", | |||
backgroundColor: "#112b67", | |||
width: 10, | |||
height: 10, | |||
fontSize: 14, | |||
padding: [6, 5, 3, 5], | |||
align: "center", | |||
shadowColor: "#3374ba", | |||
borderColor: "#3374ba", | |||
borderWidth: 1 | |||
}, | |||
num1: { | |||
color: "#51aff8", | |||
backgroundColor: "#112b67", | |||
width: 10, | |||
height: 10, | |||
fontSize: 14, | |||
padding: [7, 5, 3, 5], | |||
align: "center", | |||
shadowColor: "#4db2ff", | |||
borderColor: "#4db2ff", | |||
borderWidth: 1 | |||
}, | |||
num2: { | |||
color: "#51aff8", | |||
backgroundColor: "#112b67", | |||
width: 10, | |||
height: 10, | |||
fontSize: 14, | |||
padding: [7, 5, 3, 5], | |||
align: "center", | |||
shadowColor: "#4db2ff", | |||
borderColor: "#4db2ff", | |||
borderWidth: 1 | |||
}, | |||
num3: { | |||
color: "#51aff8", | |||
backgroundColor: "#112b67", | |||
width: 10, | |||
height: 10, | |||
fontSize: 14, | |||
padding: [7, 5, 3, 5], | |||
align: "center", | |||
shadowColor: "#4db2ff", | |||
borderColor: "#4db2ff", | |||
borderWidth: 1 | |||
}, | |||
title: { | |||
color: "#000" | |||
} | |||
} | |||
} | |||
} | |||
], | |||
dataZoom: [ | |||
{ | |||
show: false, | |||
type: "slider", | |||
yAxisIndex: 0, | |||
zoomLock: false, | |||
width: 8, | |||
showDetail: false, | |||
startValue: 0, // 从头开始。 | |||
endValue: end, // 一次性展示五个。 | |||
borderWidth: 0, | |||
borderColor: "transparent", | |||
backgroundColor: "#343F4B", | |||
fillerColor: "#4291CE", | |||
showDataShadow: false, | |||
brushSelect: false, | |||
height: "88%", | |||
handleStyle: { | |||
color: "#4291CE" | |||
}, | |||
handleIcon: "path://M512 512m-320 0a320 320 0 1 0 640 0 320 320 0 1 0-640 0Z" | |||
} | |||
], | |||
grid: { | |||
right: "14%", | |||
left: "35%", | |||
top: "4%", | |||
bottom: "1%" | |||
}, | |||
series: [ | |||
{ | |||
name: "", | |||
type: "bar", | |||
barWidth: 12, // 柱子宽度 | |||
itemStyle: { | |||
normal: { | |||
color: function (params: any) { | |||
// var colorList = [ | |||
// '#7711AF', '#CF77FF', '#AE004F', '#F35872', '#FA7729', | |||
// '#FFC526', '#F8E71C', '#34ADAE', '#3DDFD2', '#A0FFFF' | |||
// ]; | |||
var colorListr = [ | |||
"#8A64B8", | |||
"#7575D3", | |||
"#5F85DD", | |||
"#6FABE8", | |||
"#7ED3F4", | |||
"#8CD8C0", | |||
"#9CDF8D", | |||
"#BFED6B", | |||
"#EAFE4F", | |||
"#FFFD47", | |||
"#FFEA55", | |||
"#FFCF63", | |||
"#FFA069", | |||
"#fce38a", | |||
"#eaffd0", | |||
"#95e1d3", | |||
"#e3fdfd", | |||
"#749f83", | |||
"#ca8622" | |||
]; | |||
return colorListr[params.dataIndex]; | |||
}, | |||
shadowBlur: 20, | |||
shadowColor: "rgba(40, 40, 40, 0.5)" | |||
} | |||
}, | |||
label: { | |||
formatter: function (parms: any) { | |||
return parms.value + spaceLength; | |||
}, | |||
show: true, | |||
position: "right", // 位置 | |||
color: "#1CD8A8", | |||
fontSize: 14, | |||
fontWeight: "bold", // 加粗 | |||
distance: 10 // 距离 | |||
}, | |||
data: data1 | |||
} | |||
] | |||
}; | |||
chartstation.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chartstation.resize(); | |||
}); | |||
} | |||
/* 标签、地理位置 */ | |||
function getCharts5(nameArr: any, valueArr: any) { | |||
const chartstation5 = echarts.init(chart5.value); | |||
const option = { | |||
grid: { | |||
top: "10%", | |||
left: "8%", | |||
right: "5%", | |||
bottom: "20%" | |||
}, | |||
tooltip: { | |||
trigger: "axis", | |||
backgroundColor: "#3A4667", | |||
borderColor: "#3A4667", | |||
textStyle: { | |||
color: "#fff" | |||
}, | |||
formatter: "{b} : {c}", | |||
axisPointer: { | |||
type: "cross", | |||
crossStyle: { | |||
color: "#999" | |||
} | |||
} | |||
}, | |||
legend: { | |||
icon: "rect", | |||
top: 10, | |||
right: 5, | |||
itemWidth: 10, | |||
itemHeight: 10, | |||
textStyle: { | |||
fontSize: 12, // 字体大小 | |||
color: "#B3CFFF" // 字体颜色 | |||
} | |||
}, | |||
xAxis: { | |||
type: "category", | |||
axisPointer: { | |||
type: "shadow" | |||
}, | |||
axisLine: { | |||
lineStyle: { | |||
color: "rgba(112, 138, 198, 1)" | |||
} | |||
}, | |||
axisLabel: { | |||
textStyle: { | |||
color: "#333", // x轴文本颜色 | |||
fontSize: 12 | |||
} | |||
// rotate:30, | |||
}, | |||
axisTick: { | |||
show: false | |||
}, | |||
data: nameArr | |||
}, | |||
yAxis: { | |||
type: "value", | |||
splitLine: { | |||
show: true, | |||
lineStyle: { | |||
color: "#162647", | |||
type: "solid" | |||
} | |||
}, | |||
axisLabel: { | |||
formatter: "{value}", | |||
textStyle: { | |||
color: "#333", // x轴文本颜色 | |||
fontSize: 12 | |||
} | |||
}, | |||
name: "", | |||
nameTextStyle: { | |||
color: "#B3CFFF", | |||
fontSize: 12 | |||
} | |||
}, | |||
series: [ | |||
{ | |||
type: "bar", | |||
name: "", | |||
barWidth: 16, | |||
emphasis: { | |||
itemStyle: { | |||
color: "#7fb7e9" | |||
}, | |||
label: { | |||
textStyle: { | |||
color: "#7fb7e9" | |||
} | |||
} | |||
}, | |||
label: { | |||
show: true, | |||
position: "top", | |||
textStyle: { | |||
color: "rgba(147, 157, 223, 1)", | |||
padding: [0, 10] | |||
} | |||
}, | |||
itemStyle: { | |||
normal: { | |||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ | |||
{ | |||
offset: 1, | |||
color: "rgba(147, 157, 223, 1)" | |||
}, | |||
{ | |||
offset: 0, | |||
color: "rgba(147, 157, 223, 0)" | |||
} | |||
]) | |||
} | |||
}, | |||
data: valueArr | |||
}, | |||
{ | |||
name: "", | |||
type: "pictorialBar", | |||
itemStyle: { | |||
normal: { | |||
color: "rgba(147, 157, 223, 1)" | |||
} | |||
}, | |||
symbolRotate: 0, | |||
symbolSize: ["16", "3"], | |||
symbolPosition: "end", | |||
data: valueArr, | |||
z: 3 | |||
} | |||
] | |||
}; | |||
chartstation5.setOption(option); | |||
window.addEventListener("resize", function () { | |||
chartstation5.resize(); | |||
}); | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../home/index.scss"; | |||
</style> |
@@ -36,17 +36,17 @@ onMounted(() => { | |||
}); | |||
function getDataChart() { | |||
setTimeout(async () => { | |||
await statistionApi.warnstatistion({}).then(res => { | |||
await statistionApi.warnstatistion({}).then((res: any) => { | |||
let { code, data } = res; | |||
if (code == 200) { | |||
let chartData1 = data.alarm.map(item => { | |||
let chartData1 = data.alarm.map((item: any) => { | |||
return { | |||
value: item.count, | |||
name: item.name | |||
}; | |||
}); | |||
let chartData2 = data.hand.map(item => { | |||
let chartData2 = data.hand.map((item: any) => { | |||
return { | |||
value: item.count, | |||
name: item.name | |||
@@ -68,7 +68,7 @@ function getDataChart() { | |||
}); | |||
}); | |||
} | |||
function getCharts1(data) { | |||
function getCharts1(data: any) { | |||
const chart = echarts.init(chart1.value); | |||
const option = { | |||
title: { | |||
@@ -105,7 +105,7 @@ function getCharts1(data) { | |||
chart.resize(); | |||
}); | |||
} | |||
function getCharts2(data) { | |||
function getCharts2(data: any) { | |||
const chartstation = echarts.init(chart2.value); | |||
const option = { | |||
title: { | |||
@@ -142,39 +142,23 @@ function getCharts2(data) { | |||
chartstation.resize(); | |||
}); | |||
} | |||
function flattenArray(arr) { | |||
const result = []; | |||
arr.forEach(item => { | |||
if (Array.isArray(item)) { | |||
result.push(...flattenArray(item)); | |||
} else { | |||
result.push(item); | |||
} | |||
}); | |||
return result; | |||
} | |||
function unique(arr) { | |||
return arr.filter((item, index, arr) => arr.indexOf(item, 0) === index); | |||
} | |||
function getWeekData() { | |||
setTimeout(async () => { | |||
await statistionApi.weekstatistion({}).then(res => { | |||
await statistionApi.weekstatistion({}).then((res: any) => { | |||
let { code, data } = res; | |||
if (code == 200) { | |||
// let chartData = data; | |||
let time = data.dataX; | |||
let chartData = data.dataY.map(item => { | |||
let chartData = data.dataY.map((item: any) => { | |||
return { | |||
data: item.data, | |||
name: item.name, | |||
type: "bar", | |||
barWidth: "12px", // 设置柱子粗细 | |||
itemStyle: { | |||
normal: { | |||
barBorderRadius: [30, 30, 0, 0] | |||
} | |||
borderRadius: [30, 30, 0, 0] | |||
} | |||
}; | |||
}); | |||
@@ -183,7 +167,7 @@ function getWeekData() { | |||
}); | |||
}); | |||
} | |||
function getCharts3(time, data) { | |||
function getCharts3(time: any, data: any) { | |||
const chartstation3 = echarts.init(chart3.value); | |||
const option = { | |||
title: { | |||
@@ -228,21 +212,20 @@ function getCharts3(time, data) { | |||
height: 50px; | |||
line-height: 50px; | |||
} | |||
.collapse-title { | |||
flex: 1 0 90%; | |||
order: 1; | |||
display: flex; | |||
flex: 1 0 90%; | |||
align-items: center; | |||
justify-content: space-between; | |||
// padding: 30px 0; | |||
order: 1; | |||
// padding: 30px 0; | |||
.titlemodel { | |||
margin-right: 40px; | |||
// display: inline-block; | |||
font-size: 18px; | |||
margin-right: 40px; | |||
} | |||
.btns { | |||
margin-left: 40px; | |||
} | |||
@@ -250,19 +233,22 @@ function getCharts3(time, data) { | |||
.collapse-content { | |||
padding: 20px; | |||
.contentinfo { | |||
margin-top: 20px; | |||
box-sizing: border-box; | |||
// display: flex; | |||
// justify-content: space-between; | |||
// align-items: center; | |||
padding: 20px 20px; | |||
box-sizing: border-box; | |||
padding: 20px; | |||
margin-top: 20px; | |||
// 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; | |||
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; | |||
} | |||
@@ -0,0 +1,101 @@ | |||
<!-- | |||
* @Description: 表单 | |||
* @Author: syy | |||
* @Date: 2023-12-15 15:45:59 | |||
--> | |||
<template> | |||
<div> | |||
<form-container v-model="visibleChamber" title="处理" form-size="600px" @close="onClose"> | |||
<el-form | |||
ref="SysDormitoryFormRef" | |||
:rules="rules" | |||
:disabled="warnHandleProps.disabled" | |||
:model="warnHandleProps.record" | |||
:hide-required-asterisk="warnHandleProps.disabled" | |||
label-width="auto" | |||
label-suffix=" :" | |||
> | |||
<div> | |||
<el-row :gutter="16"> | |||
<el-col :span="22"> | |||
<s-form-item label="处理意见" prop="remark"> | |||
<el-input v-model="warnHandleProps.record.remark" :autosize="{ minRows: 4 }" type="textarea" placeholder="请输入处理意见" /> | |||
</s-form-item> | |||
</el-col> | |||
</el-row> | |||
</div> | |||
</el-form> | |||
<template #footer> | |||
<el-button @click="onClose"> 取消 </el-button> | |||
<el-button v-show="!warnHandleProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button> | |||
</template> | |||
</form-container> | |||
</div> | |||
</template> | |||
<script setup lang="ts" name="warnHandleForm"> | |||
import { ref } from "vue"; | |||
import { ZJRQ, warnZJRQApi } from "@/api"; | |||
import { FormOptEnum } from "@/enums"; | |||
import { required } from "@/utils/formRules"; | |||
import { FormInstance } from "element-plus"; | |||
const visibleChamber = ref(false); //是否显示表单 | |||
// 表单参数 | |||
const warnHandleProps = reactive<FormProps.Base<ZJRQ.WarnInfo>>({ | |||
opt: FormOptEnum.ADD, | |||
record: {}, | |||
disabled: false | |||
}); | |||
// 表单验证规则 | |||
const rules = reactive({ | |||
remark: [required("请输入处理意见")] | |||
}); | |||
/** | |||
* 打开表单 | |||
* @param props 表单参数 | |||
*/ | |||
function onOpen(props: FormProps.Base<ZJRQ.WarnInfo>) { | |||
Object.assign(warnHandleProps, props); //合并参数 | |||
visibleChamber.value = true; //显示表单 | |||
warnHandleProps.record = props.record; | |||
} | |||
// 提交数据(新增/编辑) | |||
const SysDormitoryFormRef = ref<FormInstance>(); | |||
/** 提交表单 */ | |||
async function handleSubmit() { | |||
SysDormitoryFormRef.value?.validate(async valid => { | |||
if (!valid) return; //表单验证失败 | |||
let params: any = { | |||
id: warnHandleProps.record.id, | |||
remark: warnHandleProps.record.remark | |||
}; | |||
await warnZJRQApi | |||
.handWarn(params) | |||
.then((res: any) => { | |||
ElMessage({ | |||
message: res.msg, | |||
type: "success" | |||
}); | |||
warnHandleProps.successful!(); //调用父组件的successful方法 | |||
}) | |||
.finally(() => { | |||
onClose(); | |||
}); | |||
}); | |||
} | |||
/** 关闭表单*/ | |||
function onClose() { | |||
visibleChamber.value = false; | |||
} | |||
// 暴露给父组件的方法 | |||
defineExpose({ | |||
onOpen | |||
}); | |||
</script> | |||
<style lang="scss" scoped></style> |
@@ -34,7 +34,7 @@ | |||
<!-- 操作 --> | |||
<template #operation="scope"> | |||
<s-button link :opt="FormOptEnum.VIEW" @click="onDetail(scope.row)"> 详情 </s-button> | |||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)">处理</s-button> | |||
<s-button v-if="scope.row.warnHand == 0" link :opt="FormOptEnum.EDIT" @click="toHandle(FormOptEnum.EDIT, scope.row)">处理</s-button> | |||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `确定删除该预警吗?`)" /> | |||
</template> | |||
</ProTable> | |||
@@ -77,6 +77,16 @@ | |||
<!-- {{ detailData.videoUrl }} --> | |||
</div> | |||
</el-col> | |||
<el-col :span="24"> | |||
<div class="linebox"> | |||
处理意见: | |||
<span style="cursor: pointer" v-if="detailData.remark"> | |||
{{ detailData.remark }} | |||
</span> | |||
<span v-else>暂无数据</span> | |||
<!-- {{ detailData.videoUrl }} --> | |||
</div> | |||
</el-col> | |||
</el-row> | |||
</div> | |||
<template #footer> | |||
@@ -111,6 +121,8 @@ | |||
<img class="detailpic" :src="faceUrl" alt="" /> | |||
</div> | |||
</el-dialog> | |||
<!-- 处理告警 --> | |||
<handleForm ref="formRefH" /> | |||
</div> | |||
</template> | |||
@@ -120,6 +132,7 @@ import { warnZJRQApi } from "@/api"; | |||
import { ZJRQ } from "@/api/interface"; | |||
import { useHandleData } from "@/hooks/useHandleData"; | |||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface"; | |||
import handleForm from "./components/handleForm.vue"; | |||
import { useDictStore } from "@/stores/modules"; | |||
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums"; | |||
const faceUrl = ref(''); | |||
@@ -136,14 +149,18 @@ const visible = ref(false); //是否显示表单 | |||
onMounted(() => { | |||
getWarnTypeList(); | |||
}); | |||
const formRefH = ref<InstanceType<typeof handleForm> | null>(null); | |||
function toHandle(opt: FormOptEnum, record: {} | ZJRQ.WarnInfo= {}) { | |||
formRefH.value?.onOpen({ opt: opt, record: JSON.parse(JSON.stringify(record)), successful: RefreshTable }); | |||
} | |||
let warnOptions = ref([]); | |||
function getWarnTypeList() { | |||
setTimeout(async ()=> { | |||
await warnZJRQApi.warnType({}).then(res => { | |||
await warnZJRQApi.warnType({}).then((res:any) => { | |||
let { code, data } = res; | |||
if (code == 200) { | |||
warnOptions.value = data.map(item => { | |||
warnOptions.value = data.map((item:any) => { | |||
return { | |||
label: item.name, | |||
value: item.code | |||
@@ -299,14 +316,14 @@ let detailData = ref({}); | |||
function onDetail(row: any) { | |||
visible.value = true; | |||
setTimeout(async ()=> { | |||
await warnZJRQApi.detail({ id: row.id }).then(res => { | |||
let { code, data } = res; | |||
if (code == 200) { | |||
detailData.value = data | |||
} | |||
}); | |||
await warnZJRQApi.detail({ id: row.id }).then((res:any) => { | |||
let { code, data } = res; | |||
if (code == 200) { | |||
detailData.value = data | |||
} | |||
}); | |||
}) | |||
} | |||