feat(order): 重构我的订单页面匹配蓝湖设计稿

- 前端:按蓝湖设计重写订单列表页,胶囊标签筛选栏(全部/已测评/待测评/已退款)
- 前端:卡片布局改为逐行展示(订单日期、编号、项目、金额、状态+操作按钮)
- 后端:OrderItemDto 新增 AssessmentStatus 和 DisplayStatusText 字段
- 后端:GetListAsync 查询测评记录状态,计算综合显示状态文本
- 后端:新增 GetDisplayStatusText 方法,结合订单状态和测评状态生成展示文本
- 邀请码订单(Status=2, PayAmount=0)正常显示在订单列表中
This commit is contained in:
zpc 2026-02-23 00:47:13 +08:00
parent 143a8fa5f2
commit f92b9db74e
4 changed files with 240 additions and 259 deletions

View File

@ -85,14 +85,14 @@ public class OrderService : IOrderService
})
.ToListAsync();
// 获取测评订单关联的测评记录ID
// 获取测评订单关联的测评记录信息(ID + 状态)
if (orders.Any(o => o.OrderType == 1))
{
var orderIds = orders.Where(o => o.OrderType == 1).Select(o => o.Id).ToList();
var assessmentRecords = await _dbContext.AssessmentRecords
.AsNoTracking()
.Where(r => orderIds.Contains(r.OrderId) && !r.IsDeleted)
.Select(r => new { r.OrderId, r.Id })
.Select(r => new { r.OrderId, r.Id, r.Status })
.ToListAsync();
foreach (var order in orders.Where(o => o.OrderType == 1))
@ -101,10 +101,17 @@ public class OrderService : IOrderService
if (record != null)
{
order.AssessmentRecordId = record.Id;
order.AssessmentStatus = record.Status;
}
}
}
// 计算综合显示状态文本
foreach (var order in orders)
{
order.DisplayStatusText = GetDisplayStatusText(order.Status, order.AssessmentStatus);
}
_logger.LogDebug("获取到 {Count} 条订单记录,总数: {Total}", orders.Count, total);
return PagedResult<OrderItemDto>.Create(orders, total, page, pageSize);
@ -635,6 +642,36 @@ public class OrderService : IOrderService
};
}
/// <summary>
/// 获取综合显示状态文本(结合订单状态和测评记录状态)
/// </summary>
/// <param name="orderStatus">订单状态</param>
/// <param name="assessmentStatus">测评记录状态可为null</param>
/// <returns>用于前端展示的状态文本</returns>
private static string GetDisplayStatusText(int orderStatus, int? assessmentStatus)
{
// 退款相关状态优先
if (orderStatus == 4) return "退款中";
if (orderStatus == 5) return "已退款";
if (orderStatus == 6) return "已取消";
if (orderStatus == 1) return "待支付";
// 已支付或已完成的订单,根据测评记录状态显示
if (assessmentStatus.HasValue)
{
return assessmentStatus.Value switch
{
1 => "待测评",
2 => "测评中",
3 => "测评生成中",
4 => "已测评",
_ => GetOrderStatusText(orderStatus)
};
}
return GetOrderStatusText(orderStatus);
}
/// <summary>
/// 获取测评状态文本
/// </summary>

View File

@ -1,5 +1,8 @@
namespace MiAssessment.Model.Models.Order;
/// <summary>
/// 订单列表项数据传输对象
/// </summary>
/// <summary>
/// 订单列表项数据传输对象
/// </summary>
@ -49,4 +52,15 @@ public class OrderItemDto
/// 关联的测评记录ID测评订单时有值
/// </summary>
public long? AssessmentRecordId { get; set; }
/// <summary>
/// 测评记录状态1待测评 2测评中 3生成中 4已完成测评订单时有值
/// </summary>
public int? AssessmentStatus { get; set; }
/// <summary>
/// 综合显示状态文本(结合订单状态和测评状态)
/// </summary>
public string DisplayStatusText { get; set; } = null!;
}

View File

