2199 lines
62 KiB
Vue

<template>
<div class="headerNoise">
<div class="headerNoise-left" v-if="!viewAllShow">
<Card title="工作票列表">
<div class="content-box">
<div class="box-header">
<div
@click="onWorkTicketStateClick(item)"
:class="{ box_active: workTicketInfo.status == item.stateType }"
v-for="item in workTicketCountList"
:key="item.label"
>
<div>
<div>{{ item.label }}</div>
<div>{{ item.value }}</div>
</div>
<div></div>
</div>
</div>
<div class="box-search">
<el-cascader
v-model="workTicketInfo.typeId"
:options="workTicketTypeTreeList"
filterable
clearable
collapse-tags
placeholder="工作票类型"
:show-all-levels="false"
:props="{
multiple: false,
checkStrictly: true,
emitPath: false,
value: 'id',
label: 'typeName',
}"
></el-cascader>
<el-input
v-model="workTicketInfo.numberOrContent"
placeholder="施工区域或工作票编号"
></el-input>
<el-date-picker
v-model="workTicketInfo.constructionTime"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
/>
<div @click="onWorkTicketQuery" class="query">查询</div>
<div @click="onWorkTicketRefresh" class="refresh">刷新</div>
</div>
<el-scrollbar class="box-main">
<div
style="height: 100%"
infinite-scroll-distance="1"
:infinite-scroll-immediate="false"
v-infinite-scroll="load"
>
<div class="box-main_box" v-for="item in workTicketList" :key="item.id">
<div>
<div>
<div></div>
<div>{{ item.workTicketNumber }}</div>
<div></div>
</div>
<div @click="onViewDetail(item)">查看</div>
</div>
<div>
<div>使用单位</div>
<div>{{ item.applicantNames }}</div>
</div>
<div>
<div>使用人员</div>
<div>{{ item.operator }}</div>
</div>
<div>
<div>提交时间</div>
<div>{{ item.applicationTime }}</div>
</div>
<div
class="box-btn"
:class="{
wks_active: item.status == 1,
sgz_active: item.status == 2,
ztz_active: item.status == 3,
ywg_acitve: item.status == 4,
}"
>
{{ updateStatus(item.status) }}
</div>
</div>
</div>
<div class="notoDta" v-if="workTicketList.length == 0">
<img src="@/assets/images/noData.png" alt />
<p>暂无数据</p>
</div>
</el-scrollbar>
</div>
</Card>
</div>
<div class="headerNoise-right" :class="{ 'headerNoise-right_active': viewAllShow }">
<div class="h-card">
<div class="title">
<div><img src="@/assets/images/titleIcon.png" alt="" /></div>
<div class="titltText">
<i>作业过程管控</i>
</div>
<div class="right-icon" @click="onViewAllClick">
<el-icon @click.stop="onRefresh"><Refresh /></el-icon>
<div v-if="viewAllShow">
<el-select
@change="initPoliceCameraItemList"
v-model="policeCameraItemInfo.deviceState"
clearable
placeholder="请选择"
@visible-change="visibleChange"
>
<el-option
v-for="(item, index) in deviceStateList"
:key="index"
:label="item.title"
:value="item.id"
>
</el-option>
</el-select>
</div>
<div>{{ !viewAllShow ? "查看所有施工中的监控录像" : "返回首页" }}</div>
<div :class="{ goback: viewAllShow }"></div>
</div>
</div>
<div class="content-main">
<div
v-if="!viewAllShow"
class="content-left"
:class="{ 'content-left_active': isShowDetail }"
>
<div
@click="isShowDetail = true"
class="content-left_box"
v-if="!isShowDetail"
>
<div>工作票详情</div>
<div></div>
</div>
<div class="content-left_box1" v-else>
<div class="box1-header">
<div>当前正在执行的工作票</div>
<div @click="isShowDetail = false" class="shrink-box"></div>
</div>
<el-scrollbar class="scrollbar-height">
<div class="box1">
<div>工作票类型</div>
<div>{{ workTicketDetail.typeName }}</div>
</div>
<!-- <div class="box1">
<div>风险状态</div>
<div
class="state-box"
:class="{
gfx_active: workTicketDetail.riskType == 2
}"
>
{{ workTicketDetail.riskType == 2 ? "高风险" : "一般风险" }}
</div>
</div> -->
<div class="box1">
<div>工作票编号</div>
<div>{{ workTicketDetail.workTicketNumber }}</div>
</div>
<div class="box1">
<div>当前绑定监控</div>
<div>{{ itemListDevNameUp }}</div>
</div>
<div class="box1">
<div>施工区域</div>
<div>{{ workTicketDetail.constructionAreaNames }}</div>
</div>
<div class="box1">
<div>申请单位</div>
<div>{{ workTicketDetail.applicantNames }}</div>
</div>
<!-- 敦煌升级 -->
<!-- <div class="box1">
<div>班组</div>
<div>{{ workTicketDetail.teamNames }}</div>
</div> -->
<div class="box1">
<div>作业人员</div>
<div>{{ workTicketDetail.operator }}</div>
</div>
<div class="box1">
<div>施工时间</div>
<div>
{{ workTicketDetail.constructionTimeBegin }} -
{{ workTicketDetail.constructionTimeEnd }}
</div>
</div>
<div class="box1">
<div>申请时间</div>
<div>{{ workTicketDetail.applicationTime }}</div>
</div>
<div class="box1">
<div>安全措施</div>
<div>{{ workTicketDetail.safetyMeasure }}</div>
</div>
<div class="box1">
<div>作业内容</div>
<div>{{ workTicketDetail.workContent }}</div>
</div>
<div class="box1-header">
<div>当前正在执行的工作票</div>
</div>
<div class="box1">
<div>工作票附件</div>
<div class="content-img">
<template
v-if="
workTicketDetail.workTicketAttachment &&
workTicketDetail.workTicketAttachment.length > 0
"
>
<el-image
v-for="item in workTicketDetail.workTicketAttachment"
:key="item.url"
:src="BASEURL + '/image/' + item.url"
:preview-src-list="[BASEURL + '/image/' + item.url]"
>
</el-image>
</template>
</div>
</div>
<div class="box1">
<div>其他附件</div>
<div class="content-file_list">
<div v-for="item in workTicketDetail.otherAttachment" :key="item.url">
<div>
<div></div>
<div>{{ item.name }}</div>
</div>
<div
@click="
downloadFileBtn(BASEURL + '/image/' + item.url, item.name)
"
>
<div></div>
<div>下载</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
</div>
<div class="content-right" :class="{ 'content-right_active': isShowDetail }">
<el-scrollbar
:class="viewAllShow ? 'content-right_box-main1' : 'content-right_box-main'"
>
<!-- v-infinite-scroll="initPoliceCameraItemList" -->
<div
style="height: 100%"
infinite-scroll-distance="1"
:infinite-scroll-immediate="false"
:class="{
'content-right-top2': viewAllShow,
'content-right-top1': isShowDetail && !viewAllShow,
'content-right-top': !isShowDetail && !viewAllShow,
}"
>
<div
v-for="item in policeCameraItemList"
:key="item.itemId"
:data-key="item.key"
>
<div class="hls-video">
<!-- <HlsPlayer :src="'https://gcalic.v.myalicdn.com/gc/wgw05_1/index.m3u8'" :autoplay="true" :controls="true" /> -->
<!-- <HlsPlayer
:src="item.videoItemInfo && item.videoItemInfo.url ? item.videoItemInfo.url : ''"
:autoplay="true"
:controls="true"
/> -->
<div v-if="isWindows() && store.forceH5Play == 0" class="videoOverview" :id="`videoOverview${item.itemId}`">
<IscPlugin
:devList="[item]"
:itemId="item.itemId"
:type="'1x1'"
:equipmentDialog="equipmentDialog"
:visibleDialog="visibleDialog"
></IscPlugin>
</div>
<IscPlayer
v-else
:devList="[
{
...item,
status: workTicketDetail.status,
},
]"
:key="'player-' + item.itemId"
:playerId="'player-' + item.itemId"
/>
</div>
<div class="hls-video_title" @click="onEquipmentClick(item)">
<div>设备详情</div>
<div></div>
</div>
</div>
</div>
</el-scrollbar>
<el-pagination
class="pagerBox1"
background
:page-size="6"
style="justify-content: center"
@current-change="onCurrentChange"
layout="prev, pager, next"
:total="Number(policeCameraItemInfo.total)"
/>
<div class="content-right-bottom" v-if="!viewAllShow">
<div class="right-bottom_header">
<div>历史记录</div>
<div>
<el-date-picker
v-model="historySearchInfo.beginTime"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
/>
<!-- <el-input v-model="historySearchInfo.value" placeholder="设备名称"></el-input> -->
<div @click="getWorkTicketHistoryList" class="query">查询</div>
<div @click="onWorkTicketHistoryRefresh" class="refresh">刷新</div>
</div>
</div>
<div class="right-bottom_main">
<el-table
:data="workTicketHistoryList"
:height="184"
:tree-props="{ children: 'fileList' }"
row-key="id"
style="width: 100%"
>
<el-table-column align="center" prop="fileName" label="序号">
<template #default="{ row }">
<span v-if="row.fileType">
{{ row.fileName }}
</span>
<span v-else> 第{{ row.no }}次作业 </span>
</template>
</el-table-column>
<el-table-column
:width="300"
align="center"
prop="startTime"
label="总时间段"
>
<template #default="{ row }">
<span v-if="row.fileType">
{{ row.startTime ? row.startTime : "暂无" }} -
{{ row.endTime ? row.endTime : "暂无" }}
</span>
<span>
{{ row.begin ? row.begin : "暂无" }} -
{{ row.end ? row.end : "暂无" }}
</span>
</template>
</el-table-column>
<el-table-column align="center" prop="begin" label="总时长">
<template #default="{ row }">
<span class="time-diff" v-if="row.fileType">{{
itemDiffUp(row.startTime, row.endTime)
}}</span>
<span class="time-diff" v-else>{{
itemDiffUp(row.begin, row.end)
}}</span>
</template>
</el-table-column>
<el-table-column align="center" prop="fileType" label="操作">
<template #default="{ row }">
<div
@click="onViewrePlayClick(row)"
v-if="row.fileType"
class="viewreplay"
>
查看回放
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</div>
</div>
<el-dialog
:show-close="false"
:modal-append-to-body="false"
v-model="viewreplayDialog"
width="360px"
class="hls-video-dialog"
>
<div class="dialog_content">
<div class="audio-content" v-if="viewreplayInfo.fileType == 2">
<div>
<div>
<div></div>
<div>{{ viewreplayInfo.devName }}音频</div>
<div>{{ (viewreplayInfo.fileLen / (1024 * 1024)).toFixed(2) }}MB</div>
</div>
<div>
<div>{{ viewreplayInfo.fileTime }}</div>
<div>时长:{{ viewreplayInfo.duration }}</div>
</div>
</div>
<audio
ref="audioRefs"
:src="BASEURL + '/image/' + viewreplayInfo.fileUrl"
controls
loop
></audio>
<div
class="bodyworn_pause"
:class="{ bodyworn_play: viewreplayInfo.is_play }"
@click.stop="onAudioClick(viewreplayInfo)"
></div>
</div>
<div class="video-content" v-else>
<div class="card-img">
<video controls v-if="viewreplayInfo.fileType == 3">
<source
:src="BASEURL + '/image/' + viewreplayInfo.fileUrl"
type="video/mp4"
/>
<source
:src="BASEURL + '/image/' + viewreplayInfo.fileUrl"
type="video/webm"
/>
您的浏览器不支持 HTML5 video 标签。
</video>
<el-image
v-else-if="viewreplayInfo.fileType == 1"
:src="BASEURL + '/image/' + viewreplayInfo.fileUrl"
:preview-src-list="[BASEURL + '/image/' + viewreplayInfo.fileUrl]"
>
</el-image>
</div>
<template v-if="viewreplayInfo.fileType == 3">
<div class="card-flex">
<div class="card-num webkit-clamp_1">{{ viewreplayInfo.fileName }}</div>
<div class="card-num">
{{ (viewreplayInfo.fileLen / (1024 * 1024)).toFixed(2) }}MB
</div>
</div>
<div class="card-flex">
<div>{{ viewreplayInfo.startTime }}</div>
<p>至</p>
<div>{{ viewreplayInfo.endTime }}</div>
</div>
</template>
<template v-else-if="viewreplayInfo.fileType == 1">
<div class="card-flex">
<div>{{ viewreplayInfo.uploadTime }}</div>
</div>
</template>
</div>
</div>
</el-dialog>
<el-dialog
title="设备详情"
:modal="false"
:modal-append-to-body="false"
v-model="equipmentDialog"
width="941px"
class="equipment-dialog"
>
<div class="dialog_content">
<div class="content-left">
<div>
<div>设备名称</div>
<div>
{{ equipmentDetail.devName ? equipmentDetail.devName : "--" }}
</div>
</div>
<div>
<div>设备编号</div>
<div>{{ equipmentDetail.devSn ? equipmentDetail.devSn : "--" }}</div>
</div>
<div>
<div>所属项目</div>
<div>{{ projectSnName }}</div>
</div>
<div>
<div>设备状态</div>
<div
class="state-box"
:class="{ 'state-box_offline': equipmentDetail.deviceState != 1 }"
>
{{ equipmentDetail.deviceState == 1 ? "在线" : "离线" }}
</div>
</div>
<!-- <div>
<div>风险状态</div>
<div
class="state-box"
:class="{
gfx_active: workTicketDetail.riskType == 2
}"
>
{{ workTicketDetail.riskType == 2 ? "高风险" : "一般风险" }}
</div>
</div> -->
<div>
<div>工作票类型</div>
<div>
{{ workTicketDetail.typeName ? workTicketDetail.typeName : "--" }}
</div>
</div>
<div>
<div>工作票编号</div>
<div>
{{
workTicketDetail.workTicketNumber
? workTicketDetail.workTicketNumber
: "--"
}}
</div>
</div>
<div>
<div>施工区域</div>
<div>
{{
workTicketDetail.constructionAreaNames
? workTicketDetail.constructionAreaNames
: "--"
}}
</div>
</div>
<div>
<div>申请单位</div>
<div>
{{
workTicketDetail.applicantNames ? workTicketDetail.applicantNames : "--"
}}
</div>
</div>
<div>
<div>作业人员</div>
<div>
{{ workTicketDetail.operator ? workTicketDetail.operator : "--" }}
</div>
</div>
<div>
<div>施工时间</div>
<div>
{{ workTicketDetail.constructionTimeBegin }} -
{{ workTicketDetail.constructionTimeEnd }}
</div>
</div>
<div>
<div>申请时间</div>
<div>
{{
workTicketDetail.applicationTime ? workTicketDetail.applicationTime : "--"
}}
</div>
</div>
<div>
<div>安全措施</div>
<div>
{{ workTicketDetail.safetyMeasure ? workTicketDetail.safetyMeasure : "--" }}
</div>
</div>
<div>
<div>作业内容</div>
<div>
{{ workTicketDetail.workContent ? workTicketDetail.workContent : "--" }}
</div>
</div>
</div>
<div class="content-right">
<div class="content-right_header">附件内容</div>
<div class="box1">
<div>工作票附件</div>
<div class="content-img">
<template
v-if="
workTicketDetail.workTicketAttachment &&
workTicketDetail.workTicketAttachment.length > 0
"
>
<el-image
v-for="item in workTicketDetail.workTicketAttachment"
:key="item.url"
:src="BASEURL + '/image/' + item.url"
:preview-src-list="[BASEURL + '/image/' + item.url]"
>
</el-image>
</template>
</div>
</div>
<div class="box1">
<div>其他附件</div>
<div class="content-file_list">
<div v-for="item in workTicketDetail.otherAttachment" :key="item.url">
<div>
<div></div>
<div>{{ item.name }}</div>
</div>
<div @click="downloadFileBtn(BASEURL + '/image/' + item.url, item.name)">
<div></div>
<div>下载</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { GlobalStore } from "@/stores";
import { ref, onMounted, reactive, computed } from "vue";
import Card from "@/components/card.vue";
import HlsPlayer from "./components/HlsPlayer.vue";
import IscPlayer from "./components/iscPlayer.vue";
import IscPlugin from "./components/isc_plugin.vue";
import { ElMessage } from "element-plus";
import {
getWorkTicketCountWorkTicketApi,
getWorkTicketPageApi,
getWorkTicketTypeTreePageApi,
getWorkTicketQueryByIdApi,
getWorkTicketHistoryListApi,
getVideoItemInfoPoliceCameraItemApi,
getPoliceCameraItemPageApi,
selectAllProjectInfoList,
} from "@/api/modules/workTicket";
import { getUseProjectVideoConfigApi } from "@/api/modules/tower";
import { isWindows } from "@/utils/util";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
dayjs.extend(duration);
const BASEURL = import.meta.env.VITE_API_URL;
const store = GlobalStore();
const workTicketCountList = ref([
{
label: "施工中",
value: 0,
stateType: 2,
},
{
label: "全部",
value: 0,
stateType: "",
},
{
label: "未开始",
value: 0,
stateType: 1,
},
{
label: "暂停中",
value: 0,
stateType: 3,
},
{
label: "已完工",
value: 0,
stateType: 4,
},
]);
const getWorkTicketCountWorkTicket = (showLoading) => {
getWorkTicketCountWorkTicketApi(
{
projectSn: store.sn,
},
showLoading
).then((res) => {
if (res.code == 200) {
// workTicketCountList.value = res.result;
workTicketCountList.value = [
{
label: "施工中",
value: res.result.ing,
stateType: 2,
},
{
label: "全部",
stateType: "",
value:
res.result.ing + res.result.done + res.result.pause + res.result.notStarted,
},
{
label: "未开始",
value: res.result.notStarted,
stateType: 1,
},
{
label: "暂停中",
value: res.result.pause,
stateType: 3,
},
{
label: "已完工",
value: res.result.done,
stateType: 4,
},
];
}
});
};
const workTicketInfo = reactive({
status: 2,
typeId: "",
numberOrContent: "",
constructionTime: [],
pageNo: 1,
pageSize: 10,
total: 0,
});
const workTicketList = ref([]);
const onWorkTicketStateClick = (row) => {
workTicketInfo.status = row.stateType;
onWorkTicketQuery();
};
const onWorkTicketQuery = () => {
workTicketInfo.pageNo = 1;
workTicketList.value = [];
getWorkTicketPage();
};
const onWorkTicketRefresh = () => {
// workTicketInfo.status = "";
workTicketInfo.typeId = "";
workTicketInfo.numberOrContent = "";
workTicketInfo.constructionTime = [];
onWorkTicketQuery();
};
const getWorkTicketPage = () => {
const params = {
...workTicketInfo,
applicationTime_begin:
workTicketInfo.constructionTime.length > 0
? workTicketInfo.constructionTime[0]
: "",
applicationTime_end:
workTicketInfo.constructionTime.length > 0
? workTicketInfo.constructionTime[1]
: "",
};
delete params.constructionTime;
getWorkTicketPageApi({
...params,
projectSn: store.sn,
}).then((res) => {
if (res.code == 200) {
workTicketList.value = workTicketList.value.concat(res.result.records);
if (workTicketInfo.pageNo == 1) {
console.log("进来了", Number(res.result.total));
workTicketInfo.total = Number(res.result.total);
if (workTicketList.value.length > 0) {
onViewDetail(workTicketList.value[0], true);
}
}
}
});
};
const load = () => {
console.log(
"load",
workTicketInfo.pageNo,
workTicketInfo.pageSize,
workTicketInfo.total
);
if (workTicketInfo.pageNo > 0 && workTicketInfo.total == 0) return;
if (workTicketInfo.pageNo * workTicketInfo.pageSize > workTicketInfo.total)
return ElMessage.warning("没有更多数据了!");
workTicketInfo.pageNo += 1;
getWorkTicketPage();
};
onMounted(() => {
getWorkTicketPage();
getWorkTicketCountWorkTicket(false);
// 定时三十秒刷新
// setInterval(() => {
// // getWorkTicketCountWorkTicket(true);
// }, 30000);
getWorkTicketTypeTreePage();
getProjectVideoConfigList();
getSelectAllProjectInfoList();
});
const viewAllShow = ref(false);
const onViewAllClick = () => {
viewAllShow.value = !viewAllShow.value;
if (viewAllShow.value) {
policeCameraItemInfo.deviceState = 1;
policeCameraItemInfo.pageSize = policeCameraItemInfo.pageSize;
} else {
policeCameraItemInfo.deviceState = "";
policeCameraItemInfo.pageSize = policeCameraItemInfo.pageSize;
}
initPoliceCameraItemList();
};
const equipmentDialog = ref(false);
const equipmentDetail = ref({});
const visibleDialog = ref(false);
const visibleChange = (val) => {
visibleDialog.value = val;
};
// 查看设备详情
const onEquipmentClick = (row) => {
equipmentDetail.value = row;
getWorkTicketQueryById(equipmentDetail.value.workTicketId);
equipmentDialog.value = true;
};
const viewreplayInfo = ref({});
const viewreplayDialog = ref(false);
const audioRefs = ref(null);
// 查看回放
const onViewrePlayClick = (row) => {
console.log(111);
viewreplayInfo.value = {
...row,
is_play: false,
};
viewreplayDialog.value = true;
};
// 播放音频
const onAudioClick = () => {
if (viewreplayInfo.value.is_play) {
audioRefs.value.pause();
viewreplayInfo.value.is_play = false;
} else {
audioRefs.value.play();
viewreplayInfo.value.is_play = true;
}
};
const isShowDetail = ref(true);
const workTicketDetail = ref({
workTicketAttachment: [],
});
// 用来判断数据是否为json格式
const isJSON = (str) => {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
};
const timeInterval = ref(null);
// 查看详情
const onViewDetail = (row, flag) => {
if (row.id == workTicketDetail.value.id && flag != true) return;
workTicketDetail.value = row;
getWorkTicketQueryById(workTicketDetail.value.id);
getWorkTicketHistoryList();
initPoliceCameraItemList();
if (timeInterval.value) {
clearInterval(timeInterval.value);
}
timeInterval.value = setInterval(() => {
initPoliceCameraItemList();
}, 1000 * 60 * 30);
};
// 通过id查询作业票详情
const getWorkTicketQueryById = (id) => {
getWorkTicketQueryByIdApi({
id: id,
projectSn: store.sn,
}).then((res) => {
if (res.code == 200) {
workTicketDetail.value = {
...res.result,
workTicketAttachment:
isJSON(res.result.workTicketAttachment) &&
JSON.parse(res.result.workTicketAttachment) instanceof Array
? JSON.parse(res.result.workTicketAttachment)
: [],
safetyRiskAnalysis:
isJSON(res.result.safetyRiskAnalysis) &&
JSON.parse(res.result.safetyRiskAnalysis) instanceof Array
? JSON.parse(res.result.safetyRiskAnalysis)
: [],
otherAttachment:
isJSON(res.result.otherAttachment) &&
JSON.parse(res.result.otherAttachment) instanceof Array
? JSON.parse(res.result.otherAttachment)
: [],
};
}
});
};
const historySearchInfo = reactive({
beginTime: [],
value: "",
});
const workTicketHistoryList = ref([]);
const onWorkTicketHistoryRefresh = () => {
historySearchInfo.value = "";
historySearchInfo.beginTime = [];
getWorkTicketHistoryList();
};
// 列表查询工作票历史记录信息
const getWorkTicketHistoryList = () => {
const params = {
...historySearchInfo,
begin_end:
historySearchInfo.beginTime.length > 0 ? historySearchInfo.beginTime[0] : "",
end_start:
historySearchInfo.beginTime.length > 0 ? historySearchInfo.beginTime[1] : "",
};
delete params.beginTime;
getWorkTicketHistoryListApi({
...params,
workTicketId: workTicketDetail.value.id,
projectSn: store.sn,
}).then((res) => {
if (res.code == 200) {
const resultList = res.result.sort((a, b) => a.no - b.no);
workTicketHistoryList.value = resultList.map((item) => {
return {
...item,
};
});
}
});
};
const policeCameraItemInfo = reactive({
pageNo: 1,
pageSize: 6,
total: 0,
deviceState: "",
});
const deviceStateList = [
{
id: "",
title: "全部设备",
},
{
id: 1,
title: "施工中在线",
},
{
id: 2,
title: "施工中离线",
},
];
const policeCameraItemList = ref([]);
const initPoliceCameraItemList = () => {
policeCameraItemInfo.pageNo = 1;
policeCameraItemList.value = [];
getPoliceCameraItemPage();
};
const onCurrentChange = (val) => {
policeCameraItemInfo.pageNo = val;
policeCameraItemList.value = [];
getPoliceCameraItemPage();
};
// 分页列表查询执法记录仪设备列表信息
const getPoliceCameraItemPage = () => {
const params = {
...policeCameraItemInfo,
};
getPoliceCameraItemPageApi({
...params,
ticketId: viewAllShow.value ? "" : workTicketDetail.value.id,
// deviceState: viewAllShow.value ? 1 : "",
bindTicket: viewAllShow.value ? 1 : "",
projectSn: store.sn,
}).then(async (res) => {
if (res.code == 200) {
const records = res.result.records;
// await Promise.all(
// records.map(async item => {
// item.videoItemInfo = await getVideoItemInfoPoliceCameraItem(item);
// })
// );
console.log("policeCameraItemList", records);
policeCameraItemList.value = policeCameraItemList.value.concat(records);
if (policeCameraItemInfo.pageNo == 1) {
console.log("进来了", Number(res.result.total));
policeCameraItemInfo.total = Number(res.result.total);
}
}
});
};
const policeCameraItemLoad = () => {
console.log(
"load",
policeCameraItemInfo.pageNo,
policeCameraItemInfo.pageSize,
policeCameraItemInfo.total
);
if (policeCameraItemInfo.pageNo > 0 && policeCameraItemInfo.total == 0) return;
if (
policeCameraItemInfo.pageNo * policeCameraItemInfo.pageSize >
policeCameraItemInfo.total
)
return ElMessage.warning("没有更多数据了!");
policeCameraItemInfo.pageNo += 1;
getPoliceCameraItemPage();
};
const getVideoItemInfoPoliceCameraItem = async (row) => {
const res = await getVideoItemInfoPoliceCameraItemApi({
itemId: row.itemId,
projectSn: store.sn,
});
if (res.code != 200) return null;
return res.result.videoInfo;
};
const videoConfig = ref({
enableNotPlugin: 1,
});
//查询项目各类型的视频配置信息
const getProjectVideoConfigList = () => {
getUseProjectVideoConfigApi({
projectSn: store.sn,
}).then((res) => {
if (res.code == 200) {
if (res.result) {
videoConfig.value = res.result;
} else {
videoConfig.enableNotPlugin = 1;
}
}
});
};
const onRefresh = () => {
if (!viewAllShow.value) {
onWorkTicketQuery();
} else {
policeCameraItemList.value = [];
getPoliceCameraItemPage();
}
};
const statusList = [
{
value: 1,
label: "未开始",
},
{
value: 2,
label: "施工中",
},
{
value: 3,
label: "暂停中",
},
{
value: 4,
label: "已完工",
},
];
const updateStatus = computed(() => {
return (id) => {
const find = statusList.find((item) => item.value == id);
return find ? find.label : "--";
};
});
const itemListDevNameUp = computed(() => {
return workTicketDetail.value.itemList
? workTicketDetail.value.itemList.map((item) => item.devName).join("、")
: "";
});
const workTicketTypeTreeList = ref([]);
// 获取工作票类型
const getWorkTicketTypeTreePage = () => {
let data = {
projectSn: store.sn,
pageNo: 1,
pageSize: -1,
};
getWorkTicketTypeTreePageApi(data).then((res) => {
if (res.code == 200) {
workTicketTypeTreeList.value = res.result.records;
}
});
};
const itemDiffUp = computed(() => {
return (begin, end) => {
// 定义两个日期
const date = dayjs();
const date1 = dayjs(begin);
const date2 = end ? dayjs(end) : date;
// 计算两个日期之间的差异(默认单位是毫秒)
const diffInMilliseconds = date2.diff(date1);
const durationObj = dayjs.duration(diffInMilliseconds);
const hour = durationObj.hours();
const minute = durationObj.minutes();
const second = durationObj.seconds();
return `${hour}h${minute}min${second}s`;
};
});
// 下载附件
const downloadFileBtn = (url, name) => {
fetch(url)
.then((response) => {
// 处理响应
if (!response.ok) {
throw new Error("下载失败");
}
return response.blob();
})
.then((blob) => {
// 创建一个下载链接
const url = window.URL.createObjectURL(blob);
// 创建一个<a>元素
const link = document.createElement("a");
link.href = url;
link.download = name; // 指定下载文件的文件名
// 模拟点击下载链接
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 释放URL对象
window.URL.revokeObjectURL(url);
// 处理导出的文件
// 这里可以使用blob对象来获取导出的文件内容或者将其保存到本地
})
.catch((error) => {
// 处理错误
console.error(error);
});
};
const projectList = ref([]);
const getSelectAllProjectInfoList = () => {
selectAllProjectInfoList({
sn: store.sn,
}).then((res) => {
if (res.code == 200) {
projectList.value = [
{
projectName: "全部项目",
projectSn: store.sn,
},
...res.result,
];
}
});
};
const projectSnName = computed(() => {
const find = projectList.value.find(
(item) => item.projectSn == workTicketDetail.value.projectSn
);
return find ? find.projectName : "";
});
</script>
<style lang="scss" scoped>
.headerNoise {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
.headerNoise-left {
width: 400px;
height: 100%;
.content-box {
padding: 20px 10px;
height: calc(100% - 40px);
width: calc(100% - 20px);
.box-main {
width: 100%;
margin-top: 10px;
overflow-y: auto;
height: 550px;
.box-main_box:not(:first-child) {
margin-top: 20px;
}
.box-main_box {
width: calc(100% - 20px);
background: url("@/assets/images/cardImg.png") no-repeat;
background-size: 100% 100%;
padding: 10px;
position: relative;
.box-btn {
width: 53px;
height: 20px;
border-radius: 2px;
border: 1px solid #f1f1f1;
font-weight: 500;
font-size: 11px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 10px;
bottom: 10px;
}
.wks_active {
background-color: #727e93;
}
.sgz_active {
background-color: rgba(108, 184, 255, 0.5);
}
.ztz_active {
background-color: rgba(195, 129, 0, 0.5);
}
.ywg_acitve {
background-color: rgba(154, 245, 108, 0.5);
}
> div:not(:first-child) {
display: flex;
align-items: flex-start;
margin-top: 8px;
margin-left: 25px;
font-size: 14px;
> div:last-child {
color: #ffffff;
margin-left: 10px;
flex: 1;
word-break: break-all;
}
> div:first-child {
width: 56px;
color: #a1accb;
}
}
> div:first-child {
display: flex;
align-items: center;
justify-content: space-between;
> div:last-child {
font-size: 14px;
color: #5181f6;
cursor: pointer;
}
> div:first-child {
display: flex;
align-items: center;
> div:nth-child(1) {
width: 15px;
height: 15px;
background: url("@/assets/images/workTicket/index-icon6.png") no-repeat;
background-size: 100% 100%;
}
> div:not(:first-child) {
margin-left: 10px;
font-weight: 800;
font-size: 15px;
color: #65d7f9;
}
}
}
}
}
.box-search {
display: flex;
flex-wrap: wrap;
margin-top: 20px;
.el-input {
margin-left: 10px;
}
.el-input,
.el-cascader {
width: 147px;
border-image: linear-gradient(
180deg,
rgba(148, 160, 180, 1),
rgba(92, 111, 141, 1)
)
1 1;
}
:deep(.el-date-editor) {
width: 36%;
margin-top: 10px;
.el-range-input {
color: white;
// background-color: transparent;
}
}
:deep(.el-cascader),
:deep(.el-input),
:deep(.el-date-editor) {
height: 23px;
color: white;
background: linear-gradient(180deg, #112d59 0%, #112d59 100%);
}
:deep(.el-input__wrapper) {
background: linear-gradient(180deg, #112d59 0%, #112d59 100%);
}
.query {
margin-top: 10px;
margin-left: 20px;
width: 45px;
// height: 20px;
padding: 2px 12px;
background: url("@/assets/images/cardImg1.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: #ffffff;
}
.refresh {
margin-top: 10px;
margin-left: 8px;
width: 45px;
padding: 2px 12px;
// height: 20px;
background: url("@/assets/images/cardImg2.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: rgba(255, 255, 255, 0.6);
}
}
.box-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.box_active {
border: 1px solid #7effef;
}
> div:nth-child(1) {
width: 41%;
> div:last-child {
width: 38px;
height: 38px;
background: url("@/assets/images/workTicket/index-icon1.png") no-repeat;
background-size: 100% 100%;
}
}
> div:nth-child(2) {
width: 41%;
> div:last-child {
width: 38px;
height: 38px;
background: url("@/assets/images/workTicket/index-icon3.png") no-repeat;
background-size: 100% 100%;
}
}
> div:nth-child(3),
> div:nth-child(4),
> div:nth-child(5) {
margin-top: 20px;
width: 24%;
> div:last-child {
width: 30px;
height: 30px;
}
}
> div:nth-child(3) > div:last-child {
background: url("@/assets/images/workTicket/index-icon2.png") no-repeat;
background-size: 100% 100%;
}
> div:nth-child(4) > div:last-child {
background: url("@/assets/images/workTicket/index-icon4.png") no-repeat;
background-size: 100% 100%;
}
> div:nth-child(5) > div:last-child {
background: url("@/assets/images/workTicket/index-icon5.png") no-repeat;
background-size: 100% 100%;
}
> div {
display: flex;
align-items: center;
justify-content: space-between;
padding: 9px 10px 9px 16px;
border: 1px solid transparent;
background: linear-gradient(
180deg,
rgba(0, 170, 255, 0.3) 0%,
rgba(0, 170, 255, 0) 100%
);
> div:first-child {
display: flex;
flex-direction: column;
justify-content: space-between;
> div:last-child {
color: #7effef;
font-size: 20px;
}
> div:first-child {
font-size: 14px;
color: #ffffff;
}
}
}
}
}
:deep(.content) {
height: 94.5%;
}
}
.headerNoise-right {
margin-left: 20px;
width: 1460px;
height: 100%;
.h-card {
width: 100%;
height: 100%;
position: relative;
.title {
background: url("@/assets/images/titleImg.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
position: relative;
div {
font-size: 18px;
color: #ffffff;
img {
width: 110%;
margin-left: 30%;
}
i {
font-family: OPPOSansH;
font-style: normal;
}
}
.titltText {
margin: 0% 0% 6px 20px;
letter-spacing: 2px;
/* 设置文字间距为2像素 */
}
.right-icon {
display: flex;
align-items: center;
position: absolute;
right: 10px;
top: 10px;
cursor: pointer;
:deep(.el-input__wrapper) {
background-color: transparent;
}
:deep(.el-input__inner) {
height: 23px;
line-height: 23px;
background-color: transparent;
color: white;
}
> div:last-child {
width: 26px;
height: 26px;
background-image: url("@/assets/images/workTicket/index-icon8.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.goback {
background-image: url("@/assets/images/workTicket/goback.png") !important;
}
> div:not(:first-child) {
margin-left: 10px;
}
> div:nth-child(2) {
font-weight: bold;
font-size: 15px;
color: #65d7f9;
}
}
}
.content-main {
margin-top: 1%;
height: 94%;
display: flex;
.content-left_active {
width: 451px !important;
}
.content-left {
width: 80px;
height: 100%;
background: url("@/assets/images/cardImg.png") no-repeat;
background-size: 100% 100%;
.content-left_box1 {
padding: 10px 20px 20px;
// overflow: auto;
overflow: hidden;
height: calc(100% - 30px);
.scrollbar-height {
height: calc(100% - 30px);
}
.box1 {
display: flex;
align-items: flex-start;
padding: 10px 0;
font-size: 14px;
> div:last-child {
color: #ffffff;
margin-left: 10px;
flex: 1;
word-break: break-all;
}
> div:first-child {
width: 70px;
color: #a2a4af;
}
.content-file_list {
display: flex;
flex-direction: column;
> div {
display: flex;
align-items: center;
justify-content: space-between;
height: 30px;
padding: 0 16px 0 20px;
background: rgba(39, 88, 192, 0.2);
> div:first-child {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
> div:last-child {
width: 170px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
> div:first-child {
width: 16px;
height: 16px;
background: url("@/assets/images/workTicket/index-icon9.png")
no-repeat;
background-size: 100% 100%;
}
}
> div:last-child {
font-size: 14px;
color: #5181f6;
cursor: pointer;
> div:first-child {
width: 16px;
height: 16px;
background: url("@/assets/images/workTicket/index-icon10.png")
no-repeat;
background-size: 100% 100%;
}
}
> div {
display: flex;
align-items: center;
> div:not(:first-child) {
margin-left: 6px;
}
}
}
}
.content-img {
display: flex;
flex-wrap: wrap;
> .el-image:nth-child(n + 4) {
margin-top: 20px;
}
> .el-image:not(:nth-child(3n + 1)) {
margin-left: 20px;
}
> .el-image {
width: 80px;
height: 80px;
border-radius: 4px;
}
}
.state-box {
padding: 4px 8px;
min-width: 40px;
color: white;
background-color: #88cf65;
border-radius: 4px;
text-align: center;
flex: initial !important;
}
.gfx_active {
background-color: #ff0000;
color: #ffffff;
}
}
.box1-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
.shrink-box {
width: 20px;
height: 20px;
background: url("@/assets/images/workTicket/index-icon7.png") no-repeat;
background-size: 100% 100%;
cursor: pointer;
transform: rotate(180deg);
}
> div:first-child {
font-weight: 800;
font-size: 16px;
color: #ffffff;
}
}
}
.content-left_box {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
> div:last-child {
width: 20px;
height: 20px;
background: url("@/assets/images/workTicket/index-icon7.png") no-repeat;
background-size: 100% 100%;
cursor: pointer;
}
> div:first-child {
font-weight: 800;
font-size: 16px;
color: #ffffff;
writing-mode: vertical-rl;
cursor: pointer;
letter-spacing: 6px;
}
}
}
.content-right_active {
width: calc(100% - 451px) !important;
}
.content-right {
margin-left: 20px;
width: calc(100% - 100px);
.hls-video {
width: 100%;
height: 160px;
}
.videoOverview {
width: 100%;
height: 100%;
}
.hls-video_title {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 8px 0;
> div:last-child {
width: 20px;
height: 20px;
background: url("@/assets/images/workTicket/index-icon7.png") no-repeat;
background-size: 100% 100%;
}
> div:first-child {
font-size: 14px;
color: #ffffff;
}
}
.content-right_box-main {
height: calc(100% - 32px - 182px - 34px - 12px - 32px);
}
.content-right-top1 {
display: flex;
flex-wrap: wrap;
> div {
width: 31%;
height: 195px;
background: url("@/assets/images/cardImg.png") no-repeat;
background-size: 100% 100%;
}
> div:not(:nth-child(3n + 1)) {
margin-left: 10px;
}
> div:nth-child(n + 4) {
margin-top: 10px;
}
}
.content-right-top {
display: flex;
flex-wrap: wrap;
> div {
width: 24%;
height: 195px;
background: url("@/assets/images/cardImg.png") no-repeat;
background-size: 100% 100%;
}
> div:not(:nth-child(4n + 1)) {
margin-left: 10px;
}
> div:nth-child(n + 5) {
margin-top: 10px;
}
}
.content-right-bottom {
margin-top: 16px;
.right-bottom_main {
height: 182px;
padding: 12px 10px;
margin-top: 12px;
background: rgba(39, 88, 192, 0.1);
box-shadow: inset 0px 0px 2px 2px #051220;
border: 1px solid rgba(39, 88, 192, 0.6);
.el-table__expand-icon > .el-icon {
font-size: 14px;
color: #b2b8c2;
}
.time-diff {
padding: 3px 10px;
background: rgba(75, 141, 236, 0.1);
border: 1px solid #1560c5;
border-radius: 38px;
font-size: 11px;
color: #ffffff;
}
.viewreplay {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #65d7f9;
cursor: pointer;
}
:deep(.el-table) {
background-color: transparent;
th {
background-color: #003173;
border-color: transparent;
font-size: 16px;
}
tr,
td {
background-color: transparent;
border-color: transparent;
color: #ffffff;
}
.el-table__header-wrapper {
padding: 5px 0;
background: url("@/assets/images/cardImg.png") no-repeat;
background-size: 100% 100%;
}
.el-table__inner-wrapper::before {
background-color: transparent;
}
}
}
.right-bottom_header {
display: flex;
align-items: center;
justify-content: space-between;
> div:last-child {
display: flex;
align-items: center;
.el-input {
width: 147px;
}
:deep(.el-date-editor) {
width: 260px;
}
.el-input,
:deep(.el-date-editor) {
height: 23px;
margin-left: 10px;
color: white;
background: linear-gradient(180deg, #112d59 0%, #112d59 100%);
}
:deep(.el-input__wrapper) {
background: linear-gradient(180deg, #112d59 0%, #112d59 100%);
}
.query {
margin-left: 20px;
width: 50px;
// height: 20px;
padding: 2px 12px;
background: url("@/assets/images/cardImg1.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: #ffffff;
}
.refresh {
margin-left: 8px;
width: 50px;
padding: 2px 12px;
// height: 20px;
background: url("@/assets/images/cardImg2.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: rgba(255, 255, 255, 0.6);
}
}
> div:first-child {
font-weight: 800;
font-size: 16px;
color: #ffffff;
}
}
}
}
}
}
}
.headerNoise-right_active {
margin-left: 20px;
width: 100%;
.h-card .content-main .content-right_active {
width: 100% !important;
}
.content-right {
margin-left: 0 !important;
width: 100% !important;
}
.content-right_box-main1 {
height: calc(100% - 32px) !important;
}
.content-right-top2 {
height: 100% !important;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
> div {
width: 32% !important;
height: 350px;
background: url("@/assets/images/cardImg.png") no-repeat;
background-size: 100% 100%;
margin: 0 !important;
.hls-video {
height: 310px !important;
}
:deep(.sub-wnd) {
width: 100% !important;
}
}
> div:not(:nth-child(3n + 1)) {
margin-left: 20px !important;
}
> div:nth-child(n + 4) {
margin-top: 20px !important;
}
}
}
}
:deep(.equipment-dialog) {
top: 50%;
transform: translateY(-50%);
margin-top: 0;
background: rgba(7, 28, 49, 0.9);
border: 1px solid #405e97;
.el-dialog__header {
padding: 15px 20px 5px;
}
.el-dialog__title {
font-style: italic;
color: white;
}
.el-dialog__body {
padding: 0 !important;
}
.dialog_content {
padding: 0 20px 5px;
display: flex;
justify-content: space-between;
overflow-y: auto;
max-height: 85vh;
// flex-wrap: wrap;
.content-right {
width: 48%;
.content-right_header {
font-weight: 800;
font-size: 16px;
color: #ffffff;
}
.box1 {
display: flex;
align-items: flex-start;
padding: 10px 0;
font-size: 14px;
> div:last-child {
color: #ffffff;
margin-left: 10px;
flex: 1;
word-break: break-all;
}
> div:first-child {
width: 70px;
color: #a2a4af;
}
.content-file_list {
display: flex;
flex-direction: column;
> div {
display: flex;
align-items: center;
justify-content: space-between;
height: 30px;
padding: 0 16px 0 20px;
background: rgba(39, 88, 192, 0.2);
> div:first-child {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
> div:last-child {
width: 170px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
> div:first-child {
width: 16px;
height: 16px;
background: url("~@/assets/images/companyBigScreen/terminalOperation/index-icon9.png")
no-repeat;
background-size: 100% 100%;
}
}
> div:last-child {
font-size: 14px;
color: #5181f6;
cursor: pointer;
> div:first-child {
width: 16px;
height: 16px;
background: url("~@/assets/images/companyBigScreen/terminalOperation/index-icon10.png")
no-repeat;
background-size: 100% 100%;
}
}
> div {
display: flex;
align-items: center;
> div:not(:first-child) {
margin-left: 6px;
}
}
}
}
.content-img {
display: flex;
flex-wrap: wrap;
> .el-image:nth-child(n + 4) {
margin-top: 20px;
}
> .el-image:not(:nth-child(3n + 1)) {
margin-left: 20px;
}
> .el-image {
width: 80px;
height: 80px;
border-radius: 4px;
}
}
}
}
.content-left {
width: 49%;
> div {
display: flex;
align-items: flex-start;
font-size: 14px;
padding: 10px 0;
> div:last-child {
color: #ffffff;
margin-left: 10px;
flex: 1;
word-break: break-all;
}
> div:first-child {
width: 70px;
color: #a2a4af;
}
.state-box {
padding: 4px 8px;
min-width: 40px;
color: white;
background-color: #88cf65;
border-radius: 4px;
text-align: center;
flex: initial !important;
}
.state-box_offline {
background-color: #f7f7f7;
color: #272d45 !important;
}
.gfx_active {
background-color: #ff0000;
color: #ffffff;
}
}
}
}
}
:deep(.hls-video-dialog) {
top: 50%;
transform: translateY(-50%);
margin-top: 0;
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0 !important;
}
.dialog_content {
padding: 0 !important;
height: initial;
overflow: hidden;
}
.audio-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 13px 22px 13px 13px;
position: relative;
overflow: hidden;
audio {
opacity: 0;
position: absolute;
left: 0;
// right: -67%;
}
> div:last-child {
width: 26px;
height: 26px;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.bodyworn_pause {
background-image: url("@/assets/images/workTicket/bodyworn_pause.png");
}
.bodyworn_play {
background-image: url("@/assets/images/workTicket/bodyworn_play.png");
}
> div:first-child {
display: flex;
flex-direction: column;
justify-content: space-between;
> div:last-child {
display: flex;
align-items: center;
font-size: 14px;
color: #808080;
margin-top: 20px;
> div:not(:first-child) {
margin-left: 19px;
}
}
> div:first-child {
display: flex;
align-items: center;
font-size: 14px;
color: #272d45;
> div:not(:first-child) {
margin-left: 10px;
}
> div:first-child {
width: 19px;
height: 18px;
background-image: url("@/assets/images/workTicket/bodyworn_audio.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}
> div:last-child {
padding: 2px 10px;
border: 1px solid #f7af13;
font-size: 14px;
color: #f7af13;
}
}
}
}
.video-content {
// height: 260px;
padding-bottom: 20px;
.card-img {
width: 100%;
height: 180px;
img,
video,
.el-image {
width: 100%;
height: 100%;
}
}
.card-flex {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 15px 0;
color: #333333;
font-size: 15px;
p {
margin: 0;
}
.webkit-clamp_1 {
width: 50%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.card-num {
font-size: 14px;
color: #808080;
}
}
}
}
.notoDta {
top: 50%;
width: 60%;
left: 50%;
position: absolute;
transform: translate(-50%, -50%);
img {
width: 40%;
margin: 5% 30%;
}
p {
color: #fff;
font-size: calc(100vw * 14 / 1920);
margin: -4% 0%;
text-align: center;
}
}
</style>