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> |
|||
<view class="dashboard-webview"> |
|||
<!-- 加载状态 --> |
|||
<view v-if="isLoading" class="loading-container"> |
|||
<uni-icons type="spinner-cycle" size="32" color="#29d3b4"></uni-icons> |
|||
<text class="loading-text">加载中...</text> |
|||
</view> |
|||
|
|||
<!-- WebView容器 --> |
|||
<web-view |
|||
v-else-if="!hasError" |
|||
:src="webViewUrl" |
|||
@message="handleMessage" |
|||
@error="handleError" |
|||
@load="handleLoad" |
|||
class="webview-container" |
|||
></web-view> |
|||
|
|||
<!-- 错误提示 --> |
|||
<view v-if="hasError" class="error-container"> |
|||
<uni-icons type="close-filled" size="48" color="#ff5722"></uni-icons> |
|||
<text class="error-title">页面加载失败</text> |
|||
<text class="error-desc">{{ errorMessage }}</text> |
|||
<button class="retry-btn" @click="retry">重试</button> |
|||
</view> |
|||
</view> |
|||
<view class="dashboard-webview"> |
|||
<!-- 加载状态 --> |
|||
<view v-if="isLoading" class="loading-container"> |
|||
<uni-icons type="spinner-cycle" size="32" color="#29d3b4"></uni-icons> |
|||
<text class="loading-text">加载中...</text> |
|||
</view> |
|||
|
|||
<!-- WebView容器 --> |
|||
<web-view |
|||
v-else-if="!hasError" |
|||
:src="webViewUrl" |
|||
@message="handleMessage" |
|||
@error="handleError" |
|||
@load="handleLoad" |
|||
class="webview-container" |
|||
></web-view> |
|||
|
|||
<!-- 错误提示 --> |
|||
<view v-if="hasError" class="error-container"> |
|||
<uni-icons type="close-filled" size="48" color="#ff5722"></uni-icons> |
|||
<text class="error-title">页面加载失败</text> |
|||
<text class="error-desc">{{ errorMessage }}</text> |
|||
<button class="retry-btn" @click="retry">重试</button> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
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'; |
|||
|
|||
// 设置页面标题 |
|||
const titleMap = { |
|||
'my_data': '我的数据', |
|||
'dept_data': '部门数据', |
|||
'campus_data': '校区数据' |
|||
}; |
|||
this.pageTitle = titleMap[this.pageType] || '数据统计'; |
|||
|
|||
// 设置导航栏标题 |
|||
uni.setNavigationBarTitle({ |
|||
title: this.pageTitle |
|||
}); |
|||
|
|||
// 初始化WebView |
|||
this.initWebView(); |
|||
}, |
|||
methods: { |
|||
async initWebView() { |
|||
try { |
|||
// 获取用户信息和token |
|||
await this.getUserInfo(); |
|||
|
|||
// 延迟一下确保URL构建完成 |
|||
setTimeout(() => { |
|||
this.isLoading = false; |
|||
}, 500); |
|||
} catch (error) { |
|||
console.error('初始化WebView失败:', error); |
|||
this.showError('初始化失败'); |
|||
} |
|||
}, |
|||
|
|||
getUserInfo() { |
|||
return new Promise((resolve, reject) => { |
|||
try { |
|||
// 从本地存储获取用户信息 |
|||
const userInfo = uni.getStorageSync('userInfo'); |
|||
const token = uni.getStorageSync('token'); |
|||
|
|||
if (!token) { |
|||
// 如果没有token,使用测试token |
|||
this.userToken = 'test123'; |
|||
this.userInfo = { name: '测试用户' }; |
|||
} else { |
|||
this.userInfo = userInfo || {}; |
|||
this.userToken = token; |
|||
} |
|||
|
|||
resolve(); |
|||
} catch (error) { |
|||
reject(error); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
handleMessage(event) { |
|||
console.log('收到WebView消息:', event.detail.data); |
|||
const data = event.detail.data[0]; |
|||
|
|||
// 处理来自WebView的消息 |
|||
switch (data.type) { |
|||
case 'navigate': |
|||
// 处理页面跳转 |
|||
this.handleNavigate(data.url); |
|||
break; |
|||
case 'refresh': |
|||
// 处理刷新请求 |
|||
this.refresh(); |
|||
break; |
|||
case 'share': |
|||
// 处理分享功能 |
|||
this.handleShare(data.content); |
|||
break; |
|||
default: |
|||
console.log('未处理的消息类型:', data.type); |
|||
} |
|||
}, |
|||
|
|||
handleError(event) { |
|||
console.error('WebView加载错误:', event); |
|||
this.showError('页面加载失败,请检查网络连接'); |
|||
}, |
|||
|
|||
handleLoad(event) { |
|||
console.log('WebView加载完成:', event); |
|||
this.isLoading = false; |
|||
this.hasError = false; |
|||
}, |
|||
|
|||
handleNavigate(url) { |
|||
// 处理页面跳转,可以是UniApp页面或外部链接 |
|||
if (url.startsWith('/pages')) { |
|||
// UniApp内部页面跳转 |
|||
uni.navigateTo({ |
|||
url: url, |
|||
fail: (err) => { |
|||
console.error('页面跳转失败:', err); |
|||
uni.showToast({ |
|||
title: '页面跳转失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
} else { |
|||
// 外部链接,可以在当前WebView中打开或提示用户 |
|||
uni.showModal({ |
|||
title: '提示', |
|||
content: '是否在浏览器中打开链接?', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
uni.showToast({ |
|||
title: '功能开发中', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
handleShare(content) { |
|||
// 处理分享功能 |
|||
uni.showToast({ |
|||
title: '分享功能开发中', |
|||
icon: 'none' |
|||
}); |
|||
}, |
|||
|
|||
refresh() { |
|||
// 刷新页面 |
|||
this.isLoading = true; |
|||
this.hasError = false; |
|||
|
|||
// 重新初始化 |
|||
setTimeout(() => { |
|||
this.initWebView(); |
|||
}, 300); |
|||
}, |
|||
|
|||
retry() { |
|||
// 重试加载 |
|||
this.hasError = false; |
|||
this.isLoading = true; |
|||
this.initWebView(); |
|||
}, |
|||
|
|||
showError(message) { |
|||
this.isLoading = false; |
|||
this.hasError = true; |
|||
this.errorMessage = message; |
|||
} |
|||
} |
|||
} |
|||
import { img_domian } from '@/common/config.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
pageType: '', |
|||
pageTitle: '', |
|||
isLoading: true, |
|||
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({ |
|||
title: this.pageTitle, |
|||
}) |
|||
|
|||
// 初始化WebView |
|||
this.initWebView() |
|||
}, |
|||
methods: { |
|||
async initWebView() { |
|||
try { |
|||
// 获取用户信息和token |
|||
await this.getUserInfo() |
|||
|
|||
// 延迟一下确保URL构建完成 |
|||
setTimeout(() => { |
|||
this.isLoading = false |
|||
}, 500) |
|||
} catch (error) { |
|||
console.error('初始化WebView失败:', error) |
|||
this.showError('初始化失败') |
|||
} |
|||
}, |
|||
|
|||
getUserInfo() { |
|||
return new Promise((resolve, reject) => { |
|||
try { |
|||
// 从本地存储获取用户信息 |
|||
const userInfo = uni.getStorageSync('userInfo') |
|||
const token = uni.getStorageSync('token') |
|||
|
|||
if (!token) { |
|||
// 如果没有token,使用测试token |
|||
this.userToken = 'test123' |
|||
this.userInfo = { name: '测试用户' } |
|||
} else { |
|||
this.userInfo = userInfo || {} |
|||
this.userToken = token |
|||
} |
|||
|
|||
resolve() |
|||
} catch (error) { |
|||
reject(error) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
handleMessage(event) { |
|||
console.log('收到WebView消息:', event.detail.data) |
|||
const data = event.detail.data[0] |
|||
|
|||
// 处理来自WebView的消息 |
|||
switch (data.type) { |
|||
case 'navigate': |
|||
// 处理页面跳转 |
|||
this.handleNavigate(data.url) |
|||
break |
|||
case 'refresh': |
|||
// 处理刷新请求 |
|||
this.refresh() |
|||
break |
|||
case 'share': |
|||
// 处理分享功能 |
|||
this.handleShare(data.content) |
|||
break |
|||
default: |
|||
console.log('未处理的消息类型:', data.type) |
|||
} |
|||
}, |
|||
|
|||
handleError(event) { |
|||
console.error('WebView加载错误:', event) |
|||
this.showError('页面加载失败,请检查网络连接') |
|||
}, |
|||
|
|||
handleLoad(event) { |
|||
console.log('WebView加载完成:', event) |
|||
this.isLoading = false |
|||
this.hasError = false |
|||
}, |
|||
|
|||
handleNavigate(url) { |
|||
// 处理页面跳转,可以是UniApp页面或外部链接 |
|||
if (url.startsWith('/pages')) { |
|||
// UniApp内部页面跳转 |
|||
uni.navigateTo({ |
|||
url: url, |
|||
fail: (err) => { |
|||
console.error('页面跳转失败:', err) |
|||
uni.showToast({ |
|||
title: '页面跳转失败', |
|||
icon: 'none', |
|||
}) |
|||
}, |
|||
}) |
|||
} else { |
|||
// 外部链接,可以在当前WebView中打开或提示用户 |
|||
uni.showModal({ |
|||
title: '提示', |
|||
content: '是否在浏览器中打开链接?', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
uni.showToast({ |
|||
title: '功能开发中', |
|||
icon: 'none', |
|||
}) |
|||
} |
|||
}, |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
handleShare(content) { |
|||
// 处理分享功能 |
|||
uni.showToast({ |
|||
title: '分享功能开发中', |
|||
icon: 'none', |
|||
}) |
|||
}, |
|||
|
|||
refresh() { |
|||
// 刷新页面 |
|||
this.isLoading = true |
|||
this.hasError = false |
|||
|
|||
// 重新初始化 |
|||
setTimeout(() => { |
|||
this.initWebView() |
|||
}, 300) |
|||
}, |
|||
|
|||
retry() { |
|||
// 重试加载 |
|||
this.hasError = false |
|||
this.isLoading = true |
|||
this.initWebView() |
|||
}, |
|||
|
|||
showError(message) { |
|||
this.isLoading = false |
|||
this.hasError = true |
|||
this.errorMessage = message |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.dashboard-webview { |
|||
height: 100vh; |
|||
background-color: #181A20; |
|||
} |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100vh; |
|||
background-color: #181A20; |
|||
} |
|||
|
|||
.loading-text { |
|||
margin-top: 16px; |
|||
font-size: 14px; |
|||
color: #29d3b4; |
|||
} |
|||
|
|||
.webview-container { |
|||
height: 100vh; |
|||
width: 100%; |
|||
} |
|||
|
|||
.error-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100vh; |
|||
background-color: #181A20; |
|||
padding: 40px; |
|||
} |
|||
|
|||
.error-title { |
|||
margin-top: 16px; |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
} |
|||
|
|||
.error-desc { |
|||
margin-top: 8px; |
|||
font-size: 14px; |
|||
color: rgba(255, 255, 255, 0.6); |
|||
text-align: center; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.retry-btn { |
|||
margin-top: 24px; |
|||
padding: 12px 24px; |
|||
background-color: #29d3b4; |
|||
color: #fff; |
|||
border-radius: 6px; |
|||
border: none; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.retry-btn:active { |
|||
background-color: #1a9b7c; |
|||
} |
|||
.dashboard-webview { |
|||
height: 100vh; |
|||
background-color: #181A20; |
|||
} |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100vh; |
|||
background-color: #181A20; |
|||
} |
|||
|
|||
.loading-text { |
|||
margin-top: 16px; |
|||
font-size: 14px; |
|||
color: #29d3b4; |
|||
} |
|||
|
|||
.webview-container { |
|||
height: 100vh; |
|||
width: 100%; |
|||
} |
|||
|
|||
.error-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100vh; |
|||
background-color: #181A20; |
|||
padding: 40px; |
|||
} |
|||
|
|||
.error-title { |
|||
margin-top: 16px; |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
} |
|||
|
|||
.error-desc { |
|||
margin-top: 8px; |
|||
font-size: 14px; |
|||
color: rgba(255, 255, 255, 0.6); |
|||
text-align: center; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.retry-btn { |
|||
margin-top: 24px; |
|||
padding: 12px 24px; |
|||
background-color: #29d3b4; |
|||
color: #fff; |
|||
border-radius: 6px; |
|||
border: none; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.retry-btn:active { |
|||
background-color: #1a9b7c; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue