24 changed files with 1775 additions and 456 deletions
@ -0,0 +1,783 @@ |
|||||
|
<!--学员订单详情页面--> |
||||
|
<template> |
||||
|
<view class="main_box"> |
||||
|
<!-- 自定义导航栏 --> |
||||
|
<view class="navbar_section"> |
||||
|
<view class="navbar_back" @click="goBack"> |
||||
|
<text class="back_icon">‹</text> |
||||
|
</view> |
||||
|
<view class="navbar_title">订单详情</view> |
||||
|
<view class="navbar_action"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 加载状态 --> |
||||
|
<view v-if="loading" class="loading_section"> |
||||
|
<view class="loading_text">加载中...</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 订单详情内容 --> |
||||
|
<view v-else-if="orderDetail" class="detail_content"> |
||||
|
<!-- 订单状态 --> |
||||
|
<view class="status_section"> |
||||
|
<view class="status_icon" :class="orderDetail.status"> |
||||
|
<text class="icon_text">{{ getStatusIcon(orderDetail.status) }}</text> |
||||
|
</view> |
||||
|
<view class="status_info"> |
||||
|
<view class="status_text">{{ getStatusText(orderDetail.status) }}</view> |
||||
|
<view class="status_desc">{{ getStatusDesc(orderDetail.status) }}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 订单信息 --> |
||||
|
<view class="order_info_section"> |
||||
|
<view class="section_title">订单信息</view> |
||||
|
<view class="info_list"> |
||||
|
<view class="info_item"> |
||||
|
<text class="label">订单号:</text> |
||||
|
<text class="value">{{ orderDetail.order_no }}</text> |
||||
|
</view> |
||||
|
<view class="info_item"> |
||||
|
<text class="label">下单时间:</text> |
||||
|
<text class="value">{{ formatDateTime(orderDetail.create_time) }}</text> |
||||
|
</view> |
||||
|
<view class="info_item" v-if="orderDetail.payment_time"> |
||||
|
<text class="label">支付时间:</text> |
||||
|
<text class="value">{{ formatDateTime(orderDetail.payment_time) }}</text> |
||||
|
</view> |
||||
|
<view class="info_item"> |
||||
|
<text class="label">订单金额:</text> |
||||
|
<text class="value amount">¥{{ orderDetail.total_amount }}</text> |
||||
|
</view> |
||||
|
<view class="info_item" v-if="orderDetail.payment_method"> |
||||
|
<text class="label">支付方式:</text> |
||||
|
<text class="value">{{ orderDetail.payment_method }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 课程信息 --> |
||||
|
<view class="course_info_section"> |
||||
|
<view class="section_title">课程信息</view> |
||||
|
<view class="course_card"> |
||||
|
<view class="course_header"> |
||||
|
<view class="course_name">{{ orderDetail.product_name }}</view> |
||||
|
<view class="course_amount">¥{{ orderDetail.total_amount }}</view> |
||||
|
</view> |
||||
|
<view class="course_details"> |
||||
|
<view class="detail_item" v-if="orderDetail.product_specs"> |
||||
|
<text class="detail_label">课程规格:</text> |
||||
|
<text class="detail_value">{{ orderDetail.product_specs }}</text> |
||||
|
</view> |
||||
|
<view class="detail_item"> |
||||
|
<text class="detail_label">购买数量:</text> |
||||
|
<text class="detail_value">{{ orderDetail.quantity }}节</text> |
||||
|
</view> |
||||
|
<view class="detail_item" v-if="orderDetail.course_count"> |
||||
|
<text class="detail_label">总课时:</text> |
||||
|
<text class="detail_value">{{ orderDetail.course_count }}节</text> |
||||
|
</view> |
||||
|
<view class="detail_item" v-if="orderDetail.used_count !== undefined"> |
||||
|
<text class="detail_label">已使用:</text> |
||||
|
<text class="detail_value">{{ orderDetail.used_count }}节</text> |
||||
|
</view> |
||||
|
<view class="detail_item" v-if="orderDetail.remaining_count !== undefined"> |
||||
|
<text class="detail_label">剩余课时:</text> |
||||
|
<text class="detail_value">{{ orderDetail.remaining_count }}节</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 备注信息 --> |
||||
|
<view class="remark_section" v-if="orderDetail.remark"> |
||||
|
<view class="section_title">备注信息</view> |
||||
|
<view class="remark_text">{{ orderDetail.remark }}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 退款信息 --> |
||||
|
<view class="refund_section" v-if="showRefundInfo"> |
||||
|
<view class="section_title">退款信息</view> |
||||
|
<view class="refund_info"> |
||||
|
<view class="info_item"> |
||||
|
<text class="label">退款金额:</text> |
||||
|
<text class="value amount">¥{{ orderDetail.refund_amount || orderDetail.total_amount }}</text> |
||||
|
</view> |
||||
|
<view class="info_item" v-if="orderDetail.refund_time"> |
||||
|
<text class="label">退款时间:</text> |
||||
|
<text class="value">{{ formatDateTime(orderDetail.refund_time) }}</text> |
||||
|
</view> |
||||
|
<view class="info_item" v-if="orderDetail.cancel_reason"> |
||||
|
<text class="label">退款原因:</text> |
||||
|
<text class="value">{{ orderDetail.cancel_reason }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 订单不存在 --> |
||||
|
<view v-else class="empty_section"> |
||||
|
<view class="empty_icon">📋</view> |
||||
|
<view class="empty_text">订单不存在</view> |
||||
|
<view class="empty_hint">请检查订单信息或联系客服</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 操作按钮 --> |
||||
|
<view class="action_section" v-if="orderDetail"> |
||||
|
<fui-button |
||||
|
v-if="orderDetail.status === 'pending_payment'" |
||||
|
background="#29d3b4" |
||||
|
@click="payOrder" |
||||
|
> |
||||
|
立即付款 |
||||
|
</fui-button> |
||||
|
|
||||
|
<fui-button |
||||
|
v-if="orderDetail.status === 'pending_payment'" |
||||
|
background="transparent" |
||||
|
color="#999" |
||||
|
@click="cancelOrder" |
||||
|
> |
||||
|
取消订单 |
||||
|
</fui-button> |
||||
|
|
||||
|
<fui-button |
||||
|
v-if="canViewContract" |
||||
|
background="#f39c12" |
||||
|
@click="viewContract" |
||||
|
> |
||||
|
查看合同 |
||||
|
</fui-button> |
||||
|
|
||||
|
<fui-button |
||||
|
v-if="orderDetail.status === 'refunding' || orderDetail.status === 'refunded'" |
||||
|
background="transparent" |
||||
|
color="#f39c12" |
||||
|
@click="viewRefundDetail" |
||||
|
> |
||||
|
查看退款 |
||||
|
</fui-button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import apiRoute from '@/api/apiRoute.js' |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
orderId: 0, |
||||
|
studentId: 0, |
||||
|
orderDetail: null, |
||||
|
loading: false, |
||||
|
// 状态文本映射 |
||||
|
STATUS_TEXT_MAP: { |
||||
|
'pending_payment': '待付款', |
||||
|
'paid': '已付款', |
||||
|
'completed': '已完成', |
||||
|
'cancelled': '已取消', |
||||
|
'refunding': '退款中', |
||||
|
'refunded': '已退款' |
||||
|
}, |
||||
|
// 状态描述映射 |
||||
|
STATUS_DESC_MAP: { |
||||
|
'pending_payment': '请在规定时间内完成支付', |
||||
|
'paid': '订单已支付成功', |
||||
|
'completed': '订单已完成,感谢您的使用', |
||||
|
'cancelled': '订单已取消', |
||||
|
'refunding': '退款处理中,请耐心等待', |
||||
|
'refunded': '退款已完成' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
computed: { |
||||
|
showRefundInfo() { |
||||
|
return this.orderDetail && (this.orderDetail.status === 'refunding' || this.orderDetail.status === 'refunded') |
||||
|
}, |
||||
|
|
||||
|
canViewContract() { |
||||
|
// 已支付的订单可以查看合同 |
||||
|
return this.orderDetail && (this.orderDetail.status === 'paid' || this.orderDetail.status === 'completed') |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onLoad(options) { |
||||
|
this.orderId = parseInt(options.id) || 0 |
||||
|
this.studentId = parseInt(options.student_id) || 0 |
||||
|
|
||||
|
// 如果没有学员ID,从本地存储获取 |
||||
|
if (!this.studentId) { |
||||
|
const userInfo = uni.getStorageSync('userInfo') |
||||
|
if (userInfo && userInfo.id) { |
||||
|
this.studentId = userInfo.id |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (this.orderId && this.studentId) { |
||||
|
this.loadOrderDetail() |
||||
|
} else { |
||||
|
uni.showToast({ |
||||
|
title: '参数错误', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
uni.navigateBack() |
||||
|
}, 1500) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
goBack() { |
||||
|
uni.navigateBack() |
||||
|
}, |
||||
|
|
||||
|
async loadOrderDetail() { |
||||
|
this.loading = true |
||||
|
try { |
||||
|
console.log('获取订单详情,参数:', { |
||||
|
id: this.orderId, |
||||
|
student_id: this.studentId |
||||
|
}); |
||||
|
|
||||
|
const response = await apiRoute.xy_getStudentOrderDetail({ |
||||
|
id: this.orderId, |
||||
|
student_id: this.studentId |
||||
|
}) |
||||
|
|
||||
|
if (response.code === 1 && response.data) { |
||||
|
this.orderDetail = this.processOrderData(response.data) |
||||
|
} else { |
||||
|
// 如果接口返回失败,尝试从订单列表接口获取数据 |
||||
|
console.warn('订单详情接口失败,尝试从列表接口获取:', response.msg) |
||||
|
await this.loadOrderFromList() |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('获取订单详情失败:', error) |
||||
|
// 接口调用失败,尝试从订单列表接口获取数据 |
||||
|
await this.loadOrderFromList() |
||||
|
} finally { |
||||
|
this.loading = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 从订单列表接口获取订单数据作为备用 |
||||
|
async loadOrderFromList() { |
||||
|
try { |
||||
|
const response = await apiRoute.xy_getStudentOrders({ |
||||
|
student_id: this.studentId, |
||||
|
page: 1, |
||||
|
limit: 100 |
||||
|
}) |
||||
|
|
||||
|
if (response.code === 1 && response.data && response.data.data) { |
||||
|
const orders = response.data.data |
||||
|
const targetOrder = orders.find(order => order.id == this.orderId) |
||||
|
|
||||
|
if (targetOrder) { |
||||
|
this.orderDetail = this.processOrderData(targetOrder) |
||||
|
} else { |
||||
|
this.showError('订单不存在') |
||||
|
} |
||||
|
} else { |
||||
|
this.showError('获取订单信息失败') |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('从列表获取订单详情失败:', error) |
||||
|
this.showError('获取订单信息失败') |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理订单数据,将后端数据转换为前端需要的格式 |
||||
|
processOrderData(rawData) { |
||||
|
return { |
||||
|
id: rawData.id, |
||||
|
order_no: rawData.order_no || rawData.order_number || rawData.payment_id, |
||||
|
product_name: rawData.course_id_name || rawData.course_name || rawData.product_name || '课程订单', |
||||
|
product_specs: rawData.course_specs || rawData.product_specs || '', |
||||
|
quantity: rawData.quantity || 1, |
||||
|
total_amount: rawData.order_amount || rawData.total_amount || rawData.amount || '0.00', |
||||
|
status: this.mapOrderStatus(rawData.order_status || rawData.status), |
||||
|
create_time: rawData.created_at || rawData.create_time, |
||||
|
payment_method: rawData.payment_type ? this.mapPaymentMethod(rawData.payment_type) : '', |
||||
|
payment_time: rawData.payment_time || rawData.paid_at, |
||||
|
refund_time: rawData.refund_time, |
||||
|
refund_amount: rawData.refund_amount, |
||||
|
// 课程相关字段 |
||||
|
course_count: rawData.course_count || 0, |
||||
|
used_count: rawData.use_total_hours || rawData.used_count || 0, |
||||
|
remaining_count: rawData.remaining_count || 0, |
||||
|
cancel_reason: rawData.after_sales_reason || rawData.cancel_reason || '', |
||||
|
remark: rawData.remark || '' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 映射订单状态 |
||||
|
mapOrderStatus(status) { |
||||
|
const statusMap = { |
||||
|
'pending': 'pending_payment', |
||||
|
'paid': 'paid', |
||||
|
'signed': 'completed', |
||||
|
'completed': 'completed', |
||||
|
'transfer': 'cancelled', |
||||
|
'cancelled': 'cancelled', |
||||
|
'refunded': 'refunded', |
||||
|
'refunding': 'refunding' |
||||
|
} |
||||
|
return statusMap[status] || 'pending_payment' |
||||
|
}, |
||||
|
|
||||
|
// 映射支付方式 |
||||
|
mapPaymentMethod(method) { |
||||
|
const methodMap = { |
||||
|
'cash': '现金支付', |
||||
|
'scan_code': '扫码支付', |
||||
|
'subscription': '预约支付', |
||||
|
'wxpay_online': '微信支付', |
||||
|
'wxpay': '微信支付', |
||||
|
'alipay': '支付宝', |
||||
|
'bank': '银行转账' |
||||
|
} |
||||
|
return methodMap[method] || method || '' |
||||
|
}, |
||||
|
|
||||
|
getStatusText(status) { |
||||
|
return this.STATUS_TEXT_MAP[status] || status |
||||
|
}, |
||||
|
|
||||
|
getStatusDesc(status) { |
||||
|
return this.STATUS_DESC_MAP[status] || '' |
||||
|
}, |
||||
|
|
||||
|
getStatusIcon(status) { |
||||
|
const iconMap = { |
||||
|
'pending_payment': '💰', |
||||
|
'paid': '✅', |
||||
|
'completed': '🎉', |
||||
|
'cancelled': '❌', |
||||
|
'refunding': '⏳', |
||||
|
'refunded': '↩️' |
||||
|
} |
||||
|
return iconMap[status] || '📋' |
||||
|
}, |
||||
|
|
||||
|
formatDateTime(dateString) { |
||||
|
if (!dateString) return '' |
||||
|
const date = new Date(dateString) |
||||
|
const year = date.getFullYear() |
||||
|
const month = String(date.getMonth() + 1).padStart(2, '0') |
||||
|
const day = String(date.getDate()).padStart(2, '0') |
||||
|
const hours = String(date.getHours()).padStart(2, '0') |
||||
|
const minutes = String(date.getMinutes()).padStart(2, '0') |
||||
|
return `${year}-${month}-${day} ${hours}:${minutes}` |
||||
|
}, |
||||
|
|
||||
|
showError(message) { |
||||
|
uni.showToast({ |
||||
|
title: message, |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 支付订单 |
||||
|
payOrder() { |
||||
|
// 跳转回订单列表页面进行支付 |
||||
|
uni.navigateBack() |
||||
|
}, |
||||
|
|
||||
|
// 取消订单 |
||||
|
cancelOrder() { |
||||
|
uni.showModal({ |
||||
|
title: '确认取消', |
||||
|
content: '确定要取消此订单吗?', |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
this.performCancelOrder() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
async performCancelOrder() { |
||||
|
try { |
||||
|
uni.showLoading({ title: '处理中...' }) |
||||
|
|
||||
|
// 这里应该调用取消订单的API |
||||
|
// 暂时模拟处理 |
||||
|
await new Promise(resolve => setTimeout(resolve, 1000)) |
||||
|
|
||||
|
uni.hideLoading() |
||||
|
uni.showToast({ |
||||
|
title: '取消成功', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
|
||||
|
// 刷新订单详情 |
||||
|
this.loadOrderDetail() |
||||
|
} catch (error) { |
||||
|
uni.hideLoading() |
||||
|
console.error('取消订单失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '取消失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 查看合同 |
||||
|
async viewContract() { |
||||
|
try { |
||||
|
uni.showLoading({ title: '获取合同信息...' }) |
||||
|
|
||||
|
// 查询合同签署记录 |
||||
|
const contractResponse = await this.getContractInfo() |
||||
|
|
||||
|
uni.hideLoading() |
||||
|
|
||||
|
if (contractResponse && contractResponse.contract_id) { |
||||
|
// 跳转到合同详情页面 |
||||
|
uni.navigateTo({ |
||||
|
url: `/pages-student/contract/detail?contract_id=${contractResponse.contract_id}&student_id=${this.studentId}` |
||||
|
}) |
||||
|
} else { |
||||
|
uni.showModal({ |
||||
|
title: '提示', |
||||
|
content: '该订单暂无关联合同,请联系客服处理', |
||||
|
showCancel: false |
||||
|
}) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
uni.hideLoading() |
||||
|
console.error('获取合同信息失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '获取合同信息失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 获取合同信息 |
||||
|
async getContractInfo() { |
||||
|
try { |
||||
|
// 调用获取学员合同列表接口 |
||||
|
const response = await apiRoute.getStudentContracts({ |
||||
|
student_id: this.studentId, |
||||
|
order_id: this.orderId // 如果接口支持按订单筛选 |
||||
|
}) |
||||
|
|
||||
|
if (response.code === 1 && response.data && response.data.length > 0) { |
||||
|
// 返回第一个合同记录 |
||||
|
return response.data[0] |
||||
|
} |
||||
|
|
||||
|
return null |
||||
|
} catch (error) { |
||||
|
console.error('获取合同信息失败:', error) |
||||
|
return null |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 查看退款详情 |
||||
|
viewRefundDetail() { |
||||
|
const refundInfo = `订单号:${this.orderDetail.order_no}\n退款金额:¥${this.orderDetail.refund_amount || this.orderDetail.total_amount}\n退款时间:${this.orderDetail.refund_time ? this.formatDateTime(this.orderDetail.refund_time) : '处理中'}` |
||||
|
|
||||
|
uni.showModal({ |
||||
|
title: '退款详情', |
||||
|
content: refundInfo, |
||||
|
showCancel: false |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.main_box { |
||||
|
background: #f8f9fa; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
// 自定义导航栏 |
||||
|
.navbar_section { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
background: #29D3B4; |
||||
|
padding: 40rpx 32rpx 20rpx; |
||||
|
|
||||
|
// 小程序端适配状态栏 |
||||
|
// #ifdef MP-WEIXIN |
||||
|
padding-top: 80rpx; |
||||
|
// #endif |
||||
|
|
||||
|
.navbar_back { |
||||
|
width: 60rpx; |
||||
|
|
||||
|
.back_icon { |
||||
|
color: #fff; |
||||
|
font-size: 40rpx; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.navbar_title { |
||||
|
color: #fff; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.navbar_action { |
||||
|
width: 60rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 加载状态 |
||||
|
.loading_section { |
||||
|
background: #fff; |
||||
|
margin: 20rpx; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 80rpx 32rpx; |
||||
|
text-align: center; |
||||
|
|
||||
|
.loading_text { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 订单详情内容 |
||||
|
.detail_content { |
||||
|
padding: 0 20rpx 120rpx; |
||||
|
} |
||||
|
|
||||
|
// 订单状态 |
||||
|
.status_section { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 40rpx 32rpx; |
||||
|
margin: 20rpx 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
|
||||
|
.status_icon { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-right: 24rpx; |
||||
|
|
||||
|
.icon_text { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
&.pending_payment { |
||||
|
background: rgba(243, 156, 18, 0.1); |
||||
|
} |
||||
|
|
||||
|
&.paid { |
||||
|
background: rgba(52, 152, 219, 0.1); |
||||
|
} |
||||
|
|
||||
|
&.completed { |
||||
|
background: rgba(39, 174, 96, 0.1); |
||||
|
} |
||||
|
|
||||
|
&.cancelled { |
||||
|
background: rgba(149, 165, 166, 0.1); |
||||
|
} |
||||
|
|
||||
|
&.refunded, &.refunding { |
||||
|
background: rgba(231, 76, 60, 0.1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.status_info { |
||||
|
flex: 1; |
||||
|
|
||||
|
.status_text { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.status_desc { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 通用区块样式 |
||||
|
.order_info_section, .course_info_section, .remark_section, .refund_section { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 32rpx; |
||||
|
margin: 20rpx 0; |
||||
|
|
||||
|
.section_title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 24rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 信息列表 |
||||
|
.info_list { |
||||
|
.info_item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
|
||||
|
&:last-child { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
min-width: 160rpx; |
||||
|
} |
||||
|
|
||||
|
.value { |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
|
||||
|
&.amount { |
||||
|
color: #e74c3c; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 课程卡片 |
||||
|
.course_card { |
||||
|
.course_header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: flex-start; |
||||
|
margin-bottom: 20rpx; |
||||
|
|
||||
|
.course_name { |
||||
|
flex: 1; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.course_amount { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #e74c3c; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.course_details { |
||||
|
.detail_item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 12rpx; |
||||
|
|
||||
|
&:last-child { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.detail_label { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
min-width: 140rpx; |
||||
|
} |
||||
|
|
||||
|
.detail_value { |
||||
|
font-size: 24rpx; |
||||
|
color: #333; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 备注文本 |
||||
|
.remark_text { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.6; |
||||
|
} |
||||
|
|
||||
|
// 退款信息 |
||||
|
.refund_info { |
||||
|
.info_item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16rpx; |
||||
|
|
||||
|
&:last-child { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
min-width: 160rpx; |
||||
|
} |
||||
|
|
||||
|
.value { |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
|
||||
|
&.amount { |
||||
|
color: #e74c3c; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 空状态 |
||||
|
.empty_section { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 80rpx 32rpx; |
||||
|
text-align: center; |
||||
|
margin: 20rpx; |
||||
|
|
||||
|
.empty_icon { |
||||
|
font-size: 80rpx; |
||||
|
margin-bottom: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.empty_text { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.empty_hint { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 操作按钮 |
||||
|
.action_section { |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
background: #fff; |
||||
|
padding: 20rpx 32rpx; |
||||
|
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.1); |
||||
|
display: flex; |
||||
|
gap: 16rpx; |
||||
|
|
||||
|
fui-button { |
||||
|
flex: 1; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -1,280 +1,281 @@ |
|||||
<template> |
<template> |
||||
<view class="dashboard-webview"> |
<view class="dashboard-webview"> |
||||
<!-- 加载状态 --> |
<!-- 加载状态 --> |
||||
<view v-if="isLoading" class="loading-container"> |
<view v-if="isLoading" class="loading-container"> |
||||
<uni-icons type="spinner-cycle" size="32" color="#29d3b4"></uni-icons> |
<uni-icons type="spinner-cycle" size="32" color="#29d3b4"></uni-icons> |
||||
<text class="loading-text">加载中...</text> |
<text class="loading-text">加载中...</text> |
||||
</view> |
</view> |
||||
|
|
||||
<!-- WebView容器 --> |
<!-- WebView容器 --> |
||||
<web-view |
<web-view |
||||
v-else-if="!hasError" |
v-else-if="!hasError" |
||||
:src="webViewUrl" |
:src="webViewUrl" |
||||
@message="handleMessage" |
@message="handleMessage" |
||||
@error="handleError" |
@error="handleError" |
||||
@load="handleLoad" |
@load="handleLoad" |
||||
class="webview-container" |
class="webview-container" |
||||
></web-view> |
></web-view> |
||||
|
|
||||
<!-- 错误提示 --> |
<!-- 错误提示 --> |
||||
<view v-if="hasError" class="error-container"> |
<view v-if="hasError" class="error-container"> |
||||
<uni-icons type="close-filled" size="48" color="#ff5722"></uni-icons> |
<uni-icons type="close-filled" size="48" color="#ff5722"></uni-icons> |
||||
<text class="error-title">页面加载失败</text> |
<text class="error-title">页面加载失败</text> |
||||
<text class="error-desc">{{ errorMessage }}</text> |
<text class="error-desc">{{ errorMessage }}</text> |
||||
<button class="retry-btn" @click="retry">重试</button> |
<button class="retry-btn" @click="retry">重试</button> |
||||
</view> |
</view> |
||||
</view> |
</view> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<script> |
||||
export default { |
import { img_domian } from '@/common/config.js' |
||||
data() { |
|
||||
return { |
|
||||
pageType: '', |
|
||||
pageTitle: '', |
|
||||
isLoading: true, |
|
||||
hasError: false, |
|
||||
errorMessage: '', |
|
||||
baseUrl: 'http://localhost:20080', // 后端基础URL |
|
||||
userToken: '', |
|
||||
userInfo: {} |
|
||||
} |
|
||||
}, |
|
||||
computed: { |
|
||||
webViewUrl() { |
|
||||
if (!this.userToken) { |
|
||||
return ''; |
|
||||
} |
|
||||
// 构建WebView URL,包含token和页面类型 |
|
||||
const params = [ |
|
||||
`token=${encodeURIComponent(this.userToken)}`, |
|
||||
`type=${encodeURIComponent(this.pageType)}`, |
|
||||
`platform=uniapp` |
|
||||
]; |
|
||||
return `${this.baseUrl}/api/dashboard/webview?${params.join('&')}`; |
|
||||
} |
|
||||
}, |
|
||||
onLoad(options) { |
|
||||
// 从URL参数获取页面类型 |
|
||||
this.pageType = options.type || 'my_data'; |
|
||||
|
|
||||
// 设置页面标题 |
export default { |
||||
const titleMap = { |
data() { |
||||
'my_data': '我的数据', |
return { |
||||
'dept_data': '部门数据', |
pageType: '', |
||||
'campus_data': '校区数据' |
pageTitle: '', |
||||
}; |
isLoading: true, |
||||
this.pageTitle = titleMap[this.pageType] || '数据统计'; |
hasError: false, |
||||
|
errorMessage: '', |
||||
|
baseUrl: img_domian, // 后端基础URL |
||||
|
userToken: '', |
||||
|
userInfo: {}, |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
webViewUrl() { |
||||
|
if (!this.userToken) { |
||||
|
return '' |
||||
|
} |
||||
|
// 构建WebView URL,包含token和页面类型 |
||||
|
const params = [ |
||||
|
`token=${encodeURIComponent(this.userToken)}`, |
||||
|
`type=${encodeURIComponent(this.pageType)}`, |
||||
|
`platform=uniapp`, |
||||
|
] |
||||
|
return `${this.baseUrl}/api/dashboard/webview?${params.join('&')}` |
||||
|
}, |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
// 从URL参数获取页面类型 |
||||
|
this.pageType = options.type || 'my_data' |
||||
|
// 设置页面标题 |
||||
|
const titleMap = { |
||||
|
'my_data': '我的数据', |
||||
|
'dept_data': '部门数据', |
||||
|
'campus_data': '校区数据', |
||||
|
} |
||||
|
this.pageTitle = titleMap[this.pageType] || '数据统计' |
||||
|
|
||||
// 设置导航栏标题 |
// 设置导航栏标题 |
||||
uni.setNavigationBarTitle({ |
uni.setNavigationBarTitle({ |
||||
title: this.pageTitle |
title: this.pageTitle, |
||||
}); |
}) |
||||
|
|
||||
// 初始化WebView |
// 初始化WebView |
||||
this.initWebView(); |
this.initWebView() |
||||
}, |
}, |
||||
methods: { |
methods: { |
||||
async initWebView() { |
async initWebView() { |
||||
try { |
try { |
||||
// 获取用户信息和token |
// 获取用户信息和token |
||||
await this.getUserInfo(); |
await this.getUserInfo() |
||||
|
|
||||
// 延迟一下确保URL构建完成 |
// 延迟一下确保URL构建完成 |
||||
setTimeout(() => { |
setTimeout(() => { |
||||
this.isLoading = false; |
this.isLoading = false |
||||
}, 500); |
}, 500) |
||||
} catch (error) { |
} catch (error) { |
||||
console.error('初始化WebView失败:', error); |
console.error('初始化WebView失败:', error) |
||||
this.showError('初始化失败'); |
this.showError('初始化失败') |
||||
} |
} |
||||
}, |
}, |
||||
|
|
||||
getUserInfo() { |
getUserInfo() { |
||||
return new Promise((resolve, reject) => { |
return new Promise((resolve, reject) => { |
||||
try { |
try { |
||||
// 从本地存储获取用户信息 |
// 从本地存储获取用户信息 |
||||
const userInfo = uni.getStorageSync('userInfo'); |
const userInfo = uni.getStorageSync('userInfo') |
||||
const token = uni.getStorageSync('token'); |
const token = uni.getStorageSync('token') |
||||
|
|
||||
if (!token) { |
if (!token) { |
||||
// 如果没有token,使用测试token |
// 如果没有token,使用测试token |
||||
this.userToken = 'test123'; |
this.userToken = 'test123' |
||||
this.userInfo = { name: '测试用户' }; |
this.userInfo = { name: '测试用户' } |
||||
} else { |
} else { |
||||
this.userInfo = userInfo || {}; |
this.userInfo = userInfo || {} |
||||
this.userToken = token; |
this.userToken = token |
||||
} |
} |
||||
|
|
||||
resolve(); |
resolve() |
||||
} catch (error) { |
} catch (error) { |
||||
reject(error); |
reject(error) |
||||
} |
} |
||||
}); |
}) |
||||
}, |
}, |
||||
|
|
||||
handleMessage(event) { |
handleMessage(event) { |
||||
console.log('收到WebView消息:', event.detail.data); |
console.log('收到WebView消息:', event.detail.data) |
||||
const data = event.detail.data[0]; |
const data = event.detail.data[0] |
||||
|
|
||||
// 处理来自WebView的消息 |
// 处理来自WebView的消息 |
||||
switch (data.type) { |
switch (data.type) { |
||||
case 'navigate': |
case 'navigate': |
||||
// 处理页面跳转 |
// 处理页面跳转 |
||||
this.handleNavigate(data.url); |
this.handleNavigate(data.url) |
||||
break; |
break |
||||
case 'refresh': |
case 'refresh': |
||||
// 处理刷新请求 |
// 处理刷新请求 |
||||
this.refresh(); |
this.refresh() |
||||
break; |
break |
||||
case 'share': |
case 'share': |
||||
// 处理分享功能 |
// 处理分享功能 |
||||
this.handleShare(data.content); |
this.handleShare(data.content) |
||||
break; |
break |
||||
default: |
default: |
||||
console.log('未处理的消息类型:', data.type); |
console.log('未处理的消息类型:', data.type) |
||||
} |
} |
||||
}, |
}, |
||||
|
|
||||
handleError(event) { |
handleError(event) { |
||||
console.error('WebView加载错误:', event); |
console.error('WebView加载错误:', event) |
||||
this.showError('页面加载失败,请检查网络连接'); |
this.showError('页面加载失败,请检查网络连接') |
||||
}, |
}, |
||||
|
|
||||
handleLoad(event) { |
handleLoad(event) { |
||||
console.log('WebView加载完成:', event); |
console.log('WebView加载完成:', event) |
||||
this.isLoading = false; |
this.isLoading = false |
||||
this.hasError = false; |
this.hasError = false |
||||
}, |
}, |
||||
|
|
||||
handleNavigate(url) { |
handleNavigate(url) { |
||||
// 处理页面跳转,可以是UniApp页面或外部链接 |
// 处理页面跳转,可以是UniApp页面或外部链接 |
||||
if (url.startsWith('/pages')) { |
if (url.startsWith('/pages')) { |
||||
// UniApp内部页面跳转 |
// UniApp内部页面跳转 |
||||
uni.navigateTo({ |
uni.navigateTo({ |
||||
url: url, |
url: url, |
||||
fail: (err) => { |
fail: (err) => { |
||||
console.error('页面跳转失败:', err); |
console.error('页面跳转失败:', err) |
||||
uni.showToast({ |
uni.showToast({ |
||||
title: '页面跳转失败', |
title: '页面跳转失败', |
||||
icon: 'none' |
icon: 'none', |
||||
}); |
}) |
||||
} |
}, |
||||
}); |
}) |
||||
} else { |
} else { |
||||
// 外部链接,可以在当前WebView中打开或提示用户 |
// 外部链接,可以在当前WebView中打开或提示用户 |
||||
uni.showModal({ |
uni.showModal({ |
||||
title: '提示', |
title: '提示', |
||||
content: '是否在浏览器中打开链接?', |
content: '是否在浏览器中打开链接?', |
||||
success: (res) => { |
success: (res) => { |
||||
if (res.confirm) { |
if (res.confirm) { |
||||
uni.showToast({ |
uni.showToast({ |
||||
title: '功能开发中', |
title: '功能开发中', |
||||
icon: 'none' |
icon: 'none', |
||||
}); |
}) |
||||
} |
} |
||||
} |
}, |
||||
}); |
}) |
||||
} |
} |
||||
}, |
}, |
||||
|
|
||||
handleShare(content) { |
handleShare(content) { |
||||
// 处理分享功能 |
// 处理分享功能 |
||||
uni.showToast({ |
uni.showToast({ |
||||
title: '分享功能开发中', |
title: '分享功能开发中', |
||||
icon: 'none' |
icon: 'none', |
||||
}); |
}) |
||||
}, |
}, |
||||
|
|
||||
refresh() { |
refresh() { |
||||
// 刷新页面 |
// 刷新页面 |
||||
this.isLoading = true; |
this.isLoading = true |
||||
this.hasError = false; |
this.hasError = false |
||||
|
|
||||
// 重新初始化 |
// 重新初始化 |
||||
setTimeout(() => { |
setTimeout(() => { |
||||
this.initWebView(); |
this.initWebView() |
||||
}, 300); |
}, 300) |
||||
}, |
}, |
||||
|
|
||||
retry() { |
retry() { |
||||
// 重试加载 |
// 重试加载 |
||||
this.hasError = false; |
this.hasError = false |
||||
this.isLoading = true; |
this.isLoading = true |
||||
this.initWebView(); |
this.initWebView() |
||||
}, |
}, |
||||
|
|
||||
showError(message) { |
showError(message) { |
||||
this.isLoading = false; |
this.isLoading = false |
||||
this.hasError = true; |
this.hasError = true |
||||
this.errorMessage = message; |
this.errorMessage = message |
||||
} |
}, |
||||
} |
}, |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<style scoped> |
<style scoped> |
||||
.dashboard-webview { |
.dashboard-webview { |
||||
height: 100vh; |
height: 100vh; |
||||
background-color: #181A20; |
background-color: #181A20; |
||||
} |
} |
||||
|
|
||||
.loading-container { |
.loading-container { |
||||
display: flex; |
display: flex; |
||||
flex-direction: column; |
flex-direction: column; |
||||
align-items: center; |
align-items: center; |
||||
justify-content: center; |
justify-content: center; |
||||
height: 100vh; |
height: 100vh; |
||||
background-color: #181A20; |
background-color: #181A20; |
||||
} |
} |
||||
|
|
||||
.loading-text { |
.loading-text { |
||||
margin-top: 16px; |
margin-top: 16px; |
||||
font-size: 14px; |
font-size: 14px; |
||||
color: #29d3b4; |
color: #29d3b4; |
||||
} |
} |
||||
|
|
||||
.webview-container { |
.webview-container { |
||||
height: 100vh; |
height: 100vh; |
||||
width: 100%; |
width: 100%; |
||||
} |
} |
||||
|
|
||||
.error-container { |
.error-container { |
||||
display: flex; |
display: flex; |
||||
flex-direction: column; |
flex-direction: column; |
||||
align-items: center; |
align-items: center; |
||||
justify-content: center; |
justify-content: center; |
||||
height: 100vh; |
height: 100vh; |
||||
background-color: #181A20; |
background-color: #181A20; |
||||
padding: 40px; |
padding: 40px; |
||||
} |
} |
||||
|
|
||||
.error-title { |
.error-title { |
||||
margin-top: 16px; |
margin-top: 16px; |
||||
font-size: 18px; |
font-size: 18px; |
||||
font-weight: bold; |
font-weight: bold; |
||||
color: #fff; |
color: #fff; |
||||
} |
} |
||||
|
|
||||
.error-desc { |
.error-desc { |
||||
margin-top: 8px; |
margin-top: 8px; |
||||
font-size: 14px; |
font-size: 14px; |
||||
color: rgba(255, 255, 255, 0.6); |
color: rgba(255, 255, 255, 0.6); |
||||
text-align: center; |
text-align: center; |
||||
line-height: 1.5; |
line-height: 1.5; |
||||
} |
} |
||||
|
|
||||
.retry-btn { |
.retry-btn { |
||||
margin-top: 24px; |
margin-top: 24px; |
||||
padding: 12px 24px; |
padding: 12px 24px; |
||||
background-color: #29d3b4; |
background-color: #29d3b4; |
||||
color: #fff; |
color: #fff; |
||||
border-radius: 6px; |
border-radius: 6px; |
||||
border: none; |
border: none; |
||||
font-size: 14px; |
font-size: 14px; |
||||
} |
} |
||||
|
|
||||
.retry-btn:active { |
.retry-btn:active { |
||||
background-color: #1a9b7c; |
background-color: #1a9b7c; |
||||
} |
} |
||||
</style> |
</style> |
||||
Loading…
Reference in new issue