feat: 订单退款功能对接

This commit is contained in:
duanshuwen
2025-09-29 21:59:07 +08:00
parent 2580e7a266
commit 9cc7b48d36
5 changed files with 212 additions and 299 deletions

View File

@@ -39,12 +39,7 @@
<script setup>
import { defineProps, computed, ref, defineEmits } from "vue";
import {
preOrder,
orderPayNow,
orderCancel,
orderRefund,
} from "@/request/api/OrderApi";
import { orderPayNow } from "@/request/api/OrderApi";
// 支付方式映射常量
const PAY_WAY_MAP = {
@@ -57,7 +52,7 @@ const PAY_WAY_MAP = {
const isLoading = ref(false);
// 定义事件发射器
const emit = defineEmits(['show-refund-popup']);
const emit = defineEmits(["show-refund-popup"]);
const props = defineProps({
orderData: {
@@ -116,7 +111,7 @@ const handleButtonClick = async () => {
if (status === "2") {
// 情况2待使用状态显示退款弹窗
emit('show-refund-popup');
emit("show-refund-popup");
return; // 直接返回,不执行后续代码
}

View File

@@ -3,61 +3,68 @@
<view class="refund-popup">
<!-- 头部卡通形象 -->
<image
src="@/static/dh.png"
:src="commodityPurchaseInstruction.refundLogo"
class="refund-popup__avatar"
mode="widthFix"
mode="aspectFill"
/>
<!-- 内容区域 -->
<view class="refund-popup__content">
<!-- 主标题 -->
<view class="refund-popup__title">{{ currentTitle }}</view>
<view class="refund-popup__title">
{{ commodityPurchaseInstruction.refundTitle }}
</view>
<!-- 金额显示 -->
<view class="refund-popup__amount" v-if="showAmount">
<view class="refund-popup__amount" v-if="isRefundable">
<text class="amount-symbol">¥</text>
<text class="amount-value">{{ refundAmount }}</text>
<text class="amount-unit"></text>
</view>
<view class="refund-popup__amount-label" v-if="showAmount"
<view class="refund-popup__amount-label" v-if="isRefundable"
>可退金额</view
>
<!-- 描述信息 -->
<view class="refund-popup__description" v-if="currentDescription">
{{ currentDescription }}
</view>
<!-- 退款政策详情 -->
<view class="refund-popup__policy" v-if="showPolicy">
<view class="refund-popup__policy" v-if="showRefundPolicy">
<view class="policy-title">退订政策</view>
<view class="policy-list">
<view
class="policy-item"
v-for="(rule, index) in currentRules"
:key="index"
>
{{ rule }}
</view>
<view class="policy-content">
{{ commodityPurchaseInstruction.refundContent }}
</view>
</view>
</view>
<!-- 按钮区域 -->
<view class="refund-popup__actions">
<view class="action-btn secondary-btn" @click="handlePolicyClick">
{{ leftButtonText }}
<view
v-if="!showRefundPolicy"
class="action-btn secondary-btn"
@click="handlePolicyClick"
>
退订政策
</view>
<view class="action-btn primary-btn" @click="handleConfirmClick">
{{ rightButtonText }}
<view
class="action-btn primary-btn"
@click="handleConfirmClick(btnText)"
>
{{ btnText }}
</view>
</view>
<!-- 关闭按钮 -->
<uni-icons
class="refund-popup__close"
type="close"
size="40"
color="#fff"
@click="handleClose"
/>
</view>
</uni-popup>
</template>
<script setup>
import { ref, computed, watch, shallowRef } from "vue";
import { ref, computed, watch } from "vue";
// Props定义
const props = defineProps({
@@ -66,107 +73,44 @@ const props = defineProps({
type: Boolean,
default: false,
},
// 退款类型
refundType: {
type: String,
default: "no_refund",
validator: (value) =>
["no_refund", "all_refund", "anytime_refund"].includes(value),
},
// 可退金额
refundAmount: {
type: Number,
default: 0,
},
// 退款规则列表
refundRules: {
type: Array,
default: () => [],
},
// 自定义标题
title: {
type: String,
default: "",
},
// 自定义描述
description: {
type: String,
default: "",
// 订单数据
orderData: {
type: Object,
default: () => {},
},
});
// Events定义
const emit = defineEmits([
"update:modelValue",
"policy-click",
"confirm-click",
"close",
]);
const emit = defineEmits(["update:modelValue", "confirm", "close"]);
// 弹窗引用
const popupRef = ref(null);
// 退款政策是否显示
const showRefundPolicy = ref(false);
// 退款场景配置使用shallowRef优化性能
const refundScenarios = shallowRef({
no_refund: {
title: "您在入住日期12小时以内申请退款不可退款,如有疑问请咨询客服",
description: "",
showAmount: false,
showPolicy: false,
leftButton: "退订政策",
rightButton: "我知道了",
rules: [],
},
all_refund: {
title: "您在入住日期24小时内申请退款可退还100%金额",
description: "",
showAmount: true,
showPolicy: true,
leftButton: "退订政策",
rightButton: "点击退款",
rules: [],
},
anytime_refund: {
title: "该商品未使用随时可退",
description: "",
showAmount: true,
showPolicy: false,
leftButton: "退订政策",
rightButton: "点击退款",
rules: [],
},
});
// 计算属性
const currentScenario = computed(
() =>
refundScenarios.value[props.refundType] || refundScenarios.value.no_refund
);
const currentTitle = computed(() => props.title || currentScenario.value.title);
const currentDescription = computed(
() => props.description || currentScenario.value.description
);
const currentRules = computed(() => {
if (props.refundRules.length) {
return props.refundRules;
// 退款金额
const refundAmount = computed(() => {
if (props.orderData.payAmt) {
return parseFloat(Number(props.orderData.payAmt) || 0);
}
return currentScenario.value.rules;
return 0;
});
const showAmount = computed(
() => currentScenario.value.showAmount && props.refundAmount > 0
);
// 是否可退款
const isRefundable = computed(() => !props.orderData.refundable);
const showPolicy = computed(
() => currentScenario.value.showPolicy && currentRules.value.length > 0
);
// 按钮文件
const btnText = computed(() => (isRefundable.value ? "点击退款" : "我知道了"));
const leftButtonText = computed(() => currentScenario.value.leftButton);
// 获取退款模板
const commodityPurchaseInstruction = computed(() => {
if (props.orderData.commodityPurchaseInstruction) {
return props.orderData.commodityPurchaseInstruction;
}
const rightButtonText = computed(() => currentScenario.value.rightButton);
return {};
});
// 方法定义
const show = () => popupRef.value.open();
@@ -186,32 +130,25 @@ watch(
{ immediate: true }
);
// 监听退款金额变化,进行数据验证
watch(
() => props.refundAmount,
(newVal) => {
if (newVal < 0) {
console.warn("RefundPopup: 退款金额不能为负数");
}
},
{ immediate: true }
);
const handleClose = () => {
showRefundPolicy.value = false;
emit("update:modelValue", false);
emit("close");
};
const handlePolicyClick = () => {
emit("policy-click");
showRefundPolicy.value = true;
};
const handleConfirmClick = () => {
emit("confirm-click");
const handleConfirmClick = (text) => {
if (text === "点击退款") {
emit("confirm", props.orderData);
}
handleClose();
};
</script>
<style lang="scss" scoped>
@use "./styles/index.scss";
</style>
</style>

View File

@@ -1,168 +1,164 @@
// RefundPopup 退款弹窗样式
.refund-popup {
width: 320px;
background: linear-gradient(173deg, #cbf6ff 3%, #ffffff 32%);
border-radius: 12px;
box-sizing: border-box;
padding-top: 64px;
position: relative;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
width: 320px;
background: linear-gradient(173deg, #cbf6ff 3%, #ffffff 32%);
border-radius: 12px;
box-sizing: border-box;
padding-top: 64px;
position: relative;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
// 头部区域
&__avatar {
width: 132px;
height: 132px;
position: absolute;
top: -45px;
left: 50%;
transform: translateX(-50%);
// 头部区域
&__avatar {
width: 132px;
height: 132px;
position: absolute;
top: -75px;
left: 50%;
transform: translateX(-50%);
}
// 内容区域
&__content {
padding: 12px 20px 20px;
text-align: center;
}
&__title {
font-size: 16px;
font-weight: 500;
color: #333;
line-height: 22px;
margin-bottom: 12px;
text-align: center;
}
&__amount {
display: flex;
justify-content: center;
align-items: baseline;
margin-bottom: 4px;
.amount-symbol {
font-size: 12px;
color: #ff6a00;
}
// 内容区域
&__content {
padding: 12px 20px 20px;
text-align: center;
.amount-value {
font-size: 24px;
color: #ff6a00;
margin: 0 2px;
}
&__title {
font-size: 16px;
font-weight: 500;
color: #333;
line-height: 22px;
margin-bottom: 12px;
text-align: center;
.amount-unit {
font-size: 12px;
color: #ff6a00;
}
}
&__amount-label {
font-size: 12px;
color: #333;
margin-bottom: 16px;
}
&__policy {
text-align: left;
margin-bottom: 16px;
.policy-title {
font-size: 14px;
color: #007aff;
font-weight: 600;
margin-bottom: 8px;
}
&__amount {
display: flex;
justify-content: center;
align-items: baseline;
margin-bottom: 4px;
.policy-content {
font-size: 12px;
color: #333333;
line-height: 22px;
text-align: justify;
}
}
.amount-symbol {
font-size: 12px;
color: #ff6a00;
// 按钮区域
&__actions {
display: flex;
gap: 12px;
padding: 0 20px 20px;
.action-btn {
flex: 1;
height: 44px;
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.3s ease;
outline: none;
&.secondary-btn {
background: #007aff;
color: #ffffff;
&:active {
background: #0056cc;
transform: scale(0.98);
}
}
.amount-value {
font-size: 24px;
color: #ff6a00;
margin: 0 2px;
}
.amount-unit {
font-size: 12px;
color: #ff6a00;
&.primary-btn {
background: #ff9500;
color: #ffffff;
&:active {
background: #e6850e;
transform: scale(0.98);
}
}
}
}
&__amount-label {
font-size: 12px;
color: #333;
margin-bottom: 16px;
}
&__description {
font-size: 14px;
color: #333333;
line-height: 1.5;
margin-bottom: 16px;
text-align: left;
}
&__policy {
text-align: left;
margin-bottom: 16px;
.policy-title {
font-size: 14px;
color: #007aff;
font-weight: 600;
margin-bottom: 8px;
}
.policy-list {
.policy-item {
font-size: 12px;
color: #333333;
line-height: 22px;
text-align: justify;
&:last-child {
margin-bottom: 0;
}
}
}
}
// 按钮区域
&__actions {
display: flex;
gap: 12px;
padding: 0 20px 20px;
.action-btn {
flex: 1;
height: 44px;
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.3s ease;
outline: none;
&.secondary-btn {
background: #007aff;
color: #ffffff;
&:active {
background: #0056cc;
transform: scale(0.98);
}
}
&.primary-btn {
background: #ff9500;
color: #ffffff;
&:active {
background: #e6850e;
transform: scale(0.98);
}
}
}
}
// 关闭按钮
&__close {
position: absolute;
bottom: -60px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 40px;
}
}
// 动画效果
@keyframes popupFadeIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes flowerBounce {
0%,
20%,
50%,
80%,
100% {
transform: translateY(0);
}
40% {
transform: translateY(-4px);
}
60% {
transform: translateY(-2px);
}
0%,
20%,
50%,
80%,
100% {
transform: translateY(0);
}
40% {
transform: translateY(-4px);
}
60% {
transform: translateY(-2px);
}
}
.refund-popup {
animation: popupFadeIn 0.3s ease-out;
animation: popupFadeIn 0.3s ease-out;
}

View File

@@ -17,10 +17,8 @@
<!-- 退款状态显示 -->
<RefundPopup
v-model="refundVisible"
:refund-type="refundType"
:refund-amount="refundAmount"
@policy-click="viewRefundPolicy"
@confirm-click="handleRefundConfirm"
:orderData="orderData"
@confirm="handleRefundConfirm"
/>
</view>
</template>
@@ -38,15 +36,12 @@ import OrderInfo from "./components/OrderInfo/index.vue";
import RefundPopup from "./components/RefundPopup/index.vue";
const refundVisible = ref(false);
const refundType = ref("free_cancel"); // 默认退款类型
const refundAmount = ref(0); // 退款金额
const orderData = ref({});
onLoad(async ({ orderId }) => {
const res = await userOrderDetail({ orderId });
orderData.value = res.data;
// 设置退款金额为订单支付金额
refundAmount.value = parseFloat(res.data.payAmt || 0);
console.log(res);
});
@@ -62,17 +57,11 @@ const showRefundPopup = () => {
refundVisible.value = true;
};
// 查看退款政策
const viewRefundPolicy = () => {
console.log("查看退款政策");
// 这里可以跳转到退款政策页面或显示详细政策
};
// 确认退款
const handleRefundConfirm = async () => {
const handleRefundConfirm = async ({ orderId }) => {
try {
// 调用退款API
await orderRefund({ orderId: orderData.value.orderId });
await orderRefund({ orderId });
uni.showToast({
title: "退款申请已提交",
@@ -80,10 +69,8 @@ const handleRefundConfirm = async () => {
});
// 刷新订单状态
const res = await userOrderDetail({ orderId: orderData.value.orderId });
const res = await userOrderDetail({ orderId });
orderData.value = res.data;
// 更新退款金额
refundAmount.value = parseFloat(res.data.payAmt || 0);
} catch (error) {
console.error("退款失败:", error);
uni.showToast({

View File

@@ -41,9 +41,7 @@
.wrapper {
box-sizing: border-box;
padding-left: 12px;
padding-right: 12px;
padding-top: 12px;
padding: 12px;
}
.good-info-wrapper {
@@ -160,7 +158,7 @@
box-sizing: border-box;
padding-left: 12px;
padding-right: 12px;
padding-bottom: var(--safe-area-inset-bottom);
padding-bottom: 24px;
.left {
display: flex;