1.智能设备 在线人员添加广播功能(聊天框);2.首页人员概况接口更改

This commit is contained in:
jiayu 2024-09-30 09:20:59 +08:00
parent 8bc763406f
commit 09d08d49ae
5 changed files with 429 additions and 30 deletions

View File

@ -29,6 +29,10 @@ export const getCompanyDataList = (params: {}) => {
export const getPersonTypeAndEduStatisticsApi = (params: {}) => {
return http.post(BASEURL + `/xmgl/workerInfo/selectPersonTypeAndEduStatistics`, params, { headers: { noLoading: true } });
};
//查询人员人数-新
export const getWorkerByNatureApi = (params: {}) => {
return http.post(BASEURL + `/xmgl/workerInfo/getWorkerByNature`, params, { headers: { noLoading: true } });
};
//查询今日作业人员趋势
export const getQueryTodayAttendanceTrendApi = (params: {}) => {
return http.get(BASEURL + `/xmgl/workerAttendance/queryTodayAttendanceTrend`, params, { headers: { noLoading: true } });

View File

@ -92,3 +92,15 @@ export const addSafeHatPositionFence = (params: {}) => {
export const getSafeyHatSessionApi = (params: {}) => {
return http.post(BASEURL + `/xmgl/projectApi/getSafeyHatSession`, params);
};
// 查询历史记录
export const getTaskMessageRecordApi = (params: {}) => {
return http.post(BASEURL + `/xmgl/task/messageRecord`, params, {
headers: { noLoading: true }
});
};
// 文字转语音并发送
export const getTaskTextToAudioApi = (params: {}) => {
return http.post(BASEURL + `/xmgl/task/textToAudio`, params);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

View File

@ -20,11 +20,21 @@
<div class="bottomPeopleNum">
<div style="display: flex; margin-left: 2%">
<div style="width: 5px; height: 5px; background: rgba(130, 251, 234, 1); border-radius: 10px; margin: 2%"></div>
<div><i>管理人员</i></div>
<div><i>人员数量</i></div>
</div>
<div class="peopleData">
<div class="peoImg"><img src="@/assets/images/comprehensiveManage/project3.png" alt="" /></div>
<div class="peoNum">
<div class="penName"></div>
<div class="penN"></div>
<div class="numData" v-for="item in personalList" :key="item.name">
<div class="text"><i>{{ item.name }}</i></div>
<div class="num">
<i style="color: #eea959">{{ item.num }}</i>
</div>
</div>
</div>
<!-- <div class="peoNum">
<div class="penName"></div>
<div class="penN"></div>
<div class="numData1 numData">
@ -56,7 +66,7 @@
>
</div>
</div>
</div>
</div> -->
</div>
</div>
</Card>
@ -68,7 +78,7 @@ import Card from "@/components/card.vue";
import { GlobalStore } from "@/stores";
import { COMPANY } from "@/config/config";
import { ref, onMounted, watch } from "vue";
import { getPersonTypeAndEduStatisticsApi } from "@/api/modules/labor";
import { getPersonTypeAndEduStatisticsApi, getWorkerByNatureApi } from "@/api/modules/labor";
const store = GlobalStore();
// ts
type Props = {
@ -84,6 +94,8 @@ const statisticsCount = ref({
presencecount: {}
} as any);
const personalList = ref([]);
watch(
() => props.statisticsCount,
newVal => {
@ -99,15 +111,21 @@ const attendancePerson = ref(0)
const toaltPerson = ref(0)
//
const getPersonList = async () => {
const res = await getPersonTypeAndEduStatisticsApi({
// const res = await getPersonTypeAndEduStatisticsApi({
// projectSn: store.sn
// });
// if (res.result) {
// console.log("+++++++++++++++++++++++++++++++++")
// console.log(res)
// presencePerson.value = res.result.personType.presencePerson;
// attendancePerson.value = res.result.personType.attendancePerson;
// toaltPerson.value = res.result.personType.toaltPerson;
// }
const res = await getWorkerByNatureApi({
projectSn: store.sn
});
if (res.result) {
console.log("+++++++++++++++++++++++++++++++++")
console.log(res)
presencePerson.value = res.result.personType.presencePerson;
attendancePerson.value = res.result.personType.attendancePerson;
toaltPerson.value = res.result.personType.toaltPerson;
personalList.value = res.result || [];
}
};
onMounted( async () => {
@ -175,15 +193,22 @@ onMounted( async () => {
height: 100%;
width: 70%;
position: relative;
display: flex;
.penName {
background: rgba(39, 88, 192, 0.6);
margin: 5% 5%;
margin: 6% 5%;
width: 90%;
position: absolute;
z-index: 0;
}
.penN {
height: 10px;
background: url("@/assets/images/comprehensiveManage/project8.png") no-repeat;
background-size: 100% 100%;
margin: 13% 5%;
margin: 0 5%;
margin-top: 24%;
width: 90%;
position: absolute;
}
.numData1 {
left: 5%;
@ -198,15 +223,18 @@ onMounted( async () => {
top: -10%;
}
.numData {
position: absolute;
// position: absolute;
height: 60%;
width: 30%;
// width: 30%;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.text {
margin-top: 18%;
margin-top: 15%;
color: #fff;
font-size: 16px;
z-index: 1;
}
.num {
font-size: 20px;

View File

@ -60,16 +60,25 @@
label: 'name'
}"
show-checkbox
check-on-click-node
default-expand-all
node-key="devSn"
ref="tree"
>
<!-- check-on-click-node -->
<template #default="{ node, data }">
<span class="custom-tree-node">
<img :src="personOn" v-if="data.online === 1" />
<img :src="personOff" v-if="data.online === 0" />
<span class="nodeName">{{ node.label }}</span>
<img
class="icon"
v-if="data.online === 1 && data.extUserId"
@click="handleBroadcast(data)"
src="@/assets/images/broadcast.png"
/>
<!-- <el-icon class="icon" v-if="laborType === 1 && data.extUserId" @click="handleBroadcast(data)"
><BellFilled
/></el-icon> -->
</span>
</template>
</el-tree>
@ -93,7 +102,7 @@
<el-form :inline="true" size="medium" :model="queryInfo" class="demo-form-inline">
<el-form-item>
<span style="color: #fff; margin-left: 25px; margin-right: 10px">人员名称</span>
<el-select filterable v-model="queryInfo.devSn" placeholder="请选择或搜索" @change="devChange" clearable>
<el-select filterable v-model="queryInfo.devSn" placeholder="请选择或搜索" @change="devChange">
<el-option
v-for="(item, index) in nameOptions"
:key="index"
@ -106,7 +115,17 @@
</el-form>
</div>
<div class="track-calendar">
<el-calendar v-model="dayValue" class="custom-hover">
<el-calendar v-model="dayValue" class="custom-hover" ref="calendar">
<template #header="{ date }">
<span>{{ date }}</span>
<el-button-group>
<el-button size="small" @click="selectDate('prev-month')"> 上个月 </el-button>
<el-button size="small" @click="selectDate('today')">今天</el-button>
<el-button size="small" :disabled="isDisabledNextMount" @click="selectDate('next-month')">
下个月
</el-button>
</el-button-group>
</template>
<template #dateCell="{ data }">
<!-- <span>{{ dealMyDate(data.day, data) }}</span> -->
<div :class="data.styleFlag == true ? 'preventClick' : ''" style="font-size: 14px">
@ -427,10 +446,55 @@
</div>
</div>
</div>
<div class="custom-dialog">
<el-dialog
:modal-append-to-body="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
title="广播内容"
v-model="dialogVisible"
width="667px"
>
<el-scrollbar style="height: 500px" ref="refScrollbar">
<div class="items" ref="innerRef">
<div :class="{ item: true, i_right: sendItYourself(item) }" v-for="item in messageList" :key="item.ctime">
<!-- <img src="@/assets/images/profile_photo.png" alt="" /> -->
<el-icon size="32"><UserFilled /></el-icon>
<div class="c_right">
<div class="info">
<div class="name">{{ item.send_user_name }}</div>
<div class="time">{{ dateFormat(item.ctime) }}</div>
</div>
<div
:class="{ 'box-warp': true, animation: currentPlayVideo == item.ctime }"
:style="{ width: Number(item.length) * 2 + 80 + 'px' }"
@click="playAudio(item)"
>
<div class="wifi-symbol">
<div class="wifi-circle first"></div>
<div class="wifi-circle second"></div>
<div class="wifi-circle third"></div>
</div>
<span class="audio-length"> {{ item.length == 0 ? 1 : item.length }}" </span>
</div>
</div>
</div>
</div>
<div class="message_no_data" v-if="!messageList.length">
<img src="@/assets/images/notoDataImg.png" />
</div>
</el-scrollbar>
<div class="message_go">
<el-input ref="messageIptRef" v-model="messageText" type="textarea" :rows="5" />
<el-button class="send_btn" type="primary" @click="sendMessage">发送</el-button>
</div>
</el-dialog>
</div>
</div>
</template>
<script setup lang="ts">
import moment from "moment";
// import { getWorkerInfoList } from "@/assets/js/api/laborPerson";
import {
getAlarmRecordInfoApi,
@ -453,7 +517,9 @@ import {
getSafeHatTypeTotalApi,
getVehiclePositionFence,
addVehiclePositionFence,
getRegionListApi
getRegionListApi,
getTaskMessageRecordApi,
getTaskTextToAudioApi
} from "@/api/modules/smartSafeHat";
// import carIcon from "@/assets/images/carPosition/carIcon.png";
import carOn from "@/assets/images/carPosition/carOn2.png";
@ -465,6 +531,7 @@ import startIcon from "@/assets/images/carPosition/startIcon.png";
import endIcon from "@/assets/images/carPosition/endIcon.png";
import personOn from "@/assets/images/carPosition/personOn.png";
import { GlobalStore } from "@/stores";
import { UserFilled, BellFilled } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
import Card from "@/components/card.vue";
@ -501,6 +568,19 @@ let isIndeterminateFence = ref(true);
let checked = ref(1);
let devName = ref("");
let locationList = ref([]);
const calendar = ref(null);
const refScrollbar = ref(null);
const innerRef = ref(null);
const messageIptRef = ref(null);
const userId = ref(0);
const dialogVisible = ref(false);
const messageList = ref([]);
const messageText = ref("");
const currentPlayVideo = ref("");
const audioDOM = ref(null);
interface RuleForm {
// fenceName: string
// areaRadius: number
@ -622,6 +702,84 @@ onMounted(async () => {
});
getRegionList();
});
const selectDate = val => {
if (!calendar.value) return;
calendar.value.selectDate(val);
};
const dateFormat = date => {
return moment.unix(date).format("YYYY-MM-DD HH:mm:ss");
};
const handleBroadcast = async data => {
userId.value = data.extUserId;
await getTaskMessageRecord();
dialogVisible.value = true;
setTimeout(() => {
messageIptRef.value?.focus();
}, 10);
};
const getTaskMessageRecord = async () => {
const res = await getTaskMessageRecordApi({
projectSn: store.sn,
userId: userId.value
});
if (res.code === 200) {
messageList.value = res.result || [];
messageList.value = messageList.value.reverse();
}
//
setTimeout(function () {
refScrollbar.value!.setScrollTop(innerRef.value!.clientHeight);
}, 10);
};
const sendMessage = async () => {
await getTaskTextToAudioApi({
content: messageText.value,
projectSn: store.sn,
userId: userId.value
});
setTimeout(() => {
messageText.value = "";
getTaskMessageRecord();
}, 100);
setTimeout(() => {
ElMessage.success("发送成功");
}, 200);
};
const playAudio = item => {
let audio = audioDOM.value;
if (audio != null) {
if (currentPlayVideo.value == item.ctime) {
audio.pause();
currentPlayVideo.value = -1;
return;
}
audio.pause();
} else {
audio = document.createElement("audio");
audioDOM.value = audio;
}
audio.src = item.url;
audio.onload = function () {
audio.play();
};
audio.play();
currentPlayVideo.value = item.ctime;
audio.addEventListener(
"ended",
function () {
currentPlayVideo.value = -1;
},
false
);
};
const sendItYourself = item => {
return item.send_user_name == "ZMML30";
};
const resetMapSize = () => {
console.log(666);
setTimeout(() => {
@ -636,6 +794,13 @@ onBeforeMount(() => {
});
});
//
const isDisabledNextMount = computed(() => {
const curMonth = formatMonthTime(new Date());
const checkedMount = formatMonthTime(dayValue.value);
return curMonth <= checkedMount;
});
watch(dayValue, newVal => {
// console.log(newVal, "");
if (newVal) {
@ -744,14 +909,16 @@ function areaRadiusChange() {
}
}
function latLongChange() {
console.info('change', addForm.value.lngLat)
let lng = addForm.value.lngLat.split(',')[0]
let lat = addForm.value.lngLat.split(',')[1]
console.info("change", addForm.value.lngLat);
let lng = addForm.value.lngLat.split(",")[0];
let lat = addForm.value.lngLat.split(",")[1];
if (lng && lat) {
locationList.value = [{
longitude: lng,
latitude: lat
}]
locationList.value = [
{
longitude: lng,
latitude: lat
}
];
drawCircle();
} else {
ElMessage({
@ -1410,7 +1577,6 @@ function pointSelectChange() {
console.log("当前devSn", alarmDevSn.value);
getProgressListData();
}
function fenceAllChange(val) {
clearFn();
let nameArr = fenceList.value.map(item => item.id);
@ -1708,10 +1874,12 @@ function initMap() {
}
console.log("您在 [ " + e.lnglat.getLng() + "," + e.lnglat.getLat() + " ] 的位置单击了地图!");
if (addForm.value.rangeType == 1) {
locationList.value = [{
longitude: e.lnglat.getLng(),
latitude: e.lnglat.getLat()
}];
locationList.value = [
{
longitude: e.lnglat.getLng(),
latitude: e.lnglat.getLat()
}
];
addForm.value.lngLat = e.lnglat.getLng() + "," + e.lnglat.getLat();
var lnglatXY = new AMap.LngLat(e.lnglat.getLng(), e.lnglat.getLat());
geocoder.getAddress(lnglatXY, function (status, result) {
@ -3169,11 +3337,20 @@ function echoPersonMarker(item) {
}
.custom-tree-node {
width: 100%;
display: flex;
align-items: center;
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
.nodeName {
flex: 1;
}
.icon {
margin: 0 12px;
cursor: pointer;
}
}
.nodeName {
overflow: hidden;
@ -3182,4 +3359,182 @@ function echoPersonMarker(item) {
display: inline-block;
color: #fff;
}
.items {
width: 100%;
overflow: hidden;
padding: 0 24px;
box-sizing: border-box;
.item {
width: 100%;
min-height: 40px;
height: auto;
font-size: 16px;
display: flex;
text-align: left;
margin: 8px 0;
&.i_right {
flex-flow: row-reverse;
.c_right {
align-items: flex-end;
.info {
flex-flow: row-reverse;
}
.box-warp {
background: #95ec69;
justify-content: flex-end;
&:hover {
background: #89d961;
}
.wifi-symbol {
transform: rotate(-45deg);
}
.audio-length {
right: 50px;
left: auto;
}
}
}
}
img {
width: 32px;
height: 32px;
object-fit: cover;
border-radius: 50%;
}
.c_right {
width: calc(100% - 40px);
display: flex;
flex-direction: column;
justify-content: center;
.info {
display: flex;
align-items: flex-end;
.name {
margin-left: 12px;
margin-right: 5px;
font-size: 20px;
color: #333;
}
.time {
font-size: 14px;
color: #999;
}
}
.text {
font-size: 14px;
}
}
}
}
.message_go {
width: 100%;
height: 146px;
border-top: 1px solid #e7e7e7;
display: flex;
align-items: center;
flex-direction: column;
margin-top: 20px;
position: relative;
.send_btn {
position: absolute;
right: 24px;
bottom: 12px;
}
}
.custom-dialog {
:deep(.el-dialog__body) {
padding: 0 !important;
border-top: 1px solid #e7e7e7;
}
:deep(.el-textarea__inner) {
box-shadow: none;
resize: none;
}
}
.box-warp {
background: #f5f5f5;
border-radius: 6px;
height: 40px;
margin: 6px 12px;
cursor: pointer;
position: relative;
display: flex;
&:hover {
background: #ebebeb;
}
.audio-length {
font-size: 14px;
line-height: 24px;
white-space: nowrap;
position: absolute;
left: 50px;
top: 8px;
}
}
.wifi-symbol {
margin: 0 20px;
width: 38px;
height: 38px;
box-sizing: border-box;
overflow: hidden;
transform: rotate(135deg);
position: relative;
}
.wifi-circle {
border: 4px solid #828487;
border-radius: 999px;
position: absolute;
}
.first {
width: 5px;
height: 5px;
background: #828487;
top: 32px;
left: 32px;
}
.second {
width: 16px;
height: 16px;
top: 25px;
left: 25px;
}
.third {
width: 26px;
height: 26px;
top: 18px;
left: 18px;
}
.animation .second {
animation: fadeInOut 1s infinite 0.2s;
}
.animation .third {
animation: fadeInOut 1s infinite 0.4s;
}
@keyframes fadeInOut {
0% {
opacity: 0; /*初始状态 透明度为0*/
}
100% {
opacity: 1; /*结尾状态 透明度为1*/
}
}
.message_no_data {
width: 100%;
height: 500px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
img {
width: 208px;
height: 208px;
-webkit-user-drag: none;
}
}
</style>