feat: 订单详情交互调整

This commit is contained in:
duanshuwen
2025-10-30 21:21:05 +08:00
parent 2b9afb936e
commit 90723fa277
10 changed files with 230 additions and 308 deletions

View File

@@ -0,0 +1,54 @@
<template>
<view class="bg-white border-box rounded-12 p-12 mb-12">
<view class="flex flex-items-center flex-justify-between mb-4">
<view class="flex flex-items-center">
<text class="font-size-16 color-171717 line-height-24 font-500">
订单金额
</text>
<text class="amt font-size-18 font-bold color-FF3D60 ml-4">
{{ orderData.payAmt }}
</text>
</view>
</view>
<view
v-if="orderData.orderStatus === '0'"
class="font-size-11 color-99A0AE"
>
超时后订单将自动取消
</view>
<view
v-if="['1', '2'].includes(orderData.orderStatus)"
class="border-box border-top flex flex-items-center flex-justify-between font-size-12 pt-12 mt-12"
>
<view class="color-525866">取消政策及说明</view>
<view class="flex flex-items-center" @click="emit('click')">
<text class="color-2D91FF mr-4">查看详情</text>
<uni-icons type="right" size="12" color="#99A0AE" />
</view>
</view>
</view>
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
orderData: {
type: Object,
required: true,
default: () => ({}),
},
});
const emit = defineEmits(["click"]);
</script>
<style scoped lang="scss">
.amt {
&::before {
content: "¥";
font-size: 16px;
font-weight: 500;
}
}
</style>

View File

