All checks were successful
continuous-integration/drone/push Build is passing
489 lines
10 KiB
Vue
489 lines
10 KiB
Vue
<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>
|