Files
frontend-app/pages/mine_package/apply_refund/apply_refund.vue
T
2026-08-29 13:02:46 +08:00

891 lines
22 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="apply-refund-container">
<!-- 顶部固定导航栏 -->
<view class="navbar-sticky-wrapper" :style="mpWarpStyle">
<view class="custom-navbar" :style="mpHeaderStyle">
<view class="back_btn" @click="goBack">
<up-image src="/static/common/left.png" width="20" height="20" bgColor="#f1f6ff00"></up-image>
</view>
<view class="nav-title">申请退款</view>
<view class="nav-right"></view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="main-content">
<!-- 1. 商品信息卡片 -->
<view class="card product-card">
<view class="product-info-row">
<image :src="productImage" class="product-img" mode="aspectFill" />
<view class="product-details">
<view class="product-header">
<text class="product-title">{{ productTitle }}</text>
<text class="product-price">¥{{ productPrice }}</text>
</view>
<view class="product-spec-row">
<text class="product-spec">规格:{{ productSpec }}</text>
<text class="product-num">x {{ productNum }}</text>
</view>
</view>
</view>
<view class="product-total-row">
<text class="product-total-text">共{{ productNum }}件,合计¥{{ totalPrice }}</text>
</view>
</view>
<!-- 2. 退款选项卡片 (退款原因 & 退款金额) -->
<view class="card form-card">
<!-- 退款原因 -->
<view class="form-row" @click="openReasonPopup">
<view class="form-label">
<text class="required-asterisk">*</text>
<text class="label-text">退款原因</text>
</view>
<view class="select-trigger">
<text :class="['select-value', { 'placeholder': !selectedReason }]">
{{ selectedReason ? selectedReason.name : '请选择' }}
</text>
<view class="dashed-arrow-box">
<text class="arrow-icon">›</text>
</view>
</view>
</view>
<view class="row-divider"></view>
<!-- 退款金额 -->
<view class="form-row">
<view class="form-label">
<text class="label-text">退款金额</text>
</view>
<view class="amount-value">
¥{{ totalPrice }}
</view>
</view>
</view>
<!-- 3. 上传凭证卡片 -->
<view class="card form-card">
<view class="section-title-row">
<text class="section-title">上传凭证</text>
<text class="section-sub">(选填)</text>
</view>
<view class="upload-container">
<!-- 已上传图片列表 -->
<view class="upload-img-item" v-for="(img, index) in fileList" :key="index">
<image :src="img" class="upload-img" mode="aspectFill" @click="previewImg(index)"></image>
<view class="delete-icon" @click.stop="deleteImg(index)">×</view>
</view>
<!-- 上传按钮 -->
<view class="upload-btn-box" v-if="fileList.length < 6" @click="chooseImage">
<text class="plus-icon">+</text>
<text class="upload-text">上传图片</text>
</view>
</view>
</view>
<!-- 4. 退款描述卡片 -->
<view class="card form-card">
<view class="section-title-row">
<text class="section-title">退款描述</text>
<text class="section-sub">(选填)</text>
</view>
<view class="textarea-box">
<textarea class="desc-input" v-model="remark" placeholder="补充描述,有助于更好的处理售后问题"
placeholder-style="color: #CCCCCC; font-size: 28rpx;" maxlength="100" />
<view class="char-counter">已输入{{ remark.length }}/100</view>
</view>
</view>
</view>
<!-- 底部间距防止按键遮挡 -->
<view class="bottom-spacer"></view>
</scroll-view>
<!-- 底部固定提交按钮 -->
<view class="fixed-bottom-bar">
<button class="submit-btn" @click="submitRefund">提交</button>
</view>
<!-- 退款原因 弹窗 Modal (图二) -->
<up-popup :show="showReasonPopup" mode="bottom" :round="16" :closeable="false" @close="showReasonPopup = false">
<view class="reason-popup-content">
<!-- 弹窗头部 -->
<view class="popup-header">
<text class="popup-title">退款原因</text>
<view class="popup-close-box" @click="showReasonPopup = false">
<text class="popup-close-icon">×</text>
</view>
</view>
<!-- 原因列表 -->
<scroll-view scroll-y class="reason-list-scroll">
<view class="reason-list">
<view v-for="(item, index) in reasonList" :key="item.id || index" class="reason-item-row"
@click="selectReasonItem(item)">
<text class="reason-item-name">{{ item.name }}</text>
<view :class="['radio-circle', { checked: tempSelectedReason && tempSelectedReason.id === item.id }]">
<text v-if="tempSelectedReason && tempSelectedReason.id === item.id" class="check-mark">✓</text>
</view>
</view>
</view>
</scroll-view>
<!-- 弹窗底部提交按钮 -->
<view class="popup-bottom-bar">
<button class="popup-submit-btn" @click="confirmReason">提交</button>
</view>
</view>
</up-popup>
</view>
</template>
<script>
import { RETURN_REASON_LIST } from "@/pages/mine_package/mine_order/emun/index.js";
import { getDetail, afterSaleApply } from "@/api/order.js";
import { BASE_URL, Authorization } from '@/utils/config.js';
import { getStorageFun, TOKEN_NAME } from '@/utils/auth.js';
import { uploadImg } from '@/api/common.js';
import { IMG_TYPE } from '@/utils/enumUtils.js';
export default {
data() {
return {
orderInfo: {},
mpWarpStyle: {},
mpHeaderStyle: {},
selectedReason: null,
tempSelectedReason: null,
showReasonPopup: false,
remark: "",
fileList: [],
reasonList: RETURN_REASON_LIST
};
},
computed: {
firstItem() {
if (this.orderInfo && Array.isArray(this.orderInfo.items) && this.orderInfo.items.length > 0) {
return this.orderInfo.items[0];
}
return {};
},
productTitle() {
return this.firstItem.title || this.orderInfo.title || "";
},
productPrice() {
return this.firstItem.goodsAmount || this.orderInfo.sumAmount || "0";
},
productSpec() {
return this.firstItem.goodsSpece || this.orderInfo.goodsSpece || "";
},
productNum() {
return this.firstItem.buyNum || this.orderInfo.buyNum || 1;
},
totalPrice() {
return this.orderInfo.sumAmount || "0";
},
productImage() {
return this.firstItem.mainGraph || this.orderInfo.mainGraph || "";
}
},
onLoad(options) {
this.initMPHeader();
let orderId = options ? options.id : null;
if (options && options.orderData) {
try {
this.orderInfo = JSON.parse(decodeURIComponent(options.orderData));
if (!orderId && this.orderInfo.id) {
orderId = this.orderInfo.id;
}
} catch (e) {
console.error("解析订单数据错误:", e);
}
}
if (orderId) {
this.fetchOrderDetail(orderId);
}
},
methods: {
async fetchOrderDetail(id) {
if (!id) return;
try {
const res = await getDetail({ id });
if (res && res.bizcode === 100 && res.data) {
this.orderInfo = res.data;
}
} catch (e) {
console.error("fetchOrderDetail error:", e);
}
},
initMPHeader() {
try {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight || 0;
// #ifdef MP-WEIXIN || MP
const menuButton = uni.getMenuButtonBoundingClientRect();
if (menuButton && menuButton.top && menuButton.top > 0) {
const menuButtonHeight = menuButton.height || 32;
const menuButtonTop = menuButton.top;
const menuButtonLeft = menuButton.left;
const windowWidth = systemInfo.windowWidth;
this.mpWarpStyle = {
paddingTop: `${menuButtonTop}px`
};
this.mpHeaderStyle = {
height: `${menuButtonHeight}px`,
paddingRight: `${windowWidth - menuButtonLeft + 10}px`,
display: 'flex',
alignItems: 'center'
};
return;
}
// #endif
// APP-PLUS 或 H5 或 无胶囊环境:获取系统状态栏高度安全边距
this.mpWarpStyle = {
paddingTop: `${statusBarHeight}px`
};
this.mpHeaderStyle = {
height: '88rpx',
display: 'flex',
alignItems: 'center'
};
} catch (e) {
console.error("initMPHeader error:", e);
}
},
goBack() {
uni.navigateBack({
fail: () => {
uni.switchTab({ url: '/pages/mine/mine' });
}
});
},
openReasonPopup() {
this.tempSelectedReason = this.selectedReason || (this.reasonList.length > 0 ? this.reasonList[0] : null);
this.showReasonPopup = true;
},
selectReasonItem(item) {
this.tempSelectedReason = item;
},
confirmReason() {
if (!this.tempSelectedReason) {
uni.showToast({ title: '请选择退款原因', icon: 'none' });
return;
}
this.selectedReason = this.tempSelectedReason;
this.showReasonPopup = false;
},
chooseImage() {
const that = this;
const count = 6 - this.fileList.length;
if (count <= 0) return;
uni.chooseImage({
count: count,
sizeType: ["original", "compressed"],
sourceType: ["album", "camera"],
success: (res) => {
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
res.tempFilePaths.forEach((filePath) => {
that.uploadFile(filePath);
});
}
}
});
},
uploadFile(url) {
uni.showLoading({
title: "图片上传中..."
});
const that = this;
const token = getStorageFun(TOKEN_NAME);
uni.uploadFile({
url: BASE_URL + uploadImg,
filePath: url,
name: "file",
formData: {
type: IMG_TYPE.COMMON
},
header: {
Authorization: Authorization,
"HSM-AUTH": token ? token : ""
},
success: (uploadFileRes) => {
let data;
try {
data = JSON.parse(uploadFileRes.data);
} catch (e) {
data = uploadFileRes.data;
}
if (data && data.bizcode === 100) {
that.fileList.push(data.data.accessUrl);
} else {
uni.showToast({
title: (data && data.msg) || "图片上传失败",
icon: "none",
mask: true,
duration: 2000
});
}
},
fail: (uploadFileErr) => {
uni.showToast({
title: "图片上传失败",
icon: "none",
mask: true,
duration: 2000
});
},
complete: (res) => {
uni.hideLoading();
}
});
},
previewImg(index) {
uni.previewImage({
current: index,
urls: this.fileList
});
},
deleteImg(index) {
this.fileList.splice(index, 1);
},
async submitRefund() {
if (!this.selectedReason) {
uni.showToast({
title: '请选择退款原因',
icon: 'none'
});
return;
}
const params = {
id: this.orderInfo.id || 0,
itemIds: (this.orderInfo.items && this.orderInfo.items[0]) ? this.orderInfo.items[0].id : '',
reason: this.selectedReason.id,
remark: this.remark,
imgUrls: this.fileList.join(','),
type: this.orderInfo?.status === 2 ? 1 : (this.orderInfo?.status === 3 || this.orderInfo?.status === 4) ? 2 : 1
};
try {
uni.showLoading({ title: '提交中...' });
const res = await afterSaleApply(params);
uni.hideLoading();
if (res && res.bizcode === 100) {
uni.showToast({
title: '申请成功',
icon: 'success'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: (res && res.msg) || '申请提交成功',
icon: 'success'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
} catch (e) {
uni.hideLoading();
uni.showToast({
title: '申请已提交',
icon: 'success'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}
}
};
</script>
<style lang="scss" scoped>
.apply-refund-container {
height: 100vh;
background-color: #F6F7F9;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
/* 顶部固定导航栏包装器 */
.navbar-sticky-wrapper {
position: sticky;
top: 0;
z-index: 100;
background-color: #FFFFFF;
width: 100%;
}
/* 顶部导航栏 */
.custom-navbar {
height: 88rpx;
background-color: #FFFFFF;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
position: relative;
box-sizing: border-box;
.back_btn,
.nav-left {
width: 60rpx;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex-shrink: 0;
.back-btn-box {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
margin-right: 20rpx;
flex-shrink: 0;
}
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #1A1A1A;
text-align: center;
flex: 1;
}
.nav-right {
width: 60rpx;
}
}
/* 主滚动区域 */
.content-scroll {
height: 100%;
min-height: 0;
}
.main-content {
padding: 20rpx 24rpx;
}
/* 通用卡片容器 */
.card {
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
}
/* 1. 商品信息卡片 */
.product-card {
.product-info-row {
display: flex;
align-items: flex-start;
.product-img {
width: 140rpx;
height: 140rpx;
border-radius: 12rpx;
background-color: #F2F3F5;
flex-shrink: 0;
margin-right: 20rpx;
}
.product-details {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 140rpx;
overflow: hidden;
.product-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
.product-title {
font-size: 28rpx;
font-weight: bold;
color: #111111;
line-height: 38rpx;
flex: 1;
margin-right: 16rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
word-break: break-all;
}
.product-price {
font-size: 30rpx;
font-weight: bold;
color: #111111;
flex-shrink: 0;
}
}
.product-spec-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12rpx;
.product-spec {
font-size: 26rpx;
color: #999999;
flex: 1;
margin-right: 16rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
word-break: break-all;
}
.product-num {
font-size: 26rpx;
color: #999999;
flex-shrink: 0;
}
}
}
}
.product-total-row {
text-align: right;
margin-top: 20rpx;
.product-total-text {
font-size: 26rpx;
color: #999999;
}
}
}
/* 2. 表单卡片 (退款原因、金额、凭证、描述) */
.form-card {
.form-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 72rpx;
.form-label {
display: flex;
align-items: center;
.required-asterisk {
color: #FF4D4F;
font-size: 28rpx;
margin-right: 6rpx;
}
.label-text {
font-size: 28rpx;
color: #666666;
}
}
.select-trigger {
display: flex;
align-items: center;
.select-value {
font-size: 28rpx;
color: #333333;
margin-right: 12rpx;
&.placeholder {
color: #AAAAAA;
}
}
.dashed-arrow-box {
width: 32rpx;
height: 32rpx;
border: 1rpx dashed #C8C9CC;
border-radius: 4rpx;
display: flex;
align-items: center;
justify-content: center;
.arrow-icon {
font-size: 24rpx;
color: #999999;
line-height: 1;
margin-top: -2rpx;
}
}
}
.amount-value {
font-size: 32rpx;
font-weight: bold;
color: #111111;
}
}
.row-divider {
height: 1rpx;
background-color: #F2F3F5;
margin: 20rpx 0;
}
.section-title-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.section-title {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
.section-sub {
font-size: 28rpx;
color: #999999;
margin-left: 8rpx;
}
}
/* 上传凭证 */
.upload-container {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.upload-img-item {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
position: relative;
overflow: hidden;
.upload-img {
width: 100%;
height: 100%;
}
.delete-icon {
position: absolute;
top: 0;
right: 0;
width: 36rpx;
height: 36rpx;
background: rgba(0, 0, 0, 0.5);
color: #FFFFFF;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
border-bottom-left-radius: 12rpx;
}
}
.upload-btn-box {
width: 160rpx;
height: 160rpx;
background-color: #F7F8FA;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.plus-icon {
font-size: 48rpx;
color: #999999;
line-height: 1;
margin-bottom: 10rpx;
font-weight: 300;
}
.upload-text {
font-size: 24rpx;
color: #999999;
}
}
}
/* 退款描述 */
.textarea-box {
background-color: #F7F8FA;
border-radius: 12rpx;
padding: 20rpx;
position: relative;
.desc-input {
width: 100%;
height: 160rpx;
font-size: 28rpx;
color: #333333;
}
.char-counter {
text-align: right;
font-size: 24rpx;
color: #CCCCCC;
margin-top: 10rpx;
}
}
}
.bottom-spacer {
height: 140rpx;
}
/* 底部固定提交按钮 */
.fixed-bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #FFFFFF;
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.03);
z-index: 100;
.submit-btn {
height: 90rpx;
line-height: 90rpx;
background-color: #7934F6;
border-radius: 16rpx;
color: #FFFFFF;
font-size: 32rpx;
font-weight: 500;
text-align: center;
border: none;
&::after {
border: none;
}
}
}
}
/* 图二: 退款原因 弹窗 */
.reason-popup-content {
background-color: #FFFFFF;
padding: 30rpx 32rpx calc(30rpx + env(safe-area-inset-bottom));
display: flex;
flex-direction: column;
max-height: 80vh;
.popup-header {
position: relative;
text-align: center;
padding-bottom: 24rpx;
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.popup-close-box {
position: absolute;
right: 0;
top: -4rpx;
width: 40rpx;
height: 40rpx;
border: 1rpx dashed #C8C9CC;
border-radius: 6rpx;
display: flex;
align-items: center;
justify-content: center;
.popup-close-icon {
font-size: 28rpx;
color: #666666;
line-height: 1;
}
}
}
.reason-list-scroll {
max-height: 55vh;
.reason-list {
padding: 10rpx 0;
.reason-item-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
.reason-item-name {
font-size: 28rpx;
color: #333333;
}
.radio-circle {
width: 38rpx;
height: 38rpx;
border-radius: 50%;
border: 2rpx solid #DCDCDC;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
&.checked {
background-color: #7934F6;
border-color: #7934F6;
}
.check-mark {
color: #FFFFFF;
font-size: 22rpx;
font-weight: bold;
line-height: 1;
}
}
}
}
}
.popup-bottom-bar {
padding-top: 20rpx;
.popup-submit-btn {
height: 88rpx;
line-height: 88rpx;
background-color: #7934F6;
border-radius: 16rpx;
color: #FFFFFF;
font-size: 32rpx;
font-weight: 500;
text-align: center;
border: none;
&::after {
border: none;
}
}
}
}
</style>