@@ -3,19 +3,21 @@
class="footer bg-white border-box flex flex-items-center flex-justify-between p-12"
>
<button
v-if="shouldShowButton"
v-if="['1', '2'].includes(statusCode)"
class="left border-none border-box bg-white rounded-10 flex flex-items-center flex-justify-center font-size-14 font-500 color-525866 mr-12"
@click="emit('refund')"
>
取消订单
申请退款
</button>
<button
:class="[
'right border-none rounded-10 flex flex-full flex-items-center flex-justify-center font-size-14 font-500',
'right border-none rounded-10 flex flex-full flex-items-center flex-justify-center font-size-14 font-500 color-white',
{
'bg-FF3D60 color-white': statusCode === '0',
'bg-color-f5f7fa color-525866': statusCode === '2',
'bg-FF3D60': statusCode === '0',
'bg-2D91FF': ['1', '2', '3', '5', '6'].includes(statusCode),
},
]"
@click="handleButtonClick"
>
{{ buttonText }}
</button>
@@ -23,7 +25,8 @@
</template>
<script setup>
import { defineProps, computed, ref, defineEmits } from "vue";
import { defineProps, defineEmits, computed } from "vue";
import { orderPayNow } from "@/request/api/OrderApi";
const props = defineProps({
orderData: {
@@ -47,8 +50,8 @@ const buttonText = computed(() => {
switch (statusCode.value) {
case "0": // 待支付状态
return "立即支付";
case "2": // 待使用状态
return "申请退款";
case "1": // 已取消状态
case "2": // 已取消状态
case "3": // 已取消状态
case "5": // 已关闭状态
case "6": // 已完成状态
@@ -56,10 +59,74 @@ const buttonText = computed(() => {
}
});
// 是否显示按钮(待支付、待使用、已取消、已关闭、已完成)
const shouldShowButton = computed(() => {
return ["0", "2", "3", "5", "6"].includes(statusCode.value);
// 定义事件发射器
const emit = defineEmits(["refund", "refresh"]);
// 处理按钮点击事件
const handleButtonClick = async (orderData) => {
try {
// 再次预定跳转商品详情
if (["3", "5", "6"].includes(statusCode.value)) {
uni.navigateTo({
url: `/pages/goods/index?commodityId=${orderData.commodityId}`,
});
}
// 待支付状态,调用支付接口
if (statusCode.value === "0") {
const orderId = orderData.orderId;
const payWay = orderData.payWay;
const paySource = orderData.paySource;
const res = await orderPayNow({ orderId, payWay, paySource });
console.log("确认订单---2:", res);
// 检查接口返回数据
if (!res || !res.data) {
uni.showToast({ title: "订单创建失败,请重试", icon: "none" });
return;
}
const { data } = res;
const { nonceStr, packageVal, paySign, signType, timeStamp } = data;
// 验证支付参数是否完整
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
// console.error("支付参数不完整:", {
// nonceStr: !!nonceStr,
// packageVal: !!packageVal,
// paySign: !!paySign,
// signType: !!signType,
// timeStamp: !!timeStamp,
// });
uni.showToast({ title: "支付参数错误,请重试", icon: "none" });
return;
}
// 调用微信支付
uni.requestPayment({
provider: "wxpay",
timeStamp: String(timeStamp), // 确保为字符串类型
nonceStr: String(nonceStr),
package: String(packageVal), // 确保为字符串类型
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.showToast({
title: "支付成功",
icon: "success",
success: () => emit("refresh"),
});
},
fail: () => {
uni.showToast({ title: "支付失败,请重试", icon: "none" });
},
});
}
} catch (error) {
console.error("操作失败:", error);
}
};
</script>
<style lang="scss" scoped>

View File

@@ -1,5 +1,8 @@
<template>
<view class="border-box bg-white p-12 rounded-12 mb-12">
<view
v-if="commodityTypeCode === '0'"
class="border-box bg-white p-12 rounded-12 mb-12"
>
<!-- 酒店类型入住离店日期部分 -->
<DateRangeSection
v-if="orderData.commodityTypeCode === '0'"
@@ -10,22 +13,44 @@
{{ orderData.commodityName }}
</view>
<view class="border-box border-bottom">
<view class="font-size-12 color-99A0AE line-height-16 pb-12">
<view
class="border-box border-bottom font-size-12 color-99A0AE line-height-16 pb-12"
>
{{ orderData.commodityDescription }}
</view>
<!-- 权益部分 -->
<view class="flex flex-items-center mb-8">
<view class="border-box flex flex-items-center pt-12">
<text
class="bg-F7F7F7 border-box rounded-4 font-size-11 color-525866 mr-4 pt-4 pb-4 pl-6 pr-6"
v-for="(item, index) in orderData.commodityFacilityList"
v-for="(item, index) in commodityFacilityList"
:key="index"
>
{{ item }}
</text>
</view>
</view>
<view
v-if="commodityTypeCode !== '0'"
class="border-box bg-white p-12 rounded-12 mb-12"
>
<view
class="font-size-16 font-500 color-000 line-height-24 ellipsis-1 mb-8"
>
{{ orderData.commodityName }}
</view>
<view class="flex font-size-12">
<text class="w-60 color-99A0AE">购买数量</text>
<text class="color-525866">{{ commodityAmount }}</text>
</view>
<view
v-if="orderData.orderStatus === '0'"
class="border-box border-top pt-12 font-size-14 font-500 color-171717 mt-12"
>[电子凭证]直接验证使用</view
>
</view>
</template>
@@ -54,6 +79,21 @@ const selectedDate = computed(() => {
totalDays,
};
});
const commodityTypeCode = computed(() => props.orderData.commodityTypeCode);
const commodityAmount = computed(() => {
// 门票单位:张、餐饮单位:份
const { commodityAmount } = props.orderData;
return commodityTypeCode.value === "2"
? `${parseInt(commodityAmount)}`
: `${parseInt(commodityAmount)}`;
});
// 取3个权益
const commodityFacilityList = computed(() => {
return props.orderData.commodityFacilityList.slice(0, 3) || [];
});
</script>
<style scoped lang="scss">

View File

@@ -70,14 +70,8 @@ const displayItems = computed(() => {
const { orderData } = props;
const { orderType } = orderData;
// 工单情况orderType 为 undefined
if (orderType === undefined) {
return [
{ label: LABELS.CREATE_TIME, value: orderData.createTime },
{ label: LABELS.CONTACT_GUEST, value: orderData.userName },
{ label: LABELS.CONTACT_PHONE, value: orderData.userPhone },
];
}
// 去掉末尾连续、、
const formattedPhone = orderData.contactPhone.replace(/、+$/, "");
// 订单情况:根据 orderType 返回不同的显示项
switch (orderType) {
@@ -97,7 +91,7 @@ const displayItems = computed(() => {
),
},
{ label: LABELS.VISITOR_NAME, value: orderData.visitorName },
{ label: LABELS.CONTACT_PHONE, value: orderData.contactPhone },
{ label: LABELS.CONTACT_PHONE, value: formattedPhone },
];
case ORDER_TYPES.TICKET:

View File

@@ -1,7 +1,7 @@
<template>
<view
class="order-card bg-white border-box p-12 rounded-12 m-12"
@click="handleCardClick"
@click.stop="handleCardClick"
>
<!-- 卡片头部 -->
<view class="card-header flex items-center">
@@ -24,6 +24,15 @@
<!-- 卡片内容 -->
<OrderCardContent :order-data="orderData" />
<view v-if="orderData.orderStatus === '0'" class="text-right">
<button
class="go-pay border-box border-none font-size-14 color-white bg-FF3D60 rounded-5 inline-block text-center line-height-30"
@click.stop="handleCardClick"
>
去支付
</button>
</view>
</view>
</template>

View File

@@ -14,31 +14,18 @@
:class="[
{
'color-FF3D60': statusCode === '0',
'color-21B466': statusCode === '1',
'color-21B466': ['1', '2'].includes(statusCode),
},
]"
>
{{ payStatusText }}
{{ statusText }}
</text>
</view>
</view>
</template>
<script setup>
import { defineProps, computed, ref, defineEmits } from "vue";
import { orderPayNow } from "@/request/api/OrderApi";
// 支付方式映射常量
const PAY_STATUS_MAP = {
0: "未支付",
1: "已支付",
};
// 加载状态
const isLoading = ref(false);
// 定义事件发射器
const emit = defineEmits(["show-refund-popup", "pay-success"]);
import { defineProps, computed } from "vue";
const props = defineProps({
orderData: {
@@ -57,108 +44,9 @@ const props = defineProps({
const statusCode = computed(() => props.orderData.orderStatus);
// 使用计算属性缓存支付方式文本
const payStatusText = computed(() => PAY_STATUS_MAP[statusCode.value]);
// 格式化金额显示
const formattedAmount = computed(() => {
const amount = props.orderData.payAmt;
return amount ? `${parseFloat(amount).toFixed(2)}` : "0.00";
});
// 处理按钮点击事件
const handleButtonClick = async (orderData) => {
if (isLoading.value) return; // 防止重复点击
try {
isLoading.value = true;
const status = orderData.orderStatus;
if (status === "2") {
// 情况2待使用状态显示退款弹窗
emit("show-refund-popup");
return; // 直接返回,不执行后续代码
}
// 再次预定跳转商品详情
if (["3", "5", "6"].includes(status)) {
uni.navigateTo({
url: `/pages/goods/index?commodityId=${orderData.commodityId}`,
});
}
// 待支付状态,调用支付接口
if (status === "0") {
const orderId = orderData.orderId;
const payWay = orderData.payWay;
const paySource = orderData.paySource;
const res = await orderPayNow({ orderId, payWay, paySource });
console.log("确认订单---2:", res);
// 检查接口返回数据
if (!res || !res.data) {
uni.showToast({
title: "订单创建失败,请重试",
icon: "none",
duration: 2000,
});
return;
}
const { data } = res;
const { nonceStr, packageVal, paySign, signType, timeStamp } = data;
// 验证支付参数是否完整
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
console.error("支付参数不完整:", {
nonceStr: !!nonceStr,
packageVal: !!packageVal,
paySign: !!paySign,
signType: !!signType,
timeStamp: !!timeStamp,
});
uni.showToast({
title: "支付参数错误,请重试",
icon: "none",
duration: 2000,
});
return;
}
// 调用微信支付
uni.requestPayment({
provider: "wxpay",
timeStamp: String(timeStamp), // 确保为字符串类型
nonceStr: String(nonceStr),
package: String(packageVal), // 确保为字符串类型
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.showToast({
title: "支付成功",
icon: "success",
duration: 2000,
success: () => {
emit("pay-success");
},
});
},
fail: (err) => {
uni.showToast({
title: "支付失败,请重试",
icon: "none",
duration: 2000,
});
},
});
}
} catch (error) {
console.error("操作失败:", error);
} finally {
isLoading.value = false;
}
};
const statusText = computed(() =>
statusCode.value === "0" ? "未支付" : "已支付"
);
</script>
<style scoped lang="scss"></style>

View File

@@ -6,32 +6,35 @@
class="order-detail-wrapper border-box flex-full overflow-hidden scroll-y"
>
<OrderStatusInfo :orderData="orderData" />
<OrderQrcode
v-if="orderData.orderStatus === '2'"
size="132"
unit="px"
:val="orderData.orderId"
/>
<AmtSection :orderData="orderData" @click="refundVisible = true" />
<GoodsInfo :orderData="orderData" />
<UserInfo :orderData="orderData" />
<UserInfo
v-if="orderData.commodityTypeCode === '0'"
:orderData="orderData"
/>
<OrderInfo :orderData="orderData" />
</view>
<FooterSection
:orderData="orderData"
@show-refund-popup="showRefundPopup"
@pay-success="handlePaySuccess"
@refund="handleRefundConfirm"
@refresh="handlePaySuccess"
/>
</view>
<!-- 退款状态显示 -->
<RefundPopup
v-model="refundVisible"
:orderData="orderData"
@confirm="handleRefundConfirm"
/>
<RefundPopup v-model="refundVisible" :orderData="orderData" />
</template>
<script setup>
@@ -43,7 +46,7 @@ import OrderQrcode from "./components/OrderQrcode/index.vue";
import OrderStatusInfo from "./components/OrderStatusInfo/index.vue";
import GoodsInfo from "./components/GoodsInfo/index.vue";
import UserInfo from "./components/UserInfo/index.vue";
import NoticeInfo from "./components/NoticeInfo/index.vue";
import AmtSection from "./components/AmtSection/index.vue";
import OrderInfo from "./components/OrderInfo/index.vue";
import FooterSection from "./components/FooterSection/index.vue";
import RefundPopup from "@/components/RefundPopup/index.vue";
@@ -57,12 +60,6 @@ onLoad(({ orderId }) => getOrderDetail(orderId));
const getOrderDetail = async (orderId) => {
const res = await userOrderDetail({ orderId });
orderData.value = res.data;
console.log(res);
};
// 显示退款弹窗
const showRefundPopup = () => {
refundVisible.value = true;
};
// 确认退款
@@ -71,20 +68,13 @@ const handleRefundConfirm = async ({ orderId }) => {
// 调用退款API
await orderRefund({ orderId });
uni.showToast({
title: "退款申请已提交",
icon: "success",
});
uni.showToast({ title: "退款申请已提交", icon: "success" });
// 刷新订单状态
const res = await userOrderDetail({ orderId });
orderData.value = res.data;
} catch (error) {
console.error("退款失败:", error);
uni.showToast({
title: "退款申请失败,请重试",
icon: "none",
});
uni.showToast({ title: "退款申请失败,请重试", icon: "none" });
}
};

View File

@@ -1,128 +0,0 @@
# 服务工单页面组件需求分析
## 页面概述
根据提供的设计图,这是一个服务工单管理页面,主要包含以下几个核心组件:
## 1. 顶部导航栏组件 (TopNavBar)
### 功能需求:
- 左侧返回按钮
### 设计要求:
- 图标使用 order/images/back.png
- 高度适配状态栏
### 提示词:
```
使用 uniapp + vue3 组合式 api 开发微信小程序顶部导航栏组件,要求如下:
1、左侧显示返回按钮点击可返回上一页
2、适配不同设备的状态栏高度
3、组件内部使用 uniapp 内置组件
```
## 2. Tab 切换组件 (Tab)
### 功能需求:
- 支持多个标签页切换(全部订单、服务工单)
- 选中状态有下划线指示器
- 下划线支持动画过渡效果
- 支持自定义标签内容
### 设计要求:
- 选中项文字颜色:深色 (#333)
- 未选中项文字颜色:灰色 (#666)
- 下划线颜色:蓝色 (#007AFF)
- 下划线宽度根据文字宽度动态调整
### 提示词:
```
使用 uniapp + vue3 组合式 api 开发微信小程序Tab切换组件要求如下
1、支持多个标签页切换默认支持"全部订单"、"服务工单"两个标签
2、选中项底部显示蓝色下划线下划线宽度根据文字宽度动态调整
3、下划线切换时有平滑的滑动动画效果
4、选中项文字为深色未选中为灰色
5、支持自定义标签列表和默认选中项
6、组件内部使用 uniapp 内置组件
7、支持插槽自定义标签内容
```
## 3. 工单卡片组件 (OrderCard)
### 功能需求:
- 显示工单基本信息(标题、创建时间、联系人、电话)
- 支持不同状态的工单样式(待处理、已完成等)
- 支持操作按钮(立即呼叫、已完成标记等)
- 支持工单状态图标显示
- 超过 30 天卡片置灰
### 设计要求:
- 卡片背景:白色,圆角设计
- 卡片间距:适当的上下间距
- 状态图标:橙色圆形图标(待处理)、绿色图标(已完成)、灰色图标(其他状态)
- 操作按钮:橙色渐变按钮(立即呼叫)、绿色边框按钮(已完成)
### 提示词:
```
使用 uniapp + vue3 组合式 api 开发微信小程序工单卡片组件,要求如下:
1、卡片式布局白色背景圆角设计
2、显示工单标题、创建时间、联系房客、联系电话等信息
3、左侧显示状态图标不同状态使用不同颜色橙色-待处理、绿色-已完成、灰色-其他)
4、右侧显示状态标签支持"已完成"等状态显示
5、底部支持操作按钮如"立即呼叫"(橙色渐变按钮)
6、支持不同工单状态的样式变化
7、组件内部使用 uniapp 内置组件
8、支持插槽自定义操作区域
```
## 4. 工单列表容器组件 (OrderList)
### 功能需求:
- 支持工单虚拟列表不固定高度的滚动显示
- 支持下拉刷新和上拉加载更多
- 支持空状态显示
- 支持加载状态显示
### 设计要求:
- 列表背景:蓝色渐变背景
- 卡片间距:统一的间距设计
- 滚动区域:支持弹性滚动
### 提示词:
```
使用 uniapp + vue3 组合式 api 开发微信小程序工单列表组件,要求如下:
1、支持工单数据的列表展示
2、背景使用蓝色渐变效果
3、支持下拉刷新和上拉加载更多功能
4、支持空状态和加载状态的显示
5、列表项使用工单卡片组件进行展示
6、支持不同Tab页面的数据筛选
7、组件内部使用 uniapp 内置组件
8、支持自定义列表项模板
```
## 整体页面集成提示词
```
使用 uniapp + vue3 组合式 api 开发微信小程序服务工单页面,要求如下:
1、页面包含顶部导航栏、Tab切换、工单列表、底部咨询栏等模块
2、支持"全部订单"和"服务工单"两个Tab页面切换
3、工单列表支持不同状态的工单展示待处理、已完成等
4、整体使用蓝色渐变背景卡片式布局
5、支持下拉刷新和上拉加载更多
6、所有组件都要求响应式设计适配不同屏幕尺寸
7、使用组件化开发各模块独立封装
8、遵循微信小程序设计规范
```

View File

@@ -31,6 +31,10 @@
background-color: #ff3d60;
}
.bg-2D91FF {
background-color: #2d91ff;
}
.bg-liner {
background: linear-gradient(205deg, #8ae3fc 0%, rgba(138, 227, 252, 0) 20%),
linear-gradient(155deg, #fef7e1 0%, rgba(254, 247, 225, 0) 20%),

View File

@@ -27,6 +27,10 @@
line-height: 24px;
}
.line-height-30 {
line-height: 30px;
}
.line-height-32 {
line-height: 32px;
}