2025-04-25 16:10:27 +08:00

1573 lines
58 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="bill-detail-container">
<el-tabs v-model="activeTab">
<el-tab-pane label="账单基本信息" name="basicInfo">
<div class="bill-header">
<div class="bill-header-left">
<h3>账单信息</h3>
</div>
<div class="bill-header-right">
<el-button
type="primary"
@click="openPaymentDialog"
:disabled="!canPayBill">
账单支付
</el-button>
</div>
</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="账单编号">{{ billDetail.totBillNo }}</el-descriptions-item>
<el-descriptions-item label="账单来源">{{ getBillSourceName(billDetail.billSource) }}</el-descriptions-item>
<el-descriptions-item label="账单状态">
<el-tag :type="billDetail.billStatus === '1' ? 'success' : 'info'">
{{ billDetail.billStatus === '1' ? '开启' : '关闭' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="对方名称">{{ billDetail.paysdName }}</el-descriptions-item>
<el-descriptions-item label="支付联系方式">{{ billDetail.payeeContact }}</el-descriptions-item>
<el-descriptions-item label="关联合同">{{ billDetail.contractNumber || '无' }}</el-descriptions-item>
<el-descriptions-item label="费用类型">{{ billDetail.feTpName }}</el-descriptions-item>
<el-descriptions-item label="计费周期">
{{ billDetail.chggBgnDt }} 至 {{ billDetail.chggEndDt }}
</el-descriptions-item>
<el-descriptions-item label="应收日期">{{ billDetail.pybDt }}</el-descriptions-item>
<el-descriptions-item label="结清状态">
<el-tag :type="getClearStatusType(billDetail.clearStatus)">
{{ getClearStatusName(billDetail.clearStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="逾期状态">
<el-tag :type="billDetail.overdueStatus === '1' ? 'danger' : 'success'">
{{ billDetail.overdueStatus === '1' ? '逾期' : '正常' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="滞纳金状态">
<el-tag :type="getLateFeeStatusType(billDetail.ovdueStatus)">
{{ getLateFeeStatusName(billDetail.ovdueStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="账单金额">{{ formatAmount(billDetail.accblAmt) }}</el-descriptions-item>
<el-descriptions-item label="应收金额">{{ formatAmount(billDetail.accigRcvAmt) }}</el-descriptions-item>
<el-descriptions-item label="实收金额">{{ formatAmount(billDetail.atmRecvAmt) }}</el-descriptions-item>
<el-descriptions-item label="需收金额">{{ formatAmount(billDetail.needAmount) }}</el-descriptions-item>
<el-descriptions-item label="调整金额">{{ formatAmount(billDetail.adjAmt) }}</el-descriptions-item>
<el-descriptions-item label="应收滞纳金">{{ formatAmount(billDetail.receivableOvdueAmt) }}</el-descriptions-item>
<el-descriptions-item label="税率">{{ billDetail.taxRate }}%</el-descriptions-item>
<el-descriptions-item label="税额">{{ formatAmount(billDetail.paybleTaxAmount) }}</el-descriptions-item>
<el-descriptions-item label="含税规则">{{ billDetail.taxInclusiveRule }}</el-descriptions-item>
<el-descriptions-item label="特殊账单类型">{{ billDetail.specialBillType }}</el-descriptions-item>
<el-descriptions-item label="开据状态">
<el-tag :type="billDetail.receiptStatus === '1' ? 'success' : 'info'">
{{ billDetail.receiptStatus === '1' ? '已开据' : '未开据' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="所属公司">{{ billDetail.corNm }}</el-descriptions-item>
<el-descriptions-item label="租赁数(计租面积)">{{ billDetail.rentArea }} ㎡</el-descriptions-item>
<el-descriptions-item label="项目名称">{{ billDetail.projectName }}</el-descriptions-item>
<el-descriptions-item label="账单备注" :span="3">{{ billDetail.billRemark || '无' }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="账单明细查询" name="billDetails">
<el-table :data="billDetailsList" border style="width: 100%">
<el-table-column prop="feeTypeName" label="费用类型" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column label="应收金额" min-width="120" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.receivableAmount) }}
</template>
</el-table-column>
<el-table-column label="税率" min-width="80" align="center">
<template slot-scope="scope">
{{ scope.row.taxRate }}%
</template>
</el-table-column>
<el-table-column label="税额" min-width="120" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.taxAmount) }}
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始日期" min-width="120" align="center"></el-table-column>
<el-table-column prop="endDate" label="结束日期" min-width="120" align="center"></el-table-column>
<el-table-column prop="remark" label="账单备注" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
@click="handleAdjustment(scope.row)"
:disabled="!canAdjustBill">
调整
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="billDetailsList.length === 0" class="empty-data">
<el-empty description="暂无账单明细"></el-empty>
</div>
</el-tab-pane>
<el-tab-pane label="账单调整" name="billAdjustment">
<div class="bill-adjustment-container">
<div class="adjustment-list">
<el-table :data="adjustmentList" border style="width: 100%; margin-top: 15px">
<el-table-column prop="adjTpCd" label="调整类型" min-width="100">
<template slot-scope="scope">
{{ scope.row.adjTpCd === '1' ? '调增' : '调减' }}
</template>
</el-table-column>
<el-table-column prop="adjDate" label="调整日期" min-width="120" align="center"></el-table-column>
<el-table-column prop="adjMethod" label="调整方式" min-width="120">
<template slot-scope="scope">
{{ scope.row.adjMethod === '1' ? '按金额调整' : '按比例调整' }}
</template>
</el-table-column>
<el-table-column label="调整金额" min-width="120" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.adjAmt) }}
</template>
</el-table-column>
<el-table-column label="调整比例" min-width="100" align="center">
<template slot-scope="scope">
{{ scope.row.adjRate ? scope.row.adjRate + '%' : '-' }}
</template>
</el-table-column>
<el-table-column prop="adjStatus" label="调整状态" min-width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.adjStatus === '1' ? 'success' : 'info'">
{{ scope.row.adjStatus === '1' ? '正常' : '作废' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="billDetailType" label="账单明细类型" min-width="120">
<template slot-scope="scope">
{{ scope.row.billDetailType === '1' ? '滞纳金' : '原账单' }}
</template>
</el-table-column>
<el-table-column prop="adjSource" label="调整来源" min-width="120">
<template slot-scope="scope">
{{ scope.row.adjSource === '1' ? '账单直接调整' : '合同调整' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="150" align="center"></el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="viewAdjustmentDetail(scope.row)" v-if="scope.row.adjStatus === '1'">附件详情</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="voidAdjustment(scope.row)" v-if="scope.row.adjStatus === '1'">作废</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="adjustmentList.length === 0" class="empty-data">
<el-empty description="暂无账单调整记录"></el-empty>
</div>
<div class="pagination-container" v-if="adjustmentList.length > 0">
<el-pagination
background
@size-change="handleAdjustmentSizeChange"
@current-change="handleAdjustmentCurrentChange"
:current-page="adjustmentQuery.pageNum"
:page-sizes="[5, 10, 20, 50]"
:page-size="adjustmentQuery.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="adjustmentTotal">
</el-pagination>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="房源信息" name="roomInfo">
<el-table :data="roomList" border style="width: 100%">
<el-table-column prop="roomNumber" label="房号" min-width="100"></el-table-column>
<el-table-column prop="floorName" label="楼层" min-width="100"></el-table-column>
<el-table-column prop="buildingName" label="楼宇" min-width="120"></el-table-column>
<el-table-column prop="projectName" label="项目" min-width="150"></el-table-column>
<el-table-column prop="rentArea" label="计租面积(㎡)" min-width="120" align="right"></el-table-column>
</el-table>
<div v-if="roomList.length === 0" class="empty-data">
<el-empty description="暂无房源信息"></el-empty>
</div>
</el-tab-pane>
<el-tab-pane label="附件信息" name="attachment">
<div class="attachment-header">
<el-upload
class="upload-area"
action="#"
:http-request="uploadFile"
:before-upload="beforeFileUpload"
multiple
:limit="10">
<el-button type="primary" icon="el-icon-plus">添加附件</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/pdf文件且不超过5MB</div>
</el-upload>
</div>
<el-table :data="attachmentList" border style="width: 100%; margin-top: 15px">
<el-table-column prop="fileName" label="文件名" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="operatorName" label="操作人" width="150"></el-table-column>
<el-table-column prop="operateTime" label="操作时间" width="180"></el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="previewFile(scope.row)">预览</el-button>
<el-button size="mini" type="text" @click="downloadFile(scope.row)">下载</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="deleteFile(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="attachmentList.length === 0" class="empty-data">
<el-empty description="暂无附件信息"></el-empty>
</div>
</el-tab-pane>
<el-tab-pane label="操作记录" name="operationLog">
<el-table :data="operationList" border style="width: 100%">
<el-table-column prop="oprContent" label="操作内容" min-width="300" show-overflow-tooltip></el-table-column>
<el-table-column prop="oprPersonNm" label="操作人" width="150"></el-table-column>
<el-table-column prop="oprTime" label="操作时间" width="180"></el-table-column>
</el-table>
<div v-if="operationList.length === 0" class="empty-data">
<el-empty description="暂无操作记录"></el-empty>
</div>
<div class="pagination-container" v-if="operationTotal > 0">
<el-pagination
background
@size-change="handleOperationSizeChange"
@current-change="handleOperationCurrentChange"
:current-page="operationQuery.pageNum"
:page-sizes="[5, 10, 20, 50]"
:page-size="operationQuery.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="operationTotal">
</el-pagination>
</div>
</el-tab-pane>
</el-tabs>
<!-- 文件预览对话框 -->
<el-dialog title="附件预览" :visible.sync="previewDialogVisible" width="800px" append-to-body>
<div class="preview-container" v-loading="previewLoading">
<div v-if="previewUrl && isPdf" class="preview-iframe">
<iframe :src="previewUrl" width="100%" height="500px" frameborder="0"></iframe>
</div>
<div v-else-if="previewUrl && isImage" class="preview-image">
<img :src="previewUrl" style="max-width: 100%;" />
</div>
<div v-else-if="!previewLoading" class="preview-error">
<i class="el-icon-warning"></i>
<p>预览加载失败,请尝试下载后查看</p>
<div class="preview-actions">
<el-button type="primary" @click="downloadCurrentPreview">下载文件</el-button>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="previewDialogVisible = false">关 闭</el-button>
</div>
</el-dialog>
<!-- 账单调整对话框 -->
<el-dialog title="账单调整" :visible.sync="adjustmentDialogVisible" width="700px" append-to-body @close="resetAdjustmentForm">
<el-form ref="adjustmentForm" :model="adjustmentForm" :rules="adjustmentRules" label-width="120px">
<el-form-item label="调整类型" prop="adjTpCd" required>
<el-radio-group v-model="adjustmentForm.adjTpCd">
<el-radio label="1">调增</el-radio>
<el-radio label="2">调减</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="调整日期" prop="adjDate" required>
<el-date-picker
v-model="adjustmentForm.adjDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 220px">
</el-date-picker>
</el-form-item>
<el-form-item label="调整方式" prop="adjMethod" required>
<el-radio-group v-model="adjustmentForm.adjMethod" @change="handleAdjMethodChange">
<el-radio label="1">按金额调整</el-radio>
<el-radio label="2">按比例调整</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="调整金额" prop="adjAmt" required v-if="adjustmentForm.adjMethod == '1'">
<el-input-number
v-model="adjustmentForm.adjAmt"
:min="0"
:precision="2"
:max="adjustmentForm.adjTpCd === '2' ? billDetail.accigRcvAmt : undefined"
@change="calculateAdjustAmount"
style="width: 220px">
</el-input-number>
<span class="form-hint">调整后金额: {{ formatAmount(adjustedAmount) }}</span>
</el-form-item>
<el-form-item label="调整比例(%)" prop="adjRate" required v-if="adjustmentForm.adjMethod == '2'">
<el-input-number
v-model="adjustmentForm.adjRate"
:min="0"
:max="adjustmentForm.adjTpCd === '2' ? 100 : undefined"
:precision="2"
@change="calculateAdjustAmount"
style="width: 220px">
</el-input-number>
<span class="form-hint">调整后金额: {{ formatAmount(adjustedAmount) }}</span>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
type="textarea"
v-model="adjustmentForm.remark"
placeholder="请输入调整原因或其他备注信息"
:rows="3"
style="width: 400px">
</el-input>
</el-form-item>
<el-form-item label="附件">
<el-upload
class="upload-area"
action="#"
:http-request="uploadAdjustmentAttachment"
:file-list="adjustmentFileList"
:before-upload="beforeAdjustmentUpload"
multiple
:limit="10">
<el-button size="small" type="primary">添加附件</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/pdf文件且不超过5MB</div>
</el-upload>
<el-table v-if="adjustmentFileList.length > 0" :data="adjustmentFileList" border style="width: 100%; margin-top: 15px">
<el-table-column prop="name" label="文件名" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="previewAdjustmentFile(scope.row)">预览</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="deleteAdjustmentFile(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="adjustmentDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitAdjustment" :loading="adjustmentSubmitting">确 定</el-button>
</div>
</el-dialog>
<!-- 账单调整附件详情弹窗 -->
<el-dialog title="账单调整附件详情" :visible.sync="adjustmentAttachmentDialogVisible" width="800px" append-to-body>
<el-table :data="adjustmentAttachmentList" border style="width: 100%">
<el-table-column prop="fileName" label="文件名" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="createUserId" label="操作人" width="150"></el-table-column>
<el-table-column prop="createTime" label="操作时间" width="180"></el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="previewFile(scope.row)">预览</el-button>
<el-button size="mini" type="text" @click="downloadFile(scope.row)">下载</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="deleteAdjustmentAttachment(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="adjustmentAttachmentList.length === 0" class="empty-data">
<el-empty description="暂无附件信息"></el-empty>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="adjustmentAttachmentDialogVisible = false">关 闭</el-button>
</div>
</el-dialog>
<!-- 账单支付对话框 -->
<el-dialog title="账单支付" :visible.sync="paymentDialogVisible" width="800px" append-to-body @close="resetPaymentForm">
<el-form ref="paymentForm" :model="paymentForm" :rules="paymentRules" label-width="120px" class="payment-form">
<!-- 第一部分:结清状态 -->
<div class="payment-section">
<h4>账单状态</h4>
<el-descriptions :column="1" border size="medium">
<el-descriptions-item label="结清状态">
<el-tag :type="getClearStatusType(billDetail.clearStatus)">
{{ getClearStatusName(billDetail.clearStatus) }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 第二部分:可编辑费用列表 -->
<div class="payment-section">
<h4>费用信息</h4>
<el-descriptions :column="2" border size="medium">
<el-descriptions-item label="费用类型">{{ billDetail.feTpName }}</el-descriptions-item>
<el-descriptions-item label="应收金额">{{ formatAmount(billDetail.accigRcvAmt) }}</el-descriptions-item>
<el-descriptions-item label="实收金额">{{ formatAmount(billDetail.atmRecvAmt) }}</el-descriptions-item>
<el-descriptions-item label="需收金额">{{ formatAmount(billDetail.needAmount) }}</el-descriptions-item>
</el-descriptions>
<el-form-item style="margin-top: 20px;" label="本次收款" prop="occuAmt" required>
<div class="amount-input-wrapper">
<el-input-number
v-model="paymentForm.occuAmt"
:min="0.01"
:max="billDetail.needAmount || 0"
:precision="2"
:step="0.01"
@change="calculateRemainingAmount"
style="width: 300px;margin-right: 10px;">
</el-input-number>
<el-button size="small" type="primary" @click="fillFullAmount" class="fill-btn">收完</el-button>
</div>
</el-form-item>
<el-form-item label="剩余待收">
<span>{{ formatAmount(remainingAmount) }}</span>
</el-form-item>
<el-form-item label="入账时间" prop="inaccDate" required>
<el-date-picker
v-model="paymentForm.inaccDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 300px">
</el-date-picker>
</el-form-item>
</div>
<!-- 第三部分:支付方式 -->
<div class="payment-section">
<h4>支付方式</h4>
<el-form-item label="支付方式" prop="payModeName" required>
<el-select v-model="paymentForm.payModeName" placeholder="请选择支付方式" style="width: 300px">
<el-option
v-for="item in paymentMethods"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="凭证号">
<el-input v-model="paymentForm.txVchrNo" placeholder="请输入凭证号" style="width: 300px"></el-input>
</el-form-item>
<el-form-item label="摘要">
<el-input v-model="paymentForm.summ" placeholder="请输入摘要" style="width: 300px"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
type="textarea"
v-model="paymentForm.remark"
placeholder="请输入备注信息"
:rows="3"
style="width: 300px">
</el-input>
</el-form-item>
</div>
<!-- 第四部分:附件信息 -->
<div class="payment-section">
<h4>附件信息</h4>
<div class="upload-wrapper">
<el-upload
class="upload-area"
action="#"
:http-request="uploadPaymentAttachment"
:file-list="paymentFileList"
:before-upload="beforePaymentUpload"
multiple
:limit="10">
<el-button size="small" type="primary">添加附件</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/pdf文件且不超过5MB</div>
</el-upload>
</div>
<el-table v-if="paymentFileList.length > 0" :data="paymentFileList" border style="width: 100%; margin-top: 15px">
<el-table-column prop="name" label="文件名" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="previewPaymentFile(scope.row)">预览</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="deletePaymentFile(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="paymentDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitPayment" :loading="paymentSubmitting"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getBillDetail, getBillAttachmentList, deleteBillAttachment, getBillAdjustmentList, addBillAdjustment, voidBillAdjustment, uploadBillAdjustmentAttachment, uploadBillAttachment, getBillAdjustmentAttachmentList, deleteBillAdjustmentAttachment, getBillOperationRecords, submitBillPayment, uploadBillPaymentAttachment, deleteBillPaymentAttachment } from '@/api/finance'
export default {
name: 'BillDetail',
props: {
billId: {
type: [Number, String],
required: true
}
},
data() {
return {
activeTab: 'basicInfo',
loading: false,
billDetail: {},
billDetailsList: [],
roomList: [],
attachmentList: [],
operationList: [],
// 账单调整相关
adjustmentList: [],
adjustmentTotal: 0,
adjustmentQuery: {
pageNum: 1,
pageSize: 10,
billId: ''
},
adjustmentLoading: false,
adjustmentDialogVisible: false,
adjustmentSubmitting: false,
adjustedAmount: 0,
adjustmentForm: {
billId: '',
billDetailId: '',
adjTpCd: '1', // 默认调增
adjDate: new Date().toISOString().slice(0, 10), // 默认今天
adjMethod: '1', // 默认按金额调整
adjAmt: 0,
adjRate: 0,
billDetailType: '原账单',
adjSource: '账单直接调整',
projectId: '',
remark: '',
adjSource: '1' // 默认 账单直接调整
},
adjustmentRules: {
adjTpCd: [
{ required: true, message: '请选择调整类型', trigger: 'change' }
],
adjDate: [
{ required: true, message: '请选择调整日期', trigger: 'change' }
],
adjMethod: [
{ required: true, message: '请选择调整方式', trigger: 'change' }
],
adjAmt: [
{ required: true, message: '请输入调整金额', trigger: 'blur' }
],
adjRate: [
{ required: true, message: '请输入调整比例', trigger: 'blur' }
]
},
adjustmentFileList: [],
// 预览相关
previewDialogVisible: false,
previewLoading: false,
previewUrl: '',
previewBlob: null,
currentPreviewName: '',
isPdf: false,
isImage: false,
// 调整附件相关
adjustmentAttachmentDialogVisible: false,
adjustmentAttachmentList: [],
currentAdjustmentId: '',
// 操作记录相关
operationTotal: 0,
operationQuery: {
pageNum: 1,
pageSize: 10
},
// 支付相关
paymentDialogVisible: false,
paymentSubmitting: false,
paymentForm: {
billId: '',
billDetailId: '',
occuAmt: 0,
inaccDate: new Date().toISOString().slice(0, 10),
payModeName: '1',
txVchrNo: '',
summ: '',
remark: ''
},
paymentRules: {
occuAmt: [
{ required: true, message: '请输入本次收款金额', trigger: 'blur' }
],
inaccDate: [
{ required: true, message: '请选择入账时间', trigger: 'change' }
],
payModeName: [
{ required: true, message: '请选择支付方式', trigger: 'change' }
]
},
paymentFileList: [],
paymentMethods: [
{ value: '1', label: '现金' },
{ value: '2', label: '网银转账' },
{ value: '3', label: 'POS机' },
{ value: '4', label: '支付宝' },
{ value: '5', label: '微信' },
{ value: '6', label: '转账支票' },
{ value: '7', label: '其他方式' },
{ value: '8', label: '线上支付' }
],
remainingAmount: 0, // 剩余待收金额
}
},
created() {
this.getBillData()
},
computed: {
// 判断是否可以调整账单
canAdjustBill() {
if (!this.billDetail) return false
// 账单状态为开启且不在支付中
return this.billDetail.billStatus === '1' && this.billDetail.clearStatus !== '6'
},
// 判断是否可以支付账单
canPayBill() {
if (!this.billDetail) return false
// 账单不能是已结清,且不能在支付中
return this.billDetail.clearStatus !== '1' && this.billDetail.clearStatus !== '6'
}
},
methods: {
// 获取账单数据
getBillData() {
this.loading = true
// 获取账单详情
getBillDetail(this.billId).then(response => {
this.billDetail = response.data || {}
// 处理账单明细
this.processBillDetails()
// 设置调整查询参数的账单ID
this.adjustmentQuery.billId = this.billId
// 获取账单调整记录
this.getAdjustmentList()
// 模拟房源信息数据,实际应从接口获取或者账单详情中提取
this.roomList = this.billDetail.rooms
this.loading = false
}).catch(() => {
this.loading = false
})
// 获取附件列表
this.getAttachmentList()
// 获取操作记录
this.getOperationList()
},
// 处理账单明细数据
processBillDetails() {
this.billDetailsList = []
// 添加原始账单明细
if (this.billDetail) {
this.billDetailsList.push({
feeTypeName: this.billDetail.feTpName || '-',
receivableAmount: this.billDetail.accigRcvAmt,
taxRate: this.billDetail.taxRate || 0,
taxAmount: this.billDetail.paybleTaxAmount || 0,
startDate: this.billDetail.chggBgnDt || '-',
endDate: this.billDetail.chggEndDt || '-',
remark: this.billDetail.billRemark || '-',
billDetailType: '0' // 原账单类型
})
}
// 添加滞纳金明细(如果存在)
if (this.billDetail.ovdueStatus === '1' && this.billDetail.receivableOvdueAmt > 0) {
this.billDetailsList.push({
feeTypeName: '滞纳金',
receivableAmount: this.billDetail.receivableOvdueAmt || 0,
taxRate: 0,
taxAmount: 0,
startDate: this.billDetail.pybDt || '-', // 滞纳金产生日期,使用应收日期
endDate: this.formatDate(new Date()) || '-', // 滞纳金产生的结束日期,使用当前日期
remark: '滞纳金',
billDetailType: '1' // 滞纳金类型
})
}
},
// 格式化日期
formatDate(date) {
if (!(date instanceof Date)) return '';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
// 格式化金额
formatAmount(amount) {
if (amount === undefined || amount === null) {
return '0.00'
}
return parseFloat(amount).toFixed(2) + ' 元'
},
// 获取账单来源名称
getBillSourceName(source) {
const sourceMap = {
'1': '合同账单',
'2': '收费标准账单',
'3': '自建全部账单',
'4': '自建关联合同账单',
'5': '自建未关联合同账单'
}
return sourceMap[source] || '未知来源'
},
// 获取结清状态类型
getClearStatusType(status) {
const statusMap = {
'0': 'info',
'1': 'success',
'2': 'warning',
'3': 'danger',
'5': 'primary',
'6': 'info',
'7': 'success'
}
return statusMap[status] || 'info'
},
// 获取结清状态名称
getClearStatusName(status) {
const statusMap = {
'0': '未付款',
'1': '已结清',
'2': '部分结清',
'3': '待退款',
'5': '待收款',
'6': '对账确认中',
'7': '已付款'
}
return statusMap[status] || '未知状态'
},
// 获取滞纳金状态类型
getLateFeeStatusType(status) {
const statusMap = {
'0': 'info',
'1': 'warning',
'2': 'success'
}
return statusMap[status] || 'info'
},
// 获取滞纳金状态名称
getLateFeeStatusName(status) {
const statusMap = {
'0': '无滞纳金',
'1': '有滞纳金',
'2': '滞纳金免除'
}
return statusMap[status] || '未知状态'
},
// 预览文件
previewFile(file) {
this.previewLoading = true
this.previewDialogVisible = true
this.currentPreviewName = file.fileName || '附件'
this.previewUrl = ''
this.isPdf = false
this.isImage = false
// 根据文件名后缀判断文件类型
const fileExt = file.fileName ? file.fileName.split('.').pop().toLowerCase() : ''
try {
// 创建blob对象 - 实际项目中应该调用预览API获取文件数据
fetch(file.storePath)
.then(response => response.blob())
.then(blob => {
this.previewBlob = blob
// 判断文件类型
if (fileExt === 'pdf') {
this.isPdf = true
this.isImage = false
} else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) {
this.isPdf = false
this.isImage = true
}
// 创建URL用于预览
this.previewUrl = URL.createObjectURL(blob)
this.previewLoading = false
})
.catch(error => {
console.error('预览失败', error)
this.$message.error('获取预览数据失败')
this.previewLoading = false
})
} catch (error) {
console.error('预览失败', error)
this.$message.error('获取预览数据失败')
this.previewLoading = false
}
},
// 下载当前预览的文件
downloadCurrentPreview() {
if (!this.previewBlob) {
this.$message.error('没有可下载的文件')
return
}
const url = URL.createObjectURL(this.previewBlob)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', this.currentPreviewName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
},
// 下载文件
downloadFile(file) {
// 实际应调用API下载
const link = document.createElement('a')
link.href = file.storePath
link.download = file.fileName
link.click()
},
// 删除附件
deleteFile(file) {
this.$confirm('确认删除该附件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteBillAttachment(file.id).then(response => {
if (response.code === '0000000000000000') {
this.$message.success('删除成功')
// 重新获取附件列表
this.getAttachmentList()
} else {
this.$message.error(response.message || '删除失败')
}
}).catch(() => {
this.$message.error('删除失败')
})
}).catch(() => {})
},
// 获取附件列表
getAttachmentList() {
getBillAttachmentList(this.billId).then(response => {
this.attachmentList = response.data || []
})
},
// 获取账单调整记录
getAdjustmentList() {
this.adjustmentLoading = true
getBillAdjustmentList({
...this.adjustmentQuery,
billId: this.billId
}).then(response => {
if (response.code === '0000000000000000') {
this.adjustmentList = response.data.list || []
this.adjustmentTotal = response.data.total || 0
} else {
this.$message.error(response.message || '获取账单调整记录失败')
this.adjustmentList = []
this.adjustmentTotal = 0
}
this.adjustmentLoading = false
}).catch(() => {
this.adjustmentList = []
this.adjustmentTotal = 0
this.adjustmentLoading = false
})
},
// 账单调整分页大小变化
handleAdjustmentSizeChange(val) {
this.adjustmentQuery.pageSize = val
this.getAdjustmentList()
},
// 账单调整页码变化
handleAdjustmentCurrentChange(val) {
this.adjustmentQuery.pageNum = val
this.getAdjustmentList()
},
// 查看账单调整详情
viewAdjustmentDetail(row) {
this.adjustmentAttachmentDialogVisible = true
this.adjustmentAttachmentList = []
this.currentAdjustmentId = row.id
this.getAdjustmentAttachmentList()
},
// 进行账单调整
handleAdjustment(row = null) {
this.adjustmentDialogVisible = true
this.adjustmentForm.billId = this.billId
this.adjustmentForm.billDetailId = this.billDetail.billDetails && this.billDetail.billDetails.length > 0
? this.billDetail.billDetails[0].id
: ''
this.adjustmentForm.projectId = this.billDetail.projectId || ''
// 设置账单明细类型
if (row && row.billDetailType) {
this.adjustmentForm.billDetailType = row.billDetailType
} else {
this.adjustmentForm.billDetailType = '0' // 默认原账单
}
// 预设调整金额为0方便用户输入
this.adjustmentForm.adjAmt = 0
this.adjustmentForm.adjRate = 0
this.calculateAdjustAmount()
},
// 调整方式变更
handleAdjMethodChange() {
// 重新计算调整后金额
this.calculateAdjustAmount()
},
// 计算调整后金额
calculateAdjustAmount() {
const originalAmount = parseFloat(this.billDetail.accigRcvAmt || 0)
let adjustmentValue = 0
if (this.adjustmentForm.adjMethod === '1') {
adjustmentValue = parseFloat(this.adjustmentForm.adjAmt || 0)
} else if (this.adjustmentForm.adjMethod === '2') {
// 按比例计算调整金额
adjustmentValue = originalAmount * parseFloat(this.adjustmentForm.adjRate || 0) / 100
}
// 根据调整类型计算最终金额
if (this.adjustmentForm.adjTpCd === '1') { // 调增
this.adjustedAmount = originalAmount + adjustmentValue
} else { // 调减
this.adjustedAmount = Math.max(0, originalAmount - adjustmentValue)
}
},
// 上传附件前检查
beforeAdjustmentUpload(file) {
const isValidType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'application/pdf'
const isLt5M = file.size / 1024 / 1024 < 5
if (!isValidType) {
this.$message.error('只能上传JPG/PNG/PDF格式的文件!')
}
if (!isLt5M) {
this.$message.error('文件大小不能超过5MB!')
}
return isValidType && isLt5M
},
// 上传调整附件
uploadAdjustmentAttachment(options) {
const file = options.file
// 创建临时URL供预览
const tempUrl = URL.createObjectURL(file)
// 向上传列表中添加文件
this.adjustmentFileList.push({
name: file.name,
file: file,
url: tempUrl,
status: 'ready'
})
},
// 预览调整附件
previewAdjustmentFile(file) {
this.previewLoading = true
this.previewDialogVisible = true
this.currentPreviewName = file.name || '附件'
this.previewUrl = ''
this.isPdf = false
this.isImage = false
// 根据文件名后缀判断文件类型
const fileExt = file.name ? file.name.split('.').pop().toLowerCase() : ''
try {
// 获取文件内容
const blob = file.file ? file.file : new Blob()
this.previewBlob = blob
// 设置文件类型标志
if (fileExt === 'pdf') {
this.isPdf = true
} else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) {
this.isImage = true
}
// 创建URL用于预览
this.previewUrl = file.url
this.previewLoading = false
} catch (error) {
console.error('预览失败', error)
this.$message.error('获取预览数据失败')
this.previewLoading = false
}
},
// 删除调整附件
deleteAdjustmentFile(index) {
this.adjustmentFileList.splice(index, 1)
},
// 重置调整表单
resetAdjustmentForm() {
if (this.$refs.adjustmentForm) {
this.$refs.adjustmentForm.resetFields()
}
this.adjustmentFileList = []
this.adjustedAmount = 0
this.adjustmentForm = {
billId: '',
billDetailId: '',
adjTpCd: '1',
adjDate: new Date().toISOString().slice(0, 10),
adjMethod: '1',
adjAmt: 0,
adjRate: 0,
billDetailType: '原账单',
adjSource: '账单直接调整',
projectId: '',
remark: '',
adjSource: '1'
}
},
// 提交账单调整
submitAdjustment() {
this.$refs.adjustmentForm.validate(valid => {
if (valid) {
this.adjustmentSubmitting = true
// 构建请求数据
const adjustmentData = { ...this.adjustmentForm }
// 如果是比例调整,需要计算实际调整金额
if (adjustmentData.adjMethod === '2') {
const originalAmount = parseFloat(this.billDetail.accigRcvAmt || 0)
adjustmentData.adjAmt = originalAmount * parseFloat(adjustmentData.adjRate || 0) / 100
}
// 提交调整
addBillAdjustment(adjustmentData)
.then(response => {
if (response.code === '0000000000000000') {
// 上传附件
if (this.adjustmentFileList.length > 0) {
this.uploadAdjustmentAttachments(response.data)
} else {
this.$message.success('账单调整成功')
this.adjustmentDialogVisible = false
// 重新获取账单信息和调整记录
this.getBillData()
this.adjustmentSubmitting = false
}
} else {
this.$message.error(response.message || '账单调整失败')
this.adjustmentSubmitting = false
}
})
.catch(error => {
console.error('账单调整失败', error)
this.$message.error('账单调整失败,请稍后重试')
this.adjustmentSubmitting = false
})
}
})
},
// 上传调整附件
uploadAdjustmentAttachments(adjustmentId) {
const uploadPromises = this.adjustmentFileList.map(fileItem => {
return uploadBillAdjustmentAttachment(adjustmentId, fileItem.file)
})
Promise.all(uploadPromises)
.then(() => {
this.$message.success('账单调整成功')
this.adjustmentDialogVisible = false
// 重新获取账单信息和调整记录
this.getBillData()
this.adjustmentSubmitting = false
})
.catch(error => {
console.error('附件上传失败', error)
this.$message.warning('账单调整成功,但部分附件上传失败')
this.adjustmentDialogVisible = false
// 重新获取账单信息和调整记录
this.getBillData()
this.adjustmentSubmitting = false
})
},
// 作废调整记录
voidAdjustment(row) {
this.$confirm('确定要作废此账单调整吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
voidBillAdjustment({
adjustmentId: row.id,
canceledDate: new Date().toISOString().slice(0, 10),
remark: '用户手动作废'
}).then(response => {
if (response.code === '0000000000000000') {
this.$message.success('作废成功')
// 重新获取账单信息和调整记录
this.getBillData()
} else {
this.$message.error(response.message || '作废失败')
}
}).catch(() => {
this.$message.error('作废失败')
})
}).catch(() => {})
},
// 上传前检查文件
beforeFileUpload(file) {
const isValidType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'application/pdf'
const isLt5M = file.size / 1024 / 1024 < 5
if (!isValidType) {
this.$message.error('只能上传JPG/PNG/PDF格式的文件!')
}
if (!isLt5M) {
this.$message.error('文件大小不能超过5MB!')
}
return isValidType && isLt5M
},
// 上传文件
uploadFile(options) {
const file = options.file
this.$confirm('确认上传该文件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
this.$message.info('开始上传附件,请稍候...')
// 调用上传附件API
uploadBillAttachment(this.billDetail.id, file).then(response => {
if (response.code === '0000000000000000') {
this.$message.success('上传成功')
// 重新获取附件列表
this.getAttachmentList()
} else {
this.$message.error(response.message || '上传失败')
}
}).catch(() => {
this.$message.error('上传失败')
})
}).catch(() => {
// 取消上传
})
},
// 获取调整附件列表
getAdjustmentAttachmentList() {
getBillAdjustmentAttachmentList(this.currentAdjustmentId).then(response => {
if (response.code === '0000000000000000') {
this.adjustmentAttachmentList = response.data || []
} else {
this.$message.error(response.message || '获取附件列表失败')
this.adjustmentAttachmentList = []
}
}).catch(() => {
this.$message.error('获取附件列表失败')
this.adjustmentAttachmentList = []
})
},
// 删除账单调整附件
deleteAdjustmentAttachment(file) {
this.$confirm('确认删除该附件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteBillAdjustmentAttachment(file.id).then(response => {
if (response.code === '0000000000000000') {
this.$message.success('删除成功')
// 重新获取附件列表
this.getAdjustmentAttachmentList()
} else {
this.$message.error(response.message || '删除失败')
}
}).catch(() => {
this.$message.error('删除失败')
})
}).catch(() => {})
},
// 操作记录分页大小变化
handleOperationSizeChange(val) {
this.operationQuery.pageSize = val
this.getOperationList()
},
// 操作记录页码变化
handleOperationCurrentChange(val) {
this.operationQuery.pageNum = val
this.getOperationList()
},
// 获取操作记录列表
getOperationList() {
getBillOperationRecords(this.billId, this.operationQuery).then(response => {
if (response.code === '0000000000000000') {
this.operationList = response.data.list || []
this.operationTotal = response.data.total || 0
} else {
this.$message.error(response.message || '获取操作记录失败')
this.operationList = []
this.operationTotal = 0
}
}).catch(() => {
this.$message.error('获取操作记录失败')
this.operationList = []
this.operationTotal = 0
})
},
// 打开支付对话框
openPaymentDialog() {
// 重置表单
this.resetPaymentForm()
this.paymentDialogVisible = true
this.paymentForm.billId = this.billId
this.paymentForm.billDetailId = this.billDetail.billDetails && this.billDetail.billDetails.length > 0
? this.billDetail.billDetails[0].id
: ''
this.paymentForm.occuAmt = 0
this.calculateRemainingAmount()
},
// 计算剩余待收金额
calculateRemainingAmount() {
const needAmount = parseFloat(this.billDetail.needAmount || 0)
const paymentAmount = parseFloat(this.paymentForm.occuAmt || 0)
this.remainingAmount = Math.max(0, needAmount - paymentAmount)
},
// 填充全额
fillFullAmount() {
this.paymentForm.occuAmt = parseFloat(this.billDetail.needAmount || 0)
this.calculateRemainingAmount()
},
// 重置支付表单
resetPaymentForm() {
if (this.$refs.paymentForm) {
this.$refs.paymentForm.resetFields()
}
this.paymentFileList = []
this.remainingAmount = 0
this.paymentForm = {
billId: '',
billDetailId: '',
occuAmt: 0,
inaccDate: new Date().toISOString().slice(0, 10),
payModeName: '1',
txVchrNo: '',
summ: '',
remark: ''
}
},
// 上传前检查支付附件
beforePaymentUpload(file) {
const isValidType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'application/pdf'
const isLt5M = file.size / 1024 / 1024 < 5
if (!isValidType) {
this.$message.error('只能上传JPG/PNG/PDF格式的文件!')
return false
}
if (!isLt5M) {
this.$message.error('文件大小不能超过5MB!')
return false
}
return isValidType && isLt5M
},
// 上传支付附件
uploadPaymentAttachment(options) {
const file = options.file
// 创建临时URL供预览
const tempUrl = URL.createObjectURL(file)
// 向上传列表中添加文件
this.paymentFileList.push({
name: file.name,
file: file,
url: tempUrl,
status: 'ready'
})
},
// 预览支付附件
previewPaymentFile(file) {
this.previewLoading = true
this.previewDialogVisible = true
this.currentPreviewName = file.name || '附件'
this.previewUrl = ''
this.isPdf = false
this.isImage = false
// 根据文件名后缀判断文件类型
const fileExt = file.name ? file.name.split('.').pop().toLowerCase() : ''
try {
// 获取文件内容
const blob = file.file ? file.file : new Blob()
this.previewBlob = blob
// 设置文件类型标志
if (fileExt === 'pdf') {
this.isPdf = true
} else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) {
this.isImage = true
}
// 创建URL用于预览
this.previewUrl = file.url
this.previewLoading = false
} catch (error) {
console.error('预览失败', error)
this.$message.error('获取预览数据失败')
this.previewLoading = false
}
},
// 删除支付附件
deletePaymentFile(index) {
this.paymentFileList.splice(index, 1)
},
// 提交支付
submitPayment() {
this.$refs.paymentForm.validate(valid => {
if (valid) {
// 验证支付金额
if (this.paymentForm.occuAmt <= 0) {
this.$message.error('支付金额必须大于0')
return
}
if (this.paymentForm.occuAmt > this.billDetail.needAmount) {
this.$message.error('支付金额不能超过需收金额')
return
}
this.paymentSubmitting = true
// 准备表单数据
const paymentData = {...this.paymentForm}
// 如果有附件需要转换日期格式后端接收Date类型
if (paymentData.inaccDate) {
// 保持字符串格式,后端会处理
}
submitBillPayment(paymentData)
.then(response => {
if (response.code === '0000000000000000') {
// 上传附件
if (this.paymentFileList.length > 0) {
this.uploadPaymentAttachments(response.data.id)
} else {
this.$message.success('账单支付成功')
this.paymentDialogVisible = false
// 重新获取账单信息
this.getBillData()
this.paymentSubmitting = false
}
} else {
this.$message.error(response.message || '账单支付失败')
this.paymentSubmitting = false
}
})
.catch(error => {
console.error('账单支付失败', error)
this.$message.error('账单支付失败,请稍后重试')
this.paymentSubmitting = false
})
}
})
},
// 上传支付附件
uploadPaymentAttachments(transactionId) {
if (this.paymentFileList.length === 0) {
this.$message.success('账单支付成功')
this.paymentDialogVisible = false
// 重新获取账单信息
this.getBillData()
this.paymentSubmitting = false
return
}
const uploadPromises = this.paymentFileList.map(fileItem => {
return uploadBillPaymentAttachment(transactionId, fileItem.file)
})
Promise.all(uploadPromises)
.then(() => {
this.$message.success('账单支付成功')
this.paymentDialogVisible = false
// 重新获取账单信息
this.getBillData()
this.paymentSubmitting = false
})
.catch(error => {
console.error('附件上传失败', error)
this.$message.warning('账单支付成功,但部分附件上传失败')
this.paymentDialogVisible = false
// 重新获取账单信息
this.getBillData()
this.paymentSubmitting = false
})
}
}
}
</script>
<style lang="scss" scoped>
.delete-btn {
color: #F56C6C;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.bill-detail-container {
padding: 10px;
.bill-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
.bill-header-left {
h3 {
margin: 0;
}
}
}
.payment-form {
.el-form-item {
margin-bottom: 18px;
}
.amount-input-wrapper {
display: flex;
align-items: center;
.fill-btn {
margin-left: 10px;
height: 32px;
padding: 8px 15px;
}
}
}
.payment-section {
margin-bottom: 25px;
h4 {
margin: 15px 0 10px;
padding-left: 8px;
border-left: 3px solid #409EFF;
font-size: 16px;
color: #303133;
}
.el-descriptions {
margin-bottom: 15px;
}
.upload-wrapper {
margin-bottom: 10px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
}
}
.empty-data {
margin: 20px 0;
display: flex;
justify-content: center;
}
.bill-adjustment-container {
.operation-bar {
display: flex;
justify-content: flex-end;
margin-bottom: 15px;
}
}
.form-hint {
margin-left: 10px;
color: #909399;
font-size: 13px;
}
.upload-area {
margin-bottom: 15px;
}
.preview-container {
min-height: 500px;
max-height: 700px;
overflow: auto;
.preview-iframe {
width: 100%;
height: 500px;
border: none;
}
.preview-image {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.preview-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
color: #909399;
i {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 16px;
}
.preview-actions {
margin-top: 20px;
}
}
}
.attachment-header {
margin-bottom: 15px;
}
}
</style>