feat: 新增确认送达弹框组件、添加安卓打包测试证书、骑手配送流程接口对接
This commit is contained in:
298
pages/index/components/delivery-popup.vue
Normal file
298
pages/index/components/delivery-popup.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<u-popup :show="show" mode="bottom" :closeable="true" @close="onClose" :safeAreaInsetBottom="false" border-radius="16">
|
||||
<view class="delivery-popup">
|
||||
<!-- 标题 -->
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">确认送达</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="popup-content">
|
||||
<view class="upload-label">
|
||||
<text class="required">*</text>
|
||||
<text>上传送达照片:</text>
|
||||
</view>
|
||||
|
||||
<!-- 照片列表 -->
|
||||
<up-upload
|
||||
:fileList="photoList"
|
||||
@afterRead="afterRead"
|
||||
@delete="deleteUpload"
|
||||
:maxCount="1"
|
||||
:maxSize="5 * 1024 * 1024"
|
||||
uploadText="上传照片"
|
||||
:previewFullImage="true"
|
||||
></up-upload>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="popup-footer">
|
||||
<view class="btn submit" @click="onSubmit">提交</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { baseUrl, apiPath } from '@/sheep/config';
|
||||
|
||||
const props = defineProps({
|
||||
// 控制显示隐藏
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:show', 'submit', 'close']);
|
||||
|
||||
// 照片列表 (用于 up-upload 组件)
|
||||
const photoList = ref([])
|
||||
|
||||
// 监听弹框显示,重置数据
|
||||
watch(() => props.show, (newVal) => {
|
||||
if (newVal) {
|
||||
photoList.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭弹框
|
||||
function onClose() {
|
||||
emit('update:show', false);
|
||||
emit('close');
|
||||
}
|
||||
|
||||
// 删除上传的图片
|
||||
function deleteUpload(event) {
|
||||
photoList.value.splice(event.index, 1);
|
||||
}
|
||||
|
||||
// 上传单个文件到 OSS
|
||||
async function uploadFile(filePath) {
|
||||
const token = uni.getStorageSync('token');
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: baseUrl + apiPath + '/app/file/uploadOss', // 上传接口地址
|
||||
header: {
|
||||
'Authorization': token
|
||||
},
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.code === 0 && data.data) {
|
||||
resolve(data.data);
|
||||
} else {
|
||||
console.error('上传失败:', data.msg);
|
||||
resolve(null);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析上传响应失败:', e);
|
||||
resolve(null);
|
||||
}
|
||||
} else {
|
||||
console.error('上传失败:', res.statusCode);
|
||||
resolve(null);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('上传请求失败:', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 上传图片后的回调
|
||||
async function afterRead(event) {
|
||||
// 当设置 mutiple 为 true 时, event.file.list 为数组
|
||||
const { file } = event;
|
||||
|
||||
// 如果是单选,转换为数组处理
|
||||
const fileList = Array.isArray(file) ? file : [file];
|
||||
|
||||
for (const item of fileList) {
|
||||
// 标记为上传中
|
||||
photoList.value.push({
|
||||
...item,
|
||||
status: 'uploading',
|
||||
message: '上传中'
|
||||
});
|
||||
}
|
||||
|
||||
// 依次上传文件
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const item = fileList[i];
|
||||
const index = photoList.value.findIndex(f => f.url === item.url);
|
||||
|
||||
try {
|
||||
const url = await uploadFile(item.url);
|
||||
|
||||
if (url) {
|
||||
// 上传成功,更新状态
|
||||
photoList.value[index] = {
|
||||
...photoList.value[index],
|
||||
status: 'success',
|
||||
message: ''
|
||||
};
|
||||
} else {
|
||||
// 上传失败
|
||||
photoList.value[index] = {
|
||||
...photoList.value[index],
|
||||
status: 'failed',
|
||||
message: '上传失败'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传图片异常:', error);
|
||||
photoList.value[index] = {
|
||||
...photoList.value[index],
|
||||
status: 'failed',
|
||||
message: '上传失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交
|
||||
async function onSubmit() {
|
||||
if (photoList.value.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请上传送达照片',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找上传成功的照片
|
||||
const uploadedPhoto = photoList.value.find(p => p.status === 'success');
|
||||
|
||||
if (!uploadedPhoto) {
|
||||
uni.showToast({
|
||||
title: '照片上传中,请稍候',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 提交成功,返回上传后的 URL
|
||||
emit('submit', uploadedPhoto.url);
|
||||
onClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.delivery-popup {
|
||||
padding: 30rpx;
|
||||
padding-bottom: calc(30rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
text-align: center;
|
||||
padding-bottom: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.upload-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #f5222d;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.photo-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.photo-item {
|
||||
position: relative;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.photo-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.photo-delete {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.photo-add {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx dashed #ddd;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 72rpx;
|
||||
color: #999;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn.submit {
|
||||
background: #1e9fff;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@
|
||||
<!-- 查询输入 -->
|
||||
<view class="search-row">
|
||||
<view style="width:620rpx;">
|
||||
<input class="search-input" v-model="orderCode" placeholder="输入收件人手机尾号4位+取单号,如8927#11" />
|
||||
<input class="search-input" v-model="orderCode" placeholder="输入收货人手机尾号4位+取单号,如8927#11" />
|
||||
</view>
|
||||
<view style="margin-left:15rpx;">
|
||||
<up-button type="primary" size="small" text="查询" @click="queryOrder"></up-button>
|
||||
@@ -23,7 +23,7 @@
|
||||
<text class="icon">🏬</text>
|
||||
<text class="text">取货点店铺名称</text>
|
||||
</view>
|
||||
<text class="muted">{{ pickupName }}</text>
|
||||
<text class="muted">取货店铺详细地址:{{ pickupName }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 送餐地址 -->
|
||||
@@ -45,20 +45,22 @@
|
||||
<view class="form-row">
|
||||
<text class="label">交接备注:</text>
|
||||
<picker :range="remarks" @change="onRemarkChange">
|
||||
<view class="picker-display">{{ selectedRemarkText }}</view>
|
||||
<view class="picker-display">{{ selectedRemarkText || '请选择交接备注' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 凭证 上传 -->
|
||||
<view class="form-row proof-row">
|
||||
<text class="label required">凭证:</text>
|
||||
<view class="proof-list">
|
||||
<view v-for="(img, idx) in proofImages" :key="idx" class="proof-item">
|
||||
<image :src="img" mode="aspectFill" class="proof-img" />
|
||||
<view class="remove" @click="removeProofImage(idx)">✕</view>
|
||||
</view>
|
||||
<view class="proof-add" @click="addProofImage">+</view>
|
||||
</view>
|
||||
<up-upload
|
||||
:fileList="uploadFileList"
|
||||
@afterRead="afterRead"
|
||||
@delete="deleteUpload"
|
||||
:maxCount="1"
|
||||
:maxSize="5 * 1024 * 1024"
|
||||
uploadText="上传凭证"
|
||||
:previewFullImage="true"
|
||||
></up-upload>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
@@ -74,6 +76,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DeliveryOrderApi from '@/sheep/api/member/deliveryOrder';
|
||||
import sheep from '@/sheep';
|
||||
import { baseUrl, apiPath } from '@/sheep/config';
|
||||
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
@@ -90,26 +96,29 @@
|
||||
const emit = defineEmits(['close', 'confirm'])
|
||||
|
||||
// 响应式数据
|
||||
const orderIndex = ref(1)
|
||||
const orderIndex = ref('')
|
||||
const orderCode = ref('')
|
||||
const pickupName = ref('取货店铺详细地址广东省广州市天河区学院站荷光路')
|
||||
const address = ref('送餐详细地址广东省广州市天河区华景新城软件园区A栋303室')
|
||||
const recipientTail = ref('1254')
|
||||
const pickupName = ref('')
|
||||
const address = ref('')
|
||||
const recipientTail = ref('')
|
||||
const customerNote = ref('依据餐量提供餐具')
|
||||
|
||||
const remarks = ['请选择 备注内容', '已核对身份证', '缺少配件', '地址异常']
|
||||
const selectedRemark = ref(0)
|
||||
const remarks = ['餐品破损', '餐品撒翻', '其他']
|
||||
const selectedRemark = ref('')
|
||||
const selectedRemarkText = computed(() => remarks[selectedRemark.value] || '')
|
||||
|
||||
const proofImages = ref([])
|
||||
// 上传文件列表 (用于 up-upload 组件)
|
||||
const uploadFileList = ref([])
|
||||
|
||||
// 已上传的凭证URL列表
|
||||
const proofUrls = ref([])
|
||||
|
||||
// 方法
|
||||
const onClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const queryOrder = () => {
|
||||
// 简单模拟查询:如果输入有内容则把 orderIndex 加 1 并回填部分数据
|
||||
const queryOrder = async () => {
|
||||
if (!orderCode.value) {
|
||||
uni.showToast({
|
||||
title: '请输入查询条件',
|
||||
@@ -117,12 +126,56 @@
|
||||
})
|
||||
return
|
||||
}
|
||||
orderIndex.value += 1
|
||||
// 这里可以调用接口查询并填充 pickupName/address 等
|
||||
uni.showToast({
|
||||
title: '查询成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
try {
|
||||
// 解析输入:支持输入订单ID或手机尾号#取单号格式
|
||||
let orderId = orderCode.value;
|
||||
|
||||
// 如果包含 # 符号,尝试提取订单ID(这里假设后缀是订单ID)
|
||||
if (orderCode.value.includes('#')) {
|
||||
const parts = orderCode.value.split('#');
|
||||
orderId = parts[parts.length - 1];
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '查询中...' });
|
||||
|
||||
const res = await DeliveryOrderApi.getDetail(orderId);
|
||||
const data = res.data || res;
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (data && data.id) {
|
||||
// 更新订单信息
|
||||
orderIndex.value = data.id;
|
||||
pickupName.value = data.shopName || '';
|
||||
address.value = data.receiverAddress || '';
|
||||
customerNote.value = data.orderRemark || '';
|
||||
|
||||
// 处理收货人信息
|
||||
if (data.receiverPhone) {
|
||||
recipientTail.value = (data.receiverPhone + '').slice(-4);
|
||||
} else {
|
||||
recipientTail.value = '';
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '查询成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '未找到订单',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
console.error('queryOrder error', e);
|
||||
uni.showToast({
|
||||
title: '查询失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 拨打电话
|
||||
@@ -140,37 +193,161 @@
|
||||
selectedRemark.value = e.detail.value
|
||||
}
|
||||
|
||||
const addProofImage = () => {
|
||||
// 使用 uni.chooseImage 上传
|
||||
uni.chooseImage({
|
||||
count: 4,
|
||||
success(res) {
|
||||
const tempFiles = res.tempFilePaths || res.tempFiles.map(f => f.path)
|
||||
proofImages.value = proofImages.value.concat(tempFiles)
|
||||
// 删除上传的图片
|
||||
const deleteUpload = (event) => {
|
||||
uploadFileList.value.splice(event.index, 1)
|
||||
proofUrls.value.splice(event.index, 1)
|
||||
}
|
||||
|
||||
// 上传单个文件到 OSS
|
||||
async function uploadFile(filePath) {
|
||||
const token = uni.getStorageSync('token');
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: baseUrl + apiPath + '/app/file/uploadOss', // 上传接口地址
|
||||
header: {
|
||||
'Authorization': token
|
||||
},
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.code === 0 && data.data) {
|
||||
resolve(data.data);
|
||||
} else {
|
||||
console.error('上传失败:', data.msg);
|
||||
resolve(null);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析上传响应失败:', e);
|
||||
resolve(null);
|
||||
}
|
||||
} else {
|
||||
console.error('上传失败:', res.statusCode);
|
||||
resolve(null);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('上传请求失败:', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 上传图片后的回调
|
||||
async function afterRead(event) {
|
||||
// 当设置 mutiple 为 true 时, event.file.list 为数组
|
||||
const { file } = event;
|
||||
|
||||
// 如果是单选,转换为数组处理
|
||||
const fileList = Array.isArray(file) ? file : [file];
|
||||
|
||||
for (const item of fileList) {
|
||||
// 标记为上传中
|
||||
uploadFileList.value.push({
|
||||
...item,
|
||||
status: 'uploading',
|
||||
message: '上传中'
|
||||
});
|
||||
}
|
||||
|
||||
// 依次上传文件
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const item = fileList[i];
|
||||
const index = uploadFileList.value.findIndex(f => f.url === item.url);
|
||||
|
||||
try {
|
||||
const url = await uploadFile(item.url);
|
||||
|
||||
if (url) {
|
||||
// 上传成功,更新状态
|
||||
uploadFileList.value[index] = {
|
||||
...uploadFileList.value[index],
|
||||
status: 'success',
|
||||
message: ''
|
||||
};
|
||||
proofUrls.value.push(url);
|
||||
} else {
|
||||
// 上传失败
|
||||
uploadFileList.value[index] = {
|
||||
...uploadFileList.value[index],
|
||||
status: 'failed',
|
||||
message: '上传失败'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传图片异常:', error);
|
||||
uploadFileList.value[index] = {
|
||||
...uploadFileList.value[index],
|
||||
status: 'failed',
|
||||
message: '上传失败'
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const removeProofImage = (idx) => {
|
||||
proofImages.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
const onConfirm = async () => {
|
||||
// 校验配送单信息
|
||||
if (!orderIndex.value) {
|
||||
uni.showToast({
|
||||
title: '没有配送单信息',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 校验凭证
|
||||
if (proofImages.value.length === 0) {
|
||||
if (proofUrls.value.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请上传凭证',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
orderIndex: orderIndex.value,
|
||||
orderCode: orderCode.value,
|
||||
remark: selectedRemarkText.value,
|
||||
proofImages: proofImages.value
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '提交中...' });
|
||||
|
||||
// 调用确认交接接口
|
||||
const res = await DeliveryOrderApi.riderConfirmHandover({
|
||||
deliveryOrderId: Number(orderIndex.value),
|
||||
// 注意:这里假设 remarks 的 index 就是 handoverRemarkId,如果后端需要实际ID可能需要调整
|
||||
handoverRemarkId: selectedRemark.value === 0 ? undefined : selectedRemark.value,
|
||||
imageUrlList: proofUrls.value
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (res.code === 0 && res.data === true) {
|
||||
uni.showToast({
|
||||
title: '交接成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 关闭弹框并通知父组件
|
||||
emit('confirm', {
|
||||
orderIndex: orderIndex.value,
|
||||
orderCode: orderCode.value,
|
||||
remark: selectedRemarkText.value,
|
||||
proofImages: proofUrls.value
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '交接失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('确认交接异常:', error);
|
||||
uni.showToast({
|
||||
title: '交接失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
emit('confirm', payload)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -212,6 +389,7 @@
|
||||
border: 1rpx solid #ddd;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
|
||||
Reference in New Issue
Block a user