Files
jg_app/src/pages/violationAlerts/index.vue
2026-04-28 15:03:03 +08:00

534 lines
11 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.

<template>
<div class="violation-alerts-page">
<!-- 顶部导航栏 -->
<TopNav title="违规任务" rightIcon="filter-o" :showRight="true"></TopNav>
<!-- 标签栏 -->
<div class="tabs-bar">
<button @click="activeTab = tab.id" v-for="tab in tabs" :key="tab.id" class="tab-item" :class="{ active: activeTab === tab.id }">
{{ tab.label }}
</button>
</div>
<!-- 违章列表 -->
<div class="alerts-list">
<div v-for="alert in filteredAlerts" :key="alert.id" class="alert-card">
<!-- 车牌信息 -->
<div class="card-header">
<div class="vehicle-info">
<van-icon name="logistics" class="vehicle-icon" />
<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="getStatusClass(alert.taskStatus)"> {{ statusMap[alert.taskStatus] }} </span>
</div>
<!-- 图片 -->
<div class="card-image">
<van-image :src="alert.imgUrl" fit="cover" class="alert-img"/>
</div>
<!-- 等级和标题 -->
<div class="card-title-row">
<span class="level-tag" :class="getLevelClass(alert.eventLevel)">
{{ levelMap[alert.eventLevel] }}
</span>
<span class="alert-title">{{ alert.eventContent }}</span>
</div>
<!-- 详细信息 -->
<div class="card-details">
<div class="detail-item">
<van-icon name="location-o" style="margin-right: 5px;" />
<span class="label">检测点位</span>
<span class="value">{{ alert.siteName }}</span>
</div>
<div class="detail-item">
<van-icon name="clock-o" style="margin-right: 5px;" />
<span class="label">检测时间</span>
<span class="value">{{ alert.eventTime }}</span>
</div>
</div>
<!-- 操作按钮 -->
<div class="card-actions">
<!-- 未执行/已完成/执行中 - 显示查看详情 -->
<van-button @click="handleDetail(alert)" block round type="primary" class="action-btn">
查看详情
</van-button>
</div>
</div>
<!-- 空状态 -->
<van-empty v-if="filteredAlerts.length === 0" description="暂无数据" class="empty-state"/>
</div>
<!-- 车牌匹配弹框 -->
<van-popup v-model:show="showMatchDialog" round position="center" class="match-popup">
<div class="popup-content">
<h2 class="popup-title">车牌匹配结果</h2>
<div class="popup-buttons">
<van-button block round class="match-btn success" @click="handleMatch(true)">
<van-icon name="checked" class="btn-icon" /> 车牌匹配
</van-button>
<van-button block round class="match-btn error" @click="handleMatch(false)">
<van-icon name="cross" class="btn-icon" /> 车牌不匹配
</van-button>
</div>
</div>
</van-popup>
</div>
</template>
<script setup>
import TopNav from "@/components/topNav.vue";
import { ref, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import { getTrafficEventList } from "@/api/traffic";
const router = useRouter();
// 状态
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: "not-executed", label: "未执行" },
{ id: "processing", label: "执行中" },
{ id: "completed", label: "已完成" },
];
// 查询参数
const listQuery = ref({
eventCategory: 2,
page: 1,
pageSize: 10,
taskStatus: "",
userId: "4"
});
// 分类映射
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;
}
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 handleDetail(alert) {
router.push({
path: "/violation-detail",
query: { id: alert.id, status: alert.status, tab: activeTab.value }
});
}
// 筛查来车
function handleScreenCar(violationId) {
selectedViolationId.value = violationId;
showMatchDialog.value = true;
}
// 车牌匹配
function handleMatch(matched) {
showMatchDialog.value = false;
if (matched && selectedViolationId.value) {
router.push({
path: "/dataReport",
query: { id: selectedViolationId.value }
});
}
}
// 切换标签时重新加载
watch(activeTab, () => {
resetList();
fetchAlerts();
});
// 页面初始化
onMounted(() => {
fetchAlerts();
});
</script>
<style lang="scss" scoped>
.violation-alerts-page {
height: 100vh;
background: #f1f5f9;
}
.tabs-bar {
display: flex;
gap: 16px;
background: white;
padding: 12px 16px;
overflow-x: auto;
.tab-item {
flex-shrink: 0;
padding: 6px 16px;
font-size: 14px;
font-weight: 500;
color: #64748b;
background: #f1f5f9;
border-radius: 20px;
transition: all 0.2s;
white-space: nowrap;
border: none;
&.active {
background: #2563eb;
color: white;
}
}
}
.alerts-list {
height: calc(100% - 13vw - 60px);
padding: 10px 16px;
box-sizing: border-box;
overflow: hidden;
overflow-y: auto;
}
.alert-card {
background: white;
border-radius: 20px;
overflow: hidden;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 16px 12px;
}
.vehicle-info {
display: flex;
align-items: center;
gap: 6px;
.vehicle-icon {
font-size: 18px;
color: #2563eb;
}
.plate-number {
font-size: 15px;
font-weight: 600;
color: #333;
}
.plate-color {
font-size: 14px;
color: #c5cad2;
margin-left: 4px;
}
.separator {
color: #d1d5db;
margin: 0 2px;
}
.vehicle-type {
font-size: 14px;
color: #c5cad2;
}
}
.status-tag {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
&.status-gray {
background: #f1f5f9;
color: #64748b;
border: 1px solid #e2e8f0;
}
&.status-blue {
background: #eff6ff;
color: #2563eb;
border: 1px solid #bfdbfe;
}
&.status-green {
background: #f0fdf4;
color: #16a34a;
border: 1px solid #bbf7d0;
}
}
.card-image {
padding: 0 16px 12px;
.alert-img {
width: 100%;
height: 180px;
border-radius: 12px;
}
}
.card-title-row {
display: flex;
align-items: center;
gap: 8px;
padding: 0 16px 12px;
.level-tag {
padding: 4px 8px;
border-radius: 8px;
font-size: 12px;
&.level-red {
background: #fee2e2;
color: #dc2626;
}
&.level-orange {
background: #ffedd5;
color: #ea580c;
}
&.level-yellow {
background: #fef9c3;
color: #ca8a04;
}
&.level-blue {
background: #dbeafe;
color: #2563eb;
}
}
.alert-title {
font-size: 15px;
font-weight: 500;
color: #333;
}
}
.card-details {
padding: 0 16px 12px;
.detail-item {
display: flex;
align-items: center;
margin-bottom: 8px;
font-size: 14px;
&:last-child {
margin-bottom: 0;
}
.detail-icon {
font-size: 16px;
color: #2563eb;
margin-right: 8px;
}
.label {
color: #94a3b8;
}
.value {
color: #475569;
}
}
}
.warning-box {
margin: 0 16px 12px;
background: #fff7ed;
border: 1px solid #fed7aa;
border-radius: 10px;
padding: 10px 12px;
font-size: 14px;
color: #ea580c;
.warning-dot {
margin-right: 6px;
}
}
.card-actions {
padding: 8px 16px 16px;
.action-btn {
height: 44px;
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
border: none;
border-radius: 12px;
font-size: 15px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
}
}
.empty-state {
padding: 60px 0;
}
.match-popup {
width: 90%;
max-width: 360px;
}
.popup-content {
padding: 24px;
}
.popup-title {
font-size: 18px;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 20px;
}
.popup-buttons {
display: flex;
flex-direction: column;
gap: 12px;
.match-btn {
height: 48px;
border-radius: 12px;
font-size: 16px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
&.success {
background: #16a34a;
border: none;
color: white;
}
&.error {
background: white;
border: 1px solid #ef4444;
color: #ef4444;
}
.btn-icon {
font-size: 20px;
}
}
}
</style>