Files
jg_app/src/pages/alertHandle/index.vue

416 lines
9.2 KiB
Vue
Raw Normal View History

2026-04-10 17:10:36 +08:00
<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">
2026-04-16 14:21:25 +08:00
<van-image :src="photo.base64" fit="cover" class="photo-img">
2026-04-10 17:10:36 +08:00
<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">
2026-04-16 14:21:25 +08:00
<van-button block round type="primary" class="submit-btn" :loading="isSubmitting" @click="handleSave">
2026-04-10 17:10:36 +08:00
保存
</van-button>
</div>
</div>
</template>
<script setup>
2026-04-16 14:21:25 +08:00
import { ref, computed, onMounted } from "vue";
2026-04-10 17:10:36 +08:00
import { useRouter, useRoute } from "vue-router";
import { Toast } from "vant";
2026-04-16 14:21:25 +08:00
import { feedback, uploadFile } from "@/api/traffic";
import { hintToast } from "@/utils/tools";
2026-04-10 17:10:36 +08:00
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("");
2026-04-16 14:21:25 +08:00
// photos 存储 { base64, fileId } 对象base64 用于预览fileId 用于提交
const photos = ref(['http://220.166.58.28:172/profile/upload/2026/04/15/bb_total_active_20260415150856A001.png']);
2026-04-10 17:10:36 +08:00
const videos = ref([]);
2026-04-16 14:21:25 +08:00
const isSubmitting = ref(false);
2026-04-10 17:10:36 +08:00
// 返回
function goBack() {
router.back();
}
// 拍照
function handleTakePhoto() {
try {
if (window.bridge && window.bridge.pZ) {
window.bridge.pZ("photo");
} else {
2026-04-16 14:21:25 +08:00
hintToast("请使用APP进行拍照");
2026-04-10 17:10:36 +08:00
}
} catch (err) {
console.error("拍照失败:", err);
2026-04-16 14:21:25 +08:00
hintToast("请使用APP进行拍照");
2026-04-10 17:10:36 +08:00
}
}
// 录视频
function handleTakeVideo() {
2026-04-16 14:21:25 +08:00
hintToast("视频录制功能开发中...");
2026-04-10 17:10:36 +08:00
}
// 删除照片
function deletePhoto(index) {
photos.value.splice(index, 1);
}
// 删除视频
function deleteVideo(index) {
videos.value.splice(index, 1);
}
2026-04-16 14:21:25 +08:00
// 将 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);
2026-04-10 17:10:36 +08:00
}
2026-04-16 14:21:25 +08:00
return new Blob([arrayBuffer], { type: mime });
}
2026-04-10 17:10:36 +08:00
2026-04-16 14:21:25 +08:00
// 接收原生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);
}
2026-04-10 17:10:36 +08:00
});
2026-04-16 14:21:25 +08:00
}
2026-04-10 17:10:36 +08:00
2026-04-16 14:21:25 +08:00
// 保存反馈
function handleSave() {
if (!feedbackText.value.trim()) {
hintToast("请输入反馈内容");
return;
2026-04-10 17:10:36 +08:00
}
2026-04-16 14:21:25 +08:00
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;
});
2026-04-10 17:10:36 +08:00
}
2026-04-16 14:21:25 +08:00
onMounted(() => {
window.setimagebase64 = setimagebase64;
});
2026-04-10 17:10:36 +08:00
</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>