Files
2025-08-26 16:17:24 +08:00

749 lines
18 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page-container">
<!-- 页面标题 -->
<view class="page-title">订单列表</view>
<!-- 搜索区域 - 仅保留时间查询 -->
<view class="search-box">
<view class="search-item">
<text class="label">订单日期</text>
<view class="date-picker" @click="showDatePicker = true">
<text>{{ dateRangeText || '选择日期范围' }}</text>
<text class="arrow"></text>
</view>
</view>
<view class="search-buttons">
<button class="search-btn" @click="handleQuery">查询</button>
<button class="reset-btn" @click="resetQuery">重置</button>
</view>
</view>
<!-- 订单列表 -->
<view class="order-list">
<!-- 加载中 -->
<view class="loading" v-if="loading">
<view class="spinner"></view>
<text>加载中...</text>
</view>
<!-- 错误提示 -->
<view class="error" v-if="errorMsg">
<text>{{ errorMsg }}</text>
</view>
<!-- 空状态 -->
<view class="empty" v-if="!loading && !errorMsg && orderList.length === 0">
<text>该时间段内暂无订单数据</text>
</view>
<!-- 列表标题 -->
<view class="list-header" v-if="!loading && !errorMsg && orderList.length > 0">
<view class="header-item" style="width: 35%;">订单号</view>
<!-- <view class="header-item" style="width: 12%;">顾客姓名</view> -->
<!-- <view class="header-item" style="width: 8%;">桌号</view> -->
<view class="header-item" style="width: 15%;">订单类型</view>
<view class="header-item" style="width: 15%;">订单状态</view>
<view class="header-item" style="width: 15%;">应收金额</view>
<view class="header-item" style="width: 15%;">实收金额</view>
<!-- <view class="header-item" style="width: 15%;">支付方式</view> -->
</view>
<!-- 列表内容 -->
<view class="list-body" v-if="!loading && !errorMsg && orderList.length > 0">
<view class="order-item" v-for="(order, index) in orderList" :key="order.id || index">
<view class="order-cell" style="width: 35%;">{{ order.id || '-' }}</view>
<!-- <view class="order-cell" style="width: 12%;">{{ order.customerName || '-' }}</view> -->
<!-- <view class="order-cell" style="width: 8%;">{{ order.tableId || '-' }}</view> -->
<view class="order-cell" style="width: 15%;">
<view class="tag" :class="getTypeClass(order.orderType)">{{ getOrderType(order.orderType) }}</view>
</view>
<view class="order-cell" style="width: 15%;">
<view class="tag" :class="getStatusClass(order.orderStatus)">{{ getOrderStatus(order.orderStatus) }}</view>
</view>
<view class="order-cell" style="width: 15%;">¥{{ formatMoney(order.totalAmount) }}</view>
<view class="order-cell" style="width: 15%;">¥{{ formatMoney(order.actualReceivedAmount) }}</view>
<!-- <view class="order-cell" style="width: 15%;">{{ getPaymentMethod(order.paymentMethod) || '-' }}</view> -->
</view>
</view>
</view>
<!-- 分页 -->
<view class="pagination" v-if="!loading && !errorMsg && total > 0">
<view class="pagination-info">
<text> {{ total }} {{ queryParams.pageNum }}/{{ totalPages }} </text>
</view>
<view class="pagination-buttons">
<button
class="page-btn"
@click="prevPage"
:disabled="queryParams.pageNum <= 1"
>上一页</button>
<button
class="page-btn"
@click="nextPage"
:disabled="queryParams.pageNum >= totalPages"
>下一页</button>
</view>
</view>
<!-- 日期选择器弹窗 -->
<view class="picker-mask" v-if="showDatePicker" @click="showDatePicker = false"></view>
<view class="picker-popup" v-if="showDatePicker">
<view class="picker-title">
<text @click="showDatePicker = false">取消</text>
<text>选择日期范围</text>
<text @click="confirmDate">确定</text>
</view>
<picker-view
class="date-picker-view"
:value="datePickerValue"
@change="onDateChange"
>
<picker-view-column>
<view v-for="(year, index) in years" :key="index">{{ year }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(month, index) in months" :key="index">{{ month }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(day, index) in days" :key="index">{{ day }}</view>
</picker-view-column>
<picker-view-column>
<view></view>
</picker-view-column>
<picker-view-column>
<view v-for="(year, index) in years" :key="index">{{ year }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(month, index) in months" :key="index">{{ month }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(day, index) in days" :key="index">{{ day }}</view>
</picker-view-column>
</picker-view>
</view>
</view>
</template>
<script>
import orderApi from '@/common/http/eatery/order.js'
export default {
data() {
return {
// 订单列表数据
orderList: [],
// 加载状态
loading: false,
// 错误信息
errorMsg: '',
// 总条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
startDate: '',
endDate: ''
},
// 支付方式列表
paymentMethods: [],
// 日期范围文本
dateRangeText: '',
// 日期选择器显示状态
showDatePicker: false,
// 日期选择器数据
years: [],
months: [],
days: [],
// 日期选择器值
datePickerValue: [],
// 开始日期和结束日期
startDateObj: null,
endDateObj: null,
// 订单类型映射
orderTypeMap: {
'1': '堂食',
'2': '外卖'
},
// 订单状态映射
orderStatusMap: {
'1': '待结算',
'2': '已结算',
'3': '已取消'
}
}
},
computed: {
// 总页数
totalPages() {
return Math.ceil(this.total / this.queryParams.pageSize)
}
},
onLoad() {
// 初始化日期数据
this.initDateData()
// 获取支付方式
this.getPaymentMethods()
// 默认加载最近7天数据
this.handleQuery()
},
methods: {
/**
* 初始化日期选择器数据
*/
initDateData() {
const now = new Date()
const year = now.getFullYear()
// 生成年份数据近3年
this.years = []
for (let i = year - 2; i <= year; i++) {
this.years.push(i)
}
// 生成月份数据
this.months = []
for (let i = 1; i <= 12; i++) {
this.months.push(i)
}
// 生成日期数据
this.days = []
for (let i = 1; i <= 31; i++) {
this.days.push(i)
}
// 设置默认日期最近7天
const start = new Date()
start.setDate(start.getDate() - 7)
this.startDateObj = start
this.endDateObj = now
// 设置日期选择器初始值
this.datePickerValue = [
this.years.indexOf(start.getFullYear()),
start.getMonth(),
start.getDate() - 1,
0, // 分隔符
this.years.indexOf(now.getFullYear()),
now.getMonth(),
now.getDate() - 1
]
// 更新日期文本
this.updateDateRangeText()
},
/**
* 更新日期范围文本
*/
updateDateRangeText() {
if (this.startDateObj && this.endDateObj) {
const formatDate = (date) => {
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
}
this.dateRangeText = `${formatDate(this.startDateObj)}${formatDate(this.endDateObj)}`
this.queryParams.startDate = formatDate(this.startDateObj)
this.queryParams.endDate = formatDate(this.endDateObj)
} else {
this.dateRangeText = ''
this.queryParams.startDate = ''
this.queryParams.endDate = ''
}
},
/**
* 日期选择变化
*/
onDateChange(e) {
const val = e.detail.value
// 解析开始日期
const startYear = this.years[val[0]]
const startMonth = this.months[val[1]]
const startDay = this.days[val[2]]
// 解析结束日期
const endYear = this.years[val[4]]
const endMonth = this.months[val[5]]
const endDay = this.days[val[6]]
// 更新日期对象
this.startDateObj = new Date(startYear, startMonth - 1, startDay)
this.endDateObj = new Date(endYear, endMonth - 1, endDay)
},
/**
* 确认日期选择
*/
confirmDate() {
// 确保开始日期不晚于结束日期
if (this.startDateObj > this.endDateObj) {
uni.showToast({ title: '开始日期不能晚于结束日期', icon: 'none' })
return
}
this.updateDateRangeText()
this.showDatePicker = false
},
/**
* 获取支付方式列表
*/
getPaymentMethods() {
orderApi.getPaymentMethods().then(res => {
if (res.data && Array.isArray(res.data)) {
this.paymentMethods = res.data.map(item => ({
value: item.dictValue,
text: item.dictLabel
}))
}
}).catch(err => {
console.error('获取支付方式失败:', err)
// 不阻断主流程,仅在控制台输出错误
})
},
/**
* 分页查询订单 - 修复数据不显示问题
*/
getOrderPage() {
// 重置错误信息
this.errorMsg = ''
// 显示加载状态
this.loading = true
// 检查日期参数是否完整
if (!this.queryParams.startDate || !this.queryParams.endDate) {
this.loading = false
this.errorMsg = '请选择完整的日期范围'
return
}
// 调用API查询数据
orderApi.getOrderPage(this.queryParams)
.then(res => {
// 确保返回数据格式正确
if (res && typeof res === 'object') {
// 适配不同的API返回格式
const data = res.data || res
this.orderList = Array.isArray(data.rows) ? data.rows : []
this.total = typeof data.total === 'number' ? data.total : 0
} else {
this.orderList = []
this.total = 0
this.errorMsg = '数据格式错误'
}
})
.catch(err => {
console.error('获取订单失败:', err)
this.orderList = []
this.total = 0
this.errorMsg = '获取订单失败,请重试'
})
.finally(() => {
// 无论成功失败都关闭加载状态
this.loading = false
})
},
/**
* 查询按钮触发
*/
handleQuery() {
this.queryParams.pageNum = 1
this.getOrderPage()
},
/**
* 重置查询条件
*/
resetQuery() {
// 重置为最近7天
const now = new Date()
const start = new Date()
start.setDate(start.getDate() - 7)
this.startDateObj = start
this.endDateObj = now
// 更新日期选择器值
this.datePickerValue = [
this.years.indexOf(start.getFullYear()),
start.getMonth(),
start.getDate() - 1,
0, // 分隔符
this.years.indexOf(now.getFullYear()),
now.getMonth(),
now.getDate() - 1
]
this.updateDateRangeText()
this.queryParams.pageNum = 1
this.getOrderPage()
},
/**
* 上一页
*/
prevPage() {
if (this.queryParams.pageNum > 1) {
this.queryParams.pageNum--
this.getOrderPage()
}
},
/**
* 下一页
*/
nextPage() {
if (this.queryParams.pageNum < this.totalPages) {
this.queryParams.pageNum++
this.getOrderPage()
}
},
/**
* 获取订单类型文本
*/
getOrderType(type) {
return this.orderTypeMap[type] || '未知'
},
/**
* 获取订单类型样式类
*/
getTypeClass(type) {
const classMap = {
'1': 'type-dining',
'2': 'type-takeout'
}
return classMap[type] || 'type-default'
},
/**
* 获取订单状态文本
*/
getOrderStatus(status) {
return this.orderStatusMap[status] || '未知'
},
/**
* 获取订单状态样式类
*/
getStatusClass(status) {
const classMap = {
'1': 'status-pending',
'2': 'status-settled',
'3': 'status-canceled'
}
return classMap[status] || 'status-default'
},
/**
* 获取支付方式文本
*/
getPaymentMethod(method) {
const methodItem = this.paymentMethods.find(item => item.value === method)
return methodItem ? methodItem.text : '未知'
},
/**
* 格式化金额
*/
formatMoney(amount) {
// 处理可能的null/undefined值
if (amount === null || amount === undefined) return '0.00'
// 转换为数字并保留两位小数
return parseFloat(amount).toFixed(2)
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
padding: 20rpx;
background-color: #f5f7fa;
min-height: 100vh;
box-sizing: border-box;
font-size: 28rpx;
}
.page-title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin: 30rpx 0;
color: #333;
}
.search-box {
background-color: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.search-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.label {
width: 140rpx;
color: #666;
}
.date-picker {
flex: 1;
height: 70rpx;
border: 1px solid #eee;
border-radius: 8rpx;
padding: 0 15rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
}
.arrow {
color: #999;
font-size: 24rpx;
}
.search-buttons {
display: flex;
gap: 20rpx;
}
.search-btn {
flex: 1;
height: 70rpx;
line-height: 70rpx;
background-color: #03AE80;
color: #fff;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.reset-btn {
flex: 1;
height: 70rpx;
line-height: 70rpx;
background-color: #f5f5f5;
color: #666;
border-radius: 8rpx;
font-size: 28rpx;
border: 1px solid #eee;
}
.order-list {
background-color: #fff;
border-radius: 16rpx;
padding: 20rpx;
overflow-x: auto;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
padding: 100rpx 0;
}
.spinner {
width: 50rpx;
height: 50rpx;
border: 5rpx solid #eee;
border-top-color: #03AE80;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.error {
text-align: center;
padding: 100rpx 0;
color: #ff3b30;
}
.empty {
text-align: center;
padding: 100rpx 0;
color: #999;
}
.list-header {
display: flex;
background-color: #f5f5f5;
border-radius: 8rpx 8rpx 0 0;
overflow: hidden;
font-weight: bold;
}
.header-item {
padding: 15rpx 10rpx;
text-align: center;
border-right: 1px solid #eee;
box-sizing: border-box;
}
.header-item:last-child {
border-right: none;
}
.list-body {
border: 1px solid #eee;
border-top: none;
border-radius: 0 0 8rpx 8rpx;
overflow: hidden;
}
.order-item {
display: flex;
border-bottom: 1px solid #eee;
}
.order-item:last-child {
border-bottom: none;
}
.order-cell {
padding: 15rpx 10rpx;
text-align: center;
border-right: 1px solid #eee;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.order-cell:last-child {
border-right: none;
}
.tag {
display: inline-block;
padding: 5rpx 10rpx;
border-radius: 4rpx;
font-size: 24rpx;
color: #fff;
}
.type-dining {
background-color: #007aff;
}
.type-takeout {
background-color: #4cd964;
}
.type-default {
background-color: #999;
}
.status-pending {
background-color: #ff9500;
}
.status-settled {
background-color: #4cd964;
}
.status-canceled {
background-color: #ff3b30;
}
.status-default {
background-color: #999;
}
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
padding: 10rpx 0;
}
.pagination-info {
color: #666;
font-size: 26rpx;
}
.pagination-buttons {
display: flex;
gap: 15rpx;
}
.page-btn {
padding: 0 25rpx;
height: 60rpx;
line-height: 60rpx;
background-color: #f5f5f5;
color: #666;
border-radius: 8rpx;
font-size: 26rpx;
border: 1px solid #eee;
}
.page-btn:disabled {
opacity: 0.5;
}
.picker-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 998;
}
.picker-popup {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
z-index: 999;
}
.picker-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1px solid #eee;
}
.picker-title text {
flex: 1;
text-align: center;
font-size: 30rpx;
}
.picker-title text:first-child,
.picker-title text:last-child {
color: #03AE80;
}
.date-picker-view {
width: 100%;
height: 400rpx;
}
</style>