<template>
|
<view class="invoice-list-container">
|
<view class="add-btn-box">
|
<button class="cu-btn block bg-blue lg" @click="handleApply">申请发票</button>
|
</view>
|
|
<!-- 搜索框 -->
|
<view class="search-box">
|
<view class="search-input-wrapper">
|
<input
|
class="search-input"
|
placeholder="请输入服务单号搜索"
|
v-model="searchKeyword"
|
@confirm="handleSearch"
|
/>
|
<text class="cuIcon-search search-icon" @click="handleSearch"></text>
|
<text v-if="searchKeyword" class="cuIcon-close clear-icon" @click="clearSearch"></text>
|
</view>
|
</view>
|
|
<!-- 状态Tab -->
|
<view class="tabs-wrapper">
|
<view
|
class="tab-item"
|
:class="currentTab === null ? 'active' : ''"
|
@click="switchTab(null)"
|
>
|
<text>全部</text>
|
</view>
|
<view
|
class="tab-item"
|
:class="currentTab === 0 ? 'active' : ''"
|
@click="switchTab(0)"
|
>
|
<text>待审核</text>
|
</view>
|
<view
|
class="tab-item"
|
:class="currentTab === 1 ? 'active' : ''"
|
@click="switchTab(1)"
|
>
|
<text>已通过</text>
|
</view>
|
<view
|
class="tab-item"
|
:class="currentTab === 2 ? 'active' : ''"
|
@click="switchTab(2)"
|
>
|
<text>已驳回</text>
|
</view>
|
</view>
|
|
<scroll-view scroll-y="true" class="list-scroll" @scrolltolower="loadMore">
|
<!-- 空列表提示 -->
|
<view v-if="list.length === 0" class="empty-box">
|
<text class="text-gray">暂无发票申请记录</text>
|
</view>
|
|
<!-- 发票列表 -->
|
<view v-for="(item, index) in list" :key="index" class="invoice-item bg-white margin-sm padding-sm radius shadow">
|
<view class="flex justify-between border-bottom padding-bottom-xs margin-bottom-xs">
|
<text class="text-bold">服务单号: {{ item.serviceCode || item.legacyServiceOrderId || '未知' }}</text>
|
<text :class="item.status === 0 ? 'text-orange' : item.status === 1 ? 'text-green' : item.status === 2 ? 'text-red' : 'text-gray'">{{ item.status === 0 ? '待审核' : item.status === 1 ? '已通过' : item.status === 2 ? '已驳回' : '未知' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">发票抬头:</text>
|
<text class="value">{{ item.invoiceName }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">申请金额:</text>
|
<text class="value text-red">¥{{ item.invoiceMoney ? Number(item.invoiceMoney).toFixed(2) : '0.00' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">申请时间:</text>
|
<text class="value">{{ formatApplyTime(item.applyTime) }}</text>
|
</view>
|
<view v-if="item.invoiceNo" class="info-row">
|
<text class="label">发票编号:</text>
|
<text class="value">{{ item.invoiceNo }}</text>
|
</view>
|
|
<view class="action-bar margin-top-sm flex justify-end" v-if="item.invoiceUrl">
|
<button class="cu-btn sm bg-green" @click="handleDownload" :data-url="item.invoiceUrl">查看发票</button>
|
</view>
|
<view v-if="item.status === 2 && item.auditRemarks" class="margin-top-xs padding-xs bg-gray radius">
|
<text class="text-sm text-red">驳回原因: {{ item.auditRemarks }}</text>
|
</view>
|
</view>
|
</scroll-view>
|
</view>
|
</template>
|
|
<script>
|
import { listMyInvoice } from "@/api/invoice"
|
import config from '@/config.js'
|
|
export default {
|
data() {
|
return {
|
list: [],
|
queryParams: {
|
pageNum: 1,
|
pageSize: 10,
|
serviceCode: null, // 服务单号搜索
|
status: null // 状态筛选
|
},
|
total: 0,
|
searchKeyword: '', // 搜索关键词
|
currentTab: null // 当前Tab:null=全部, 0=待审核, 1=已通过, 2=已驳回
|
}
|
},
|
onShow() {
|
this.queryParams.pageNum = 1
|
this.list = []
|
this.getList()
|
},
|
methods: {
|
getList() {
|
listMyInvoice(this.queryParams).then(res => {
|
if (res.rows) {
|
let rows = res.rows;
|
// 按申请时间倒序排序
|
rows.sort((a, b) => {
|
// 如果有 applyTime,则比较时间,否则放在最后
|
if (!a.applyTime) return 1;
|
if (!b.applyTime) return -1;
|
return new Date(b.applyTime) - new Date(a.applyTime);
|
});
|
this.list = this.list.concat(rows);
|
this.total = res.total || 0;
|
}
|
}).catch(err => {
|
console.error('获取发票列表失败:', err)
|
this.$modal.msgError('加载失败,请重试')
|
})
|
},
|
// 搜索
|
handleSearch() {
|
this.queryParams.serviceCode = this.searchKeyword.trim() || null
|
this.resetList()
|
},
|
// 清空搜索
|
clearSearch() {
|
this.searchKeyword = ''
|
this.queryParams.serviceCode = null
|
this.resetList()
|
},
|
// 切换Tab
|
switchTab(status) {
|
this.currentTab = status
|
this.queryParams.status = status
|
this.resetList()
|
},
|
// 重置列表
|
resetList() {
|
this.queryParams.pageNum = 1
|
this.list = []
|
this.getList()
|
},
|
loadMore() {
|
if (this.list.length < this.total) {
|
this.queryParams.pageNum++
|
this.getList()
|
}
|
},
|
handleApply() {
|
this.$tab.navigateTo('/pages/mine/invoice/apply')
|
},
|
handleDownload(e) {
|
const url = e.currentTarget.dataset.url
|
console.log('=== 发票下载调试信息 ===');
|
console.log('1. 原始URL:', url)
|
|
if (!url) {
|
this.$modal.msgError('发票文件地址为空')
|
return
|
}
|
|
// 处理URL,确保是完整的地址
|
let fullUrl = url
|
if (!url.startsWith('http')) {
|
// 如果是相对路径,拼接完整地址
|
fullUrl = config.baseUrl + url
|
}
|
|
console.log('2. baseUrl:', config.baseUrl)
|
console.log('3. 完整URL:', fullUrl)
|
|
const fileExt = fullUrl.match(/\.(\w+)$/)?.[1]?.toLowerCase()
|
console.log('4. 文件扩展名:', fileExt)
|
|
// #ifdef H5
|
console.log('5. H5环境,直接打开')
|
window.open(fullUrl)
|
// #endif
|
|
// #ifndef H5
|
console.log('5. 小程序/App环境')
|
|
// 先预览图片,如果是图片格式
|
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
|
if (imageExts.includes(fileExt)) {
|
console.log('6. 图片文件,使用previewImage')
|
uni.previewImage({
|
urls: [fullUrl],
|
current: fullUrl,
|
success: () => {
|
console.log('图片预览成功')
|
},
|
fail: (err) => {
|
console.error('图片预览失败:', err)
|
this.$modal.msgError(`无法预览图片: ${err.errMsg || '未知错误'}`)
|
}
|
})
|
return
|
}
|
|
// PDF文件下载并打开
|
console.log('6. PDF文件,使用downloadFile')
|
uni.showLoading({ title: '下载中...', mask: true })
|
|
uni.downloadFile({
|
url: fullUrl,
|
success: (res) => {
|
console.log('7. 下载成功:', res)
|
uni.hideLoading()
|
|
if (res.statusCode === 200) {
|
console.log('8. 打开文档:', res.tempFilePath)
|
uni.openDocument({
|
filePath: res.tempFilePath,
|
showMenu: true,
|
success: function (openRes) {
|
console.log('9. 文档打开成功:', openRes)
|
},
|
fail: function (err) {
|
console.error('9. 文档打开失败:', err)
|
uni.showModal({
|
title: '提示',
|
content: `无法打开文件: ${err.errMsg || '未知错误'}`,
|
showCancel: false
|
})
|
}
|
})
|
} else {
|
console.error('8. 下载失败,状态码:', res.statusCode)
|
this.$modal.msgError(`下载失败,状态码: ${res.statusCode}`)
|
}
|
},
|
fail: (err) => {
|
console.error('7. 下载失败:', err)
|
uni.hideLoading()
|
uni.showModal({
|
title: '下载失败',
|
content: `错误信息: ${err.errMsg || '未知错误'}\n\n请检查:\n1. 网络连接是否正常\n2. 文件是否存在\n3. 服务器是否可访问`,
|
showCancel: false
|
})
|
}
|
})
|
// #endif
|
console.log('=== 调试信息结束 ===');
|
},
|
statusText(status) {
|
const map = { 0: '待审核', 1: '已通过', 2: '已驳回' }
|
return map[status] || '未知'
|
},
|
statusClass(status) {
|
const map = { 0: 'text-orange', 1: 'text-green', 2: 'text-red' }
|
return map[status] || 'text-gray'
|
},
|
formatMoney(money) {
|
if (money === null || money === undefined) return '0.00'
|
return Number(money).toFixed(2)
|
},
|
// 格式化申请时间
|
formatApplyTime(time) {
|
if (!time) return ''
|
// 将时间字符串格式化为 yyyy-MM-dd HH:mm
|
const date = new Date(time)
|
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}`
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss">
|
.invoice-list-container {
|
height: 100vh;
|
display: flex;
|
flex-direction: column;
|
background-color: #f8f8f8;
|
|
.add-btn-box {
|
padding: 20rpx;
|
background-color: #fff;
|
}
|
|
// 搜索框样式
|
.search-box {
|
padding: 20rpx;
|
background-color: #fff;
|
border-bottom: 1rpx solid #e5e5e5;
|
|
.search-input-wrapper {
|
position: relative;
|
background: #f5f5f5;
|
border-radius: 40rpx;
|
padding: 0 80rpx 0 40rpx;
|
|
.search-input {
|
height: 70rpx;
|
line-height: 70rpx;
|
font-size: 28rpx;
|
}
|
|
.search-icon {
|
position: absolute;
|
right: 40rpx;
|
top: 50%;
|
transform: translateY(-50%);
|
color: #999;
|
font-size: 36rpx;
|
}
|
|
.clear-icon {
|
position: absolute;
|
right: 80rpx;
|
top: 50%;
|
transform: translateY(-50%);
|
color: #999;
|
font-size: 32rpx;
|
padding: 10rpx;
|
}
|
}
|
}
|
|
// Tab样式
|
.tabs-wrapper {
|
display: flex;
|
background-color: #fff;
|
border-bottom: 1rpx solid #e5e5e5;
|
|
.tab-item {
|
flex: 1;
|
text-align: center;
|
padding: 25rpx 0;
|
font-size: 28rpx;
|
color: #666;
|
position: relative;
|
|
&.active {
|
color: #0081ff;
|
font-weight: bold;
|
|
&::after {
|
content: '';
|
position: absolute;
|
bottom: 0;
|
left: 50%;
|
transform: translateX(-50%);
|
width: 60rpx;
|
height: 4rpx;
|
background-color: #0081ff;
|
border-radius: 2rpx;
|
}
|
}
|
}
|
}
|
|
.list-scroll {
|
flex: 1;
|
overflow: hidden;
|
}
|
|
.invoice-item {
|
.border-bottom {
|
border-bottom: 1rpx solid #eee;
|
}
|
|
.info-row {
|
display: flex;
|
line-height: 1.8;
|
font-size: 26rpx;
|
|
.label {
|
color: #666;
|
width: 140rpx;
|
}
|
.value {
|
color: #333;
|
flex: 1;
|
}
|
}
|
}
|
|
.empty-box {
|
padding: 100rpx;
|
text-align: center;
|
}
|
}
|
</style>
|