campus-errand/miniapp/pages/order/complete-order.vue
18631081161 b359070a0e
All checks were successful
continuous-integration/drone/push Build is passing
聊天修改
2026-04-02 01:09:02 +08:00

489 lines
10 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="complete-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 class="info-section">
<view class="info-title">
<text>订单信息</text>
</view>
<view class="info-row">
<text class="info-label">订单ID</text>
<text class="info-value">{{ orderInfo.orderNo || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">下单时间</text>
<text class="info-value">{{ formatTime(orderInfo.createdAt) }}</text>
</view>
<view class="info-row">
<text class="info-label">接单时间</text>
<text class="info-value">{{ formatTime(orderInfo.acceptedAt) }}</text>
</view>
</view>
<!-- 上传完成凭证 -->
<view class="upload-section">
<view class="upload-title">
<text>完成凭证选填</text>
</view>
<view class="upload-area" @click="chooseImage">
<image
v-if="proofImage"
class="proof-image"
:src="proofImage"
mode="aspectFill"
></image>
<view v-else class="upload-placeholder">
<text class="upload-icon">📷</text>
<text class="upload-text">点击上传凭证图片</text>
</view>
</view>
<view v-if="proofImage" class="remove-btn" @click="removeImage">
<text>移除图片</text>
</view>
</view>
<!-- 单主审核区域 -->
<view v-if="isOwnerConfirm" class="confirm-section">
<view class="confirm-title">
<text>订单完成确认</text>
</view>
<view class="confirm-hint">
<text>跑腿已提交订单完成请在24小时内处理超时将默认视为完成</text>
</view>
<view v-if="orderInfo.completionProof" class="confirm-proof">
<text class="confirm-proof-label">完成凭证:</text>
<image
class="confirm-proof-image"
:src="orderInfo.completionProof"
mode="aspectFill"
@click="previewImage(orderInfo.completionProof)"
></image>
</view>
<view class="confirm-actions">
<view class="confirm-btn confirm-ok" @click="confirmComplete">
<text>确认订单完成</text>
</view>
<view class="confirm-btn confirm-no" @click="rejectComplete">
<text>订单未完成</text>
</view>
</view>
</view>
<!-- 跑腿提交完成按钮 -->
<view v-if="!isOwnerConfirm" class="submit-section">
<view class="submit-btn" :class="{ disabled: submitting }" @click="submitComplete">
<text>{{ submitting ? '提交中...' : '确认完成' }}</text>
</view>
</view>
</view>
</template>
<script>
import { getOrderDetail, completeOrder, confirmOrder, rejectOrder } from '../../utils/api'
import { uploadFile } from '../../utils/request'
import { useUserStore } from '../../stores/user'
import { initIM, sendCustomMessage } from '../../utils/im'
export default {
data() {
return {
orderId: null,
orderInfo: {},
proofImage: '',
proofImageUrl: '',
submitting: false,
// 是否为单主确认模式
isOwnerConfirm: false,
statusBarHeight: 0
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.orderId = options.id
// mode=confirm 表示单主审核模式
this.isOwnerConfirm = options.mode === 'confirm'
this.loadOrderInfo()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载订单信息 */
async loadOrderInfo() {
if (!this.orderId) return
try {
const res = await getOrderDetail(this.orderId)
this.orderInfo = res || {}
// 如果订单状态为待确认且当前用户是单主,自动切换为确认模式
const userStore = useUserStore()
if (res.status === 'WaitConfirm' && res.ownerId === userStore.userId) {
this.isOwnerConfirm = true
}
} catch (e) {
uni.showToast({ title: '加载订单信息失败', icon: 'none' })
}
},
/** 选择凭证图片 */
chooseImage() {
uni.chooseImage({
count: 1,
sourceType: ['album', 'camera'],
success: (res) => {
this.proofImage = res.tempFilePaths[0]
}
})
},
/** 移除已选图片 */
removeImage() {
this.proofImage = ''
this.proofImageUrl = ''
},
/** 跑腿提交完成 */
async submitComplete() {
if (this.submitting) return
this.submitting = true
try {
let completionProof = null
// 如果有凭证图片,先上传
if (this.proofImage) {
const uploadRes = await uploadFile(this.proofImage)
completionProof = uploadRes.url
}
await completeOrder(this.orderId, { completionProof })
// 通过 IM 通知单主
try {
await initIM()
const groupId = this.orderInfo.imGroupId || `order_${this.orderId}`
await sendCustomMessage(groupId, {
bizType: 'order-status',
action: 'InProgress→WaitConfirm',
description: '跑腿已提交完成,请在订单详情中确认'
})
} catch (ex) {}
uni.showToast({ title: '已提交完成', icon: 'success' })
// 返回聊天页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.showToast({ title: e.message || '提交失败', icon: 'none' })
} finally {
this.submitting = false
}
},
/** 单主确认订单完成 */
async confirmComplete() {
uni.showModal({
title: '确认完成',
content: '确认该订单已完成?',
success: async (res) => {
if (!res.confirm) return
try {
await confirmOrder(this.orderId)
uni.showToast({ title: '订单已完成', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.showToast({ title: e.message || '操作失败', icon: 'none' })
}
}
})
},
/** 单主拒绝订单完成 */
async rejectComplete() {
uni.showModal({
title: '拒绝完成',
content: '确认该订单未完成?订单将继续进行。',
success: async (res) => {
if (!res.confirm) return
try {
await rejectOrder(this.orderId)
uni.showToast({ title: '已拒绝,订单继续进行', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.showToast({ title: e.message || '操作失败', icon: 'none' })
}
}
})
},
/** 格式化时间(精确至年月日时分) */
formatTime(dateStr) {
if (!dateStr) return '-'
const d = new Date(dateStr)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const h = String(d.getHours()).padStart(2, '0')
const min = String(d.getMinutes()).padStart(2, '0')
return `${y}-${m}-${day} ${h}:${min}`
},
/** 预览图片 */
previewImage(url) {
if (!url) return
uni.previewImage({ urls: [url] })
}
}
}
</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;
}
.complete-page {
padding: 30rpx;
min-height: 100vh;
background-color: #f5f5f5;
}
/* 订单信息区域 */
.info-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
}
.info-title {
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.info-title text {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
}
.info-label {
font-size: 26rpx;
color: #999999;
}
.info-value {
font-size: 26rpx;
color: #333333;
}
/* 上传凭证区域 */
.upload-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
}
.upload-title {
margin-bottom: 20rpx;
}
.upload-title text {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
.upload-area {
width: 100%;
height: 360rpx;
border: 2rpx dashed #dddddd;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.proof-image {
width: 100%;
height: 100%;
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
}
.upload-icon {
font-size: 64rpx;
margin-bottom: 16rpx;
}
.upload-text {
font-size: 26rpx;
color: #999999;
}
.remove-btn {
text-align: center;
padding: 16rpx 0;
margin-top: 12rpx;
}
.remove-btn text {
font-size: 26rpx;
color: #ff4d4f;
}
/* 单主审核区域 */
.confirm-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
}
.confirm-title {
margin-bottom: 16rpx;
}
.confirm-title text {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
.confirm-hint {
background-color: #fff7e6;
padding: 20rpx;
border-radius: 8rpx;
margin-bottom: 20rpx;
}
.confirm-hint text {
font-size: 24rpx;
color: #ff9900;
}
.confirm-proof {
margin-bottom: 20rpx;
}
.confirm-proof-label {
font-size: 26rpx;
color: #666666;
display: block;
margin-bottom: 12rpx;
}
.confirm-proof-image {
width: 300rpx;
height: 300rpx;
border-radius: 8rpx;
}
.confirm-actions {
display: flex;
gap: 20rpx;
}
.confirm-btn {
flex: 1;
padding: 24rpx 0;
border-radius: 12rpx;
text-align: center;
font-size: 28rpx;
}
.confirm-ok {
background-color: #FAD146;
color: #ffffff;
}
.confirm-no {
background-color: #f5f5f5;
color: #666666;
}
/* 跑腿提交按钮 */
.submit-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 24rpx 30rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background-color: #ffffff;
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.submit-btn {
background-color: #FAD146;
color: #ffffff;
text-align: center;
padding: 24rpx 0;
border-radius: 12rpx;
font-size: 30rpx;
}
.submit-btn.disabled {
opacity: 0.6;
}
</style>