修改
This commit is contained in:
58
package-lock.json
generated
58
package-lock.json
generated
@ -13,7 +13,9 @@
|
||||
"axios": "^0.27.2",
|
||||
"base-64": "^1.0.0",
|
||||
"core-js": "^3.8.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.3.3",
|
||||
"image-compressor.js": "^1.1.4",
|
||||
"mitt": "^3.0.0",
|
||||
"ol": "^6.15.1",
|
||||
"vant": "^3.4.3",
|
||||
@ -5012,6 +5014,12 @@
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/blueimp-canvas-to-blob": {
|
||||
"version": "3.29.0",
|
||||
"resolved": "https://registry.npmmirror.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
|
||||
"integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
@ -6370,6 +6378,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-color-names": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
|
||||
@ -10152,6 +10166,17 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/image-compressor.js": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/image-compressor.js/-/image-compressor.js-1.1.4.tgz",
|
||||
"integrity": "sha512-DF1YFSw+m6FqpXsleD4+q9eu/wFFkm8sHuYhgYy5GWFVencXeuB1/UqC12xz+dCZooPetf5LIb8JOGkgEWmlcg==",
|
||||
"deprecated": "No longer maintainted, please use `comprossorjs`",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"blueimp-canvas-to-blob": "^3.14.0",
|
||||
"is-blob": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||
@ -10496,6 +10521,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-blob": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-blob/-/is-blob-1.0.0.tgz",
|
||||
"integrity": "sha512-QIZDHQZpRfMEZwSTD7egdNZS7H/awVW9FZ3yJv+gg1z8d8GPXEs76QWL67fZs2BoBqp2dGtamTJpEYFJHmD73g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-boolean-object": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
||||
@ -26358,6 +26392,11 @@
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
},
|
||||
"blueimp-canvas-to-blob": {
|
||||
"version": "3.29.0",
|
||||
"resolved": "https://registry.npmmirror.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
|
||||
"integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
@ -27455,6 +27494,11 @@
|
||||
"randomfill": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"css-color-names": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
|
||||
@ -30390,6 +30434,15 @@
|
||||
"integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
|
||||
"dev": true
|
||||
},
|
||||
"image-compressor.js": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/image-compressor.js/-/image-compressor.js-1.1.4.tgz",
|
||||
"integrity": "sha512-DF1YFSw+m6FqpXsleD4+q9eu/wFFkm8sHuYhgYy5GWFVencXeuB1/UqC12xz+dCZooPetf5LIb8JOGkgEWmlcg==",
|
||||
"requires": {
|
||||
"blueimp-canvas-to-blob": "^3.14.0",
|
||||
"is-blob": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||
@ -30646,6 +30699,11 @@
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-blob": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-blob/-/is-blob-1.0.0.tgz",
|
||||
"integrity": "sha512-QIZDHQZpRfMEZwSTD7egdNZS7H/awVW9FZ3yJv+gg1z8d8GPXEs76QWL67fZs2BoBqp2dGtamTJpEYFJHmD73g=="
|
||||
},
|
||||
"is-boolean-object": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
"axios": "^0.27.2",
|
||||
"base-64": "^1.0.0",
|
||||
"core-js": "^3.8.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.3.3",
|
||||
"image-compressor.js": "^1.1.4",
|
||||
"mitt": "^3.0.0",
|
||||
"ol": "^6.15.1",
|
||||
"vant": "^3.4.3",
|
||||
|
||||
76
src/api/traffic.js
Normal file
76
src/api/traffic.js
Normal file
@ -0,0 +1,76 @@
|
||||
import { service } from '../utils/request';
|
||||
|
||||
/**
|
||||
* 获取交通预警事件列表
|
||||
* @param {Object} data - 查询参数
|
||||
* @param {number} data.eventCategory - 事件分类:1-路面事件,2-违章事件
|
||||
* @param {number} data.page - 当前页号
|
||||
* @param {number} data.pageSize - 每页大小
|
||||
*/
|
||||
export function getTrafficEventList(data) {
|
||||
return service({
|
||||
url: '/traffic/eventTask/page',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取交通预警事件详情
|
||||
* @param {string|number} taskId - 任务ID
|
||||
*/
|
||||
export function getTrafficEventDetail(taskId) {
|
||||
return service({
|
||||
url: `/traffic/eventTask/detail/${taskId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 定位打卡
|
||||
* @param {Object} data - 打卡参数
|
||||
* @param {string|number} data.taskId - 任务ID
|
||||
* @param {string} data.address - 地址
|
||||
* @param {string} data.clickLatLong - 经纬度
|
||||
*/
|
||||
export function clockIn(data) {
|
||||
return service({
|
||||
url: '/traffic/eventTask/click',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务反馈
|
||||
* @param {Object} data - 反馈参数
|
||||
* @param {string|number} data.taskId - 任务ID
|
||||
* @param {string} data.feedback - 反馈内容
|
||||
* @param {string} data.images - 图片URL(逗号拼接)
|
||||
* @param {string} data.video - 视频URL
|
||||
* @param {string} data.filesUrl - 附件URL
|
||||
* @param {string} data.singUrl - 签名URL
|
||||
*/
|
||||
export function feedback(data) {
|
||||
return service({
|
||||
url: '/traffic/eventTask/feedback',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传(图片/视频)
|
||||
* @param {File|Blob} file - 文件对象
|
||||
* @returns {Promise<string>} 上传后的URL
|
||||
*/
|
||||
export function uploadFile(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return service({
|
||||
url: '/common/upload',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
}
|
||||
@ -20,7 +20,7 @@ export function login(data) {
|
||||
*/
|
||||
export function idCardlogin(data) {
|
||||
return service({
|
||||
url:'/mosty-base/idCardNoLogin',
|
||||
url:'/auth/loginByIdCardNo',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
@ -52,7 +52,7 @@ export function getUserInfo(data) {
|
||||
// 新增的用户信息用于民辅警一起
|
||||
export function getUserOrFjInfo(params) {
|
||||
return service({
|
||||
url: `/mosty-base/sysUser/getUserOrFjInfo`,
|
||||
url: `/admin/users/getInfo`,
|
||||
method: "get",
|
||||
params
|
||||
});
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
<div class="preview-label">已拍照片:</div>
|
||||
<div class="photo-grid">
|
||||
<div v-for="(photo, index) in photos" :key="index" class="photo-item">
|
||||
<van-image :src="photo" fit="cover" class="photo-img">
|
||||
<van-image :src="photo.base64" fit="cover" class="photo-img">
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20" />
|
||||
</template>
|
||||
@ -68,7 +68,7 @@
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<div class="submit-bar">
|
||||
<van-button block round type="primary" class="submit-btn" @click="handleSave">
|
||||
<van-button block round type="primary" class="submit-btn" :loading="isSubmitting" @click="handleSave">
|
||||
保存
|
||||
</van-button>
|
||||
</div>
|
||||
@ -76,9 +76,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { Toast } from "vant";
|
||||
import { feedback, uploadFile } from "@/api/traffic";
|
||||
import { hintToast } from "@/utils/tools";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@ -94,8 +96,10 @@ const pageTitle = computed(() => {
|
||||
|
||||
// 表单数据
|
||||
const feedbackText = ref("");
|
||||
const photos = ref([]);
|
||||
// photos 存储 { base64, fileId } 对象,base64 用于预览,fileId 用于提交
|
||||
const photos = ref(['http://220.166.58.28:172/profile/upload/2026/04/15/bb_total_active_20260415150856A001.png']);
|
||||
const videos = ref([]);
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 返回
|
||||
function goBack() {
|
||||
@ -108,19 +112,17 @@ function handleTakePhoto() {
|
||||
if (window.bridge && window.bridge.pZ) {
|
||||
window.bridge.pZ("photo");
|
||||
} else {
|
||||
// H5 环境模拟
|
||||
const mockPhoto = "https://picsum.photos/400/300?random=" + Date.now();
|
||||
photos.value.push(mockPhoto);
|
||||
hintToast("请使用APP进行拍照");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("拍照失败:", err);
|
||||
Toast("请使用APP进行拍照");
|
||||
hintToast("请使用APP进行拍照");
|
||||
}
|
||||
}
|
||||
|
||||
// 录视频
|
||||
function handleTakeVideo() {
|
||||
Toast("视频录制功能开发中...");
|
||||
hintToast("视频录制功能开发中...");
|
||||
}
|
||||
|
||||
// 删除照片
|
||||
@ -133,38 +135,81 @@ function deleteVideo(index) {
|
||||
videos.value.splice(index, 1);
|
||||
}
|
||||
|
||||
// 保存反馈
|
||||
async function handleSave() {
|
||||
if (photos.value.length === 0 && videos.value.length === 0 && !feedbackText.value) {
|
||||
Toast("请至少上传照片或填写反馈内容");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Toast.loading({
|
||||
message: "提交中...",
|
||||
forbidClick: true,
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
// 模拟提交
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
Toast.clear();
|
||||
Toast.success("保存成功!");
|
||||
setTimeout(() => {
|
||||
router.back();
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
Toast.clear();
|
||||
Toast.fail("保存失败,请重试");
|
||||
// 将 base64 转为 Blob
|
||||
function base64ToBlob(base64) {
|
||||
const parts = base64.split(',');
|
||||
const mime = parts[0].match(/:(.*?);/)[1];
|
||||
const byteString = atob(parts[1]);
|
||||
const arrayBuffer = new ArrayBuffer(byteString.length);
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
uint8Array[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
return new Blob([arrayBuffer], { type: mime });
|
||||
}
|
||||
|
||||
// 接收原生APP返回的图片
|
||||
function setimagebase64(base64) {
|
||||
photos.value.push(`data:image/jpeg;base64,${base64}`);
|
||||
const preview = `data:image/jpeg;base64,${base64}`;
|
||||
const tempId = `uploading_${Date.now()}`;
|
||||
photos.value.push({ id: tempId, base64: preview, fileId: "" });
|
||||
const blob = base64ToBlob(base64);
|
||||
const fileName = `photo_${Date.now()}.jpg`;
|
||||
const file = new File([blob], fileName, { type: blob.type });
|
||||
uploadFile(file)
|
||||
.then((res) => {
|
||||
const idx = photos.value.findIndex((p) => p.id === tempId);
|
||||
if (idx !== -1) {
|
||||
photos.value[idx].fileId = res;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("图片上传失败:", e);
|
||||
const idx = photos.value.findIndex((p) => p.id === tempId);
|
||||
if (idx !== -1) {
|
||||
photos.value.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 保存反馈
|
||||
function handleSave() {
|
||||
if (!feedbackText.value.trim()) {
|
||||
hintToast("请输入反馈内容");
|
||||
return;
|
||||
}
|
||||
if (isSubmitting.value) return;
|
||||
isSubmitting.value = true;
|
||||
Toast.loading({ message: "提交中...", forbidClick: true, duration: 0 });
|
||||
|
||||
const images = photos.value.map((p) => p.fileId).filter(Boolean).join(",");
|
||||
feedback({
|
||||
taskId: alertId,
|
||||
feedback: feedbackText.value,
|
||||
images,
|
||||
video: "",
|
||||
filesUrl: "",
|
||||
singUrl: ""
|
||||
})
|
||||
.then(() => {
|
||||
Toast.clear();
|
||||
hintToast("保存成功");
|
||||
setTimeout(() => {
|
||||
router.back();
|
||||
}, 1000);
|
||||
})
|
||||
.catch((e) => {
|
||||
Toast.clear();
|
||||
hintToast(e.message || "保存失败,请重试");
|
||||
})
|
||||
.finally(() => {
|
||||
isSubmitting.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.setimagebase64 = setimagebase64;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -48,19 +48,19 @@
|
||||
<div class="tips-title">打卡说明</div>
|
||||
<ul class="tips-list">
|
||||
<li>请确保在指定地点范围内打卡</li>
|
||||
<li>请拍摄现场实景照片作为打卡凭证</li>
|
||||
<li>打卡成功后任务状态将更新为"执行中"</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- :disabled="!isLocationVerified || isSubmitting" -->
|
||||
<!-- 底部提交按钮 -->
|
||||
<div class="submit-bar">
|
||||
<van-button
|
||||
block
|
||||
round
|
||||
type="primary"
|
||||
:disabled="!photo || !isLocationVerified"
|
||||
|
||||
:loading="isSubmitting"
|
||||
class="submit-btn"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
@ -83,7 +83,8 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { Toast } from "vant";
|
||||
import { clockIn } from "@/api/traffic";
|
||||
import { hintToast } from "@/utils/tools";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@ -91,8 +92,10 @@ const route = useRoute();
|
||||
// 打卡数据
|
||||
const photo = ref("");
|
||||
const location = ref("正在获取位置...");
|
||||
const locationCoords = ref("");
|
||||
const isLocationVerified = ref(false);
|
||||
const fileInputRef = ref(null);
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 获取URL参数
|
||||
const taskId = route.query.id;
|
||||
@ -102,12 +105,18 @@ function goBack() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 模拟获取位置
|
||||
// 获取实际位置
|
||||
function initLocation() {
|
||||
setTimeout(() => {
|
||||
location.value = "中山路与发展大道交叉口";
|
||||
isLocationVerified.value = true;
|
||||
}, 1500);
|
||||
try {
|
||||
const loc = JSON.parse(bridge.getLocation());
|
||||
const addr = loc.address || loc.addr || "";
|
||||
location.value = addr || "未知位置";
|
||||
locationCoords.value = `${loc.app_x},${loc.app_y}`;
|
||||
isLocationVerified.value = !!(loc.app_x && loc.app_y);
|
||||
} catch (e) {
|
||||
location.value = "获取位置失败";
|
||||
isLocationVerified.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击拍照
|
||||
@ -125,7 +134,6 @@ function handleFileChange(event) {
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
// 重置 input 以允许重复选择同一文件
|
||||
event.target.value = "";
|
||||
}
|
||||
|
||||
@ -136,18 +144,28 @@ function deletePhoto() {
|
||||
|
||||
// 提交打卡
|
||||
function handleSubmit() {
|
||||
if (!photo.value) {
|
||||
Toast("请先拍照");
|
||||
return;
|
||||
}
|
||||
if (!isLocationVerified.value) {
|
||||
Toast("正在获取位置信息,请稍候");
|
||||
return;
|
||||
}
|
||||
Toast.success("打卡成功!");
|
||||
// if (!isLocationVerified.value) {
|
||||
// hintToast("正在获取位置信息,请稍候");
|
||||
// return;
|
||||
// }
|
||||
// if (isSubmitting.value) return;
|
||||
// isSubmitting.value = true;
|
||||
clockIn({
|
||||
taskId,
|
||||
address: '测试',
|
||||
// address: location.value,
|
||||
clickLatLong: "1,1",
|
||||
// clickLatLong: locationCoords.value,
|
||||
}).then(() => {
|
||||
hintToast("打卡成功");
|
||||
setTimeout(() => {
|
||||
router.back();
|
||||
}, 1000);
|
||||
}).catch((e) => {
|
||||
hintToast(e.message || "打卡失败");
|
||||
}).finally(() => {
|
||||
isSubmitting.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@ -61,6 +61,7 @@
|
||||
import "vant/es/toast/style";
|
||||
import Base64 from "base-64";
|
||||
import { login, idCardlogin, getUserOrFjInfo } from "../../api/user.js";
|
||||
import { encryptAES } from "../../utils/crypto.js";
|
||||
import { computed, ref, reactive, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useStore } from "vuex";
|
||||
@ -80,13 +81,17 @@ const form = reactive({
|
||||
});
|
||||
const countdown = ref(0);
|
||||
const isLoading = ref(false);
|
||||
const idCardForm = reactive({
|
||||
idCard: "510922199202281888"
|
||||
});
|
||||
onMounted(() => {
|
||||
// _idCardlogin(); // 已屏蔽自动登录
|
||||
_idCardlogin(); // 已屏蔽自动登录
|
||||
});
|
||||
//身份证登录
|
||||
function _idCardlogin() {
|
||||
try {
|
||||
let userinfo = JSON.parse(bridge.getLocation());
|
||||
// let userinfo = JSON.parse(bridge.getLocation());
|
||||
let sfzh='510922199202281888'
|
||||
Toast.loading({
|
||||
message: "登录中...",
|
||||
forbidClick: true,
|
||||
@ -95,11 +100,12 @@ function _idCardlogin() {
|
||||
duration: 0,
|
||||
});
|
||||
isLoading.value = true;
|
||||
form.userName = userinfo.app_sfzh;
|
||||
idCardlogin({ idCardNo: userinfo.app_sfzh })
|
||||
// form.userName = userinfo.app_sfzh;
|
||||
const encryptedIdCard = encryptAES(sfzh);
|
||||
idCardlogin({ username: encryptedIdCard })
|
||||
.then((res) => {
|
||||
const token = res.jwtToken;
|
||||
_getUserInfo(res.userId, res.idEntityCard, res.deptList[0].deptName, res.deptList);
|
||||
const token = res.accessToken;
|
||||
_getUserInfo();
|
||||
store.commit("setToken", token);
|
||||
})
|
||||
.catch((err) => {
|
||||
@ -118,6 +124,34 @@ const sendCode = async () => {
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// 身份证号格式校验
|
||||
function validateIdCard(val) {
|
||||
return /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(val);
|
||||
}
|
||||
|
||||
// 身份证登录提交
|
||||
function onIdCardSubmit() {
|
||||
Toast.loading({
|
||||
message: "登录中...",
|
||||
forbidClick: true,
|
||||
loadingType: "spinner",
|
||||
overlay: true,
|
||||
duration: 0,
|
||||
});
|
||||
isLoading.value = true;
|
||||
const encryptedIdCard = encryptAES(idCardForm.idCard);
|
||||
idCardlogin({ username: encryptedIdCard })
|
||||
.then((res) => {
|
||||
const token = res.jwtToken;
|
||||
_getUserInfo(res.userId, idCardForm.idCard, res.deptList[0].deptName, res.deptList);
|
||||
store.commit("setToken", token);
|
||||
})
|
||||
.catch((err) => {
|
||||
Toast.clear();
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
//手动登录
|
||||
const onSubmit = (e) => {
|
||||
if (TabActiveName.value === 'a') {
|
||||
@ -145,21 +179,10 @@ const onSubmit = (e) => {
|
||||
}
|
||||
};
|
||||
//获取用户信息
|
||||
function _getUserInfo(id, sfzh, deptName, deptList) {
|
||||
getUserOrFjInfo({ id, sfzh }).then((res) => {
|
||||
let { userName, id, idEntityCard, mobile, inDustRialId, sex } = res;
|
||||
let userinfo = {
|
||||
deptName,
|
||||
userName,
|
||||
deptList,
|
||||
id,
|
||||
idEntityCard,
|
||||
mobile,
|
||||
inDustRialId,
|
||||
sex,
|
||||
};
|
||||
store.commit("userStatus", userinfo);
|
||||
let path = getStorage("homeUrl");
|
||||
function _getUserInfo() {
|
||||
getUserOrFjInfo({}).then((res) => {
|
||||
console.log(res,'+++++++');
|
||||
store.commit("userStatus", res);
|
||||
setTimeout(() => {
|
||||
isLoading.value = false;
|
||||
router.replace("/Home");
|
||||
|
||||
@ -4,18 +4,18 @@
|
||||
<div class="header-bg">
|
||||
<div class="user-section">
|
||||
<div class="avatar">
|
||||
{{ userNameChar }}
|
||||
{{ userInfo.nickName?userInfo.nickName?.charAt(0):"" }}
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="user-name">{{ userInfo.userName || '张警官' }}</div>
|
||||
<div class="dept-name">{{ userInfo.deptName || '交警支队一大队' }}</div>
|
||||
<div class="user-name">{{ userInfo.nickName }}</div>
|
||||
<div class="dept-name">{{ userInfo.departName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前位置 -->
|
||||
<div class="location">
|
||||
<div class="location" v-if="userInfo.workAddress">
|
||||
<van-icon name="location" class="location-icon" />
|
||||
<span>{{ currentLocation || '武汉市江汉区中山大道与建设大道交叉口' }}</span>
|
||||
<span>{{ userInfo.workAddress }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -113,7 +113,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { List } from "vant";
|
||||
import BottomTabs from "@/components/bottomTabs.vue";
|
||||
@ -308,7 +308,9 @@ const currentPage = ref(0);
|
||||
const filteredAlerts = computed(() => {
|
||||
return allAlerts.value.filter(alert => alert.status === "未执行");
|
||||
});
|
||||
|
||||
onMounted(()=>{
|
||||
userInfo.value=JSON.parse(localStorage.getItem('userInfo'))
|
||||
})
|
||||
// 加载更多数据
|
||||
function onLoad() {
|
||||
// 模拟异步加载
|
||||
|
||||
@ -7,25 +7,29 @@
|
||||
<div class="nav-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<van-loading v-if="loading" class="loading-state" color="#2563eb">加载中...</van-loading>
|
||||
|
||||
<template v-else>
|
||||
<!-- 预警信息卡片 -->
|
||||
<div class="detail-card">
|
||||
<!-- 头部信息 -->
|
||||
<div class="card-header">
|
||||
<div class="header-left">
|
||||
<span class="level-tag" :class="alertDetail.levelClass">
|
||||
{{ alertDetail.level }}
|
||||
<span class="level-tag" :class="getLevelClass(alertDetail.eventLevel)">
|
||||
{{ levelMap[alertDetail.eventLevel] }}
|
||||
</span>
|
||||
<span class="alert-title">{{ alertDetail.title }}</span>
|
||||
<span class="alert-title">{{ alertDetail.eventType }}</span>
|
||||
</div>
|
||||
<span class="status-tag" :class="alertDetail.statusClass">
|
||||
{{ alertDetail.status }}
|
||||
<span class="status-tag" :class="getStatusClass(allDetail.taskStatus)">
|
||||
{{ statusMap[allDetail.taskStatus] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 图片 -->
|
||||
<div class="card-image" v-if="alertDetail.image">
|
||||
<van-image
|
||||
:src="alertDetail.image"
|
||||
:src="alertDetail.imgUrl"
|
||||
fit="cover"
|
||||
class="alert-img"
|
||||
/>
|
||||
@ -36,17 +40,22 @@
|
||||
<div class="detail-item">
|
||||
<van-icon name="clock" class="detail-icon" />
|
||||
<span class="label">检测时间:</span>
|
||||
<span class="value">{{ alertDetail.time }}</span>
|
||||
<span class="value">{{ alertDetail.eventTime }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<van-icon name="location" class="detail-icon" />
|
||||
<span class="label">检测地点:</span>
|
||||
<span class="value">{{ alertDetail.location }}</span>
|
||||
<span class="value">{{ alertDetail.siteName }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="alertDetail.tasksVo">
|
||||
<van-icon name="phone-o" class="detail-icon" />
|
||||
<span class="label">联系电话:</span>
|
||||
<span class="value">{{ alertDetail.phoneNumber }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 视频展示按钮 -->
|
||||
<div class="video-btn-wrapper">
|
||||
<van-button block round class="video-btn">
|
||||
<div class="video-btn-wrapper" v-if="alertDetail.videoUrls && alertDetail.videoUrls.length > 0">
|
||||
<van-button block round class="video-btn" @click="openVideo(alertDetail.videoUrls[0])">
|
||||
<van-icon name="video-o" class="video-icon" />
|
||||
视频展示
|
||||
</van-button>
|
||||
@ -61,116 +70,110 @@
|
||||
<!-- 蓝色边框卡片 -->
|
||||
<div class="execution-box">
|
||||
<!-- 绿色边框内层卡片 -->
|
||||
<div class="info-box">
|
||||
<div class="info-row">
|
||||
<span class="info-label">执行时间:</span>
|
||||
<span class="info-value">03/29 09:30</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-text">交通指挥中心</span>
|
||||
<span class="info-divider">|</span>
|
||||
<span class="info-text">联系电话:027-8888-8888</span>
|
||||
</div>
|
||||
<div class="info-box" v-if="taskDetail">
|
||||
<div class="info-row">
|
||||
<span class="info-label">任务地点:</span>
|
||||
<span class="info-value">{{ alertDetail.location }}</span>
|
||||
<span class="info-value">{{ taskDetail.clickAddress }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="taskDetail.deptName">
|
||||
<span class="info-text">{{ taskDetail.deptName }}</span>
|
||||
<span class="info-divider" v-if="taskDetail.phoneNumber">|</span>
|
||||
<span class="info-text" v-if="taskDetail.phoneNumber">联系电话:{{ taskDetail.phoneNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务描述 -->
|
||||
<div class="task-desc">
|
||||
协助疏导交通,确保道路畅通,维持现场秩序。
|
||||
<div class="task-desc" v-if="taskDetail">
|
||||
{{ taskDetail.taskContent }}
|
||||
</div>
|
||||
|
||||
<!-- 打卡情况 - 执行中和已完成状态显示 -->
|
||||
<div v-if="status === '执行中' || status === '已完成'" class="checkin-box">
|
||||
<!-- 打卡情况 -->
|
||||
<div v-if="checkinRecords.length > 0" class="checkin-box">
|
||||
<div v-for="(record, index) in checkinRecords" :key="index" class="checkin-row-wrapper">
|
||||
<div class="checkin-row">
|
||||
<span class="checkin-date">11-16</span>
|
||||
<span class="checkin-time">16:20:15</span>
|
||||
<span class="checkin-type">【签到打卡】</span>
|
||||
<span class="checkin-time">{{ record.date }}</span>
|
||||
<span class="checkin-type">{{ record.type }}</span>
|
||||
</div>
|
||||
<div class="checkin-row">
|
||||
<span class="checkin-label">打卡账号:</span>
|
||||
<span class="checkin-value">21515800</span>
|
||||
<span class="checkin-value">{{ record.account }}</span>
|
||||
</div>
|
||||
<div class="checkin-row checkin-location">
|
||||
<div class="checkin-row checkin-location" v-if="record.location">
|
||||
<van-icon name="location" class="location-icon" />
|
||||
<span>四川省德阳市旌阳区嘉明街道光兴街2号</span>
|
||||
<span>{{ record.location }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<!-- 已完成状态 - 显示三次反馈按钮 -->
|
||||
<div v-if="status === '已完成'" class="feedback-buttons">
|
||||
<van-button block round class="feedback-btn" @click="showFeedback(1)">
|
||||
第一次反馈结果
|
||||
</van-button>
|
||||
<van-button block round class="feedback-btn" @click="showFeedback(2)">
|
||||
第二次反馈结果
|
||||
</van-button>
|
||||
<van-button block round class="feedback-btn" @click="showFeedback(3)">
|
||||
第三次反馈结果
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<!-- 未执行状态 - 显示定位打卡按钮 -->
|
||||
<div v-else-if="status === '未执行'" class="action-buttons">
|
||||
<!-- 待派发状态 - 显示定位打卡按钮 -->
|
||||
<div v-if="showCheckInBtn" class="action-buttons">
|
||||
<van-button block round type="primary" class="action-btn" @click="handleCheckIn">
|
||||
定位打卡
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<!-- 执行中状态 - 显示反馈按钮 -->
|
||||
<div v-else-if="status === '执行中'" class="feedback-buttons">
|
||||
<van-button block round class="feedback-btn success" @click="showFeedback(1)">
|
||||
第一次反馈结果
|
||||
<!-- 执行中/已完成状态 - 显示反馈按钮 -->
|
||||
<div v-if="showFeedbackBtns" class="feedback-buttons">
|
||||
<van-button
|
||||
v-for="(feedback, index) in allDetail.feebacks"
|
||||
:key="index"
|
||||
block
|
||||
round
|
||||
class="feedback-btn success"
|
||||
@click="showFeedback(index + 1)"
|
||||
>
|
||||
第{{ index+1 }}次反馈结果
|
||||
</van-button>
|
||||
<van-button block round class="feedback-btn success" @click="showFeedback(2)">
|
||||
第二次反馈结果
|
||||
</van-button>
|
||||
<van-button block round type="primary" class="action-btn" @click="handleFeedback">
|
||||
<van-button
|
||||
v-if="allDetail.taskStatus == '1'"
|
||||
block
|
||||
round
|
||||
type="primary"
|
||||
class="action-btn"
|
||||
@click="handleFeedback"
|
||||
>
|
||||
立即反馈
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 反馈结果弹窗 -->
|
||||
<van-popup v-model:show="showResultDialog" round position="center" class="feedback-popup">
|
||||
<div class="popup-content">
|
||||
<div class="popup-content" v-if="currentFeedback.title">
|
||||
<h2 class="popup-title">{{ currentFeedback.title }}</h2>
|
||||
|
||||
<div class="popup-info">
|
||||
<p>反馈时间:{{ currentFeedback.time }}</p>
|
||||
<p>反馈时间:{{ currentFeedback.feedbackTime }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 现场照片 -->
|
||||
<div class="popup-section">
|
||||
<div class="popup-section" v-if="currentFeedback.images && currentFeedback.images.length > 0">
|
||||
<p class="section-label">现场照片:</p>
|
||||
<div class="image-grid">
|
||||
<van-image
|
||||
v-for="(img, idx) in currentFeedback.images"
|
||||
:key="idx"
|
||||
@click.stop="ImagePreview([img])"
|
||||
:src="img"
|
||||
fit="cover"
|
||||
class="feedback-img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 场地视频 -->
|
||||
<div class="popup-section">
|
||||
<!-- 现场照片 -->
|
||||
<div class="popup-section" v-if="currentFeedback.video && currentFeedback.video.length > 0">
|
||||
<p class="section-label">场地视频:</p>
|
||||
<div class="video-item">
|
||||
<van-icon name="video-o" class="video-icon" />
|
||||
<span>视频 1</span>
|
||||
<div class="image-grid">
|
||||
<span v-for="(img, idx) in currentFeedback.video"
|
||||
:key="idx">视频{{ idx+1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 反馈内容 -->
|
||||
<div class="popup-section">
|
||||
<div class="popup-section" v-if="currentFeedback.feedback">
|
||||
<h3 class="content-title">反馈内容</h3>
|
||||
<p class="content-text">{{ currentFeedback.content }}</p>
|
||||
<p class="content-text">{{ currentFeedback.feedback }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
@ -179,75 +182,141 @@
|
||||
</van-button>
|
||||
</div>
|
||||
</van-popup>
|
||||
|
||||
<!-- 视频播放弹窗 -->
|
||||
<van-popup v-model:show="showVideoDialog" round position="center" class="video-popup" :close-on-click-overlay="true">
|
||||
<div class="video-content">
|
||||
<video
|
||||
v-if="currentVideoUrl"
|
||||
:src="currentVideoUrl"
|
||||
controls
|
||||
autoplay
|
||||
class="video-player"
|
||||
/>
|
||||
<p v-if="!currentVideoUrl" class="video-tip">暂无视频</p>
|
||||
<van-icon name="cross" class="video-close" @click="showVideoDialog = false" />
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
||||
import { getTrafficEventDetail } from "@/api/traffic";
|
||||
import { ImagePreview } from 'vant';
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
// 获取URL参数
|
||||
const alertId = route.query.id || "1";
|
||||
const status = route.query.status || "未执行";
|
||||
const taskId = route.query.id || "";
|
||||
const statusText = route.query.status || "";
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 预警详情数据
|
||||
const alertDetail = ref({});
|
||||
const allDetail=ref({})
|
||||
const taskDetail=ref({})
|
||||
// 打卡记录
|
||||
const checkinRecords = ref([]);
|
||||
|
||||
// 反馈数据
|
||||
const feedbackList = ref([]);
|
||||
|
||||
// 反馈弹窗状态
|
||||
const showResultDialog = ref(false);
|
||||
const selectedFeedbackIndex = ref(1);
|
||||
const currentFeedback = ref({});
|
||||
|
||||
// 预警详情数据
|
||||
const alertDetail = ref({
|
||||
id: alertId,
|
||||
level: "一级",
|
||||
levelClass: "level-red",
|
||||
title: "交通事故",
|
||||
status: status,
|
||||
statusClass: computed(() => {
|
||||
if (status === "执行中") return "status-blue";
|
||||
if (status === "已完成") return "status-green";
|
||||
return "status-orange";
|
||||
}).value,
|
||||
image: "https://images.unsplash.com/photo-1772962622823-77778ff7b192?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmFmZmljJTIwYWNjaWRlbnQlMjBlbWVyZ2VuY3l8ZW58MXx8fHwxNzc0NjA1NzE3fDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "解放大道光谷广场",
|
||||
time: "03/29 08:00",
|
||||
issuer: "110指挥中心",
|
||||
phone: "027-110"
|
||||
});
|
||||
// 视频弹窗状态
|
||||
const showVideoDialog = ref(false);
|
||||
const currentVideoUrl = ref("");
|
||||
|
||||
// 反馈数据
|
||||
const feedbackData = {
|
||||
1: {
|
||||
title: "第一次反馈结果",
|
||||
time: "03/29 10:15",
|
||||
content: "已到达现场,正在疏导交通。现场车流量较大,已设置临时路障引导车辆分流。",
|
||||
images: [
|
||||
"https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=800&q=80",
|
||||
"https://images.unsplash.com/photo-1486006920555-c77dcf18193c?w=800&q=80"
|
||||
]
|
||||
},
|
||||
2: {
|
||||
title: "第二次反馈结果",
|
||||
time: "03/29 10:45",
|
||||
content: "交通疏导进行中,拥堵情况有所缓解。已联系拖车处理事故车辆,预计15分钟后完全恢复通行。",
|
||||
images: [
|
||||
"https://images.unsplash.com/photo-1485833077593-4278bba3f11f?w=800&q=80",
|
||||
"https://images.unsplash.com/photo-1502444330042-d1a1ddf9bb5b?w=800&q=80"
|
||||
]
|
||||
},
|
||||
3: {
|
||||
title: "第三次反馈结果",
|
||||
time: "03/29 11:10",
|
||||
content: "任务已完成,交通已恢复正常。事故车辆已清离现场,临时路障已撤除,道路完全畅通。",
|
||||
images: [
|
||||
"https://images.unsplash.com/photo-1464037866556-6812c9d1c72e?w=800&q=80",
|
||||
"https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=800&q=80"
|
||||
]
|
||||
}
|
||||
// 状态文本映射
|
||||
const statusMap = {
|
||||
0: "待执行",
|
||||
1: "执行中",
|
||||
2: "已完成",
|
||||
};
|
||||
|
||||
const currentFeedback = computed(() => feedbackData[selectedFeedbackIndex.value] || feedbackData[1]);
|
||||
// 等级映射
|
||||
const levelMap = {
|
||||
0: "一级",
|
||||
1: "二级",
|
||||
2: "三级",
|
||||
3: "四级"
|
||||
};
|
||||
|
||||
// 等级样式
|
||||
function getLevelClass(level) {
|
||||
const map = {
|
||||
1: "level-red",
|
||||
2: "level-orange",
|
||||
3: "level-blue"
|
||||
};
|
||||
return map[level] || "level-blue";
|
||||
}
|
||||
|
||||
// 状态样式
|
||||
function getStatusClass(status) {
|
||||
const map = {
|
||||
0: "status-gray",
|
||||
1: "status-blue",
|
||||
2: "status-green",
|
||||
};
|
||||
return map[status] || "status-gray";
|
||||
}
|
||||
|
||||
// 时间格式化
|
||||
function formatTime(timestamp) {
|
||||
if (!timestamp) return "";
|
||||
const date = new Date(timestamp);
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${month}/${day} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
if (!taskId) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getTrafficEventDetail(taskId);
|
||||
console.log(res, '详情res');
|
||||
|
||||
if (res) {
|
||||
const data = res.data || res;
|
||||
// 处理详情数据
|
||||
alertDetail.value = data.eventDetailVO;
|
||||
taskDetail.value = data.tasksVo;
|
||||
allDetail.value = data
|
||||
|
||||
// 处理打卡记录
|
||||
checkinRecords.value = (data.checkinRecords || data.signList || []).map(item => ({
|
||||
date: formatTime(item.signTime || item.createTime),
|
||||
type: item.type === 1 ? "【签到打卡】" : "【签退打卡】",
|
||||
account: item.userName || item.account || "",
|
||||
location: item.address || item.location || ""
|
||||
}));
|
||||
|
||||
// 处理反馈列表
|
||||
feedbackList.value = (data.feedbackList || data.reportList || []).map((item, index) => ({
|
||||
title: `${index + 1}反馈结果`,
|
||||
time: formatTime(item.reportTime || item.createTime),
|
||||
content: item.reportContent || item.content || "",
|
||||
images: item.imageUrls || item.images || []
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取详情失败:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 返回
|
||||
function goBack() {
|
||||
@ -258,7 +327,7 @@ function goBack() {
|
||||
function handleCheckIn() {
|
||||
router.push({
|
||||
path: "/checkInPage",
|
||||
query: { id: alertId }
|
||||
query: { id: taskId }
|
||||
});
|
||||
}
|
||||
|
||||
@ -266,20 +335,51 @@ function handleCheckIn() {
|
||||
function handleFeedback() {
|
||||
router.push({
|
||||
path: "/alert-handle",
|
||||
query: { id: alertId, status: status }
|
||||
query: { id: taskId, status: alertDetail.value.status }
|
||||
});
|
||||
}
|
||||
|
||||
// 显示反馈弹窗
|
||||
function showFeedback(index) {
|
||||
selectedFeedbackIndex.value = index;
|
||||
if (allDetail.value.feebacks[index - 1]) {
|
||||
currentFeedback.value = JSON.parse(JSON.stringify(allDetail.value.feebacks[index - 1]));
|
||||
if(typeof currentFeedback.value.images=='string' && currentFeedback.value.images){
|
||||
currentFeedback.value.images = currentFeedback.value.images.split(',')
|
||||
}
|
||||
if(typeof currentFeedback.value.video=='string' && currentFeedback.value.video){
|
||||
currentFeedback.value.video = currentFeedback.value.video.split(',')
|
||||
}
|
||||
currentFeedback.value.title=`第${index}次反馈结果`
|
||||
showResultDialog.value = true;
|
||||
}
|
||||
}
|
||||
//预览图片
|
||||
function onClickImg(url) {
|
||||
ImagePreview([url]);
|
||||
}
|
||||
// 打开视频弹窗
|
||||
function openVideo(url) {
|
||||
currentVideoUrl.value = url;
|
||||
showVideoDialog.value = true;
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
function closePopup() {
|
||||
showResultDialog.value = false;
|
||||
}
|
||||
|
||||
// 判断按钮显示
|
||||
const showCheckInBtn = computed(() => {
|
||||
return allDetail.value.taskStatus == "0";
|
||||
});
|
||||
|
||||
const showFeedbackBtns = computed(() => {
|
||||
return allDetail.value.taskStatus == "1" || allDetail.value.taskStatus == "2";
|
||||
});
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
fetchDetail();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -372,6 +472,11 @@ function closePopup() {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
&.status-gray {
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
&.status-orange {
|
||||
background: #fff7ed;
|
||||
@ -597,6 +702,7 @@ function closePopup() {
|
||||
|
||||
.popup-content {
|
||||
padding: 24px;
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
@ -680,4 +786,35 @@ function closePopup() {
|
||||
background: #d1d5db;
|
||||
}
|
||||
}
|
||||
|
||||
.video-content {
|
||||
position: relative;
|
||||
width: 90vw;
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.video-tip {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 40px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.video-close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -10,7 +10,7 @@
|
||||
<!-- 标签栏 -->
|
||||
<div class="tabs-bar">
|
||||
<button
|
||||
v-for="tab in displayTabs"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === tab.id }"
|
||||
@ -21,7 +21,18 @@
|
||||
</div>
|
||||
|
||||
<!-- 预警列表 -->
|
||||
<div class="alerts-list">
|
||||
<div class="alerts-list" ref="listRef">
|
||||
<!-- 加载状态 -->
|
||||
<van-loading v-if="initLoading" class="loading-state" color="#2563eb">加载中...</van-loading>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<van-list
|
||||
v-else
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoadMore"
|
||||
>
|
||||
<div
|
||||
v-for="alert in filteredAlerts"
|
||||
:key="alert.id"
|
||||
@ -32,24 +43,24 @@
|
||||
<div class="card-title-row">
|
||||
<span
|
||||
class="level-tag"
|
||||
:class="alert.levelClass"
|
||||
:class="getLevelClass(alert.eventLevel)"
|
||||
>
|
||||
{{ alert.level }}
|
||||
{{ levelMap[alert.eventLevel] }}
|
||||
</span>
|
||||
<span class="alert-title">{{ alert.title }}</span>
|
||||
<span class="alert-title">{{ alert.eventType }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="status-tag"
|
||||
:class="alert.statusTagClass"
|
||||
:class="getStatusClass(alert.taskStatus)"
|
||||
>
|
||||
{{ alert.status }}
|
||||
{{ statusMap[alert.taskStatus] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 图片 -->
|
||||
<div class="card-image" v-if="alert.image">
|
||||
<van-image
|
||||
:src="alert.image"
|
||||
:src="alert.imgUrl"
|
||||
fit="cover"
|
||||
class="alert-img"
|
||||
/>
|
||||
@ -60,20 +71,19 @@
|
||||
<div class="detail-item">
|
||||
<van-icon name="location" class="detail-icon" />
|
||||
<span class="label">检测点位:</span>
|
||||
<span class="value">{{ alert.location }}</span>
|
||||
<span class="value">{{ alert.siteName }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<van-icon name="clock" class="detail-icon" />
|
||||
<span class="label">检测时间:</span>
|
||||
<span class="value">{{ alert.time }}</span>
|
||||
<span class="value">{{ alert.eventTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="card-actions">
|
||||
<!-- 未执行/已完成 - 显示查看详情 -->
|
||||
<!-- 待执行/执行中/已完成 - 显示查看详情 -->
|
||||
<van-button
|
||||
v-if="alert.status === '未执行' || alert.status === '已完成'"
|
||||
block
|
||||
round
|
||||
type="primary"
|
||||
@ -83,9 +93,8 @@
|
||||
查看详情
|
||||
</van-button>
|
||||
|
||||
<!-- 待处理 - 显示定位打卡 -->
|
||||
<van-button
|
||||
v-else-if="alert.status === '待处理'"
|
||||
<!-- 待派发 - 显示定位打卡 -->
|
||||
<!-- <van-button
|
||||
block
|
||||
round
|
||||
type="primary"
|
||||
@ -93,25 +102,14 @@
|
||||
@click="handleCheckIn(alert)"
|
||||
>
|
||||
定位打卡
|
||||
</van-button>
|
||||
|
||||
<!-- 执行中 - 显示查看详情 -->
|
||||
<van-button
|
||||
v-else-if="alert.status === '执行中'"
|
||||
block
|
||||
round
|
||||
type="primary"
|
||||
class="action-btn"
|
||||
@click="handleDetail(alert)"
|
||||
>
|
||||
查看详情
|
||||
</van-button>
|
||||
</van-button> -->
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<van-empty
|
||||
v-if="filteredAlerts.length === 0"
|
||||
v-if="filteredAlerts.length === 0 && !initLoading"
|
||||
description="暂无数据"
|
||||
class="empty-state"
|
||||
/>
|
||||
@ -120,97 +118,142 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, onMounted, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getTrafficEventList } from "@/api/traffic";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const filteredAlerts = ref([]);
|
||||
const userInfo=ref(JSON.parse(localStorage.getItem('userInfo')))
|
||||
// 状态
|
||||
const activeTab = ref("all");
|
||||
const loading = ref(false);
|
||||
const initLoading = ref(false);
|
||||
const finished = ref(false);
|
||||
|
||||
// 标签栏
|
||||
const tabs = [
|
||||
const tabs = ref([
|
||||
{ id: "all", label: "全部" },
|
||||
{ id: "not-executed", label: "未执行" },
|
||||
{ id: "pending", label: "待处理" },
|
||||
{ id: "processing", label: "执行中" },
|
||||
{ id: "completed", label: "已完成" },
|
||||
];
|
||||
]);
|
||||
|
||||
// 显示的标签(过滤掉"待处理")
|
||||
const displayTabs = computed(() => {
|
||||
return tabs.filter(tab => tab.id !== "pending");
|
||||
const listQuery = ref({
|
||||
eventCategory: '1',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
taskStatus:"",
|
||||
userId:"4"
|
||||
// userId:userInfo.value.id
|
||||
});
|
||||
|
||||
// 预警数据(与 Greeting message 一致)
|
||||
const alerts = [
|
||||
{
|
||||
id: 1,
|
||||
level: "一级",
|
||||
levelClass: "level-red",
|
||||
title: "道路拥堵",
|
||||
status: "未执行",
|
||||
statusTagClass: "status-gray",
|
||||
image: "https://images.unsplash.com/photo-1771433053081-458d46481c09?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmFmZmljJTIwY29uZ2VzdGlvbiUyMHJvYWR8ZW58MXx8fHwxNzc0NjA1NTc2fDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "中山路与发展大道交叉口",
|
||||
time: "03/29 08:30",
|
||||
deadline: "03/29 12:00",
|
||||
issuer: "交通指挥中心",
|
||||
category: "all"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
level: "三级",
|
||||
levelClass: "level-blue",
|
||||
title: "道路施工",
|
||||
status: "未执行",
|
||||
statusTagClass: "status-gray",
|
||||
image: "https://images.unsplash.com/photo-1460533078824-f51edbff2726?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyb2FkJTIwY29uc3RydWN0aW9uJTIwd29ya3xlbnwxfHx8fDE3NzQ2MDI2MTJ8MA&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "上新河桥",
|
||||
time: "03/29 10:15",
|
||||
deadline: "03/29 15:00",
|
||||
issuer: "二大队李队长",
|
||||
category: "all"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
level: "二级",
|
||||
levelClass: "level-orange",
|
||||
title: "交通事故",
|
||||
status: "执行中",
|
||||
statusTagClass: "status-blue",
|
||||
image: "https://images.unsplash.com/photo-1772962622823-77778ff7b192?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmFmZmljJTIwYWNjaWRlbnQlMjBlbWVyZ2VuY3l8ZW58MXx8fHwxNzc0NjA1NzE3fDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "解放大道光谷广场",
|
||||
time: "03/29 07:20",
|
||||
deadline: "03/29 10:30",
|
||||
issuer: "110指挥中心",
|
||||
category: "processing"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
level: "三级",
|
||||
levelClass: "level-blue",
|
||||
title: "违章停车",
|
||||
status: "已完成",
|
||||
statusTagClass: "status-green",
|
||||
image: "https://images.unsplash.com/photo-1685582664602-24ea8d0a4ca7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmFmZmljJTIwcG9saWNlJTIwcGF0cm9sfGVufDF8fHx8MTc3NDYwNTgzNHww&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "武汉大道CBD商业区",
|
||||
time: "03/28 15:50",
|
||||
deadline: "03/28 17:00",
|
||||
issuer: "三大队王队长",
|
||||
category: "completed"
|
||||
// 分类映射:tabId -> status
|
||||
const categoryMap = {
|
||||
"all": null,
|
||||
"not-executed": 0, // 待派发
|
||||
"processing": 1, // 执行中
|
||||
"completed": 2 // 已完成
|
||||
};
|
||||
|
||||
// 状态文本映射
|
||||
const statusMap = {
|
||||
0: "待执行",
|
||||
1: "执行中",
|
||||
2: "已完成",
|
||||
};
|
||||
|
||||
// 等级映射
|
||||
const levelMap = {
|
||||
0: "一级",
|
||||
1: "二级",
|
||||
2: "三级",
|
||||
3: "四级"
|
||||
};
|
||||
|
||||
// 重置列表
|
||||
function resetList() {
|
||||
filteredAlerts.value = [];
|
||||
listQuery.value.page = 1;
|
||||
finished.value = false;
|
||||
}
|
||||
|
||||
// 获取列表数据
|
||||
const fetchAlerts = async (isLoadMore = false) => {
|
||||
if (isLoadMore) {
|
||||
loading.value = true;
|
||||
} else {
|
||||
initLoading.value = true;
|
||||
}
|
||||
];
|
||||
|
||||
// 过滤后的预警列表
|
||||
const filteredAlerts = computed(() => {
|
||||
if (activeTab.value === "all") return alerts;
|
||||
if (activeTab.value === "not-executed") return alerts.filter(a => a.status === "未执行");
|
||||
if (activeTab.value === "pending") return alerts.filter(a => a.status === "待处理");
|
||||
if (activeTab.value === "processing") return alerts.filter(a => a.status === "执行中");
|
||||
if (activeTab.value === "completed") return alerts.filter(a => a.status === "已完成");
|
||||
return alerts;
|
||||
});
|
||||
try {
|
||||
// 根据标签添加状态筛选
|
||||
listQuery.value.taskStatus = categoryMap[activeTab.value];
|
||||
const res = await getTrafficEventList(listQuery.value);
|
||||
console.log(res, 'res');
|
||||
if (res && res.content) {
|
||||
const records = res.content || [];
|
||||
if (isLoadMore) {
|
||||
filteredAlerts.value.push(...records);
|
||||
} else {
|
||||
filteredAlerts.value = records;
|
||||
}
|
||||
|
||||
// 判断是否加载完成
|
||||
const totalPages = res.content.totalPages || res.content.pages || 1;
|
||||
const currentPage = res.content.pageNum || res.content.current || listQuery.value.page;
|
||||
if (currentPage >= totalPages) {
|
||||
finished.value = true;
|
||||
}
|
||||
} else if (res && Array.isArray(res)) {
|
||||
if (isLoadMore) {
|
||||
filteredAlerts.value.push(...res);
|
||||
} else {
|
||||
filteredAlerts.value = res;
|
||||
}
|
||||
finished.value = true;
|
||||
} else {
|
||||
filteredAlerts.value = [];
|
||||
finished.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取交通预警列表失败:", error);
|
||||
if (!isLoadMore) {
|
||||
filteredAlerts.value = [];
|
||||
}
|
||||
finished.value = true;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
initLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载更多
|
||||
function onLoadMore() {
|
||||
listQuery.value.page++;
|
||||
fetchAlerts(true);
|
||||
}
|
||||
|
||||
// 等级样式
|
||||
function getLevelClass(level) {
|
||||
const map = {
|
||||
1: "level-red",
|
||||
2: "level-orange",
|
||||
3: "level-yellow",
|
||||
4: "level-blue",
|
||||
};
|
||||
return map[level] || "level-blue";
|
||||
}
|
||||
|
||||
// 状态样式
|
||||
function getStatusClass(status) {
|
||||
const map = {
|
||||
0: "status-gray",
|
||||
1: "status-blue",
|
||||
2: "status-green",
|
||||
};
|
||||
return map[status] || "status-gray";
|
||||
}
|
||||
|
||||
// 返回
|
||||
function goBack() {
|
||||
@ -232,6 +275,17 @@ function handleCheckIn(alert) {
|
||||
query: { id: alert.id }
|
||||
});
|
||||
}
|
||||
|
||||
// 切换标签时重新加载
|
||||
watch(activeTab, () => {
|
||||
resetList();
|
||||
fetchAlerts();
|
||||
});
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
fetchAlerts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -286,6 +340,7 @@ function handleCheckIn(alert) {
|
||||
border-radius: 20px;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
border: none;
|
||||
|
||||
&.active {
|
||||
background: #2563eb;
|
||||
|
||||
@ -10,11 +10,11 @@
|
||||
<!-- 标签栏 -->
|
||||
<div class="tabs-bar">
|
||||
<button
|
||||
v-for="tab in displayTabs"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === tab.id }"
|
||||
@click="handleTabChange(tab.id)"
|
||||
@click="activeTab = tab.id"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
@ -31,20 +31,20 @@
|
||||
<div class="card-header">
|
||||
<div class="vehicle-info">
|
||||
<van-icon name="cart" class="vehicle-icon" />
|
||||
<span class="plate-number">{{ alert.plateNumber }}</span>
|
||||
<span class="plate-number">{{ alert.plateNo }}</span>
|
||||
<span class="plate-color">{{ alert.plateColor }}</span>
|
||||
<span class="separator">|</span>
|
||||
<span class="vehicle-type">{{ alert.vehicleType }}</span>
|
||||
</div>
|
||||
<span class="status-tag" :class="alert.statusClass">
|
||||
{{ alert.status }}
|
||||
<span class="status-tag" :class="getStatusClass(alert.taskStatus)">
|
||||
{{ statusMap[alert.taskStatus] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 图片 -->
|
||||
<div class="card-image" v-if="alert.image">
|
||||
<van-image
|
||||
:src="alert.image"
|
||||
:src="alert.imgUrl"
|
||||
fit="cover"
|
||||
class="alert-img"
|
||||
/>
|
||||
@ -52,10 +52,10 @@
|
||||
|
||||
<!-- 等级和标题 -->
|
||||
<div class="card-title-row">
|
||||
<span class="level-tag" :class="alert.levelClass">
|
||||
{{ alert.level }}
|
||||
<span class="level-tag" :class="getLevelClass(alert.eventLevel)">
|
||||
{{ levelMap[alert.eventLevel] }}
|
||||
</span>
|
||||
<span class="alert-title">{{ alert.title }}</span>
|
||||
<span class="alert-title">{{ alert.eventContent }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
@ -63,12 +63,12 @@
|
||||
<div class="detail-item">
|
||||
<van-icon name="location" class="detail-icon" />
|
||||
<span class="label">检测点位:</span>
|
||||
<span class="value">{{ alert.location }}</span>
|
||||
<span class="value">{{ alert.siteName }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<van-icon name="clock" class="detail-icon" />
|
||||
<span class="label">检测时间:</span>
|
||||
<span class="value">{{ alert.time }}</span>
|
||||
<span class="value">{{ alert.eventTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -115,149 +115,148 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getTrafficEventList } from "@/api/traffic";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
// 状态
|
||||
const activeTab = ref("all");
|
||||
const loading = ref(false);
|
||||
const initLoading = ref(false);
|
||||
const finished = ref(false);
|
||||
const showMatchDialog = ref(false);
|
||||
const selectedViolationId = ref(null);
|
||||
|
||||
// 标签栏
|
||||
const tabs = [
|
||||
{ id: "all", label: "全部" },
|
||||
{ id: "unexecuted", label: "未执行" },
|
||||
{ id: "not-executed", label: "未执行" },
|
||||
{ id: "processing", label: "执行中" },
|
||||
{ id: "completed", label: "已完成" },
|
||||
];
|
||||
|
||||
// 显示的标签(违章预警不显示待处理)
|
||||
const displayTabs = computed(() => {
|
||||
return tabs;
|
||||
// 查询参数
|
||||
const listQuery = ref({
|
||||
eventCategory: 2,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
taskStatus: "",
|
||||
userId: "4"
|
||||
});
|
||||
|
||||
// 违章数据(与 Greeting message 一致)
|
||||
const violations = [
|
||||
{
|
||||
id: 1,
|
||||
plateNumber: "鄂A12345",
|
||||
plateColor: "蓝色",
|
||||
vehicleType: "小型汽车",
|
||||
level: "四级",
|
||||
levelClass: "level-blue",
|
||||
title: "违规停车",
|
||||
status: "执行中",
|
||||
statusClass: "status-blue",
|
||||
image: "https://images.unsplash.com/photo-1710939968666-a7447d3c584e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjYXIlMjBzaWxob3VldHRlJTIwc3Vuc2V0fGVufDF8fHx8MTc3NDYwODA4Mnww&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "江汉路步行街入口",
|
||||
time: "03/27 10:00",
|
||||
warning: "来车预警",
|
||||
category: "all"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
plateNumber: "鄂A99999",
|
||||
plateColor: "蓝色",
|
||||
vehicleType: "小型汽车",
|
||||
level: "三级",
|
||||
levelClass: "level-yellow",
|
||||
title: "闯红灯",
|
||||
status: "执行中",
|
||||
statusClass: "status-blue",
|
||||
image: "https://images.unsplash.com/photo-1449965408869-eaa3f722e40d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmFmZmljJTIwbGlnaHQlMjByZWR8ZW58MXx8fHwxNzc0NjA4MDg0fDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "解放大道循礼门路口",
|
||||
time: "03/27 09:15",
|
||||
warning: "来车预警",
|
||||
category: "all"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
plateNumber: "鄂A88888",
|
||||
plateColor: "蓝色",
|
||||
vehicleType: "小型汽车",
|
||||
level: "二级",
|
||||
levelClass: "level-orange",
|
||||
title: "违规变道",
|
||||
status: "已完成",
|
||||
statusClass: "status-green",
|
||||
image: "https://images.unsplash.com/photo-1756467988694-9953cd7ade0a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmFmZmljJTIwcG9saWNlJTIwZW5mb3JjZW1lbnR8ZW58MXx8fHwxNzc0NjA4MDgyfDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "中山大道民生路口",
|
||||
time: "03/26 15:20",
|
||||
warning: "来车预警",
|
||||
category: "completed"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
plateNumber: "鄂B33333",
|
||||
plateColor: "蓝色",
|
||||
vehicleType: "小型汽车",
|
||||
level: "一级",
|
||||
levelClass: "level-red",
|
||||
title: "违规掉头",
|
||||
status: "已完成",
|
||||
statusClass: "status-green",
|
||||
image: "https://images.unsplash.com/photo-1533473359331-0135ef1b58bf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjYXIlMjB0dXJuaW5nJTIwc3RyZWV0fGVufDF8fHx8MTc3NDYwODA4NXww&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "建设大道花桥街口",
|
||||
time: "03/26 11:50",
|
||||
warning: "来车预警",
|
||||
category: "completed"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
plateNumber: "鄂A77777",
|
||||
plateColor: "蓝色",
|
||||
vehicleType: "小型汽车",
|
||||
level: "二级",
|
||||
levelClass: "level-orange",
|
||||
title: "违规停车",
|
||||
status: "未执行",
|
||||
statusClass: "status-gray",
|
||||
image: "https://images.unsplash.com/photo-1710939968666-a7447d3c584e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjYXIlMjBzaWxob3VldHRlJTIwc3Vuc2V0fGVufDF8fHx8MTc3NDYwODA4Mnww&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "武昌区中南路",
|
||||
time: "04/09 08:30",
|
||||
warning: "来车预警",
|
||||
category: "all"
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
plateNumber: "鄂A66666",
|
||||
plateColor: "蓝色",
|
||||
vehicleType: "小型汽车",
|
||||
level: "三级",
|
||||
levelClass: "level-yellow",
|
||||
title: "违规掉头",
|
||||
status: "未执行",
|
||||
statusClass: "status-gray",
|
||||
image: "https://images.unsplash.com/photo-1533473359331-0135ef1b58bf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjYXIlMjB0dXJuaW5nJTIwc3RyZWV0fGVufDF8fHx8MTc3NDYwODA4NXww&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
location: "江汉区解放大道",
|
||||
time: "04/09 09:15",
|
||||
warning: "来车预警",
|
||||
category: "all"
|
||||
// 分类映射
|
||||
const categoryMap = {
|
||||
"all": null,
|
||||
"not-executed": 0,
|
||||
"processing": 1,
|
||||
"completed": 2
|
||||
};
|
||||
|
||||
// 列表数据
|
||||
const filteredAlerts = ref([]);
|
||||
|
||||
// 状态映射
|
||||
const statusMap = {
|
||||
0: "待执行",
|
||||
1: "执行中",
|
||||
2: "已完成",
|
||||
};
|
||||
|
||||
// 等级映射
|
||||
const levelMap = {
|
||||
0: "一级",
|
||||
1: "二级",
|
||||
2: "三级",
|
||||
3: "四级"
|
||||
};
|
||||
|
||||
// 重置列表
|
||||
function resetList() {
|
||||
filteredAlerts.value = [];
|
||||
listQuery.value.page = 1;
|
||||
finished.value = false;
|
||||
}
|
||||
|
||||
// 获取列表数据
|
||||
const fetchAlerts = async (isLoadMore = false) => {
|
||||
if (isLoadMore) {
|
||||
loading.value = true;
|
||||
} else {
|
||||
initLoading.value = true;
|
||||
}
|
||||
];
|
||||
|
||||
// 过滤后的列表
|
||||
const filteredAlerts = computed(() => {
|
||||
if (activeTab.value === "all") return violations;
|
||||
if (activeTab.value === "unexecuted") return violations.filter(v => v.status === "未执行");
|
||||
if (activeTab.value === "processing") return violations.filter(v => v.status === "执行中");
|
||||
if (activeTab.value === "completed") return violations.filter(v => v.status === "已完成");
|
||||
return violations;
|
||||
});
|
||||
try {
|
||||
listQuery.value.taskStatus = categoryMap[activeTab.value];
|
||||
const res = await getTrafficEventList(listQuery.value);
|
||||
if (res && res.content) {
|
||||
const records = res.content.records || res.content.list || res.content;
|
||||
if (isLoadMore) {
|
||||
filteredAlerts.value.push(...records);
|
||||
} else {
|
||||
filteredAlerts.value = records;
|
||||
}
|
||||
const totalPages = res.content.totalPages || res.content.pages || 1;
|
||||
const currentPage = res.content.pageNum || res.content.current || listQuery.value.page;
|
||||
if (currentPage >= totalPages) {
|
||||
finished.value = true;
|
||||
}
|
||||
} else if (res && Array.isArray(res)) {
|
||||
if (isLoadMore) {
|
||||
filteredAlerts.value.push(...res);
|
||||
} else {
|
||||
filteredAlerts.value = res;
|
||||
}
|
||||
finished.value = true;
|
||||
} else {
|
||||
filteredAlerts.value = [];
|
||||
finished.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取违章预警列表失败:", error);
|
||||
if (!isLoadMore) {
|
||||
filteredAlerts.value = [];
|
||||
}
|
||||
finished.value = true;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
initLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载更多
|
||||
function onLoadMore() {
|
||||
listQuery.value.page++;
|
||||
fetchAlerts(true);
|
||||
}
|
||||
|
||||
// 等级样式
|
||||
function getLevelClass(level) {
|
||||
const map = {
|
||||
1: "level-red",
|
||||
2: "level-orange",
|
||||
3: "level-yellow",
|
||||
4: "level-blue",
|
||||
};
|
||||
return map[level] || "level-blue";
|
||||
}
|
||||
|
||||
// 状态样式
|
||||
function getStatusClass(status) {
|
||||
const map = {
|
||||
0: "status-gray",
|
||||
1: "status-blue",
|
||||
2: "status-green",
|
||||
};
|
||||
return map[status] || "status-gray";
|
||||
}
|
||||
|
||||
// 返回
|
||||
function goBack() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 切换标签
|
||||
function handleTabChange(tabId) {
|
||||
activeTab.value = tabId;
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
function handleDetail(alert) {
|
||||
router.push({
|
||||
@ -282,6 +281,17 @@ function handleMatch(matched) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 切换标签时重新加载
|
||||
watch(activeTab, () => {
|
||||
resetList();
|
||||
fetchAlerts();
|
||||
});
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
fetchAlerts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
32
src/utils/crypto.js
Normal file
32
src/utils/crypto.js
Normal file
@ -0,0 +1,32 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
const KEY = 'GZFkEC$u*9xWdQsy';
|
||||
const IV = 'UKUEWnKv4l2KkZCX';
|
||||
|
||||
/**
|
||||
* AES CBC模式 PKCS5Padding加密
|
||||
*/
|
||||
export function encryptAES(plaintext) {
|
||||
const key = CryptoJS.enc.Utf8.parse(KEY);
|
||||
const iv = CryptoJS.enc.Utf8.parse(IV);
|
||||
const encrypted = CryptoJS.AES.encrypt(plaintext, key, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
return encrypted.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* AES CBC模式 PKCS5Padding解密
|
||||
*/
|
||||
export function decryptAES(ciphertext) {
|
||||
const key = CryptoJS.enc.Utf8.parse(KEY);
|
||||
const iv = CryptoJS.enc.Utf8.parse(IV);
|
||||
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||
}
|
||||
@ -3,21 +3,18 @@ import axios from 'axios';
|
||||
import { hintToast } from "./tools";
|
||||
let baseUrl2 = ''; //二类区地址
|
||||
let baseUrlZddwUrl = ''; //重点单位地址
|
||||
let zyURL = '/mosty-api';
|
||||
let gjURL = '/api';
|
||||
|
||||
try {
|
||||
let fwzxToken = bridge.getToken();
|
||||
baseUrl2 = `http://118.122.165.45:35623`;
|
||||
baseUrlZddwUrl = `http://118.122.165.45:35623`;
|
||||
zyURL = `http://118.122.165.45:35623/mosty-api`;
|
||||
// baseUrl2 = `http://172.20.19.221:8006`;
|
||||
// baseUrlZddwUrl = `http://172.20.19.221:8006`;
|
||||
// zyURL = `http://172.20.19.221:8006/mosty-api`;
|
||||
baseUrl2 = `http://220.166.58.28:172`;
|
||||
baseUrlZddwUrl = `http://220.166.58.28:172`;
|
||||
gjURL = `http://220.166.58.28:172/api`;
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: zyURL,
|
||||
baseURL: gjURL,
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
@ -28,7 +25,7 @@ service.interceptors.request.use(
|
||||
//1.统一注入token
|
||||
let token = window.localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = token;
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
//2.设置headers icode
|
||||
// config.headers.code = '';
|
||||
|
||||
@ -11,9 +11,8 @@ module.exports = {
|
||||
devServer: {
|
||||
port: 9528,
|
||||
proxy: {
|
||||
'/mosty-api': {
|
||||
target: "http://118.122.165.45:35623",
|
||||
// target: "http://192.168.31.91:8006",
|
||||
'/api': {
|
||||
target: "http://220.166.58.28:172",
|
||||
changeOrigin: true,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user