添加失物招领详情,修复拍照图片无法上传
This commit is contained in:
1 parent
20b5569412
commit
8641e708d7
6 files changed
+307
-5
No files matched your search
@@ -20,15 +20,15 @@ VITE_OAUTH2_APP_CLIENT='mini:mini'
|
||||
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
|
||||
#VITE_APP_DEV_SERVICE = http://59.202.170.152:81/api # 信创线上
|
||||
# VITE_APP_DEV_SERVICE = http://192.168.101.174:9999 # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
|
||||
VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
|
||||
VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
|
||||
#VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
|
||||
|
||||
# --------------------------- 开发环境 图片访问基础地址 ---------------------------
|
||||
# VITE_IMAGE_DEV_PATH = https://stgl.pj.gov.cn/api # 域名线上
|
||||
#VITE_IMAGE_DEV_PATH = http://59.202.170.152:81/api # 信创线上
|
||||
# VITE_IMAGE_DEV_PATH = http://192.168.101.174:9999 # 邓-本地
|
||||
# VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
|
||||
VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
|
||||
VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
|
||||
#VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
|
||||
|
||||
# --------------------------- 生产环境 api地址 ----------------------------------
|
||||
VITE_APP_PROD_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
|
||||
|
||||
@@ -507,6 +507,12 @@
|
||||
"navigationBarTitleText": "失物招领"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "lostAndFound/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "失物详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "meeting/index",
|
||||
"style": {
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<view class="detail-wrapper">
|
||||
<view v-if="loading" class="loading">加载中...</view>
|
||||
|
||||
<view v-else-if="detail">
|
||||
<view class="detail-card">
|
||||
<view class="detail-title">{{ detail.title || '失物招领详情' }}</view>
|
||||
<view class="detail-meta">
|
||||
<text class="meta-item">时间:{{ formatDate(detail.dateFound) }}</text>
|
||||
<text class="meta-item">地点:{{ detail.location || '暂无' }}</text>
|
||||
</view>
|
||||
<view class="detail-status" :class="statusClass">{{ statusText }}</view>
|
||||
</view>
|
||||
|
||||
<view v-if="imageList.length" class="image-list">
|
||||
<image
|
||||
v-for="(src, index) in imageList"
|
||||
:key="index"
|
||||
class="detail-image"
|
||||
:src="src"
|
||||
mode="aspectFill"
|
||||
@tap="previewImage(index)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="detail-section">
|
||||
<view class="section-title">物品描述</view>
|
||||
<rich-text v-if="descriptionNodes" :nodes="descriptionNodes"></rich-text>
|
||||
<view v-else class="section-content">暂无描述信息</view>
|
||||
</view>
|
||||
|
||||
<view v-if="contactInfo" class="detail-section">
|
||||
<view class="section-title">联系方式</view>
|
||||
<view class="section-content">{{ contactInfo }}</view>
|
||||
</view>
|
||||
|
||||
<view v-if="detail.remark" class="detail-section">
|
||||
<view class="section-title">备注</view>
|
||||
<view class="section-content">{{ detail.remark }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-state">暂无详情数据</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import dayjs from 'dayjs'
|
||||
import lostAndFoundApi from '@/sheep/api/lostAndFound'
|
||||
import { filePath } from '@/sheep/utils/file'
|
||||
|
||||
interface LostAndFoundDetail {
|
||||
id: string
|
||||
title?: string
|
||||
picUrl?: string | string[] | null
|
||||
description?: string
|
||||
dateFound?: string
|
||||
location?: string
|
||||
status?: string | number
|
||||
contactPerson?: string
|
||||
contactPhone?: string
|
||||
phone?: string
|
||||
userName?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
const detail = ref<LostAndFoundDetail | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const toAbsoluteUrl = (url?: string | null) => {
|
||||
if (!url) return ''
|
||||
if (/^https?:\/\//i.test(url)) return url
|
||||
return filePath(url)
|
||||
}
|
||||
|
||||
const statusText = computed(() => {
|
||||
const status = detail.value?.status?.toString()
|
||||
if (status === '1') return '已认领'
|
||||
if (status === '2') return '已撤销'
|
||||
return '待认领'
|
||||
})
|
||||
|
||||
const statusClass = computed(() => {
|
||||
const status = detail.value?.status?.toString()
|
||||
if (status === '1') return 'status-claimed'
|
||||
if (status === '2') return 'status-cancelled'
|
||||
return 'status-pending'
|
||||
})
|
||||
|
||||
const imageList = computed(() => {
|
||||
const imgs = detail.value?.picUrl
|
||||
if (!imgs) return [] as string[]
|
||||
const rawList = Array.isArray(imgs)
|
||||
? imgs
|
||||
: imgs
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
|
||||
return rawList
|
||||
.map((item) => toAbsoluteUrl(item))
|
||||
.filter((item): item is string => Boolean(item))
|
||||
})
|
||||
|
||||
const descriptionNodes = computed(() => {
|
||||
const desc = detail.value?.description
|
||||
if (!desc) return ''
|
||||
return desc.replace(/<img\s+[^>]*src=["']([^"']+)["']/gi, (match, src) => {
|
||||
const fullSrc = toAbsoluteUrl(src)
|
||||
if (!fullSrc) return match
|
||||
return match.replace(src, fullSrc)
|
||||
})
|
||||
})
|
||||
|
||||
const contactInfo = computed(() => {
|
||||
const person = detail.value?.contactPerson || detail.value?.userName
|
||||
const phone = detail.value?.contactPhone || detail.value?.phone
|
||||
if (person && phone) return `${person} ${phone}`
|
||||
return person || phone || ''
|
||||
})
|
||||
|
||||
const formatDate = (date?: string) => {
|
||||
if (!date) return '未知'
|
||||
return dayjs(date).format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
|
||||
const previewImage = (index: number) => {
|
||||
if (!imageList.value.length) return
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: imageList.value,
|
||||
})
|
||||
}
|
||||
|
||||
const fetchDetail = async (id: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response: any = await lostAndFoundApi.getDetail(id)
|
||||
if (response?.code === 0 && response.data) {
|
||||
detail.value = {
|
||||
...detail.value,
|
||||
...response.data,
|
||||
status: response.data.status ?? detail.value?.status,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch lost-and-found detail:', error)
|
||||
if (!detail.value) {
|
||||
uni.showToast({ title: '获取详情失败', icon: 'none' })
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options: Record<string, any>) => {
|
||||
try {
|
||||
const eventChannel = getOpenerEventChannel?.()
|
||||
eventChannel?.on('initDetail', (data: LostAndFoundDetail) => {
|
||||
detail.value = data
|
||||
})
|
||||
} catch (error) {
|
||||
// H5 环境可能不存在 getOpenerEventChannel
|
||||
console.warn('initDetail event registration failed:', error)
|
||||
}
|
||||
|
||||
const id = options?.id
|
||||
if (id) {
|
||||
fetchDetail(id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.detail-wrapper {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.empty-state {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.detail-status {
|
||||
margin-top: 16rpx;
|
||||
display: inline-block;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-claimed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background-color: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.image-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.detail-image {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="item-card"
|
||||
@tap="viewDetail(item)"
|
||||
>
|
||||
<view class="item-title">{{ item.title }}</view>
|
||||
<view class="item-description" v-html="item.description"></view>
|
||||
@@ -105,6 +106,16 @@ const onRefresh = async () => {
|
||||
await fetchData(1)
|
||||
}
|
||||
|
||||
const viewDetail = (item: LostAndFoundItem) => {
|
||||
if (!item?.id) return
|
||||
uni.navigateTo({
|
||||
url: `/pages/integrated/lostAndFound/detail?id=${item.id}`,
|
||||
success: (res) => {
|
||||
res.eventChannel.emit('initDetail', item)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const formatDate = (date: string) => {
|
||||
return dayjs(date).format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
v-model="uploadFileUrl"
|
||||
:url="'/api/admin/sys-file/upload'"
|
||||
:maxCount="5"
|
||||
:maxFileSize="1024 * 1024"
|
||||
:maxFileSize="10 * 1024 * 1024"
|
||||
:beforeComplete="beforeComplete"
|
||||
:header="header"
|
||||
/>
|
||||
|
||||
@@ -33,4 +33,12 @@ export default {
|
||||
method: "GET",
|
||||
params,
|
||||
}),
|
||||
|
||||
// 获取失物招领详情
|
||||
getDetail: (id: string) =>
|
||||
request({
|
||||
url: "integrated/lostAndFound/app/getDetails",
|
||||
method: "GET",
|
||||
params: { id },
|
||||
}),
|
||||
}
|
||||
Reference in new issue
Block a user