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.

347 lines
8.8 KiB

<template>
<view class="reservation-page">
<view class="fixed-nav">
<NavBar title="发票管理">
</NavBar>
</view>
<view class="content-area">
<template v-if="invoiceList.length > 0">
<view v-for="item in invoiceList" :key="item.id" class="invoice-card">
<view class="invoice-header">
<view class="status-tag pending">待开具</view>
<view class="invoice-date">{{ formatChinaDate(item.created_at) }}</view>
</view>
<view class="invoice-title">{{ item.direction_name }}</view>
<view class="invoice-amount">¥{{ (item.price || 0) }}</view>
<view class="invoice-batch">批次号:{{ item.batch && item.batch.name ? item.batch.name : '-' }}</view>
<view class="invoice-ship">船舶编号:{{ item.ship ? item.ship.ship_number : '-' }}</view>
<view class="invoice-actions single-btn">
<button
class="invoice-detail-btn issue"
@click="handleInvoiceAction(item)"
>
{{ item.bill_status === 3 ? '查看发票' : '去开票' }}
</button>
</view>
</view>
</template>
<template v-else>
<view class="empty-box">
<image src="/static/empty.png" class="empty-img" mode="aspectFit" />
<view class="empty-text">暂无发票</view>
</view>
</template>
<!-- 加载更多提示 -->
<view v-if="invoiceList.length > 0" class="load-more">
<view v-if="loading" class="loading-text">加载中...</view>
<view v-else-if="!hasMore" class="no-more-text"></view>
</view>
</view>
</view>
</template>
<script>
import NavBar from '@/components/NavBar.vue'
import { base } from '@/common/util.js'
import { API } from '@/config/index.js'
export default {
name: 'InvoiceManagePage',
components: { NavBar },
data() {
return {
invoiceList: [],
page: 1,
lastPage: 1,
loading: false,
hasMore: true
}
},
onShow() {
// 重置分页
this.page = 1
this.hasMore = true
this.fetchInvoiceList(true)
},
// 下拉刷新
onPullDownRefresh() {
this.page = 1
this.hasMore = true
this.fetchInvoiceList(true).finally(() => {
uni.stopPullDownRefresh()
})
},
// 上拉加载更多
onReachBottom() {
if (this.hasMore && !this.loading) {
this.loadMore()
}
},
methods: {
formatChinaDate: base.formatChinaDate,
async fetchInvoiceList(reset = false) {
const token = uni.getStorageSync('token')
if (!token) return
// 如果正在加载,直接返回
if (this.loading) return
this.loading = true
try {
const res = await new Promise((resolve, reject) => {
uni.request({
url: `${API.RESERVATION_LIST}?token=${token}`,
method: 'GET',
data: {
page: this.page,
page_size: 10,
bill_status: 4 // 只获取待开票的订单
},
success: resolve,
fail: reject
})
})
if (res.data && res.data.errcode === 0) {
const newList = res.data.data.data || []
this.lastPage = res.data.data.last_page || 1
if (reset) {
// 重置列表
this.invoiceList = newList
} else {
// 追加数据
this.invoiceList = [...this.invoiceList, ...newList]
}
// 判断是否还有更多数据
this.hasMore = this.page < this.lastPage
} else {
uni.showToast({
title: res.data.errmsg || '获取发票列表失败',
icon: 'none'
})
}
} catch (e) {
console.error('获取发票列表失败:', e)
uni.showToast({
title: '网络错误',
icon: 'none'
})
} finally {
this.loading = false
}
},
// 加载更多
loadMore() {
if (this.hasMore && !this.loading) {
this.page += 1
this.fetchInvoiceList(false)
}
},
handleInvoiceAction(item) {
if (!item) return
if (item.bill_status === 3) {
const bill = item.bill || {}
const billInfoList = Array.isArray(bill.billInfoList) ? bill.billInfoList : []
const firstInfo = billInfoList[0] || {}
const pictureUrl = firstInfo.pictureUrl || ''
if (pictureUrl) {
this.openExternalLink(pictureUrl)
} else {
uni.showToast({ title: '暂无发票链接', icon: 'none' })
}
return
}
this.goInvoice(item)
},
goInvoice(item) {
const token = uni.getStorageSync('token')
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none' })
return
}
if (!item || !item.id) {
uni.showToast({ title: '订单信息缺失', icon: 'none' })
return
}
uni.showLoading({ title: '提交中...' })
uni.request({
url: `${API.GET_INVOICE}`,
method: 'GET',
data: {
reservation_id: item.id,
token
},
success: (res) => {
uni.hideLoading()
if (res.data && res.data.errcode === 0) {
const billData = res.data && res.data.data
const billInfo = billData && billData.bill
const billInfoList = billInfo && billInfo.billInfoList
const pictureUrl = Array.isArray(billInfoList) && billInfoList.length > 0
? billInfoList[0].pictureUrl
: ''
uni.showToast({
title: '开票申请已提交',
icon: 'success',
duration: 2000,
success: () => {
if (pictureUrl) {
this.openExternalLink(pictureUrl)
}
}
})
this.page = 1
this.hasMore = true
this.fetchInvoiceList(true)
} else {
uni.showToast({ title: (res.data && res.data.errmsg) || '提交失败', icon: 'none' })
}
},
fail: () => {
uni.hideLoading()
uni.showToast({ title: '提交失败', icon: 'none' })
}
})
},
openExternalLink(url) {
if (!url) return
window.open(url, '_blank')
}
}
}
</script>
<style scoped>
.reservation-page {
background: linear-gradient(180deg, #eaf3ff 0%, #f6faff 100%);
min-height: 100vh;
padding-bottom: 40rpx;
}
.fixed-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: linear-gradient(180deg, #cbe6ff 0%, #f6faff 100%);
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
}
.content-area {
padding: 220rpx 24rpx 24rpx 24rpx;
}
.invoice-card {
background: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
padding: 32rpx 28rpx;
margin-bottom: 32rpx;
border: 1rpx solid #e5e5e5;
}
.invoice-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.status-tag {
padding: 6rpx 24rpx;
border-radius: 20rpx;
font-size: 24rpx;
color: #fff;
}
.status-tag.issued {
background: #2ecc71;
}
.status-tag.pending {
background: #ffa940;
}
.invoice-date {
color: #888;
font-size: 24rpx;
}
.invoice-title {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 12rpx;
}
.invoice-amount {
font-size: 32rpx;
color: #222;
font-weight: bold;
margin-bottom: 8rpx;
}
.invoice-batch {
font-size: 24rpx;
color: #888;
margin-bottom: 8rpx;
}
.invoice-ship {
font-size: 24rpx;
color: #888;
margin-bottom: 16rpx;
}
.invoice-actions {
display: flex;
justify-content: flex-end;
}
.invoice-actions.single-btn .invoice-detail-btn {
flex: 0 0 auto;
width: 153px;
}
.invoice-detail-btn {
background: #e4f3fe;
color: #217aff;
height: 69rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
margin-right: 24rpx;
border-radius: 4px;
padding: 8px 0;
font-weight: 500;
border: none;
outline: none;
}
.invoice-detail-btn.issue {
background: #217aff;
color: #fff;
}
.invoice-btn {
display: none;
}
.empty-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 120rpx;
}
.empty-img {
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
}
.empty-text {
color: #888;
font-size: 28rpx;
}
.load-more {
padding: 30rpx 0;
text-align: center;
}
.loading-text {
color: #888;
font-size: 28rpx;
}
.no-more-text {
color: #999;
font-size: 26rpx;
}
</style>