petstore-frontend/pages/mine/my-orders.vue
2026-04-12 22:57:48 +08:00

177 lines
6.2 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="orders-page">
<!-- 导航栏 -->
<view class="nav-bar">
<text class="nav-back" @click="goBack"></text>
<text class="nav-title">我的订单</text>
<view style="width:40px;"></view>
</view>
<!-- Tab切换 -->
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.key"
:class="['tab-item', { active: currentStatus === tab.key }]"
@click="currentStatus = tab.key"
>{{ tab.label }}</view>
</view>
<!-- 列表 -->
<view class="order-list">
<view v-for="item in filteredOrders" :key="item.id" class="order-item">
<view class="order-title">{{ item.title }}</view>
<view class="order-desc">{{ item.desc }}</view>
<view class="order-footer">
<text class="order-time">{{ item.time }}</text>
<view class="status-tag" :class="'status-' + item.status">{{ item.statusText }}</view>
</view>
<view v-if="item.status === 'new'" class="action-btns">
<view class="btn-sm btn-primary-sm" @click="startService(item)">开始服务</view>
<view class="btn-sm btn-default-sm" @click="cancelService(item)">取消</view>
</view>
<view v-else-if="item.status === 'doing'">
<view class="btn-sm btn-outline-sm" @click="goReport(item)">填写报告</view>
</view>
</view>
<view v-if="filteredOrders.length === 0" class="empty-wrap">
<text class="empty-icon">📦</text>
<text class="empty-text">暂无数据</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getAppointmentList, startAppointment, cancelAppointment } from '../../../utils/api.js'
const userInfo = JSON.parse(uni.getStorageSync('petstore_user') || '{}')
const currentUserId = userInfo.id
const currentStatus = ref('new')
const orders = ref([])
const tabs = [
{ key: 'new', label: '待确认' },
{ key: 'doing', label: '进行中' },
{ key: 'done', label: '已完成' }
]
const filteredOrders = computed(() => orders.value.filter(o => {
if (currentStatus.value === 'new') return o.status === 'new'
if (currentStatus.value === 'doing') return o.status === 'doing'
if (currentStatus.value === 'done') return o.status === 'done' || o.status === 'cancel'
return true
}))
const goBack = () => uni.navigateBack()
const fetchOrders = async () => {
if (!currentUserId) return
const res = await getAppointmentList(currentUserId)
if (res.code === 200) {
orders.value = res.data.map(appt => ({
id: appt.id,
title: appt.serviceType || '洗澡美容预约',
desc: `${appt.petType || ''} - ${appt.petName || ''}`,
time: appt.appointmentTime ? new Date(appt.appointmentTime).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '',
status: appt.status || 'new',
statusText: { new: '待确认', doing: '服务中', done: '已完成', cancel: '已取消' }[appt.status] || '待确认',
petName: appt.petName,
petType: appt.petType,
serviceType: appt.serviceType,
appointmentTime: appt.appointmentTime
}))
}
}
const startService = async (item) => {
const res = await startAppointment(item.id, userInfo.id)
if (res.code === 200) { uni.showToast({ title: '已开始服务', icon: 'success' }); fetchOrders() }
else { uni.showToast({ title: res.message || '操作失败', icon: 'none' }) }
}
const cancelService = async (item) => {
uni.showModal({
title: '确认取消',
content: '确定取消该预约?',
success: async (res) => {
if (res.confirm) {
const r = await cancelAppointment(item.id)
if (r.code === 200) { uni.showToast({ title: '已取消', icon: 'success' }); fetchOrders() }
else { uni.showToast({ title: r.message || '操作失败', icon: 'none' }) }
}
}
})
}
const goReport = (item) => {
uni.setStorageSync('petstore_report_prefill', JSON.stringify({
appointmentId: item.id, petName: item.petName, serviceType: item.serviceType, appointmentTime: item.appointmentTime
}))
uni.switchTab({ url: '/pages/report/report' })
}
onMounted(() => fetchOrders())
</script>
<style scoped>
.orders-page { padding-bottom: 20px; background: #f5f5f5; min-height: 100vh; }
.nav-bar {
background: #fff;
padding: 40px 16px 12px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
}
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
.tab-bar {
display: flex;
background: #fff;
padding: 0 16px;
border-bottom: 1px solid #eee;
}
.tab-item {
flex: 1;
text-align: center;
padding: 12px 0;
font-size: 14px;
color: #999;
border-bottom: 2px solid transparent;
}
.tab-item.active { color: #07c160; border-bottom-color: #07c160; font-weight: 600; }
.order-list { padding: 12px 16px 0; }
.order-item {
background: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.order-title { font-size: 15px; font-weight: 500; color: #333; margin-bottom: 6px; }
.order-desc { font-size: 13px; color: #999; margin-bottom: 10px; }
.order-footer { display: flex; justify-content: space-between; align-items: center; }
.order-time { font-size: 12px; color: #999; }
.action-btns { display: flex; gap: 8px; margin-top: 10px; }
.btn-sm { font-size: 12px; padding: 5px 14px; border-radius: 6px; display: inline-flex; align-items: center; justify-content: center; }
.btn-primary-sm { background: #07c160; color: #fff; border: 1px solid #07c160; }
.btn-default-sm { background: #fff; color: #666; border: 1px solid #ddd; }
.btn-outline-sm { background: #fff; color: #07c160; border: 1px solid #07c160; }
.status-tag { font-size: 12px; padding: 2px 8px; border-radius: 10px; }
.status-new { background: #fff3e8; color: #ff9f00; }
.status-doing { background: #e8f7ef; color: #07c160; }
.status-done { background: #f0f0f0; color: #888; }
.status-cancel { background: #f5f5f5; color: #999; }
.empty-wrap { display: flex; flex-direction: column; align-items: center; padding: 60px 0; }
.empty-icon { font-size: 48px; }
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
</style>