2025-05-09 16:23:59 +08:00

619 lines
17 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="payment-page">
<h1 class="page-title">在线缴费</h1>
<!-- 缴费人信息与记录按钮 -->
<div class="user-info-bar bg-white p-3 flex justify-between align-center">
<div>
<span>缴费人: </span>
<span class="text-primary">{{ payerName }}</span>
</div>
<van-button icon="records" size="small" type="info" plain @click="goToRecords">缴费记录</van-button>
</div>
<div class="divider"></div>
<!-- 账单列表 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<!-- 没有账单数据时显示的内容 -->
<div v-if="buildingGroups.length === 0 && !loading" class="empty-data">
<van-empty image="search" description="暂无账单数据" />
</div>
<!-- 账单列表 (楼宇分组) -->
<div v-for="building in buildingGroups" :key="building.buildingId" class="bill-group">
<!-- 楼宇名称 (一级) -->
<div class="bill-group-header">
<!-- <div class="checkbox-wrapper" @click.stop="toggleBuildingCheck(building)">
<van-checkbox
:value="isBuildingChecked(building)"
/>
</div> -->
<div class="header-content" @click="toggleBuilding(building.buildingId)">
<span class="building-name">{{ building.buildingName }}</span>
<span class="building-info">
{{ building.totalCount }} | ¥{{ formatAmount(building.totalAmount) }}
</span>
<van-icon :name="buildingExpanded[building.buildingId] ? 'arrow-up' : 'arrow-down'" />
</div>
</div>
<div v-show="buildingExpanded[building.buildingId]">
<!-- 费用类型分组 (二级) -->
<div
v-for="feeType in building.feeTypeGroups"
:key="`${building.buildingId}_${feeType.feTypeName}`"
class="fee-type-group"
>
<div class="fee-type-header ml-4">
<!-- <div class="checkbox-wrapper ml-4" @click.stop="toggleFeeTypeCheck(building, feeType)">
<van-checkbox
:value="isFeeTypeChecked(building.buildingId, feeType)"
/>
</div> -->
<div class="header-content" @click="toggleFeeType(building.buildingId, feeType.feTypeName)">
<span class="fee-type-name">{{ feeType.feTypeName }}</span>
<span class="fee-type-info">
{{ feeType.totalCount }} | ¥{{ formatAmount(feeType.totalAmount) }}
</span>
<van-icon :name="feeTypeExpanded[`${building.buildingId}_${feeType.feTypeName}`] ? 'arrow-up' : 'arrow-down'" />
</div>
</div>
<div v-show="feeTypeExpanded[`${building.buildingId}_${feeType.feTypeName}`]">
<!-- 账单列表 (三级) -->
<div
v-for="bill in feeType.bills"
:key="bill.billId"
class="bill-item"
>
<div class="checkbox-wrapper ml-5" @click.stop="toggleCheck(bill)">
<van-checkbox
:value="isChecked(bill)"
/>
</div>
<div class="bill-content" @click="viewBillDetail(bill.billId)">
<div class="bill-info">
<div class="bill-remark">{{ bill.remark }}</div>
<div class="bill-date">应收日期: {{ bill.pybDt }}</div>
</div>
<div class="bill-amount">¥{{ formatAmount(bill.totalNeedAmount) }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
<!-- 底部操作栏 -->
<div class="footer-action-bar">
<div class="summary-info">
<div class="flex align-center">
<span class="ml-3">已选: {{ selectedBillsCount }}</span>
</div>
<div class="total-amount">合计: <span class="text-danger">¥{{ formatAmount(selectedBillsTotalAmount) }}</span></div>
</div>
<div class="action-buttons">
<van-button
type="default"
size="normal"
class="mr-2"
@click="goToPrepay"
>预缴费</van-button>
<van-button
type="primary"
size="normal"
:disabled="selectedBillsCount === 0"
@click="showPaymentTypeDialog"
>去缴费</van-button>
</div>
</div>
<!-- 支付方式选择 -->
<van-dialog
v-model="paymentTypeVisible"
title="选择支付方式"
:show-confirm-button="false"
:close-on-click-overlay="true"
>
<div class="payment-type-options">
<div class="payment-type-item">
<div class="radio-wrapper" @click.stop="selectPaymentType('personal')">
<van-radio :name="'personal'" v-model="paymentType">个对公缴费</van-radio>
</div>
<div class="payment-type-desc">个人对公司支付通过微信/支付宝等方式</div>
</div>
<div class="payment-type-item">
<div class="radio-wrapper" @click.stop="selectPaymentType('enterprise')">
<van-radio :name="'enterprise'" v-model="paymentType">公对公缴费</van-radio>
</div>
<div class="payment-type-desc">公司对公司支付通过银行转账方式</div>
</div>
</div>
<div class="dialog-footer">
<van-button type="default" block @click="cancelPaymentType">取消</van-button>
<van-button type="primary" block @click="confirmPaymentType">确定</van-button>
</div>
</van-dialog>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
import api from '@/api/payment';
export default {
name: 'PaymentIndex',
data() {
return {
refreshing: false,
loading: false,
finished: false,
pageNum: 1,
pageSize: 20,
buildingGroups: [], // 本地保存楼宇分组数据
totalAmount: 0, // 总金额
totalCount: 0, // 总数量
payerName: '', // 缴费人姓名
buildingExpanded: {}, // 楼宇展开状态
feeTypeExpanded: {}, // 费用类型展开状态
paymentTypeVisible: false, // 支付方式选择弹窗
paymentType: 'personal', // 支付方式
};
},
computed: {
...mapGetters('payment', [
'selectedBillsCount',
'selectedBillsTotalAmount',
]),
...mapState('payment', ['selectedBills']),
},
watch: {
// 监听选中账单数量变化
selectedBillsCount(newVal) {
// 监听但不需要执行全选相关逻辑
},
},
created() {
this.loadData();
},
methods: {
...mapMutations('payment', [
'ADD_SELECTED_BILL',
'REMOVE_SELECTED_BILL',
'CLEAR_SELECTED_BILLS',
'SET_PAY_TYPE',
]),
...mapActions('payment', ['createPayOrder']),
// 格式化金额
formatAmount(amount) {
return parseFloat(amount).toFixed(2);
},
// 加载数据
async loadData() {
try {
this.loading = true;
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize,
payerId: '1', // 直接使用默认业户ID
clearStatus: '4', // 待收款
};
const response = await api.getBills(params);
if (response.code === '0000000000000000') {
const data = response.data;
// 保存数据到本地
this.buildingGroups = data.buildingGroups || [];
this.totalAmount = data.totalAmount || 0;
this.totalCount = data.totalCount || 0;
this.payerName = data.payerName || '默认业户';
this.pageNum++;
this.finished = true; // 一次性加载所有数据
// 默认展开所有楼宇
this.buildingGroups.forEach(building => {
this.$set(this.buildingExpanded, building.buildingId, true);
});
} else {
this.$toast.fail(response.message || '获取账单列表失败');
}
} catch (error) {
this.$toast.fail('获取账单列表失败');
} finally {
this.loading = false;
this.refreshing = false;
}
},
// 下拉刷新
onRefresh() {
this.resetData();
this.loadData();
},
// 上拉加载更多
onLoad() {
this.loadData();
},
// 重置数据
resetData() {
this.pageNum = 1;
this.finished = false;
this.CLEAR_SELECTED_BILLS();
},
// 切换楼宇展开状态
toggleBuilding(buildingId) {
this.$set(this.buildingExpanded, buildingId, !this.buildingExpanded[buildingId]);
},
// 切换费用类型展开状态
toggleFeeType(buildingId, feTypeName) {
const key = `${buildingId}_${feTypeName}`;
this.$set(this.feeTypeExpanded, key, !this.feeTypeExpanded[key]);
},
// 判断账单是否被选中
isChecked(bill) {
return this.selectedBills.some(item => item.billId === bill.billId);
},
// 判断费用类型是否被选中(全部子账单被选中)
isFeeTypeChecked(buildingId, feeType) {
if (feeType.bills.length === 0) return false;
return feeType.bills.every(bill => this.isChecked(bill));
},
// 判断楼宇是否被选中(全部费用类型被选中)
isBuildingChecked(building) {
if (building.feeTypeGroups.length === 0) return false;
return building.feeTypeGroups.every(feeType =>
this.isFeeTypeChecked(building.buildingId, feeType)
);
},
// 切换费用类型选中状态
toggleFeeTypeCheck(building, feeType) {
const checked = this.isFeeTypeChecked(building.buildingId, feeType);
if (checked) {
// 如果已选中,则移除该费用类型下的所有账单
const billIdsToRemove = feeType.bills.map(bill => bill.billId);
billIdsToRemove.forEach(billId => {
this.REMOVE_SELECTED_BILL(billId);
});
} else {
// 如果未选中,则选择该费用类型下的第一条账单
if (feeType.bills.length > 0) {
const bill = feeType.bills[0];
// 确保账单对象包含费用类型名称和楼宇ID信息
bill.feeTypeName = feeType.feTypeName;
bill.buildingId = building.buildingId;
this.ADD_SELECTED_BILL(bill);
}
}
},
// 切换楼宇选中状态
toggleBuildingCheck(building) {
const checked = this.isBuildingChecked(building);
if (checked) {
// 如果已选中,则移除该楼宇下的所有账单
building.feeTypeGroups.forEach(feeType => {
const billIdsToRemove = feeType.bills.map(bill => bill.billId);
billIdsToRemove.forEach(billId => {
this.REMOVE_SELECTED_BILL(billId);
});
});
} else {
// 如果未选中,则选择每个费用类型下的第一条账单
building.feeTypeGroups.forEach(feeType => {
if (feeType.bills.length > 0) {
const bill = feeType.bills[0];
// 确保账单对象包含费用类型名称和楼宇ID信息
bill.feeTypeName = feeType.feTypeName;
bill.buildingId = building.buildingId;
this.ADD_SELECTED_BILL(bill);
}
});
}
},
// 切换账单选中状态
toggleCheck(bill) {
// 确保账单对象包含所需信息
const feeType = this.findFeeTypeByBill(bill);
const building = this.findBuildingByBill(bill);
if (feeType && building) {
bill.feeTypeName = feeType.feTypeName;
bill.buildingId = building.buildingId;
}
if (this.isChecked(bill)) {
this.REMOVE_SELECTED_BILL(bill.billId);
} else {
this.ADD_SELECTED_BILL(bill);
}
},
// 查找账单所属的费用类型
findFeeTypeByBill(bill) {
for (const building of this.buildingGroups) {
for (const feeType of building.feeTypeGroups) {
if (feeType.bills.some(b => b.billId === bill.billId)) {
return feeType;
}
}
}
return null;
},
// 查找账单所属的楼宇
findBuildingByBill(bill) {
for (const building of this.buildingGroups) {
for (const feeType of building.feeTypeGroups) {
if (feeType.bills.some(b => b.billId === bill.billId)) {
return building;
}
}
}
return null;
},
// 查看账单详情
viewBillDetail(billId) {
this.$router.push(`/payment/bill-detail/${billId}`);
},
// 跳转到缴费记录页面
goToRecords() {
this.$router.push('/payment/records');
},
// 跳转到预缴费页面
goToPrepay() {
this.$router.push('/payment/prepay');
},
// 显示支付方式选择弹窗
showPaymentTypeDialog() {
this.paymentTypeVisible = true;
},
// 选择支付方式
selectPaymentType(type) {
this.paymentType = type;
},
// 取消支付方式选择
cancelPaymentType() {
this.paymentTypeVisible = false;
},
// 确认支付方式
confirmPaymentType() {
this.SET_PAY_TYPE(this.paymentType);
this.paymentTypeVisible = false;
this.goPay();
},
// 去支付
async goPay() {
if (this.selectedBillsCount === 0) {
this.$toast('请选择需要缴费的账单');
return;
}
// 创建支付订单
const orderData = await this.createPayOrder();
if (!orderData) return;
// 根据支付类型处理
if (this.paymentType === 'personal') {
// 个对公支付,唤起支付
// 这里应该跳转到第三方支付页面,根据实际情况实现
window.location.href = orderData.payUrl;
} else {
// 公对公支付,展示支付链接
this.$router.push({
path: '/payment/enterprise-pay',
query: {
orderNo: orderData.orderNo,
amount: orderData.amount
}
});
}
},
}
};
</script>
<style lang="scss" scoped>
.payment-page {
padding-bottom: 60px;
}
.user-info-bar {
padding: 12px 16px;
}
.bill-group {
margin-bottom: 8px;
background-color: #fff;
}
.bill-group-header {
display: flex;
align-items: center;
padding: 12px 16px;
font-size: 16px;
font-weight: 500;
background-color: #f8f8f8;
.header-content {
display: flex;
flex: 1;
align-items: center;
}
.building-name {
flex: 1;
margin-left: 8px;
}
.building-info {
margin-right: 8px;
font-size: 14px;
color: #666;
}
}
.fee-type-group {
border-bottom: 1px solid #eee;
}
.fee-type-header {
display: flex;
align-items: center;
padding: 10px 16px;
font-size: 15px;
.header-content {
display: flex;
flex: 1;
align-items: center;
}
.fee-type-name {
flex: 1;
margin-left: 8px;
}
.fee-type-info {
margin-right: 8px;
font-size: 14px;
color: #666;
}
}
.bill-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #f5f5f5;
.bill-content {
display: flex;
flex: 1;
align-items: center;
}
.bill-info {
flex: 1;
margin-left: 8px;
}
.bill-remark {
font-size: 14px;
margin-bottom: 4px;
}
.bill-date {
font-size: 12px;
color: #999;
}
.bill-amount {
font-size: 15px;
font-weight: 500;
color: #ee0a24;
}
}
.empty-data {
padding: 40px 0;
}
.footer-action-bar {
display: flex;
justify-content: space-between;
align-items: center;
.summary-info {
flex: 1;
}
.total-amount {
margin-top: 4px;
font-size: 15px;
span {
font-weight: 500;
font-size: 16px;
}
}
.action-buttons {
min-width: 120px;
display: flex;
justify-content: flex-end;
}
}
.payment-type-options {
padding: 16px;
.payment-type-item {
padding: 12px 0;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
.payment-type-desc {
margin-top: 6px;
margin-left: 24px;
font-size: 12px;
color: #999;
}
}
}
.dialog-footer {
display: flex;
padding: 16px;
.van-button {
flex: 1;
margin: 0 4px;
}
}
.checkbox-wrapper, .radio-wrapper {
padding: 6px;
margin-top: -2px;
margin-bottom: -2px;
z-index: 10;
}
</style>