campus-errand/miniapp/pages/food/food-order-detail.vue
18631081161 1fd330b233
Some checks reported errors
continuous-integration/drone/push Build encountered an error
下单优化
2026-04-03 15:40:09 +08:00

493 lines
11 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="detail-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">美食街订单详情</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<view v-if="loading" class="loading-tip">加载中...</view>
<view v-else-if="order" class="detail-content">
<!-- 订单基本信息 -->
<view class="section">
<view class="info-row">
<text class="info-label">送达地点</text>
<text class="info-value">{{ order.deliveryLocation }}</text>
</view>
<view v-if="order.remark" class="info-row">
<text class="info-label">备注信息</text>
<text class="info-value">{{ order.remark }}</text>
</view>
<view class="info-row">
<text class="info-label">跑腿佣金</text>
<text class="info-value price">¥{{ order.commission }}</text>
</view>
</view>
<!-- 菜品列表,按门店分组 -->
<view class="section" v-for="shop in shopGroups" :key="shop.shopId">
<view class="shop-header">
<image v-if="shop.shopPhoto" class="shop-photo" :src="shop.shopPhoto" mode="aspectFill" @click="previewImage(shop.shopPhoto)"></image>
<text class="shop-name">{{ shop.shopName }}</text>
</view>
<view class="dish-item" v-for="item in shop.items" :key="item.id">
<image v-if="item.dishPhoto" class="dish-photo" :src="item.dishPhoto" mode="aspectFill"></image>
<view class="dish-info">
<text class="dish-name">{{ item.dishName }}</text>
<view class="dish-meta">
<text class="dish-price">¥{{ item.unitPrice }} × {{ item.quantity }}</text>
</view>
</view>
</view>
</view>
<!-- 费用汇总 -->
<view class="section summary">
<view class="info-row" v-if="order.packingFee">
<text class="info-label">打包费等额外费用</text>
<text class="info-value price">¥{{ order.packingFee }}</text>
</view>
<view class="info-row">
<text class="info-label">垫付金额总和</text>
<text class="info-value price">¥{{ order.goodsAmount }}</text>
</view>
</view>
</view>
<!-- 底部接单按钮 -->
<view v-if="order && order.status === 'Pending'" class="bottom-bar">
<button class="accept-btn" @click="onAcceptClick" :loading="accepting">接单</button>
</view>
<!-- 接单确认弹窗 -->
<view class="modal-mask" v-if="showAcceptModal" @click="showAcceptModal = false">
<view class="modal-content" @click.stop>
<text class="modal-title">确认接单</text>
<text class="modal-desc">确认接取该美食街订单?接单后请尽快完成。</text>
<view class="modal-actions">
<button class="modal-btn cancel" @click="showAcceptModal = false">取消</button>
<button class="modal-btn confirm" @click="confirmAccept" :loading="accepting">接单</button>
</view>
</view>
</view>
<!-- 跑腿认证弹窗 -->
<view class="modal-mask" v-if="showCertModal" @click="showCertModal = false">
<view class="modal-content" @click.stop>
<text class="modal-title">跑腿认证</text>
<text class="modal-desc">接单前需完成跑腿认证</text>
<view class="cert-form">
<view class="form-item">
<text class="form-label">姓名</text>
<input class="form-input" v-model="certForm.realName" placeholder="请输入真实姓名" />
</view>
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="certForm.phone" type="number" placeholder="请输入手机号" maxlength="11" />
</view>
</view>
<view class="modal-actions">
<button class="modal-btn cancel" @click="showCertModal = false">取消</button>
<button class="modal-btn confirm" @click="submitCert" :loading="certSubmitting">提交</button>
</view>
</view>
</view>
</view>
</template>
<script>
import { getOrderDetail, acceptOrder, getCertificationStatus, submitCertification } from '../../utils/api'
export default {
data() {
return {
orderId: null,
order: null,
loading: true,
// 接单
showAcceptModal: false,
accepting: false,
// 认证
showCertModal: false,
certForm: { realName: '', phone: '' },
certSubmitting: false,
certStatus: null,
statusBarHeight: 0
}
},
computed: {
/** 按门店分组菜品 */
shopGroups() {
if (!this.order?.foodItems) return []
const map = {}
for (const item of this.order.foodItems) {
const key = item.shopId
if (!map[key]) {
map[key] = {
shopId: item.shopId,
shopName: item.shopName || `门店${item.shopId}`,
shopPhoto: item.shopPhoto || '',
items: []
}
}
map[key].items.push(item)
}
return Object.values(map)
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.orderId = options.id
this.loadDetail()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载订单详情 */
async loadDetail() {
this.loading = true
try {
this.order = await getOrderDetail(this.orderId)
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
/** 点击接单 */
async onAcceptClick() {
try {
if (this.certStatus === null) {
const res = await getCertificationStatus()
this.certStatus = res?.status || null
}
if (this.certStatus === 'Approved') {
this.showAcceptModal = true
} else if (this.certStatus === 'Pending') {
uni.showToast({ title: '平台审核中', icon: 'none' })
} else {
this.certForm = { realName: '', phone: '' }
this.showCertModal = true
}
} catch (e) {
this.certForm = { realName: '', phone: '' }
this.showCertModal = true
}
},
/** 确认接单 */
async confirmAccept() {
this.accepting = true
try {
await acceptOrder(this.orderId)
uni.showToast({ title: '接单成功', icon: 'success' })
this.showAcceptModal = false
setTimeout(() => { uni.navigateBack() }, 1500)
} catch (e) {
// 并发接单提示已在 request 中处理
} finally {
this.accepting = false
}
},
/** 预览图片 */
previewImage(url) {
if (!url) return
uni.previewImage({ urls: [url] })
},
async submitCert() {
if (!this.certForm.realName.trim()) {
uni.showToast({ title: '请输入姓名', icon: 'none' })
return
}
if (!this.certForm.phone.trim()) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return
}
this.certSubmitting = true
try {
await submitCertification({
realName: this.certForm.realName.trim(),
phone: this.certForm.phone.trim()
})
this.certStatus = 'Pending'
this.showCertModal = false
uni.showToast({ title: '认证已提交,等待审核', icon: 'none' })
} catch (e) {
// 错误已在 request 中处理
} finally {
this.certSubmitting = false
}
}
}
}
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.detail-page {
padding: 24rpx;
padding-bottom: 180rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.loading-tip {
text-align: center;
color: #999999;
font-size: 28rpx;
padding: 80rpx 0;
}
.section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
}
.info-row {
display: flex;
padding: 8rpx 0;
}
.info-label {
font-size: 26rpx;
color: #999999;
width: 200rpx;
flex-shrink: 0;
}
.info-value {
font-size: 26rpx;
color: #333333;
flex: 1;
}
.info-value.price {
color: #e64340;
font-weight: bold;
}
.shop-header {
display: flex;
align-items: center;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #f0f0f0;
margin-bottom: 16rpx;
}
.shop-photo {
width: 164rpx;
height: 164rpx;
border-radius: 10rpx;
margin-right: 16rpx;
flex-shrink: 0;
}
.shop-name {
font-size: 30rpx;
font-weight: bold;
color: #333333;
}
.dish-item {
display: flex;
align-items: center;
padding: 12rpx 0;
}
.dish-photo {
width: 100rpx;
height: 100rpx;
border-radius: 12rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.dish-info {
flex: 1;
}
.dish-name {
font-size: 28rpx;
color: #333333;
display: block;
margin-bottom: 8rpx;
}
.dish-meta {
display: flex;
justify-content: space-between;
}
.dish-price {
font-size: 26rpx;
color: #e64340;
}
.summary {
border-top: none;
}
/* 底部接单栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.accept-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #FAD146;
color: #333333;
font-size: 32rpx;
font-weight: bold;
border-radius: 44rpx;
border: none;
}
.accept-btn::after {
border: none;
}
/* 弹窗样式(复用) */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.modal-content {
width: 600rpx;
background-color: #ffffff;
border-radius: 20rpx;
padding: 40rpx;
}
.modal-title {
font-size: 34rpx;
font-weight: bold;
color: #333333;
text-align: center;
display: block;
margin-bottom: 20rpx;
}
.modal-desc {
font-size: 28rpx;
color: #666666;
text-align: center;
display: block;
margin-bottom: 30rpx;
}
.modal-actions {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
}
.modal-btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
border-radius: 40rpx;
border: none;
}
.modal-btn.cancel {
background-color: #f5f5f5;
color: #666666;
border: none;
}
.modal-btn.cancel::after {
border: none;
}
.modal-btn.confirm::after {
border: none;
}
.modal-btn.confirm {
background-color: #FAD146;
color: #333333;
}
.cert-form .form-item {
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.cert-form .form-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 8rpx;
display: block;
}
.cert-form .form-input {
font-size: 28rpx;
color: #333333;
height: 64rpx;
}
</style>