dbsd_kczx/src/views/site/common/video/videojs/viewPage.vue

680 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="siteMain">
<div id="maxSite">
<a-layout>
<!-- 页头 -->
<headerPage/>
<!-- 主体部分 -->
<a-layout-content>
<div style="width:100%;height: 100%;margin-top: 1rem;" v-if="!isError">
<a-row :gutter="[16,16]">
<a-col :xs="{ span: 24 }" :sm="{ span: 19 }" :lg="{ span: 19 }">
<div>
<a-card class="videoCardMain" style="width:100%">
<template #title>
<span class="" style="border-radius: 5px;">
{{ ktangInfo.kcmc || ' ' }}
</span>
<span style="float: right;margin-left: 10px;background: #1c84c6;color: #fff;padding: 9px;border-radius: 5px;font-size: 16px;" @click="funpingjia(ktangInfo)">填写评价表</span>
<div style="font-size: 12px;">
{{ ktangInfo.zc || ' ' }}&nbsp;&nbsp;{{ ktangInfo.skjs || ' ' }}&nbsp;&nbsp;学分:{{ ktangInfo.xf || ' ' }}
&nbsp;&nbsp;课程性质:{{ ktangInfo.kcxz || ' ' }}
&nbsp;&nbsp;开课单位:{{ ktangInfo.kkdw || ' ' }}
&nbsp;&nbsp;节次:{{ ktangInfo.hh || ' ' }}
</div>
<div style="font-size: 12px;text-wrap: wrap;width: 100%;word-wrap: break-word;" hidden>
课程介绍:{{ ktangInfo?.zyJxdg?.kcjs }}
</div>
</template>
<div style="padding: 1rem;">
<a-row>
<a-col :span="24">
<div style="font-size: 16px;float: left;font-weight: 600;">{{ mainVideoCardBoxTitle || '' }}</div>
<div style="float: right;">
<span>本教室容量:</span><span style="font-size: 14px; color:#1c84c6 ;">{{ktangInfo?.jiaoshirongliang?.jsrl || ' '}}座位</span>
<span style="margin-left:15px;">选课人数:</span><span style="font-size: 14px; color:#1c84c6 ;">{{ ktangInfo.xkrs || ' ' }}</span>
<span style="margin-left:15px;">AI识别出勤人数: <a @click="handleZqrs(ktangInfo)">{{ ktangInfo?.kcDetectionDetailed?.num||'-' }}</a></span>
<span style="margin-left:15px;">
<a-tooltip placement="topRight">
<template #title>
<span>平台分别在三个时间节点上课后10分钟课中50分钟下课前10分钟抓取学生全景图片进行AI人流量识别当前显示的人数为最近一个时间的人数识别</span>
</template>
<!-- <Icon icon="ant-design:question-circle-outlined" /> -->
<span class="helpClass">?</span>
</a-tooltip>
</span>
</div>
</a-col>
<a-col :span="24">
<div style="width:100%;display: block;text-align: right;font-size:12px; color: #ccc;">
说明目前部分教室摄像头拍摄画面无法覆盖到全体学生AI识别出勤人数仅供参考
</div>
</a-col>
</a-row>
<a-row>
<a-col :span="fpxsShow=='1'?24:12">
<div v-if="fpxsShow != '1'" class="fptitle">教师近景</div>
<div style="height: 1px;background-color: black;width:100%;">&nbsp;</div>
<bVideo ref="mainVideo" videoId="mainVideo" :videoOption="{ autoplay: true }" @load-end="mainVideoLoadEnd"/>
</a-col>
<a-col :span="12" v-show="fpxsShow=='2' || fpxsShow=='3' || fpxsShow=='4'">
<div class="fptitle">学生全景</div>
<div style="height: 1px;background-color: black;width:100%;">&nbsp;</div>
<bVideo ref="main2Video" videoId="main2Video" :videoOption="{ autoplay: true }" @load-end="loadEnd"/>
</a-col>
<a-col :span="12" v-show="fpxsShow=='3' || fpxsShow=='4'">
<div class="fptitle">PPT</div>
<div style="height: 1px;background-color: black;width:100%;">&nbsp;</div>
<bVideo ref="main3Video" videoId="main3Video" :videoOption="{ autoplay: true }" @load-end="loadEnd"/>
</a-col>
<a-col :span="12" v-show="fpxsShow=='4'">
<div class="fptitle">教师全景</div>
<div style="height: 1px;background-color: black;width:100%;">&nbsp;</div>
<bVideo ref="main4Video" videoId="main4Video" :videoOption="{ autoplay: true }" @load-end="loadEnd"/>
</a-col>
</a-row>
<div class="jxDiv">
<a-space>
<div v-if="ktangInfo?.zyJxdg?.filePath">
教学大纲:
<a-button type="primary" size="small" style="margin-left:10px;" @click="openPdf(ktangInfo?.zyJxdg?.pdfPath)">预览</a-button>
<a-button type="primary" size="small" style="margin-left:10px;" @click="downloadFile(ktangInfo?.zyJxdg?.filePath)">下载</a-button>
</div>
<div v-if="ktangInfo?.zyJxdg?.filePath">
教学日历:
<a-button type="primary" size="small" style="margin-left:10px;" @click="openPdf(ktangInfo?.zyJxdg?.jxrlPdfPath)">预览</a-button>
<a-button type="primary" size="small" style="margin-left:10px;" @click="downloadFile(ktangInfo?.zyJxdg?.jxrlFilePath)">下载</a-button>
</div>
</a-space>
</div>
<div style="width: 100%;margin-top:10px;">
<a-textarea style="width: calc(100% - 7rem);height:120px;float: left;" v-model:value="model.notes" placeholder="您可以填写听课笔记"></a-textarea>
<div style="width: 7rem;height: 100%;float: right;">
<a-button type="primary" style="width: 7rem;height: 100%;" @click="openAllSuiBi">查看听课笔记</a-button>
<a-button type="primary" style="width: 7rem;height: 100%;margin-top: 10px;" @click="saveSuibi">保存听课笔记</a-button>
<a-button type="primary" style="width: 7rem;height: 100%;margin-top: 10px;" @click="handleBaocuo(ktangInfo)">报错</a-button>
</div>
</div>
</div>
</a-card>
</div>
</a-col>
<a-col :xs="{ span: 24 }" :sm="{ span: 5 }" :lg="{ span: 5 }">
<a-row>
<a-col :span="24" v-for="(item,index) of tableData" style="text-align: center;" :key="'col-'+index" @click="changeLive(item)">
<a-card :class="`handClass`">
<div>
<div style="margin-bottom: .5rem;">
<span class="smallTxt miniButton" :class="item.id == curentPlayerVideo.id && fpxsShow=='1'?'activte':''">
<i class="fa-solid fa-right-left"></i>{{ item?.xm }}
</span>
<!-- <span class="smallTxt miniButton miniButtonRight" v-show="!item.isShow" @click="(item.isShow = true,initVideo('other-'+item.id))"><i class="fa-solid fa-film"></i>显示缩略窗口</span> -->
<!-- <span class="smallTxt miniButton miniButtonRight" v-show="item.isShow" @click="item.isShow = false"><i class="fa-solid fa-film"></i>隐藏缩略窗口</span> -->
</div>
<div style="height: 2rem;">&nbsp;</div>
<!-- <div v-show="item.isShow" :key="'other-div-'+index">
<bVideo :key="'other-'+index" :ref="el=> bVideoRefs['other-'+item.id] = el" :videoId="'other-'+item.id" :src="item.pullUrl" :videoOption="{ autoplay: true, controls: false }" @load-end="loadEnd"/>
</div> -->
</div>
</a-card>
</a-col>
<a-col :span="24">
<a-card @click="changeJfpLive('2')" :class="`handClass`">
<div>
<div style="margin-bottom: .5rem;">
<span :class="`smallTxt miniButton ${fpxsShow=='2'?'activte':''}`" ><i class="fa-solid fa-film"></i>二分屏</span>
</div>
</div>
</a-card>
</a-col>
<a-col :span="24">
<a-card @click="changeJfpLive('3')" class="handClass">
<div >
<div style="margin-bottom: .5rem;">
<span :class="`smallTxt miniButton ${fpxsShow=='3'?'activte':''}`" ><i class="fa-solid fa-film"></i>三分屏</span>
</div>
</div>
</a-card>
</a-col>
<a-col :span="24">
<a-card @click="changeJfpLive('4')" class="handClass">
<div >
<div style="margin-bottom: .5rem;">
<span :class="`smallTxt miniButton ${fpxsShow=='4'?'activte':''}`" ><i class="fa-solid fa-film"></i>四分屏</span>
</div>
</div>
</a-card>
</a-col>
</a-row>
</a-col>
</a-row>
</div>
<div v-else style="height: 100%;display: flex;justify-content: center;align-items: center;">
<a-empty>
<template #description>
<span v-if="!playStatus">
没有找到直播间
</span>
<span v-if="playStatus">
{{ getSysConfig()?.videoPlayErrTitle || '直播间暂无内容' }}
</span>
</template>
<!-- <a-button type="primary" @="">关闭</a-button> -->
</a-empty>
</div>
</a-layout-content>
</a-layout>
</div>
</div>
<KcErrorreportIndexModal ref="kcErrorreportIndexModal"></KcErrorreportIndexModal>
<addModalPage ref="tingKeZuJiAddModal"/>
<a-modal title="查看听课笔记" width="800px" :visible="tkbjVisible" :okButtonProps="{ class: { 'jee-hidden': true } }" @cancel="() => tkbjVisible = false" cancelText="关闭">
<div style="white-space:normal; word-break:break-all;overflow:hidden;">
<a-table tableLayout="fixed" :dataSource="suibiList" :scroll="{ x: true }" :pagination="false">
<a-table-column width="160px" title="填写时间" key="createTime" data-index="createTime" />
<a-table-column title="内容" key="notes" data-index="notes" />
</a-table>
</div>
</a-modal>
<KcDetectionMainModal @register="registerModal" ></KcDetectionMainModal>
</template>
<script lang="ts" setup name="zhihuijiaoshiIndexPage">
import { defHttp } from '/@/utils/http/axios';
import { ref, reactive, onMounted, createVNode, h } from 'vue';
import headerPage from '/@/views/site/common/header.vue';
import bVideo from '/@/views/site/common/video/videojs/video.vue';
import addModalPage from '/@/views/site/tingKeZuJi/components/addModal.vue';
import { nextTick } from 'vue';
import { useRoute } from 'vue-router'
import { getUserId } from '/@/views/site/utils/index';
import KcErrorreportIndexModal from '/@/views/kc/kcErrorreport/components/KcErrorreportIndexZbModal.vue'
import videojs from "video.js";
import { getSysConfig } from '/@/views/site/utils/index';
import { useMessage } from '/@/hooks/web/useMessage';
import { baseApiUrl, getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import KcDetectionMainModal from '/@/views/kc/detection/components/KcDetectionMainModal.vue'
import { useModal } from '/@/components/Modal';
import { Modal } from 'ant-design-vue';
import { CloseOutlined } from '@ant-design/icons-vue';
const { createMessage, createInfoModal, createErrorModal } = useMessage();
const mainVideo = ref<any>();
const bVideoRefs = ref<any>([]);
const kcErrorreportIndexModal = ref();
const tingKeZuJiAddModal = ref<any>({});
const curentPlayerVideo = ref<any>({});
const kcCardBoxTitle = ref<any>('');
const ktangInfo = ref<any>({});
// const jxdgInfo = ref<any>({});
const mainVideoCardBoxTitle = ref<any>('');
const tableData = ref<Recordable>([])
const suibiList = ref<Recordable>([])
const isError = ref(false);
const tkbjVisible = ref(false);
const playStatus = ref(false);
const fpxsShow = ref<any>('1');
const model = reactive<Record<string, any>>({ notes:'' });
//注册model
const [registerModal, { openModal }] = useModal();
const route = useRoute();
enum Api {
list = '/jiaoshi/kcZhihuijiaoshi/list',
querySuibi = '/kc/kcKetangSuibi/list',
addSuibi = '/kc/kcKetangSuibi/add',
// editSuibi = '/kc/kcKetangSuibi/edit'
getKtangInfo = '/ktgl/kcKetangbiao/queryAllDataById',
savePlayLog = '/jiaoshi/kcZhihuijiaoshiAccessLog/savePlayLog',
changeAvyLiveByJsbhsApi = '/httpinterface/runAvyApiByJsbhs',
}
/**
* 列表接口
* @param params
*/
const list = (params) => defHttp.get({ url: Api.list, params });
const querySuibi = (params) => defHttp.get({ url: Api.querySuibi, params });
const addSuibi = (params) => defHttp.post({ url: Api.addSuibi, params });
const getKtangInfo = (params) => defHttp.get({ url: Api.getKtangInfo, params });
const savePlayLog = (params) => defHttp.post({ url: Api.savePlayLog, params, }, { isTransformResponse: false });
const changeAvyLiveByJsbhsApi = (params) => defHttp.get({ url: Api.changeAvyLiveByJsbhsApi, params,timeout: 9000000 });
// const editSuibi = (params) => defHttp.post({ url: Api.editSuibi, params });
onMounted(() => {
if(route.query.id){
model.notes = '';
//按教师编号加载数据,屏蔽掉无效的,不分页
list({ pageSize: -1, sfyx: '0', jsbh: route.query.id }).then(res => {
let list = (res?.records) ?? [];
console.log("🚀 ~ list ~ list:", list)
tableData.value = list;
tableData.value.forEach(x => x.isShow = true);//默认展开
let zjData = tableData.value.find(x => x.xm == '教师近景') || {};
nextTick(() => {
if(zjData){
changeLive(zjData);
}
savePlayLogFn(zjData);
calcPlayStatus(zjData);
tableData.value.forEach(x => x.isShow = false);//关闭
})
});
getSuibi();
getKcxx();
//记录播放日志
isError.value = false;
}else{
isError.value = true;
}
});
function handleBaocuo(item) {
kcErrorreportIndexModal.value.disableSubmit = false;
kcErrorreportIndexModal.value.add(item);
}
const listTkjlApi = (params) => defHttp.get({ url: '/kcTingke/kcTingke/findTingKeZuJiBytingketimeAndUserId', params });
function funpingjia(record) {
console.log('11111--------->',record)
console.log('222--------->',record.szkc)
var item = { ketangbiaoid: record.id };
let userid = getUserId();
listTkjlApi({ userid: userid, ketangbiaoid: item.ketangbiaoid }).then((res) => {
var list = res;
console.log(`🚀 ~ file: list.vue:106 ~ listTkjlApi ~ list:`, list);
if (list.length > 0) {
var score = list[0].score;
if (score) {
Modal.error({
icon: createVNode({}),
content: h('div', { style: 'height:200px;text-align:center;' }, [
h('icon', { style: 'font-size:80px;font-weight:600;color:red;' }, createVNode(CloseOutlined)),
h('p', { style: 'font-size:22px;font-weight:600;color:black;' }, '已经对此课程进行评价,不可重复评价!'),
]),
okText: 'OK',
width: '500px',
});
} else {
// tingKeZuJiAddModal.value.view(item);
tingKeZuJiAddModal.value.view({ ketangbiaoid: route.query.ktId,szkc:record.szkc })
}
} else {
// tingKeZuJiAddModal.value.view(item);
tingKeZuJiAddModal.value.view({ ketangbiaoid: route.query.ktId,szkc:record.szkc })
}
});
}
/**
* 子页加载完成后回调
* @param player
*/
function loadEnd(player){
nextTick(() => {
// player.on('play',() => {
// setTimeout(() => {
// player.pause();
// },2000);
// })
})
}
function initVideo(key){
nextTick(() => {
let ref = bVideoRefs.value[key];
ref.init();
})
}
function changeLive(item:any){
fpxsShow.value = '1'
let url = item.pullUrl
let mainVideo = document.querySelector<any>('#mainVideo');
// mainVideo?.player?.src([{ type:'application/x-mpegURL',src: 'http://127.0.0.1/live_hls/a.m3u8' }])
mainVideo?.player?.src([{ type:'application/x-mpegURL',src: url }])
mainVideoCardBoxTitle.value = item.jsmc+" "+item.xm;
console.log(`🚀 --------------------------------------------------------------------------------🚀`);
console.log(`🚀 ~ file: viewPage.vue:164 ~ changeLive ~ mainVideo?.player:`, mainVideo?.player);
console.log(`🚀 --------------------------------------------------------------------------------🚀`);
// let buttonEl = mainVideo?.player.el().querySelector('.changeDefinitionBtn');
//赋值原URL
// if(buttonEl){
// buttonEl.dataset.url = url;
// }
curentPlayerVideo.value = item;
// nextTick(() => {
//增加自动播放功能
//mainVideo?.player?.play();
// })
}
function changeJfpLive(type){
fpxsShow.value = type;
let item= tableData.value.find(x => x.xm == '教师近景') || {};
let url = item.pullUrl
let mainVideo = document.querySelector<any>('#mainVideo');
mainVideo?.player?.src([{ type:'application/x-mpegURL',src: url }])
if(type==2){
let item2= tableData.value.find(x => x.xm == '学生全景') || {};
let url2 = item2.pullUrl
let main2Video = document.querySelector<any>('#main2Video');
main2Video?.player?.src([{ type:'application/x-mpegURL',src: url2 }])
mainVideoCardBoxTitle.value = "二分屏";
}else if(type == 3){
let item2= tableData.value.find(x => x.xm == '学生全景') || {};
let url2 = item2.pullUrl
let main2Video = document.querySelector<any>('#main2Video');
main2Video?.player?.src([{ type:'application/x-mpegURL',src: url2 }])
mainVideoCardBoxTitle.value = "二分屏";
let item3 = tableData.value.find(x => x.xm == 'PPT') || {};
let url3 = item3.pullUrl
let main3Video = document.querySelector<any>('#main3Video');
main3Video?.player?.src([{ type:'application/x-mpegURL',src: url3 }])
mainVideoCardBoxTitle.value = "三分屏";
}else if(type == 4){
let item2= tableData.value.find(x => x.xm == '学生全景') || {};
let url2 = item2.pullUrl
let main2Video = document.querySelector<any>('#main2Video');
main2Video?.player?.src([{ type:'application/x-mpegURL',src: url2 }])
mainVideoCardBoxTitle.value = "二分屏";
let item3 = tableData.value.find(x => x.xm == 'PPT') || {};
let url3 = item3.pullUrl
let main3Video = document.querySelector<any>('#main3Video');
main3Video?.player?.src([{ type:'application/x-mpegURL',src: url3 }])
mainVideoCardBoxTitle.value = "三分屏";
let item4 = tableData.value.find(x => x.xm == '教师全景') || {};
let url4 = item4.pullUrl
let main4Video = document.querySelector<any>('#main4Video');
main4Video?.player?.src([{ type:'application/x-mpegURL',src: url4 }])
mainVideoCardBoxTitle.value = "四分屏";
}
}
function mainVideoLoadEnd(player){
setTimeout(() => {
setTimeout(() => {
player.muted(false);
//player.play();
},50);
},50);
}
function openLive(item: any){
changeAvyLiveByJsbhsApi({ jsbhs:item.jsbh, type: 1 }).then(res => {
let content = '';
res.forEach(x => {
// content += x.jsmc + "-" + x.xm
content += x.jsmc
let text = '';
if(x.resText){
if(x.resText.includes('ok')){
text = '播放失败,开启直播间成功,请稍后再试'
}else{
text = '播放失败,开启直播间失败,请联系管理员。'
}
}
content += " " + text + "<br/>"
});
// if(content.includes('开启直播间成功')){
// //延迟12秒
// setTimeout(() => {
// createInfoModal({ width:'50%', title: '结果',content })
// }, 12*1000);
// }else{
createInfoModal({ width:'50%', title: '结果',content })
// }
}).catch(e => {
console.error(e);
//loading.value = false;
//createInfoModal({title: '错误结果',content:e})
})
}
function getSuibi(){
//查询随笔
let param = {
userId: getUserId(),
ketangbiaoId: route.query.ktId,
column: 'createTime',
order: 'desc',
};
querySuibi(param).then(res => {
Object.assign(model, (res.records??[{}])[0]);
// model = (res.records??[{}])[0];
});
}
function getKcxx(){
//查询课程信息
//route.query.ktId
let param = {
id: route.query.ktId
}
getKtangInfo(param).then(res => {
console.log(res);
ktangInfo.value = res;
kcCardBoxTitle.value = res.kcmc;
// defHttp.get({ url: '/zyJxdg/zyJxdg/getKcjsJxdg', params: { rwbh: res.rwbh,xqxn: res.xnxq } }).then((res) => {
// if(res){
// jxdgInfo.value = res;
// }
// });
});
}
function savePlayLogFn(item){
savePlayLog({ playUrl: item.pullUrl, jxlId: item.jxlId, jxlName: item.jxlName, jsbh: route.query.id, jsmc: item.jsmc, ketangbiaoId: route.query.ktId,ketangbiaoName: ktangInfo.value.kcmc })
}
//计算播放状态
function calcPlayStatus(item){
if(item.pullUrl){
videojs.xhr.get(item.pullUrl,(err, resp, body) => {
if(err){
playStatus.value = false;
isError.value = true;
openLive(item);
}else{
playStatus.value = true;
isError.value = false;
}
})
}else {
console.log(1111111);
playStatus.value = true;
isError.value = true;
}
}
function saveSuibi(){
//addSuibi editSuibi
// if(model.value.id){
// editSuibi({...model.value, })
// }else{
addSuibi({ notes: model.notes, userId: getUserId(), ketangbiaoId: route.query.ktId })
// }
}
function openAllSuiBi(){
//打开弹窗
tkbjVisible.value = true;
//查询随笔
let param = {
userId: getUserId(),
ketangbiaoId: route.query.ktId,
column: 'createTime',
order: 'desc',
pageSize: '-1',
};
querySuibi(param).then(res => {
// model = (res.records??[{}])[0];
suibiList.value = res.records??[]
}).finally(() => {
//关闭等待
});
}
function calcPercentage(averageNum, zrs) {
if(averageNum === 0 || zrs === 0){
return '0%';
}else{
return (averageNum / zrs * 100).toFixed(2) + '%';
}
}
function openPdf(miniUrl) {
let url2 = getFileAccessHttpUrl(miniUrl)
let url = baseApiUrl+"/generic/web/viewer.html?file="+encodeURIComponent(url2);
window.open(url,"_blank");
}
function downloadFile(miniUrl) {
let url = getFileAccessHttpUrl(miniUrl);
window.open(url,"_blank");
}
function handleZqrs(record){
console.log(`🚀 ~ handleZqrs ~ record:`, record)
console.log(`🚀 ~ handleZqrs ~ zqrsList:`, record.zqrsList)
var a = record.detectionMain;
if(record.zqrsList){
a.detectionDetailedList = record.zqrsList
handleZqrsDetail(a);
}else{
createMessage.warning("暂无数据!")
}
}
function handleZqrsDetail(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: false,
});
}
</script>
<style lang="less" scoped>
.jxDiv {
padding-top: .5rem;
}
#siteMain {
// font-size: ;
height: 100%;
background: #f3f3f4;
#maxSite {
//最大宽度
max-width: 1170px;
//居中
margin: 0 auto;
.rowGutter{
margin-top: 1rem;
margin-bottom: 1rem;
}
.ant-layout-header {
color: #fff;
background: #1ab394;
}
.ant-layout-footer {
line-height: 1.5;
background: #FFF;
}
.ant-layout-sider {
color: #fff;
line-height: 120px;
background: #3ba0e9;
}
.ant-layout-content {
min-height: 120px;
color: #000;
/*line-height: 120px;*/
background: #f3f3f4;
}
}
}
/**暗黑模式特殊配色*/
[data-theme='dark'] #siteMain #maxSite {
.ant-layout-header, .ant-layout-footer {
background: #6aa0c7;
}
.ant-layout-content {
background: #107bcb;
}
.ant-layout-sider {
background: #3499ec;
}
}
.videoMax{
width: 25%;
}
.videoCardMain {
:deep(.ant-card-body) {
padding: 0;
}
}
/* 隐藏video 进度条 */
video::-webkit-media-controls-timeline {
display: none;
}
.smallTxt {
font-size: .8rem;
}
.activte {
background: #1ab394;
color: #FFF;
}
.miniButton {
float: left;
padding: 0.3rem;
border-radius: 5px;
}
.miniButtonRight {
float: right
}
.helpClass{
font-size: 16px;
font-weight: 700;
/* background: #1c84c6; */
color: #1c84c6;
border-radius: 5px;
padding: 1px 8px;
border: 1px #1c84c6 solid;
cursor: pointer;
}
.handClass{
cursor: pointer;
}
.fptitle{
background: black;
color: white;
font-weight: 700;
text-align: center;
}
</style>