odf_new/odf-uniapp/pages/fault-detail/index.vue
2026-03-14 14:34:48 +08:00

336 lines
7.4 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>
<view class="fault-detail-page">
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
<view class="content">
<!-- 顶部导航栏 -->
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-bar-inner">
<image
class="nav-icon"
src="/static/images/ic_back.png"
mode="aspectFit"
@click="goBack"
/>
<text class="nav-title">故障详情</text>
<view class="nav-icon-placeholder" />
</view>
</view>
<!-- 图片区域 -->
<view class="image-area" v-if="imageList.length > 0">
<scroll-view class="image-scroll" scroll-x>
<view class="image-grid">
<view
class="image-wrapper"
v-for="(img, index) in imageList"
:key="img"
@click="previewImage(index)"
>
<!-- 占位层loading 或加载失败 -->
<view class="image-placeholder" v-if="imageStatus[index] !== 'loaded'">
<text class="placeholder-text">{{ imageStatus[index] === 'error' ? '加载失败' : '加载中...' }}</text>
</view>
<image
class="image-item"
:class="{ 'image-hidden': imageStatus[index] !== 'loaded' }"
:src="img"
mode="aspectFill"
@load="onImageLoad(index)"
@error="onImageError(index)"
/>
</view>
</view>
</scroll-view>
</view>
<!-- 信息展示区域 -->
<view class="info-area">
<view class="info-row">
<text class="info-label">故障时间</text>
<text class="info-value">{{ detail.faultTime }}</text>
</view>
<view class="info-row">
<text class="info-label">人员</text>
<text class="info-value">{{ detail.personnel }}</text>
</view>
<view class="info-row">
<text class="info-label">故障原因</text>
<text class="info-value">{{ detail.faultReason }}</text>
</view>
<view class="info-row">
<text class="info-label">表显故障里程</text>
<text class="info-value">{{ detail.mileage }}</text>
</view>
<view class="info-row">
<text class="info-label">所属光缆</text>
<text class="info-value">{{ detail.cableName }}</text>
</view>
<view class="info-row">
<text class="info-label">地点</text>
<text class="info-value">{{ detail.location }}</text>
</view>
<view class="info-row last-row">
<text class="info-label">备注</text>
<text class="info-value">{{ detail.remark }}</text>
</view>
</view>
</view>
<!-- 底部导航按钮 -->
<view class="bottom-bar" v-if="hasLocation">
<view class="navigate-btn" @click="handleNavigate">
<text class="navigate-btn-text">导航至地点</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getFaultDetail } from '@/services/trunk'
import { BASE_URL } from '@/services/api'
import { openNavigation } from '@/utils/navigation'
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
const faultId = ref('')
const imageList = ref([])
const imageStatus = reactive({}) // 'loading' | 'loaded' | 'error'
const detail = reactive({
faultTime: '',
personnel: '',
faultReason: '',
mileage: '',
cableName: '',
location: '',
latitude: 0,
longitude: 0,
remark: ''
})
const hasLocation = computed(() => {
return detail.latitude && detail.longitude &&
Number(detail.latitude) !== 0 && Number(detail.longitude) !== 0
})
async function loadDetail() {
try {
const res = await getFaultDetail(faultId.value)
if (res.code === 200 && res.data) {
const d = res.data
detail.faultTime = d.faultTime || ''
detail.personnel = d.personnel || ''
detail.faultReason = d.faultReason || ''
detail.mileage = d.mileage || ''
detail.cableName = d.cableName || ''
detail.location = d.location || ''
detail.latitude = d.latitude || 0
detail.longitude = d.longitude || 0
// 如果没有地点描述但有经纬度,用经纬度显示
if (!detail.location && detail.latitude && detail.longitude) {
detail.location = `经度:${detail.longitude} 纬度:${detail.latitude}`
}
detail.remark = d.remark || ''
imageList.value = (d.images || []).map((img, i) => {
const url = img.url || img.imageUrl || ''
imageStatus[i] = 'loading'
// 相对路径需要拼接 BASE_URL
return url.startsWith('http') ? url : BASE_URL + url
})
}
} catch (err) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
}
function onImageLoad(index) {
imageStatus[index] = 'loaded'
}
function onImageError(index) {
imageStatus[index] = 'error'
}
function goBack() {
uni.navigateBack()
}
function previewImage(index) {
uni.previewImage({
urls: imageList.value,
current: imageList.value[index]
})
}
function handleNavigate() {
openNavigation(detail.latitude, detail.longitude, detail.location || '故障地点')
}
onLoad((options) => {
if (options.faultId) {
faultId.value = options.faultId
}
loadDetail()
})
</script>
<style scoped>
.fault-detail-page {
position: relative;
min-height: 100vh;
background-color: #F5F5F5;
padding-bottom: 120rpx;
}
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 500rpx;
z-index: 0;
}
.content {
position: relative;
z-index: 1;
}
.nav-bar {
width: 100%;
}
.nav-bar-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 24rpx;
}
.nav-icon {
width: 44rpx;
height: 44rpx;
}
.nav-icon-placeholder {
width: 44rpx;
height: 44rpx;
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #fff;
}
.image-area {
padding: 24rpx;
}
.image-scroll {
white-space: nowrap;
}
.image-grid {
display: inline-flex;
gap: 16rpx;
}
.image-item {
width: 280rpx;
height: 280rpx;
border-radius: 8rpx;
flex-shrink: 0;
}
.image-wrapper {
position: relative;
width: 280rpx;
height: 280rpx;
flex-shrink: 0;
border-radius: 8rpx;
overflow: hidden;
}
.image-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #E8E8E8;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
.placeholder-text {
font-size: 24rpx;
color: #999;
}
.image-hidden {
opacity: 0;
}
.info-area {
background-color: #fff;
margin: 0 24rpx;
padding: 24rpx;
border-radius: 12rpx;
}
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
}
.info-row.last-row {
margin-bottom: 0;
}
.info-label {
font-size: 26rpx;
color: #999;
flex-shrink: 0;
width: 180rpx;
}
.info-value {
font-size: 26rpx;
color: #333;
flex: 1;
word-break: break-all;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding: 24rpx;
background: #fff;
box-sizing: border-box;
}
.navigate-btn {
width: 100%;
height: 88rpx;
background: #1A73EC;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
}
.navigate-btn-text {
color: #fff;
font-size: 32rpx;
}
</style>