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(); .ToListAsync();
// 获取测评订单关联的测评记录ID // 获取测评订单关联的测评记录信息(ID + 状态)
if (orders.Any(o => o.OrderType == 1)) if (orders.Any(o => o.OrderType == 1))
{ {
var orderIds = orders.Where(o => o.OrderType == 1).Select(o => o.Id).ToList(); var orderIds = orders.Where(o => o.OrderType == 1).Select(o => o.Id).ToList();
var assessmentRecords = await _dbContext.AssessmentRecords var assessmentRecords = await _dbContext.AssessmentRecords
.AsNoTracking() .AsNoTracking()
.Where(r => orderIds.Contains(r.OrderId) && !r.IsDeleted) .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(); .ToListAsync();
foreach (var order in orders.Where(o => o.OrderType == 1)) foreach (var order in orders.Where(o => o.OrderType == 1))
@ -101,10 +101,17 @@ public class OrderService : IOrderService
if (record != null) if (record != null)
{ {
order.AssessmentRecordId = record.Id; 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); _logger.LogDebug("获取到 {Count} 条订单记录,总数: {Total}", orders.Count, total);
return PagedResult<OrderItemDto>.Create(orders, total, page, pageSize); 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>
/// 获取测评状态文本 /// 获取测评状态文本
/// </summary> /// </summary>

View File

@ -1,5 +1,8 @@
namespace MiAssessment.Model.Models.Order; namespace MiAssessment.Model.Models.Order;
/// <summary>
/// 订单列表项数据传输对象
/// </summary>
/// <summary> /// <summary>
/// 订单列表项数据传输对象 /// 订单列表项数据传输对象
/// </summary> /// </summary>
@ -49,4 +52,15 @@ public class OrderItemDto
/// 关联的测评记录ID测评订单时有值 /// 关联的测评记录ID测评订单时有值
/// </summary> /// </summary>
public long? AssessmentRecordId { get; set; } 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.page] - 页码
* @param {number} [params.pageSize] - 每页数量 * @param {number} [params.pageSize] - 每页数量
* @param {number} [params.status] - 订单状态 * @param {number} [params.status] - 订单状态
* @param {number} [params.orderType] - 订单类型1测评订单 2规划订单
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
export function getOrderList(params = {}) { export function getOrderList(params = {}) {

View File

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