Files
jg_app/src/pages/alertHandle/index.vue
2026-04-16 14:21:25 +08:00

416 lines
9.2 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="alert-handle-page">
<!-- 顶部导航栏 -->
<div class="nav-bar">
<van-icon name="arrow-left" class="nav-back" @click="goBack" />
<div class="nav-title">{{ pageTitle }}</div>
<div class="nav-placeholder"></div>
</div>
<!-- 现场情况 -->
<div class="section-card">
<div class="section-title">现场情况</div>
<!-- 拍照和录视频按钮 -->
<div class="action-buttons">
<div class="action-btn" @click="handleTakePhoto">
<van-icon name="photograph" class="action-icon" />
<span>拍照</span>
</div>
<div class="action-btn" @click="handleTakeVideo">
<van-icon name="video-o" class="action-icon" />
<span>录视频</span>
</div>
</div>
<!-- 照片预览 -->
<div v-if="photos.length > 0" class="preview-section">
<div class="preview-label">已拍照片</div>
<div class="photo-grid">
<div v-for="(photo, index) in photos" :key="index" class="photo-item">
<van-image :src="photo.base64" fit="cover" class="photo-img">
<template v-slot:loading>
<van-loading type="spinner" size="20" />
</template>
</van-image>
<van-icon name="close" class="photo-delete" @click="deletePhoto(index)" />
</div>
</div>
</div>
<!-- 视频预览 -->
<div v-if="videos.length > 0" class="preview-section">
<div class="preview-label">已录视频</div>
<div class="video-list">
<div v-for="(video, index) in videos" :key="index" class="video-item">
<van-icon name="video-o" class="video-icon" />
<span class="video-name">视频 {{ index + 1 }}</span>
<span class="video-delete" @click="deleteVideo(index)">删除</span>
</div>
</div>
</div>
</div>
<!-- 反馈内容 -->
<div class="section-card">
<div class="section-title">反馈内容</div>
<van-field
v-model="feedbackText"
type="textarea"
placeholder="请输入现场处理情况和反馈内容..."
rows="5"
autosize
maxlength="500"
show-word-limit
class="feedback-input"
/>
</div>
<!-- 底部保存按钮 -->
<div class="submit-bar">
<van-button block round type="primary" class="submit-btn" :loading="isSubmitting" @click="handleSave">
保存
</van-button>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import { Toast } from "vant";
import { feedback, uploadFile } from "@/api/traffic";
import { hintToast } from "@/utils/tools";
const router = useRouter();
const route = useRoute();
// 获取URL参数
const alertId = route.query.id;
const status = route.query.status;
// 页面标题
const pageTitle = computed(() => {
return status === "执行中" ? "立即反馈" : "立即处理";
});
// 表单数据
const feedbackText = ref("");
// photos 存储 { base64, fileId } 对象base64 用于预览fileId 用于提交
const photos = ref(['http://220.166.58.28:172/profile/upload/2026/04/15/bb_total_active_20260415150856A001.png']);
const videos = ref([]);
const isSubmitting = ref(false);
// 返回
function goBack() {
router.back();
}
// 拍照
function handleTakePhoto() {
try {
if (window.bridge && window.bridge.pZ) {
window.bridge.pZ("photo");
} else {
hintToast("请使用APP进行拍照");
}
} catch (err) {
console.error("拍照失败:", err);
hintToast("请使用APP进行拍照");
}
}
// 录视频
function handleTakeVideo() {
hintToast("视频录制功能开发中...");
}
// 删除照片
function deletePhoto(index) {
photos.value.splice(index, 1);
}
// 删除视频
function deleteVideo(index) {
videos.value.splice(index, 1);
}
// 将 base64 转为 Blob
function base64ToBlob(base64) {
const parts = base64.split(',');
const mime = parts[0].match(/:(.*?);/)[1];
const byteString = atob(parts[1]);
const arrayBuffer = new ArrayBuffer(byteString.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
uint8Array[i] = byteString.charCodeAt(i);
}
return new Blob([arrayBuffer], { type: mime });
}
// 接收原生APP返回的图片
function setimagebase64(base64) {
const preview = `data:image/jpeg;base64,${base64}`;
const tempId = `uploading_${Date.now()}`;
photos.value.push({ id: tempId, base64: preview, fileId: "" });
const blob = base64ToBlob(base64);
const fileName = `photo_${Date.now()}.jpg`;
const file = new File([blob], fileName, { type: blob.type });
uploadFile(file)
.then((res) => {
const idx = photos.value.findIndex((p) => p.id === tempId);
if (idx !== -1) {
photos.value[idx].fileId = res;
}
})
.catch((e) => {
console.error("图片上传失败:", e);
const idx = photos.value.findIndex((p) => p.id === tempId);
if (idx !== -1) {
photos.value.splice(idx, 1);
}
});
}
// 保存反馈
function handleSave() {
if (!feedbackText.value.trim()) {
hintToast("请输入反馈内容");
return;
}
if (isSubmitting.value) return;
isSubmitting.value = true;
Toast.loading({ message: "提交中...", forbidClick: true, duration: 0 });
const images = photos.value.map((p) => p.fileId).filter(Boolean).join(",");
feedback({
taskId: alertId,
feedback: feedbackText.value,
images,
video: "",
filesUrl: "",
singUrl: ""
})
.then(() => {
Toast.clear();
hintToast("保存成功");
setTimeout(() => {
router.back();
}, 1000);
})
.catch((e) => {
Toast.clear();
hintToast(e.message || "保存失败,请重试");
})
.finally(() => {
isSubmitting.value = false;
});
}
onMounted(() => {
window.setimagebase64 = setimagebase64;
});
</script>
<style lang="scss" scoped>
// 通用样式规范
$primary-color: #2563eb;
$primary-dark: #1d4ed8;
$card-radius: 20px;
$btn-radius: 16px;
$card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
$text-gray: #64748b;
$text-dark: #1e293b;
$bg-gray: #f1f5f9;
.alert-handle-page {
min-height: 100vh;
background: $bg-gray;
padding-bottom: 80px;
}
.nav-bar {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
background: white;
border-bottom: 1px solid #e2e8f0;
padding: 12px 16px;
.nav-back {
font-size: 24px;
color: $text-dark;
}
.nav-title {
font-size: 17px;
font-weight: 600;
color: $text-dark;
}
.nav-placeholder {
width: 24px;
}
}
.section-card {
background: white;
border-radius: $card-radius;
padding: 20px;
margin: 16px;
box-shadow: $card-shadow;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: $text-dark;
margin-bottom: 20px;
}
.action-buttons {
display: flex;
gap: 16px;
margin-bottom: 20px;
.action-btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
border: 2px solid $primary-color;
border-radius: $btn-radius;
color: $primary-color;
transition: all 0.2s;
cursor: pointer;
&:active {
background: #eff6ff;
transform: scale(0.98);
}
.action-icon {
font-size: 32px;
margin-bottom: 10px;
}
span {
font-size: 14px;
font-weight: 500;
}
}
}
.preview-section {
margin-top: 20px;
.preview-label {
font-size: 14px;
color: $text-gray;
margin-bottom: 12px;
}
}
.photo-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
.photo-item {
position: relative;
aspect-ratio: 1;
border-radius: 12px;
overflow: hidden;
.photo-img {
width: 100%;
height: 100%;
}
.photo-delete {
position: absolute;
top: 6px;
right: 6px;
font-size: 18px;
color: white;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
padding: 4px;
}
}
}
.video-list {
.video-item {
display: flex;
align-items: center;
padding: 14px;
background: $bg-gray;
border-radius: 12px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.video-icon {
font-size: 22px;
color: $primary-color;
margin-right: 12px;
}
.video-name {
flex: 1;
font-size: 14px;
color: $text-dark;
}
.video-delete {
font-size: 14px;
color: #ef4444;
cursor: pointer;
}
}
}
.feedback-input {
padding: 16px;
background: $bg-gray;
border-radius: 16px;
:deep(.van-field__control) {
font-size: 14px;
color: $text-dark;
}
:deep(.van-field__word-limit) {
text-align: right;
}
}
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
border-top: 1px solid #e2e8f0;
padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom));
}
.submit-btn {
height: 48px;
font-size: 16px;
font-weight: 600;
background: linear-gradient(135deg, $primary-color 0%, $primary-dark 100%);
border: none;
border-radius: $btn-radius;
box-shadow: 0 4px 12px rgba($primary-color, 0.3);
}
</style>