初始提交

This commit is contained in:
2025-09-04 16:35:14 +08:00
commit 5cd52c4d2c
735 changed files with 155784 additions and 0 deletions

View File

@ -0,0 +1,129 @@
<template>
<div style="padding-top: 13vw">
<TopNav navTitle="指令" :showRight="false" :showLeft="true" />
<div class="main_box">
<van-form @submit="onSubmit" class="form" :disabled="isSeeInfo ? false : true">
<van-field v-model="params.zlbt" name="zlbt" label="指令标题" placeholder="请输入指令标题"
:rules="[{ required: true, message: '请输入指令标题' }]" />
<van-field v-model="params.zlnr" name="zlnr" type="textarea" label="指令内容" placeholder="请输入指令内容"
:rules="[{ required: true, message: '请输入指令内容' }]" />
<van-field name="uploader" label="图片">
<template #input>
<van-uploader :max-count="1" :disabled="isSeeInfo ? false : true" v-model="wpTpIdList" multiple
:after-read="upLoadImg" />
</template>
</van-field>
<div style="margin: 16px" v-show="isSeeInfo">
<van-button round block type="primary" native-type="submit" :loading="isLoading" loading-type="spinner"
loading-text="提交中...">提交</van-button>
</div>
</van-form>
</div>
</div>
</template>
<script setup>
import TopNav from "../../../components/topNav.vue";
import { qcckGet, qcckPost } from "../../../api/qcckApi.js";
import ImageCompressor from "image-compressor.js";
import {
timeValidate
} from "../../../utils/tools.js";
import { ImagePreview } from "vant";
import { getMybbTodayNew } from "../../../api/common";
import { useRoute } from "vue-router";
import { onMounted, ref } from 'vue';
import { upImage } from "@/api/common";
import { hintToast } from "@/utils/tools";
import router from "@/router";
import { getDictList, setDict } from "../../../utils/dict";
const { D_QW_CJSBLX } = getDictList(
"D_QW_CJSBLX"
);
const route = useRoute();
const wpTpIdList = ref([]);
const isSeeInfo = ref(true);
const fileList = ref([])
const params = ref({
cjsbTpid: '',//图片
zllx: '10',
zlbt:'请求协助'
})
const baseUrl = ref("");
const workTips = ref();
// 上传图片
async function upLoadImg(file) {
file.message = "上传中...";
let fileBlob = await _compressImage(file.file);
let fileData = new File([fileBlob], fileBlob.name, { type: fileBlob.type });
const data = new FormData();
data.append("file", fileData);
upImage(data).then((res) => {
console.log(res, 'res');
file.status = "done";
file.message = "上传成功";
if (!fileList.value.includes(res)) fileList.value.push(res);
});
}
//压缩图片
const _compressImage = (file) => {
return new Promise((resolve, reject) => {
new ImageCompressor(file, {
quality: 0.6, //压缩质量
success(res) {
resolve(res);
},
error(e) {
reject(e);
},
});
});
};
const onSubmit = () => {
let { lng, lat, zbly } = getLocation();
params.value.jd = lng;
params.value.wd = lat;
if (fileList.value.length > 0) params.value.fjId = fileList.value.join(',');
qcckPost(params.value, "/mosty-yjzl/tbZl/addZl").then(res => {
hintToast('添加成功')
router.back()
})
}
const getImageUrl = (item) => {
return new Promise((ok) => {
qcckGet({}, `/mosty-base/minio/file/download/${item}`).then(res => {
ok(res.url);
})
});
}
onMounted(async () => {
if (route.query.item) {
isSeeInfo.value = false;
params.value = JSON.parse(route.query.item);
let imgId = params.value.cjsbTpid.split(',');
for (let i = 0; i < imgId.length; i++) {
const el = imgId[i];
fileList.value.push({ url: await getImageUrl(el) });
}
}
})
</script>
<style lang="scss" scoped>
.main_box {
padding: 10px;
box-sizing: border-box;
}
::v-deep .van-radio {
margin-bottom: 4px;
}
::v-deep .van-radio__label--disabled {
color: #000 !important;
}
::v-deep .van-field__label {
color: #000 !important;
}
</style>

View File

