feat: 订单退款功能对接
This commit is contained in:
@@ -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; // 直接返回,不执行后续代码
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user