1577 lines
42 KiB
Vue
1577 lines
42 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="list-page-container">
|
|||
|
|
<div class="list-page-card">
|
|||
|
|
<!-- 页面头部 -->
|
|||
|
|
<div class="list-page-header mb-4">
|
|||
|
|
<div class="flex justify-between items-start">
|
|||
|
|
<div>
|
|||
|
|
<h1 class="list-page-title">发票申请</h1>
|
|||
|
|
<p class="list-page-subtitle">管理您的发票申请和查看申请记录</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="list-page-wrapper">
|
|||
|
|
<!-- 可开票金额信息 -->
|
|||
|
|
<!-- <div class="invoice-info-section">
|
|||
|
|
<div class="invoice-balance-card" :class="getBalanceCardClass()">
|
|||
|
|
<div class="balance-icon">
|
|||
|
|
<i class="el-icon-document"></i>
|
|||
|
|
</div>
|
|||
|
|
<div class="balance-content">
|
|||
|
|
<div class="balance-label">可开票金额</div>
|
|||
|
|
<div class="balance-amount">
|
|||
|
|
¥{{ formatPrice(availableAmount.available_amount || 0) }}
|
|||
|
|
</div>
|
|||
|
|
<div class="balance-details">
|
|||
|
|
<span>总充值:¥{{ formatPrice(availableAmount.total_recharged || 0) }}</span>
|
|||
|
|
<span>已开票:¥{{ formatPrice(availableAmount.total_invoiced || 0) }}</span>
|
|||
|
|
<span v-if="availableAmount.pending_applications > 0" class="pending-amount">
|
|||
|
|
待处理:¥{{ formatPrice(availableAmount.pending_applications || 0) }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="invoice-notice-alert">
|
|||
|
|
<el-alert
|
|||
|
|
title="发票申请说明"
|
|||
|
|
description="您只能申请已充值金额(除去赠送)减去已开票金额的部分。普票和专票需要填写不同的企业信息。"
|
|||
|
|
type="info"
|
|||
|
|
:closable="false"
|
|||
|
|
show-icon
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div> -->
|
|||
|
|
|
|||
|
|
<!-- 标签页 -->
|
|||
|
|
<div class="invoice-tabs">
|
|||
|
|
<div
|
|||
|
|
class="tab-item"
|
|||
|
|
:class="{ active: activeTab === 'info' }"
|
|||
|
|
@click="activeTab = 'info'"
|
|||
|
|
>
|
|||
|
|
<i class="el-icon-edit-outline"></i>
|
|||
|
|
开票信息
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
class="tab-item"
|
|||
|
|
:class="{ active: activeTab === 'records' }"
|
|||
|
|
@click="activeTab = 'records'"
|
|||
|
|
>
|
|||
|
|
<i class="el-icon-document"></i>
|
|||
|
|
申请记录
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 开票信息管理 -->
|
|||
|
|
<div v-if="activeTab === 'info'" class="info-form-section">
|
|||
|
|
<div class="info-header">
|
|||
|
|
<h3 class="section-title">开票信息管理</h3>
|
|||
|
|
<div class="info-actions">
|
|||
|
|
<el-button
|
|||
|
|
v-if="!isEditing"
|
|||
|
|
type="primary"
|
|||
|
|
size="large"
|
|||
|
|
@click="startEdit"
|
|||
|
|
class="edit-btn"
|
|||
|
|
>
|
|||
|
|
<i class="el-icon-edit"></i>
|
|||
|
|
编辑信息
|
|||
|
|
</el-button>
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
size="large"
|
|||
|
|
@click="showApplyDialog = true"
|
|||
|
|
class="apply-btn"
|
|||
|
|
>
|
|||
|
|
<i class="el-icon-plus"></i>
|
|||
|
|
申请开票
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 只读信息显示 -->
|
|||
|
|
<div v-if="!isEditing" class="info-display">
|
|||
|
|
<div class="info-card">
|
|||
|
|
<div class="info-row">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">公司名称:</span>
|
|||
|
|
<span class="info-value">{{ invoiceInfo.company_name || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">纳税人识别号:</span>
|
|||
|
|
<span class="info-value">{{ invoiceInfo.taxpayer_id || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-row">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">开户银行:</span>
|
|||
|
|
<span class="info-value">{{ invoiceInfo.bank_name || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">银行账号:</span>
|
|||
|
|
<span class="info-value">{{ invoiceInfo.bank_account || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-row">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">企业地址:</span>
|
|||
|
|
<span class="info-value">{{ invoiceInfo.company_address || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">企业电话:</span>
|
|||
|
|
<span class="info-value">{{ invoiceInfo.company_phone || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-row">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">接收邮箱:</span>
|
|||
|
|
<span class="info-value">{{ invoiceInfo.receiving_email || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 编辑表单 -->
|
|||
|
|
<el-form
|
|||
|
|
v-else
|
|||
|
|
ref="infoFormRef"
|
|||
|
|
:model="editingInfo"
|
|||
|
|
:rules="infoRules"
|
|||
|
|
label-width="120px"
|
|||
|
|
class="invoice-form"
|
|||
|
|
>
|
|||
|
|
<div class="form-row">
|
|||
|
|
<el-form-item label="公司名称" prop="company_name">
|
|||
|
|
<el-input
|
|||
|
|
v-model="editingInfo.company_name"
|
|||
|
|
placeholder="请输入公司名称"
|
|||
|
|
:disabled="invoiceInfo.company_name_read_only"
|
|||
|
|
:class="{ 'readonly-field': invoiceInfo.company_name_read_only }"
|
|||
|
|
/>
|
|||
|
|
<div v-if="invoiceInfo.company_name_read_only" class="field-note">
|
|||
|
|
<i class="el-icon-info"></i>
|
|||
|
|
公司名称从企业认证信息自动获取,不可修改
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="纳税人识别号" prop="taxpayer_id">
|
|||
|
|
<el-input
|
|||
|
|
v-model="editingInfo.taxpayer_id"
|
|||
|
|
placeholder="请输入纳税人识别号"
|
|||
|
|
:disabled="invoiceInfo.taxpayer_id_read_only"
|
|||
|
|
:class="{ 'readonly-field': invoiceInfo.taxpayer_id_read_only }"
|
|||
|
|
/>
|
|||
|
|
<div v-if="invoiceInfo.taxpayer_id_read_only" class="field-note">
|
|||
|
|
<i class="el-icon-info"></i>
|
|||
|
|
纳税人识别号从企业认证信息自动获取,不可修改
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="form-row">
|
|||
|
|
<el-form-item label="开户银行" prop="bank_name">
|
|||
|
|
<el-input v-model="editingInfo.bank_name" placeholder="请输入开户银行" />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="银行账号" prop="bank_account">
|
|||
|
|
<el-input v-model="editingInfo.bank_account" placeholder="请输入银行账号" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="form-row">
|
|||
|
|
<el-form-item label="企业地址" prop="company_address">
|
|||
|
|
<el-input v-model="editingInfo.company_address" placeholder="请输入企业注册地址" />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="企业电话" prop="company_phone">
|
|||
|
|
<el-input v-model="editingInfo.company_phone" placeholder="请输入企业注册电话" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="form-row">
|
|||
|
|
<el-form-item label="接收邮箱" prop="receiving_email">
|
|||
|
|
<el-input v-model="editingInfo.receiving_email" placeholder="请输入发票接收邮箱" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<el-form-item>
|
|||
|
|
<div class="form-actions">
|
|||
|
|
<el-button @click="cancelEdit" class="cancel-btn"> 取消 </el-button>
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
@click="handleSaveInfo"
|
|||
|
|
:loading="saveLoading"
|
|||
|
|
class="save-btn"
|
|||
|
|
>
|
|||
|
|
保存信息
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
|
|||
|
|
<!-- 信息完整性提示 -->
|
|||
|
|
<div class="info-completeness">
|
|||
|
|
<el-alert
|
|||
|
|
title="开票信息要求"
|
|||
|
|
description="申请专票需要填写所有信息,申请普票至少需要公司名称、纳税人识别号和接收邮箱。"
|
|||
|
|
type="info"
|
|||
|
|
:closable="false"
|
|||
|
|
show-icon
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 申请记录 -->
|
|||
|
|
<div v-if="activeTab === 'records'" class="records-section">
|
|||
|
|
<h3 class="section-title">申请记录</h3>
|
|||
|
|
|
|||
|
|
<!-- 申请说明 -->
|
|||
|
|
<div class="invoice-notice-section">
|
|||
|
|
<el-alert
|
|||
|
|
title="发票申请说明"
|
|||
|
|
description="发票申请提交后,我们将在 1-7 个工作日内处理您的申请。电子发票将发送至您的邮箱,或在开票记录中下载。"
|
|||
|
|
type="info"
|
|||
|
|
:closable="false"
|
|||
|
|
show-icon
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 筛选器 -->
|
|||
|
|
<FilterSection>
|
|||
|
|
<FilterItem label="申请状态">
|
|||
|
|
<el-select
|
|||
|
|
v-model="recordsFilter.status"
|
|||
|
|
placeholder="选择申请状态"
|
|||
|
|
clearable
|
|||
|
|
@change="handleFilterChange"
|
|||
|
|
class="w-full"
|
|||
|
|
>
|
|||
|
|
<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="recordsFilter.dateRange"
|
|||
|
|
type="datetimerange"
|
|||
|
|
range-separator="至"
|
|||
|
|
start-placeholder="开始时间"
|
|||
|
|
end-placeholder="结束时间"
|
|||
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|||
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|||
|
|
@change="handleDateRangeChange"
|
|||
|
|
class="w-full"
|
|||
|
|
/>
|
|||
|
|
</FilterItem>
|
|||
|
|
|
|||
|
|
<template #stats> 共找到 {{ recordsTotal }} 条申请记录 </template>
|
|||
|
|
|
|||
|
|
<template #buttons>
|
|||
|
|
<el-button @click="resetFilters">重置筛选</el-button>
|
|||
|
|
<el-button type="primary" @click="loadRecords">应用筛选</el-button>
|
|||
|
|
</template>
|
|||
|
|
</FilterSection>
|
|||
|
|
|
|||
|
|
<div v-if="recordsLoading" class="flex justify-center items-center py-12">
|
|||
|
|
<el-loading size="large" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-else-if="records.length === 0" class="text-center py-12">
|
|||
|
|
<el-empty description="暂无申请记录" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-else class="records-list">
|
|||
|
|
<div v-for="record in records" :key="record.id" class="record-item">
|
|||
|
|
<div class="record-header">
|
|||
|
|
<div class="record-info">
|
|||
|
|
<div class="record-id">申请编号:{{ record.id }}</div>
|
|||
|
|
<div class="record-status" :class="getStatusClass(record.status)">
|
|||
|
|
{{ getStatusText(record.status) }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="record-amount">¥{{ formatPrice(record.amount) }}</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="record-details">
|
|||
|
|
<div class="detail-row">
|
|||
|
|
<span class="detail-label">发票类型:</span>
|
|||
|
|
<span class="detail-value">{{ getInvoiceTypeText(record.invoice_type) }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="detail-row">
|
|||
|
|
<span class="detail-label">申请时间:</span>
|
|||
|
|
<span class="detail-value">{{ formatDateTime(record.created_at) }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="record.processed_at" class="detail-row">
|
|||
|
|
<span class="detail-label">处理时间:</span>
|
|||
|
|
<span class="detail-value">{{ formatDateTime(record.processed_at) }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="record.reject_reason" class="detail-row">
|
|||
|
|
<span class="detail-label">拒绝原因:</span>
|
|||
|
|
<span class="detail-value reject-reason">{{ record.reject_reason }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 开票信息详情 -->
|
|||
|
|
<div class="invoice-info-details">
|
|||
|
|
<div class="info-section-title">开票信息</div>
|
|||
|
|
<div class="info-grid">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">公司名称:</span>
|
|||
|
|
<span class="info-value">{{ record.company_name || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">纳税人识别号:</span>
|
|||
|
|
<span class="info-value">{{ record.taxpayer_id || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<span class="info-label">接收邮箱:</span>
|
|||
|
|
<span class="info-value">{{ record.receiving_email || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="record.invoice_type === 'special'" class="info-item">
|
|||
|
|
<span class="info-label">开户银行:</span>
|
|||
|
|
<span class="info-value">{{ record.bank_name || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="record.invoice_type === 'special'" class="info-item">
|
|||
|
|
<span class="info-label">银行账号:</span>
|
|||
|
|
<span class="info-value">{{ record.bank_account || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="record.invoice_type === 'special'" class="info-item">
|
|||
|
|
<span class="info-label">企业地址:</span>
|
|||
|
|
<span class="info-value">{{ record.company_address || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="record.invoice_type === 'special'" class="info-item">
|
|||
|
|
<span class="info-label">企业电话:</span>
|
|||
|
|
<span class="info-value">{{ record.company_phone || '未填写' }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="record-actions mt-4">
|
|||
|
|
<el-button
|
|||
|
|
v-if="record.status === 'completed' && record.file_url"
|
|||
|
|
type="primary"
|
|||
|
|
@click="downloadInvoice(record.id)"
|
|||
|
|
>
|
|||
|
|
下载发票
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 分页 -->
|
|||
|
|
<div v-if="recordsTotal > 0" class="records-pagination">
|
|||
|
|
<el-pagination
|
|||
|
|
v-model:current-page="recordsCurrentPage"
|
|||
|
|
v-model:page-size="recordsPageSize"
|
|||
|
|
:page-sizes="[10, 20, 50]"
|
|||
|
|
:total="recordsTotal"
|
|||
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|||
|
|
@size-change="handleRecordsSizeChange"
|
|||
|
|
@current-change="handleRecordsCurrentChange"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 申请开票弹窗 -->
|
|||
|
|
<el-dialog
|
|||
|
|
v-model="showApplyDialog"
|
|||
|
|
title="申请开票"
|
|||
|
|
width="600px"
|
|||
|
|
:close-on-click-modal="false"
|
|||
|
|
:close-on-press-escape="false"
|
|||
|
|
>
|
|||
|
|
<el-form ref="applyFormRef" :model="applyForm" :rules="applyRules" label-width="120px">
|
|||
|
|
<el-form-item label="发票类型" prop="invoice_type">
|
|||
|
|
<el-radio-group v-model="applyForm.invoice_type" class="w-full">
|
|||
|
|
<el-radio label="general">增值税普通发票(普票)</el-radio>
|
|||
|
|
<el-radio label="special">增值税专用发票(专票)</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="开票金额" prop="amount">
|
|||
|
|
<el-input
|
|||
|
|
v-model="applyForm.amount"
|
|||
|
|
placeholder="请输入开票金额"
|
|||
|
|
@input="handleAmountInput"
|
|||
|
|
>
|
|||
|
|
<template #prepend>¥</template>
|
|||
|
|
</el-input>
|
|||
|
|
<div class="form-tip">
|
|||
|
|
可开票金额:¥{{ formatPrice(availableAmount.available_amount || 0) }}
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<!-- 信息预览 -->
|
|||
|
|
<div class="info-preview">
|
|||
|
|
<h4>开票信息预览</h4>
|
|||
|
|
|
|||
|
|
<!-- 申请类型提示 -->
|
|||
|
|
<div class="apply-type-notice">
|
|||
|
|
<el-alert
|
|||
|
|
:title="applyTypeNoticeTitle"
|
|||
|
|
:description="applyTypeNoticeDescription"
|
|||
|
|
:type="applyTypeNoticeType"
|
|||
|
|
:closable="false"
|
|||
|
|
show-icon
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="preview-content">
|
|||
|
|
<div class="preview-item">
|
|||
|
|
<span class="preview-label">公司名称:</span>
|
|||
|
|
<span class="preview-value" :class="{ missing: !invoiceInfo.company_name }">
|
|||
|
|
{{ invoiceInfo.company_name || '未填写' }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="preview-item">
|
|||
|
|
<span class="preview-label">纳税人识别号:</span>
|
|||
|
|
<span class="preview-value" :class="{ missing: !invoiceInfo.taxpayer_id }">
|
|||
|
|
{{ invoiceInfo.taxpayer_id || '未填写' }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="preview-item">
|
|||
|
|
<span class="preview-label">接收邮箱:</span>
|
|||
|
|
<span class="preview-value" :class="{ missing: !invoiceInfo.receiving_email }">
|
|||
|
|
{{ invoiceInfo.receiving_email || '未填写' }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="applyForm.invoice_type === 'special'" class="preview-item">
|
|||
|
|
<span class="preview-label">开户银行:</span>
|
|||
|
|
<span class="preview-value" :class="{ missing: !invoiceInfo.bank_name }">
|
|||
|
|
{{ invoiceInfo.bank_name || '未填写' }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="applyForm.invoice_type === 'special'" class="preview-item">
|
|||
|
|
<span class="preview-label">银行账号:</span>
|
|||
|
|
<span class="preview-value" :class="{ missing: !invoiceInfo.bank_account }">
|
|||
|
|
{{ invoiceInfo.bank_account || '未填写' }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="applyForm.invoice_type === 'special'" class="preview-item">
|
|||
|
|
<span class="preview-label">企业地址:</span>
|
|||
|
|
<span class="preview-value" :class="{ missing: !invoiceInfo.company_address }">
|
|||
|
|
{{ invoiceInfo.company_address || '未填写' }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="applyForm.invoice_type === 'special'" class="preview-item">
|
|||
|
|
<span class="preview-label">企业电话:</span>
|
|||
|
|
<span class="preview-value" :class="{ missing: !invoiceInfo.company_phone }">
|
|||
|
|
{{ invoiceInfo.company_phone || '未填写' }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-form>
|
|||
|
|
|
|||
|
|
<template #footer>
|
|||
|
|
<div class="dialog-footer">
|
|||
|
|
<el-button @click="showApplyDialog = false">取消</el-button>
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
@click="handleApplyInvoice"
|
|||
|
|
:loading="applyLoading"
|
|||
|
|
:disabled="!canSubmitApplyInDialog"
|
|||
|
|
>
|
|||
|
|
确认申请
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
</el-dialog>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { invoiceApi } from '@/api'
|
|||
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|||
|
|
import FilterItem from '@/components/common/FilterItem.vue'
|
|||
|
|
import FilterSection from '@/components/common/FilterSection.vue'
|
|||
|
|
|
|||
|
|
const router = useRouter()
|
|||
|
|
|
|||
|
|
// 响应式数据
|
|||
|
|
const activeTab = ref('info')
|
|||
|
|
const isEditing = ref(false)
|
|||
|
|
const saveLoading = ref(false)
|
|||
|
|
const applyLoading = ref(false)
|
|||
|
|
const showApplyDialog = ref(false)
|
|||
|
|
const recordsLoading = ref(false)
|
|||
|
|
const records = ref([])
|
|||
|
|
const recordsTotal = ref(0)
|
|||
|
|
const recordsCurrentPage = ref(1)
|
|||
|
|
const recordsPageSize = ref(10)
|
|||
|
|
|
|||
|
|
// 可开票金额信息
|
|||
|
|
const availableAmount = ref({
|
|||
|
|
available_amount: 0,
|
|||
|
|
total_recharged: 0,
|
|||
|
|
total_gifted: 0,
|
|||
|
|
total_invoiced: 0,
|
|||
|
|
pending_applications: 0,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 开票信息(只读)
|
|||
|
|
const invoiceInfo = reactive({
|
|||
|
|
company_name: '',
|
|||
|
|
taxpayer_id: '',
|
|||
|
|
bank_name: '',
|
|||
|
|
bank_account: '',
|
|||
|
|
company_address: '',
|
|||
|
|
company_phone: '',
|
|||
|
|
receiving_email: '',
|
|||
|
|
company_name_read_only: false,
|
|||
|
|
taxpayer_id_read_only: false,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 编辑中的开票信息
|
|||
|
|
const editingInfo = reactive({
|
|||
|
|
company_name: '',
|
|||
|
|
taxpayer_id: '',
|
|||
|
|
bank_name: '',
|
|||
|
|
bank_account: '',
|
|||
|
|
company_address: '',
|
|||
|
|
company_phone: '',
|
|||
|
|
receiving_email: '',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 申请开票表单
|
|||
|
|
const applyFormRef = ref()
|
|||
|
|
const applyForm = reactive({
|
|||
|
|
invoice_type: 'general',
|
|||
|
|
amount: '',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 表单引用
|
|||
|
|
const infoFormRef = ref()
|
|||
|
|
|
|||
|
|
// 记录筛选
|
|||
|
|
const recordsFilter = reactive({
|
|||
|
|
status: '',
|
|||
|
|
dateRange: null,
|
|||
|
|
start_time: '',
|
|||
|
|
end_time: '',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 开票信息验证规则
|
|||
|
|
const infoRules = computed(() => ({
|
|||
|
|
company_name: invoiceInfo.company_name_read_only
|
|||
|
|
? []
|
|||
|
|
: [{ required: true, message: '请输入公司名称', trigger: 'blur' }],
|
|||
|
|
taxpayer_id: invoiceInfo.taxpayer_id_read_only
|
|||
|
|
? []
|
|||
|
|
: [{ required: true, message: '请输入纳税人识别号', trigger: 'blur' }],
|
|||
|
|
receiving_email: [
|
|||
|
|
{ required: true, message: '请输入接收邮箱', trigger: 'blur' },
|
|||
|
|
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' },
|
|||
|
|
],
|
|||
|
|
}))
|
|||
|
|
|
|||
|
|
// 申请开票验证规则
|
|||
|
|
const applyRules = {
|
|||
|
|
invoice_type: [{ required: true, message: '请选择发票类型', trigger: 'change' }],
|
|||
|
|
amount: [
|
|||
|
|
{ required: true, message: '请输入开票金额', trigger: 'blur' },
|
|||
|
|
{
|
|||
|
|
validator: (rule, value, callback) => {
|
|||
|
|
if (!value) {
|
|||
|
|
callback()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
const amount = parseFloat(value)
|
|||
|
|
const available = parseFloat(availableAmount.value.available_amount || 0)
|
|||
|
|
if (amount > available) {
|
|||
|
|
callback(new Error(`开票金额不能超过可开票金额 ¥${available}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (amount <= 0) {
|
|||
|
|
callback(new Error('开票金额必须大于0'))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
callback()
|
|||
|
|
},
|
|||
|
|
trigger: 'blur',
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
onMounted(() => {
|
|||
|
|
loadAvailableAmount()
|
|||
|
|
loadUserInvoiceInfo()
|
|||
|
|
loadRecords()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 加载可开票金额
|
|||
|
|
const loadAvailableAmount = async () => {
|
|||
|
|
try {
|
|||
|
|
const response = await invoiceApi.getAvailableAmount()
|
|||
|
|
if (response && response.data) {
|
|||
|
|
availableAmount.value = response.data
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载可开票金额失败:', error)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载用户开票信息
|
|||
|
|
const loadUserInvoiceInfo = async () => {
|
|||
|
|
try {
|
|||
|
|
const response = await invoiceApi.getUserInvoiceInfo()
|
|||
|
|
if (response && response.data) {
|
|||
|
|
Object.assign(invoiceInfo, response.data)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载开票信息失败:', error)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载申请记录
|
|||
|
|
const loadRecords = async () => {
|
|||
|
|
recordsLoading.value = true
|
|||
|
|
try {
|
|||
|
|
const params = {
|
|||
|
|
page: recordsCurrentPage.value,
|
|||
|
|
page_size: recordsPageSize.value,
|
|||
|
|
status: recordsFilter.status,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加时间范围筛选
|
|||
|
|
if (recordsFilter.start_time) {
|
|||
|
|
params.start_time = recordsFilter.start_time
|
|||
|
|
}
|
|||
|
|
if (recordsFilter.end_time) {
|
|||
|
|
params.end_time = recordsFilter.end_time
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await invoiceApi.getUserInvoiceRecords(params)
|
|||
|
|
if (response && response.data) {
|
|||
|
|
records.value = response.data.records || []
|
|||
|
|
recordsTotal.value = response.data.total || 0
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载申请记录失败:', error)
|
|||
|
|
} finally {
|
|||
|
|
recordsLoading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 格式化价格
|
|||
|
|
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 formatDateTime = (date) => {
|
|||
|
|
if (!date) return '-'
|
|||
|
|
const d = new Date(date)
|
|||
|
|
const year = d.getFullYear()
|
|||
|
|
const month = String(d.getMonth() + 1).padStart(2, '0')
|
|||
|
|
const day = String(d.getDate()).padStart(2, '0')
|
|||
|
|
const hours = String(d.getHours()).padStart(2, '0')
|
|||
|
|
const minutes = String(d.getMinutes()).padStart(2, '0')
|
|||
|
|
const seconds = String(d.getSeconds()).padStart(2, '0')
|
|||
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取余额卡片样式类
|
|||
|
|
const getBalanceCardClass = () => {
|
|||
|
|
const available = parseFloat(availableAmount.value.available_amount || 0)
|
|||
|
|
if (available <= 0) {
|
|||
|
|
return 'balance-card-empty'
|
|||
|
|
}
|
|||
|
|
return ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理金额输入
|
|||
|
|
const handleAmountInput = (event) => {
|
|||
|
|
const value = event.target.value
|
|||
|
|
// 只允许数字和小数点
|
|||
|
|
const formatted = value.replace(/[^\d.]/g, '')
|
|||
|
|
applyForm.amount = formatted
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取状态样式类
|
|||
|
|
const getStatusClass = (status) => {
|
|||
|
|
switch (status) {
|
|||
|
|
case 'pending':
|
|||
|
|
return 'status-pending'
|
|||
|
|
case 'completed':
|
|||
|
|
return 'status-completed'
|
|||
|
|
case 'rejected':
|
|||
|
|
return 'status-rejected'
|
|||
|
|
default:
|
|||
|
|
return ''
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取状态文本
|
|||
|
|
const getStatusText = (status) => {
|
|||
|
|
switch (status) {
|
|||
|
|
case 'pending':
|
|||
|
|
return '待处理'
|
|||
|
|
case 'completed':
|
|||
|
|
return '已完成'
|
|||
|
|
case 'rejected':
|
|||
|
|
return '已拒绝'
|
|||
|
|
default:
|
|||
|
|
return '未知状态'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取发票类型文本
|
|||
|
|
const getInvoiceTypeText = (type) => {
|
|||
|
|
switch (type) {
|
|||
|
|
case 'general':
|
|||
|
|
return '普通发票'
|
|||
|
|
case 'special':
|
|||
|
|
return '专用发票'
|
|||
|
|
default:
|
|||
|
|
return '未知类型'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 开始编辑
|
|||
|
|
const startEdit = () => {
|
|||
|
|
// 复制当前信息到编辑表单
|
|||
|
|
Object.assign(editingInfo, invoiceInfo)
|
|||
|
|
isEditing.value = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 取消编辑
|
|||
|
|
const cancelEdit = () => {
|
|||
|
|
isEditing.value = false
|
|||
|
|
// 重置编辑表单
|
|||
|
|
Object.assign(editingInfo, {
|
|||
|
|
company_name: '',
|
|||
|
|
taxpayer_id: '',
|
|||
|
|
bank_name: '',
|
|||
|
|
bank_account: '',
|
|||
|
|
company_address: '',
|
|||
|
|
company_phone: '',
|
|||
|
|
receiving_email: '',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存开票信息
|
|||
|
|
const handleSaveInfo = async () => {
|
|||
|
|
if (!infoFormRef.value) return
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await infoFormRef.value.validate()
|
|||
|
|
saveLoading.value = true
|
|||
|
|
|
|||
|
|
const response = await invoiceApi.updateUserInvoiceInfo(editingInfo)
|
|||
|
|
if (response && response.success) {
|
|||
|
|
// 更新只读信息
|
|||
|
|
Object.assign(invoiceInfo, editingInfo)
|
|||
|
|
ElMessage.success('开票信息保存成功')
|
|||
|
|
isEditing.value = false
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('保存开票信息失败:', error)
|
|||
|
|
} finally {
|
|||
|
|
saveLoading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 申请开票
|
|||
|
|
const handleApplyInvoice = async () => {
|
|||
|
|
if (!applyFormRef.value) return
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await applyFormRef.value.validate()
|
|||
|
|
|
|||
|
|
// 显示确认框
|
|||
|
|
await ElMessageBox.confirm(`确认申请开票 ¥${applyForm.amount} 吗?`, '确认申请', {
|
|||
|
|
confirmButtonText: '确认申请',
|
|||
|
|
cancelButtonText: '取消',
|
|||
|
|
type: 'warning',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
applyLoading.value = true
|
|||
|
|
|
|||
|
|
const response = await invoiceApi.applyInvoice({
|
|||
|
|
invoice_type: applyForm.invoice_type,
|
|||
|
|
amount: applyForm.amount,
|
|||
|
|
...invoiceInfo,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (response && response.data) {
|
|||
|
|
ElMessage.success('发票申请提交成功')
|
|||
|
|
// 重置表单
|
|||
|
|
applyFormRef.value.resetFields()
|
|||
|
|
// 关闭弹窗
|
|||
|
|
showApplyDialog.value = false
|
|||
|
|
// 重新加载数据
|
|||
|
|
loadAvailableAmount()
|
|||
|
|
loadRecords()
|
|||
|
|
// 切换到记录页
|
|||
|
|
activeTab.value = 'records'
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error === 'cancel' || error === 'close') {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
console.error('提交申请失败:', error)
|
|||
|
|
} finally {
|
|||
|
|
applyLoading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 下载发票
|
|||
|
|
const downloadInvoice = async (applicationId) => {
|
|||
|
|
try {
|
|||
|
|
const response = await invoiceApi.downloadInvoiceFile(applicationId)
|
|||
|
|
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_${applicationId}.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 canApplyInvoice = computed(() => {
|
|||
|
|
// 普票至少需要:公司名称、纳税人识别号、接收邮箱
|
|||
|
|
const hasBasicInfo =
|
|||
|
|
invoiceInfo.company_name && invoiceInfo.taxpayer_id && invoiceInfo.receiving_email
|
|||
|
|
|
|||
|
|
// 专票还需要:开户银行、银行账号、企业地址、企业电话
|
|||
|
|
const hasSpecialInfo =
|
|||
|
|
invoiceInfo.bank_name &&
|
|||
|
|
invoiceInfo.bank_account &&
|
|||
|
|
invoiceInfo.company_address &&
|
|||
|
|
invoiceInfo.company_phone
|
|||
|
|
|
|||
|
|
return hasBasicInfo && (applyForm.invoice_type === 'general' || hasSpecialInfo)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const canSubmitApply = computed(() => {
|
|||
|
|
return canApplyInvoice.value && applyForm.amount && parseFloat(applyForm.amount) > 0
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 弹窗中的申请类型提示
|
|||
|
|
const applyTypeNoticeTitle = computed(() => {
|
|||
|
|
if (applyForm.invoice_type === 'general') {
|
|||
|
|
return '普通发票申请'
|
|||
|
|
} else {
|
|||
|
|
return '专用发票申请'
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const applyTypeNoticeDescription = computed(() => {
|
|||
|
|
// 检查基本信息(普票和专票都需要)
|
|||
|
|
const hasBasicInfo =
|
|||
|
|
invoiceInfo.company_name && invoiceInfo.taxpayer_id && invoiceInfo.receiving_email
|
|||
|
|
|
|||
|
|
if (!hasBasicInfo) {
|
|||
|
|
// 基本信息不完整
|
|||
|
|
const missing = []
|
|||
|
|
if (!invoiceInfo.company_name) missing.push('公司名称')
|
|||
|
|
if (!invoiceInfo.taxpayer_id) missing.push('纳税人识别号')
|
|||
|
|
if (!invoiceInfo.receiving_email) missing.push('接收邮箱')
|
|||
|
|
|
|||
|
|
return `请先填写以下信息:${missing.join('、')}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (applyForm.invoice_type === 'general') {
|
|||
|
|
// 普票基本信息已完整
|
|||
|
|
return '基本信息已完整,请确认开票信息无误后提交申请'
|
|||
|
|
} else {
|
|||
|
|
// 专票需要检查额外信息
|
|||
|
|
const hasSpecialInfo =
|
|||
|
|
invoiceInfo.bank_name &&
|
|||
|
|
invoiceInfo.bank_account &&
|
|||
|
|
invoiceInfo.company_address &&
|
|||
|
|
invoiceInfo.company_phone
|
|||
|
|
|
|||
|
|
if (!hasSpecialInfo) {
|
|||
|
|
// 专票额外信息不完整
|
|||
|
|
const missing = []
|
|||
|
|
if (!invoiceInfo.bank_name) missing.push('开户银行')
|
|||
|
|
if (!invoiceInfo.bank_account) missing.push('银行账号')
|
|||
|
|
if (!invoiceInfo.company_address) missing.push('企业地址')
|
|||
|
|
if (!invoiceInfo.company_phone) missing.push('企业电话')
|
|||
|
|
|
|||
|
|
return `申请专票还需要填写:${missing.join('、')}`
|
|||
|
|
} else {
|
|||
|
|
// 专票信息已完整
|
|||
|
|
return '所有信息已完整,请确认开票信息无误后提交申请'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const applyTypeNoticeType = computed(() => {
|
|||
|
|
// 检查基本信息(普票和专票都需要)
|
|||
|
|
const hasBasicInfo =
|
|||
|
|
invoiceInfo.company_name && invoiceInfo.taxpayer_id && invoiceInfo.receiving_email
|
|||
|
|
|
|||
|
|
if (!hasBasicInfo) {
|
|||
|
|
return 'error' // 基本信息缺失,显示错误提示
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (applyForm.invoice_type === 'general') {
|
|||
|
|
return 'success' // 普票基本信息完整,显示成功提示
|
|||
|
|
} else {
|
|||
|
|
// 专票需要检查额外信息
|
|||
|
|
const hasSpecialInfo =
|
|||
|
|
invoiceInfo.bank_name &&
|
|||
|
|
invoiceInfo.bank_account &&
|
|||
|
|
invoiceInfo.company_address &&
|
|||
|
|
invoiceInfo.company_phone
|
|||
|
|
|
|||
|
|
if (!hasSpecialInfo) {
|
|||
|
|
return 'warning' // 专票额外信息缺失,显示警告提示
|
|||
|
|
} else {
|
|||
|
|
return 'success' // 专票信息完整,显示成功提示
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 弹窗中的提交按钮状态
|
|||
|
|
const canSubmitApplyInDialog = computed(() => {
|
|||
|
|
// 检查金额
|
|||
|
|
if (!applyForm.amount || parseFloat(applyForm.amount) <= 0) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查基本信息(普票和专票都需要)
|
|||
|
|
const hasBasicInfo =
|
|||
|
|
invoiceInfo.company_name && invoiceInfo.taxpayer_id && invoiceInfo.receiving_email
|
|||
|
|
|
|||
|
|
if (!hasBasicInfo) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是专票,还需要检查额外信息
|
|||
|
|
if (applyForm.invoice_type === 'special') {
|
|||
|
|
const hasSpecialInfo =
|
|||
|
|
invoiceInfo.bank_name &&
|
|||
|
|
invoiceInfo.bank_account &&
|
|||
|
|
invoiceInfo.company_address &&
|
|||
|
|
invoiceInfo.company_phone
|
|||
|
|
return hasSpecialInfo
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 分页处理
|
|||
|
|
const handleRecordsSizeChange = (size) => {
|
|||
|
|
recordsPageSize.value = size
|
|||
|
|
recordsCurrentPage.value = 1
|
|||
|
|
loadRecords()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleRecordsCurrentChange = (page) => {
|
|||
|
|
recordsCurrentPage.value = page
|
|||
|
|
loadRecords()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理筛选变化
|
|||
|
|
const handleFilterChange = () => {
|
|||
|
|
recordsCurrentPage.value = 1
|
|||
|
|
loadRecords()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理时间范围变化
|
|||
|
|
const handleDateRangeChange = (range) => {
|
|||
|
|
if (range && range.length === 2) {
|
|||
|
|
recordsFilter.start_time = range[0]
|
|||
|
|
recordsFilter.end_time = range[1]
|
|||
|
|
} else {
|
|||
|
|
recordsFilter.start_time = ''
|
|||
|
|
recordsFilter.end_time = ''
|
|||
|
|
}
|
|||
|
|
recordsCurrentPage.value = 1
|
|||
|
|
loadRecords()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清除筛选
|
|||
|
|
const resetFilters = () => {
|
|||
|
|
recordsFilter.status = ''
|
|||
|
|
recordsFilter.dateRange = null
|
|||
|
|
recordsFilter.start_time = ''
|
|||
|
|
recordsFilter.end_time = ''
|
|||
|
|
recordsCurrentPage.value = 1
|
|||
|
|
loadRecords()
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
/* 发票信息区域 */
|
|||
|
|
.invoice-info-section {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.invoice-balance-card {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
background: linear-gradient(135deg, #10b981 80%, #a7f3d0 100%);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
padding: 16px;
|
|||
|
|
color: white;
|
|||
|
|
box-shadow:
|
|||
|
|
0 4px 16px 0 rgba(0, 0, 0, 0.1),
|
|||
|
|
0 2px 8px 0 rgba(16, 185, 129, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-icon {
|
|||
|
|
font-size: 32px;
|
|||
|
|
margin-right: 16px;
|
|||
|
|
opacity: 0.9;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-content {
|
|||
|
|
flex: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-label {
|
|||
|
|
font-size: 14px;
|
|||
|
|
opacity: 0.9;
|
|||
|
|
margin-bottom: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-amount {
|
|||
|
|
font-size: 28px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
line-height: 1;
|
|||
|
|
margin-bottom: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-details {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 12px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
opacity: 0.8;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pending-amount {
|
|||
|
|
color: #fde68a;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 发票申请提示 */
|
|||
|
|
.invoice-notice-alert {
|
|||
|
|
margin: 12px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 余额状态卡片样式 */
|
|||
|
|
.balance-card-empty {
|
|||
|
|
background: linear-gradient(135deg, #6b7280 60%, #d1d5db 100%) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 标签页样式 */
|
|||
|
|
.invoice-tabs {
|
|||
|
|
display: flex;
|
|||
|
|
background: rgba(255, 255, 255, 0.8);
|
|||
|
|
border: 1px solid rgba(226, 232, 240, 0.6);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 3px;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tab-item {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
padding: 8px 16px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #64748b;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tab-item:hover {
|
|||
|
|
background: rgba(59, 130, 246, 0.1);
|
|||
|
|
color: #3b82f6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tab-item.active {
|
|||
|
|
background: #3b82f6;
|
|||
|
|
color: white;
|
|||
|
|
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 开票信息表单区域 */
|
|||
|
|
.info-form-section {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #1e293b;
|
|||
|
|
margin-bottom: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-actions {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.edit-btn {
|
|||
|
|
height: 40px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.apply-btn {
|
|||
|
|
height: 40px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.invoice-form {
|
|||
|
|
background: rgba(255, 255, 255, 0.8);
|
|||
|
|
border: 1px solid rgba(226, 232, 240, 0.6);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 只读字段样式 */
|
|||
|
|
.readonly-field .el-input__inner {
|
|||
|
|
background-color: #f5f7fa;
|
|||
|
|
color: #606266;
|
|||
|
|
cursor: not-allowed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.field-note {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #909399;
|
|||
|
|
margin-top: 4px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.field-note i {
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 申请类型提示样式 */
|
|||
|
|
.apply-type-notice {
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 缺失字段样式 */
|
|||
|
|
.preview-value.missing {
|
|||
|
|
color: #f56c6c;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-row {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 1fr 1fr;
|
|||
|
|
gap: 16px;
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-section {
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-section-title {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #374151;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
padding-bottom: 6px;
|
|||
|
|
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.special-fields {
|
|||
|
|
background: rgba(59, 130, 246, 0.05);
|
|||
|
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
|||
|
|
border-radius: 6px;
|
|||
|
|
padding: 12px;
|
|||
|
|
margin-top: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.amount-input {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-tip {
|
|||
|
|
font-size: 11px;
|
|||
|
|
color: #64748b;
|
|||
|
|
margin-top: 3px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.submit-btn {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 40px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 记录区域 */
|
|||
|
|
.records-section {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 记录区域样式优化 */
|
|||
|
|
.records-section {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 申请说明样式 */
|
|||
|
|
.invoice-notice-section {
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 开票信息详情样式 */
|
|||
|
|
.invoice-info-details {
|
|||
|
|
margin-top: 16px;
|
|||
|
|
padding: 16px;
|
|||
|
|
background: rgba(248, 250, 252, 0.8);
|
|||
|
|
border: 1px solid rgba(226, 232, 240, 0.6);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-section-title {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #374151;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
padding-bottom: 6px;
|
|||
|
|
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-label {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #6b7280;
|
|||
|
|
min-width: 100px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-value {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #374151;
|
|||
|
|
word-break: break-all;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.records-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-item {
|
|||
|
|
background: rgba(255, 255, 255, 0.8);
|
|||
|
|
border: 1px solid rgba(226, 232, 240, 0.6);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 16px;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-item:hover {
|
|||
|
|
border-color: rgba(59, 130, 246, 0.3);
|
|||
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
padding-bottom: 8px;
|
|||
|
|
border-bottom: 1px solid rgba(226, 232, 240, 0.4);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-info {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-id {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #64748b;
|
|||
|
|
font-family: 'Courier New', monospace;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-status {
|
|||
|
|
padding: 3px 10px;
|
|||
|
|
border-radius: 16px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-pending {
|
|||
|
|
background: rgba(245, 158, 11, 0.1);
|
|||
|
|
color: #d97706;
|
|||
|
|
border: 1px solid rgba(245, 158, 11, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-completed {
|
|||
|
|
background: rgba(16, 185, 129, 0.1);
|
|||
|
|
color: #059669;
|
|||
|
|
border: 1px solid rgba(16, 185, 129, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-rejected {
|
|||
|
|
background: rgba(239, 68, 68, 0.1);
|
|||
|
|
color: #dc2626;
|
|||
|
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-amount {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
color: #10b981;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-details {
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.detail-row {
|
|||
|
|
display: flex;
|
|||
|
|
margin-bottom: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.detail-label {
|
|||
|
|
width: 90px;
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #64748b;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.detail-value {
|
|||
|
|
flex: 1;
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #1e293b;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.reject-reason {
|
|||
|
|
color: #dc2626;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-actions {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.records-pagination {
|
|||
|
|
margin-top: 16px;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 信息完整性提示 */
|
|||
|
|
.info-completeness {
|
|||
|
|
margin-top: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 申请弹窗样式 */
|
|||
|
|
.info-preview {
|
|||
|
|
margin-top: 16px;
|
|||
|
|
padding: 16px;
|
|||
|
|
background: rgba(59, 130, 246, 0.05);
|
|||
|
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-preview h4 {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #374151;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-content {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-label {
|
|||
|
|
width: 120px;
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #64748b;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-value {
|
|||
|
|
flex: 1;
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #1e293b;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dialog-footer {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 信息显示样式 */
|
|||
|
|
.info-display {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-card {
|
|||
|
|
background: rgba(255, 255, 255, 0.8);
|
|||
|
|
border: 1px solid rgba(226, 232, 240, 0.6);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-row {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 1fr 1fr;
|
|||
|
|
gap: 20px;
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-row:last-child {
|
|||
|
|
margin-bottom: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-label {
|
|||
|
|
width: 120px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #64748b;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-value {
|
|||
|
|
flex: 1;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #1e293b;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 表单操作按钮 */
|
|||
|
|
.form-actions {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cancel-btn {
|
|||
|
|
height: 40px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.save-btn {
|
|||
|
|
height: 40px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 响应式设计 */
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.invoice-balance-card {
|
|||
|
|
padding: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-icon {
|
|||
|
|
font-size: 28px;
|
|||
|
|
margin-right: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-amount {
|
|||
|
|
font-size: 24px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.balance-details {
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.invoice-tabs {
|
|||
|
|
flex-direction: column;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-row {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-header {
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.record-info {
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.detail-row {
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 3px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.detail-label {
|
|||
|
|
width: auto;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|