first commit
This commit is contained in:
944
src/pages/admin/invoices/index.vue
Normal file
944
src/pages/admin/invoices/index.vue
Normal file
@@ -0,0 +1,944 @@
|
||||
<template>
|
||||
<ListPageLayout
|
||||
title="发票管理"
|
||||
subtitle="管理用户的发票申请"
|
||||
>
|
||||
<!-- 统计信息 -->
|
||||
<!-- <template #actions>
|
||||
<div class="flex gap-4">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ stats.total || 0 }}</div>
|
||||
<div class="stat-label">总申请数</div>
|
||||
</div>
|
||||
<div class="stat-item pending">
|
||||
<div class="stat-value">{{ stats.pending || 0 }}</div>
|
||||
<div class="stat-label">待处理</div>
|
||||
</div>
|
||||
<div class="stat-item completed">
|
||||
<div class="stat-value">{{ stats.completed || 0 }}</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</div>
|
||||
<div class="stat-item rejected">
|
||||
<div class="stat-value">{{ stats.rejected || 0 }}</div>
|
||||
<div class="stat-label">已拒绝</div>
|
||||
</div>
|
||||
</div>
|
||||
</template> -->
|
||||
|
||||
<template #filters>
|
||||
<FilterSection>
|
||||
<FilterItem label="状态筛选">
|
||||
<el-select v-model="filters.status" placeholder="全部状态" clearable @change="handleFilterChange">
|
||||
<el-option label="全部状态" value="" />
|
||||
<el-option label="待处理" value="pending" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="已拒绝" value="rejected" />
|
||||
</el-select>
|
||||
</FilterItem>
|
||||
|
||||
<FilterItem label="申请时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handleDateRangeChange"
|
||||
class="w-full"
|
||||
/>
|
||||
</FilterItem>
|
||||
|
||||
<template #stats>
|
||||
共找到 {{ total }} 个申请
|
||||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<el-button @click="resetFilters">重置筛选</el-button>
|
||||
<el-button type="primary" @click="loadApplications">应用筛选</el-button>
|
||||
</template>
|
||||
</FilterSection>
|
||||
</template>
|
||||
|
||||
<template #table>
|
||||
<div v-if="loading" class="flex justify-center items-center py-12">
|
||||
<el-loading size="large" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="applications.length === 0" class="text-center py-12">
|
||||
<el-empty description="暂无发票申请" />
|
||||
</div>
|
||||
|
||||
<div v-else class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||
<el-table
|
||||
:data="applications"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{
|
||||
background: '#f8fafc',
|
||||
color: '#475569',
|
||||
fontWeight: '600',
|
||||
fontSize: '14px'
|
||||
}"
|
||||
:cell-style="{
|
||||
fontSize: '14px',
|
||||
color: '#1e293b'
|
||||
}"
|
||||
>
|
||||
<el-table-column prop="id" label="申请编号" >
|
||||
<template #default="{ row }">
|
||||
<span class="font-mono text-sm text-gray-600">{{ row.id }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
|
||||
<el-table-column prop="invoice_type" label="发票类型" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.invoice_type === 'special' ? 'warning' : 'info'" effect="light">
|
||||
{{ getInvoiceTypeText(row.invoice_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="amount" label="申请金额" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="font-semibold text-green-600">¥{{ formatPrice(row.amount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="company_name" label="申请公司" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<div class="font-medium text-gray-900">{{ row.company_name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ row.taxpayer_id }}</div>
|
||||
<div class="text-sm text-gray-500">{{ row.receiving_email }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="getStatusTagType(row.status)" effect="light">
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="created_at" label="申请时间" width="160">
|
||||
<template #default="{ row }">
|
||||
<div class="text-sm">
|
||||
<div class="text-gray-900">{{ formatDate(row.created_at) }}</div>
|
||||
<div class="text-gray-500">{{ formatTime(row.created_at) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" min-width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center space-x-2">
|
||||
<el-button
|
||||
v-if="row.status === 'pending'"
|
||||
size="small"
|
||||
type="success"
|
||||
@click="handleApprove(row)"
|
||||
>
|
||||
通过
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'pending'"
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleReject(row)"
|
||||
>
|
||||
拒绝
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'completed'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="handleDownload(row)"
|
||||
>
|
||||
下载
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="info"
|
||||
@click="handleViewDetails(row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #pagination>
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #extra>
|
||||
<!-- 通过申请弹窗 -->
|
||||
<el-dialog
|
||||
v-model="approveDialogVisible"
|
||||
title="通过发票申请"
|
||||
width="500px"
|
||||
class="approve-dialog"
|
||||
>
|
||||
<div v-if="selectedApplication" class="space-y-6">
|
||||
<div class="approve-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请编号:</span>
|
||||
<span class="info-value">{{ selectedApplication.id }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请公司:</span>
|
||||
<span class="info-value">{{ selectedApplication.company_name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请金额:</span>
|
||||
<span class="info-value">¥{{ formatPrice(selectedApplication.amount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form ref="approveFormRef" :model="approveForm" :rules="approveRules" label-width="100px">
|
||||
<el-form-item label="上传发票" prop="file">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:file-list="fileList"
|
||||
accept=".pdf,.jpg,.jpeg,.png"
|
||||
class="upload-demo"
|
||||
>
|
||||
<el-button type="primary">选择文件</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
支持 PDF、JPG、PNG 格式,文件大小不超过 10MB
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="管理员备注">
|
||||
<el-input
|
||||
v-model="approveForm.admin_notes"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选:添加备注信息"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="approveDialogVisible = false">取消</el-button>
|
||||
<el-button type="success" @click="confirmApprove" :loading="approveLoading">
|
||||
确认通过
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 拒绝申请弹窗 -->
|
||||
<el-dialog
|
||||
v-model="rejectDialogVisible"
|
||||
title="拒绝发票申请"
|
||||
width="500px"
|
||||
class="reject-dialog"
|
||||
>
|
||||
<div v-if="selectedApplication" class="space-y-6">
|
||||
<div class="reject-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请编号:</span>
|
||||
<span class="info-value">{{ selectedApplication.id }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请公司:</span>
|
||||
<span class="info-value">{{ selectedApplication.company_name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请金额:</span>
|
||||
<span class="info-value">¥{{ formatPrice(selectedApplication.amount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px">
|
||||
<el-form-item label="拒绝原因" prop="reason">
|
||||
<el-input
|
||||
v-model="rejectForm.reason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入拒绝原因"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="rejectDialogVisible = false">取消</el-button>
|
||||
<el-button type="danger" @click="confirmReject" :loading="rejectLoading">
|
||||
确认拒绝
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 申请详情弹窗 -->
|
||||
<el-dialog
|
||||
v-model="detailsDialogVisible"
|
||||
title="申请详情"
|
||||
width="600px"
|
||||
class="details-dialog"
|
||||
>
|
||||
<div v-if="selectedApplication" class="space-y-6">
|
||||
<div class="details-grid">
|
||||
<div class="detail-card">
|
||||
<div class="detail-value">{{ selectedApplication.id }}</div>
|
||||
<div class="detail-label">申请编号</div>
|
||||
</div>
|
||||
<div class="detail-card">
|
||||
<div class="detail-value">¥{{ formatPrice(selectedApplication.amount) }}</div>
|
||||
<div class="detail-label">申请金额</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="details-info">
|
||||
|
||||
<div class="info-item">
|
||||
<span class="info-label">发票类型:</span>
|
||||
<span class="info-value">{{ getInvoiceTypeText(selectedApplication.invoice_type) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请公司:</span>
|
||||
<span class="info-value">{{ selectedApplication.company_name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">纳税人识别号:</span>
|
||||
<span class="info-value">{{ selectedApplication.taxpayer_id }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">接收邮箱:</span>
|
||||
<span class="info-value">{{ selectedApplication.receiving_email }}</span>
|
||||
</div>
|
||||
<div v-if="selectedApplication.invoice_type === 'special'" class="info-item">
|
||||
<span class="info-label">开户银行:</span>
|
||||
<span class="info-value">{{ selectedApplication.bank_name }}</span>
|
||||
</div>
|
||||
<div v-if="selectedApplication.invoice_type === 'special'" class="info-item">
|
||||
<span class="info-label">银行账号:</span>
|
||||
<span class="info-value">{{ selectedApplication.bank_account }}</span>
|
||||
</div>
|
||||
<div v-if="selectedApplication.invoice_type === 'special'" class="info-item">
|
||||
<span class="info-label">企业地址:</span>
|
||||
<span class="info-value">{{ selectedApplication.company_address }}</span>
|
||||
</div>
|
||||
<div v-if="selectedApplication.invoice_type === 'special'" class="info-item">
|
||||
<span class="info-label">企业电话:</span>
|
||||
<span class="info-value">{{ selectedApplication.company_phone }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">申请时间:</span>
|
||||
<span class="info-value">{{ formatDate(selectedApplication.created_at) }}</span>
|
||||
</div>
|
||||
<div v-if="selectedApplication.processed_at" class="info-item">
|
||||
<span class="info-label">处理时间:</span>
|
||||
<span class="info-value">{{ formatDate(selectedApplication.processed_at) }}</span>
|
||||
</div>
|
||||
<div v-if="selectedApplication.reject_reason" class="info-item">
|
||||
<span class="info-label">拒绝原因:</span>
|
||||
<span class="info-value reject-reason">{{ selectedApplication.reject_reason }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</ListPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { adminInvoiceApi } from '@/api'
|
||||
import FilterItem from '@/components/common/FilterItem.vue'
|
||||
import FilterSection from '@/components/common/FilterSection.vue'
|
||||
import ListPageLayout from '@/components/common/ListPageLayout.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const applications = ref([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const dateRange = ref([])
|
||||
|
||||
// 统计数据
|
||||
const stats = ref({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
completed: 0,
|
||||
rejected: 0
|
||||
})
|
||||
|
||||
// 筛选条件
|
||||
const filters = reactive({
|
||||
status: ''
|
||||
})
|
||||
|
||||
// 弹窗状态
|
||||
const approveDialogVisible = ref(false)
|
||||
const rejectDialogVisible = ref(false)
|
||||
const detailsDialogVisible = ref(false)
|
||||
const selectedApplication = ref(null)
|
||||
|
||||
// 表单数据
|
||||
const approveFormRef = ref()
|
||||
const rejectFormRef = ref()
|
||||
const uploadRef = ref()
|
||||
const fileList = ref([])
|
||||
|
||||
const approveForm = reactive({
|
||||
file: null,
|
||||
admin_notes: ''
|
||||
})
|
||||
|
||||
const rejectForm = reactive({
|
||||
reason: ''
|
||||
})
|
||||
|
||||
// 加载状态
|
||||
const approveLoading = ref(false)
|
||||
const rejectLoading = ref(false)
|
||||
|
||||
// 表单验证规则
|
||||
const approveRules = {
|
||||
file: [
|
||||
{ required: true, message: '请选择发票文件', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
const rejectRules = {
|
||||
reason: [
|
||||
{ required: true, message: '请输入拒绝原因', trigger: 'blur' },
|
||||
{ min: 5, message: '拒绝原因至少5个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadApplications()
|
||||
loadStats()
|
||||
})
|
||||
|
||||
// 加载申请列表
|
||||
const loadApplications = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
status: filters.status,
|
||||
}
|
||||
|
||||
// 添加时间范围(转换为后端需要的格式)
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
params.start_time = `${dateRange.value[0]} 00:00:00`
|
||||
params.end_time = `${dateRange.value[1]} 23:59:59`
|
||||
}
|
||||
|
||||
const response = await adminInvoiceApi.getPendingApplications(params)
|
||||
if (response && response.data) {
|
||||
applications.value = response.data.applications || []
|
||||
total.value = response.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载申请列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载统计数据
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
// 这里可以调用专门的统计API,或者从列表数据中计算
|
||||
// 暂时使用模拟数据
|
||||
stats.value = {
|
||||
total: total.value,
|
||||
pending: applications.value.filter(app => app.status === 'pending').length,
|
||||
completed: applications.value.filter(app => app.status === 'completed').length,
|
||||
rejected: applications.value.filter(app => app.status === 'rejected').length
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化价格
|
||||
const formatPrice = (price) => {
|
||||
if (!price) return '0.00'
|
||||
return Number(price).toFixed(2)
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (date) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (date) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取发票类型文本
|
||||
const getInvoiceTypeText = (type) => {
|
||||
switch (type) {
|
||||
case 'general':
|
||||
return '普通发票'
|
||||
case 'special':
|
||||
return '专用发票'
|
||||
default:
|
||||
return '未知类型'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return '待处理'
|
||||
case 'completed':
|
||||
return '已完成'
|
||||
case 'rejected':
|
||||
return '已拒绝'
|
||||
default:
|
||||
return '未知状态'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusTagType = (status) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'warning'
|
||||
case 'completed':
|
||||
return 'success'
|
||||
case 'rejected':
|
||||
return 'danger'
|
||||
default:
|
||||
return 'info'
|
||||
}
|
||||
}
|
||||
|
||||
// 处理筛选变化
|
||||
const handleFilterChange = () => {
|
||||
currentPage.value = 1
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
// 处理日期范围变化
|
||||
const handleDateRangeChange = () => {
|
||||
currentPage.value = 1
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 重置筛选
|
||||
const resetFilters = () => {
|
||||
filters.status = ''
|
||||
dateRange.value = []
|
||||
currentPage.value = 1
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
// 处理分页大小变化
|
||||
const handleSizeChange = (size) => {
|
||||
pageSize.value = size
|
||||
currentPage.value = 1
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
// 处理当前页变化
|
||||
const handleCurrentChange = (page) => {
|
||||
currentPage.value = page
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
// 处理通过申请
|
||||
const handleApprove = (application) => {
|
||||
selectedApplication.value = application
|
||||
approveDialogVisible.value = true
|
||||
fileList.value = []
|
||||
approveForm.file = null
|
||||
approveForm.admin_notes = ''
|
||||
}
|
||||
|
||||
// 处理拒绝申请
|
||||
const handleReject = (application) => {
|
||||
selectedApplication.value = application
|
||||
rejectDialogVisible.value = true
|
||||
rejectForm.reason = ''
|
||||
}
|
||||
|
||||
// 处理查看详情
|
||||
const handleViewDetails = (application) => {
|
||||
selectedApplication.value = application
|
||||
detailsDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 处理下载
|
||||
const handleDownload = async (application) => {
|
||||
try {
|
||||
const response = await adminInvoiceApi.downloadInvoiceFile(application.id)
|
||||
if (response && response.data) {
|
||||
// 创建blob URL
|
||||
const blob = new Blob([response.data], { type: 'application/pdf' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `invoice_${application.id}.pdf`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
// 清理blob URL
|
||||
window.URL.revokeObjectURL(url)
|
||||
ElMessage.success('开始下载发票文件')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载发票失败:', error)
|
||||
ElMessage.error('下载发票失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件选择
|
||||
const handleFileChange = (file) => {
|
||||
fileList.value = [file]
|
||||
approveForm.file = file.raw // 更新表单数据
|
||||
}
|
||||
|
||||
// 确认通过
|
||||
const confirmApprove = async () => {
|
||||
if (!approveFormRef.value) return
|
||||
|
||||
try {
|
||||
await approveFormRef.value.validate()
|
||||
|
||||
approveLoading.value = true
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', approveForm.file)
|
||||
formData.append('admin_notes', approveForm.admin_notes)
|
||||
|
||||
const response = await adminInvoiceApi.approveInvoiceApplication(
|
||||
selectedApplication.value.id,
|
||||
formData
|
||||
)
|
||||
|
||||
if (response) {
|
||||
ElMessage.success('申请已通过')
|
||||
approveDialogVisible.value = false
|
||||
loadApplications()
|
||||
loadStats()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('通过申请失败:', error)
|
||||
} finally {
|
||||
approveLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 确认拒绝
|
||||
const confirmReject = async () => {
|
||||
if (!rejectFormRef.value) return
|
||||
|
||||
try {
|
||||
await rejectFormRef.value.validate()
|
||||
|
||||
rejectLoading.value = true
|
||||
|
||||
const response = await adminInvoiceApi.rejectInvoiceApplication(
|
||||
selectedApplication.value.id,
|
||||
{ reason: rejectForm.reason }
|
||||
)
|
||||
|
||||
if (response) {
|
||||
ElMessage.success('申请已拒绝')
|
||||
rejectDialogVisible.value = false
|
||||
loadApplications()
|
||||
loadStats()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('拒绝申请失败:', error)
|
||||
ElMessage.error('拒绝申请失败')
|
||||
} finally {
|
||||
rejectLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 统计项样式 */
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid rgba(226, 232, 240, 0.6);
|
||||
border-radius: 12px;
|
||||
min-width: 120px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-item.pending {
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
background: rgba(245, 158, 11, 0.05);
|
||||
}
|
||||
|
||||
.stat-item.completed {
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
background: rgba(16, 185, 129, 0.05);
|
||||
}
|
||||
|
||||
.stat-item.rejected {
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
background: rgba(239, 68, 68, 0.05);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.approve-dialog :deep(.el-dialog),
|
||||
.reject-dialog :deep(.el-dialog),
|
||||
.details-dialog :deep(.el-dialog) {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.approve-dialog :deep(.el-dialog__header),
|
||||
.reject-dialog :deep(.el-dialog__header),
|
||||
.details-dialog :deep(.el-dialog__header) {
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.approve-dialog :deep(.el-dialog__title),
|
||||
.reject-dialog :deep(.el-dialog__title),
|
||||
.details-dialog :deep(.el-dialog__title) {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.approve-dialog :deep(.el-dialog__body),
|
||||
.reject-dialog :deep(.el-dialog__body),
|
||||
.details-dialog :deep(.el-dialog__body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.approve-dialog :deep(.el-dialog__footer),
|
||||
.reject-dialog :deep(.el-dialog__footer),
|
||||
.details-dialog :deep(.el-dialog__footer) {
|
||||
background: rgba(248, 250, 252, 0.5);
|
||||
border-top: 1px solid rgba(226, 232, 240, 0.4);
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
/* 申请信息样式 */
|
||||
.approve-info,
|
||||
.reject-info {
|
||||
background: rgba(248, 250, 252, 0.5);
|
||||
border: 1px solid rgba(226, 232, 240, 0.4);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.3);
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.reject-reason {
|
||||
color: #dc2626;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 详情网格样式 */
|
||||
.details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: rgba(248, 250, 252, 0.8);
|
||||
border: 1px solid rgba(226, 232, 240, 0.6);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.detail-card:hover {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
line-height: 1;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 详情信息样式 */
|
||||
.details-info {
|
||||
background: rgba(248, 250, 252, 0.5);
|
||||
border: 1px solid rgba(226, 232, 240, 0.4);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* 上传组件样式 */
|
||||
.upload-demo {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-upload__tip {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8fafc !important;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background: #f8fafc !important;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.stat-item {
|
||||
padding: 12px 16px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user