@ -0,0 +1,560 @@
<template>
<div id="scrollDIV" class="message_list_box" style="height: 100%">
<div v-for="(item, index) in list" :key="index" id="message_item_box" class="message_item">
<!-- 对方的消息 -->
<div class="item_left" v-if="item.type == 1">
<div class="df_avator">{{ item.name.substring(0, 1) }}</div>
&nbsp;
<div>
<div style="margin-bottom: 2vw">
<span>{{ item.time }}</span>&emsp;<span>{{ item.name }}</span>
</div>
<!-- 图片文件 -->
<van-image width="100px" height="100px" fit="cover" :src="baseUrl + item.fjid"
v-if="item.fjid && item.fileFormat == 'png'" @click="onClickImg(item.fjid)">
<template v-slot:loading>
<van-loading type="spinner" size="20" />
</template>
</van-image>
<!-- 文档文件 -->
<div class="left_message" v-if="item.fileFormat == 'file'">
<a style="text-decoration: underline" :href="baseUrl + item.fjid">{{
item.text
}}</a>
</div>
<!-- 消息 -->
<div class="left_message" v-if="item.text && !item.fjid">
{{ item.text }}
</div>
<!-- 语音消息 -->
<div class="audio-detail-msg" v-if="item.fileFormat == 'mp3'">
<div class="audio-style" style="margin-right: 8px"
:class="{ 'add-animation': isPlay && audioId == item.fjid }" :style="{
width: handleAudioStyleWidth(item.yysc ? item.yysc : 1),
}" @click="playAudio(item.fjid, item.yysc ? item.yysc : 1)">
<div class="small"></div>
<div class="middle"></div>
<div class="large"></div>
</div>
<div class="duration-seconds">{{ item.yysc ? item.yysc : 1 }}s</div>
<audio :id="item.fjid" style="display: none"></audio>
</div>
<!-- 视频 -->
<div class="videos_box" v-if="item.fileFormat == 'mp4'">
<video :src="item.videoUrl" class="video_image"></video>
<!-- <img :src="`${baseUrl}${item.sptpid}`" alt="" class="video_image" /> -->
<!-- <div class="yysc left_video_time">{{ item.yysc }}s</div> -->
<img class="play_box left_video_play" src="../../../../assets/images/play.png" alt=""
@click="onClickVideo(item.videoUrl)" />
</div>
</div>
</div>
<!-- 自己的消息 -->
<div class="item_right" v-if="item.type == 2">
<!-- 图片文件 -->
<div>
<div style="margin-bottom: 2vw">{{ item.time }}</div>
<van-image width="100px" height="100px" fit="cover" :src="baseUrl + item.fjid"
v-if="item.fjid && item.fileFormat == 'png'" @click="onClickImg(item.fjid)">
<template v-slot:loading>
<van-loading type="spinner" size="20" />
</template>
</van-image>
<!-- 文档文件 -->
<div class="right_message" v-if="item.fileFormat == 'file'">
<a style="text-decoration: underline" @click="getPreviewText(item)">{{ item.text }}</a>
</div>
<!-- 消息 -->
<div class="right_message" v-if="item.text && !item.fjid">
{{ item.text }}
</div>
<!-- 语音消息 -->
<div class="audio-detail-msg msgRight_box" v-if="item.fileFormat == 'mp3'">
<div class="duration-seconds">{{ item.yysc ? item.yysc : 1 }}s</div>
<div style="margin-left: 8px" class="audio-style msgRight"
:class="{ 'add-animation': isPlay && audioId == item.fjid }" :style="{
width: handleAudioStyleWidth(item.yysc ? item.yysc : 1),
}" @click="playAudio(item.fjid, item.yysc ? item.yysc : 1)">
<div class="small"></div>
<div class="middle"></div>
<div class="large"></div>
</div>
<audio :id="item.fjid" style="display: none"></audio>
</div>
<!-- 视频 -->
<div class="videos_box" v-if="item.fileFormat == 'mp4'">
<video :src="item.videoUrl" class="video_image"></video>
<!-- <img :src="`${baseUrl}${item.sptpid}`" alt="" class="video_image" /> -->
<!-- <div class="yysc right_video_time">{{ item.yysc }}s</div> -->
<img class="play_box right_video_play" src="../../../../assets/images/play.png" alt=""
@click="onClickVideo(item.videoUrl)" />
</div>
</div>
&nbsp;
<div class="df_avator">{{ item.name.substring(0, 1) }}</div>
</div>
</div>
<Iframe :url="fillFrams" :isPreviw="isPreviw" @closePre="isPreviw = false"></Iframe>
</div>
</template>
<script setup>
import emitter from "@/utils/eventBus";
import {
ref,
onMounted,
defineProps,
defineEmits,
watch,
onBeforeUnmount,
} from "vue";
import { baseUrl2 } from "../../../../utils/request.js";
import { ImagePreview } from "vant";
import { dataQc } from "../../../../utils/tools.js";
import { getFjInfo } from "../../../../api/rwzx.js";
import Iframe from "../../../../assets/html/filePreview.vue";
import axios from "axios";
const props = defineProps({
list: {
type: Boolean,
default: [],
}, //消息数据
mesHeight: Number, //盒子高度
});
const baseUrl = `${baseUrl2}/mosty-api/mosty-base/minio/image/download/`;
const emits = defineEmits(["update:scroll", "click:video"]);
const isPlay = ref(false); //开始语音播放动画
const playAudioTimer = ref(null); //语音播放定时器
const audioId = ref(""); //被选中播放的音频ID
const divHeight = ref(0);
const timeValue = ref(null);
const fillFrams = ref(null);
const isPreviw = ref(false);
watch(
() => props.list,
(newVal) => {
newVal.forEach((item) => {
if (item.fileFormat == "mp3" || item.fileFormat == "mp4") {
axios
.get(
`${baseUrl2}/mosty-api/mosty-base/minio/file/download/${item.fjid}`,
{
params: {},
}
)
.then((res) => {
if (res) {
let url = res.data.data.url.replace(
"http://80.93.7.13:9009",
"/zyminio"
);
// let url = res.data.data.url;
if (item.fileFormat == "mp3") {
let au = document.getElementById(item.fjid);
au.src = `${baseUrl2 + url}`;
} else {
item.videoUrl = `${baseUrl2 + url}`;
}
}
});
}
});
},
{ immediate: true, deep: true }
);
onMounted(() => {
dataQc(props.list, "id");
scrollDIV();
timeValue.value = setInterval(() => {
if (divHeight.value <= 50) scrollDIV();
}, 1.5e3);
scrollBotton();
});
onBeforeUnmount(() => {
clearTimeout(playAudioTimer.value);
playAudioTimer.value = null;
clearTimeout(timeValue.value);
timeValue.value = null;
});
// 获取预览文件
function getPreviewText(item) {
getFjInfo(item.fjid).then((res) => {
let url = res.url; //要预览文件的访问地址
let Base64Url = Base64.encode(url);
fillFrams.value =
"http://10.64.201.126:8012/onlinePreview?url=" +
encodeURIComponent(Base64Url);
isPreviw.value = true;
});
}
//点击视频
function onClickVideo(url) {
emits("click:video", url);
}
//播放语音
function playAudio(id, yysc) {
let au = document.getElementById(id);
audioId.value = id;
if (isPlay.value) {
isPlay.value = false;
au.pause();
return;
}
isPlay.value = true;
playAudioTimer.value = setTimeout(() => {
isPlay.value = false;
}, parseInt(yysc * 1000));
au.play();
}
// 设置语音条宽度样式
function handleAudioStyleWidth(yycs) {
if (yycs === 1) {
return "38px";
} else if (yycs > 1 && yycs < 20) {
return `${38 + (yycs / 10) * 36}px`;
} else if (yycs >= 20) {
return `${106.39 + (yycs / 10) * 18.935}px`;
}
}
//预览图片
function onClickImg(fjid) {
let url = baseUrl + fjid;
ImagePreview([url]);
}
//滚动到元素底部
function scrollDIV() {
try {
const div = document.getElementById("scrollDIV");
div.scrollTop = div.scrollHeight;
} catch (error) {}
}
//监听是否到达底部
function scrollBotton() {
const div = document.getElementById("scrollDIV");
div.addEventListener("scroll", () => {
const clientHeight = div.clientHeight;
const scrollTop = div.scrollTop;
const scrollHeight = div.scrollHeight;
const dist = scrollHeight - scrollTop - clientHeight;
divHeight.value = dist;
// emits("update:scroll", dist);
});
}
</script>
<style lang="scss" scoped>
@import "../../../../assets/styles/mixin.scss";
.message_list_box {
// padding: 2.5vw;
@include font_color($font-color-theme);
@include font_size($font_medium_s);
overflow: auto;
.message_item {
.item_left,
.item_right {
display: flex;
margin-bottom: 4vw;
.left_message {
background: #fff;
box-shadow: -1px 1px 1px 1px rgba(0, 0, 0, 0.1);
}
}
.item_left {
justify-content: flex-start;
text-align: left;
}
.item_right {
text-align: right;
justify-content: flex-end;
.right_message {
background: rgba(149, 236, 105, 0.5);
box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.1);
text-align: left;
}
}
}
}
.left_message,
.right_message {
padding: 1.5vw;
display: inline-block;
border-radius: 3px;
color: #333;
word-break: break-all;
}
img {
width: 25vw;
height: 25vw;
}
.df_avator {
@include font_size($font_medium_s);
display: inline-block;
height: 35px;
min-width: 35px;
border-radius: 50%;
background-color: #0078e0;
text-align: center;
line-height: 35px;
color: #fff;
overflow: hidden;
overflow-x: auto;
}
// 语音条
.audio-detail-msg {
display: flex;
align-items: center;
.msgRight {
transform: rotate(180deg);
}
.audio-style {
display: flex;
align-items: center;
height: 32px;
padding: 0 10px;
border-radius: 4px;
background: rgba(149, 236, 105, 0.5);
.small {
border: 4px solid #4c4c4c;
border-top-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
}
.middle {
width: 16px;
height: 16px;
margin-left: -11px;
opacity: 1;
}
.large {
width: 24px;
height: 24px;
margin-left: -19px;
opacity: 1;
}
&>div {
border: 2px solid #4c4c4c;
border-top-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-radius: 50%;
box-sizing: border-box;
}
&.add-animation {
.middle {
animation: show2 1.2s ease-in-out infinite;
}
.large {
animation: show3 1.2s ease-in-out infinite;
}
}
}
// 语音播放动画
@keyframes show2 {
0% {
opacity: 0;
}
10% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes show3 {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
60% {
opacity: 0;
}
100% {
opacity: 0;
}
}
// 语音录制动画
@keyframes backgroundInfinite2 {
0% {
background: #666;
}
20% {
background: #f5f5f5;
}
95% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite3 {
0% {
background: #666;
}
30% {
background: #f5f5f5;
}
85% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite4 {
0% {
background: #666;
}
55% {
background: #f5f5f5;
}
75% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite5 {
0% {
background: #666;
}
45% {
background: #666;
}
60% {
background: #f5f5f5;
}
75% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite6 {
0% {
background: #666;
}
65% {
background: #666;
}
85% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite7 {
0% {
background: #666;
}
75% {
background: #666;
}
95% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
}
.msgRight_box {
justify-content: flex-end;
}
.videos_box {
position: relative;
.yysc {
position: absolute;
bottom: 1.5vw;
color: #fff;
}
.play_box {
position: absolute;
width: 10vw;
height: 10vw;
top: 50%;
margin-top: -5vw;
}
.right_video_time {
right: 1vw;
}
.left_video_time {
left: 1vw;
}
.right_video_play {
right: 11vw;
margin-right: -5vw;
}
.left_video_play {
left: 11vw;
margin-left: -5vw;
}
}
.video_image {
height: 40vw;
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<div class="zl">
<div class="zl_title">
<span v-for="(item, index) in list" :key="index">{{ item.label }}</span>
</div>
<div class="zl_table">
<div
class="zl_table_item"
v-for="(item, index) in tableList"
:key="index"
>
<div class="tab_item">
<img
v-if="index === 0"
:src="require('../../../../assets/gxapp/j.png')"
alt=""
width="18"
height="22"
/>
<img
v-else-if="index === 1"
:src="require('../../../../assets/gxapp/y.png')"
alt=""
width="18"
height="22"
/>
<img
v-else-if="index === 2"
:src="require('../../../../assets/gxapp/t.png')"
alt=""
width="18"
height="22"
/>
<span v-else>{{ index + 1 }}</span>
</div>
<div class="tab_item">{{ item.name }}</div>
<div class="tab_item">
{{ Number(item.ytotal) + Number(item.ntotal) }}
</div>
<div class="tab_item">{{ item.ytotal }}</div>
<div class="tab_item">{{ item.ntotal }}</div>
<div class="tab_item">
{{ item.wcl ? (item.wcl * 100).toFixed(2) + "%" : "--" }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, defineProps, watch } from "vue";
import { zlPhTjxx } from "../../../..//api/zlzx.js";
const props = defineProps({
timeType: {
type: String,
default: "1",
},
});
watch(
() => props.timeType,
() => {
getList();
},{deep:true}
);
const list = ref([
{ label: "排名" },
{ label: "部门名称" },
{ label: "总数" },
{ label: "已完成" },
{ label: "未完成" },
{ label: "完成率" },
]);
const tableList = ref([]);
function getList() {
tableList.value = []
zlPhTjxx({ type: props.timeType }).then((res) => {
for (let i = 0; i < res.length; i++) {
if (res[i].name == "高新区分局") {
res[i].name = "高新分局";
} else {
res[i].name = res[i].name
.replace(/高新区分局/g, "")
.replace(/派出所/g, "");
}
}
tableList.value = res;
const total = res.length;
});
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
@import "../../../../assets/styles/mixin.scss";
.zl {
margin: 5px 0;
@include font_color($font-color-theme);
@include font_size($font_medium_s);
.zl_title {
margin: 0 5px;
padding: 0 7px;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
}
.zl_table {
margin-top: 5px;
.zl_table_item {
min-height: 26px;
display: flex;
justify-content: space-between;
align-items: center;
.tab_item {
flex: 1;
text-align: center;
}
}
}
}
</style>

View File

@ -0,0 +1,558 @@
<template>
<div>
<!-- 音频录制 -->
<div class="audio-record">
<p class="duration-seconds-style">
<span v-show="countDownRecord"
>{{ countDownSecond }}&nbsp;后停止录制</span
>
</p>
<div class="icon">
<img src="../../../../assets/images/audio.png" width="45" />
<div class="voice-animation" ref="voiceAnimation">
<p v-for="item in 7" :key="item"></p>
</div>
</div>
</div>
<div class="btn_crossAndbtn_succes">
<div class="cross_box" :class="{ voiceCanael: isCancel }">
<van-icon name="cross" />
</div>
<!-- <div><van-icon name="success" /></div> -->
</div>
</div>
</template>
<script setup>
// import TitleTab from "../components/TitleTab.vue";
import {
reactive,
onMounted,
ref,
watch,
defineExpose,
defineProps,
} from "vue";
import { uploadImg } from "../../../../api/yyzxApi.js";
import emitter from "../../../../utils/eventBus.js";
import { Toast } from "vant";
const recordStatus = ref(false); //是否显示录音
const showDurationSeconds = ref(0); // 语音时长
const durationSeconds = ref(0); // 语音时长
const isDurationSeconds = ref(false); // 开始录制时间状态
const playAudioTimer = ref(null); // 语音播放定时器
const confTime = ref(null); //录音动画 定时函数
const startRecord = ref(false); // 开始录制按钮状态
const isShowAudio = ref(true); //是否显示语音条
const countDownRecord = ref(false); // 倒计时录制状态
const countDownSecond = ref(60); // 倒计时录制时间
const isPlay = ref(false); //是否可以播放
const voiceAnimation = ref(); //录音动画demo
//webkitURL is deprecated but nevertheless
const URL = window.URL || window.webkitURL;
const fileName = ref(""); //文件名
var gumStream; //语音
var rec; //Recorder.js 对象
var input; //MediaStreamAudioSourceNode we'll be recording //文件
// shim for AudioContext when it's not avb.
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext; //audio context to help us record
const props = defineProps({
isCancel: Boolean, //是否取消
});
onMounted(() => {});
// 完成录制并发送
function sendRecord() {
stopRecording();
// isShowAudio.value = true;
}
// 取消语音录制
function cancelRecord() {
showDurationSeconds.value = durationSeconds.value;
recordStatus.value = false;
startRecord.value = false;
isDurationSeconds.value = false;
countDownRecord.value = false;
voiceAnimation.value.classList.add("start");
clearInterval(confTime.value);
confTime.value = null;
durationSeconds.value = 0;
rec.stop();
countDownSecond.value = 60;
}
// 开始录制语音消息开始动画效果
function startAudioRecord() {
startRecord.value = true;
isDurationSeconds.value = true;
voiceAnimation.value.classList.add("start");
confTime.value = setInterval(() => {
durationSeconds.value++;
countDownSecond.value--;
//开启录音倒计时
if (durationSeconds.value === 10) {
countDownRecord.value = true;
}
//自动结束录音并发送
if (countDownSecond.value === 0) {
clearInterval(confTime.value);
confTime.value = null;
stopRecording();
emitter.emit("hideAudio", {});
}
}, 1000);
startRecording();
}
//开始录音
function startRecording() {
var constraints = {
audio: true,
video: false,
};
//调用录音
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
audioContext = new AudioContext();
gumStream = stream;
/* use the stream */
input = audioContext.createMediaStreamSource(stream);
rec = new Recorder(input, {
numChannels: 1,
});
rec.record();
});
}
//点击暂停 | 恢复录音
function pauseRecording() {
if (rec.recording) {
//pause
rec.stop();
} else {
//resume
rec.record();
}
}
//停止录音
function stopRecording() {
//停止录音
rec.stop();
gumStream.getAudioTracks()[0].stop();
rec.exportWAV(createDownloadLink);
clearInterval(confTime.value);
confTime.value = null;
}
//添加语音内容
function createDownloadLink(blob) {
uploadMav(blob);
// var url = URL.createObjectURL(blob);
// var au = document.getElementById("audio");
// //添加语音标签
// au.controls = true;
// au.src = url;
// //文件名称
// var filename = new Date().toISOString();
// fileName.value = filename + ".wav"
}
//上传语音文件
function uploadMav(blob) {
Toast({
message: "图片发送中。。。",
forbidClick: true,
duration: 0,
});
const data = new FormData();
const filename = new Date().toISOString();
const files = new File([blob], filename + ".wav");
data.append("file", files);
uploadImg(data)
.then((res) => {
if (res) {
emitter.emit("getAudio", {
durationSeconds: durationSeconds.value,
fjid: res,
});
Toast.clear();
}
})
.catch((err) => {
Toast.clear();
});
}
defineExpose({
cancelRecord,
stopRecording,
startAudioRecord,
});
</script>
<style lang="scss" scoped>
p {
margin: 0;
}
// 音频录制
.audio-record {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 150px;
// height: 160px;
background: rgba(0, 0, 0, 0.8);
padding: 15px;
border-radius: 15px;
color: #ccc;
font-size: 15px;
text-align: center;
.cancel-img {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
}
.duration-seconds-style {
height: 20px;
}
.icon {
display: flex;
justify-content: center;
align-items: flex-end;
margin: 18px 0 30px;
}
.voice-animation {
margin-left: 15px;
p {
height: 3px;
margin-top: 4px;
background: #666;
}
p:nth-of-type(1) {
width: 28px;
}
p:nth-of-type(2) {
width: 24px;
}
p:nth-of-type(3) {
width: 20px;
}
p:nth-of-type(4) {
width: 16px;
}
p:nth-of-type(5) {
width: 12px;
}
p:nth-of-type(6) {
width: 8px;
}
p:nth-of-type(7) {
width: 5px;
}
&.start {
p:nth-of-type(1) {
animation: backgroundInfinite7 1.5s ease-in-out infinite;
}
p:nth-of-type(2) {
animation: backgroundInfinite6 1.5s ease-in-out infinite;
}
p:nth-of-type(3) {
animation: backgroundInfinite5 1.5s ease-in-out infinite;
}
p:nth-of-type(4) {
animation: backgroundInfinite4 1.5s ease-in-out infinite;
}
p:nth-of-type(5) {
animation: backgroundInfinite3 1.5s ease-in-out infinite;
}
p:nth-of-type(6) {
animation: backgroundInfinite2 1.5s ease-in-out infinite;
}
p:nth-of-type(7) {
background: #f5f5f5;
}
}
}
.record-status {
width: 100px;
height: 28px;
margin: 0 auto;
line-height: 28px;
color: #888;
border-radius: 2px;
&:hover {
color: #04113d;
background: #e1e1e1;
}
p {
cursor: pointer;
}
}
.ly_btn {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 4vw;
div {
width: 60px;
height: 30px;
color: #888;
cursor: pointer;
line-height: 30px;
border-radius: 4px;
}
.cancel {
color: #cbcbcb;
background: #830808;
}
.send {
color: #cbcbcb;
background: #4a8b2a;
}
}
}
// 语音条
.audio-detail-msg {
display: flex;
align-items: center;
justify-content: flex-end;
.audio-style {
display: flex;
align-items: center;
height: 32px;
margin-left: 8px;
padding: 0 10px;
border-radius: 4px;
background: #fff;
transform: rotate(180deg);
.small {
border: 4px solid #4c4c4c;
border-top-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
}
.middle {
width: 16px;
height: 16px;
margin-left: -11px;
opacity: 1;
}
.large {
width: 24px;
height: 24px;
margin-left: -19px;
opacity: 1;
}
& > div {
border: 2px solid #4c4c4c;
border-top-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-radius: 50%;
box-sizing: border-box;
}
&.add-animation {
.middle {
animation: show2 1.2s ease-in-out infinite;
}
.large {
animation: show3 1.2s ease-in-out infinite;
}
}
}
// 语音播放动画
@keyframes show2 {
0% {
opacity: 0;
}
10% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes show3 {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
60% {
opacity: 0;
}
100% {
opacity: 0;
}
}
// 语音录制动画
@keyframes backgroundInfinite2 {
0% {
background: #666;
}
20% {
background: #f5f5f5;
}
95% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite3 {
0% {
background: #666;
}
30% {
background: #f5f5f5;
}
85% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite4 {
0% {
background: #666;
}
55% {
background: #f5f5f5;
}
75% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite5 {
0% {
background: #666;
}
45% {
background: #666;
}
60% {
background: #f5f5f5;
}
75% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite6 {
0% {
background: #666;
}
65% {
background: #666;
}
85% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite7 {
0% {
background: #666;
}
75% {
background: #666;
}
95% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
}
.btn_crossAndbtn_succes {
display: flex;
justify-content: space-between;
position: absolute;
bottom: 20vw;
width: 100%;
padding: 13vw;
.cross_box {
width: 15vw;
height: 15vw;
border-radius: 50%;
text-align: center;
line-height: 15vw;
}
.voiceCanael {
color: #fff;
background: rgba(230, 23, 24, 0.6);
}
}
</style>

View File

@ -0,0 +1,234 @@
<template>
<div class="zl">
<div id="pie" style="width: 100%; height: 55vw"></div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch,defineProps } from "vue";
import * as echarts from "echarts";
import { zlZqtjxx } from "../../../../api/zlzx.js";
const props = defineProps({
timeType: {
type: String,
default: "1",
},
})
watch(
() => props.timeType,
() => {
getData();
},{deep:true}
);
const D_BZ_ZLLY = [
{ label: "巡逻方案", value: "01" },
{ label: "人员预警", value: "02" },
{ label: "车辆预警", value: "03" },
{ label: "事件预警", value: "04" },
{ label: "警情", value: "05" },
{ label: "人员", value: "06" },
{ label: "事务", value: "08" },
];
const data = reactive({
data1: [0, 0, 0, 0, 0, 0, 0],
data2: [0, 0, 0, 0, 0, 0, 0],
data3: [0, 0, 0, 0, 0, 0, 0],
data4: [0, 0, 0, 0, 0, 0, 0],
data5: [0, 0, 0, 0, 0, 0, 0],
data6: [0, 0, 0, 0, 0, 0, 0],
data7: [0, 0, 0, 0, 0, 0, 0],
data8: [0, 0, 0, 0, 0, 0, 0],
time:[1,2,3,4,5,6,7]
});
function lineChartFn() {
var chartDom = document.getElementById("pie");
var myChart = echarts.init(chartDom);
var option;
option = {
grid: {
left: "3%",
right: "5%",
bottom: "3%",
top: "40%",
containLabel: true,
},
legend: {
show: true,
textStyle: {
color: "#333",
},
},
tooltip: {},
xAxis: {
type: "category",
data: data.time,
axisLine: {
show: true,
axisTick: {
show: false,
},
},
axisLabel: {
color: "#333",
},
axisLabel: {
interval: 0,
rotate: 30, // 主要是这个 设置角度即可 - 90 ~ 90 旋转方向也不同
},
},
yAxis: {
show: true,
minInterval: 1,
axisLine: {
show: true,
},
splitLine: {
show: false,
},
axisLabel: {
color: "#333",
},
},
series: [
{
name: "巡逻方案",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#ffcc40",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data1,
},
{
name: "人员预警",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#F6426C",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data2,
},
{
name: "车辆预警",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#177FFF",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data3,
},
{
name: "事件预警",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#FF18F1",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data4,
},
{
name: "警情",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#45E3FF",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data5,
},
{
name: "人工",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#45E30F",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data6,
},
{
name: "人员管控",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#fff981",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data7,
},
{
name: "事务",
type: "bar",
barWidth: 4,
itemStyle: {
normal: {
color: "#f56925",
barBorderRadius: [15, 15, 15, 15],
},
},
data: data.data8,
},
],
};
option && myChart.setOption(option);
document.getElementById("pie").setAttribute("_echarts_instance_", "");
}
//获取近一周日期集合
function getWeek() {
const time = new Date().getTime();
var timeArr = [];
for (let i = 0; i < 7; i++) {
const timen = new Date(time - i * 60 * 60 * 24 * 1000);
var y = timen.getFullYear();
var m =
timen.getMonth() < 9 ? "0" + timen.getMonth() + 1 : timen.getMonth() + 1;
var d = timen.getDate() < 10 ? "0" + timen.getDate() : timen.getDate();
timeArr.unshift(y + "-" + m + "-" + d);
}
return timeArr;
}
function getData() {
zlZqtjxx({ type: props.timeType }).then((res) => {
({
data1: data.data1,
data2: data.data2,
data3: data.data3,
data4: data.data4,
data5: data.data5,
data6: data.data6,
data7: data.data7,
data8: data.data8,
} = res);
data.time = res.time
lineChartFn();
});
}
onMounted(() => {
getData();
});
</script>
<style lang="scss" scoped>
.zl {
margin: 5px 0;
padding-top: 8px;
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<div class="zl">
<div id="bar" style="width: 100%; height: 55vw"></div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted,defineProps,watch } from "vue";
import * as echarts from "echarts";
import { zlDjtjxx } from "../../../../api/zlzx.js";
const props = defineProps({
timeType: {
type: String,
default: "1",
},
})
watch(
() => props.timeType,
() => {
getData();
},{deep:true}
);
const data = ref([
{
name: "蓝色",
value: 0,
dict: "40",
},
{
name: "橙色",
value: 0,
dict: "20",
},
{
name: "黄色",
value: 0,
dict: "30",
},
{
name: "红色",
value: 0,
dict: "10",
},
]);
function lineChartFn() {
var chartDom = document.getElementById("bar");
var myChart = echarts.init(chartDom);
let bgColor = "#fff";
let title = "总量";
let formatNumber = function (num) {
let reg = /(?=(\B)(\d{3})+$)/g;
return num.toString().replace(reg, ",");
};
let total = data.value.reduce((a, b) => {
return a + b.value * 1;
}, 0);
var option;
const color = ["#177FFF", "orange", "#ffcc40", "#F6426C"];
option = {
color,
tooltip: {
trigger: "item",
},
legend: {
bottom: "0",
left: "center",
textStyle: {
color: "#333",
},
},
// legend: {},
series: [
{
type: "pie",
radius: ["45%", "70%"],
center: ["50%", "50%"],
data: data.value,
hoverAnimation: false,
labelLine: {
normal: {
length: 20,
length2: 30,
},
},
label: {
normal: {
formatter: function(val){
if(val.percent === undefined){
return val.name +'('+ val.value +')'+ '\n' + 0 +'%'
}else{
return val.name +'('+ val.value +')'+'\n' + val.percent +'%'
}
}
},
},
},
],
};
option && myChart.setOption(option);
document.getElementById("bar").setAttribute("_echarts_instance_", "");
}
function getData() {
zlDjtjxx({type:props.timeType}).then((res) => {
res.forEach((v) => {
data.value.filter((item) => item.dict === v.zldj).map((item) => {
item.value = v.total;
});
});
lineChartFn();
});
}
onMounted(() => {
getData();
});
</script>
<style lang="scss" scoped>
.zl {
margin: 5px 0;
padding-top: 8px;
background-size: 100% 100%;
}
</style>

View File

@ -0,0 +1,970 @@
<template>
<div style="padding-top: 13vw">
<TopNav navTitle="指令详情" :showRight="false" :showLeft="true" />
<div class="people_detail" ref="zlxq">
<div class="p_qb_info" v-if="zlDetail">
<div class="p_title">{{ zlDetail.zlbt }}</div>
<div class="p_info">
<div class="p_img_box" v-if="zlDetail.zltp">
<van-image width="75px" height="82px" fit="fill" :class="_getBase64(zlDetail)" :src="zlDetail.baseUrl"
@click="onClickImg(zlDetail.baseUrl)">
<template v-slot:loading>
<van-loading type="spinner" size="20" />
</template>
</van-image>
</div>
<div class="p_text_info">
<div class="first_line">
<div class="first_line_text">
<span>指令等级</span> &emsp;
<span :class="{
text_red: zlDetail.zldj == '10',
text_orange: zlDetail.zldj == '20',
text_yellow: zlDetail.zldj == '30',
text_bule: zlDetail.zldj == '40',
}">{{
zlDetail.zldj == "10"
? "红"
: zlDetail.zldj == "20"
? "橙"
: zlDetail.zldj == "30"
? "黄"
: "蓝"
}}</span>
</div>
<template style="display: flex" v-if="!['92', '93', '94'].includes(dqzt)">
<van-button round type="primary" style="padding: 0 5vw" size="mini" color="#009944" v-if="dqzt == '01'"
@click="onClickQs('签收')">
签收
</van-button>
<van-button round type="primary" style="padding: 0 5vw" size="mini" color="#009944"
v-if="(dqzt == '02' || dqzt == '03') && zlDetail.zllx != '07'" @click="onClickQs('到达现场')">
到达现场
</van-button>
<!-- trs研判 -->
<van-button round type="primary" color="#009944" style="padding: 0 5vw" size="mini" v-if="
dqzt == '11' ||
dqzt == '21' ||
dqzt == '22' ||
dqzt == '23' ||
dqzt == '24' ||
dqzt == '25' ||
(dqzt == '31' && zlDetail.zllx != '07')
" @click="onClickQs('处置完成')">
处置完成
</van-button>
<van-button round type="primary" color="#009944" style="padding: 0 5vw" size="mini" v-if="dqzt == '32'"
@click="onClickQs('完结')">
完结
</van-button>
</template>
</div>
<div class="other_line">
来源: {{ setDict(zlDetail.zlly, D_BZ_ZLLY) }} &emsp; &emsp; 类型:
{{ setDict(zlDetail.zllx, D_BZ_ZLLX) }}
</div>
<div class="other_line">时间: {{ zlDetail.zlfqsj }}</div>
<div class="other_line" v-if="fjList.length > 0">
附件:
<span style="color:blue;margin-right2px;" v-for="(item, index) in fjList" :key="index + 'file'">
<a @click="getPreviewText(item)" style="text-decoration: underline;margin-right2px;">附件{{ index +
1}}</a>
</span>
</div>
<div class="video_line"></div>
</div>
</div>
<div class="nr_text">内容: {{ zlDetail.zlnr }}</div>
<div class="check_box">
<div class="check_btn" @click="onClickPr">盘查人员</div>
<div class="check_btn" @click="onClickPc">盘查车辆</div>
</div>
</div>
<div class="chat_box">
<MesssageList :list="zlMessage.messageList" :mesHeight="mesHeight" :audioSc="audioSc"
@click:video="onClickVideoUrl" />
</div>
</div>
<div class="bottom_box" :style="{ background: themeType == 'light' ? '' : '#092556' }"
v-if="dqzt != '92' && dqzt != '93' && dqzt != '94'">
&nbsp;<van-icon :name="require('../../../assets/images/audio_h.png')" size="35"
@click="(showAudio = !showAudio), (voiceCanael = false)"></van-icon>
<div class="message_box">
<van-config-provider :theme-vars="themeVars" v-if="!showAudio">
<van-field v-model="message" label-width="60px" placeholder="请输入你描述的内容..." type="textarea" autosize>
<template #button>
<van-button size="small" type="primary" @click="onClickSend">发送</van-button>
</template>
</van-field>
</van-config-provider>
<div class="azsh_btn" @touchstart="onTouchstart" @touchmove="onTouchmove" @touchend="onTouchchend" v-else>
<van-button type="default" block>按住说话</van-button>
</div>
</div>
&nbsp;<van-icon name="add-o" size="35" @click="showShare = true"></van-icon>
&nbsp;
</div>
<!-- 附件弹窗 -->
<van-popup v-model:show="showShare" position="bottom" round>
<div class="addfj_box">
<van-uploader :after-read="afterRead">
<div class="fj_item">
<img src="../../../assets/images/images.png" alt="" />
<div>图片</div>
</div>
</van-uploader>
<div class="fj_item" @click="onSelectShare">
<img src="../../../assets/images/videos.png" alt="" />
<div>视频</div>
</div>
</div>
<div class="fj_cancel" @click="(showShare = false), Toast.clear()">
取消
</div>
</van-popup>
<!-- 盘人 -->
<checkedPeople ref="peo" :zlId="zlDetail.id" />
<!-- 盘车 -->
<checkedCar ref="car" :zlId="zlDetail.id" />
<van-dialog v-model:show="isWjShow" title="完结说明" show-cancel-button confirm-button-color="#3e6ee8"
@cancel="isWjShow = false" @confirm="onConfirmWjsm">
<van-field v-model="wjsmVal" type="textarea" placeholder="请输入完结说明"></van-field>
</van-dialog>
<van-overlay :show="showAudio">
<Voice ref="voices" :key="voiceIndex" :isCancel="voiceCanael" />
</van-overlay>
<input type="file" accept="video/*" capture="camcorder" style="display: none" id="getVideo" @change="changeVideo" />
<div class="video_play_box" v-show="showLangVideoUrl">
<video style="width: 100%; height: 100vh" controls id="langVideo"></video>
<div class="cross_box" @click="onCloseVideo">
<van-icon name="cross" size="15" color="#fff"></van-icon>
</div>
</div>
<Iframe :url="fillFrams" :isPreviw="isPreviw" @closePre="isPreviw = false"></Iframe>
</div>
</template>
<script setup>
import axios from "axios";
import Base64 from "base-64";
import { getMyTaskDetail, getFjInfo } from "../../../api/rwzx.js";
import {
trsGetRequest,
trsPostRequest,
trs2PostRequest,
timeValidate,
} from "../../../utils/tools";
import Voice from "./components/voice.vue";
import { ImagePreview } from "vant";
import { baseUrl2 } from "../../../utils/request.js";
import TopNav from "../../../components/topNav.vue";
import checkedCar from "../../spsHome/components/checkCar.vue";
import checkedPeople from "../../spsHome/components/checkPeople.vue";
import { ref, reactive, onMounted, onUnmounted, onBeforeUnmount } from "vue";
import MesssageList from "./components/messsageList.vue";
import upVideo from "../../../utils/fpsc.js";
import Iframe from "../../../assets/html/filePreview.vue";
import {
dateFormat,
getDistance,
IS_PNG,
IS_MP3,
IS_MP4,
getBase64,
hintToast,
dataQc,
} from "../../../utils/tools.js";
import emitter from "../../../utils/eventBus.js";
import { Dialog, Toast } from "vant";
import { useRoute } from "vue-router";
import {
getZlczData,
getZlDetail,
addZxjl,
updateZlStatus,
zlHandleRygk,
getToken,
} from "../../../api/zlzx.js";
import { endTaskStatus } from "../../../api/yyzxApi.js";
import { upImage, bigDataUpload } from "../../../api/common.js";
import { getDictList, setDict } from "../../../utils/dict";
const { D_BZ_ZLLY, D_BZ_ZLLX, D_BZ_ZXZTAI } = getDictList(
"D_BZ_ZLLY",
"D_BZ_ZLLX",
"D_BZ_ZXZTAI"
); //字典信息
const baseUrl = `${baseUrl2}/mosty-api/mosty-base/minio/image/download/`;
const themeType = ref(getStorage("themeSetting"));
const themeVars = {
cellBackgroundColor: themeType.value == "light" ? "#ffffff" : "#092556",
cellHorizontalPadding: "8px",
cellVerticalPadding: "8px",
fieldRightIconColor: themeType.value == "light" ? "#333" : "#fff",
FieldIconSize: "28px",
};
const URL = window.URL || window.webkitURL;
const audioSc = ref("");
const voiceIndex = ref(1);
const caEnevt = ref(null); //按住说话事件
const showAudio = ref(false); //是否显示录音弹窗
const peo = ref(); //盘查人员组件对象
const car = ref(); //盘查车辆组件对象
const zlxq = ref(""); //指令详情dom
const message = ref(""); //消息类容
const mesHeight = ref(47); //聊天盒子高度
const mesIndex = ref(1); //消息盒子组件KEY
const id = ref(""); //指令ID
const lastTime = ref(""); //最后一条流程时间
const zlDetail = ref({}); //指令详情
const userInfo = JSON.parse(getStorage("userInfo"));
// const token = getStorage("TRS") && JSON.parse(getStorage("TRS"));
const mesTime = ref(null); // 消息记录轮询时间函数
const isArriveTime = ref(null); //是否到达时间函数
const isWjShow = ref(false); //是否显示完结说明弹窗
const wjsmVal = ref(""); //完结说明内容
const fjid = ref(""); //上传成功的附件ID
const yp = ref(false); // 研判弹框
const ypText = ref("");
const voices = ref(); //语音组件
const showShare = ref(false); //显示附件弹窗
const showLangVideoUrl = ref(false); //是否显示大视频
const isPreviw = ref(false);
const fillFrams = ref(null);
//手指触摸开始位置
const startBtn = ref({
s_x: 0,
s_y: 0,
});
//手指触摸结束位置
const endBtn = ref({
e_x: 0,
e_y: 0,
});
const voiceCanael = ref(false); //是否取消发送语音
const zlMessage = reactive({
//消息数据
messageList: [], //消息记录
});
const sptpid = ref(""); //视频第一帧图片ID
const toast3 = ref(""); //视频上传时间提示
const toast4 = ref(""); //消息发送提示
Toast.allowMultiple();
onMounted(() => {
id.value = useRoute().query.id;
//点击盘查功能
emitter.on("onClickPc", (res) => {
if (res == "pr") {
checkPeople();
} else {
checkCar();
}
});
Promise.all([_getZlczData(true), _getZlDetail(id.value)]).then((res) => {
mesTime.value = setInterval(() => {
//获取做最后一条数据的时间
if (zlMessage.messageList.length > 0) {
lastTime.value =
zlMessage.messageList[zlMessage.messageList.length - 1].time;
_getZlczData();
}
}, 1500);
//获取语音时长
emitter.on("getAudio", (res) => {
audioSc.value = res.durationSeconds;
message.value = `${res.fjid}.wav`;
fjid.value = res.fjid;
submitMessage("audio");
});
//自动结束关闭语音窗口
emitter.on("hideAudio", (res) => {
showAudio.value = false;
voiceIndex.value++;
message.value = "";
});
});
scrollToBottom();
});
onBeforeUnmount(() => {
clearInterval(mesTime.value);
mesTime.value = null;
clearInterval(isArriveTime.value);
isArriveTime.value = null;
});
onUnmounted(() => {
emitter.off("onClickPc");
emitter.off("getAudio");
});
// 获取预览文件
function getPreviewText(item) {
let url = item.url; //要预览文件的访问地址
let Base64Url = Base64.encode(url);
fillFrams.value =
"http://10.64.201.126:8012/onlinePreview?url=" +
encodeURIComponent(Base64Url);
isPreviw.value = true;
}
//关闭大视频
function onCloseVideo() {
let videos = document.getElementById("langVideo");
videos.pause();
showLangVideoUrl.value = false;
}
//获取放大视频地址
function onClickVideoUrl(url) {
if (url) {
let videos = document.getElementById("langVideo");
videos.src = url;
videos.play();
showLangVideoUrl.value = true;
}
}
//调用
function onSelectShare() {
window.addEventListener("message", receivePost);
// let videos = document.getElementById("getVideo");
// videos.click();
// toast3.value = Toast({
// message: "拍摄视频请不要超过20s",
// position: "bottom",
// duration: 6000,
// });
try {
bridge.recordVideo(
`${baseUrl2}/mosty-api/mosty-base/minio/image/upload/id`,
30
);
} catch (error) { }
}
//接收postMessage消息由安卓上传的视频 返回ID
function receivePost(mes) {
try {
let str = mes.data.type;
switch (str) {
case "videoId":
fjid.value = mes.data.data;
message.value = `${new Date().getTime()}_video.mp4`;
showShare.value = false;
submitMessage("video");
window.removeEventListener("message", receivePost);
break;
}
} catch (error) { }
}
//长按事件
function onTouchstart(item) {
//获取起点位置
startBtn.value.s_x = item.touches[0].pageX;
startBtn.value.s_y = item.touches[0].pageY;
item.stopPropagation();
//长按事件后执行录音
caEnevt.value = setTimeout(() => {
caEnevt.value = 0;
voices.value.startAudioRecord();
}, 800);
return false;
}
//手指移动
function onTouchmove(item) {
//手指离开的位置
let e_x = item.changedTouches[0].pageX;
let e_y = item.changedTouches[0].pageY;
//计算出偏移度
let direction = getDirection(
startBtn.value.s_x,
startBtn.value.s_y,
e_x,
e_y
).toFixed(0);
//手指滑动到取消范围内 取消高亮
if (direction < -110 && direction > -150) {
voiceCanael.value = true;
}
}
//释放事件
function onTouchchend(item) {
//手指离开的位置
endBtn.value.e_x = item.changedTouches[0].pageX;
endBtn.value.e_y = item.changedTouches[0].pageY;
//计算出偏移度
let direction = getDirection(
startBtn.value.s_x,
startBtn.value.s_y,
endBtn.value.e_x,
endBtn.value.e_y
).toFixed(0);
item.stopPropagation();
showAudio.value = false;
voiceIndex.value++;
//在取消范围取消语音发送
if (direction < -110 && direction > -150) {
clearTimeout(caEnevt.value);
voices.value.cancelRecord();
} else {
//松开手指或者不在取消范围 就发送语音
if (caEnevt.value != 0) {
hintToast("说话时间太短!!");
clearTimeout(caEnevt.value);
} else {
voices.value.stopRecording();
}
}
clearTimeout(caEnevt.value);
return false;
}
//获得角度
function getAngle(angx, angy) {
return (Math.atan2(angx, angy) * 180) / Math.PI;
}
//计算偏移量
function getDirection(startX, startY, endX, endY) {
let angx = endX - startX;
let angy = endY - startY;
let result = 0;
if (Math.abs(angx) < 2 && Math.abs(angy) < 2) {
return result;
}
let angle = getAngle(angx, angy);
result = angle;
return result;
}
//预览图片
function onClickImg(fjid) {
ImagePreview([fjid]);
}
//结束任务
function _endTaskStatus(rwxl, ywid, rwzt, rwxxlx) {
let data = { rwxl, ywid, rwzt, rwxxlx };
try {
let location = JSON.parse(bridge.getLocation());
data.jd = location.app_x;
data.wd = location.app_y;
} catch (error) { }
endTaskStatus(data).then((res) => { });
}
//获取base64地址
function _getBase64(item) {
getBase64((res) => {
item.baseUrl = res;
}, item.zltp);
}
// 滚动条始终在最底部
function scrollToBottom() {
var container = document.getElementsByClassName("check_box")[0];
container.scrollTop = container.scrollHeight;
}
//获取指令详情
const dqzt = ref();
function _getZlDetail(id) {
getZlDetail(id).then((res) => {
zlDetail.value = res;
dqzt.value = res.zxrVo.dqzt;
_getFile(res.fjId);
if (res.zlzxzt == "02" || res.zlzxzt == "03") {
countJl();
isArriveTime.value = setInterval(() => {
countJl();
}, 30e3);
} else {
clearInterval(isArriveTime.value);
isArriveTime.value = null;
}
});
}
const fjList = ref([]);
function _getFile(fjid) {
try {
const requestArr = fjid.split(",").map((item) => {
return getFjInfo(item);
});
Promise.all(requestArr).then((resArr) => {
resArr.forEach((res) => {
if (IS_PNG(res.suffix.substring(1, res.suffix.length))) {
res.fileFormat = "png";
} else {
res.fileFormat = "file";
}
fjList.value.push(res);
});
});
} catch (error) { }
}
//获取指令处置流程
function _getZlczData(flag) {
let data = {
zlid: id.value,
time: lastTime.value,
};
getZlczData(data).then((res) => {
if (res && res.length > 0) {
res.forEach((item) => {
let fileFormat = "";
if (item.zxnr && item.fjid) {
//判断是那种文件类型.
let fjIndex = item.zxnr.lastIndexOf(".");
if (fjIndex != -1) {
let file_Format = item.zxnr.substring(
fjIndex + 1,
item.zxnr.length
);
if (IS_PNG(file_Format)) {
fileFormat = "png";
} else if (IS_MP3(file_Format)) {
fileFormat = "mp3";
} else if (IS_MP4(file_Format)) {
fileFormat = "mp4";
} else {
fileFormat = "file";
}
}
}
zlMessage.messageList.push({
time: item.zxsj,
type: userInfo.id == item.xtCjrId ? 2 : 1, //1为 对方的数据 2为自己的数据
text: item.zxnr,
yysc: item.yysc,
name: item.xtCjr || "系统",
fjid: item.fjid,
sptpid: item.sptpid,
fileFormat,
});
dataQc(zlMessage.messageList, "id");
});
}
});
}
//点击盘人
function onClickPr() {
peo.value.handleOpen();
}
//点击盘车
function onClickPc() {
car.value.handleOpen();
}
//确认完结说明
function onConfirmWjsm() {
isWjShow.value = false;
_updateZlStatus("04");
}
//签收
const ypflag = ref(true);
const fkFlag = ref(true);
// 处理状态
function onClickQs(text) {
if (text == "完结") return isWjShow.value = true;
Dialog.confirm({ title: "提示", message: `是否${text}!`, }).then((res) => {
switch (text) {
case "签收":
_updateZlStatus("01");
break;
case "到达现场":
_updateZlStatus("02", 1);
break;
case "处置完成":
_updateZlStatus("03");
break;
}
}).catch((err) => { });
}
//计算用户与指令地址距离
function countJl() {
try {
let location = JSON.parse(bridge.getLocation());
let jl = getDistance(
zlDetail.value.jd,
zlDetail.value.wd,
location.app_x,
location.app_y
);
//小于100米 自动到达现场
if (jl < 100) {
_updateZlStatus("02", 0);
}
} catch (error) { }
}
//修改指令状态
function _updateZlStatus(type, sfsddd) {
let { zxrVo, ssbmid, zlfsdd, zljsdx } = zlDetail.value;
let data = {
ssbmid: ssbmid,
zlzxdd: zlfsdd,
zljsdx: zljsdx,
zlzxrDh: zxrVo.zxrDh,
zlzxrId: zxrVo.zxrId,
zlzxrSfz: zxrVo.zxrSfz,
zlzxrXm: zxrVo.zxrXm,
zlzxrXzid: zxrVo.zxrXzid,
zlzxzt: type,
zlId: zxrVo.zlId,
zlwjsm: wjsmVal.value,
sfsddd,
};
try {
let location = JSON.parse(bridge.getLocation());
data.jd = location.app_x;
data.wd = location.app_y;
} catch (error) {
data.jd = 104.065637;
data.wd = 30.592008;
}
updateZlStatus(data).then((res) => {
hintToast("成功");
_getZlDetail(id.value);
_getZlczData();
_endTaskStatus("05", zlDetail.value.id, "2", type);
});
}
//获取文件
function afterRead(file) {
toast4.value = Toast({
message: "图片发送中。。。",
forbidClick: true,
});
showShare.value = false;
const data = new FormData();
data.append("file", file.file);
upImage(data).then((res) => {
if (res) {
fjid.value = res;
message.value = file.file.name;
submitMessage("img");
}
});
}
//发送消息
function onClickSend() {
if (!message.value) return;
lastTime.value = zlMessage.messageList[zlMessage.messageList.length - 1].time;
submitMessage();
}
//提交聊天数据到后台
function submitMessage(type) {
if (type == "audio") {
toast4.value = Toast({
message: "语音发送中。。。",
forbidClick: true,
});
}
let data = {
zlId: id.value,
zlzxdd: zlDetail.value.zlfsdd,
zxnr: message.value,
fjid: fjid.value,
yysc: audioSc.value,
sptpid: sptpid.value,
};
message.value = "";
try {
let location = JSON.parse(bridge.getLocation());
data.jd = location.app_x;
data.wd = location.app_y;
} catch (error) { }
addZxjl(data).then((res) => {
fjid.value = "";
message.value = "";
_getZlczData();
})
}
</script>
<style lang="scss" scoped>
@import "../../../assets/styles/mixin.scss";
::v-deep .van-field__control {
@include border_color($border-color-theme);
@include user_function_item_color($user-function-item-theme);
@include font_size($font_medium_s);
padding: 1vw;
height: 35px !important;
border-radius: 5px;
}
.top {
padding: 3vw;
margin: 2vw;
}
.basic-info {
color: #4d4d4d;
display: flex;
flex-direction: row;
.img-box {
flex: 1;
}
.IDcard-info {
flex: 3;
@include font_size($font_medium_s);
.text {
@include font_size($font_medium);
font-weight: bold;
color: #1f6cec;
}
}
}
.but_box {
display: flex;
justify-content: space-between;
margin-top: 1vw;
}
.bottom_box {
position: fixed;
width: 100vw;
bottom: 0;
max-height: 21vw;
background-color: #eff0f2;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #cac9c9;
z-index: 1001;
.message_box {
flex: 1;
}
}
.zldj_item {
display: inline-block;
width: 3.5vw;
height: 3.5vw;
border-radius: 50%;
}
.contact-way {
margin-top: 3.5vw;
@include font_size($font_medium_s);
.zl_detail {
height: 14vw;
overflow: auto;
margin-bottom: 1vw;
}
.fjsp {
display: flex;
justify-content: space-between;
.fjsp_item {
display: flex;
align-items: center;
}
}
}
.preview-cover {
position: absolute;
bottom: 0;
box-sizing: border-box;
width: 100%;
padding: 4px;
color: #fff;
font-size: 12px;
text-align: center;
background: rgba(0, 0, 0, 0.3);
}
.fj_box {
padding: 20px;
font-size: 12px;
}
.yp_btn {
margin: 40px 10px 10px 10px;
display: flex;
justify-content: center;
}
.people_detail {
@include font_size($font_medium_s);
display: flex;
flex-direction: column;
.p_qb_info {
background-image: linear-gradient(to bottom,
#b7dafc 0%,
#ffffff 35%,
#ffffff 100%);
padding: 12px 17px 17px 17px;
}
.p_title {
@include font_size($font_medium);
font-weight: bold;
color: #004cab;
word-wrap: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.p_info {
display: flex;
margin-top: 1vh;
.p_text_info {
position: relative;
flex: 1;
.first_line {
display: flex;
justify-content: space-between;
.first_line_text {
@include font_size($font_medium);
font-weight: bold;
.text_red {
color: #e20000;
}
.text_orange {
color: #fa5200;
}
.text_yellow {
color: #f18608;
}
.text_bule {
color: #013791;
}
}
}
.other_line {
line-height: 3vh;
}
.video_line {
position: absolute;
right: 0;
bottom: 0.5vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.video_text {
color: #0289fe;
@include font_size($font_medium_s);
}
}
}
}
.nr_text {
color: #555;
}
.check_box {
display: flex;
justify-content: space-between;
overflow-anchor: none;
.check_btn {
width: 44vw;
height: 6vh;
text-align: center;
line-height: 6vh;
background-color: #f7e4e0;
@include font_size($font_medium);
font-weight: bold;
border-radius: 3px;
margin-top: 2vh;
}
}
.chat_box {
padding: 17px;
box-sizing: border-box;
height: 61vh;
overflow: hidden;
overflow-y: auto;
background-color: #eff0f2;
}
}
::v-deep .van-divider {
border-color: #cac9c9;
}
::v-deep .van-cell {
background-color: #eff0f2;
}
::v-deep .trs.van-button--block {
width: unset;
}
::v-deep .van-field__right-icon {
padding-top: 2vw;
}
::v-deep .van-overlay {
z-index: 1000;
}
.azsh_btn {
background: #e1e1e1;
text-align: center;
-webkit-touch-callout: none !important;
-webkit-user-select: none;
}
.addfj_box {
display: flex;
@include font_size($font_medium);
.fj_item {
text-align: center;
padding: 7vw;
&>img {
width: 12vw;
height: 11.8vw;
}
}
}
.fj_cancel {
border-top: 3vw solid #eff0f2;
text-align: center;
padding: 4vw;
}
.video_play_box {
position: fixed;
z-index: 9999999999;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 1);
.cross_box {
position: fixed;
z-index: 999999999912;
top: 5vw;
right: 5vw;
width: 7vw;
height: 7vw;
background: gray;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
}
</style>

View File

@ -0,0 +1,148 @@
<template>
<div style="padding-top: 15vw">
<TopNav navTitle="指令统计" :showRight="false" :showLeft="true" />
<TopSelectTime @chenge:time="onSelectTime" />
<TopTotal :list="zlTotal.list" />
<!-- 任务完成情况 -->
<div class="item_box gsdyj_box">
<div class="other_type_title">完成率排名</div>
<TaskRank :timeType="timeType" />
</div>
<div class="item_box gsdyj_box">
<div class="other_type_title">指令趋势</div>
<WeekTrendBar :timeType="timeType" />
</div>
<div class="item_box gsdyj_box">
<div class="other_type_title">指令等级统计</div>
<WeekTrendPie :timeType="timeType" />
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from "vue";
import TopSelectTime from "../../../components/topSelectTime.vue";
import TopNav from "../../../components/topNav.vue";
import TopTotal from "../../../components/topTotal";
import TaskRank from "./components/taskRank";
import WeekTrendBar from "./components/weekTrendBar";
import WeekTrendPie from "./components/weekTrendPie";
import { getTjOfZllx } from "../../../api/zlzx.js";
import { Toast } from "vant";
const timeType = ref("1"); // 统计条件
const zlTotal = reactive({
list: [
{
title: "全部",
count: 0,
zllx: "",
},
{
title: "巡逻指令",
count: 0,
zllx: "01",
},
{
title: "感知预警指令",
count: 0,
zllx: "02",
},
{
title: "警情处置指令",
count: 0,
zllx: "05",
},
{
title: "人工指令",
count: 0,
zllx: "06",
},
{
title: "人员管控指令",
count: 0,
zllx: "07",
},
],
});
function onSelectTime(val) {
const copyTime = timeType.value;
timeType.value = val + 1 + "";
if (timeType.value !== copyTime) {
_getZlTotal();
}
}
onMounted(() => {
_getZlTotal();
});
//获取指令统计
function _getZlTotal() {
Toast.loading({
message: "加载中",
forbidClick: true,
loadingType: "spinner",
duration: 0,
});
zlTotal.list = [
{
title: "全部",
count: 0,
zllx: "",
},
{
title: "巡逻指令",
count: 0,
zllx: "01",
},
{
title: "感知预警指令",
count: 0,
zllx: "02",
},
{
title: "警情处置指令",
count: 0,
zllx: "05",
},
{
title: "人工指令",
count: 0,
zllx: "06",
},
{
title: "人员管控指令",
count: 0,
zllx: "07",
},
];
getTjOfZllx({ type: timeType.value }).then((res) => {
res.forEach((item) => {
zlTotal.list[0].count += item.count;
if (["02", "03", "04"].includes(item.zllx)) {
zlTotal.list[2].count += item.count;
} else {
zlTotal.list.forEach((el) => {
if (item.zllx == el.zllx) {
el.count = item.count;
}
});
}
});
Toast.clear();
});
}
</script>
<style lang="scss" scoped>
@import "../../../assets/styles/mixin.scss";
.gsdyj_box {
margin: 2vw 3vw 5vw 3vw;
padding: 2vw;
}
.other_type_title {
margin-left: 2vw;
}
::v-deep .van-tabs__nav--card {
margin: 0 3vw;
}
</style>

View File

@ -0,0 +1,417 @@
<template>
<div>
<TopNav navTitle="信息交互" :showRight="true" :showLeft="true" rightIcon="add-o" @clickRight="onClickRight" />
<van-sticky>
<div class="sticky_box">
<van-tabs v-model:active="activeTabName" class="top_tabs" :class="activeTabName == '01' ? 'left_top_tabs' : ''"
@change="changeTab">
<van-tab :title="item.name" :name="item.type" v-for="item in toptabs" :key="item"></van-tab>
</van-tabs>
<Tabs v-if="activeTabName == '01'" :list="tabs" @onYjjb="onSelect" :key="tabsIndex"></Tabs>
<Search placeholder="请输入关键字" v-model="kwd" :isSx="true" @update:sx="showPopup = !showPopup"
@update:modelValue="onSearch"></Search>
</div>
</van-sticky>
<van-pull-refresh v-model="loadingPull" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" finished-text=" " @load="onLoad" offset="1"
:immediate-check="false">
<ZlList v-for="(item, index) in zlList.list" :key="index" :item="item" :dict="{ D_BZ_ZXZTAI }"
:path="`/yyzx/zlzx/zlDetail?id=${item.zlId}`"></ZlList>
<van-empty description="暂无指令信息" image="default" v-if="zlList.list.length <= 0 && loadingPull == false" />
</van-list>
</van-pull-refresh>
<SxPopup :showPopup="showPopup" :list="zlList.sxTypeList" :p_top="145" @update:close="showPopup = false"
@update:onConfirm="onConfirm" />
</div>
</template>
<script setup>
import { getTrsToken } from "@/api/common";
import Tabs from "../../../components/tabs.vue";
import TopNav from "../../../components/topNav.vue";
import Search from "../../../components/search.vue";
import ZlList from "../../../components/zlList.vue";
import SxPopup from "../../../components/SxPopup.vue";
import { dateFormat, setTimeQuantum } from "../../../utils/tools.js";
import { getDictList, setDict } from "../../../utils/dict";
import { ref, onMounted, reactive, watch } from "vue";
import router from "@/router";
import { Toast } from "vant";
import { getZlList, getZlTotal } from "../../../api/zlzx.js";
import store from "../../../store";
const { D_BZ_ZXZTAI } = getDictList("D_BZ_ZXZTAI"); //字典信息
const userInfo = JSON.parse(window.localStorage.getItem("userInfo"));
const kwd = ref(""); //搜索关键字
const showPopup = ref(false); //赛选条件窗口
const zlztIndex = ref(-1); //选中的指令状态下标
const timeShow = ref(false); //时间选择器
const presentTime = ref(new Date()); //当前时间
const timeType = ref(""); //选择的时间类型
const startTime = ref(""); //开始时间
const endTime = ref(""); //结束时间
const loading = ref(false);
const loadingPull = ref(false);
const finished = ref(false);
const pageSize = ref(10);
const pageCurrent = ref(1);
const zllx = ref(""); // 指令类型
const zlTypeIndex = ref(""); //选中的查询类型下标
const total = ref(0); //
const tabsIndex = ref(1);
const activeTabName = ref('01');
const listQuery = ref({
zlly: '01,02,03,04,05,06,07,08,09'
})
const zlList = reactive({
list: [], //指令列表数据
sxTypeList: [], //筛选项数据
});
const toptabs = ref([
{
name: "任务下达",
type: "01",
},
{
name: "任务需求",
type: "02",
},
])
const tabs = ref([
{
name: "全部",
count: 0,
zllx: "",
},
{
name: "巡逻",
count: 0,
zllx: "01",
},
{
name: "人员",
count: 0,
zllx: "02",
},
{
name: "车辆",
count: 0,
zllx: "03",
},
{
name: "事件",
count: 0,
zllx: "04",
},
{
name: "警情",
count: 0,
zllx: "05",
},
{
name: "人工指令",
count: 0,
zllx: "06",
},
{
name: "人员管控",
count: 0,
zllx: "07",
},
{
name: "事务",
count: 0,
zllx: "08",
},
]);
watch(D_BZ_ZXZTAI, (newValue) => {
//设置筛选值
let array = [];
for (let i = 0; i < newValue.length; i++) {
array.push({
name: newValue[i].text,
key: newValue[i].dm,
isCheck: false,
});
}
zlList.sxTypeList = [
{
title: "指令进行状态",
isCheckBox: false,
array: array,
},
];
zlList.sxTypeList[1] = setTimeQuantum();
});
onMounted(() => {
_getZlTotal();
_getZlList();
});
//下拉刷新
function onRefresh() {
reset()
_getZlTotal();
_getZlList();
}
const reset = () => {
zlList.list = [];
pageCurrent.value = 1;
zlTypeIndex.value = "";
kwd.value = "";
startTime.value = "";
endTime.value = "";
loading.value = false;
loadingPull.value = false;
finished.value = false;
tabsIndex.value++;
}
// 切换tab
const changeTab = (val) => {
console.log(val, 'val');
reset()
if (val == '01') {
listQuery.value.zlly = '01,02,03,04,05,06,07,08,09';
} else if (val == '02') {
listQuery.value.zlly = '10';
}
_getZlList();
}
//关键字查询
function onSearch(e) {
kwd.value = e;
zlList.list = [];
pageCurrent.value = 1;
_getZlTotal();
_getZlList();
}
// //类型查询
function onSelect(val) {
zlList.list = [];
pageCurrent.value = 1;
zllx.value = tabs.value[val].zllx;
loading.value = false;
loadingPull.value = false;
finished.value = false;
_getZlList();
}
//获取指令数据列表
function _getZlList() {
loading.value = true;
let data = {
pageSize: pageSize.value,
pageCurrent: pageCurrent.value,
zlzxzt: zlTypeIndex.value,
zlnr: kwd.value,
kssj: startTime.value,
jssj: endTime.value,
zllx: zllx.value,
zlly: listQuery.value.zlly
};
getZlList(data)
.then((res) => {
loading.value = false;
loadingPull.value = false;
total.value = res.pages;
let arr = res.records ? res.records : [];
arr.forEach((item) => {
zlList.list.push(item);
});
})
.catch((err) => {
loading.value = false;
});
}
//获取指令统计数据
function _getZlTotal() {
getZlTotal()
.then((res) => {
tabs.value[0].count = 0;
if (res.length > 0) {
res.forEach((item) => {
tabs.value[0].count += item.count;
switch (item.zllx) {
case "01":
tabs.value[1].count = item.count;
break;
case "02":
tabs.value[2].count = item.count;
break;
case "03":
tabs.value[3].count = item.count;
break;
case "04":
tabs.value[4].count = item.count;
break;
case "05":
tabs.value[5].count = item.count;
break;
case "06":
tabs.value[6].count = item.count;
break;
case "07":
tabs.value[7].count = item.count;
break;
case "08":
tabs.value[8].count = item.count;
break;
}
});
}
})
.catch((err) => { });
}
const onClickRight = () => {
router.push('/addZlInfo')
}
//选中时间
function onTimeConfirm(val) {
if (timeType.value == "start") {
startTime.value = dateFormat("", val);
} else {
endTime.value = dateFormat("", val);
}
timeShow.value = false;
}
//选中指令状态
function onClickZlzt(index, dm) {
zlTypeIndex.value = dm;
zlztIndex.value = index;
}
//确认筛选条件
function onConfirm(val) {
zlList.list = [];
pageCurrent.value = 1;
startTime.value = val.startTime;
endTime.value = val.endTime;
zlTypeIndex.value = "";
for (let i = 0; i < val.list.length; i++) {
for (let j = 0; j < val.list[i].array.length; j++) {
if (val.list[i].array[j].isCheck) {
zlTypeIndex.value = val.list[i].array[j].key;
break;
}
}
}
_getZlList();
showPopup.value = false;
}
//重置筛选条件
function onReset() {
startTime.value = "";
endTime.value = "";
zlztIndex.value = -1;
}
//触底加载
function onLoad() {
if (pageCurrent.value >= total.value) {
finished.value = true;
return;
}
pageCurrent.value += 1;
_getZlList();
}
</script>
<style lang="scss" scoped>
@import "../../../assets/styles/mixin.scss";
.sticky_box {
@include bg_color($background-color-theme);
.tabs_box {
@include font_size($font_medium_s);
@include font_color($font-color-theme);
display: flex;
.tabs_item {
width: 25%;
text-align: center;
.tabs_count {
margin-top: 2vw;
}
}
}
}
.zl_sx_box {
@include font_size($font_medium_s);
padding: 4vw 0;
.item_title {
padding: 0 3.5vw;
font-weight: 600;
}
.zlzt_box {
padding: 4vw;
.tag_item {
margin: 0 3vw 3vw 0;
padding: 1vw 3vw;
}
}
}
.but_box {
display: flex;
justify-content: space-between;
margin-top: 5vw;
}
.item_box {
@include font_size($font_medium_s);
@include font_color($font-color-theme);
margin: 0 3.5vw 4vw 3.5vw;
padding: 2vw;
.time_box {
display: flex;
justify-content: space-between;
align-items: center;
.ststus {
font-size: 10px;
padding: 0 10px;
color: #fff;
border-radius: 10px;
}
.dfk {
background-color: rgb(242, 199, 6);
}
.yfk {
background-color: #3e6ee8;
}
.ywj {
background-color: #4fbe0d;
}
.ddc {
background-color: orangered;
}
.djc {
background-color: orange;
}
}
>div {
line-height: 20px;
}
.title {
@include font_size($font_medium);
font-weight: bold;
}
}
.top_tabs {
margin-top: 13vw;
}
.left_top_tabs {
margin-bottom: -10.5vw;
}
</style>