pet/pages/adoption/my-applications.vue

832 lines
19 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="my-applications-container page-container-with-bg">
<!-- 统计卡片 -->
<view class="stats-card">
<view class="stats-grid">
<view class="stat-item" :class="{ active: currentFilter === 'all' }" @click="setFilter('all')">
<view class="stat-number">{{ totalCount }}</view>
<view class="stat-label">全部</view>
</view>
<view class="stat-item" :class="{ active: currentFilter === 'pending' }" @click="setFilter('pending')">
<view class="stat-number">{{ pendingCount }}</view>
<view class="stat-label">待审核</view>
</view>
<view class="stat-item" :class="{ active: currentFilter === 'approved' }" @click="setFilter('approved')">
<view class="stat-number">{{ approvedCount }}</view>
<view class="stat-label">已通过</view>
</view>
</view>
</view>
<!-- 申请列表 -->
<view class="applications-list" v-if="filteredList.length > 0">
<view
class="application-item"
v-for="item in filteredList"
:key="item.id"
@click="viewDetail(item)"
>
<view class="item-image">
<image class="pet-image" :src="item.petPhoto || '/static/default-pet.png'" mode="aspectFill" />
</view>
<view class="item-content">
<view class="item-header">
<view class="pet-name">{{ item.petName }}</view>
<view class="status-badge" :class="item.status">
<text class="status-text">{{ getStatusText(item.status) }}</text>
</view>
</view>
<view class="pet-info">
<view class="info-row">
<text class="info-text">{{ item.petBreed }} · {{ item.petGender }} · {{ item.petAge }}</text>
</view>
<view class="info-row">
<text class="info-label">发布者:</text>
<text class="info-value">{{ item.publisherName }}</text>
</view>
</view>
<view class="application-info">
<view class="info-row">
<text class="info-label">申请时间:</text>
<text class="info-value">{{ formatTime(item.applicationTime) }}</text>
</view>
<view class="info-row" v-if="item.responseTime">
<text class="info-label">回复时间:</text>
<text class="info-value">{{ formatTime(item.responseTime) }}</text>
</view>
</view>
<view class="application-message" v-if="item.message">
<view class="message-label">申请留言:</view>
<view class="message-content">{{ item.message }}</view>
</view>
<view class="response-message" v-if="item.responseMessage">
<view class="message-label">回复消息:</view>
<view class="message-content">{{ item.responseMessage }}</view>
</view>
</view>
<view class="item-actions">
<view class="action-button" @click.stop="showActions(item)">
<text class="action-icon">⋯</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else>
<view class="empty-icon">📋</view>
<view class="empty-text">{{ getEmptyText() }}</view>
<view class="empty-action" @click="browseAdoptions" v-if="currentFilter === 'all'">
<text class="action-text">去看看可领养的宠物</text>
</view>
</view>
<!-- 操作菜单 -->
<u-action-sheet
:show="showActionSheet"
:actions="actionList"
@close="showActionSheet = false"
@select="onActionSelect"
></u-action-sheet>
<!-- 申请详情弹窗 -->
<u-modal
:show="showDetailModal"
:title="currentItem?.petName || '申请详情'"
@confirm="closeDetailModal"
@cancel="closeDetailModal"
:show-cancel-button="false"
confirm-text="关闭"
>
<view class="detail-content" v-if="currentItem">
<view class="detail-section">
<text class="section-title">宠物信息</text>
<view class="detail-row">
<text class="detail-label">名称:</text>
<text class="detail-value">{{ currentItem.petName }}</text>
</view>
<view class="detail-row">
<text class="detail-label">品种:</text>
<text class="detail-value">{{ currentItem.petBreed }}</text>
</view>
<view class="detail-row">
<text class="detail-label">性别:</text>
<text class="detail-value">{{ currentItem.petGender }}</text>
</view>
<view class="detail-row">
<text class="detail-label">年龄:</text>
<text class="detail-value">{{ currentItem.petAge }}</text>
</view>
</view>
<view class="detail-section">
<text class="section-title">申请信息</text>
<view class="detail-row">
<text class="detail-label">状态:</text>
<text class="detail-value" :class="currentItem.status">{{ getStatusText(currentItem.status) }}</text>
</view>
<view class="detail-row">
<text class="detail-label">申请时间:</text>
<text class="detail-value">{{ formatDateTime(currentItem.applicationTime) }}</text>
</view>
<view class="detail-row" v-if="currentItem.responseTime">
<text class="detail-label">回复时间:</text>
<text class="detail-value">{{ formatDateTime(currentItem.responseTime) }}</text>
</view>
</view>
<view class="detail-section" v-if="currentItem.message">
<text class="section-title">申请留言</text>
<view class="message-box">{{ currentItem.message }}</view>
</view>
<view class="detail-section" v-if="currentItem.responseMessage">
<text class="section-title">回复消息</text>
<view class="message-box response">{{ currentItem.responseMessage }}</view>
</view>
</view>
</u-modal>
</view>
</template>
<script>
import { reactive, ref, onMounted, computed } from 'vue'
export default {
name: 'MyApplicationsPage',
setup() {
// 响应式数据
const applicationsList = ref([])
const currentFilter = ref('all')
const showActionSheet = ref(false)
const showDetailModal = ref(false)
const currentItem = ref(null)
const actionList = ref([
{ name: '查看详情', value: 'detail' },
{ name: '联系发布者', value: 'contact' },
{ name: '撤销申请', value: 'cancel', color: '#FF5722' }
])
// 计算属性
const totalCount = computed(() => applicationsList.value.length)
const pendingCount = computed(() => applicationsList.value.filter(item => item.status === 'pending').length)
const approvedCount = computed(() => applicationsList.value.filter(item => item.status === 'approved').length)
const filteredList = computed(() => {
if (currentFilter.value === 'all') {
return applicationsList.value
}
return applicationsList.value.filter(item => item.status === currentFilter.value)
})
// 生命周期
onMounted(() => {
loadApplicationsList()
})
// 方法定义
const loadApplicationsList = () => {
try {
const savedList = uni.getStorageSync('myAdoptionApplications') || []
// 如果没有数据,创建模拟数据
if (savedList.length === 0) {
const mockData = [
{
id: 'app_1',
petName: '小白',
petBreed: '英短',
petGender: '母',
petAge: '1岁',
petPhoto: '/static/mock-cat2.jpg',
publisherName: '爱心铲屎官',
status: 'pending',
applicationTime: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
message: '我很喜欢猫咪有3年的养猫经验家里环境很适合养宠物希望能给小白一个温暖的家。',
responseTime: null,
responseMessage: null
},
{
id: 'app_2',
petName: '小黑',
petBreed: '田园犬',
petGender: '公',
petAge: '2岁',
petPhoto: '/static/mock-dog1.jpg',
publisherName: '宠物救助站',
status: 'approved',
applicationTime: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
message: '我想领养小黑,我有充足的时间照顾它,也有养狗的经验。',
responseTime: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
responseMessage: '感谢您的申请!经过审核,我们认为您很适合领养小黑。请联系我们安排见面。'
},
{
id: 'app_3',
petName: '小花',
petBreed: '三花猫',
petGender: '母',
petAge: '6个月',
petPhoto: '/static/mock-cat3.jpg',
publisherName: '个人用户',
status: 'rejected',
applicationTime: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
message: '希望能领养小花,我会好好照顾它的。',
responseTime: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString(),
responseMessage: '很抱歉,我们已经为小花找到了合适的家庭。感谢您的关注!'
}
]
uni.setStorageSync('myAdoptionApplications', mockData)
applicationsList.value = mockData
} else {
applicationsList.value = savedList
}
} catch (error) {
console.error('加载申请列表失败:', error)
}
}
const saveApplicationsList = () => {
uni.setStorageSync('myAdoptionApplications', applicationsList.value)
}
const setFilter = (filter) => {
currentFilter.value = filter
}
const getStatusText = (status) => {
const statusMap = {
'pending': '待审核',
'approved': '已通过',
'rejected': '已拒绝',
'cancelled': '已取消'
}
return statusMap[status] || '未知'
}
const getEmptyText = () => {
switch (currentFilter.value) {
case 'pending':
return '暂无待审核的申请'
case 'approved':
return '暂无已通过的申请'
default:
return '还没有申请过领养'
}
}
const formatTime = (timeString) => {
if (!timeString) return ''
const date = new Date(timeString)
const now = new Date()
const diffTime = now - date
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
if (diffDays === 0) {
return '今天'
} else if (diffDays === 1) {
return '昨天'
} else if (diffDays < 7) {
return `${diffDays}天前`
} else {
return date.toLocaleDateString('zh-CN')
}
}
const formatDateTime = (timeString) => {
if (!timeString) return ''
const date = new Date(timeString)
return date.toLocaleString('zh-CN')
}
const viewDetail = (item) => {
currentItem.value = item
showDetailModal.value = true
}
const closeDetailModal = () => {
showDetailModal.value = false
currentItem.value = null
}
const showActions = (item) => {
currentItem.value = item
// 根据状态调整操作菜单
if (item.status === 'pending') {
actionList.value = [
{ name: '查看详情', value: 'detail' },
{ name: '联系发布者', value: 'contact' },
{ name: '撤销申请', value: 'cancel', color: '#FF5722' }
]
} else {
actionList.value = [
{ name: '查看详情', value: 'detail' },
{ name: '联系发布者', value: 'contact' }
]
}
showActionSheet.value = true
}
const onActionSelect = (action) => {
switch (action.value) {
case 'detail':
viewDetail(currentItem.value)
break
case 'contact':
contactPublisher(currentItem.value)
break
case 'cancel':
cancelApplication(currentItem.value)
break
}
showActionSheet.value = false
}
const contactPublisher = (item) => {
uni.showModal({
title: '联系发布者',
content: `发布者:${item.publisherName}\n\n您可以通过以下方式联系\n• 站内私信\n• 电话联系\n• 微信联系`,
showCancel: false
})
}
const cancelApplication = (item) => {
uni.showModal({
title: '撤销申请',
content: '确定要撤销这个领养申请吗?撤销后无法恢复。',
success: (res) => {
if (res.confirm) {
item.status = 'cancelled'
saveApplicationsList()
uni.showToast({
title: '申请已撤销',
icon: 'success'
})
}
}
})
}
const browseAdoptions = () => {
uni.navigateTo({
url: '/pages/adoption/adoption',
fail: () => {
uni.showToast({
title: '页面开发中',
icon: 'none'
})
}
})
}
return {
applicationsList,
currentFilter,
showActionSheet,
showDetailModal,
currentItem,
actionList,
totalCount,
pendingCount,
approvedCount,
filteredList,
setFilter,
getStatusText,
getEmptyText,
formatTime,
formatDateTime,
viewDetail,
closeDetailModal,
showActions,
onActionSelect,
browseAdoptions
}
}
}
</script>
<style lang="scss" scoped>
.my-applications-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
padding-bottom: 40rpx;
}
/* 统计卡片 */
.stats-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20rpx;
.stat-item {
text-align: center;
padding: 24rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
transition: all 0.3s ease;
cursor: pointer;
&.active {
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
.stat-number,
.stat-label {
color: white;
}
}
&:active {
transform: scale(0.95);
}
.stat-number {
font-size: 40rpx;
font-weight: 700;
color: #FF8A80;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 22rpx;
color: #666666;
}
}
}
}
/* 申请列表 */
.applications-list {
margin: 0 30rpx;
.application-item {
display: flex;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.1);
border: 1rpx solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
.item-image {
margin-right: 20rpx;
.pet-image {
width: 120rpx;
height: 120rpx;
border-radius: 16rpx;
}
}
.item-content {
flex: 1;
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.pet-name {
font-size: 30rpx;
font-weight: 700;
color: #333333;
}
.status-badge {
padding: 6rpx 12rpx;
border-radius: 12rpx;
font-size: 20rpx;
&.pending {
background: #FFF3E0;
color: #FF9800;
border: 1rpx solid #FFE0B2;
}
&.approved {
background: #E8F5E8;
color: #4CAF50;
border: 1rpx solid #C8E6C9;
}
&.rejected {
background: #FFEBEE;
color: #F44336;
border: 1rpx solid #FFCDD2;
}
&.cancelled {
background: #F5F5F5;
color: #757575;
border: 1rpx solid #E0E0E0;
}
.status-text {
font-size: 20rpx;
font-weight: 500;
}
}
}
.pet-info {
margin-bottom: 12rpx;
.info-row {
display: flex;
margin-bottom: 6rpx;
&:last-child {
margin-bottom: 0;
}
.info-text {
font-size: 24rpx;
color: #666666;
}
.info-label {
font-size: 22rpx;
color: #999999;
margin-right: 8rpx;
}
.info-value {
font-size: 22rpx;
color: #666666;
}
}
}
.application-info {
margin-bottom: 12rpx;
.info-row {
display: flex;
margin-bottom: 6rpx;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 22rpx;
color: #999999;
margin-right: 8rpx;
}
.info-value {
font-size: 22rpx;
color: #666666;
}
}
}
.application-message,
.response-message {
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
.message-label {
font-size: 22rpx;
color: #999999;
margin-bottom: 6rpx;
}
.message-content {
font-size: 24rpx;
color: #666666;
line-height: 1.5;
background: rgba(255, 138, 128, 0.05);
padding: 12rpx;
border-radius: 8rpx;
}
}
.response-message {
.message-content {
background: rgba(76, 175, 80, 0.05);
border-left: 4rpx solid #4CAF50;
}
}
}
.item-actions {
display: flex;
align-items: flex-start;
.action-button {
padding: 8rpx 12rpx;
border-radius: 12rpx;
background: rgba(255, 138, 128, 0.1);
.action-icon {
font-size: 24rpx;
color: #FF8A80;
}
}
}
}
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 80rpx 40rpx;
margin: 0 30rpx;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.empty-icon {
font-size: 80rpx;
margin-bottom: 24rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
color: #999999;
margin-bottom: 32rpx;
line-height: 1.5;
}
.empty-action {
display: inline-block;
padding: 16rpx 32rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 20rpx;
.action-text {
font-size: 26rpx;
color: white;
}
}
}
/* 详情弹窗 */
.detail-content {
padding: 20rpx 0;
.detail-section {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.section-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
padding-bottom: 8rpx;
border-bottom: 2rpx solid rgba(255, 138, 128, 0.2);
}
.detail-row {
display: flex;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
.detail-label {
font-size: 24rpx;
color: #666666;
width: 120rpx;
flex-shrink: 0;
}
.detail-value {
font-size: 24rpx;
color: #333333;
flex: 1;
&.pending {
color: #FF9800;
}
&.approved {
color: #4CAF50;
}
&.rejected {
color: #F44336;
}
&.cancelled {
color: #757575;
}
}
}
.message-box {
font-size: 24rpx;
color: #666666;
line-height: 1.6;
background: rgba(255, 138, 128, 0.05);
padding: 16rpx;
border-radius: 12rpx;
&.response {
background: rgba(76, 175, 80, 0.05);
border-left: 4rpx solid #4CAF50;
}
}
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.my-applications-container {
.stats-card,
.applications-list,
.empty-state {
margin-left: 20rpx;
margin-right: 20rpx;
}
.stats-card {
padding: 24rpx;
}
.stats-grid {
gap: 16rpx;
.stat-item {
padding: 20rpx 12rpx;
.stat-number {
font-size: 36rpx;
}
}
}
.application-item {
padding: 20rpx;
.item-image {
margin-right: 16rpx;
.pet-image {
width: 100rpx;
height: 100rpx;
}
}
}
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.my-applications-container > view {
animation: fadeIn 0.5s ease-out;
}
.application-item {
animation: fadeIn 0.3s ease-out;
}
/* 状态过渡动画 */
.status-badge {
animation: fadeIn 0.4s ease-out;
}
.action-button:active {
transform: scale(0.9);
}
</style>