@ -10,6 +10,7 @@ import { get, post } from './request'
* @param {number} [params.page] - 页码
* @param {number} [params.pageSize] - 每页数量
* @param {number} [params.status] - 订单状态
* @param {number} [params.orderType] - 订单类型1测评订单 2规划订单
* @returns {Promise<Object>}
*/
export function getOrderList(params = {}) {

View File

@ -1,9 +1,10 @@
<script setup>
/**
* 我的订单页面
* 展示用户所有已支付或退款的订单
* 展示用户订单列表支持按状态筛选
* 设计稿参考蓝湖我的订单
*/
import { ref, computed, onMounted } from 'vue'
import { ref, computed } from 'vue'
import { onShow, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/user.js'
import { useAuth } from '@/composables/useAuth.js'
@ -14,75 +15,46 @@ import Loading from '@/components/Loading/index.vue'
const userStore = useUserStore()
const { checkLogin } = useAuth()
//
const ORDER_STATUS = {
PENDING_PAYMENT: 1, //
PAID: 2, //
COMPLETED: 3, //
REFUNDING: 4, // 退
REFUNDED: 5, // 退
CANCELLED: 6, //
GENERATING: 7, //
PENDING_ASSESSMENT: 8 //
}
//
const tabs = [
{ label: '全部', value: 'all' },
{ label: '已测评', value: 'completed' },
{ label: '待测评', value: 'pending' },
{ label: '已退款', value: 'refunded' }
]
//
const activeTab = ref('all')
const loading = ref(false)
const refreshing = ref(false)
const orderList = ref([])
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const noMore = ref(false)
//
const isEmpty = computed(() => !loading.value && orderList.value.length === 0)
const hasMore = computed(() => orderList.value.length < total.value)
/**
* 根据当前tab过滤订单列表
* 前端过滤tab分类基于 displayStatusText 的混合状态
*/
const filteredList = computed(() => {
if (activeTab.value === 'all') return orderList.value
return orderList.value.filter(order => {
const dst = order.displayStatusText
if (activeTab.value === 'completed') return dst === '已测评'
if (activeTab.value === 'pending') return dst === '待测评' || dst === '测评中'
if (activeTab.value === 'refunded') return dst === '已退款' || dst === '退款中'
return true
})
})
/**
* 获取订单状态文本
* 获取显示状态的样式类
*/
function getStatusText(status) {
const statusMap = {
[ORDER_STATUS.PENDING_PAYMENT]: '待支付',
[ORDER_STATUS.PAID]: '已支付',
[ORDER_STATUS.COMPLETED]: '已测评',
[ORDER_STATUS.REFUNDING]: '退款中',
[ORDER_STATUS.REFUNDED]: '已退款',
[ORDER_STATUS.CANCELLED]: '已取消',
[ORDER_STATUS.GENERATING]: '测评生成中',
[ORDER_STATUS.PENDING_ASSESSMENT]: '待测评'
}
return statusMap[status] || '未知状态'
}
/**
* 获取订单状态样式类
*/
function getStatusClass(status) {
const classMap = {
[ORDER_STATUS.PENDING_PAYMENT]: 'status-pending',
[ORDER_STATUS.PAID]: 'status-paid',
[ORDER_STATUS.COMPLETED]: 'status-completed',
[ORDER_STATUS.REFUNDING]: 'status-refunding',
[ORDER_STATUS.REFUNDED]: 'status-refunded',
[ORDER_STATUS.CANCELLED]: 'status-cancelled',
[ORDER_STATUS.GENERATING]: 'status-generating',
[ORDER_STATUS.PENDING_ASSESSMENT]: 'status-pending-assessment'
}
return classMap[status] || ''
}
/**
* 格式化日期
*/
function formatDate(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
function getStatusClass(order) {
const dst = order.displayStatusText
if (dst === '已测评' || dst === '待测评') return 'status-green'
if (dst === '测评生成中' || dst === '退款中' || dst === '已退款') return 'status-red'
return 'status-gray'
}
/**
@ -93,67 +65,60 @@ function formatAmount(amount) {
return Number(amount).toFixed(2)
}
/**
* 切换标签
*/
function switchTab(tab) {
if (activeTab.value === tab.value) return
activeTab.value = tab.value
}
/**
* 加载订单列表
*/
async function loadOrderList(isRefresh = false) {
if (loading.value) return
if (isRefresh) {
page.value = 1
noMore.value = false
}
loading.value = true
try {
const res = await getOrderList({
page: page.value,
pageSize: pageSize.value
})
if (res.code === 0 && res.data) {
const list = res.data.list || []
total.value = res.data.total || 0
if (isRefresh) {
orderList.value = list
} else {
orderList.value = [...orderList.value, ...list]
}
//
noMore.value = orderList.value.length >= total.value
} else {
uni.showToast({
title: res.message || '获取订单失败',
icon: 'none'
})
uni.showToast({ title: res.message || '获取订单失败', icon: 'none' })
}
} catch (error) {
console.error('获取订单列表失败:', error)
uni.showToast({
title: '网络错误,请重试',
icon: 'none'
})
uni.showToast({ title: '网络错误,请重试', icon: 'none' })
} finally {
loading.value = false
refreshing.value = false
uni.stopPullDownRefresh()
}
}
/**
* 下拉刷新
*/
onPullDownRefresh(() => {
refreshing.value = true
loadOrderList(true)
})
/**
* 上拉加载更多
*/
onReachBottom(() => {
if (!noMore.value && !loading.value) {
page.value++
@ -166,7 +131,7 @@ onReachBottom(() => {
*/
function viewResult(order) {
uni.navigateTo({
url: `/pages/assessment/result/index?recordId=${order.recordId}`
url: `/pages/assessment/result/index?recordId=${order.assessmentRecordId}`
})
}
@ -175,109 +140,116 @@ function viewResult(order) {
*/
function startAssessment(order) {
uni.navigateTo({
url: `/pages/assessment/questions/index?orderId=${order.id}&typeId=${order.productId}`
url: `/pages/assessment/questions/index?recordId=${order.assessmentRecordId}`
})
}
/**
* 判断是否显示查看结果按钮
*/
function showViewResultBtn(status) {
return status === ORDER_STATUS.COMPLETED
function showViewResultBtn(order) {
return order.displayStatusText === '已测评'
}
/**
* 判断是否显示开始测评按钮
*/
function showStartBtn(status) {
return status === ORDER_STATUS.PENDING_ASSESSMENT
function showStartBtn(order) {
return order.displayStatusText === '待测评'
}
/**
* 页面显示时检查登录状态并加载数据
*/
onShow(() => {
userStore.restoreFromStorage()
if (checkLogin()) {
loadOrderList(true)
}
})
/**
* 页面加载
*/
onMounted(() => {
userStore.restoreFromStorage()
})
</script>
<template>
<view class="order-list-page">
<!-- 标签筛选栏 - 胶囊标签样式 -->
<view class="tab-bar">
<view
class="tab-item"
:class="{ active: activeTab === tab.value }"
v-for="tab in tabs"
:key="tab.value"
@click="switchTab(tab)"
>
{{ tab.label }}
</view>
</view>
<!-- 页面加载中 -->
<Loading type="page" :loading="loading && orderList.length === 0" />
<!-- 订单列表 -->
<view class="order-list" v-if="!isEmpty">
<view
class="order-card"
v-for="order in orderList"
<view class="order-list" v-if="filteredList.length > 0">
<view
class="order-card"
v-for="order in filteredList"
:key="order.id"
>
<!-- 订单头部 -->
<view class="order-header">
<view class="order-date">{{ formatDate(order.createTime) }}</view>
<view class="order-status" :class="getStatusClass(order.status)">
{{ getStatusText(order.status) }}
</view>
<!-- 订单日期 -->
<view class="info-row">
<text class="info-label">订单日期</text>
<text class="info-value">{{ order.createTime }}</text>
</view>
<!-- 订单内容 -->
<view class="order-content">
<view class="order-info-row">
<text class="info-label">订单编号</text>
<text class="info-value">{{ order.orderNo || '--' }}</text>
</view>
<view class="order-info-row">
<text class="info-label">测评项目</text>
<text class="info-value">{{ order.productName || '--' }}</text>
</view>
<view class="order-info-row">
<text class="info-label">测评金额</text>
<text class="info-value amount">¥{{ formatAmount(order.amount) }}</text>
</view>
<!-- 订单编号 -->
<view class="info-row">
<text class="info-label">订单编号</text>
<text class="info-value">{{ order.orderNo || '--' }}</text>
</view>
<!-- 订单操作按钮 -->
<view class="order-actions" v-if="showViewResultBtn(order.status) || showStartBtn(order.status)">
<view
class="action-btn primary"
v-if="showViewResultBtn(order.status)"
@click="viewResult(order)"
>
查看测评结果
<!-- 测评项目 -->
<view class="info-row">
<text class="info-label">测评项目</text>
<text class="info-value">{{ order.productName || '--' }}</text>
</view>
<!-- 测评金额 -->
<view class="info-row">
<text class="info-label">测评金额</text>
<text class="info-value amount">¥{{ formatAmount(order.amount) }}</text>
</view>
<!-- 订单状态 + 操作按钮 -->
<view class="info-row status-row">
<view class="status-wrap">
<text class="info-label">订单状态</text>
<text class="status-text" :class="getStatusClass(order)">
{{ order.displayStatusText }}
</text>
</view>
<view
class="action-btn primary"
v-if="showStartBtn(order.status)"
@click="startAssessment(order)"
>
开始测评
<view class="action-wrap">
<view
class="action-btn"
v-if="showViewResultBtn(order)"
@click="viewResult(order)"
>
查看测评结果
</view>
<view
class="action-btn"
v-if="showStartBtn(order)"
@click="startAssessment(order)"
>
开始测评
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<Loading
type="more"
:loading="loading && orderList.length > 0"
<Loading
type="more"
:loading="loading && orderList.length > 0"
:noMore="noMore"
noMoreText="没有更多订单了"
/>
</view>
<!-- 空状态 -->
<Empty
v-if="isEmpty"
<Empty
v-if="!loading && filteredList.length === 0"
text="暂无订单记录"
:showButton="false"
/>
@ -290,147 +262,104 @@ onMounted(() => {
.order-list-page {
min-height: 100vh;
background-color: $bg-color;
padding: $spacing-lg;
padding-bottom: calc(#{$spacing-lg} + env(safe-area-inset-bottom));
}
// -
.tab-bar {
display: flex;
align-items: center;
padding: $spacing-lg $spacing-lg $spacing-md;
background-color: $bg-white;
gap: $spacing-md;
.tab-item {
padding: 12rpx 32rpx;
font-size: $font-size-md;
color: $text-secondary;
border: 2rpx solid $border-color;
border-radius: $border-radius-round;
white-space: nowrap;
&.active {
color: #FF6B00;
border-color: #FF6B00;
background-color: rgba(255, 107, 0, 0.05);
}
}
}
//
.order-list {
padding: $spacing-md $spacing-lg;
.order-card {
background-color: $bg-white;
border-radius: $border-radius-lg;
padding: $spacing-lg;
margin-bottom: $spacing-lg;
overflow: hidden;
//
.order-header {
.info-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-md $spacing-lg;
border-bottom: 1rpx solid $border-light;
.order-date {
padding: $spacing-xs 0;
.info-label {
font-size: $font-size-md;
color: $text-color;
font-weight: $font-weight-medium;
}
.order-status {
font-size: $font-size-sm;
padding: 4rpx 16rpx;
border-radius: $border-radius-sm;
// - 绿
&.status-completed {
color: $success-color;
background-color: rgba(82, 196, 26, 0.1);
}
// -
&.status-generating {
color: $primary-color;
background-color: rgba(74, 144, 226, 0.1);
}
// -
&.status-pending-assessment {
color: $warning-color;
background-color: rgba(250, 173, 20, 0.1);
}
// -
&.status-paid {
color: $primary-color;
background-color: rgba(74, 144, 226, 0.1);
}
// -
&.status-pending {
color: $text-placeholder;
background-color: rgba(153, 153, 153, 0.1);
}
// 退 -
&.status-refunding {
color: $warning-color;
background-color: rgba(250, 173, 20, 0.1);
}
// 退 -
&.status-refunded {
color: $text-secondary;
background-color: rgba(102, 102, 102, 0.1);
}
// -
&.status-cancelled {
color: $text-placeholder;
background-color: rgba(153, 153, 153, 0.1);
.info-value {
font-size: $font-size-md;
color: $text-color;
&.amount {
color: $error-color;
}
}
}
//
.order-content {
padding: $spacing-md $spacing-lg;
.order-info-row {
// +
.status-row {
margin-top: $spacing-xs;
.status-wrap {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-xs 0;
.info-label {
gap: $spacing-sm;
.status-text {
font-size: $font-size-md;
color: $text-secondary;
}
.info-value {
font-size: $font-size-md;
color: $text-color;
&.amount {
font-weight: $font-weight-medium;
&.status-green {
color: $success-color;
}
&.status-red {
color: $error-color;
font-weight: $font-weight-medium;
}
&.status-gray {
color: $text-placeholder;
}
}
}
}
//
.order-actions {
display: flex;
justify-content: flex-end;
padding: $spacing-md $spacing-lg;
border-top: 1rpx solid $border-light;
.action-btn {
min-width: 180rpx;
height: 64rpx;
line-height: 64rpx;
text-align: center;
font-size: $font-size-sm;
border-radius: 32rpx;
margin-left: $spacing-md;
&.primary {
background-color: $primary-color;
.action-wrap {
.action-btn {
padding: 12rpx 32rpx;
font-size: $font-size-sm;
color: $text-white;
background-color: $success-color;
border-radius: $border-radius-md;
&:active {
opacity: 0.8;
}
}
&.outline {
border: 1rpx solid $primary-color;
color: $primary-color;
background-color: transparent;
&:active {
background-color: rgba(74, 144, 226, 0.1);
}
}
}
}
}