Files
jg_app/src/pages/sspReport/index.vue
2026-04-20 19:53:56 +08:00

929 lines
24 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="ssp-report-page">
<!-- 顶部导航 -->
<div class="top-nav">
<div class="nav-left" @click="goBack">
<van-icon name="arrow-left" />
</div>
<div class="nav-title">随手拍</div>
<div class="nav-right" @click="handleSubmit">
提交
</div>
</div>
<!-- 表单内容 -->
<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-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" 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-cell-group :border="false">
<van-field v-model="formData.zpsl" is-link readonly name="照片数量" label="照片数量 *" placeholder="请选择照片数量"
@click="openPicker({ columns: zpslColumns, title: '选择照片数量', onConfirm: onZpslConfirm })" />
<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 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" 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 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>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { useRouter } from "vue-router";
import { showToast, showConfirmDialog } from "vant";
import { upImage } from "@/api/common";
const router = useRouter();
// 日期选择器
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 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({
device: true,
vehicle: true,
violation: true,
photo: true,
video: true,
other: true
});
// 照片列表
const photoList = ref([
{ label: "照片1", url: "", link: "" },
{ label: "照片2", url: "", link: "" },
{ label: "照片3", url: "", link: "" }
]);
const currentUploadIndex = ref(0);
const fileInputs = ref(null);
// 照片数量选择器选项
const zpslColumns = [
{ text: "1", value: "1" },
{ text: "2", value: "2" },
{ text: "3", value: "3" },
{ text: "4", value: "4" },
{ text: "5", value: "5" }
];
// 图片方式选择器选项
const tpfsColumns = [
{ text: "手机拍摄", value: "01" },
{ text: "相机拍摄", value: "02" },
{ text: "监控截图", value: "03" },
{ text: "行车记录仪", value: "04" }
];
// 选择器显示状态(通用)
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;
}
// 选择器显示状态
const showWffssjPicker = ref(false);
// 选择器列数据(模拟数据)
const deviceColumns = [
{ text: "设备001", value: "001" },
{ text: "设备002", value: "002" },
{ text: "设备003", value: "003" }
];
const officerColumns = [
{ text: "张三", value: "001" },
{ text: "李四", value: "002" },
{ 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) {
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) {
photoList.value[index].url = "";
const uploadedCount = photoList.value.filter(p => p.url).length;
formData.zpsl = uploadedCount.toString();
}
// vant upload上传后处理
function onPhotoRead(file, index) {
photoList.value[index].url = file.content;
const uploadedCount = photoList.value.filter(p => p.url).length;
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() {
router.back();
}
// 提交表单
function handleSubmit() {
// 表单验证
if (!formData.sbbh) {
showToast("请选择设备编号");
return;
}
if (!formData.zfmj) {
showToast("请选择执法民警");
return;
}
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(() => {
// 获取用户信息
const userInfo = JSON.parse(localStorage.getItem("userInfo") || "{}");
if (userInfo.userName) {
formData.zfmj = userInfo.userName;
formData.zfmjdm = userInfo.userId;
}
});
</script>
<style lang="scss" scoped>
.ssp-report-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 80px;
}
.top-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 44px;
background: #2563eb;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
z-index: 100;
.nav-left {
color: white;
font-size: 18px;
display: flex;
align-items: center;
}
.nav-title {
color: white;
font-size: 17px;
font-weight: 600;
}
.nav-right {
color: white;
font-size: 15px;
}
}
.form-content {
padding-top: 44px;
padding: 44px 12px 16px;
}
.section-card {
background: white;
border-radius: 8px;
margin-bottom: 12px;
overflow: hidden;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: white;
border-bottom: 1px solid #f0f0f0;
.section-title-box {
display: flex;
align-items: center;
gap: 8px;
.section-icon {
font-size: 12px;
color: #2563eb;
transition: transform 0.3s;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #333;
}
}
}
.section-body {
padding: 4px 0;
:deep(.van-cell-group) {
background: transparent;
}
:deep(.van-cell) {
padding: 12px 16px;
&::after {
left: 16px;
right: 16px;
}
}
:deep(.van-field__label) {
color: #666;
font-size: 14px;
width: 130px;
}
:deep(.van-field__control) {
color: #333;
font-size: 14px;
}
:deep(.van-field__body) {
justify-content: flex-end;
}
}
.photo-upload {
padding: 16px;
:deep(.van-uploader) {
display: block;
}
:deep(.van-uploader__wrapper) {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
:deep(.van-uploader__preview) {
margin: 0;
}
:deep(.van-uploader__preview-image) {
width: 80px;
height: 80px;
border-radius: 8px;
}
:deep(.van-uploader__preview-delete) {
width: 18px;
height: 18px;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
top: -6px;
right: -6px;
}
.upload-btn {
width: 80px;
height: 80px;
background: #f5f5f5;
border-radius: 8px;
border: 1px dashed #dcdee0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: #969799;
span {
font-size: 12px;
}
}
}
.photo-upload-list {
display: flex;
flex-direction: column;
gap: 16px;
padding: 12px 16px;
margin-top: 12px;
}
.photo-upload-item {
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
background: #f9fafb;
border-radius: 8px;
border: 1px solid #e5e7eb;
:deep(.van-uploader) {
width: 100%;
}
:deep(.van-uploader__wrapper) {
display: block;
}
:deep(.van-uploader__preview) {
margin: 0;
}
:deep(.van-uploader__preview-image) {
width: 100%;
aspect-ratio: 3/1;
border-radius: 6px;
object-fit: cover;
}
:deep(.van-uploader__preview-delete) {
width: 18px;
height: 18px;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
top: -6px;
right: -6px;
}
:deep(.van-uploader__upload) {
margin: 0;
width: 100%;
aspect-ratio: 3/1;
background: #fff;
border: 1px dashed #d1d5db;
border-radius: 6px;
}
:deep(.van-uploader__upload-icon) {
font-size: 28px;
color: #9ca3af;
}
:deep(.van-uploader__upload-text) {
font-size: 12px;
color: #6b7280;
}
.photo-upload-content {
width: 100%;
aspect-ratio: 3/1;
background: #fff;
border: 1px dashed #d1d5db;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
.photo-icon {
font-size: 32px;
color: #9ca3af;
}
.photo-text {
font-size: 12px;
color: #6b7280;
}
}
.photo-preview-img {
width: 100px;
border-radius: 6px;
object-fit: cover;
}
.photo-delete {
position: absolute;
top: 8px;
right: 8px;
width: 20px;
height: 20px;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
color: #fff;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.photo-label {
font-size: 13px;
color: #374151;
font-weight: 500;
}
.photo-link-field {
width: 100%;
background: #fff;
border-radius: 6px;
margin-top: 4px;
:deep(.van-field__label) {
width: 70px;
color: #6b7280;
}
:deep(.van-field__control) {
font-size: 13px;
}
}
}
.photo-address-input {
display: flex;
align-items: center;
gap: 8px;
margin: 16px;
padding: 12px;
background: #f5f5f5;
border-radius: 6px;
cursor: pointer;
&:active {
background: #eeeeee;
}
.address-icon {
font-size: 18px;
color: #666;
}
.address-text {
flex: 1;
font-size: 14px;
color: #333;
}
.address-placeholder {
flex: 1;
font-size: 14px;
color: #999;
}
}
.photo-address-dialog {
padding: 16px;
}
.photo-item {
position: relative;
aspect-ratio: 1;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
.photo-preview {
width: 100%;
height: 100%;
object-fit: cover;
}
.photo-placeholder {
width: 100%;
height: 100%;
background: #f5f5f5;
border: 1px dashed #dcdee0;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: #969799;
font-size: 12px;
}
.photo-delete {
position: absolute;
top: 4px;
right: 4px;
width: 20px;
height: 20px;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
&:active {
opacity: 0.8;
}
}
.bottom-action {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12px 16px;
background: white;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
:deep(.van-button) {
height: 44px;
font-size: 16px;
font-weight: 600;
}
}
</style>