Files
dy_app/src/pages/taskList/historyDetail.vue
2026-03-18 18:04:15 +08:00

382 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<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 { 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 = ref({});
const pointsList = ref([]);
const listQuery = ref({
dktp: "",
jd: "104.67926708276372",
wd: "31.038282761737406",
});
const zxdksj = ref("");
const dwIndex = ref();
const imageMap = new Map();
const getImageUrl = async (fileId) => {
if (!fileId) return null;
if (imageMap.has(fileId)) return imageMap.get(fileId);
const res = await qcckGet({}, `/mosty-base/minio/file/download/${fileId}`);
if (res?.url) {
const base64 = await getBase64("", res.url);
imageMap.set(fileId, base64);
return base64;
}
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 selectByTaskInfo({ fgrwid: id });
if (detail) {
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);
});
// 批量获取图片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");
});
</script>
<template>
<div class="page-container">
<TopNav nav-title="任务详情" show-left />
<div class="content">
<!-- Header Card -->
<div class="card header-card">
<div class="title">{{ info.fgRwmc }}</div>
<div class="row info-row">
<div class="sub">方格编号{{ info.fgmc }}</div>
<div class="sub">任务日期{{ info.xfrq }}</div>
</div>
<div class="sub">
打卡间隔时间{{ info.dkjgsj ? info.dkjgsj : "100min" }}
</div>
</div>
<!-- Point List -->
<div class="list">
<div v-for="(item, index) in pointsList" :key="index" class="card point-card">
<div class="row header-row">
<div class="name">{{ item.bxdMc }}</div>
<div class="status-tag">{{item.dkzt}}</div>
</div>
<!-- Checked In: Show Image -->
<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" @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>
</div>
</div>
</template>
<style scoped lang="scss">
.page-container {
min-height: 100vh;
background: #f5f7fa;
padding-bottom: 5vw;
}
.content {
padding: 16vw 3vw 5vw; // Top padding for fixed nav
}
.card {
background: #fff;
border-radius: 3.73vw;
padding: 4vw;
margin-bottom: 3vw;
box-shadow: 0 1.6vw 4vw rgba(0, 0, 0, 0.04);
}
.header-card {
.title {
font-size: 4.2vw;
font-weight: bold;
color: #333;
margin-bottom: 3vw;
}
.info-row {
display: flex;
justify-content: space-between;
margin-bottom: 1.5vw;
}
.sub {
font-size: 3.4vw;
color: #999;
}
}
.point-card {
.header-row {
display: flex;
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;
padding: 0.5vw 1.5vw;
border-radius: 1vw;
}
}
.img-container {
position: relative;
width: 100%;
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);
}
}
.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);
}
}
}
.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%;
}
}
}
</style>