336 lines
7.4 KiB
Vue
336 lines
7.4 KiB
Vue
<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>
|