打卡
This commit is contained in:
@ -11,7 +11,14 @@ export function fetchPatrolList(params) {
|
||||
// 获取任务中心列表
|
||||
export function fetchTaskList(params) {
|
||||
return service({
|
||||
url: `${api}/tbZdxlFgxlrw/selectPage`,
|
||||
url: `${api}/tbZdyrw/selectListApp`,
|
||||
params
|
||||
})
|
||||
}
|
||||
// 获取巡逻详情
|
||||
export function selectByTaskInfo(params) {
|
||||
return service({
|
||||
url: `${api}/tbZdyrw/getFgrwDkXq`,
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,10 +4,6 @@ const props = defineProps({
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
showReceive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
@ -21,13 +17,19 @@ const handleClockInPage = (item) => {
|
||||
<template>
|
||||
<div class="item-wrapper">
|
||||
<template v-for="(item, index) in list" :key="index">
|
||||
<div class="card" @click="handleClockInPage(item)">
|
||||
<div class="title">*************任务</div>
|
||||
<div class="row">
|
||||
<div class="col">方格编号:{{ item?.fgId || '04' }}</div>
|
||||
<div class="col right">任务日期:{{ item?.rwRq }}</div>
|
||||
<div class="item" @click="handleClockInPage(item)">
|
||||
<div class="rowWrapper">
|
||||
<div class="title">{{ item?.fgMc }}</div>
|
||||
<div class="progress">{{ item?.bddAllProgress || 0 }}%</div>
|
||||
</div>
|
||||
|
||||
<div class="rowWrapper mt co99 fz12">
|
||||
<div>预警等级:{{ item?.fgYjdjLabel }}</div>
|
||||
<div>任务日期:{{ item?.rwRq }}</div>
|
||||
</div>
|
||||
<div class="rowWrapper mt co99 fz12">
|
||||
<div>高发类型:{{ item?.fgJqtjLx }}</div>
|
||||
</div>
|
||||
<div class="btn" v-if="showReceive" @click.stop="handleClockInPage(item)">领取</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@ -35,50 +37,37 @@ const handleClockInPage = (item) => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.item-wrapper {
|
||||
padding: 3vw;
|
||||
margin: 2vw;
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 3.73vw;
|
||||
padding: 4vw;
|
||||
margin-bottom: 3vw;
|
||||
box-shadow: 0 1.6vw 4vw rgba(0, 0, 0, 0.04);
|
||||
.item {
|
||||
padding: 2vw;
|
||||
border-top: 0.13333vw solid #f4f5f1;
|
||||
|
||||
.title {
|
||||
font-size: 4vw;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 3vw;
|
||||
}
|
||||
|
||||
.row {
|
||||
.rowWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4vw;
|
||||
font-size: 3.4vw;
|
||||
color: #999;
|
||||
|
||||
.col {
|
||||
&.right {
|
||||
text-align: right;
|
||||
}
|
||||
align-items: center;
|
||||
.title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
font-weight: 400;
|
||||
font-size: 3.73vw;
|
||||
color: #3E6EE8;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 11vw;
|
||||
line-height: 11vw;
|
||||
text-align: center;
|
||||
border-radius: 6vw;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(90deg, #28A5FF 0%, #3E6EE8 100%);
|
||||
font-size: 4vw;
|
||||
box-shadow: 0 2vw 4vw rgba(62, 110, 232, 0.3);
|
||||
|
||||
&.disabled {
|
||||
background: #ccc;
|
||||
box-shadow: none;
|
||||
}
|
||||
.fz12 {
|
||||
font-size: 3.2vw;
|
||||
}
|
||||
|
||||
.co99 {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.mt {
|
||||
margin-top: 2vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import { qcckPost, qcckGet } from "@/api/qcckApi";
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
@ -18,7 +19,11 @@ const handleClockInPage = (item) => {
|
||||
}
|
||||
|
||||
const handleClaim = (item) => {
|
||||
emits("claim", item);
|
||||
qcckGet({ fgrwid: item.id }, "/mosty-yjzl/tbZdyrw/getLqrw").then(res => {
|
||||
if (res) {
|
||||
emits("claim", item);
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -26,10 +31,10 @@ const handleClaim = (item) => {
|
||||
<div class="item-wrapper">
|
||||
<template v-for="(item, index) in list" :key="index">
|
||||
<div class="card" @click="handleClockInPage(item)">
|
||||
<div class="title">*************任务</div>
|
||||
<div class="title">{{ item.fgRwmc }}</div>
|
||||
<div class="row">
|
||||
<div class="col">方格编号:{{ item?.fgId || '04' }}</div>
|
||||
<div class="col right">任务日期:{{ item?.rwRq }}</div>
|
||||
<div class="col">方格编号:{{ item.fgmc }}</div>
|
||||
<div class="col right">任务日期:{{ item?.xfrq }}</div>
|
||||
</div>
|
||||
<div class="btn" v-if="showReceive" @click.stop="handleClaim(item)">领取</div>
|
||||
</div>
|
||||
@ -61,7 +66,7 @@ const handleClaim = (item) => {
|
||||
margin-bottom: 4vw;
|
||||
font-size: 3.4vw;
|
||||
color: #999;
|
||||
|
||||
|
||||
.col {
|
||||
&.right {
|
||||
text-align: right;
|
||||
@ -78,7 +83,7 @@ const handleClaim = (item) => {
|
||||
background: linear-gradient(90deg, #28A5FF 0%, #3E6EE8 100%);
|
||||
font-size: 4vw;
|
||||
box-shadow: 0 2vw 4vw rgba(62, 110, 232, 0.3);
|
||||
|
||||
|
||||
&.disabled {
|
||||
background: #ccc;
|
||||
box-shadow: none;
|
||||
|
||||
@ -1,23 +1,24 @@
|
||||
<script setup>
|
||||
import TopNav from "@/components/topNav.vue";
|
||||
import { qcckPost, qcckGet } from "@/api/qcckApi";
|
||||
import { onMounted, ref, reactive, onUnmounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { selectByBBdId, fetchSelectListByBddxlrwId } from "@/api/patrolList";
|
||||
import { qcckGet } from "@/api/qcckApi";
|
||||
import { selectByTaskInfo } from "@/api/patrolList";
|
||||
import { getBase64, hintToast } from "@/utils/tools";
|
||||
import { ImagePreview } from "vant";
|
||||
import MapWrapper from "@/pages/clockInPage/components/mapWrapper.vue";
|
||||
import emitter from "@/utils/eventBus";
|
||||
|
||||
const route = useRoute();
|
||||
const info = reactive({
|
||||
fgId: "",
|
||||
rwRq: "",
|
||||
dkjgsj: "",
|
||||
bddList: []
|
||||
const info = ref({});
|
||||
const pointsList = ref([]);
|
||||
const listQuery = ref({
|
||||
dktp: "",
|
||||
jd: "104.67926708276372",
|
||||
wd: "31.038282761737406",
|
||||
});
|
||||
const points = ref([]);
|
||||
|
||||
const zxdksj = ref("");
|
||||
const dwIndex = ref();
|
||||
const imageMap = new Map();
|
||||
const getImageUrl = async (fileId) => {
|
||||
if (!fileId) return null;
|
||||
@ -30,111 +31,221 @@ const getImageUrl = async (fileId) => {
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const getImageUrlOld = (item) => {
|
||||
qcckGet({}, `/mosty-base/minio/file/download/${item}`).then((res) => {
|
||||
console.log(res.url, 'res.url');
|
||||
_getBase64(res.url);
|
||||
});
|
||||
};
|
||||
const preview = (url) => {
|
||||
if (url) ImagePreview([url]);
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
const id = route.query.id || "";
|
||||
const detail = await selectByBBdId({ bbdId: id });
|
||||
const detail = await selectByTaskInfo({ fgrwid: id });
|
||||
if (detail) {
|
||||
info.fgId = detail.fgId;
|
||||
info.rwRq = detail.rwRq;
|
||||
info.dkjgsj = detail.dkjgsj;
|
||||
info.bddList = detail.bddList || [];
|
||||
}
|
||||
const res = await fetchSelectListByBddxlrwId({ bddxlrwId: id });
|
||||
if (Array.isArray(res)) {
|
||||
const arr = [];
|
||||
const mapPoints = [];
|
||||
|
||||
for (const item of res) {
|
||||
const imgUrl = item.dkJsFj ? await getImageUrl(item.dkJsFj) : null;
|
||||
arr.push({
|
||||
title: "*************点位",
|
||||
dkKsSj: item.dkKsSj,
|
||||
dkJsSj: item.dkJsSj, // Use end time as check-in time for display if available
|
||||
isChecked: !!item.dkJsFj, // Assume checked in if there is an image
|
||||
imgUrlDkJsFj: imgUrl
|
||||
getUserLocation();
|
||||
// 删除方格
|
||||
emitter.emit("deletePointArea", "zdxl_fzyc");
|
||||
// 生成方格
|
||||
const { x1, y1, x2, y2, fgId, zxX, zxY, fgMc } = detail.fgdwVO;
|
||||
const centerPoint = [zxX, zxY];
|
||||
const position = [
|
||||
[Number(x1), Number(y1)],
|
||||
[Number(x2), Number(y2)],
|
||||
];
|
||||
const text = fgMc;
|
||||
const obj = [{ position: position, text, id: fgId, userData: detail.bxds }];
|
||||
emitter.emit("echoPlane", {
|
||||
fontColor: "#12fdb8",
|
||||
coords: obj,
|
||||
type: "rectangle",
|
||||
flag: "zdxl_fzyc",
|
||||
color: "rgba(2,20,51,0.5)",
|
||||
linecolor: "#1C97FF",
|
||||
});
|
||||
emitter.emit("setMapCenter", { location: centerPoint, zoomLevel: 12 });
|
||||
// 获取所有唯一的图片ID
|
||||
const uniqueImageIds = new Set();
|
||||
detail.bxds?.forEach((item) => {
|
||||
if (item?.dktp) uniqueImageIds.add(item.dktp);
|
||||
});
|
||||
|
||||
// Collect coordinates for map
|
||||
if (item.dkJsJd && item.dkJsWd) {
|
||||
mapPoints.push({ jd: item.dkJsJd, wd: item.dkJsWd });
|
||||
}
|
||||
}
|
||||
points.value = arr;
|
||||
|
||||
// Plot on map
|
||||
if (mapPoints.length > 0) {
|
||||
setTimeout(() => {
|
||||
emitter.emit("deletePointArea", "historyPoints");
|
||||
emitter.emit("addPointArea", {
|
||||
coords: mapPoints,
|
||||
icon: require("@/assets/images/11.png"), // Reuse existing icon or use a generic one
|
||||
flag: "historyPoints",
|
||||
});
|
||||
// Center map on first point
|
||||
emitter.emit("setMapCenter", {
|
||||
location: [mapPoints[0].jd, mapPoints[0].wd],
|
||||
zoomLevel: 14
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
// 批量获取图片URL
|
||||
const imageEntries = await Promise.allSettled(
|
||||
Array.from(uniqueImageIds).map(async (id) => {
|
||||
try {
|
||||
const url = await getImageUrl(id);
|
||||
return { id, url };
|
||||
} catch (error) {
|
||||
return { id, url: null };
|
||||
}
|
||||
})
|
||||
);
|
||||
// 创建图片映射表
|
||||
const imageMap = new Map();
|
||||
imageEntries.forEach((entry) => {
|
||||
if (entry.status === "fulfilled") {
|
||||
imageMap.set(entry.value.id, entry.value.url);
|
||||
}
|
||||
});
|
||||
// 设置数据
|
||||
pointsList.value = detail.bxds.map((item, index) => ({
|
||||
...item,
|
||||
imgUrlDkFj: item?.dktp ? imageMap.get(item.dktp) : null,
|
||||
}));
|
||||
// 为每个必到点添加图片URL
|
||||
zxdksj.value = detail.zxdksj;
|
||||
info.value = detail.fgrw;
|
||||
listQuery.value.fgrwid = info.value.id;
|
||||
}
|
||||
};
|
||||
|
||||
//获取当前位置信息
|
||||
function getUserLocation(sfdw) {
|
||||
let { lng, lat } = getLocation();
|
||||
if (lng && lat) {
|
||||
emitter.emit("deletePointArea", "dw");
|
||||
//地图撒点然后移动
|
||||
emitter.emit("addPointArea", {
|
||||
coords: [{ jd: lng, wd: lat }],
|
||||
icon: require("../../assets/lz/dw.png"),
|
||||
flag: "dw",
|
||||
sfdw: sfdw,
|
||||
sizeX: 30,
|
||||
sizeY: 35,
|
||||
});
|
||||
} else {
|
||||
hintToast("暂无坐标信息");
|
||||
}
|
||||
}
|
||||
function isInThirtyMinutes(targetTime, jgsj) {
|
||||
let flag = false
|
||||
// 获取当前时间
|
||||
const now = new Date();
|
||||
// 最新打卡时间
|
||||
const targetDate = new Date(targetTime);
|
||||
let nowMin = now.getTime();//当前时间
|
||||
let newMin = targetDate.getTime();//最新打卡时间
|
||||
let count = nowMin - newMin;
|
||||
if (count > jgsj * 60 * 1000) {
|
||||
flag = true
|
||||
} else {
|
||||
flag = false
|
||||
}
|
||||
return flag
|
||||
}
|
||||
// 点击上传
|
||||
const photoFn = (val, index) => {
|
||||
dwIndex.value = index;
|
||||
listQuery.value.bxdid = val.bxdid;
|
||||
// 判断此任务最新打卡时间如果在打卡间隔时间之后才能继续打卡
|
||||
if (zxdksj.value) {
|
||||
if (!isInThirtyMinutes(zxdksj.value, info.value.dkjgsj)) {
|
||||
const now = new Date();
|
||||
const time1 = new Date(zxdksj.value);
|
||||
let newMin = time1.getTime() + Number(info.value.dkjgsj) * 60 * 1000;//30分钟后的时间
|
||||
let max = Math.floor((newMin - now.getTime()) / 1000 / 60);//最大可打卡时间
|
||||
hintToast(`请于${max}分钟后打卡`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
bridge.pZ("photo");
|
||||
} catch (err) {
|
||||
console.log(err, "err");
|
||||
}
|
||||
handleClick()
|
||||
} else {
|
||||
try {
|
||||
bridge.pZ("photo");
|
||||
} catch (err) {
|
||||
console.log(err, "err");
|
||||
}
|
||||
// handleClick()
|
||||
}
|
||||
};
|
||||
// 打卡
|
||||
const handleClick = () => {
|
||||
// const { lng, lat } = getLocation();
|
||||
// listQuery.value.jd = lng;
|
||||
// listQuery.value.wd = lat;
|
||||
qcckPost(listQuery.value, "/mosty-yjzl/tbZdyrw/zdyRwdk")
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
hintToast(`打卡成功`);
|
||||
loadData()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err, "err");
|
||||
hintToast("打卡失败");
|
||||
});
|
||||
};
|
||||
function setimage_base64(pzid, base64) {
|
||||
pointsList.value[
|
||||
dwIndex.value
|
||||
].imgUrlDkFj = `data:image/jpeg;base64,${base64}`;
|
||||
console.log(pointsList.value[dwIndex.value].imgUrlDkFj, "point");
|
||||
qcckPost({ base64: base64 }, "/mosty-base/minio/image/upload/base64").then(
|
||||
(res) => {
|
||||
listQuery.value.dktp = res;
|
||||
handleClick();
|
||||
},
|
||||
);
|
||||
}
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
window.setimagebase64 = setimage_base64;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.emit("deletePointArea", "historyPoints");
|
||||
emitter.emit("deletePointArea", "historyPoints");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<TopNav nav-title="任务详情" show-left />
|
||||
|
||||
|
||||
<div class="content">
|
||||
<!-- Header Card -->
|
||||
<div class="card header-card">
|
||||
<div class="title">*************任务</div>
|
||||
<div class="title">{{ info.fgRwmc }}</div>
|
||||
<div class="row info-row">
|
||||
<div class="sub">方格编号:{{ info.fgId || '04' }}</div>
|
||||
<div class="sub">任务日期:{{ info.rwRq }}</div>
|
||||
<div class="sub">方格编号:{{ info.fgmc }}</div>
|
||||
<div class="sub">任务日期:{{ info.xfrq }}</div>
|
||||
</div>
|
||||
<div class="sub">
|
||||
打卡间隔时间:{{ info.dkjgsj ? info.dkjgsj : "100min" }}
|
||||
</div>
|
||||
<div class="sub">打卡间隔时间:{{ info.dkjgsj ? info.dkjgsj + 'min' : '100min' }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Point List -->
|
||||
<div class="list">
|
||||
<div v-for="(item, index) in points" :key="index" class="card point-card">
|
||||
<div v-for="(item, index) in pointsList" :key="index" class="card point-card">
|
||||
<div class="row header-row">
|
||||
<div class="name">{{ item.title }}</div>
|
||||
<div v-if="item.isChecked" class="status-tag">已打卡</div>
|
||||
<div class="name">{{ item.bxdMc }}</div>
|
||||
<div class="status-tag">{{item.dkzt}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Checked In: Show Image -->
|
||||
<div v-if="item.isChecked && item.imgUrlDkJsFj" class="img-container" @click="preview(item.imgUrlDkJsFj)">
|
||||
<img :src="item.imgUrlDkJsFj" alt="打卡图片" />
|
||||
<div class="time-overlay">{{ item.dkJsSj }}</div>
|
||||
<div v-if="item.dktp || item.imgUrlDkFj" class="img-container" @click="preview(item.imgUrlDkFj)">
|
||||
<img :src="item.imgUrlDkFj" alt="打卡图片" />
|
||||
<div class="time-overlay">{{ item.dksj }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Not Checked In: Show Button (Visual only) -->
|
||||
<div v-else class="btn-container">
|
||||
<div class="btn">打卡拍照</div>
|
||||
<div class="btn" @click="photoFn(item, index)">打卡拍照</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Section -->
|
||||
<div class="card map-card">
|
||||
<div class="map-title">当前位置</div>
|
||||
<div class="map-box">
|
||||
<MapWrapper />
|
||||
</div>
|
||||
<div class="map-title">当前位置</div>
|
||||
<div class="map-box">
|
||||
<MapWrapper />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -143,7 +254,7 @@ onUnmounted(() => {
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #F5F7FA;
|
||||
background: #f5f7fa;
|
||||
padding-bottom: 5vw;
|
||||
}
|
||||
|
||||
@ -166,11 +277,13 @@ onUnmounted(() => {
|
||||
color: #333;
|
||||
margin-bottom: 3vw;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5vw;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5vw;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 3.4vw;
|
||||
color: #999;
|
||||
@ -183,17 +296,18 @@ onUnmounted(() => {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 3vw;
|
||||
|
||||
|
||||
.name {
|
||||
font-size: 4vw;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 2vw;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 3vw;
|
||||
color: #3E6EE8;
|
||||
border: 1px solid #3E6EE8;
|
||||
color: #3e6ee8;
|
||||
border: 1px solid #3e6ee8;
|
||||
padding: 0.5vw 1.5vw;
|
||||
border-radius: 1vw;
|
||||
}
|
||||
@ -205,62 +319,63 @@ onUnmounted(() => {
|
||||
height: 40vw;
|
||||
border-radius: 2vw;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
.time-overlay {
|
||||
position: absolute;
|
||||
top: 2vw;
|
||||
left: 2vw;
|
||||
color: #fff;
|
||||
font-size: 3.5vw;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
margin-top: 2vw;
|
||||
.btn {
|
||||
height: 11vw;
|
||||
line-height: 11vw;
|
||||
text-align: center;
|
||||
border-radius: 6vw;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(90deg, #28A5FF 0%, #3E6EE8 100%);
|
||||
font-size: 4vw;
|
||||
box-shadow: 0 2vw 4vw rgba(62, 110, 232, 0.3);
|
||||
}
|
||||
margin-top: 2vw;
|
||||
|
||||
.btn {
|
||||
height: 11vw;
|
||||
line-height: 11vw;
|
||||
text-align: center;
|
||||
border-radius: 6vw;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(90deg, #28a5ff 0%, #3e6ee8 100%);
|
||||
font-size: 4vw;
|
||||
box-shadow: 0 2vw 4vw rgba(62, 110, 232, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.map-card {
|
||||
padding: 0; // Remove default padding for map card to let map fill or have custom padding
|
||||
overflow: hidden;
|
||||
|
||||
.map-title {
|
||||
padding: 4vw;
|
||||
font-size: 4vw;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.map-box {
|
||||
height: 50vw;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
// Adjust MapWrapper internal style via deep selector if needed,
|
||||
// but MapWrapper has fixed height in its scoped style usually.
|
||||
// We might need to override it.
|
||||
:deep(.mapWrapper) {
|
||||
margin-top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
padding: 0; // Remove default padding for map card to let map fill or have custom padding
|
||||
overflow: hidden;
|
||||
|
||||
.map-title {
|
||||
padding: 4vw;
|
||||
font-size: 4vw;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.map-box {
|
||||
height: 50vw;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
// Adjust MapWrapper internal style via deep selector if needed,
|
||||
// but MapWrapper has fixed height in its scoped style usually.
|
||||
// We might need to override it.
|
||||
:deep(.mapWrapper) {
|
||||
margin-top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
708
src/pages/taskList/historyDetail1.vue
Normal file
708
src/pages/taskList/historyDetail1.vue
Normal file
@ -0,0 +1,708 @@
|
||||
<script setup>
|
||||
import TopNav from "@/components/topNav.vue";
|
||||
import {
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
computed,
|
||||
nextTick,
|
||||
onUnmounted,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import Timeline from "@/pages/clockInPage/components/Timeline.vue";
|
||||
import {
|
||||
selectByTaskInfo,
|
||||
fetchSelectByBddxlrwId,
|
||||
fetchSelectListByBddxlrwId,
|
||||
fetchTbZdxlFgdwBddxlrwJlClockIn,
|
||||
} from "@/api/patrolList";
|
||||
import { getBase64, hintToast } from "@/utils/tools";
|
||||
import { ImagePreview } from "vant";
|
||||
import { qcckPost, qcckGet } from "@/api/qcckApi";
|
||||
import MapWrapper from "@/pages/clockInPage/components/mapWrapper.vue";
|
||||
import emitter from "@/utils/eventBus";
|
||||
|
||||
const route = useRoute();
|
||||
const active = ref(0);
|
||||
const nextStep = ref(0);
|
||||
const baseUrl = ref("");
|
||||
const fileId = ref("");
|
||||
const startTime = ref("2025-09-18 18:15:00");
|
||||
const useCountdownFromTime = () => {
|
||||
const timeLeft = ref(0); // 剩余毫秒数
|
||||
const timer = ref(null);
|
||||
const isRunning = ref(false); // 是否运行中
|
||||
const isExpired = ref(false); // 是否过期
|
||||
const expirationTime = ref(null); // 过期时间
|
||||
// 计算过期时间
|
||||
const calculateExpirationTime = (time, minutes = 10) => {
|
||||
if (!time) return null;
|
||||
try {
|
||||
const startDate = new Date(time);
|
||||
if (isNaN(startDate.getTime())) return null;
|
||||
return new Date(startDate.getTime() + minutes * 60000);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
// 更新倒计时
|
||||
const update = () => {
|
||||
if (!expirationTime.value) {
|
||||
isExpired.value = true;
|
||||
isRunning.value = false;
|
||||
return;
|
||||
}
|
||||
const now = new Date();
|
||||
timeLeft.value = expirationTime.value - now;
|
||||
if (timeLeft.value <= 0) {
|
||||
timeLeft.value = 0;
|
||||
isExpired.value = true;
|
||||
isRunning.value = false;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (timeLeft.value > 0) {
|
||||
timer.value = setTimeout(update, 1000);
|
||||
}
|
||||
};
|
||||
// 开始倒计时
|
||||
const start = (startTime, minutes) => {
|
||||
stop();
|
||||
expirationTime.value = calculateExpirationTime(startTime, minutes);
|
||||
|
||||
if (!expirationTime.value) {
|
||||
isExpired.value = true;
|
||||
return;
|
||||
}
|
||||
isRunning.value = true;
|
||||
isExpired.value = false;
|
||||
update();
|
||||
};
|
||||
// 停止倒计时
|
||||
const stop = () => {
|
||||
if (timer.value) {
|
||||
clearTimeout(timer.value);
|
||||
timer.value = null;
|
||||
}
|
||||
isRunning.value = false;
|
||||
};
|
||||
// 格式化时间显示
|
||||
const formattedTime = computed(() => {
|
||||
if (timeLeft.value <= 0) return '00:00';
|
||||
const totalSeconds = Math.floor(timeLeft.value / 1000);
|
||||
const mins = Math.floor(totalSeconds / 60);
|
||||
const secs = totalSeconds % 60;
|
||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
});
|
||||
|
||||
// 过期时间显示
|
||||
const expirationTimeFormatted = computed(() => {
|
||||
if (!expirationTime.value) return '';
|
||||
return expirationTime.value.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
});
|
||||
// 自动清理
|
||||
onUnmounted(stop);
|
||||
return {
|
||||
timeLeft,
|
||||
formattedTime,
|
||||
expirationTime: expirationTimeFormatted,
|
||||
isRunning,
|
||||
isExpired,
|
||||
start,
|
||||
stop
|
||||
};
|
||||
};
|
||||
|
||||
const data = reactive({
|
||||
patroObj: {},
|
||||
info: [],
|
||||
query: {},
|
||||
});
|
||||
|
||||
const infoData = computed(() => {
|
||||
return data.info?.[nextStep.value];
|
||||
});
|
||||
const activeInfoData = computed(() => data.patroObj?.bxds?.[active.value]);
|
||||
const { formattedTime, isExpired, expirationTime, start, stop } =
|
||||
useCountdownFromTime();
|
||||
// 删除打卡图片
|
||||
const clearImage = () => {
|
||||
baseUrl.value = "";
|
||||
fileId.value = "";
|
||||
};
|
||||
|
||||
// 浏览图片
|
||||
const onClickImg = (url) => {
|
||||
ImagePreview([url]);
|
||||
};
|
||||
|
||||
// 点击上传
|
||||
const photoFn = () => {
|
||||
try {
|
||||
bridge.pZ("photo");
|
||||
} catch (err) {
|
||||
console.log(err, "err");
|
||||
}
|
||||
};
|
||||
|
||||
// 支持更大数字的转换
|
||||
const getChineseNumber = (num) => {
|
||||
const numbers = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];
|
||||
if (num <= 10) {
|
||||
return numbers[num - 1];
|
||||
} else if (num <= 19) {
|
||||
return `十${numbers[num - 11] || ""}`;
|
||||
} else if (num <= 99) {
|
||||
const tens = Math.floor(num / 10);
|
||||
const units = num % 10;
|
||||
return `${numbers[tens - 1]}十${units > 0 ? numbers[units - 1] : ""}`;
|
||||
} else {
|
||||
return num.toString(); // 超过99返回阿拉伯数字
|
||||
}
|
||||
};
|
||||
// 切换tab
|
||||
const onChange = (value) => {
|
||||
active.value = value;
|
||||
nextStep.value = 0;
|
||||
baseUrl.value = "";
|
||||
fileId.value = "";
|
||||
getData();
|
||||
};
|
||||
|
||||
const handleNext = (index) => {
|
||||
stop();
|
||||
nextStep.value = index;
|
||||
start(infoData?.value?.dkKsSj, activeInfoData.value.dkjgsj);
|
||||
};
|
||||
|
||||
function setimage_base64(pzid, base64) {
|
||||
baseUrl.value = `data:image/jpeg;base64,${base64}`;
|
||||
qcckPost({ base64: base64 }, "/mosty-base/minio/image/upload/base64").then(
|
||||
(res) => {
|
||||
fileId.value = res;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const imageCache = new Map();
|
||||
const getImageUrl = async (fileId) => {
|
||||
if (!fileId) return null;
|
||||
// 检查缓存
|
||||
if (imageCache.has(fileId)) {
|
||||
return imageCache.get(fileId);
|
||||
}
|
||||
try {
|
||||
const res = await qcckGet({}, `/mosty-base/minio/file/download/${fileId}`);
|
||||
if (res?.url) {
|
||||
const base64 = await getBase64("", res.url);
|
||||
// 存入缓存
|
||||
imageCache.set(fileId, base64);
|
||||
return base64;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn("获取图片失败:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
try {
|
||||
const res = await fetchSelectListByBddxlrwId({
|
||||
bddxlrwId: activeInfoData.value.id || "",
|
||||
});
|
||||
if (res && res?.length > 0) {
|
||||
// 获取所有唯一的图片ID
|
||||
const uniqueImageIds = new Set();
|
||||
res?.forEach((item) => {
|
||||
if (item?.dkJsFj) uniqueImageIds.add(item.dkJsFj);
|
||||
if (item?.dkKsFj) uniqueImageIds.add(item.dkKsFj);
|
||||
});
|
||||
// 批量获取图片URL
|
||||
const imageEntries = await Promise.allSettled(
|
||||
Array.from(uniqueImageIds).map(async (id) => {
|
||||
try {
|
||||
const url = await getImageUrl(id);
|
||||
return { id, url };
|
||||
} catch (error) {
|
||||
return { id, url: null };
|
||||
}
|
||||
})
|
||||
);
|
||||
// 创建图片映射表
|
||||
const imageMap = new Map();
|
||||
imageEntries.forEach((entry) => {
|
||||
if (entry.status === "fulfilled") {
|
||||
imageMap.set(entry.value.id, entry.value.url);
|
||||
}
|
||||
});
|
||||
|
||||
// 设置数据
|
||||
data.info = res.map((item, index) => ({
|
||||
...item,
|
||||
count: getChineseNumber(index + 1),
|
||||
imgUrlDkJsFj: item?.dkJsFj ? imageMap.get(item.dkJsFj) : null,
|
||||
imgUrlDkKsFj: item?.dkKsFj ? imageMap.get(item.dkKsFj) : null,
|
||||
}));
|
||||
// 打卡结束经纬度
|
||||
const dkJs = res?.map((i) => ({ jd: i?.dkJsJd, wd: i?.dkJsWd }));
|
||||
// 打卡开始经纬度
|
||||
const dkKs = res?.map((i) => ({ jd: i?.dkKsJd, wd: i?.dkKsWd }));
|
||||
// 删除标注
|
||||
emitter.emit("deletePointArea", "annotationDkKs");
|
||||
//地图撒点然后移动
|
||||
emitter.emit("addPointArea", {
|
||||
coords: [...dkJs, ...dkKs],
|
||||
icon: require("../../assets/images/11.png"),
|
||||
flag: "annotationDkKs",
|
||||
});
|
||||
|
||||
await nextTick(() => {
|
||||
const firstItem = data.info[nextStep.value || 0];
|
||||
if (firstItem?.dkKsSj) {
|
||||
startTime.value = infoData?.value?.dkKsSj;
|
||||
start(startTime.value, activeInfoData.value.dkjgsj);
|
||||
} else {
|
||||
start("");
|
||||
}
|
||||
});
|
||||
}
|
||||
if (activeInfoData.value) {
|
||||
const result = await fetchSelectByBddxlrwId(
|
||||
activeInfoData?.value?.id || ""
|
||||
);
|
||||
if (result) {
|
||||
const { jd, wd } = result;
|
||||
// 删除标注
|
||||
emitter.emit("deletePointArea", "checkPoint");
|
||||
//地图撒点然后移动
|
||||
emitter.emit("addPointArea", {
|
||||
coords: [{ jd, wd }],
|
||||
icon: require("../../assets/lz/peoplePolice.png"),
|
||||
flag: "checkPoint",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
//获取当前位置信息
|
||||
function getUserLocation(sfdw) {
|
||||
let { lng, lat } = getLocation();
|
||||
if (lng && lat) {
|
||||
emitter.emit("deletePointArea", "dw");
|
||||
//地图撒点然后移动
|
||||
emitter.emit("addPointArea", {
|
||||
coords: [{ jd: lng, wd: lat }],
|
||||
icon: require("../../assets/lz/dw.png"),
|
||||
flag: "dw",
|
||||
sfdw: sfdw,
|
||||
sizeX: 30,
|
||||
sizeY: 35,
|
||||
});
|
||||
} else {
|
||||
hintToast("暂无坐标信息");
|
||||
}
|
||||
}
|
||||
|
||||
const getPatrolList = async ({ id }) => {
|
||||
const res = await selectByTaskInfo({ fgrwid: id });
|
||||
if (res) {
|
||||
getUserLocation();
|
||||
data.patroObj = res;
|
||||
// 删除方格
|
||||
emitter.emit("deletePointArea", "zdxl_fzyc");
|
||||
// 生成方格
|
||||
const { x1, y1, x2, y2, fgId, zxX, zxY, fgMc } = data.patroObj.fgdwVO;
|
||||
const centerPoint = [zxX, zxY];
|
||||
const position = [
|
||||
[Number(x1), Number(y1)],
|
||||
[Number(x2), Number(y2)],
|
||||
];
|
||||
const text = fgMc;
|
||||
const obj = [
|
||||
{ position: position, text, id: fgId, userData: activeInfoData?.value },
|
||||
];
|
||||
emitter.emit("echoPlane", {
|
||||
fontColor: "#12fdb8",
|
||||
coords: obj,
|
||||
type: "rectangle",
|
||||
flag: "zdxl_fzyc",
|
||||
color: "rgba(2,20,51,0.5)",
|
||||
linecolor: "#1C97FF",
|
||||
});
|
||||
emitter.emit("setMapCenter", { location: centerPoint, zoomLevel: 12 });
|
||||
await getData();
|
||||
}
|
||||
};
|
||||
|
||||
// 简单的时间加法函数
|
||||
const addTenMinutes = (timeString) => {
|
||||
if (!timeString) return "";
|
||||
const date = new Date(timeString);
|
||||
if (isNaN(date.getTime())) return "";
|
||||
date.setMinutes(date.getMinutes() + 10);
|
||||
return date
|
||||
.toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
})
|
||||
.replace(/\//g, "-");
|
||||
};
|
||||
|
||||
// 判断是否超过10分钟
|
||||
const isTenMinutesPassed = (startTime) => {
|
||||
if (!startTime) return false;
|
||||
try {
|
||||
const startDate = new Date(startTime);
|
||||
if (isNaN(startDate.getTime())) return false;
|
||||
// 计算10分钟后的时间
|
||||
const tenMinutesLater = new Date(startDate.getTime() + 10 * 60 * 1000);
|
||||
const now = new Date();
|
||||
// 判断当前时间是否超过10分钟后
|
||||
return now > tenMinutesLater;
|
||||
} catch (error) {
|
||||
console.error("时间判断错误:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 打卡
|
||||
const handleClick = async () => {
|
||||
if (data.info.length == 0) {
|
||||
hintToast("此方格没有必打卡点位!");
|
||||
return;
|
||||
}
|
||||
const { id } = data.info?.[nextStep.value];
|
||||
if (!isTenMinutesPassed(infoData?.value.dkKsSj) && infoData?.value.dkKsSj) {
|
||||
const newTime = addTenMinutes(infoData?.value.dkKsSj);
|
||||
hintToast(`请于${newTime.split(" ")[1]}后打卡`);
|
||||
return;
|
||||
}
|
||||
if (fileId.value === "") {
|
||||
hintToast("请拍照再打卡");
|
||||
return;
|
||||
}
|
||||
const { lng, lat } = getLocation();
|
||||
try {
|
||||
const res = await fetchTbZdxlFgdwBddxlrwJlClockIn({
|
||||
id,
|
||||
dkWd: lat,
|
||||
dkJd: lng,
|
||||
dkFj: fileId.value,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
hintToast(`打卡成功`);
|
||||
baseUrl.value = ""; //打卡成功需要把图片删除掉
|
||||
fileId.value = "";
|
||||
await getData();
|
||||
start();
|
||||
}
|
||||
} catch (error) {
|
||||
hintToast(`打卡异常`);
|
||||
stop();
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (route?.query) {
|
||||
data.query = route?.query;
|
||||
getPatrolList(route?.query);
|
||||
}
|
||||
window.setimagebase64 = setimage_base64;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<top-nav navTitle="打卡" :showLeft="true" />
|
||||
<div class="clockInWrapper">
|
||||
<van-tabs v-model:active="active" @click="onChange">
|
||||
<template v-for="(item, index) in data.patroObj?.bxds" :key="index">
|
||||
<van-tab :title="item.bxdMc" />
|
||||
</template>
|
||||
</van-tabs>
|
||||
<div class="clockInList">
|
||||
<template v-for="(item, index) in data.info" :key="index">
|
||||
<div :class="['clockInList_item', { active: nextStep === index }]" @click="handleNext(index)">
|
||||
<div class="label">{{ `第${item?.count}次打卡` }}</div>
|
||||
<div class="dec">
|
||||
<van-icon v-if="item?.dkKsSj" name="checked" color="#007DE9" />
|
||||
<div>开始</div>
|
||||
<div v-if="item?.dkKsSj" class="time">
|
||||
{{ item?.dkKsSj?.split(" ")[1] }}已打卡
|
||||
</div>
|
||||
</div>
|
||||
<div class="dec">
|
||||
<van-icon v-if="item?.dkJsSj" name="checked" color="#007DE9" />
|
||||
<div>离开</div>
|
||||
<div v-if="item?.dkJsSj" class="time">
|
||||
{{ item?.dkJsSj?.split(" ")[1] }}已打卡
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="upload_box">
|
||||
<div class="image_box" v-if="baseUrl">
|
||||
<van-icon name="close" class="close_icon" @click="clearImage" color="#000" size="24px" />
|
||||
<van-image :src="baseUrl" @click="onClickImg(baseUrl)" style="flex: 1">
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20" />
|
||||
</template>
|
||||
</van-image>
|
||||
</div>
|
||||
<div class="upload_icon_box" v-else>
|
||||
<van-icon @click="photoFn" color="#1DB1FF" name="plus" />
|
||||
<span>点击拍照</span>
|
||||
</div>
|
||||
<!-- <van-uploader v-model="clockList" :max-count="1" :after-read="afterRead" capture="camera"
|
||||
:before-read="beforeRead" accept="image/*" /> -->
|
||||
<div class="upload_tip">
|
||||
<span style="color: red">*</span>须拍摄实景图才可进行打卡
|
||||
</div>
|
||||
</div>
|
||||
<div class="clockWrapper">
|
||||
<div v-if="!infoData?.dkJsSj || !infoData?.dkKsSj" class="circleWrapper" :class="{
|
||||
disabled: (!isExpired && expirationTime) || infoData?.dkJsSj,
|
||||
}" @click="handleClick">
|
||||
<div v-if="!isExpired && expirationTime" class="time">
|
||||
{{ formattedTime }}后
|
||||
</div>
|
||||
<div class="title">{{ !infoData?.dkKsSj ? `开始` : `离开` }}</div>
|
||||
<div class="info">{{ `第${infoData?.count || ""}次打卡` }}</div>
|
||||
</div>
|
||||
<div v-else class="circleWrapper" :class="{ disabled: (!isExpired && expirationTime) || infoData?.dkJsSj, }">
|
||||
<div class="title">已结束</div>
|
||||
</div>
|
||||
<div class="circleWrapperTip">
|
||||
<van-icon name="success" color="#FFFFFF" />
|
||||
<div>已进入考勤范围:{{ activeInfoData?.bddMc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<timeline v-if="data.info.length > 0" :data="[data.info?.[nextStep]]" />
|
||||
<map-wrapper />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.clockInWrapper {
|
||||
margin-top: 13vw;
|
||||
padding: 2vw;
|
||||
|
||||
.clockWrapper {
|
||||
margin-top: 23.47vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.circleWrapperTip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 7.2vw;
|
||||
font-family: PingFang HK, PingFang HK;
|
||||
font-weight: 400;
|
||||
font-size: 3.73vw;
|
||||
color: #707070;
|
||||
|
||||
::v-deep {
|
||||
.van-icon-success {
|
||||
margin-right: 1.33vw;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 3.73vw;
|
||||
height: 3.73vw;
|
||||
background: #11aa66;
|
||||
border-radius: 13.33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.circleWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42.67vw;
|
||||
height: 42.67vw;
|
||||
background: linear-gradient(180deg, #1db1ff 0%, #007de9 100%);
|
||||
border-radius: 26.67vw;
|
||||
color: #fff;
|
||||
font-family: PingFang HK, PingFang HK;
|
||||
|
||||
.time {
|
||||
font-weight: 400;
|
||||
font-size: 4.8vw;
|
||||
margin-bottom: 1.33vw;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 400;
|
||||
font-size: 4.8vw;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-weight: 400;
|
||||
font-size: 3.73vw;
|
||||
margin-top: 1.33vw;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
background: #ededed !important;
|
||||
color: #75787f !important;
|
||||
}
|
||||
}
|
||||
|
||||
.upload_box {
|
||||
margin-top: 4vw;
|
||||
display: flex;
|
||||
padding-bottom: 4vw;
|
||||
border-bottom: 0.27vw solid #d9d9d9;
|
||||
|
||||
.upload_tip {
|
||||
color: #999999;
|
||||
font-size: 2.67vw;
|
||||
margin-left: 2.67vw;
|
||||
}
|
||||
|
||||
.image_box {
|
||||
position: relative;
|
||||
width: 40vw;
|
||||
height: 24vw;
|
||||
|
||||
.close_icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
::v-deep .van-image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.upload_icon_box {
|
||||
font-size: 3.2vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1db1ff;
|
||||
border: 0.27vw dashed #1db1ff;
|
||||
text-align: center;
|
||||
width: 29.33vw;
|
||||
height: 16.53vw;
|
||||
border-radius: 2.67vw;
|
||||
}
|
||||
}
|
||||
|
||||
.clockInList {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 4vw;
|
||||
overflow-y: auto;
|
||||
|
||||
.clockInList_item {
|
||||
padding: 4vw 2vw;
|
||||
width: 43vw;
|
||||
height: 18vw;
|
||||
background: #ededed;
|
||||
border-radius: 2.67vw;
|
||||
flex-shrink: 0;
|
||||
margin-right: 2.67vw;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 400;
|
||||
font-size: 4.27vw;
|
||||
}
|
||||
|
||||
.dec {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #75787f;
|
||||
font-size: 3.73vw;
|
||||
margin-top: 1.33vw;
|
||||
|
||||
.van-icon-checked {
|
||||
margin-right: 1.33vw;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: 1.33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background: rgba(62, 110, 232, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.van-tabs__nav {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.van-tabs__nav--line {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.van-tabs__wrap {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.van-tabs__line {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.van-tab {
|
||||
flex: initial;
|
||||
font-size: 4vw;
|
||||
width: auto !important;
|
||||
height: 9.33vw;
|
||||
padding: 0 2.67vw;
|
||||
border: 0.27vw solid #ededed;
|
||||
color: #75787f;
|
||||
flex-shrink: 0;
|
||||
border-radius: 2vw;
|
||||
margin-right: 2vw;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.van-tab--active {
|
||||
background: rgba(62, 110, 232, 0.2);
|
||||
border: 0.27vw solid #3e6ee8;
|
||||
color: #3e6ee8 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,11 +1,10 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import TopNav from "@/components/topNav.vue";
|
||||
import Tabs from "@/components/tabs.vue";
|
||||
import Search from "@/components/search.vue";
|
||||
import PatrolWrapper from "./copmonents/patrolWrapper.vue";
|
||||
import {fetchTaskList} from "@/api/patrolList";
|
||||
import {getDictListByCode} from "@/api/common";
|
||||
import { fetchTaskList } from "@/api/patrolList";
|
||||
import { getDictListByCode } from "@/api/common";
|
||||
import router from "@/router";
|
||||
import SxPopup from "@/components/SxPopup.vue";
|
||||
import SelectTime from "@/components/SelectTime.vue";
|
||||
@ -15,19 +14,8 @@ const showPopup = ref(false);
|
||||
const timeShow = ref(false);
|
||||
const finished = ref(false);
|
||||
const loading = ref(false);
|
||||
const rwlx=ref('1')
|
||||
const loadingRefresh = ref(false);
|
||||
const searchValue = ref("")
|
||||
const tabs = ref([
|
||||
{
|
||||
name: "警情任务",
|
||||
value: "1",
|
||||
},
|
||||
{
|
||||
name: "自定义任务",
|
||||
value: "2",
|
||||
},
|
||||
]);
|
||||
const pageData = reactive({
|
||||
pageSize: 10,
|
||||
pageCurrent: 1,
|
||||
@ -37,7 +25,8 @@ const pageData = reactive({
|
||||
const data = reactive({
|
||||
list: [],
|
||||
dictList: [],
|
||||
startTime: ''
|
||||
startTime: '',
|
||||
appRwlx: '01',//app任务类型 01当日任务 02历史任务
|
||||
})
|
||||
|
||||
const onSearch = () => {
|
||||
@ -62,67 +51,32 @@ const onLoad = () => {
|
||||
finished.value = true;
|
||||
return
|
||||
}
|
||||
|
||||
pageData.pageCurrent++
|
||||
getData()
|
||||
}
|
||||
|
||||
const handleClaim = (item) => {
|
||||
hintToast("领取成功");
|
||||
setTab('history')
|
||||
}
|
||||
|
||||
const handleClockInPage = (item) => {
|
||||
if (activeTab.value === 'history') {
|
||||
router.push({ path: '/taskHistoryDetail', query: { id: item.id } });
|
||||
} else {
|
||||
router.push({ path: '/clockInPage', query: { id: item.id, current: pageData.pageCurrent } });
|
||||
router.push({ path: '/taskHistoryDetail', query: { id: item.id, current: pageData.pageCurrent } });
|
||||
}
|
||||
}
|
||||
|
||||
const parseAndJoinLx = (jsonString, type = 'lx') => {
|
||||
if (!jsonString) return '';
|
||||
|
||||
try {
|
||||
let data = jsonString;
|
||||
// 如果是字符串,尝试解析为JSON
|
||||
if (typeof jsonString === 'string') {
|
||||
data = JSON.parse(jsonString);
|
||||
}
|
||||
|
||||
// 处理数组情况
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(item => item?.[type]).filter(Boolean).join(",");
|
||||
}
|
||||
// 处理对象情况
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
return data?.[type] || '';
|
||||
}
|
||||
return '';
|
||||
|
||||
} catch (error) {
|
||||
console.warn('数据处理失败:', error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
const { total, ...ret } = pageData
|
||||
const { total, ...ret } = pageData
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await fetchTaskList({ ...ret, fgMc: searchValue.value, rwRq: data.startTime,rwlx:rwlx.value })
|
||||
const res = await fetchTaskList({ ...ret, fgMc: searchValue.value, rwRq: data.startTime, appRwlx: data.appRwlx })
|
||||
if (res?.records.length > 0) {
|
||||
data.list = data.list.concat(res?.records)?.map((item) => ({
|
||||
...item,
|
||||
fgJqtjLx: parseAndJoinLx(item?.fgJqtjLx, 'lx'),
|
||||
fgYjdjLabel: data.dictList?.find(i => i.dm === item.fgYjdj)?.zdmc || item.fgYjdj
|
||||
})) || []
|
||||
|
||||
pageData.total = res?.total
|
||||
loading.value = false;
|
||||
|
||||
data.list = data.list.concat(res?.records) || []
|
||||
}
|
||||
pageData.total = res?.total
|
||||
loading.value = false;
|
||||
loadingRefresh.value = false;
|
||||
|
||||
} catch (error) {
|
||||
loading.value = false;
|
||||
loadingRefresh.value = false;
|
||||
@ -135,21 +89,6 @@ const getDictList = async () => {
|
||||
data.dictList = res?.itemList
|
||||
}
|
||||
}
|
||||
//按类型查询
|
||||
function onSelect(val) {
|
||||
data.list = [];
|
||||
pageData.pageCurrent = 1;
|
||||
switch (val) {
|
||||
case 0:
|
||||
rwlx.value="1"
|
||||
break;
|
||||
default:
|
||||
rwlx.value="2"
|
||||
break;
|
||||
}
|
||||
getData();
|
||||
finished.value = false;
|
||||
}
|
||||
const onSelectTime = (val) => {
|
||||
data.startTime = val;
|
||||
timeShow.value = false
|
||||
@ -159,24 +98,18 @@ const onClickTime = () => {
|
||||
timeShow.value = true;
|
||||
}
|
||||
|
||||
const activeTab = ref('today');
|
||||
const formatDate = (date) => {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
};
|
||||
const setTab = (tab) => {
|
||||
activeTab.value = tab;
|
||||
// if (tab === 'today') {
|
||||
// data.startTime = formatDate(new Date());
|
||||
// } else {
|
||||
// data.startTime = '';
|
||||
// }
|
||||
// pageData.pageCurrent = 1;
|
||||
// data.list = [];
|
||||
// getData();
|
||||
};
|
||||
const activeTab = ref('today');
|
||||
const setTab = (tab) => {
|
||||
activeTab.value = tab;
|
||||
if (tab === 'today') {
|
||||
data.appRwlx = '01'
|
||||
} else {
|
||||
data.appRwlx = '02'
|
||||
}
|
||||
pageData.pageCurrent = 1;
|
||||
data.list = [];
|
||||
getData();
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
pageData.pageCurrent = 1;
|
||||
@ -208,28 +141,16 @@ onMounted(async () => {
|
||||
<div class="segment-item" :class="{ active: activeTab === 'history' }" @click="setTab('history')">历史任务</div>
|
||||
</div>
|
||||
</div>
|
||||
<search
|
||||
:isSx="true"
|
||||
placeholder="请输入方格名称进行查询"
|
||||
v-model="searchValue"
|
||||
@update:sx="showPopup = !showPopup"
|
||||
@update:modelValue="onSearch"
|
||||
/>
|
||||
<search :isSx="true" placeholder="请输入方格名称进行查询" v-model="searchValue" @update:sx="showPopup = !showPopup"
|
||||
@update:modelValue="onSearch" />
|
||||
</div>
|
||||
</van-sticky>
|
||||
|
||||
<sx-popup :showPopup="showPopup" :p_top="110" @update:close="showPopup = false"
|
||||
@update:onConfirm="onConfirm" @reset="handleReset">
|
||||
<sx-popup :showPopup="showPopup" :p_top="150" @update:close="showPopup = false" @update:onConfirm="onConfirm"
|
||||
@reset="handleReset">
|
||||
<div class="time_box">
|
||||
<van-field
|
||||
v-model="data.startTime"
|
||||
label-width="60px"
|
||||
placeholder="请选择时间"
|
||||
input-align="left"
|
||||
right-icon="arrow-down"
|
||||
readonly
|
||||
@click.stop="onClickTime"
|
||||
/>
|
||||
<van-field v-model="data.startTime" label-width="60px" placeholder="请选择时间" input-align="left"
|
||||
right-icon="arrow-down" readonly @click.stop="onClickTime" />
|
||||
</div>
|
||||
</sx-popup>
|
||||
|
||||
@ -237,8 +158,10 @@ onMounted(async () => {
|
||||
|
||||
<div class="content">
|
||||
<van-pull-refresh v-model="loadingRefresh" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="onLoad" offset="1" :immediate-check="false">
|
||||
<patrol-wrapper :list="data?.list" :show-receive="activeTab === 'today'" @click="handleClockInPage" @claim="handleClaim" />
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="onLoad" offset="1"
|
||||
:immediate-check="false">
|
||||
<patrol-wrapper :list="data?.list" :show-receive="activeTab === 'today'" @click="handleClockInPage"
|
||||
@claim="handleClaim" />
|
||||
|
||||
<van-empty description="暂无采集数据" image="default" v-if="data.list.length <= 0 && loadingRefresh === false" />
|
||||
</van-list>
|
||||
@ -256,6 +179,7 @@ onMounted(async () => {
|
||||
padding: 0 4vw 3vw;
|
||||
box-sizing: border-box;
|
||||
margin-top: 13.5vw;
|
||||
|
||||
.segment-bg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -264,6 +188,7 @@ onMounted(async () => {
|
||||
border-radius: 6vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.segment-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
@ -272,6 +197,7 @@ onMounted(async () => {
|
||||
font-size: 3.73vw;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.segment-item.active {
|
||||
color: #ffffff;
|
||||
background: linear-gradient(90deg, #28A5FF 0%, #3E6EE8 100%);
|
||||
|
||||
@ -47,8 +47,7 @@ service.interceptors.response.use(
|
||||
return data; // 成功后返回解析后的数据
|
||||
} else if (resultCode == 0) {
|
||||
return data;
|
||||
}
|
||||
else {
|
||||
}else {
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user