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

View File

@@ -3,61 +3,68 @@
<view class="refund-popup"> <view class="refund-popup">
<!-- 头部卡通形象 --> <!-- 头部卡通形象 -->
<image <image
src="@/static/dh.png" :src="commodityPurchaseInstruction.refundLogo"
class="refund-popup__avatar" class="refund-popup__avatar"
mode="widthFix" mode="aspectFill"
/> />
<!-- 内容区域 --> <!-- 内容区域 -->
<view class="refund-popup__content"> <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-symbol">¥</text>
<text class="amount-value">{{ refundAmount }}</text> <text class="amount-value">{{ refundAmount }}</text>
<text class="amount-unit"></text> <text class="amount-unit"></text>
</view> </view>
<view class="refund-popup__amount-label" v-if="showAmount" <view class="refund-popup__amount-label" v-if="isRefundable"
>可退金额</view >可退金额</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-title">退订政策</view>
<view class="policy-list"> <view class="policy-content">
<view {{ commodityPurchaseInstruction.refundContent }}
class="policy-item"
v-for="(rule, index) in currentRules"
:key="index"
>
{{ rule }}
</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 按钮区域 --> <!-- 按钮区域 -->
<view class="refund-popup__actions"> <view class="refund-popup__actions">
<view class="action-btn secondary-btn" @click="handlePolicyClick"> <view
{{ leftButtonText }} v-if="!showRefundPolicy"
class="action-btn secondary-btn"
@click="handlePolicyClick"
>
退订政策
</view> </view>
<view class="action-btn primary-btn" @click="handleConfirmClick"> <view
{{ rightButtonText }} class="action-btn primary-btn"
@click="handleConfirmClick(btnText)"
>
{{ btnText }}
</view> </view>
</view> </view>
<!-- 关闭按钮 -->
<uni-icons
class="refund-popup__close"
type="close"
size="40"
color="#fff"
@click="handleClose"
/>
</view> </view>
</uni-popup> </uni-popup>
</template> </template>
<script setup> <script setup>
import { ref, computed, watch, shallowRef } from "vue"; import { ref, computed, watch } from "vue";
// Props定义 // Props定义
const props = defineProps({ const props = defineProps({
@@ -66,107 +73,44 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
// 退款类型 // 订单数据
refundType: { orderData: {
type: String, type: Object,
default: "no_refund", default: () => {},
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: "",
}, },
}); });
// Events定义 // Events定义
const emit = defineEmits([ const emit = defineEmits(["update:modelValue", "confirm", "close"]);
"update:modelValue",
"policy-click",
"confirm-click",
"close",
]);
// 弹窗引用 // 弹窗引用
const popupRef = ref(null); const popupRef = ref(null);
// 退款政策是否显示
const showRefundPolicy = ref(false);
// 退款场景配置使用shallowRef优化性能 // 退款金额
const refundScenarios = shallowRef({ const refundAmount = computed(() => {
no_refund: { if (props.orderData.payAmt) {
title: "您在入住日期12小时以内申请退款不可退款,如有疑问请咨询客服", return parseFloat(Number(props.orderData.payAmt) || 0);
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;
} }
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(); const show = () => popupRef.value.open();
@@ -186,32 +130,25 @@ watch(
{ immediate: true } { immediate: true }
); );
// 监听退款金额变化,进行数据验证
watch(
() => props.refundAmount,
(newVal) => {
if (newVal < 0) {
console.warn("RefundPopup: 退款金额不能为负数");
}
},
{ immediate: true }
);
const handleClose = () => { const handleClose = () => {
showRefundPolicy.value = false;
emit("update:modelValue", false); emit("update:modelValue", false);
emit("close"); emit("close");
}; };
const handlePolicyClick = () => { const handlePolicyClick = () => {
emit("policy-click"); showRefundPolicy.value = true;
}; };
const handleConfirmClick = () => { const handleConfirmClick = (text) => {
emit("confirm-click"); if (text === "点击退款") {
emit("confirm", props.orderData);
}
handleClose(); handleClose();
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use "./styles/index.scss"; @use "./styles/index.scss";
</style> </style>

View File

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

View File

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

View File

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