Przeglądaj źródła

feat(售后): 新增填写退货物流页面及状态展示优化

- 新增退货物流填写页面,支持扫码录入单号、选择物流公司、上传凭证
- 在退款详情页增加协商金额确认UI和退货方式选择模块
- 调整图片上传组件API路径,移除多余的前缀
- 扩展退款状态映射,支持协商中、待寄回等新状态
- 新增填写物流API接口,完善售后流程闭环
ylong 4 dni temu
rodzic
commit
1567f31604

+ 3 - 0
api/modules/mall.js

@@ -83,6 +83,9 @@ export const useMallApi = (Vue, vm) => {
         // 撤销退款申请
         refundCancelAjax: (data) => vm.$u.post('/token/shop/order/refundCancel', data),
 
+        // 填写退货物流
+        refundFillExpressAjax: (data) => vm.$u.post('/token/shop/order/refundFillExpress', data),
+
         // 减钱分享信息
         clickReduceInviteAjax: (params) => vm.$u.get('/token/shop/order/clickReduceInvite', params),
         

+ 1 - 1
components/image-upload.vue

@@ -105,7 +105,7 @@
 							reject({ msg: '缺少上传场景码' });
 							return;
 						}
-						url = `/api/token/mini/fileUpload/${this.code}`;
+						url = `/token/mini/fileUpload/${this.code}`;
 					}
 
 					if (!url.startsWith('http')) {

+ 223 - 0
pages-car/pages/fill-logistics.vue

@@ -0,0 +1,223 @@
+<template>
+	<view class="fill-logistics-page">
+		<!-- 表单区域 -->
+		<view class="form-card">
+			<u-form :model="form" ref="uForm" label-width="160">
+				<u-form-item label="物流单号" prop="expressNumber">
+					<u-input v-model="form.expressNumber" placeholder="请填写" />
+					<u-icon slot="right" name="scan" size="40" color="#999" @click="scanCode"></u-icon>
+				</u-form-item>
+
+				<u-form-item label="物流公司" prop="expressName" right-icon="arrow-right">
+					<u-input v-model="form.expressName" type="select" placeholder="请选择"
+						@click="showExpressSelect = true" />
+				</u-form-item>
+
+				<u-form-item label="联系电话" prop="mobile" :border-bottom="false">
+					<u-input v-model="form.mobile" type="number" placeholder="请填写" />
+				</u-form-item>
+			</u-form>
+		</view>
+
+		<!-- 补充描述和凭证 -->
+		<view class="upload-card">
+			<view class="card-title">补充描述和凭证</view>
+			<view class="upload-box">
+				<u-input v-model="desc" type="textarea" placeholder="有助于平台更好的处理售后问题" :maxlength="200" height="150"
+					:clearable="false" class="textarea" />
+				<view class="word-count">{{ desc.length }}/200</view>
+
+				<view class="image-upload-wrapper">
+					<CommonImageUpload v-model="form.urlList" :max-count="5" code="shopRefund" />
+				</view>
+			</view>
+		</view>
+
+		<!-- 底部提交按钮 -->
+		<view class="bottom-bar">
+			<u-button type="success" shape="circle" :custom-style="submitBtnStyle" @click="submit">提交</u-button>
+		</view>
+
+		<!-- 物流公司选择器 -->
+		<u-select v-model="showExpressSelect" :list="expressList" value-name="dictValue" label-name="dictLabel"
+			@confirm="onExpressConfirm"></u-select>
+	</view>
+</template>
+
+<script>
+	import CommonImageUpload from '@/components/image-upload.vue';
+
+	export default {
+		components: {
+			CommonImageUpload
+		},
+		data() {
+			return {
+				refundOrderId: '',
+				desc: '', // 备注描述暂时不传接口,UI里有
+				form: {
+					refundOrderId: '',
+					expressType: 3, // 3-自行寄回
+					expressNumber: '',
+					expressName: '',
+					mobile: '',
+					urlList: []
+				},
+				showExpressSelect: false,
+				expressList: [],
+				submitBtnStyle: {
+					backgroundColor: '#38C148',
+					color: '#fff',
+					height: '80rpx',
+					fontSize: '32rpx',
+					width: '100%'
+				},
+				rules: {
+					expressNumber: [{
+						required: true,
+						message: '请输入物流单号',
+						trigger: ['blur', 'change']
+					}],
+					expressName: [{
+						required: true,
+						message: '请选择物流公司',
+						trigger: ['blur', 'change']
+					}],
+					mobile: [{
+						required: true,
+						message: '请输入联系电话',
+						trigger: ['blur', 'change']
+					}, {
+						validator: (rule, value, callback) => {
+							return this.$u.test.mobile(value);
+						},
+						message: '手机号码不正确',
+						trigger: ['blur', 'change']
+					}]
+				}
+			};
+		},
+		onLoad(options) {
+			if (options.refundOrderId) {
+				this.refundOrderId = options.refundOrderId;
+				this.form.refundOrderId = options.refundOrderId;
+			}
+			this.getExpressOptions();
+		},
+		onReady() {
+			this.$refs.uForm.setRules(this.rules);
+		},
+		methods: {
+			getExpressOptions() {
+				uni.$u.http.get('/token/common/getDictOptions?type=shop_order_refund_express_name').then(res => {
+					if (res.code === 200) {
+						this.expressList = res.data || [];
+					}
+				});
+			},
+			onExpressConfirm(e) {
+				this.form.expressName = e[0].label;
+			},
+			scanCode() {
+				uni.scanCode({
+					success: (res) => {
+						this.form.expressNumber = res.result;
+					}
+				});
+			},
+			submit() {
+				this.$refs.uForm.validate(valid => {
+					if (valid) {
+						uni.showLoading({ title: '提交中' });
+
+						// 如果需要将 desc 拼接到某个字段,可以这里处理
+						// 接口文档没写 desc 字段,暂时忽略或拼到别处。
+
+						this.$u.api.refundFillExpressAjax(this.form).then(res => {
+							uni.hideLoading();
+							if (res.code == 200) {
+								uni.showToast({
+									title: '提交成功',
+									icon: 'success'
+								});
+								setTimeout(() => {
+									uni.navigateBack({ delta: 1 });
+								}, 1500);
+							} else {
+								uni.showToast({
+									title: res.msg || '提交失败',
+									icon: 'none'
+								});
+							}
+						}).catch(() => {
+							uni.hideLoading();
+						});
+					}
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.fill-logistics-page {
+		min-height: 100vh;
+		background-color: #F5F5F5;
+		padding: 20rpx;
+		padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
+
+		.form-card {
+			background-color: #fff;
+			border-radius: 16rpx;
+			padding: 0 30rpx;
+			margin-bottom: 20rpx;
+		}
+
+		.upload-card {
+			background-color: #fff;
+			border-radius: 16rpx;
+			padding: 30rpx;
+
+			.card-title {
+				font-size: 30rpx;
+				font-weight: bold;
+				color: #333;
+				margin-bottom: 20rpx;
+			}
+
+			.upload-box {
+				background-color: #F8F8F8;
+				border-radius: 12rpx;
+				padding: 20rpx;
+				position: relative;
+
+				.textarea {
+					font-size: 28rpx;
+				}
+
+				.word-count {
+					text-align: right;
+					font-size: 24rpx;
+					color: #999;
+					margin-bottom: 20rpx;
+				}
+
+				.image-upload-wrapper {
+					margin-top: 20rpx;
+				}
+			}
+		}
+
+		.bottom-bar {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			right: 0;
+			background-color: #fff;
+			padding: 20rpx 40rpx;
+			padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+			box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+			z-index: 100;
+		}
+	}
+</style>

+ 267 - 6
pages-car/pages/refund-detail.vue

@@ -13,8 +13,22 @@
 			</template>
 			<template v-else>
 				<view class="status-title">{{ getStatusText(orderInfo.status) }}</view>
+				<!-- 协商UI -->
+				<view class="negotiation-card" v-if="orderInfo.status == '2'">
+					<view class="negotiation-content">
+						<u-icon name="file-text-fill" color="#FF6600" size="40" class="negotiation-icon"></u-icon>
+						<view class="negotiation-text">
+							<view class="negotiation-title">协商修改退款金额为{{ orderInfo.refundMoney }}元</view>
+							<view class="negotiation-desc">我们愿意支持退款,若您接受修改金额,我们将立刻退款给您</view>
+						</view>
+					</view>
+					<view class="negotiation-actions">
+						<u-button size="mini" :custom-style="rejectBtnStyle" @click="handleNegotiation(false)">拒绝协商</u-button>
+						<u-button size="mini" :custom-style="acceptBtnStyle" @click="handleNegotiation(true)">接受协商</u-button>
+					</view>
+				</view>
 				<!-- 退款成功金额展示 -->
-				<view class="refund-amount-box" v-if="orderInfo.status == '6' || orderInfo.status == '2'">
+				<view class="refund-amount-box" v-if="orderInfo.status == '8'">
 					<view class="amount-row">
 						<text class="label">退回余额</text>
 						<text class="value">¥{{ orderInfo.refundMoney }}</text>
@@ -23,6 +37,42 @@
 			</template>
 		</view>
 
+		<!-- 退货方式 (状态为4时显示) -->
+		<view class="info-card return-method-card" v-if="orderInfo.status == '4'">
+			<view class="card-title">请选择退货方式</view>
+			<!-- 这里可以加倒计时,类似:1天11时57分后未寄出平台将自动撤销退货申请,需根据接口返回字段 -->
+			
+			<view class="method-tabs">
+				<view class="tab-item disabled">
+					<view class="tab-name">上门取件</view>
+					<view class="tab-desc">(快递员上门取包裹)</view>
+				</view>
+				<view class="tab-item disabled">
+					<view class="tab-name">驿站自寄</view>
+					<view class="tab-desc">(随到随退)</view>
+				</view>
+				<view class="tab-item active">
+					<view class="tab-name">自行寄回</view>
+					<view class="tab-desc">(需填写单号)</view>
+				</view>
+			</view>
+			
+			<view class="warning-box">
+				需您自行联系快递公司退回,请不要邮寄到付
+			</view>
+			
+			<view class="address-box">
+				<view class="address-label">平台地址</view>
+				<view class="address-content">
+					<view class="name">{{ orderInfo.platformReceiveName || '张三' }}</view>
+					<view class="detail">{{ orderInfo.platformReceiveAddress || '河南省鹤壁市浚县即可获得发货单收到单方事故师范号到' }} <image src="/pages-mine/static/copy.png" class="copy-icon" @click="copyAddress"></image></view>
+				</view>
+				<u-icon name="arrow-right" color="#ccc" size="28"></u-icon>
+			</view>
+			
+			<u-button type="success" shape="circle" :custom-style="fillBtnStyle" @click="goToFillLogistics">填写单号</u-button>
+		</view>
+
 		<!-- 商品列表 -->
 		<view class="info-card goods-card">
 			<view class="goods-item" v-for="(goods, index) in orderInfo.detailList" :key="index">
@@ -165,6 +215,34 @@
 					backgroundColor: '#38C148',
 					color: '#fff',
 					border: 'none'
+				},
+				rejectBtnStyle: {
+					color: '#333',
+					backgroundColor: '#fff',
+					border: '1rpx solid #ccc',
+					minWidth: '160rpx',
+					height: '60rpx',
+					lineHeight: '60rpx',
+					fontSize: '26rpx',
+					margin: '0 20rpx 0 0'
+				},
+				acceptBtnStyle: {
+					color: '#fff',
+					backgroundColor: '#FF6600',
+					border: 'none',
+					minWidth: '160rpx',
+					height: '60rpx',
+					lineHeight: '60rpx',
+					fontSize: '26rpx',
+					margin: '0'
+				},
+				fillBtnStyle: {
+					backgroundColor: '#38C148',
+					color: '#fff',
+					height: '80rpx',
+					fontSize: '30rpx',
+					marginTop: '30rpx',
+					width: '100%'
 				}
 			};
 		},
@@ -198,16 +276,33 @@
 			},
 			getStatusText(status) {
 				// 状态 1-申请退款 2-审核通过 3-审核驳回 4-超时关闭 5-卖家已发货 6-已完成
+				// 状态 1-申请退款 2-协商中待用户确认 3-协商中待商家确认 4-审核通过 5-审核驳回 6-超时关闭 7-卖家已发货 8-已完成
 				const map = {
 					'1': '待卖家处理',
-					'2': '审核通过',
-					'3': '审核驳回',
-					'4': '退款关闭', // 超时关闭
-					'5': '卖家已发货', // 可能是换货场景
-					'6': '退款成功'
+					'2': '协商中待用户确认',
+					'3': '协商中待商家确认',
+					'4': '审核已通过,请您把书籍自行寄回',
+					'5': '审核已驳回',
+					'6': '退款关闭', // 超时关闭
+					'7': '卖家已发货', // 可能是换货场景
+					'8': '退款成功'
 				};
 				return map[status] || '处理中';
 			},
+			copyAddress() {
+				const address = (this.orderInfo.platformReceiveName || '张三') + ' ' + (this.orderInfo.platformReceiveAddress || '河南省鹤壁市浚县即可获得发货单收到单方事故师范号到');
+				uni.setClipboardData({
+					data: address,
+					success: () => {
+						uni.showToast({ title: '地址复制成功', icon: 'none' });
+					}
+				});
+			},
+			goToFillLogistics() {
+				uni.navigateTo({
+					url: `/pages-car/pages/fill-logistics?refundOrderId=${this.refundOrderId}`
+				});
+			},
 			copyOrderNo() {
 				uni.setClipboardData({
 					data: String(this.orderInfo.refundOrderId || ''),
@@ -230,6 +325,32 @@
 			handlePositionChange(position) {
 				this.servicePosition = position;
 			},
+			handleNegotiation(accept) {
+				const title = accept ? '确认接受协商金额?' : '确认拒绝协商?';
+				uni.showModal({
+					title: '提示',
+					content: title,
+					success: (res) => {
+						if (res.confirm) {
+							// TODO: Call API
+							// const api = accept ? this.$u.api.refundNegotiateAgreeAjax : this.$u.api.refundNegotiateRefuseAjax;
+							// api(...)
+							
+							// 模拟成功
+							uni.showLoading({ title: '处理中' });
+							setTimeout(() => {
+								uni.hideLoading();
+								uni.showToast({
+									title: accept ? '已接受协商' : '已拒绝协商',
+									icon: 'success'
+								});
+								// 刷新详情
+								this.getDetail();
+							}, 1000);
+						}
+					}
+				});
+			},
 			handleAction(type) {
 				switch (type) {
 					case 'cancel':
@@ -320,6 +441,44 @@
 				color: #999;
 			}
 
+			.negotiation-card {
+				margin-top: 30rpx;
+				text-align: left;
+				
+				.negotiation-content {
+					display: flex;
+					align-items: flex-start;
+					margin-bottom: 30rpx;
+					
+					.negotiation-icon {
+						margin-right: 20rpx;
+						margin-top: 6rpx;
+					}
+					
+					.negotiation-text {
+						flex: 1;
+						
+						.negotiation-title {
+							font-size: 30rpx;
+							font-weight: bold;
+							color: #333;
+							margin-bottom: 10rpx;
+						}
+						
+						.negotiation-desc {
+							font-size: 26rpx;
+							color: #999;
+							line-height: 1.4;
+						}
+					}
+				}
+				
+				.negotiation-actions {
+					display: flex;
+					justify-content: flex-end;
+				}
+			}
+
 			.refund-amount-box {
 				margin-top: 30rpx;
 				padding-top: 30rpx;
@@ -347,6 +506,108 @@
 			margin: 20rpx;
 			border-radius: 16rpx;
 			padding: 30rpx;
+			
+			&.return-method-card {
+				.card-title {
+					font-size: 32rpx;
+					font-weight: bold;
+					color: #333;
+					text-align: center;
+					margin-bottom: 30rpx;
+				}
+				
+				.method-tabs {
+					display: flex;
+					justify-content: space-between;
+					margin-bottom: 30rpx;
+					
+					.tab-item {
+						flex: 1;
+						background-color: #F8F8F8;
+						border-radius: 12rpx;
+						padding: 20rpx 0;
+						text-align: center;
+						margin-right: 20rpx;
+						border: 2rpx solid transparent;
+						
+						&:last-child {
+							margin-right: 0;
+						}
+						
+						&.active {
+							background-color: #F0F9F1;
+							border-color: #38C148;
+							.tab-name { color: #38C148; }
+							.tab-desc { color: #38C148; opacity: 0.8; }
+						}
+						
+						&.disabled {
+							opacity: 0.5;
+						}
+						
+						.tab-name {
+							font-size: 30rpx;
+							font-weight: bold;
+							color: #333;
+							margin-bottom: 6rpx;
+						}
+						
+						.tab-desc {
+							font-size: 22rpx;
+							color: #999;
+						}
+					}
+				}
+				
+				.warning-box {
+					background-color: #E8F5E9;
+					color: #38C148;
+					font-size: 26rpx;
+					padding: 16rpx;
+					border-radius: 8rpx;
+					text-align: center;
+					margin-bottom: 30rpx;
+				}
+				
+				.address-box {
+					display: flex;
+					align-items: center;
+					padding: 20rpx 0;
+					border-top: 1rpx solid #F5F5F5;
+					
+					.address-label {
+						width: 140rpx;
+						font-size: 28rpx;
+						color: #333;
+						font-weight: bold;
+					}
+					
+					.address-content {
+						flex: 1;
+						padding-right: 20rpx;
+						
+						.name {
+							font-size: 30rpx;
+							font-weight: bold;
+							color: #333;
+							margin-bottom: 10rpx;
+						}
+						
+						.detail {
+							font-size: 26rpx;
+							color: #666;
+							line-height: 1.4;
+							
+							.copy-icon {
+								width: 28rpx;
+								height: 28rpx;
+								vertical-align: middle;
+								margin-left: 10rpx;
+							}
+						}
+					}
+				}
+			}
 
 			&.goods-card {
 				.goods-item {

+ 6 - 0
pages.json

@@ -374,6 +374,12 @@
                     "style": {
                         "navigationBarTitleText": "订单详情"
                     }
+                },
+                {
+                    "path": "pages/fill-logistics",
+                    "style": {
+                        "navigationBarTitleText": "填写退货物流"
+                    }
                 }
             ]
         },