更新页面
This commit is contained in:
18
src/api/http.js
Normal file
18
src/api/http.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { service } from '../utils/request';
|
||||||
|
|
||||||
|
export const http = {
|
||||||
|
get(params = {}, url){
|
||||||
|
return service({ url, method: "get", params});
|
||||||
|
},
|
||||||
|
post(data = {}, url){
|
||||||
|
return service({ url, method: "post", data });
|
||||||
|
},
|
||||||
|
put(data = {}, url){
|
||||||
|
return service({ url, method: "put", data });
|
||||||
|
},
|
||||||
|
delete(data = {}, url){
|
||||||
|
return service({url,method: "delete",data});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
174
src/components/ChooseList.vue
Normal file
174
src/components/ChooseList.vue
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
<template>
|
||||||
|
<van-popup v-model:show="props.modelValue" class="training-popup">
|
||||||
|
<div class="head">{{ props.title }}</div>
|
||||||
|
<div class="companyBox">
|
||||||
|
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" offset="20" :immediate-check="false">
|
||||||
|
<van-cell @click="handleItemClick(item)" v-for="(item, index) in listData" :key="index" :title="item.name" clickable>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-checkbox v-model="item.checked" :shape="props.isRadio ? 'round' : 'square'" :checked="isSelected(item)" />
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
<van-empty v-if="listData.length <= 0" description="暂无数据" image="default" />
|
||||||
|
</van-list>
|
||||||
|
</div>
|
||||||
|
<div class="footBtns">
|
||||||
|
<van-button round block type="primary" class="btn" @click="handleClose">取消</van-button>
|
||||||
|
<van-button round block type="primary" class="btn" @click="submitForm">确认</van-button>
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Toast } from 'vant';
|
||||||
|
import { getItem } from "@/utils/storage";
|
||||||
|
import { http } from "@/api/http";
|
||||||
|
import { ref, defineProps, defineEmits, watch } from 'vue'
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isRadio: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择数据'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''//选择的类型
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
const finished = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const listData = ref([])
|
||||||
|
const selectedItems = ref([]);
|
||||||
|
const page = ref(0)
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 添加缓存对象,用于存储不同类型的完整数据
|
||||||
|
const dataCache = ref({})
|
||||||
|
|
||||||
|
|
||||||
|
// 获取对应的列表
|
||||||
|
const getList = () => {
|
||||||
|
// 检查缓存中是否已经有完整的数据
|
||||||
|
if (dataCache.value[props.type] && dataCache.value[props.type].length > 0) {
|
||||||
|
// 使用缓存数据
|
||||||
|
listData.value = dataCache.value[props.type];
|
||||||
|
total.value = dataCache.value[props.type].length;
|
||||||
|
finished.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = { pageNum: page.value, pageSize: 10 };
|
||||||
|
let url = null, methods = 'post'
|
||||||
|
let nameKey = ''
|
||||||
|
switch (props.type) {
|
||||||
|
case 'zfjly':
|
||||||
|
nameKey = 'xmmc'
|
||||||
|
try {
|
||||||
|
let siteArray = JSON.parse(getItem("userInfo").siteArray) || [];
|
||||||
|
listData.value = siteArray.map(site => ({ name: site.siteName, id: site.siteCode }));
|
||||||
|
} catch (error) { }
|
||||||
|
// url = '/mosty-base/baxx/pxxm/page' //接口需要换,现在不知道接口,这个是乱写的接口
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!url) return false;
|
||||||
|
loading.value = true;
|
||||||
|
http[methods](params, url).then(res => {
|
||||||
|
loading.value = false;
|
||||||
|
const arr = res.records.map((el) => {
|
||||||
|
return { name: el[nameKey], ...el }
|
||||||
|
}) || [];
|
||||||
|
listData.value = page.value == 1 ? arr : listData.value.concat(arr);
|
||||||
|
total.value = res.total;
|
||||||
|
// 当数据全部加载完成后,将数据存入缓存
|
||||||
|
if (listData.value.length >= total.value) {
|
||||||
|
dataCache.value[props.type] = listData.value;
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//触底加载
|
||||||
|
function onLoad() {
|
||||||
|
if (listData.value.length == total.value) return (finished.value = true);
|
||||||
|
page.value += 1;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSelected = (item) => {
|
||||||
|
return selectedItems.value.some((i) => i.id === item.id); // 多选
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择数据
|
||||||
|
const handleItemClick = (item) => {
|
||||||
|
if (props.isRadio) { //单选
|
||||||
|
selectedItems.value = [item]
|
||||||
|
} else { //多选
|
||||||
|
const index = selectedItems.value.findIndex((i) => i.id === item.id);
|
||||||
|
index > -1 ? selectedItems.value.splice(index, 1) : selectedItems.value.push(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 关闭
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const submitForm = () => {
|
||||||
|
if(selectedItems.value.length <= 0) return Toast('请至少选择一项');
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
emit('change', selectedItems.value)
|
||||||
|
}
|
||||||
|
watch(() => props.modelValue, val => {
|
||||||
|
if (val) {
|
||||||
|
// 重置分页状态,但保留listData以便在请求失败时显示缓存数据
|
||||||
|
page.value = 1;
|
||||||
|
finished.value = false;
|
||||||
|
loading.value = false;
|
||||||
|
// 只有在缓存为空时才清空listData
|
||||||
|
if (!dataCache.value[props.type]) {
|
||||||
|
listData.value = [];
|
||||||
|
}
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.companyBox {
|
||||||
|
height: 60vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head {
|
||||||
|
border-bottom: 1px solid #e7e7e7;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footBtns{
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
.btn{
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.training-popup {
|
||||||
|
width: 90%;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
226
src/components/popupView.vue
Normal file
226
src/components/popupView.vue
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<van-popup v-model:show="showPopup" position="bottom" round
|
||||||
|
:style="{ maxHeight: '70%', width: '98%', marginLeft: '1%' }">
|
||||||
|
<div class="popup-container">
|
||||||
|
<div class="popup-header" v-if="props.title && props.type == 'list'">
|
||||||
|
<div class="popup-title">{{ props.title }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- 列表选择 -->
|
||||||
|
<div class="popup-list" v-if="props.type == 'list'">
|
||||||
|
<van-empty v-if="!listData.length" :description="emptyText" />
|
||||||
|
<template v-else>
|
||||||
|
<van-cell v-for="(item, index) in listData" :key="index" :title="item[titleKey]"
|
||||||
|
@click="handleItemClick(item)" clickable>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-checkbox v-if="multiple" :checked="isSelected(item)" />
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="popup-footer" v-if="props.type == 'list'">
|
||||||
|
<span class="btn" @click="showPopup = false">取消</span>
|
||||||
|
<span class="gapline" v-if="multiple"></span>
|
||||||
|
<span class="btn" v-if="multiple" @click="handleConfirm">确认 {{ selectedItems.length }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间选择 -->
|
||||||
|
<van-datetime-picker v-if="props.type === 'datetime'" v-model="currentDate" type="datetime" title="选择日期"
|
||||||
|
:min-date="minDate" :max-date="maxDate" :formatter="formatter" @confirm="handleDateConfirm"
|
||||||
|
@cancel="showPopup = false" />
|
||||||
|
<!-- 选择时间 -->
|
||||||
|
<van-datetime-picker v-if="props.type === 'time'" v-model="currentTime" type="time" title="选择时间"
|
||||||
|
@confirm="handleDateConfirm" @cancel="showPopup = false" />
|
||||||
|
|
||||||
|
<!-- 日期选择 -->
|
||||||
|
<van-datetime-picker v-if="props.type === 'date'" v-model="currentDate" type="date" @confirm="handleDateConfirm"
|
||||||
|
@cancel="showPopup = false" :min-date="minDate" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { timeValidate } from "@/utils/tools.js";
|
||||||
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
const props = defineProps({
|
||||||
|
// 弹窗展示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 接受的数据
|
||||||
|
listData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
// 数据渲染字段
|
||||||
|
titleKey: {
|
||||||
|
type: String,
|
||||||
|
default: "text",
|
||||||
|
},
|
||||||
|
// 标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 是否多选
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 接受数据的类型 list 列表 date 日期 datetime 时间
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: "list",
|
||||||
|
},
|
||||||
|
// 空数据提示
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "暂无数据",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentTime = ref();
|
||||||
|
const currentDate = ref(new Date());
|
||||||
|
const minDate = ref(new Date(2020, 0, 1));
|
||||||
|
const maxDate = ref(new Date(new Date().getFullYear() + 10, 11, 31));
|
||||||
|
const emit = defineEmits(["update:show", "select"]);
|
||||||
|
|
||||||
|
const formatter = (type, val) => {
|
||||||
|
switch (type) {
|
||||||
|
case "year":
|
||||||
|
return `${val}年`;
|
||||||
|
case "month":
|
||||||
|
return `${val}月`;
|
||||||
|
case "day":
|
||||||
|
return `${val}日`;
|
||||||
|
case "hour":
|
||||||
|
return `${val}时`;
|
||||||
|
case "minute":
|
||||||
|
return `${val}分`;
|
||||||
|
default:
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedItems = ref([]);
|
||||||
|
|
||||||
|
const showPopup = computed({
|
||||||
|
get: () => props.show,
|
||||||
|
set: (val) => emit("update:show", val),
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleItemClick = (item) => {
|
||||||
|
if (props.multiple) {
|
||||||
|
const index = selectedItems.value.findIndex(
|
||||||
|
(i) => i[props.titleKey] === item[props.titleKey]
|
||||||
|
);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedItems.value.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
selectedItems.value.push(item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit("select", item);
|
||||||
|
showPopup.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSelected = (item) => {
|
||||||
|
return selectedItems.value.some(
|
||||||
|
(i) => i[props.titleKey] === item[props.titleKey]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
emit("select", selectedItems.value);
|
||||||
|
showPopup.value = false;
|
||||||
|
selectedItems.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 时间选择
|
||||||
|
const handleDateConfirm = (value) => {
|
||||||
|
if (props.type === "date") {
|
||||||
|
emit("select", { value: timeValidate(value, "ymd") });
|
||||||
|
} else if (props.type === "datetime") {
|
||||||
|
emit("select", { value: timeValidate(value) });
|
||||||
|
} else {
|
||||||
|
emit("select", { value: value });
|
||||||
|
}
|
||||||
|
showPopup.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//选择器字段
|
||||||
|
onMounted(() => {
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.popup-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 6px 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-footer {
|
||||||
|
padding: 16px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(183, 288, 231, 0.1);
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gapline {
|
||||||
|
width: 2px;
|
||||||
|
height: 30px;
|
||||||
|
background: #eee;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .van-cascader {
|
||||||
|
height: 370px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .van-tabs__track {
|
||||||
|
height: 320px;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -20,7 +20,6 @@
|
|||||||
<!-- 右下角浮动按钮 -->
|
<!-- 右下角浮动按钮 -->
|
||||||
<div class="float-buttons">
|
<div class="float-buttons">
|
||||||
<div class="float-btn" @click="handleLocationClick"><van-icon name="location" /></div>
|
<div class="float-btn" @click="handleLocationClick"><van-icon name="location" /></div>
|
||||||
<div class="float-btn"><van-icon name="layer" /></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 标记详情卡片 -->
|
<!-- 标记详情卡片 -->
|
||||||
@ -123,6 +122,10 @@ async function fetchUnfinishedEvents() {
|
|||||||
const res = await getEventUnfinished({ eventCategory, areaCode });
|
const res = await getEventUnfinished({ eventCategory, areaCode });
|
||||||
console.log('未完成预警事件:', res);
|
console.log('未完成预警事件:', res);
|
||||||
const list = Array.isArray(res) ? res : (res.data || []);
|
const list = Array.isArray(res) ? res : (res.data || []);
|
||||||
|
list.forEach(item => {
|
||||||
|
item.jd = 104.39013;
|
||||||
|
item.wd = 31.1244028;
|
||||||
|
});
|
||||||
markers.value = list;
|
markers.value = list;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
addMapMarkers(list);
|
addMapMarkers(list);
|
||||||
@ -149,7 +152,7 @@ function getMarkerColor(eventLevel) {
|
|||||||
function addMapMarkers(list) {
|
function addMapMarkers(list) {
|
||||||
if (!window.map || !list || list.length === 0) return;
|
if (!window.map || !list || list.length === 0) return;
|
||||||
|
|
||||||
const validList = list.filter(item => item.longitude && item.latitude);
|
const validList = list.filter(item => item.jd && item.wd);
|
||||||
|
|
||||||
validList.forEach(item => {
|
validList.forEach(item => {
|
||||||
const color = getMarkerColor(item.eventLevel);
|
const color = getMarkerColor(item.eventLevel);
|
||||||
@ -162,13 +165,14 @@ function addMapMarkers(list) {
|
|||||||
icon.style.cssText = 'color:#fff;font-size:18px;font-weight:bold;line-height:1;';
|
icon.style.cssText = 'color:#fff;font-size:18px;font-weight:bold;line-height:1;';
|
||||||
el.appendChild(icon);
|
el.appendChild(icon);
|
||||||
|
|
||||||
const marker = window.map.Marker(el, [item.longitude, item.latitude], { anchor: 'bottom' });
|
const marker = window.map.Marker(el, [item.jd, item.wd], { anchor: 'bottom' });
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
|
console.log('点击了标记', item);
|
||||||
selectedMarker.value = {
|
selectedMarker.value = {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
type: activeAlertType.value,
|
type: activeAlertType.value,
|
||||||
title: item.eventType || item.title || '预警事件',
|
title: item.msg || item.eventType || item.title || '预警事件',
|
||||||
location: item.siteName || item.location || '',
|
location: item.siteName || item.location || '',
|
||||||
time: item.eventTime || item.time || '',
|
time: item.eventTime || item.time || '',
|
||||||
rawData: item
|
rawData: item
|
||||||
|
|||||||
@ -23,11 +23,11 @@
|
|||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-value violation">{{ stats.violationCount }}</div>
|
<div class="stat-value violation">{{ stats.violationCount }}</div>
|
||||||
<div class="stat-label">违章预警</div>
|
<div class="stat-label">违规任务</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-value traffic">{{ stats.trafficCount }}</div>
|
<div class="stat-value traffic">{{ stats.trafficCount }}</div>
|
||||||
<div class="stat-label">路况预警</div>
|
<div class="stat-label">路况任务</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-value completed">{{ stats.completedCount }}</div>
|
<div class="stat-value completed">{{ stats.completedCount }}</div>
|
||||||
@ -62,7 +62,7 @@
|
|||||||
<span class="menu-label">执法点位</span>
|
<span class="menu-label">执法点位</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-right">
|
<div class="menu-right">
|
||||||
<span class="location-name">中山大道与建设大道交叉口</span>
|
<span class="location-name">{{ dataForm.sitPosition }}</span>
|
||||||
<van-icon name="arrow" class="arrow-icon" />
|
<van-icon name="arrow" class="arrow-icon" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -94,23 +94,29 @@
|
|||||||
|
|
||||||
<!-- 底部导航 -->
|
<!-- 底部导航 -->
|
||||||
<BottomTabs :active-tab="'profile'" />
|
<BottomTabs :active-tab="'profile'" />
|
||||||
|
<!-- 弹出框 -->
|
||||||
|
<ChooseList v-model="showCompany" title="选择执法点位" type="zfjly" @change="handleSeclt"></ChooseList>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import ChooseList from "@/components/ChooseList.vue";
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Dialog } from "vant";
|
import { Dialog } from "vant";
|
||||||
import BottomTabs from "@/components/bottomTabs.vue";
|
import BottomTabs from "@/components/bottomTabs.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const showCompany = ref(false);
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const userInfo = ref({});
|
const userInfo = ref({});
|
||||||
const userNameChar = computed(() => {
|
const dataForm = ref({
|
||||||
const name = userInfo.value?.userName;
|
sitPosition:'中山大道与建设大道交叉口'
|
||||||
return name ? name.charAt(0) : '?';
|
})
|
||||||
});
|
const handleSeclt = (val) => {
|
||||||
|
console.log("选择的执法点位:", val);
|
||||||
|
dataForm.value.sitPosition = val[0]?.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
// 预警语音开关
|
// 预警语音开关
|
||||||
const alarmVoiceEnabled = ref(true);
|
const alarmVoiceEnabled = ref(true);
|
||||||
@ -125,6 +131,7 @@ const stats = ref({
|
|||||||
|
|
||||||
// 执法点位点击
|
// 执法点位点击
|
||||||
function handleLocationClick() {
|
function handleLocationClick() {
|
||||||
|
showCompany.value = true;
|
||||||
console.log("执法点位");
|
console.log("执法点位");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,207 +12,153 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 表单内容 -->
|
<!-- 表单内容 -->
|
||||||
<div class="form-content">
|
<van-form @submit="handleSubmit" class="form">
|
||||||
<!-- 设备信息 -->
|
<div class="form-content">
|
||||||
<div class="section-card">
|
|
||||||
<div class="section-header" @click="toggleSection('device')">
|
|
||||||
<div class="section-title-box">
|
|
||||||
<span class="section-icon">▶</span>
|
|
||||||
<span class="section-title">设备信息</span>
|
|
||||||
</div>
|
|
||||||
<van-icon :name="openSections.device ? 'arrow-up' : 'arrow-down'" />
|
|
||||||
</div>
|
|
||||||
<div class="section-body" v-show="openSections.device">
|
|
||||||
<van-cell-group :border="false">
|
|
||||||
<van-field v-model="formData.sbbh" is-link readonly name="设备编号" label="设备编号" placeholder="请选择"
|
|
||||||
@click="openPicker({ columns: deviceColumns, title: '选择设备编号', onConfirm: onDeviceConfirm })" />
|
|
||||||
<van-field v-model="formData.zfmj" is-link readonly name="执法民警" label="执法民警" placeholder="请选择"
|
|
||||||
@click="openPicker({ columns: officerColumns, title: '选择执法民警', onConfirm: onOfficerConfirm })" />
|
|
||||||
</van-cell-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 车辆信息 -->
|
|
||||||
<div class="section-card">
|
<!-- 设备信息 -->
|
||||||
<div class="section-header" @click="toggleSection('vehicle')">
|
<div class="section-card">
|
||||||
<div class="section-title-box">
|
<div class="section-header" @click="toggleSection('device')">
|
||||||
<span class="section-icon">▶</span>
|
<div class="section-title-box">
|
||||||
<span class="section-title">车辆信息</span>
|
<span class="section-icon">▶</span>
|
||||||
</div>
|
<span class="section-title">设备信息</span>
|
||||||
<van-icon :name="openSections.vehicle ? 'arrow-up' : 'arrow-down'" />
|
|
||||||
</div>
|
|
||||||
<div class="section-body" v-show="openSections.vehicle">
|
|
||||||
<van-cell-group :border="false">
|
|
||||||
<van-field v-model="formData.clfl" is-link readonly name="车辆分类" label="车辆分类" placeholder="请选择"
|
|
||||||
@click="openPicker({ columns: clflColumns, title: '选择车辆分类', onConfirm: onClflConfirm })" />
|
|
||||||
<van-field v-model="formData.hpzl" is-link readonly name="号牌种类" label="号牌种类" placeholder="请选择"
|
|
||||||
@click="openPicker({ columns: hpzlColumns, title: '选择号牌种类', onConfirm: onHpzlConfirm })" />
|
|
||||||
<van-field v-model="formData.hppch" name="号牌牌号" label="号牌牌号" placeholder="请输入" maxlength="10" />
|
|
||||||
</van-cell-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 违法信息 -->
|
|
||||||
<div class="section-card">
|
|
||||||
<div class="section-header" @click="toggleSection('violation')">
|
|
||||||
<div class="section-title-box">
|
|
||||||
<span class="section-icon">▶</span>
|
|
||||||
<span class="section-title">违法信息</span>
|
|
||||||
</div>
|
|
||||||
<van-icon :name="openSections.violation ? 'arrow-up' : 'arrow-down'" />
|
|
||||||
</div>
|
|
||||||
<div class="section-body" v-show="openSections.violation">
|
|
||||||
<van-cell-group :border="false">
|
|
||||||
<van-field v-model="formData.wgxzq" is-link readonly name="非法行政区划" label="非法行政区划" placeholder="请输入非法行政区划"
|
|
||||||
@click="openPicker({ columns: xzqhColumns, title: '选择行政区划', onConfirm: onXzqhConfirm })" />
|
|
||||||
<van-field v-model="formData.wgdd" name="违法地点" label="违法地点" placeholder="请输入违法地点" />
|
|
||||||
<van-field v-model="formData.ldmkms" name="路段码公里数" label="路段码公里数" placeholder="请输入路段码公里数" />
|
|
||||||
<van-field v-model="formData.ddmcs" name="地点米数" label="地点米数" placeholder="请输入地点米数" />
|
|
||||||
<van-field v-model="formData.wfdz" name="违法地址" label="违法地址" placeholder="请输入违法地址" />
|
|
||||||
<van-field v-model="formData.wffssj" is-link readonly name="违法发生时间" label="违法发生时间" placeholder="请选择违法发生时间"
|
|
||||||
@click="showWffssjPicker = true" />
|
|
||||||
<van-field v-model="formData.wfsj" name="违法时间" label="违法时间" placeholder="HH:mm:ss" />
|
|
||||||
<van-field v-model="formData.scz" name="实测值" label="实测值" placeholder="请输入实测值" />
|
|
||||||
<van-field v-model="formData.bzz" name="标准值" label="标准值" placeholder="请输入标准值" />
|
|
||||||
<van-field v-model="formData.wffxjg" name="违法发现机关" label="违法发现机关" placeholder="请输入违法发现机关" />
|
|
||||||
</van-cell-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 照片信息 -->
|
|
||||||
<div class="section-card">
|
|
||||||
<div class="section-header" @click="toggleSection('photo')">
|
|
||||||
<div class="section-title-box">
|
|
||||||
<span class="section-icon">▶</span>
|
|
||||||
<span class="section-title">照片信息</span>
|
|
||||||
</div>
|
|
||||||
<van-icon :name="openSections.photo ? 'arrow-up' : 'arrow-down'" />
|
|
||||||
</div>
|
|
||||||
<div class="section-body" v-show="openSections.photo">
|
|
||||||
<!-- 照片上传卡片 -->
|
|
||||||
<div class="photo-upload-list">
|
|
||||||
<div class="photo-upload-item" v-for="(photo, index) in photoList" :key="index">
|
|
||||||
<div style="text-align: left;">照片{{ index + 1 }}</div>
|
|
||||||
<van-uploader :max-count="1" :after-read="(file) => onPhotoRead(file, index)" :preview-size="200"
|
|
||||||
:show-upload="!photo.url">
|
|
||||||
<div class="photo-upload-content" v-if="!photo.url">
|
|
||||||
<van-icon name="photograph" class="photo-icon" />
|
|
||||||
<div class="photo-text">点击拍照/上传</div>
|
|
||||||
</div>
|
|
||||||
</van-uploader>
|
|
||||||
<van-image
|
|
||||||
v-if="photo.url"
|
|
||||||
:src="photo.url"
|
|
||||||
class="photo-preview-img"
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
fit="cover"
|
|
||||||
radius="6"
|
|
||||||
preview
|
|
||||||
:preview-src-list="[photo.url]"
|
|
||||||
/>
|
|
||||||
<van-icon name="cross" class="photo-delete" v-if="photo.url" @click.stop="deletePhoto(index)" />
|
|
||||||
<van-field v-model="photo.url" label="图片链接" placeholder="请输入图片链接" class="photo-link-field" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<van-icon :name="openSections.device ? 'arrow-up' : 'arrow-down'" />
|
||||||
|
</div>
|
||||||
|
<div class="section-body" v-show="openSections.device">
|
||||||
|
<van-cell-group :border="false">
|
||||||
|
<van-field v-model="formData.sbbh" @click="openPicker('设备编号', 'sbbh')" is-link readonly name="设备编号" label="设备编号" placeholder="请选择" required :rules="[{ required: true, message: '请选择设备编号' }]"/>
|
||||||
|
<van-field v-model="formData.zfmj" @click="openPicker('执法民警', 'zfmj')" is-link readonly name="执法民警" label="执法民警" placeholder="请选择" required :rules="[{ required: true, message: '请选择执法民警' }]"/>
|
||||||
|
</van-cell-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 车辆信息 -->
|
||||||
|
<div class="section-card">
|
||||||
|
<div class="section-header" @click="toggleSection('vehicle')">
|
||||||
|
<div class="section-title-box">
|
||||||
|
<span class="section-icon">▶</span>
|
||||||
|
<span class="section-title">车辆信息</span>
|
||||||
|
</div>
|
||||||
|
<van-icon :name="openSections.vehicle ? 'arrow-up' : 'arrow-down'" />
|
||||||
|
</div>
|
||||||
|
<div class="section-body" v-show="openSections.vehicle">
|
||||||
|
<van-cell-group :border="false">
|
||||||
|
<van-field v-model="formData.clfl" @click="openPicker('车辆分类', 'clfl')" is-link readonly name="车辆分类" label="车辆分类" placeholder="请选择"/>
|
||||||
|
<van-field v-model="formData.hpzl" @click="openPicker('号牌种类', 'hpzl')" is-link readonly name="号牌种类" label="号牌种类" placeholder="请选择"/>
|
||||||
|
<van-field v-model="formData.hppch" name="号牌牌号" label="号牌牌号" placeholder="请输入" maxlength="10" />
|
||||||
|
</van-cell-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 违法信息 -->
|
||||||
|
<div class="section-card">
|
||||||
|
<div class="section-header" @click="toggleSection('violation')">
|
||||||
|
<div class="section-title-box">
|
||||||
|
<span class="section-icon">▶</span>
|
||||||
|
<span class="section-title">违法信息</span>
|
||||||
|
</div>
|
||||||
|
<van-icon :name="openSections.violation ? 'arrow-up' : 'arrow-down'" />
|
||||||
|
</div>
|
||||||
|
<div class="section-body" v-show="openSections.violation">
|
||||||
|
<van-cell-group :border="false">
|
||||||
|
<van-field v-model="formData.wgxzq" @click="openPicker('非法行政区划', 'wgxzq')" is-link readonly name="非法行政区划" label="非法行政区划" placeholder="请输入非法行政区划" />
|
||||||
|
<van-field v-model="formData.wgdd" name="违法地点" label="违法地点" placeholder="请输入违法地点" required :rules="[{ required: true, message: '请输入违法地点' }]" />
|
||||||
|
<van-field v-model="formData.ldmkms" name="路段码公里数" label="路段码公里数" placeholder="请输入路段码公里数" />
|
||||||
|
<van-field v-model="formData.ddmcs" name="地点米数" label="地点米数" placeholder="请输入地点米数" />
|
||||||
|
<van-field v-model="formData.wfdz" name="违法地址" label="违法地址" placeholder="请输入违法地址" />
|
||||||
|
<van-field v-model="formData.wffssj" @click="openPicker('违法发生时间', 'wffssj')" is-link readonly name="违法发生时间" label="违法发生时间" placeholder="请选择违法发生时间"/>
|
||||||
|
<van-field v-model="formData.wfsj" name="违法时间" label="违法时间" placeholder="HH:mm:ss" />
|
||||||
|
<van-field v-model="formData.scz" name="实测值" label="实测值" placeholder="请输入实测值" />
|
||||||
|
<van-field v-model="formData.bzz" name="标准值" label="标准值" placeholder="请输入标准值" />
|
||||||
|
<van-field v-model="formData.wffxjg" name="违法发现机关" label="违法发现机关" placeholder="请输入违法发现机关" />
|
||||||
|
</van-cell-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 照片信息 -->
|
||||||
|
<div class="section-card">
|
||||||
|
<div class="section-header" @click="toggleSection('photo')">
|
||||||
|
<div class="section-title-box">
|
||||||
|
<span class="section-icon">▶</span>
|
||||||
|
<span class="section-title">照片信息</span>
|
||||||
|
</div>
|
||||||
|
<van-icon :name="openSections.photo ? 'arrow-up' : 'arrow-down'" />
|
||||||
|
</div>
|
||||||
|
<div class="section-body" v-show="openSections.photo">
|
||||||
|
<!-- 照片上传卡片 -->
|
||||||
|
<div class="photo-upload-list">
|
||||||
|
<div class="photo-upload-item" v-for="(photo, index) in photoList" :key="index">
|
||||||
|
<div style="text-align: left;">照片{{ index + 1 }}</div>
|
||||||
|
<van-uploader :max-count="1" :after-read="(file) => onPhotoRead(file, index)" :preview-size="200"
|
||||||
|
:show-upload="!photo.url">
|
||||||
|
<div class="photo-upload-content" v-if="!photo.url">
|
||||||
|
<van-icon name="photograph" class="photo-icon" />
|
||||||
|
<div class="photo-text">点击拍照/上传</div>
|
||||||
|
</div>
|
||||||
|
</van-uploader>
|
||||||
|
<van-image v-if="photo.url" :src="photo.url" :preview-src-list="[photo.url]" class="photo-preview-img" width="100" height="100" fit="cover" radius="6" preview/>
|
||||||
|
<van-icon v-if="photo.url" @click.stop="deletePhoto(index)" name="cross" class="photo-delete" />
|
||||||
|
<van-field v-model="photo.url" label="图片链接" placeholder="请输入图片链接" class="photo-link-field" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 照片基本信息 -->
|
||||||
|
<van-cell-group :border="false">
|
||||||
|
<van-field v-model="formData.zpsl" name="照片数量" label="照片数量" type="number" placeholder="请输入照片数量" required :rules="[{ required: true, message: '请输入照片数量' }]" />
|
||||||
|
<van-field v-model="formData.tpmc" name="图片文件名" label="图片文件名" placeholder="请输入图片文件名" />
|
||||||
|
<van-field v-model="formData.tpfs" @click="openPicker('图片方式', 'tpfs')" is-link readonly name="图片方式" label="图片方式" placeholder="请选择图片方式"/>
|
||||||
|
<van-field v-model="formData.wfspdz" name="违法视频地址" label="违法视频地址" placeholder="请输入违法视频地址" />
|
||||||
|
</van-cell-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他信息 -->
|
||||||
|
<div class="section-card">
|
||||||
|
<div class="section-header" @click="toggleSection('other')">
|
||||||
|
<div class="section-title-box">
|
||||||
|
<span class="section-icon">▶</span>
|
||||||
|
<span class="section-title">其他信息</span>
|
||||||
|
</div>
|
||||||
|
<van-icon :name="openSections.other ? 'arrow-up' : 'arrow-down'" />
|
||||||
|
</div>
|
||||||
|
<div class="section-body" v-show="openSections.other">
|
||||||
|
<van-cell-group :border="false">
|
||||||
|
<van-field v-model="formData.qtlx" name="通知书号" label="通知书号" placeholder="请输入通知书号"/>
|
||||||
|
<van-field v-model="formData.tzrq" @click="openPicker('通知日期', 'tzrq')" is-link readonly name="通知日期" label="通知日期" placeholder="请输入通知日期" />
|
||||||
|
</van-cell-group>
|
||||||
</div>
|
</div>
|
||||||
<!-- 照片基本信息 -->
|
|
||||||
<van-cell-group :border="false">
|
|
||||||
<van-field v-model="formData.zpsl" name="照片数量" label="照片数量 *" placeholder="请选择照片数量" />
|
|
||||||
<van-field v-model="formData.tpmc" name="图片文件名" label="图片文件名" placeholder="请输入图片文件名" />
|
|
||||||
<van-field v-model="formData.tpfs" is-link readonly name="图片方式" label="图片方式" placeholder="请选择图片方式"
|
|
||||||
@click="openPicker({ columns: tpfsColumns, title: '选择图片方式', onConfirm: onTpfsConfirm })" />
|
|
||||||
<van-field v-model="formData.wfspdz" name="违法视频地址" label="违法视频地址" placeholder="请输入违法视频地址" />
|
|
||||||
</van-cell-group>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 底部提交按钮 -->
|
||||||
<!-- 其他信息 -->
|
<div class="bottom-action">
|
||||||
<div class="section-card">
|
<van-button type="primary" :loading="isLoading" block round color="#2563eb" native-type="submit">
|
||||||
<div class="section-header" @click="toggleSection('other')">
|
提交
|
||||||
<div class="section-title-box">
|
</van-button>
|
||||||
<span class="section-icon">▶</span>
|
|
||||||
<span class="section-title">其他信息</span>
|
|
||||||
</div>
|
|
||||||
<van-icon :name="openSections.other ? 'arrow-up' : 'arrow-down'" />
|
|
||||||
</div>
|
|
||||||
<div class="section-body" v-show="openSections.other">
|
|
||||||
<van-cell-group :border="false">
|
|
||||||
<van-field v-model="formData.qtlx" is-link readonly name="通知书号" label="通知书号" placeholder="请输入通知书号"
|
|
||||||
@click="openPicker({ columns: qtlxColumns, title: '选择通知书号', onConfirm: onQtlxConfirm })" />
|
|
||||||
<van-field v-model="formData.bz" name="通知日期" label="通知日期" placeholder="请输入通知日期" />
|
|
||||||
</van-cell-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</van-form>
|
||||||
|
|
||||||
<!-- 底部提交按钮 -->
|
|
||||||
<div class="bottom-action">
|
|
||||||
<van-button type="primary" block round color="#2563eb" @click="handleSubmit">
|
|
||||||
提交
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 通用选择器 -->
|
|
||||||
<van-popup v-model:show="pickerState.show" position="bottom">
|
|
||||||
<van-picker :columns="pickerState.columns" :title="pickerState.title" @confirm="onPickerConfirm" @cancel="pickerState.show = false" />
|
|
||||||
</van-popup>
|
|
||||||
|
|
||||||
<!-- 违法发生时间选择器 -->
|
|
||||||
<van-popup v-model:show="showWffssjPicker" position="bottom">
|
|
||||||
<van-datetime-picker v-model="currentDate" type="datetime" title="选择违法发生时间" :min-date="minDate"
|
|
||||||
:max-date="maxDate" @confirm="onWffssjConfirm" @cancel="showWffssjPicker = false" />
|
|
||||||
</van-popup>
|
|
||||||
</div>
|
</div>
|
||||||
|
<PopupView v-model:show="pop.show" :title="pop.title" :type="pop.type" :listData="pop.listData" @select="chooseDate"></PopupView>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import PopupView from '@/components/popupView.vue'
|
||||||
import { ref, reactive, onMounted } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { showToast, showConfirmDialog } from "vant";
|
import { Toast } from "vant";
|
||||||
import { upImage } from "@/api/common";
|
import { upImage } from "@/api/common";
|
||||||
|
const isLoading = ref(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const chooseType = ref(null) //选择的类型
|
||||||
// 日期选择器
|
const pop = reactive({
|
||||||
const currentDate = ref(new Date());
|
show: false,
|
||||||
const minDate = ref(new Date(2020, 0, 1));
|
title: "选择列表",
|
||||||
const maxDate = ref(new Date(new Date().getFullYear() + 10, 11, 31));
|
type: "list",
|
||||||
|
multiple: false,
|
||||||
|
listData: [],
|
||||||
|
})
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = reactive({
|
const formData = reactive({});
|
||||||
sbbh: "",
|
|
||||||
sbbhdm: "",
|
|
||||||
zfmj: "",
|
|
||||||
zfmjdm: "",
|
|
||||||
clfl: "",
|
|
||||||
clfldm: "",
|
|
||||||
hpzl: "",
|
|
||||||
hpzldm: "",
|
|
||||||
hppch: "",
|
|
||||||
wgxzq: "",
|
|
||||||
wgdd: "",
|
|
||||||
ldmkms: "",
|
|
||||||
ddmcs: "",
|
|
||||||
wfdz: "",
|
|
||||||
wffssj: "",
|
|
||||||
wfrq: "",
|
|
||||||
wfsj: "",
|
|
||||||
wffxjg: "",
|
|
||||||
zplj1: "",
|
|
||||||
zplj2: "",
|
|
||||||
zplj3: "",
|
|
||||||
zpsl: "3",
|
|
||||||
wfspdz: "",
|
|
||||||
tpfs: "",
|
|
||||||
tpfsdm: "",
|
|
||||||
tpmc: "",
|
|
||||||
qtlx: "",
|
|
||||||
qtlxdm: "",
|
|
||||||
bz: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
// 图片列表
|
|
||||||
const fileList = ref([]);
|
|
||||||
|
|
||||||
// 区域展开状态
|
// 区域展开状态
|
||||||
const openSections = reactive({
|
const openSections = reactive({
|
||||||
device: true,
|
device: true,
|
||||||
@ -229,57 +175,122 @@ const photoList = ref([
|
|||||||
{ label: "照片2", url: "", link: "" },
|
{ label: "照片2", url: "", link: "" },
|
||||||
{ label: "照片3", url: "", link: "" }
|
{ label: "照片3", url: "", link: "" }
|
||||||
]);
|
]);
|
||||||
const currentUploadIndex = ref(0);
|
|
||||||
const fileInputs = ref(null);
|
|
||||||
|
|
||||||
// 照片数量选择器选项
|
function chooseDate(val) {
|
||||||
const zpslColumns = [
|
console.log("选择的值:", val);
|
||||||
{ text: "1", value: "1" },
|
switch (pop.type) {
|
||||||
{ text: "2", value: "2" },
|
case 'list':
|
||||||
{ text: "3", value: "3" },
|
formData[chooseType.value] = val.text;
|
||||||
{ text: "4", value: "4" },
|
let dmKey = chooseType.value + 'dm';
|
||||||
{ text: "5", value: "5" }
|
formData[dmKey] = val.value;
|
||||||
];
|
break;
|
||||||
|
case 'datetime':
|
||||||
// 图片方式选择器选项
|
if(chooseType.value === 'wffssj') {
|
||||||
const tpfsColumns = [
|
formData.wffssj = val.value;
|
||||||
{ text: "手机拍摄", value: "01" },
|
formData.wfrq = val.value.slice(0, 10);
|
||||||
{ text: "相机拍摄", value: "02" },
|
formData.wfsj = val.value.slice(11, 19);
|
||||||
{ text: "监控截图", value: "03" },
|
}
|
||||||
{ text: "行车记录仪", value: "04" }
|
break;
|
||||||
];
|
case 'date':
|
||||||
|
formData[chooseType.value] = val.value;
|
||||||
// 选择器显示状态(通用)
|
break;
|
||||||
const pickerState = reactive({
|
|
||||||
show: false,
|
|
||||||
columns: [],
|
|
||||||
onConfirm: null,
|
|
||||||
title: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
function openPicker({ columns, onConfirm, title }) {
|
|
||||||
pickerState.columns = columns;
|
|
||||||
pickerState.title = title;
|
|
||||||
pickerState.onConfirm = onConfirm;
|
|
||||||
pickerState.show = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPickerConfirm({ selectedOptions }) {
|
|
||||||
if (pickerState.onConfirm) {
|
|
||||||
pickerState.onConfirm(selectedOptions);
|
|
||||||
}
|
}
|
||||||
pickerState.show = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择器显示状态
|
function openPicker(name,type) {
|
||||||
const showWffssjPicker = ref(false);
|
chooseType.value = type;
|
||||||
|
pop.type = 'list';
|
||||||
|
pop.title = `选择${name}`;
|
||||||
|
switch (name) {
|
||||||
|
case '设备编号':
|
||||||
|
pop.listData = [
|
||||||
|
{ text: "设备A", value: "001" },
|
||||||
|
{ text: "设备B", value: "002" },
|
||||||
|
{ text: "设备C", value: "003" }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case '执法民警':
|
||||||
|
pop.listData = [
|
||||||
|
{ text: "张三", value: "001" },
|
||||||
|
{ text: "李四", value: "002" },
|
||||||
|
{ text: "王五", value: "003" }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case '车辆分类':
|
||||||
|
pop.listData = [
|
||||||
|
{ text: "小型汽车", value: "01" },
|
||||||
|
{ text: "大型汽车", value: "02" },
|
||||||
|
{ text: "摩托车", value: "03" },
|
||||||
|
{ text: "电动车", value: "04" }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case '号牌种类':
|
||||||
|
pop.listData = [
|
||||||
|
{ text: "蓝色", value: "01" },
|
||||||
|
{ text: "黄色", value: "02" },
|
||||||
|
{ text: "绿色", value: "03" },
|
||||||
|
{ text: "白色", value: "04" },
|
||||||
|
{ text: "黑色", value: "05" }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case '非法行政区划':
|
||||||
|
pop.listData = [
|
||||||
|
{ text: "北京市", value: "110000" },
|
||||||
|
{ text: "天津市", value: "120000" },
|
||||||
|
{ text: "河北省", value: "130000" },
|
||||||
|
{ text: "山西省", value: "140000" },
|
||||||
|
{ text: "内蒙古", value: "150000" },
|
||||||
|
{ text: "辽宁省", value: "210000" },
|
||||||
|
{ text: "吉林省", value: "220000" },
|
||||||
|
{ text: "黑龙江省", value: "230000" },
|
||||||
|
{ text: "上海市", value: "310000" },
|
||||||
|
{ text: "江苏省", value: "320000" },
|
||||||
|
{ text: "浙江省", value: "330000" },
|
||||||
|
{ text: "安徽省", value: "340000" },
|
||||||
|
{ text: "福建省", value: "350000" },
|
||||||
|
{ text: "江西省", value: "360000" },
|
||||||
|
{ text: "山东省", value: "370000" },
|
||||||
|
{ text: "河南省", value: "410000" },
|
||||||
|
{ text: "湖北省", value: "420000" },
|
||||||
|
{ text: "湖南省", value: "430000" },
|
||||||
|
{ text: "广东省", value: "440000" },
|
||||||
|
{ text: "广西", value: "450000" },
|
||||||
|
{ text: "海南省", value: "460000" },
|
||||||
|
{ text: "重庆市", value: "500000" },
|
||||||
|
{ text: "四川省", value: "510000" },
|
||||||
|
{ text: "贵州省", value: "520000" },
|
||||||
|
{ text: "云南省", value: "530000" },
|
||||||
|
{ text: "西藏", value: "540000" },
|
||||||
|
{ text: "陕西省", value: "610000" },
|
||||||
|
{ text: "甘肃省", value: "620000" },
|
||||||
|
{ text: "青海省", value: "630000" },
|
||||||
|
{ text: "宁夏", value: "640000" },
|
||||||
|
{ text: "新疆", value: "650000" },
|
||||||
|
{ text: "台湾省", value: "710000" },
|
||||||
|
{ text: "香港", value: "810000" },
|
||||||
|
{ text: "澳门", value: "820000" }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case '违法发生时间':
|
||||||
|
pop.type = 'datetime';
|
||||||
|
break;
|
||||||
|
case '图片方式':
|
||||||
|
pop.listData = [
|
||||||
|
{ text: "手机拍摄", value: "01" },
|
||||||
|
{ text: "相机拍摄", value: "02" },
|
||||||
|
{ text: "监控截图", value: "03" },
|
||||||
|
{ text: "行车记录仪", value: "04" }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case '通知日期':
|
||||||
|
pop.type = 'date';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pop.show = true;
|
||||||
|
console.log(pop);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 选择器列数据(模拟数据)
|
|
||||||
const deviceColumns = [
|
|
||||||
{ text: "设备001", value: "001" },
|
|
||||||
{ text: "设备002", value: "002" },
|
|
||||||
{ text: "设备003", value: "003" }
|
|
||||||
];
|
|
||||||
|
|
||||||
const officerColumns = [
|
const officerColumns = [
|
||||||
{ text: "张三", value: "001" },
|
{ text: "张三", value: "001" },
|
||||||
@ -287,152 +298,13 @@ const officerColumns = [
|
|||||||
{ text: "王五", value: "003" }
|
{ text: "王五", value: "003" }
|
||||||
];
|
];
|
||||||
|
|
||||||
const clflColumns = [
|
|
||||||
{ text: "小型汽车", value: "01" },
|
|
||||||
{ text: "大型汽车", value: "02" },
|
|
||||||
{ text: "摩托车", value: "03" },
|
|
||||||
{ text: "电动车", value: "04" }
|
|
||||||
];
|
|
||||||
|
|
||||||
const hpzlColumns = [
|
|
||||||
{ text: "蓝色", value: "01" },
|
|
||||||
{ text: "黄色", value: "02" },
|
|
||||||
{ text: "绿色", value: "03" },
|
|
||||||
{ text: "白色", value: "04" },
|
|
||||||
{ text: "黑色", value: "05" }
|
|
||||||
];
|
|
||||||
|
|
||||||
const qtlxColumns = [
|
|
||||||
{ text: "闯红灯", value: "01" },
|
|
||||||
{ text: "逆行", value: "02" },
|
|
||||||
{ text: "超速", value: "03" },
|
|
||||||
{ text: "违规停车", value: "04" },
|
|
||||||
{ text: "违规变道", value: "05" },
|
|
||||||
{ text: "其他", value: "99" }
|
|
||||||
];
|
|
||||||
|
|
||||||
// 行政区划列数据
|
|
||||||
const xzqhColumns = [
|
|
||||||
{ text: "北京市", value: "110000" },
|
|
||||||
{ text: "天津市", value: "120000" },
|
|
||||||
{ text: "河北省", value: "130000" },
|
|
||||||
{ text: "山西省", value: "140000" },
|
|
||||||
{ text: "内蒙古", value: "150000" },
|
|
||||||
{ text: "辽宁省", value: "210000" },
|
|
||||||
{ text: "吉林省", value: "220000" },
|
|
||||||
{ text: "黑龙江省", value: "230000" },
|
|
||||||
{ text: "上海市", value: "310000" },
|
|
||||||
{ text: "江苏省", value: "320000" },
|
|
||||||
{ text: "浙江省", value: "330000" },
|
|
||||||
{ text: "安徽省", value: "340000" },
|
|
||||||
{ text: "福建省", value: "350000" },
|
|
||||||
{ text: "江西省", value: "360000" },
|
|
||||||
{ text: "山东省", value: "370000" },
|
|
||||||
{ text: "河南省", value: "410000" },
|
|
||||||
{ text: "湖北省", value: "420000" },
|
|
||||||
{ text: "湖南省", value: "430000" },
|
|
||||||
{ text: "广东省", value: "440000" },
|
|
||||||
{ text: "广西", value: "450000" },
|
|
||||||
{ text: "海南省", value: "460000" },
|
|
||||||
{ text: "重庆市", value: "500000" },
|
|
||||||
{ text: "四川省", value: "510000" },
|
|
||||||
{ text: "贵州省", value: "520000" },
|
|
||||||
{ text: "云南省", value: "530000" },
|
|
||||||
{ text: "西藏", value: "540000" },
|
|
||||||
{ text: "陕西省", value: "610000" },
|
|
||||||
{ text: "甘肃省", value: "620000" },
|
|
||||||
{ text: "青海省", value: "630000" },
|
|
||||||
{ text: "宁夏", value: "640000" },
|
|
||||||
{ text: "新疆", value: "650000" },
|
|
||||||
{ text: "台湾省", value: "710000" },
|
|
||||||
{ text: "香港", value: "810000" },
|
|
||||||
{ text: "澳门", value: "820000" }
|
|
||||||
];
|
|
||||||
|
|
||||||
// 切换区域展开状态
|
// 切换区域展开状态
|
||||||
function toggleSection(key) {
|
function toggleSection(key) {
|
||||||
openSections[key] = !openSections[key];
|
openSections[key] = !openSections[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择器确认事件
|
|
||||||
function onDeviceConfirm({ selectedOptions }) {
|
|
||||||
formData.sbbh = selectedOptions[0].text;
|
|
||||||
formData.sbbhdm = selectedOptions[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOfficerConfirm({ selectedOptions }) {
|
|
||||||
formData.zfmj = selectedOptions[0].text;
|
|
||||||
formData.zfmjdm = selectedOptions[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClflConfirm({ selectedOptions }) {
|
|
||||||
formData.clfl = selectedOptions[0].text;
|
|
||||||
formData.clfldm = selectedOptions[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onHpzlConfirm({ selectedOptions }) {
|
|
||||||
formData.hpzl = selectedOptions[0].text;
|
|
||||||
formData.hpzldm = selectedOptions[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onQtlxConfirm({ selectedOptions }) {
|
|
||||||
formData.qtlx = selectedOptions[0].text;
|
|
||||||
formData.qtlxdm = selectedOptions[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onXzqhConfirm({ selectedOptions }) {
|
|
||||||
formData.wgxzq = selectedOptions[0].text;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onZpslConfirm({ selectedOptions }) {
|
|
||||||
formData.zpsl = selectedOptions[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTpfsConfirm({ selectedOptions }) {
|
|
||||||
formData.tpfs = selectedOptions[0].text;
|
|
||||||
formData.tpfsdm = selectedOptions[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWffssjConfirm(value) {
|
|
||||||
// 格式化日期时间
|
|
||||||
const date = new Date(value);
|
|
||||||
const year = date.getFullYear();
|
|
||||||
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");
|
|
||||||
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
||||||
|
|
||||||
const dateTimeStr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
||||||
formData.wffssj = dateTimeStr;
|
|
||||||
formData.wfrq = `${year}-${month}-${day}`;
|
|
||||||
formData.wfsj = `${hours}:${minutes}:${seconds}`;
|
|
||||||
showWffssjPicker.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 触发上传
|
|
||||||
function triggerUpload(index) {
|
|
||||||
currentUploadIndex.value = index;
|
|
||||||
fileInputs.value.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件选择处理
|
|
||||||
function onFileChange(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
photoList.value[currentUploadIndex.value].url = e.target.result;
|
|
||||||
// 更新照片数量
|
|
||||||
const uploadedCount = photoList.value.filter(p => p.url).length;
|
|
||||||
formData.zpsl = uploadedCount.toString();
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
// 清空input值,允许重复选择同一张图片
|
|
||||||
event.target.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除照片
|
// 删除照片
|
||||||
function deletePhoto(index) {
|
function deletePhoto(index) {
|
||||||
photoList.value[index].url = "";
|
photoList.value[index].url = "";
|
||||||
@ -447,36 +319,6 @@ function onPhotoRead(file, index) {
|
|||||||
formData.zpsl = uploadedCount.toString();
|
formData.zpsl = uploadedCount.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片上传后处理
|
|
||||||
function afterRead(file) {
|
|
||||||
// 如果是多个文件,遍历处理
|
|
||||||
if (Array.isArray(file)) {
|
|
||||||
file.forEach(item => {
|
|
||||||
uploadImage(item);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
uploadImage(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadImage(file) {
|
|
||||||
// 模拟上传,实际项目中调用接口
|
|
||||||
file.status = "uploading";
|
|
||||||
file.message = "上传中...";
|
|
||||||
|
|
||||||
// 模拟上传成功
|
|
||||||
setTimeout(() => {
|
|
||||||
file.status = "success";
|
|
||||||
file.message = "";
|
|
||||||
showToast("图片上传成功");
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除图片前处理
|
|
||||||
function beforeDelete(file, item) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回上一页
|
// 返回上一页
|
||||||
function goBack() {
|
function goBack() {
|
||||||
router.back();
|
router.back();
|
||||||
@ -484,41 +326,16 @@ function goBack() {
|
|||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
// 表单验证
|
isLoading.value = true;
|
||||||
if (!formData.sbbh) {
|
// 提交数据
|
||||||
showToast("请选择设备编号");
|
const submitData = {
|
||||||
return;
|
...formData,
|
||||||
}
|
photos: photoList.value.filter(p => p.url).map(p => p.url)
|
||||||
if (!formData.zfmj) {
|
};
|
||||||
showToast("请选择执法民警");
|
console.log("提交数据:", submitData);
|
||||||
return;
|
Toast("提交成功");
|
||||||
}
|
router.back();
|
||||||
if (!formData.wgdd) {
|
|
||||||
showToast("请输入违法地点");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!formData.zpsl) {
|
|
||||||
showToast("请选择照片数量");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showConfirmDialog({
|
|
||||||
title: "提示",
|
|
||||||
message: "确认提交随手拍信息?"
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// 提交数据
|
|
||||||
const submitData = {
|
|
||||||
...formData,
|
|
||||||
photos: photoList.value.filter(p => p.url).map(p => p.url)
|
|
||||||
};
|
|
||||||
console.log("提交数据:", submitData);
|
|
||||||
showToast("提交成功");
|
|
||||||
router.back();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// 取消操作
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@ -3,150 +3,139 @@
|
|||||||
<!-- 顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<div class="nav-bar">
|
<div class="nav-bar">
|
||||||
<van-icon name="arrow-left" class="nav-back" @click="goBack" />
|
<van-icon name="arrow-left" class="nav-back" @click="goBack" />
|
||||||
<h1 class="nav-title">路况预警详情</h1>
|
<h1 class="nav-title">路况任务详情</h1>
|
||||||
<div class="nav-placeholder"></div>
|
<div class="nav-placeholder"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
<!-- 预警信息卡片 -->
|
||||||
<van-loading v-if="loading" class="loading-state" color="#2563eb">加载中...</van-loading>
|
<div class="detail-card">
|
||||||
|
<!-- 头部信息 -->
|
||||||
<template v-else>
|
<div class="card-header">
|
||||||
<!-- 预警信息卡片 -->
|
<div class="header-left">
|
||||||
<div class="detail-card">
|
<span class="level-tag" :class="getLevelClass(alertDetail.eventLevel)">
|
||||||
<!-- 头部信息 -->
|
{{ levelMap[alertDetail.eventLevel] }}
|
||||||
<div class="card-header">
|
|
||||||
<div class="header-left">
|
|
||||||
<span class="level-tag" :class="getLevelClass(alertDetail.eventLevel)">
|
|
||||||
{{ levelMap[alertDetail.eventLevel] }}
|
|
||||||
</span>
|
|
||||||
<span class="alert-title">{{ alertDetail.eventType }}</span>
|
|
||||||
</div>
|
|
||||||
<span class="status-tag" :class="getStatusClass(allDetail.taskStatus)">
|
|
||||||
{{ statusMap[allDetail.taskStatus] }}
|
|
||||||
</span>
|
</span>
|
||||||
|
<span class="alert-title">{{ alertDetail.eventType }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="status-tag" :class="getStatusClass(allDetail.taskStatus)">
|
||||||
<!-- 图片 -->
|
{{ statusMap[allDetail.taskStatus] }}
|
||||||
<div class="card-image">
|
</span>
|
||||||
<van-image
|
|
||||||
:src="alertDetail.imgUrl"
|
|
||||||
:error-icon="defaultImg"
|
|
||||||
fit="cover"
|
|
||||||
lazy-load
|
|
||||||
class="alert-img"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 详细信息 -->
|
|
||||||
<div class="card-details">
|
|
||||||
<div class="detail-item">
|
|
||||||
<van-icon name="clock" class="detail-icon" />
|
|
||||||
<span class="label">检测时间:</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.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" v-if="alertDetail.videoUrls && alertDetail.videoUrls.length > 0">
|
|
||||||
<van-button block round color="#2a7efe" class="video-btn" @click="openVideo(alertDetail.videoUrls[0])">
|
|
||||||
<van-icon name="video-o" class="video-icon" />
|
|
||||||
视频展示
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 执行记录卡片 -->
|
<!-- 图片 -->
|
||||||
<div class="record-card">
|
<div class="card-image">
|
||||||
<div class="card-title">执行记录</div>
|
<van-image :src="alertDetail.imgUrls" fit="cover" class="alert-img" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 蓝色边框卡片 -->
|
<!-- 详细信息 -->
|
||||||
<div class="execution-box">
|
<div class="card-details">
|
||||||
<!-- 绿色边框内层卡片 -->
|
<div class="detail-item">
|
||||||
<div class="info-box">
|
<van-icon name="clock" class="detail-icon" />
|
||||||
<div class="info-row">
|
<span class="label">检测时间:</span>
|
||||||
<span class="info-label">要求任务完成时间:</span>
|
<span class="value">{{ alertDetail.eventTime }}</span>
|
||||||
<span class="info-value">{{ taskDetail.eventTime }}</span>
|
</div>
|
||||||
</div>
|
<div class="detail-item">
|
||||||
<div class="info-row">
|
<van-icon name="location" class="detail-icon" />
|
||||||
<span class="info-text">{{ taskDetail.deptName }}</span>
|
<span class="label">检测地点:</span>
|
||||||
<span class="info-divider" v-if="taskDetail.deptName">|</span>
|
<span class="value">{{ alertDetail.siteName }}</span>
|
||||||
<span class="info-text">联系电话:{{ taskDetail.phoneNumber }}</span>
|
</div>
|
||||||
</div>
|
<div class="detail-item" v-if="alertDetail.tasksVo">
|
||||||
<div class="info-row">
|
<van-icon name="phone-o" class="detail-icon" />
|
||||||
<span class="info-label">任务地点:</span>
|
<span class="label">联系电话:</span>
|
||||||
<span class="info-value">{{ taskDetail.clickAddress }}</span>
|
<span class="value">{{ alertDetail.phoneNumber }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 任务描述 -->
|
<!-- 视频展示按钮 -->
|
||||||
<div class="task-desc">
|
<div class="video-btn-wrapper" v-if="alertDetail.videoUrls && alertDetail.videoUrls.length > 0">
|
||||||
{{ taskDetail.taskContent }}
|
<van-button block round color="#2a7efe" class="video-btn" @click="openVideo(alertDetail.videoUrls[0])">
|
||||||
</div>
|
<van-icon name="video-o" class="video-icon" />
|
||||||
|
视频展示
|
||||||
<!-- 打卡情况 -->
|
</van-button>
|
||||||
<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-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">{{ record.account }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="checkin-row checkin-location" v-if="record.location">
|
|
||||||
<van-icon name="location" class="location-icon" />
|
|
||||||
<span>{{ record.location }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 待派发状态 - 显示定位打卡按钮 -->
|
|
||||||
<div v-if="showCheckInBtn" class="action-buttons">
|
|
||||||
<van-button block round type="primary" class="action-btn" @click="handleCheckIn">
|
|
||||||
定位打卡
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 执行中/已完成状态 - 显示反馈按钮 -->
|
|
||||||
<div v-if="showFeedbackBtns" class="feedback-buttons">
|
|
||||||
<van-button
|
|
||||||
v-for="(feedback, index) in allDetail.feebacks"
|
|
||||||
:key="index"
|
|
||||||
block
|
|
||||||
round
|
|
||||||
color="#00c84f"
|
|
||||||
class="feedback-btn success"
|
|
||||||
@click="showFeedback(index + 1)"
|
|
||||||
>
|
|
||||||
第{{ index+1 }}次反馈结果
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-if="allDetail.taskStatus == '1'"
|
|
||||||
block
|
|
||||||
round
|
|
||||||
color="#2a7efe"
|
|
||||||
type="primary"
|
|
||||||
class="action-btn"
|
|
||||||
@click="handleFeedback"
|
|
||||||
>
|
|
||||||
立即反馈
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
|
||||||
|
<!-- 执行记录卡片 -->
|
||||||
|
<div class="record-card">
|
||||||
|
<div class="card-title">执行记录</div>
|
||||||
|
|
||||||
|
<!-- 蓝色边框卡片 -->
|
||||||
|
<div class="execution-box">
|
||||||
|
<!-- 绿色边框内层卡片 -->
|
||||||
|
<div class="info-box">
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">要求任务完成时间:</span>
|
||||||
|
<span class="info-value">{{ taskDetail.eventTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-text">{{ taskDetail.deptName }}</span>
|
||||||
|
<span class="info-divider" v-if="taskDetail.deptName">|</span>
|
||||||
|
<span class="info-text">联系电话:{{ taskDetail.phoneNumber }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">任务地点:</span>
|
||||||
|
<span class="info-value">{{ taskDetail.clickAddress }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 任务描述 -->
|
||||||
|
<div class="task-desc">
|
||||||
|
{{ taskDetail.taskContent }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 打卡情况 -->
|
||||||
|
<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-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">{{ record.account }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="checkin-row checkin-location" v-if="record.location">
|
||||||
|
<van-icon name="location" class="location-icon" />
|
||||||
|
<span>{{ record.location }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 待派发状态 - 显示定位打卡按钮 -->
|
||||||
|
<div v-if="showCheckInBtn" class="action-buttons">
|
||||||
|
<van-button block round type="primary" class="action-btn" @click="handleCheckIn">
|
||||||
|
定位打卡
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 执行中/已完成状态 - 显示反馈按钮 -->
|
||||||
|
<div v-if="showFeedbackBtns" class="feedback-buttons">
|
||||||
|
<van-button
|
||||||
|
v-for="(feedback, index) in allDetail.feebacks"
|
||||||
|
:key="index"
|
||||||
|
block
|
||||||
|
round
|
||||||
|
color="#00c84f"
|
||||||
|
class="feedback-btn success"
|
||||||
|
@click="showFeedback(index + 1)"
|
||||||
|
>
|
||||||
|
第{{ index+1 }}次反馈结果
|
||||||
|
</van-button>
|
||||||
|
<van-button
|
||||||
|
v-if="allDetail.taskStatus == '1'"
|
||||||
|
block
|
||||||
|
round
|
||||||
|
color="#2a7efe"
|
||||||
|
type="primary"
|
||||||
|
class="action-btn"
|
||||||
|
@click="handleFeedback"
|
||||||
|
>
|
||||||
|
立即反馈
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 反馈结果弹窗 -->
|
<!-- 反馈结果弹窗 -->
|
||||||
<van-popup v-model:show="showResultDialog" round position="center" class="feedback-popup">
|
<van-popup v-model:show="showResultDialog" round position="center" class="feedback-popup">
|
||||||
@ -218,7 +207,6 @@ import { ref, computed, onMounted } from "vue";
|
|||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { getTrafficEventDetail } from "@/api/traffic";
|
import { getTrafficEventDetail } from "@/api/traffic";
|
||||||
import { ImagePreview } from 'vant';
|
import { ImagePreview } from 'vant';
|
||||||
import defaultImg from "@/assets/error.png";
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@ -304,7 +292,6 @@ const fetchDetail = async () => {
|
|||||||
// 处理详情数据
|
// 处理详情数据
|
||||||
alertDetail.value = data.eventDetailVO;
|
alertDetail.value = data.eventDetailVO;
|
||||||
taskDetail.value = data.eventDetailVO.tasksVo || {};
|
taskDetail.value = data.eventDetailVO.tasksVo || {};
|
||||||
console.log(data,'=============00');
|
|
||||||
allDetail.value = data
|
allDetail.value = data
|
||||||
|
|
||||||
// 处理打卡记录
|
// 处理打卡记录
|
||||||
|
|||||||
@ -17,39 +17,38 @@
|
|||||||
|
|
||||||
<!-- 预警列表 -->
|
<!-- 预警列表 -->
|
||||||
<div class="alerts-list" ref="listRef">
|
<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">
|
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoadMore">
|
||||||
<div v-for="alert in filteredAlerts" :key="alert.id" class="alert-card">
|
<div v-for="alert in filteredAlerts" :key="alert.id" class="alert-card">
|
||||||
<!-- 等级、标题和状态标签 -->
|
<!-- 等级、标题和状态标签 -->
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title-row">
|
|
||||||
<span class="level-tag" :class="getLevelClass(alert.eventLevel)">
|
|
||||||
{{ levelMap[alert.eventLevel] }}
|
|
||||||
</span>
|
|
||||||
<span class="alert-title">{{ alert.eventType }}</span>
|
|
||||||
</div>
|
|
||||||
<span class="status-tag" :class="getStatusClass(alert.taskStatus)">
|
<span class="status-tag" :class="getStatusClass(alert.taskStatus)">
|
||||||
{{ statusMap[alert.taskStatus] }}
|
{{ statusMap[alert.taskStatus] }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<div class="card-image" v-if="alert.image">
|
<div class="card-image">
|
||||||
<van-image :src="alert.imgUrl" fit="cover" class="alert-img" />
|
<van-image :src="alert.imgUrl" fit="cover" class="alert-img" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card-title-row">
|
||||||
|
<span class="level-tag" :class="getLevelClass(alert.eventLevel)">
|
||||||
|
{{ levelMap[alert.eventLevel] }}
|
||||||
|
</span>
|
||||||
|
<span class="alert-title">{{ alert.eventType }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- 详细信息 -->
|
<!-- 详细信息 -->
|
||||||
<div class="card-details">
|
<div class="card-details">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<van-icon name="location" class="detail-icon" />
|
<van-icon name="location-o" class="detail-icon" />
|
||||||
<span class="label">检测点位:</span>
|
<span class="label">检测点位:</span>
|
||||||
<span class="value">{{ alert.siteName }}</span>
|
<span class="value">{{ alert.siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<van-icon name="clock" class="detail-icon" />
|
<van-icon name="clock-o" class="detail-icon" />
|
||||||
<span class="label">检测时间:</span>
|
<span class="label">检测时间:</span>
|
||||||
<span class="value">{{ alert.eventTime }}</span>
|
<span class="value">{{ alert.eventTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -77,7 +76,7 @@
|
|||||||
</van-list>
|
</van-list>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<van-empty v-if="filteredAlerts.length === 0 && !initLoading" description="暂无数据" class="empty-state" />
|
<van-empty v-if="filteredAlerts.length === 0 " description="暂无数据" class="empty-state" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -93,7 +92,6 @@ const userInfo = ref(JSON.parse(localStorage.getItem('userInfo')))
|
|||||||
// 状态
|
// 状态
|
||||||
const activeTab = ref("all");
|
const activeTab = ref("all");
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const initLoading = ref(false);
|
|
||||||
const finished = ref(false);
|
const finished = ref(false);
|
||||||
|
|
||||||
// 标签栏
|
// 标签栏
|
||||||
@ -145,11 +143,7 @@ function resetList() {
|
|||||||
|
|
||||||
// 获取列表数据
|
// 获取列表数据
|
||||||
const fetchAlerts = async (isLoadMore = false) => {
|
const fetchAlerts = async (isLoadMore = false) => {
|
||||||
if (isLoadMore) {
|
if (isLoadMore) loading.value = true;
|
||||||
loading.value = true;
|
|
||||||
} else {
|
|
||||||
initLoading.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 根据标签添加状态筛选
|
// 根据标签添加状态筛选
|
||||||
@ -183,13 +177,10 @@ const fetchAlerts = async (isLoadMore = false) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取交通预警列表失败:", error);
|
console.error("获取交通预警列表失败:", error);
|
||||||
if (!isLoadMore) {
|
if (!isLoadMore) filteredAlerts.value = [];
|
||||||
filteredAlerts.value = [];
|
|
||||||
}
|
|
||||||
finished.value = true;
|
finished.value = true;
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
initLoading.value = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -327,9 +318,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
text-align: right;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 12px 16px 0;
|
padding: 12px 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,6 +367,8 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
.level-tag {
|
.level-tag {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
|
|||||||
372
src/pages/videoMonitorMap/index.vue
Normal file
372
src/pages/videoMonitorMap/index.vue
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<template>
|
||||||
|
<div class="map-page">
|
||||||
|
<!-- 顶部导航栏 -->
|
||||||
|
<div class="nav-bar">
|
||||||
|
<div class="nav-left">
|
||||||
|
<div class="action-btn" >
|
||||||
|
<van-icon name="arrow-left" class="nav-back" @click="goBack" />
|
||||||
|
</div>
|
||||||
|
<h1 class="nav-title">监控地图</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-right">
|
||||||
|
<div class="action-btn" >
|
||||||
|
<van-icon name="location" />
|
||||||
|
</div>
|
||||||
|
<div class="action-btn" :class="{ active: showFilter }" >
|
||||||
|
<van-icon name="filter-o" @click="showFilter = !showFilter" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 地图区域 -->
|
||||||
|
<div class="map-container">
|
||||||
|
<!-- 筛选面板 -->
|
||||||
|
<div v-if="showFilter" class="filter-panel-container">
|
||||||
|
<div class="filter-panel">
|
||||||
|
<div class="filter-section">
|
||||||
|
<div class="filter-label">区域筛选</div>
|
||||||
|
<div class="filter-tags">
|
||||||
|
<button
|
||||||
|
v-for="area in areas"
|
||||||
|
:key="area"
|
||||||
|
class="filter-tag"
|
||||||
|
:class="{ active: filterArea === area }"
|
||||||
|
@click="filterArea = area"
|
||||||
|
>
|
||||||
|
{{ area }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-row">
|
||||||
|
<span class="filter-row-label">仅显示收藏</span>
|
||||||
|
<van-switch v-model="showFavoriteOnly" size="20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="alett-btn">
|
||||||
|
<li>在线 <span>5</span></li>
|
||||||
|
<li>离线 <span>2</span></li>
|
||||||
|
</ul>
|
||||||
|
<GdMap mapid="map-page-map" :is-show="false" />
|
||||||
|
|
||||||
|
<!-- 标记详情卡片 -->
|
||||||
|
<div class="marker-card" v-show="showCard">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-info">
|
||||||
|
<span class="card-title">长江大桥监控</span>
|
||||||
|
<span class="line">在线</span>
|
||||||
|
</div>
|
||||||
|
<van-icon name="cross" class="close-icon" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<van-icon name="location-o" color="#89909d" />
|
||||||
|
<span class="card-location">江苏省南京市长江大桥</span>
|
||||||
|
</div>
|
||||||
|
<div class="btn-box">
|
||||||
|
<span class="smallBtn">桥梁监控</span>
|
||||||
|
<span class="smallBtn">江岸区</span>
|
||||||
|
<span class="smallBtn">M002</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<span class="card-btn"><van-icon name="eye-o" size="16px" style="top:2px;margin-right: 10px;" />查看视频</span>
|
||||||
|
<span class="detail-btn" > 详情</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部导航 -->
|
||||||
|
<BottomTabs :active-tab="'map'" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import BottomTabs from "@/components/bottomTabs.vue";
|
||||||
|
import GdMap from "@/components/GdMap/index.vue";
|
||||||
|
const router = useRouter();
|
||||||
|
const showCard = ref(false); //展示卡片
|
||||||
|
// 区域列表
|
||||||
|
const areas = ["全部", "江汉区", "江岸区", "洪山区", "武昌区"];
|
||||||
|
const showFavoriteOnly = ref(false);
|
||||||
|
const showFilter = ref(false);
|
||||||
|
// 页面加载时获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
function goBack() {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.map-page {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #2057e0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.nav-back {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.nav-left{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.nav-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.action-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: white;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.map-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #eff9f9;
|
||||||
|
.filter-panel-container{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #2563eb;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.filter-panel {
|
||||||
|
background: rgb(94, 138, 233);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.filter-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tag {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: white;
|
||||||
|
color: #2563eb;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.filter-row-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alett-btn{
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 16px;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 6px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
li{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
color: #4d5868;
|
||||||
|
margin-left: 15px;
|
||||||
|
span{
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
&:before{
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -15px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #10b981;
|
||||||
|
}
|
||||||
|
&:nth-child(2):before{
|
||||||
|
background: #4d5868;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-card {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 90px;
|
||||||
|
left: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 80%;
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 20;
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.card-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.line{
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #10b981;
|
||||||
|
background: #d1fae5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #9ca3af;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-location{
|
||||||
|
margin-left: 5px;
|
||||||
|
color: #89909d;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.btn-box{
|
||||||
|
color: #89909d;
|
||||||
|
font-size: 14px;
|
||||||
|
span{
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 10px 0;
|
||||||
|
&:nth-child(1){
|
||||||
|
background: #e0e7ff;
|
||||||
|
color: #4338ca;
|
||||||
|
}
|
||||||
|
&:nth-child(2){
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #4d5868;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.card-btn {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
background: #2b7fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-btn {
|
||||||
|
width: 60px;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-left: 8px;
|
||||||
|
background: #f3f4f6;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #4d5868;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -3,7 +3,7 @@
|
|||||||
<!-- 顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<div class="nav-bar">
|
<div class="nav-bar">
|
||||||
<van-icon name="arrow-left" class="nav-back" @click="goBack" />
|
<van-icon name="arrow-left" class="nav-back" @click="goBack" />
|
||||||
<h1 class="nav-title">违章详情</h1>
|
<h1 class="nav-title">违规任务详情</h1>
|
||||||
<div class="nav-placeholder"></div>
|
<div class="nav-placeholder"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 违章信息卡片 -->
|
<!-- 违章信息卡片 -->
|
||||||
@ -48,30 +48,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 打卡情况 - 执行中和已完成状态显示 -->
|
|
||||||
<div v-if="allDetail.taskStatus == '1' || allDetail.taskStatus == '2'" class="checkin-card">
|
|
||||||
<div class="checkin-time-row">
|
|
||||||
<span class="checkin-date">{{allDetail.clickTime}}</span>
|
|
||||||
<span class="checkin-type">【定位打卡】</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkin-row">
|
|
||||||
<span class="checkin-label">打卡账号:</span>
|
|
||||||
<span class="checkin-value">21515800</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkin-row checkin-location">
|
|
||||||
<van-icon name="location" class="location-icon" />
|
|
||||||
<span>{{allDetail.clickAddress}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 未拦截成功标识 -->
|
|
||||||
<div v-if="violationId !== '1' && violationId !== '3' && allDetail.taskStatus === '1'" class="intercept-status">
|
|
||||||
<span v-if="violationId === '6'" class="result-btn" @click="goToResult">处罚结果</span>
|
|
||||||
<span v-else class="fail-tag">未拦截成功</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 卡口预警折叠面板 -->
|
<!-- 卡口预警折叠面板 -->
|
||||||
<van-collapse v-model="activeNames" :border="false" class="alert-collapse">
|
<van-collapse v-model="activeNames" :border="false" class="alert-collapse">
|
||||||
<van-collapse-item name="1" :border="false">
|
<van-collapse-item name="1" :border="false">
|
||||||
@ -134,6 +110,30 @@
|
|||||||
</van-collapse-item>
|
</van-collapse-item>
|
||||||
</van-collapse>
|
</van-collapse>
|
||||||
|
|
||||||
|
<!-- 打卡情况 - 执行中和已完成状态显示 -->
|
||||||
|
<div v-if="allDetail.taskStatus == '1' || allDetail.taskStatus == '2'" class="checkin-card">
|
||||||
|
<div class="checkin-time-row">
|
||||||
|
<span class="checkin-date">{{allDetail.clickTime}}</span>
|
||||||
|
<span class="checkin-type">【定位打卡】</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkin-row">
|
||||||
|
<span class="checkin-label">打卡账号:</span>
|
||||||
|
<span class="checkin-value">21515800</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkin-row checkin-location">
|
||||||
|
<van-icon name="location" class="location-icon" />
|
||||||
|
<span>{{allDetail.clickAddress}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 未拦截成功标识 -->
|
||||||
|
<div v-if="violationId !== '1' && violationId !== '3' && allDetail.taskStatus == '1'" class="intercept-status">
|
||||||
|
<span v-if="violationId === '6'" class="result-btn" @click="goToResult">处罚结果</span>
|
||||||
|
<span v-else class="fail-tag">未拦截成功</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 底部按钮 - 未执行状态 -->
|
<!-- 底部按钮 - 未执行状态 -->
|
||||||
<div v-if="allDetail.taskStatus == '0'" class="action-bar">
|
<div v-if="allDetail.taskStatus == '0'" class="action-bar">
|
||||||
<van-button block round type="primary" class="action-btn" @click="handleCheckIn">定位打卡</van-button>
|
<van-button block round type="primary" class="action-btn" @click="handleCheckIn">定位打卡</van-button>
|
||||||
@ -145,11 +145,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 执行结果弹框 -->
|
<!-- 执行结果弹框 -->
|
||||||
<van-popup v-model:show="showMatchDialog" round class="result-popup">
|
<van-popup v-model:show="showMatchDialog" round class="result-popup" :style="{ width: '80%' }">
|
||||||
<div class="popup-content">
|
<div class="popup-content">
|
||||||
<h2 class="popup-title">执行结果</h2>
|
<h2 class="popup-title">执行结果</h2>
|
||||||
<div class="popup-buttons">
|
<div class="popup-buttons">
|
||||||
<van-button block round class="popup-btn success" @click="handlePlateMatch(true)">
|
<van-button block round color="#00bc4a" class="popup-btn success" @click="handlePlateMatch(true)">
|
||||||
<van-icon name="checked" class="btn-icon" />
|
<van-icon name="checked" class="btn-icon" />
|
||||||
处罚上报
|
处罚上报
|
||||||
</van-button>
|
</van-button>
|
||||||
|
|||||||
@ -22,27 +22,21 @@
|
|||||||
|
|
||||||
<!-- 违章列表 -->
|
<!-- 违章列表 -->
|
||||||
<div class="alerts-list">
|
<div class="alerts-list">
|
||||||
<div
|
<div v-for="alert in filteredAlerts" :key="alert.id" class="alert-card">
|
||||||
v-for="alert in filteredAlerts"
|
|
||||||
:key="alert.id"
|
|
||||||
class="alert-card"
|
|
||||||
>
|
|
||||||
<!-- 车牌信息 -->
|
<!-- 车牌信息 -->
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="vehicle-info">
|
<div class="vehicle-info">
|
||||||
<van-icon name="cart" class="vehicle-icon" />
|
<van-icon name="logistics" class="vehicle-icon" />
|
||||||
<span class="plate-number">{{ alert.plateNo }}</span>
|
<span class="plate-number">{{ alert.plateNo }}</span>
|
||||||
<span class="plate-color">{{ alert.plateColor }}</span>
|
<span class="plate-color">{{ alert.plateColor }}</span>
|
||||||
<span class="separator">|</span>
|
<span class="separator">|</span>
|
||||||
<span class="vehicle-type">{{ alert.vehicleType }}</span>
|
<span class="vehicle-type">{{ alert.vehicleType }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="status-tag" :class="getStatusClass(alert.taskStatus)">
|
<span class="status-tag" :class="getStatusClass(alert.taskStatus)"> {{ statusMap[alert.taskStatus] }} </span>
|
||||||
{{ statusMap[alert.taskStatus] }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<div class="card-image" v-if="alert.image">
|
<div class="card-image">
|
||||||
<van-image
|
<van-image
|
||||||
:src="alert.imgUrl"
|
:src="alert.imgUrl"
|
||||||
fit="cover"
|
fit="cover"
|
||||||
@ -61,12 +55,12 @@
|
|||||||
<!-- 详细信息 -->
|
<!-- 详细信息 -->
|
||||||
<div class="card-details">
|
<div class="card-details">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<van-icon name="location" class="detail-icon" />
|
<van-icon name="location-o" style="margin-right: 5px;" />
|
||||||
<span class="label">检测点位:</span>
|
<span class="label">检测点位:</span>
|
||||||
<span class="value">{{ alert.siteName }}</span>
|
<span class="value">{{ alert.siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<van-icon name="clock" class="detail-icon" />
|
<van-icon name="clock-o" style="margin-right: 5px;" />
|
||||||
<span class="label">检测时间:</span>
|
<span class="label">检测时间:</span>
|
||||||
<span class="value">{{ alert.eventTime }}</span>
|
<span class="value">{{ alert.eventTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -392,7 +386,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
.plate-color {
|
.plate-color {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #64748b;
|
color: #c5cad2;
|
||||||
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
@ -402,7 +397,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.vehicle-type {
|
.vehicle-type {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #64748b;
|
color: #c5cad2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
227
src/pages/warningList/index.vue
Normal file
227
src/pages/warningList/index.vue
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<div class="traffic-warn-page">
|
||||||
|
<!-- 顶部导航栏 -->
|
||||||
|
<div class="nav-bar">
|
||||||
|
<van-icon name="arrow-left" class="nav-back" @click="goBack" />
|
||||||
|
<h1 class="nav-title">拦截预警</h1>
|
||||||
|
<van-icon name="filter-o" class="nav-filter" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 预警列表 -->
|
||||||
|
<div class="warns-list">
|
||||||
|
<!-- 列表内容 -->
|
||||||
|
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoadMore">
|
||||||
|
<div v-for="item in warningList" :key="item.id" class="warn-card" @click="lookDetail(item)">
|
||||||
|
<div class="warn-header" v-if="item.plateNo">{{ item.eventType }} <span class="level" :class="getLevelClass(item.eventLevel)">{{ getStatusText(item.eventLevel) }}</span></div>
|
||||||
|
<div class="warn-text"><span><van-icon name="logistics" /></span> <span>{{ item.plateNo }}</span> | <span>{{ item.vehicleColor }}</span> | <span>{{ item.vehicleType }}</span></div>
|
||||||
|
<div class="warn-text"><span><van-icon name="location-o" /></span> 监测点位:{{ item.siteName }}</div>
|
||||||
|
<div class="warn-text"><span><van-icon name="clock-o" /></span> 监测时间:{{ item.passTime }}</div>
|
||||||
|
</div>
|
||||||
|
</van-list>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<van-empty v-if="warningList.length === 0 && !initLoading" description="暂无数据" class="empty-state" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getInterceptWarnList } from "@/api/traffic";
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const warningList = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const initLoading = ref(false);
|
||||||
|
const finished = ref(false);
|
||||||
|
|
||||||
|
const listQuery = ref({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
status: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
const fetchAlerts = async (isLoadMore = false) => {
|
||||||
|
if (!isLoadMore) {
|
||||||
|
initLoading.value = true;
|
||||||
|
listQuery.value.page = 0;
|
||||||
|
warningList.value = [];
|
||||||
|
finished.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getInterceptWarnList({
|
||||||
|
status: listQuery.value.status,
|
||||||
|
pageSize: listQuery.value.pageSize,
|
||||||
|
page: listQuery.value.page + 1
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res && res.content) {
|
||||||
|
const records = res.content || [];
|
||||||
|
if (listQuery.value.page === 0) {
|
||||||
|
warningList.value = records;
|
||||||
|
} else {
|
||||||
|
warningList.value.push(...records);
|
||||||
|
}
|
||||||
|
listQuery.value.page++;
|
||||||
|
|
||||||
|
// 判断是否还有更多数据
|
||||||
|
if (records.length < listQuery.value.pageSize) {
|
||||||
|
finished.value = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finished.value = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
finished.value = true;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
initLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载更多
|
||||||
|
function onLoadMore() {
|
||||||
|
fetchAlerts(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等级样式
|
||||||
|
function getLevelClass(level) {
|
||||||
|
const map = {
|
||||||
|
1: "level-red",
|
||||||
|
2: "level-orange",
|
||||||
|
// 3: "level-yellow",
|
||||||
|
3: "level-blue",
|
||||||
|
};
|
||||||
|
return map[level] || "level-blue";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态样式
|
||||||
|
function getStatusText(status) {
|
||||||
|
const map = {
|
||||||
|
1: "一级",
|
||||||
|
2: "二级",
|
||||||
|
3: "三级",
|
||||||
|
};
|
||||||
|
return map[status] || "三级";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function lookDetail(item) {
|
||||||
|
router.push({
|
||||||
|
path: "/violation-detail",
|
||||||
|
query: { id: item.taskId, status: item.status }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
function goBack() {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 页面初始化
|
||||||
|
onMounted(() => {
|
||||||
|
fetchAlerts();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.traffic-warn-page {
|
||||||
|
background: #f9fafb;
|
||||||
|
position: relative;
|
||||||
|
.warns-list{
|
||||||
|
height: calc(100vh - 82px);
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 0 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
.nav-back {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-filter {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.warn-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
.warn-header{
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
.level{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-left: 4px;
|
||||||
|
&.level-red {
|
||||||
|
background: #ffe2e2;
|
||||||
|
color: #e8101a;
|
||||||
|
}
|
||||||
|
&.level-orange {
|
||||||
|
background: #fff3e0;
|
||||||
|
color: #ff9800;
|
||||||
|
}
|
||||||
|
&.level-yellow {
|
||||||
|
background: #fffde7;
|
||||||
|
color: #ffc107;
|
||||||
|
}
|
||||||
|
&.level-blue {
|
||||||
|
background: #e3f2fd;
|
||||||
|
color: #2196f3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.warn-text{
|
||||||
|
color: #555;
|
||||||
|
margin-top: 6px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: #565c69;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding: 60px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user