244 lines
4.8 KiB
Vue
244 lines
4.8 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="custom-nav">
|
||
<view class="nav-back" @tap="goBack">
|
||
<text class="back-arrow">←</text>
|
||
</view>
|
||
<view class="nav-title">失物招领</view>
|
||
</view>
|
||
<scroll-view
|
||
scroll-y
|
||
class="scroll-view"
|
||
@scrolltolower="onLoadMore"
|
||
refresher-enabled
|
||
:refresher-triggered="refreshing"
|
||
@refresherrefresh="onRefresh"
|
||
>
|
||
<view v-if="list.length > 0">
|
||
<view
|
||
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>
|
||
<view class="item-info">
|
||
<text>地点:{{ item.location }}</text>
|
||
<text>时间:{{ formatDate(item.dateFound) }}</text>
|
||
</view>
|
||
<view :class="['status-tag', item.status === '0' ? 'status-pending' : 'status-claimed']">
|
||
{{ item.status === '0' ? '待认领' : '已认领' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-box">
|
||
<text class="empty-text">暂无失物招领信息</text>
|
||
</view>
|
||
<view v-if="loading" class="loading">加载中...</view>
|
||
<view v-if="!hasMore && list.length > 0" class="no-more">没有更多了</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import dayjs from 'dayjs'
|
||
import lostAndFoundApi from '@/sheep/api/lostAndFound'
|
||
|
||
interface LostAndFoundItem {
|
||
id: string
|
||
title: string
|
||
picUrl: string | null
|
||
description: string
|
||
dateFound: string
|
||
location: string
|
||
status: string
|
||
}
|
||
|
||
interface ApiResponse {
|
||
code: number
|
||
data: {
|
||
records: LostAndFoundItem[]
|
||
pages: number
|
||
}
|
||
}
|
||
|
||
const list = ref<LostAndFoundItem[]>([])
|
||
const current = ref(1)
|
||
const hasMore = ref(true)
|
||
const loading = ref(false)
|
||
const refreshing = ref(false)
|
||
|
||
const fetchData = async (page: number = 1) => {
|
||
try {
|
||
loading.value = true
|
||
const response = await lostAndFoundApi.getList({
|
||
current: page,
|
||
size: 10,
|
||
}) as unknown as ApiResponse
|
||
|
||
if (response.code === 0 && response.data) {
|
||
if (page === 1) {
|
||
list.value = response.data.records
|
||
} else {
|
||
list.value = [...list.value, ...response.data.records]
|
||
}
|
||
hasMore.value = page < response.data.pages
|
||
current.value = page
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch lost and found items:', error)
|
||
} finally {
|
||
loading.value = false
|
||
refreshing.value = false
|
||
}
|
||
}
|
||
|
||
const onLoadMore = () => {
|
||
if (!loading.value && hasMore.value) {
|
||
fetchData(current.value + 1)
|
||
}
|
||
}
|
||
|
||
const onRefresh = async () => {
|
||
refreshing.value = true
|
||
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')
|
||
}
|
||
|
||
const goBack = () => {
|
||
uni.navigateBack()
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchData()
|
||
})
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.container {
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.custom-nav {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 88rpx;
|
||
background-color: #1777FF;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 30rpx;
|
||
z-index: 100;
|
||
}
|
||
|
||
.nav-back {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.back-arrow {
|
||
color: #fff;
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.nav-title {
|
||
flex: 1;
|
||
text-align: center;
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
margin-right: 60rpx; // 为了保持标题居中,抵消返回按钮的宽度
|
||
}
|
||
|
||
.scroll-view {
|
||
height: calc(100vh - 88rpx);
|
||
margin-top: 88rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.item-card {
|
||
margin: 12px;
|
||
padding: 16px;
|
||
background-color: #fff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.item-title {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.item-description {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.item-info {
|
||
display: flex;
|
||
gap: 16px;
|
||
color: #999;
|
||
font-size: 12px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.status-tag {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.status-pending {
|
||
background-color: #e6f7ff;
|
||
color: #1890ff;
|
||
}
|
||
|
||
.status-claimed {
|
||
background-color: #f6ffed;
|
||
color: #52c41a;
|
||
}
|
||
|
||
.empty-box {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 200px;
|
||
}
|
||
|
||
.empty-text {
|
||
color: #999;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.loading, .no-more {
|
||
text-align: center;
|
||
padding: 12px;
|
||
color: #999;
|
||
font-size: 12px;
|
||
}
|
||
</style> |