智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1335 lines
34 KiB

<!--订单列表组件 - 独立业务组件-->
<template>
<view class="order-list-card">
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<text class="loading-icon">⏳</text>
<text class="loading-text">加载中...</text>
</view>
<!-- 订单内容 -->
<view v-else>
<!-- 操作按钮区域 -->
<view class="action-header" v-if="showAddButton && orderList.length > 0">
<view class="add-order-btn" @click.stop="openAddOrder">
<text class="add-icon">+</text>
<text class="add-text">新增订单</text>
</view>
</view>
<!-- 订单列表 -->
<scroll-view
class="order-list"
v-if="orderList.length > 0"
scroll-y="true"
:enhanced="true"
:style="{height: maxHeight}"
>
<view
class="order-item"
v-for="order in orderList"
:key="order.id"
:class="{ 'pending-payment': order.status === 'pending' }"
@click.stop="handleOrderClick(order)"
>
<view class="order-header">
<view class="order-info">
<view class="order-no">订单号:{{ order.order_no }}</view>
<view class="order-time" v-if="order.create_time">
{{ formatTime(order.create_time) }}
</view>
</view>
<view :class="['order-status', getStatusClass(order.status)]">
{{ getStatusText(order.status) }}
</view>
</view>
<!-- 订单内容 -->
<view class="order-content">
<view class="product-info" v-if="order.product_name">
<view class="product-name">{{ order.product_name }}</view>
<view class="product-specs" v-if="order.product_specs">
{{ order.product_specs }}
</view>
</view>
<view class="order-details">
<!-- 价格信息 -->
<view class="detail-row">
<text class="detail-label">订单金额:</text>
<text class="detail-value price">¥{{ order.total_amount }}</text>
</view>
<view class="detail-row">
<text class="detail-label">已付金额:</text>
<text class="detail-value paid">¥{{ order.paid_amount }}</text>
</view>
<view class="detail-row">
<text class="detail-label">未付金额:</text>
<text class="detail-value unpaid">¥{{ order.unpaid_amount }}</text>
</view>
<!-- 其他信息 -->
<view class="detail-row" v-if="order.payment_method">
<text class="detail-label">支付方式:</text>
<text class="detail-value">{{ order.payment_method }}</text>
</view>
<view class="detail-row" v-if="order.salesperson_name">
<text class="detail-label">销售顾问:</text>
<text class="detail-value">{{ order.salesperson_name }}</text>
</view>
<view class="detail-row" v-if="order.course_count">
<text class="detail-label">课时数量:</text>
<text class="detail-value">{{ order.course_count }}节</text>
</view>
</view>
<!-- 备注信息 -->
<view class="order-remark" v-if="order.remark">
<view class="remark-label">备注:</view>
<view class="remark-content">{{ order.remark }}</view>
</view>
</view>
</view>
</scroll-view>
<!-- 空状态 -->
<view class="empty-state" v-else>
<view class="empty-icon">📋</view>
<view class="empty-text">暂无订单记录</view>
<view class="empty-tip">客户还未产生任何订单</view>
<view class="empty-add-btn" v-if="showAddButton" @click.stop="openAddOrder">
<text>新增订单</text>
</view>
</view>
</view>
<!-- 新增订单弹窗 -->
<uni-popup ref="orderFormPopup" type="bottom">
<OrderFormPopup
:visible="showOrderForm"
:student-info="studentInfo"
:resource-id="resourceId"
@cancel="closeOrderForm"
@confirm="handleOrderCreated"
/>
</uni-popup>
<!-- 二维码支付弹窗 -->
<uni-popup ref="qrCodePopup" type="center" @close="closeQRCodeModal">
<QRCodePaymentDialog
v-if="qrCodePaymentData"
:payment-data="qrCodePaymentData"
@close="closeQRCodeModal"
@confirm="confirmQRCodePayment"
/>
</uni-popup>
<!-- 支付凭证上传弹窗 -->
<uni-popup ref="paymentVoucherPopup" type="bottom" :safe-area="true" :z-index="9998">
<PaymentVoucherPopup
v-if="showPaymentVoucherPopup"
:order-info="currentOrder"
@cancel="closePaymentVoucherPopup"
@confirm="confirmPaymentVoucher"
/>
</uni-popup>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
import dictUtilSimple from '@/common/dictUtilSimple.js'
import OrderFormPopup from '@/components/order-form-popup/index.vue'
import QRCodePaymentDialog from './qrcode-payment-dialog.vue'
export default {
name: 'OrderListCard',
components: {
OrderFormPopup,
QRCodePaymentDialog,
PaymentVoucherPopup: () => import('./payment-voucher-popup.vue')
},
props: {
// 学员ID
studentId: {
type: [Number, String],
default: null
},
// 资源ID
resourceId: {
type: [Number, String],
default: null
},
// 是否自动加载数据
autoLoad: {
type: Boolean,
default: true
},
// 是否显示新增按钮
showAddButton: {
type: Boolean,
default: true
},
// 列表最大高度
maxHeight: {
type: String,
default: '70vh'
}
},
data() {
return {
// 订单数据
orderList: [],
loading: false,
// 弹窗状态
showOrderForm: false,
showQRCodeModal: false,
showPaymentVoucherPopup: false,
// 当前操作的订单
currentOrder: null,
// 二维码支付轮询定时器
paymentPollingTimer: null,
// 二维码支付数据
qrCodePaymentData: null,
// 字典数据
paymentTypeDict: [],
// 学生信息对象(用于传递给订单表单组件)
studentInfo: {}
}
},
mounted() {
// 加载字典数据
this.loadDictData()
// 构建学生信息对象
if (this.studentId) {
this.buildStudentInfo()
}
// 自动加载订单列表
if (this.autoLoad && (this.studentId || this.resourceId)) {
this.loadOrderList()
}
},
watch: {
// 监听学员ID变化,自动刷新
studentId(newVal) {
if (newVal && this.autoLoad) {
this.loadOrderList()
this.buildStudentInfo()
}
}
},
methods: {
/**
* 构建学生信息对象
* 通过员工端API获取学生详情包含 campus_id
*/
async buildStudentInfo() {
if (!this.studentId) {
this.studentInfo = {}
return
}
try {
// 使用员工端学生信息接口
const res = await apiRoute.getStudentBasicInfo({ student_id: this.studentId })
if (res.code === 1 && res.data) {
this.studentInfo = {
id: res.data.id,
name: res.data.name,
campus_id: res.data.campus_id,
gender: res.data.gender,
gender_text: res.data.gender_text,
birthday: res.data.birthday,
headimg: res.data.headimg,
emergency_contact: res.data.emergency_contact,
contact_phone: res.data.contact_phone
}
console.log('学生信息加载成功:', this.studentInfo)
} else {
console.error('获取学生信息失败:', res.msg)
// 降级方案: 使用基本信息
this.studentInfo = {
id: this.studentId,
name: '学员',
campus_id: null
}
}
} catch (error) {
console.error('获取学生信息异常:', error)
// 降级方案
this.studentInfo = {
id: this.studentId,
name: '学员',
campus_id: null
}
}
},
/**
* 加载字典数据
*/
async loadDictData() {
try {
const dictResult = await dictUtilSimple.getBatchDict(['payment_type'])
if (dictResult.payment_type && Array.isArray(dictResult.payment_type)) {
this.paymentTypeDict = dictResult.payment_type
} else {
// 使用备用数据
this.paymentTypeDict = [
{ name: '现金支付', value: 'cash' },
{ name: '扫码支付', value: 'scan_code' },
{ name: '订阅支付', value: 'subscription' },
{ name: '微信在线代付', value: 'wxpay_online' },
{ name: '客户自行付款', value: 'client_wxpay' },
{ name: '定金', value: 'deposit' }
]
}
} catch (error) {
console.error('加载支付方式字典失败:', error)
// 使用备用数据
this.paymentTypeDict = [
{ name: '现金支付', value: 'cash' },
{ name: '扫码支付', value: 'scan_code' },
{ name: '订阅支付', value: 'subscription' },
{ name: '微信在线代付', value: 'wxpay_online' },
{ name: '客户自行付款', value: 'client_wxpay' },
{ name: '定金', value: 'deposit' }
]
}
},
/**
* 加载订单列表
*/
async loadOrderList() {
if (!this.studentId && !this.resourceId) {
console.warn('OrderListCard: 缺少必要参数 studentId 或 resourceId')
return
}
this.loading = true
try {
const params = {}
if (this.studentId) {
params.student_id = this.studentId
} else if (this.resourceId) {
params.resource_id = this.resourceId
}
const res = await apiRoute.xs_orderTableList(params)
if (res.code === 1) {
this.orderList = this.processOrderData(res.data?.data || [])
this.$emit('loaded', this.orderList)
} else {
console.error('获取订单列表失败:', res.msg)
this.orderList = []
this.$emit('error', { type: 'load', message: res.msg })
}
} catch (error) {
console.error('获取订单列表异常:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
this.$emit('error', { type: 'load', error })
} finally {
this.loading = false
}
},
/**
* 处理订单数据格式
*/
processOrderData(orders) {
if (!Array.isArray(orders)) return []
return orders.map(order => ({
id: order.id,
student_id: order.student_id,
order_no: order.payment_id || `ORD${order.id}`,
product_name: order.course_id_name || '课程',
total_amount: order.order_amount || '0.00',
paid_amount: order.order_status === 'paid' ? order.order_amount : '0.00',
unpaid_amount: order.order_status === 'paid' ? '0.00' : order.order_amount,
payment_method: this.formatPaymentType(order.payment_type),
salesperson_name: order.staff_id_name || '未指定',
course_count: order.total_hours || 0,
status: this.mapOrderStatus(order.order_status),
create_time: order.created_at,
contract_id: order.contract_id,
contract_sign_id: order.contract_sign_id,
remark: order.remark || '',
_raw: order
}))
},
/**
* 格式化支付类型
*/
formatPaymentType(paymentType) {
if (!paymentType) return '未知'
const paymentItem = this.paymentTypeDict.find(item => item.value === paymentType)
if (paymentItem) return paymentItem.name
const fallbackMap = {
'cash': '现金支付',
'scan_code': '扫码支付',
'subscription': '订阅支付',
'wxpay_online': '微信在线代付',
'client_wxpay': '客户自行付款',
'deposit': '定金'
}
return fallbackMap[paymentType] || paymentType || '未知'
},
/**
* 映射订单状态
*/
mapOrderStatus(orderStatus) {
const statusMap = {
'pending': 'pending',
'paid': 'paid',
'signed': 'completed',
'completed': 'completed',
'transfer': 'partial'
}
return statusMap[orderStatus] || 'pending'
},
/**
* 订单点击处理
*/
handleOrderClick(order) {
this.currentOrder = order
if (order.status === 'pending') {
// 待支付订单,触发支付
this.handleOrderPay(order)
} else {
// 已支付订单,查看详情
this.viewOrderDetail(order)
}
},
/**
* 处理订单支付
*/
handleOrderPay(order) {
const paymentType = order._raw?.payment_type
switch(paymentType) {
case 'cash':
case 'client_wxpay':
case 'deposit':
this.processVoucherPayment(order)
break
case 'scan_code':
this.processQRCodePayment(order)
break
case 'subscription':
this.processSubscriptionPayment(order)
break
case 'wxpay_online':
this.processWechatOnlinePayment(order)
break
default:
uni.showToast({ title: '不支持的支付方式', icon: 'none' })
}
},
/**
* 上传支付凭证的支付方式处理(cash, client_wxpay, deposit)
*/
processVoucherPayment(order) {
this.currentOrder = order
this.showPaymentVoucherPopup = true
this.$refs.paymentVoucherPopup.open()
},
/**
* 二维码支付处理
*/
async processQRCodePayment(order) {
uni.showLoading({ title: '生成支付二维码...' })
try {
const res = await apiRoute.getOrderPayQrcode({
order_id: order._raw?.id || order.id
})
uni.hideLoading()
if (res.code === 1 && res.data) {
this.qrCodePaymentData = {
order: order,
qrcode: res.data.code_url,
qrcodeImage: res.data.qrcode_base64 || res.data.qrcode_url
}
this.showQRCodeModal = true
this.$refs.qrCodePopup.open()
// 开始轮询支付状态
this.startPaymentPolling(order.id)
} else {
uni.showToast({ title: res.msg || '获取支付二维码失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('获取支付二维码失败:', error)
uni.showToast({ title: '获取支付二维码失败', icon: 'none' })
}
},
/**
* 开始轮询支付状态
*/
startPaymentPolling(order_no) {
// 清除之前的定时器
this.stopPaymentPolling()
// 每3秒轮询一次
this.paymentPollingTimer = setInterval(async () => {
try {
const res = await apiRoute.checkOrderPaymentStatus({ order_no })
if (res.code === 1 && res.data) {
// 如果订单状态为已支付,停止轮询并关闭弹窗
if (res.data.order_status === 'paid') {
this.stopPaymentPolling()
this.closeQRCodeModal()
uni.showToast({ title: '支付成功', icon: 'success' })
// 刷新订单列表
await this.refresh()
// 触发支付成功事件
this.$emit('payment-success', this.currentOrder)
}
}
} catch (error) {
console.error('查询支付状态失败:', error)
}
}, 3000)
},
/**
* 停止轮询支付状态
*/
stopPaymentPolling() {
if (this.paymentPollingTimer) {
clearInterval(this.paymentPollingTimer)
this.paymentPollingTimer = null
}
},
/**
* 订阅支付处理
*/
processSubscriptionPayment(order) {
uni.showModal({
title: '订阅服务说明',
content: '每日支付超过3000笔后解锁订阅服务',
showCancel: false,
confirmText: '知道了'
})
},
/**
* 微信在线代付处理
*/
async processWechatOnlinePayment(order) {
try {
uni.showLoading({ title: '正在发起支付...' })
// 获取用户的微信openid(异步方法)
const openid = await this.getWechatOpenid()
if (!openid) {
uni.hideLoading()
uni.showModal({
title: '提示',
content: '未获取到微信授权信息,请重新登录或授权',
showCancel: false,
success: () => {
// 可选:跳转到登录页面
// uni.navigateTo({ url: '/pages/student/login/login' })
}
})
return
}
console.log('发起微信支付 - 订单ID:', order._raw?.id || order.id)
console.log('发起微信支付 - OpenID:', openid.substring(0, 10) + '***')
// 调用后端创建微信支付订单(使用校区支付配置)
const res = await apiRoute.createWechatPayment({
order_id: order._raw?.id || order.id,
openid: openid
})
uni.hideLoading()
if (res.code !== 1 || !res.data) {
console.error('创建支付订单失败:', res)
uni.showToast({ title: res.msg || '发起支付失败', icon: 'none' })
return
}
console.log('支付参数:', res.data)
// 唤起微信支付
await this.callWechatPay(res.data)
// 支付成功后刷新订单列表
uni.showToast({ title: '支付成功', icon: 'success' })
await this.refresh()
this.$emit('payment-success', order)
} catch (error) {
uni.hideLoading()
console.error('微信支付异常:', error)
if (error.message?.includes('cancel') || error.message?.includes('取消')) {
uni.showToast({ title: '已取消支付', icon: 'none' })
} else {
uni.showToast({ title: error.message || '支付失败', icon: 'none' })
}
}
},
/**
* 获取微信openid
* @returns {Promise<string>} 微信openid
*/
async getWechatOpenid() {
try {
// 首先尝试从本地存储获取
const userInfo = uni.getStorageSync('userInfo')
let openid = ''
// #ifdef MP-WEIXIN
openid = userInfo?.weapp_openid || userInfo?.openid || ''
if (openid) {
console.log('本地获取小程序openid成功')
return openid
}
console.log('本地无openid,使用 uni.login 获取...')
// 调用 uni.login 获取 code
const loginRes = await new Promise((resolve, reject) => {
uni.login({
success: (res) => resolve(res),
fail: (err) => reject(err)
})
})
if (!loginRes.code) {
console.error('uni.login 获取 code 失败')
return ''
}
console.log('uni.login 获取 code 成功:', loginRes.code.substring(0, 10) + '***')
// 使用 code 调用接口换取 openid
const res = await apiRoute.getWechatOpenid({
code: loginRes.code,
type: 'miniprogram'
})
if (res.code === 1 && res.data?.openid) {
openid = res.data.openid
// 更新本地存储
if (userInfo) {
userInfo.weapp_openid = openid
uni.setStorageSync('userInfo', userInfo)
}
console.log('接口获取小程序openid成功')
return openid
} else {
console.error('接口换取openid失败:', res.msg || '未知错误')
return ''
}
// #endif
// #ifdef H5
openid = userInfo?.wx_openid || userInfo?.openid || ''
if (openid) {
console.log('本地获取公众号openid成功')
return openid
}
console.log('本地无openid,从URL获取code...')
// 如果本地没有,从URL参数获取code(公众号授权后会跳转回来带上code参数)
const urlParams = new URLSearchParams(window.location.search)
const code = urlParams.get('code')
if (code) {
console.log('从URL获取code成功,调用接口换取openid...')
const res = await apiRoute.getWechatOpenid({
code: code,
type: 'official'
})
if (res.code === 1 && res.data?.openid) {
openid = res.data.openid
// 更新本地存储
if (userInfo) {
userInfo.wx_openid = openid
uni.setStorageSync('userInfo', userInfo)
}
console.log('接口获取公众号openid成功')
return openid
} else {
console.error('接口换取openid失败:', res.msg || '未知错误')
return ''
}
} else {
console.error('URL中未找到code参数,需要进行微信授权')
// TODO: 这里可以引导用户进行微信授权
return ''
}
// #endif
console.error('无法获取openid,可能需要重新微信授权')
return ''
} catch (error) {
console.error('获取openid异常:', error)
return ''
}
},
/**
* 调用微信支付
*/
callWechatPay(payParams) {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType || 'MD5',
paySign: payParams.paySign,
success: (res) => {
console.log('微信支付成功:', res)
resolve({ code: 1, msg: '支付成功' })
},
fail: (err) => {
console.error('微信支付失败:', err)
if (err.errMsg?.includes('cancel')) {
reject(new Error('用户取消支付'))
} else {
reject(new Error('微信支付失败'))
}
}
})
// #endif
// #ifndef MP-WEIXIN
reject(new Error('当前环境不支持微信支付'))
// #endif
})
},
/**
* 更新订单支付状态
*/
async updateOrderPaymentStatus(order, status, paymentId = '') {
try {
const updateData = {
order_id: order._raw?.id || order.id,
order_status: status,
payment_id: paymentId
}
const result = await apiRoute.xs_orderTableUpdatePaymentStatus(updateData)
if (result.code === 1) {
const statusText = {
'paid': '支付成功',
'partial': '分期支付确认成功',
'cancelled': '支付已取消'
}
uni.showToast({
title: statusText[status] || '状态更新成功',
icon: 'success'
})
// 刷新订单列表
await this.refresh()
// 触发支付成功事件
this.$emit('payment-success', order)
} else {
uni.showToast({ title: result.msg || '状态更新失败', icon: 'none' })
}
} catch (error) {
console.error('更新订单状态失败:', error)
uni.showToast({ title: '状态更新失败', icon: 'none' })
this.$emit('error', { type: 'payment', error })
}
},
/**
* 关闭二维码支付弹窗
*/
closeQRCodeModal() {
// 停止轮询
this.stopPaymentPolling()
this.showQRCodeModal = false
this.qrCodePaymentData = null
this.$refs.qrCodePopup.close()
},
/**
* 确认二维码支付完成
*/
async confirmQRCodePayment() {
if (!this.qrCodePaymentData?.order) return
const order = this.qrCodePaymentData.order
uni.showModal({
title: '支付确认',
content: '请确认是否已完成扫码支付?',
success: async (res) => {
if (res.confirm) {
try {
await this.updateOrderPaymentStatus(order, 'paid', `QR${Date.now()}`)
this.closeQRCodeModal()
} catch (error) {
console.error('支付确认失败:', error)
}
}
}
})
},
/**
* 关闭支付凭证弹窗
*/
closePaymentVoucherPopup() {
this.showPaymentVoucherPopup = false
this.$refs.paymentVoucherPopup?.close()
this.$refs.paymentVoucherPopup?.reset()
},
/**
* 确认提交支付凭证
*/
async confirmPaymentVoucher(data) {
try {
uni.showLoading({ title: '提交中...' })
const res = await apiRoute.updateOrderPaymentVoucher({
order_id: data.order_id,
payment_voucher: data.payment_voucher
})
uni.hideLoading()
if (res.code === 1) {
uni.showToast({ title: '提交成功', icon: 'success' })
// 关闭弹窗
this.closePaymentVoucherPopup()
// 刷新订单列表
await this.refresh()
// 触发支付成功事件
this.$emit('payment-success', this.currentOrder)
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('提交支付凭证失败:', error)
uni.showToast({ title: '提交失败', icon: 'none' })
this.$emit('error', { type: 'payment-voucher', error })
}
},
/**
* 查看订单详情
*/
viewOrderDetail(order) {
const orderInfo = order._raw || order
const isOrderPaid = order.status === 'paid'
const detailText = `
订单号:${order.order_no}
课程:${order.product_name}
金额:¥${order.total_amount}
已付:¥${order.paid_amount}
未付:¥${order.unpaid_amount}
支付方式:${order.payment_method}
销售顾问:${order.salesperson_name}
课时数:${order.course_count}
状态:${this.getStatusText(order.status)}
创建时间:${this.formatTime(order.create_time)}
${orderInfo.paid_at ? '支付时间:' + this.formatTime(orderInfo.paid_at) : ''}
`.trim()
uni.showModal({
title: '订单详情',
content: detailText,
showCancel: isOrderPaid,
cancelText: isOrderPaid ? '知道了' : '',
confirmText: isOrderPaid ? '合同签署' : '知道了',
success: (res) => {
if (res.confirm && isOrderPaid) {
this.goToContractSign(order)
}
}
})
},
/**
* 跳转到合同签署页面
*/
goToContractSign(order) {
const studentId = order.student_id || this.studentId
const contractId = order.contract_id || order._raw?.contract_id
const contractSignId = order.contract_sign_id || order._raw?.contract_sign_id
if (!studentId) {
uni.showToast({ title: '缺少学生信息', icon: 'none' })
return
}
if (!contractId) {
this.getContractByOrder(order, studentId)
return
}
let url = `/pages-student/contracts/sign?contract_id=${contractId}&student_id=${studentId}&contract_name=${encodeURIComponent(order.product_name + '合同')}&user_role=staff`
if (contractSignId) {
url += `&contract_sign_id=${contractSignId}`
}
uni.navigateTo({ url })
},
/**
* 根据订单获取合同信息
*/
async getContractByOrder(order, studentId) {
try {
uni.showLoading({ title: '获取合同信息...' })
const res = await apiRoute.getContractByOrder({
order_id: order._raw?.id || order.id,
student_id: studentId
})
uni.hideLoading()
if (res.code === 1 && res.data) {
const contractInfo = res.data
uni.navigateTo({
url: `/pages-student/contracts/sign?contract_id=${contractInfo.contract_id}&student_id=${studentId}&contract_name=${encodeURIComponent(contractInfo.contract_name || order.product_name + '合同')}&user_role=staff`
})
} else {
uni.showToast({ title: res.msg || '未找到相关合同', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('获取合同信息失败:', error)
uni.showToast({ title: '获取合同信息失败', icon: 'none' })
}
},
/**
* 新增订单
*/
openAddOrder() {
if (!this.studentId) {
uni.showToast({ title: '缺少学员信息', icon: 'none' })
return
}
this.showOrderForm = true
this.$refs.orderFormPopup.open()
},
/**
* 关闭新增订单弹窗
*/
closeOrderForm() {
this.showOrderForm = false
this.$refs.orderFormPopup.close()
},
/**
* 订单创建成功回调
*/
async handleOrderCreated() {
this.closeOrderForm()
uni.showToast({ title: '订单创建成功', icon: 'success' })
// 刷新订单列表
await this.refresh()
// 触发订单创建事件
this.$emit('order-created')
},
/**
* 刷新订单列表(对外暴露方法)
*/
async refresh() {
await this.loadOrderList()
this.$emit('refreshed')
},
/**
* 获取当前订单列表(对外暴露方法)
*/
getOrderList() {
return this.orderList
},
/**
* 获取状态样式类
*/
getStatusClass(status) {
const statusMap = {
'pending': 'status-pending',
'paid': 'status-paid',
'partial': 'status-partial',
'cancelled': 'status-cancelled',
'completed': 'status-completed',
'refunded': 'status-refunded'
}
return statusMap[status] || 'status-default'
},
/**
* 获取状态文本
*/
getStatusText(status) {
const statusMap = {
'pending': '待支付',
'paid': '已支付',
'partial': '部分支付',
'cancelled': '已取消',
'completed': '已完成',
'refunded': '已退款'
}
return statusMap[status] || '未知状态'
},
/**
* 格式化时间
*/
formatTime(timeStr) {
if (!timeStr) return ''
try {
const date = new Date(timeStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
} catch (e) {
return timeStr
}
}
}
}
</script>
<style lang="scss" scoped>
.order-list-card {
padding: 0;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
}
.loading-icon {
font-size: 60rpx;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 28rpx;
color: #999999;
}
.order-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.order-item {
background: #3A3A3A;
border-radius: 16rpx;
padding: 32rpx;
border: 1px solid #404040;
transition: all 0.3s ease;
position: relative;
&:active {
background: #4A4A4A;
}
&.pending-payment {
border-color: #FFC107;
background: rgba(255, 193, 7, 0.05);
}
}
.order-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 1px solid #404040;
}
.order-info {
flex: 1;
}
.order-no {
font-size: 30rpx;
font-weight: 600;
color: #ffffff;
margin-bottom: 8rpx;
}
.order-time {
font-size: 24rpx;
color: #999999;
}
.order-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
&.status-pending {
background: rgba(255, 193, 7, 0.2);
color: #FFC107;
}
&.status-paid {
background: rgba(76, 175, 80, 0.2);
color: #4CAF50;
}
&.status-partial {
background: rgba(255, 152, 0, 0.2);
color: #FF9800;
}
&.status-cancelled {
background: rgba(158, 158, 158, 0.2);
color: #9E9E9E;
}
&.status-completed {
background: rgba(41, 211, 180, 0.2);
color: #29D3B4;
}
&.status-refunded {
background: rgba(244, 67, 54, 0.2);
color: #F44336;
}
&.status-default {
background: rgba(158, 158, 158, 0.2);
color: #9E9E9E;
}
}
.order-content {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.product-info {
padding: 20rpx;
background: rgba(41, 211, 180, 0.05);
border-radius: 12rpx;
border-left: 4rpx solid #29D3B4;
}
.product-name {
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
margin-bottom: 8rpx;
}
.product-specs {
font-size: 24rpx;
color: #cccccc;
}
.order-details {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.detail-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-label {
font-size: 26rpx;
color: #999999;
min-width: 140rpx;
}
.detail-value {
font-size: 26rpx;
color: #ffffff;
flex: 1;
text-align: right;
&.price {
color: #FFC107;
font-weight: 600;
font-size: 28rpx;
}
&.paid {
color: #4CAF50;
font-weight: 600;
}
&.unpaid {
color: #F44336;
font-weight: 600;
}
}
.order-remark {
padding: 20rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 12rpx;
}
.remark-label {
font-size: 24rpx;
color: #999999;
margin-bottom: 12rpx;
}
.remark-content {
font-size: 26rpx;
color: #cccccc;
line-height: 1.5;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
text-align: center;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 32rpx;
opacity: 0.6;
}
.empty-text {
font-size: 32rpx;
color: #ffffff;
margin-bottom: 16rpx;
font-weight: 500;
}
.empty-tip {
font-size: 26rpx;
color: #999999;
line-height: 1.4;
margin-bottom: 32rpx;
}
.empty-add-btn {
background: #29D3B4;
border-radius: 30rpx;
padding: 16rpx 40rpx;
color: #ffffff;
font-size: 28rpx;
font-weight: 500;
transition: all 0.3s ease;
&:active {
background: #1fb396;
}
}
.action-header {
display: flex;
justify-content: flex-end;
margin-bottom: 24rpx;
}
.add-order-btn {
display: flex;
align-items: center;
background: #29D3B4;
border-radius: 30rpx;
padding: 12rpx 24rpx;
color: #ffffff;
font-size: 26rpx;
font-weight: 500;
transition: all 0.3s ease;
&:active {
background: #1fb396;
}
}
.add-icon {
font-size: 32rpx;
font-weight: bold;
margin-right: 8rpx;
line-height: 1;
}
.add-text {
font-size: 26rpx;
}
</style>