Files
estate/src/views/system/Sdb/SdbCashier/index.vue
2026-08-11 09:03:27 +08:00

1226 lines
42 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="p-2 h-full flex flex-col">
<PageHeader></PageHeader>
<div class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" @submit.prevent="handleQuery" :inline="true">
<div>
<el-form-item label="小区" prop="villageId">
<el-select @change="handleChangeVillage" v-model="queryParams.villageId" placeholder="请选择小区">
<el-option v-for="(item, index) in villageList" :key="index" :value="item.villageId" :label="item.villageName"></el-option>
</el-select>
</el-form-item>
<el-form-item label="楼栋" prop="buildingId">
<el-select @change="handleChangeBuilding" v-model="queryParams.buildingId" placeholder="请选择楼栋">
<el-option v-for="(item, index) in buildingList" :key="index" :value="item.id" :label="item.buildingName"></el-option>
</el-select>
</el-form-item>
<el-form-item label="单元" prop="unitNoId">
<el-select @change="handleChangeUnit" v-model="queryParams.unitNoId" placeholder="请选择单元">
<el-option v-for="(item, index) in unitList" :key="index" :value="item.unitNoId" :label="item.unitNoName"></el-option>
</el-select>
</el-form-item>
<el-form-item @change="handleChangeHouseNo" label="房号" prop="houseId">
<el-select v-model="queryParams.houseId" placeholder="请选择房号">
<el-option-group v-for="group in houseList" :key="group.floorNo" :label="String(group.floorNo) + ' 楼'">
<el-option v-for="item in group.houses" :key="item.houseId" :label="item.houseNo" :value="item.houseId" />
</el-option-group>
</el-select>
</el-form-item>
</div>
<div>
<el-form-item label="收费项目" prop="itemId">
<el-select multiple v-model="queryParams.chargeItemId" placeholder="请选择收费项目">
<el-option
:disabled="item.status === '1'"
v-for="(item, index) in chargeItems"
:key="index"
:value="item.id"
:label="item.itemName"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="缴费周期" prop="chargePeriod">
<el-date-picker
v-model="chargePeriod"
type="daterange"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
range-separator=""
start-placeholder="开始日期"
@clear="handleClearChargePeriod"
end-placeholder="结束日期"
placeholder="请选择缴费日期"
/>
</el-form-item>
<el-form-item label="房屋分类" prop="houseUseTypeId">
<el-select v-model="queryParams.houseUseTypeId">
<el-option v-for="item in houseUseTypeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="缴费时间" prop="payTime">
<el-date-picker
v-model="payTime"
type="datetimerange"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
range-separator=""
start-placeholder="开始日期"
@clear="handleClearPayTime"
end-placeholder="结束日期"
placeholder="请选择缴费日期"
/>
</el-form-item>
<el-form-item label="业主姓名" prop="residentName">
<el-input maxlength="30" v-model.trim="queryParams.residentName" placeholder="请选择业主姓名"></el-input>
</el-form-item>
<el-form-item>
<div style="text-align: right">
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
</el-form-item>
</div>
</el-form>
</el-card>
</div>
<div class="actionsBox">
<div class="tabs-box">
<div
class="tabs-items"
v-for="(item, index) in tabs"
:key="index"
:class="{
active: activeTabs === item.value
}"
@click="changeTabs(item.value)"
>
{{ item.type }}
</div>
</div>
<div class="actions">
<el-button @click="handleIncome(false)" :disabled="!single">收费</el-button>
<el-button @click="printPIaoJuByOnly(false)" :disabled="!printSingle">打印票据</el-button>
</div>
</div>
<el-card class="flex-1" shadow="never">
<el-table v-loading="loading" border :data="tableData" @selection-change="handleSelectionChange">
<el-table-column type="selection" fixed="left" width="55" align="center" />
<el-table-column label="房屋/车位/车辆" fixed="left" align="center" prop="unitNo" width="120">
<template #default="scope">
{{ scope.row.unitNo }}
</template>
</el-table-column>
<el-table-column label="收费项目" fixed="left" align="center" prop="billCode" width="120">
<template #default="scope">
{{ scope.row.chargeItemName }}
</template>
</el-table-column>
<el-table-column label="收费周期" align="center" prop="periodDisplay" width="280">
<template #default="scope">
<span>{{ scope.row.periodDisplay }}</span>
</template>
</el-table-column>
<el-table-column label="单价" width="100" align="center" prop="fixedPrice">
<template #default="scope">
<div class="flex justify-center">
<span>
{{ scope.row.fixedPrice }}
</span>
<span v-if="scope.row.unit"> / </span>
<dict-tag v-if="scope.row.unit" :options="com_charge_item_unit" :value="scope.row.unit"></dict-tag>
</div>
</template>
</el-table-column>
<el-table-column label="数量" width="80" align="center" prop="quantity">
<template #default="scope">
<span>{{ scope.row.quantity }}</span>
</template>
</el-table-column>
<el-table-column label="应收金额" width="100" align="center" prop="amount" />
<el-table-column label="缴费状态" width="100" align="center" prop="payStatus">
<template #default="scope">
<span v-if="scope.row.payStatus === '0'">未缴纳</span>
<span v-else-if="scope.row.payStatus === '1'">已缴纳</span>
<span v-else-if="scope.row.payStatus === '2'">缴纳失败</span>
<span v-else>部分缴纳</span>
</template>
</el-table-column>
<el-table-column label="应缴金额" width="100" align="center" prop="amount" />
<el-table-column label="优惠金额" width="100" align="center" prop="singleDiscountAmount" />
<el-table-column label="实缴金额" width="100" align="center" prop="actualAccount" />
<el-table-column label="缴费人" width="120" align="center" prop="residentName" />
<el-table-column label="缴费时间" width="200" align="center" prop="payTime" />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" width="110" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" v-if="scope.row.payStatus !== '1'" @click="handleIncome(scope.row)" v-has-permi="['bill:bill:edit']"
>收款</el-button
>
<el-button link type="success" v-if="scope.row.payStatus === '1'" @click="printPIaoJuByOnly(scope.row)" v-has-permi="['bill:bill:query']"
>打印票据</el-button
>
</template>
</el-table-column>
</el-table>
<div class="footerBox">
<div class="totalBox">
<span>合计</span>
<span>应收{{ totalAll.totalAccount }}</span>
<span style="padding: 0 20px">实收{{ totalAll.TotalPaymentAmount }}</span>
<span>优惠{{ totalAll.totalDiscountAmount }}</span>
</div>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="650px" append-to-body>
<div class="payInfo">
<div>
<div class="flex flex-items-center justify-between">
<span>缴费人 {{ inComeFrom.residentName }}</span>
<span style="color: #ff9947">待缴纳</span>
</div>
</div>
<div class="flex">
<span>收费名称 </span>
<div>
<div
v-for="(item, index) in inComeFrom.billResidentId"
:key="index"
class="flex flex-items-center"
style="text-wrap: wrap; margin-bottom: 5px"
>
<div class="flex flex-items-center">
<div style="text-decoration: underline; padding-right: 5px; text-align: center">
{{ item.detailsName }} &nbsp;&nbsp;(应缴{{ item.needPayAmount }})
</div>
<el-tooltip class="box-item" effect="light" placement="top" v-if="item.payStatus === '3'">
<el-icon color="#909399"><QuestionFilled /></el-icon>
<template #content>
<div style="line-height: 1.8; font-size: 13px; width: 180px">
<div style="text-align: right" class="flex justify-between">
<span>总金额</span>
<span>{{ item.amount }}</span>
</div>
<div style="text-align: right" class="flex justify-between">
<span>累计优惠金额</span>
<span> {{ item.singleDiscountAmount }}</span>
</div>
<div style="text-align: right" class="flex justify-between">
<span>累计已交金额</span>
<span> {{ item.paidAmount }}</span>
</div>
<div
class="flex justify-between cursor-pointer"
style="margin-top: 6px; border-top: 1px solid #ccc; text-align: right; padding-top: 6px"
>
<span>待缴金额</span>
<span style="font-weight: bold; color: red">{{ item.needPayAmount }}</span>
</div>
</div>
</template>
</el-tooltip>
</div>
<span v-if="item.payStatus === '1'">已缴费不计入金额</span>
</div>
</div>
</div>
<div>
<span>应收合计{{ inComeFrom.allCost }}</span>
</div>
<div
:class="{
DiscountColorRed: gtMoney(totalDiscountAmount, inComeFrom.allCost)
}"
class="flex flex-items-center"
>
<div>优惠合计{{ totalDiscountAmount }}</div>
<div v-if="totalDiscountAmount !== totalReadDiscount && totalReadDiscount !== '0' && inComeFrom.discountMethod === 2">
&nbsp;&nbsp;
<span>(实际优惠合计{{ totalReadDiscount }})</span>
<el-tooltip class="box-item" effect="light" placement="top">
<el-icon color="#909399" style="vertical-align: middle"><QuestionFilled /></el-icon>
<template #content>
<span>有账单 应缴费用 不足 优惠金额</span>
</template>
</el-tooltip>
</div>
</div>
<div>
<span>预计实缴合计{{ totalPayAmount }}</span>
</div>
<div>
<span>实际实缴合计{{ payListAllAmount }}</span>
</div>
</div>
<el-form ref="formRef" :model="inComeFrom" label-width="100px">
<el-form-item label="费用合计" prop="allCost">
<el-input v-model="inComeFrom.allCost" disabled style="width: 400px">
<template #suffix>
<span></span>
</template>
</el-input>
</el-form-item>
<el-form-item label="缴纳状态" prop="payStatus">
<el-radio-group v-model="inComeFrom.payStatus">
<el-radio value="1" label="已缴纳"></el-radio>
<el-radio value="2" label="部分缴纳"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="优惠方式" prop="discountMethod" v-if="inComeFrom.payStatus !== '0'">
<el-select style="width: 400px" @change="handleChangeDisCountMethod" v-model="inComeFrom.discountMethod" placeholder="请选择">
<el-option label="不优惠" :value="0"></el-option>
<el-option label="金额" :value="2"></el-option>
<el-option label="比例" :value="1"></el-option>
</el-select>
</el-form-item>
<div v-if="inComeFrom.discountMethod !== 0">
<div v-if="inComeFrom.discountMethod === 2">
<el-form-item label="单笔优惠金额" prop="discount">
<el-input style="width: 400px" v-model.trim.number="inComeFrom.discount" @input="debouncedHandleDiscountMoney">
<template #append>
<span></span>
</template>
</el-input>
</el-form-item>
</div>
<el-form-item label="优惠比例" prop="discountRate" v-else>
<el-input
style="width: 400px"
v-model.trim.number="inComeFrom.discountRate"
placeholder="0~1000=无优惠80=优惠80%100=全免"
@input="debounceDiscountRate"
>
<template #append>
<span>%</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="总优惠金额" prop="totalDiscountAmount">
<el-input style="width: 400px" disabled v-model="totalDiscountAmount">
<template #append>
<span></span>
</template>
</el-input>
</el-form-item>
</div>
<el-form-item label="支付方式" prop="payType" v-if="inComeFrom.payStatus !== '0'">
<div class="flex-1">
<div class="flex flex-items-center" v-for="(item, index) in payList" :key="index">
<div class="flex-1 flex">
<el-select @change="handleChangePayTypeId($event, index)" v-model="item.payType" placeholder="请选择">
<el-option
:disabled="isOptionDisabled(dict.value, item.payType)"
v-for="dict in com_pay_method"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
<el-input @change="(val: string) => handleChangePay(val, index)" v-model.number.trim="item.payAmount" style="margin: 0 10px">
<template #append></template>
</el-input>
</div>
<div style="width: 80px; text-align: left">
<el-icon
color="blue"
size="24"
v-if="index === payList.length - 1 && payList.length < com_pay_method.length"
@click="addPayList"
style="margin-right: 10px; cursor: pointer"
><CirclePlus
/></el-icon>
<el-icon color="red" style="cursor: pointer" size="24" v-if="payList.length > 1" @click="removePayList(index)"><Delete /></el-icon>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input style="width: 400px" type="textarea" show-word-limit maxlength="100" :rows="5" v-model="inComeFrom.remark"></el-input>
</el-form-item>
<div class="flex justify-end">
<el-button @click="closer">取消</el-button>
<el-button @click="submitForm" :loading="buttonLoading" type="primary">提交</el-button>
</div>
</el-form>
</el-dialog>
<el-dialog :title="dialog_print.title" v-model="dialog_print.visible" width="1200px" append-to-body>
<template #header>
<div class="flex" style="justify-content: space-between; padding: 0 20px">
<div class="title">票据打印</div>
<el-button type="primary" v-print="printOption" :loading="isLoading" @click="conFimPrint">打印</el-button>
</div>
</template>
<div class="printBox" id="printBox-Bill" style="margin: 0; color: rgba(0, 0, 0, 1); padding: 5px">
<div class="title" style="font-size: 1.2rem; margin-bottom: 10px; text-indent: 5em">{{ print_form.title }}</div>
<div style="font-size: 12px; display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; grid-gap: 10px; grid-template-rows: 1fr">
<div>单据日期{{ print_form.printDate }}</div>
<div>房号{{ print_form.HouseName }}</div>
<div>产权人{{ print_form.residentName }}</div>
<div>No.</div>
</div>
<div class="tableBox" style="font-size: 10px">
<table class="printTable" style="border-collapse: collapse; border: 1px solid #000; width: 90%">
<thead>
<tr>
<th style="min-width: 50px">收费项目</th>
<th>收费标准</th>
<th>账单时间</th>
<th style="min-width: 50px">面积</th>
<th style="min-width: 50px">应收金额</th>
<th style="min-width: 50px">优免金额</th>
<th style="min-width: 50px">实收金额</th>
<th>收款方式</th>
<th style="min-width: 100px">备注</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in print_form.detailsList" :key="index">
<td>{{ item.chargeItemName }}</td>
<td>{{ item.billName }}</td>
<td>{{ item.detailsTime }}</td>
<td>{{ item.useCount }}</td>
<td>{{ item.account }}</td>
<td>{{ item.singleDiscountAmount }}</td>
<td>{{ item.actualAccount }}</td>
<td>{{ item.payType }}</td>
<td style="text-wrap: wrap; white-space: wrap">{{ item.remark }}</td>
</tr>
<tr>
<td>合计</td>
<td></td>
<td></td>
<td></td>
<td>{{ print_form.totalAccount }}</td>
<td>{{ print_form.totalDiscountAmount }}</td>
<td>{{ print_form.totalActualAccount }}</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p style="font-size: 10px">说明本收据收款单位和收款具签章方为有效本收据手写无效</p>
<div style="font-size: 12px; margin-bottom: 0; display: flex; align-items: center">
<div style="flex: 1">交款人</div>
<div style="flex: 1; display: flex; align-items: center">
<span style="white-space: nowrap">收款人 </span>
<el-input v-model="inHomelyUse" v-if="!isPrint" placeholder="请输入收款人姓名" style="width: 150px"></el-input>
<span style="white-space: nowrap" v-else> {{ inHomelyUse }}</span>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import PageHeader from '@/components/Pageheader/index.vue';
import pagination from '@/components/Pagination/index.vue';
import DictTag from '@/components/DictTag/index.vue';
import { getBuildinglistAPI, getHouselistAPI } from '@/api/system/house';
import { getCommunitylistAPI } from '@/api/system/community';
import { billEdit, billlistType, billPrint, BillPrintType, billQuery } from '@/api/system/SdbCashier';
import { listChargeItem } from '@/api/system/SdbChargeItem';
import { ref } from 'vue';
import { add, div, eq, eqMoney, gtMoney, ltMoney, minMoney, moneyAdd, mul, sub, sum, toBig } from '@/utils/money';
import { BuildingUnitlist, BuildingVo, FloorsType } from '@/api/system/house/type';
import { ChargeItemVO } from '@/api/system/SdbChargeItem/type';
import { communitylist_rows_type } from '@/api/system/community/type';
import { debounce } from '@/utils';
import { listHouseUseType_add_house } from '@/api/system/houseUse';
import { CirclePlus, Delete, QuestionFilled } from '@element-plus/icons-vue';
import { useUserStore } from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_pay_method, com_charge_item_unit } = toRefs<any>(proxy?.useDict('com_pay_method', 'com_charge_item_unit'));
const userStore = useUserStore();
const loading = ref(false);
const ids = ref<Array<billlistType>>([]);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const activeTabs = ref(null);
const tabs = [
{
value: null,
type: '全部'
},
{
value: 0,
type: '待缴纳'
},
{
value: 1,
type: '已缴纳'
},
{
value: 3,
type: '部分缴纳'
}
];
const villageId = localStorage.getItem('villageid') ?? undefined;
const queryParams = ref({
pageNum: 1,
pageSize: 10,
// 收款状态
payStatus: null,
// 小区id
villageId: villageId,
// 楼栋id
buildingId: undefined,
// 单元id
unitNoId: undefined,
// 房屋id
houseId: undefined,
// 缴费周期
chargePeriod: undefined,
// 房屋类型id
houseUseTypeId: undefined,
// 缴费时间
payTime: undefined,
// 缴费人
residentName: undefined,
// 缴费项目id
chargeItemId: undefined,
period: undefined,
// 缴费时间开始
payTimeBegin: undefined,
// 缴费时间结束
payTimeEnd: undefined,
// 开始时间
startDate: undefined,
// 结束时间
endDate: undefined
});
// 缴费周期
const chargePeriod = ref([]);
// 缴费时间
const payTime = ref([]);
const villageList = ref<communitylist_rows_type[]>([]); // 小区列表
const buildingList = ref<BuildingVo[]>([]); // 楼栋列表
const unitList = ref<BuildingUnitlist[]>([]); // 单元列表
const houseList = ref<FloorsType[]>([]); // 房屋列表
const chargeItems = ref<ChargeItemVO[]>([]);
const tableData = ref([]);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const houseUseTypeList = ref([]);
function getHouseTypeList() {
listHouseUseType_add_house().then((res) => {
houseUseTypeList.value = res.data;
});
}
// 切换 账单类型
function changeTabs(value: number) {
activeTabs.value = value;
queryParams.value.payStatus = value;
getList();
}
// 选择小区
function handleChangeVillage(val: string) {
queryParams.value.unitNoId = undefined;
queryParams.value.buildingId = undefined;
queryParams.value.chargeItemId = undefined;
queryParams.value.houseId = undefined;
buildingList.value = [];
unitList.value = [];
if (val) {
getBuildinglistAPI({
villageId: val
}).then((res) => {
buildingList.value = res.data;
handleChargeItem(val);
});
return;
}
}
// 获取收费项目
function handleChargeItem(id: string) {
listChargeItem({
villageId: id,
status: '0'
}).then((res) => {
chargeItems.value = res.rows;
});
}
// 选择楼栋
function handleChangeBuilding(val: string): void {
queryParams.value.unitNoId = undefined;
queryParams.value.houseId = undefined;
unitList.value = [];
getHouselistAPI(val).then((res) => {
unitList.value = res.data.units;
});
}
// 选择 单元
function handleChangeUnit(val: string) {
const find = unitList.value.find((item) => item.unitNoId === val);
houseList.value = [];
if (find) {
houseList.value = find.floors;
}
}
// 选择房屋
function handleChangeHouseNo() {}
// 清空缴费时间
function handleClearPayTime() {
payTime.value = [];
}
// 清空缴费周期
function handleClearChargePeriod() {
chargePeriod.value = [];
}
// 获取列表
function getList() {
loading.value = true;
queryParams.value.payTimeBegin = payTime.value[0] ? payTime.value[0] : undefined;
queryParams.value.payTimeEnd = payTime.value[1] ? payTime.value[1] : undefined;
queryParams.value.startDate = chargePeriod.value[0] ? chargePeriod.value[0] : undefined;
queryParams.value.endDate = chargePeriod.value[1] ? chargePeriod.value[1] : undefined;
billQuery(queryParams.value)
.then((res) => {
tableData.value = res.rows;
total.value = res.total;
handleSelectBill(res.rows);
})
.finally(() => {
loading.value = false;
});
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
payTime.value = [];
chargePeriod.value = [];
queryParams.value = {
chargePeriod: undefined,
payTime: undefined,
pageNum: 1,
pageSize: 20,
payStatus: null,
villageId: undefined,
buildingId: undefined,
unitNoId: undefined,
houseId: undefined,
chargeItemId: undefined,
period: undefined,
houseUseTypeId: undefined,
residentName: undefined,
payTimeBegin: undefined,
payTimeEnd: undefined,
startDate: undefined,
endDate: undefined
};
queryParams.value.villageId = villageId;
handleQuery();
};
// 控制收费 按钮 不能有已缴纳的(后端会校验)
const single = ref(true);
// 控制打印票据的 按钮 不能有收费的
const printSingle = ref(true);
/** 多选框选中数据 */
const handleSelectionChange = (selection: billlistType[]) => {
ids.value = selection.map((item) => item);
// 不能全部都是已缴纳的 0待缴纳 1已缴纳 2缴纳失败 3混合缴纳
single.value = ids.value.every((item) => item.payStatus === '0' || item.payStatus === '3');
// 必须都要缴费后打印
printSingle.value = ids.value.every((item) => item.payStatus === '1');
};
const buttonLoading = ref(false);
// 是否多选
let isMore = false;
const inComeFrom = ref({
allCost: null, // 金额
billResidentId: [], // 账单id
/** 0:未缴 1:已缴 2: 缴纳失败 3: 混合缴纳*/
payStatus: '1',
/** 0:微信 1:支付宝 2 线下 */
payType: '0', //
remark: '', // 备注
/**优惠方式 0 不优惠 1 比例 2 金额*/
discountMethod: 0, //
discountRate: '0', // 优惠比例 10 === 10%
discount: '0', // 优惠金额
residentName: '' // 缴费人
});
// 总计算优惠金额
const totalDiscountAmount = ref('0.00');
// 总实际优惠金额
const totalReadDiscount = ref('0.00');
// 总实际支付金额
const totalPayAmount = ref('0.00');
// 初始化
function initInComeFrom() {
inComeFrom.value = {
allCost: 0, // 总金额
billResidentId: [], // 账单id
payStatus: '1', // 0:未缴 1:已缴 2: 缴纳失败 3: 部分缴纳
payType: '2', // 0:微信 1:支付宝 2 线下
remark: '',
discountMethod: 0, // 优惠方式 0 不优惠 1 比例 2 金额
discountRate: '0', // 优惠比例 10 === 10%
discount: '0', // 优惠金额
residentName: '' // 缴费人
};
totalAll.value = {
totalDiscountAmount: 0,
TotalPaymentAmount: 0,
totalAccount: 0
};
payList.value = [
{
payType: '',
payAmount: null
}
];
totalDiscountAmount.value = '0.00';
totalPayAmount.value = '0.00';
totalReadDiscount.value = '0.00';
isMore = false;
}
// 改变优惠方式
function handleChangeDisCountMethod(val: number) {
inComeFrom.value.discountRate = '0'; // 优惠比例
inComeFrom.value.discount = '0.00'; // 优惠金额
totalReadDiscount.value = '0.00'; // 实际优惠金额
totalDiscountAmount.value = '0.00'; // 总优惠金额
totalPayAmount.value = inComeFrom.value.allCost; // 总实际付款金额
if (val === 0) return;
if (val === 2) {
// 金额
handleChangeDiscountMoney(inComeFrom.value.discount);
} else {
// 比例
handleChangeDiscountRate(inComeFrom.value.discount);
}
}
// 输入折扣固定金额
function handleChangeDiscountMoney(val: string) {
// 1. 清洗输入空、纯符号、非数字兜底为0
const cleanVal = val.trim();
if (!checkMoney(cleanVal)) {
return ElMessage.warning('金额格式错误');
}
let discount = toBig(cleanVal).toNumber();
if (Number.isNaN(discount)) discount = 0;
const allCost = inComeFrom.value.allCost ?? 0;
if (discount < 0) {
ElMessage.warning('优惠金额不能为负数');
return;
}
// 折扣为0直接赋值原总价无需计算
if (eq(discount, 0)) {
totalDiscountAmount.value = '0.00';
totalReadDiscount.value = '0.00';
totalPayAmount.value = allCost.toString();
return;
}
if (isMore) {
// 过滤:只保留未支付账单,已支付直接丢弃
const unPaidList = ids.value.filter((item) => {
return item.payStatus !== '1';
});
// 计算优惠总额
const allDiscount = mul(unPaidList.length, discount);
if (gtMoney(allDiscount, inComeFrom.value.allCost)) {
ElMessage.warning('优惠总金额不能超过总费用');
}
// 实际优惠总额
let readDiscount = 0;
unPaidList.forEach((item) => {
readDiscount = add(readDiscount, minMoney(item.needPayAmount, discount));
});
totalDiscountAmount.value = `${allDiscount}`;
totalReadDiscount.value = `${readDiscount}`;
totalPayAmount.value = sub(allCost, readDiscount).toString();
} else {
// 单笔优惠不能超过整体总费用
if (gtMoney(discount, allCost)) {
ElMessage.warning('单笔优惠金额不能超过总费用');
}
totalDiscountAmount.value = discount.toString();
totalReadDiscount.value = `${discount}`;
totalPayAmount.value = sub(allCost, discount).toString();
}
validatePayAmountSufficiency();
}
const debouncedHandleDiscountMoney = debounce(handleChangeDiscountMoney, 550);
// 输入折扣固定比例
function handleChangeDiscountRate(val: string) {
const inputStr = val.trim();
let discountPercent = toBig(inputStr).toNumber();
if (Number.isNaN(discountPercent)) discountPercent = 0;
// 百分比校验
if (discountPercent < 0 || discountPercent > 100) {
ElMessage.warning('请输入0-100之间的优惠打折百分比');
return;
}
const allCost = inComeFrom.value.allCost ?? 0;
const rate = div(discountPercent, 100);
// 1. 输入0优惠总额为0全价支付
if (eq(rate, 0)) {
totalDiscountAmount.value = '0.00';
totalReadDiscount.value = '0.00';
totalPayAmount.value = allCost;
return;
}
// 2. 输入100全免价格免费送
if (eq(rate, 1)) {
totalDiscountAmount.value = allCost;
totalReadDiscount.value = allCost;
totalPayAmount.value = '0.00';
return;
}
// 批量多选模式
if (isMore) {
const unPaidList = ids.value.filter((item) => {
return item.payStatus !== '1';
});
// 总价格
const sourceTotal = sum(unPaidList.map((item) => item.needPayAmount ?? 0));
// 优惠总额 = 未付原价总和 - 打折后实付总和
const discountTotal = mul(sourceTotal, rate);
// 优惠总额
totalDiscountAmount.value = discountTotal.toString();
totalReadDiscount.value = `${discountTotal}`;
totalPayAmount.value = mul(sourceTotal, 1 - rate).toString();
} else {
// 单选单条账单
totalDiscountAmount.value = mul(allCost, rate).toString();
totalReadDiscount.value = mul(allCost, rate).toString();
totalPayAmount.value = mul(allCost, 1 - rate).toString();
}
validatePayAmountSufficiency();
}
const debounceDiscountRate = debounce(handleChangeDiscountRate);
function validatePayAmountSufficiency() {
if (payList.value.length > 0) {
if (gtMoney(payListAllAmount.value, 0)) {
payList.value.forEach((item) => (item.payAmount = 0));
ElMessage.warning(`优惠变更,请重新填写支付金额`);
}
}
}
// 收费 --------------------------------------------------------------
/** 收费 */
function handleIncome(row: billlistType | false) {
initInComeFrom();
if (row) {
inComeFrom.value.allCost = row.needPayAmount;
inComeFrom.value.billResidentId = [row];
inComeFrom.value.remark = row.remark;
inComeFrom.value.residentName = row.residentName;
totalPayAmount.value = row.needPayAmount;
} else {
if (ids.value.length <= 0) return ElMessage.warning('请选择账单');
const set = new Set();
ids.value.forEach((item) => {
set.add(item.residentId);
});
if (ids.value.some((item) => item.payStatus === '1')) {
return ElMessage.warning('存在已支付账单,请重新选择');
}
if (set.size > 1) {
ElMessage.warning('缴费人不一致,重新选择');
return;
} else if (set.size === 1) {
inComeFrom.value.remark = '';
inComeFrom.value.residentName = ids.value[0].residentName;
ids.value.forEach((item) => {
inComeFrom.value.billResidentId.push(item);
if (item.payStatus === '0' || item.payStatus === '3') {
inComeFrom.value.allCost = moneyAdd(inComeFrom.value.allCost || 0, item.needPayAmount);
}
});
totalPayAmount.value = inComeFrom.value.allCost;
isMore = true;
}
}
dialog.visible = true;
}
// 关闭模态框
const closer = () => {
dialog.visible = false;
dialog.title = '';
initInComeFrom();
};
// 提交
const submitForm = () => {
if (!handlePayListIsEmpt()) {
ElMessage.warning('请填写完整支付方式和支付金额');
return;
}
buttonLoading.value = true;
// 部分缴纳时 不管
// 已缴纳时 判断 支付金额 和 预计支付金额 是否相等
if (inComeFrom.value.payStatus === '1') {
if (!eqMoney(totalPayAmount.value, payListAllAmount.value)) {
buttonLoading.value = false;
return ElMessage.warning('支付金额不足,可选择 部分缴纳 方式');
}
}
const obj = {
'detailsId': inComeFrom.value.billResidentId.map((item) => item.detailsId),
'payType': inComeFrom.value.payType,
'payStatus': inComeFrom.value.payStatus,
discountMethod: inComeFrom.value.discountMethod, //优惠方式: 0不优惠 1 比例 2 金额
discountRate: inComeFrom.value.discountRate, // 优惠比例(优惠方式为比例时使用,如 10 表示 10%
singleDiscountAmount: inComeFrom.value.discount, // 单笔优惠金额(优惠方式为金额时使用
remark: inComeFrom.value.remark,
payItems: payList.value
};
billEdit(obj)
.then(() => {
ElMessage.success('缴费成功');
getList();
closer();
})
.finally(() => {
buttonLoading.value = false;
});
};
//
// 打印票据--------------------------------------------------------------
const isPrint = ref(false);
const dialog_print = reactive<DialogOption>({
visible: false,
title: '票据打印'
});
// 收款人
const inHomelyUse = ref('');
// 票据表单
const print_form = ref<BillPrintType>({
totalDiscountAmount: '',
totalActualAccount: '',
'totalAccount': '',
'residentName': '',
'detailsList': [],
'printDate': '',
'HouseName': '',
'title': ''
});
// 打印票据
function printPIaoJuByOnly(row: billlistType | false) {
isPrint.value = false;
inHomelyUse.value = userStore.nickname;
let query = null;
if (row) {
query = {
'residentId': row.residentId,
'detailsIds': [row.detailsId]
};
} else {
if (ids.value.length <= 0) {
return ElMessage.warning('请选择账单!');
}
let residentId = null;
const set = new Set();
ids.value.forEach((item) => {
set.add(item.residentId);
});
if (set.size > 1) {
ElMessage.warning('缴费人不一致,重新选择');
return;
} else if (set.size === 1) {
residentId = Array.from(set).at(0);
query = {
'residentId': residentId,
'detailsIds': ids.value.map((item) => item.detailsId)
};
}
}
billPrint(query).then((res) => {
print_form.value = res.data;
dialog_print.visible = true;
});
}
const isLoading = ref(false);
const printOption = {
id: 'printBox-Bill',
popTitle: '票据打印',
printStyle: `
@page {
size: 25cm 10cm;
margin: 0;
}
* {
box-sizing: border-box;
margin: 0 !important;
padding: 0 !important;
}
@media print {
html {
height: 10cm !important;
overflow: hidden !important;
}
body {
height: 10cm !important;
overflow: hidden !important;
position: absolute;
left:0;
top:0;
transform-origin: top left;
transform: scale(0.999);
}
#printBox-Bill {
width: 25cm !important;
height: 10cm !important;
}
table {
border-collapse: collapse;
width: 100% !important;
table-layout: auto;
}
table th, table td {
border: 1px solid #333;
font-size: 10px;
min-height: 32px;
text-align: center;
padding: 1px 2px;
line-height: 1.1;
white-space: normal;
}
}
`,
afterCloseCallback: () => {
isPrint.value = false;
isLoading.value = false;
}
};
// 打印状态恢复函数
function resetPrintState() {
isPrint.value = false;
isLoading.value = false;
}
function conFimPrint() {
isLoading.value = true;
isPrint.value = true;
setTimeout(() => {
// printTicketDiv('printBox-Bill', '票据打印', () => {
// console.log('打印完成');
// isPrint.value = false;
// isLoading.value = false;
// });
// printTicket('printBox-Bill', '票据打印', () => {
// console.log('打印完成');
// isPrint.value = false;
// isLoading.value = false;
// });
resetPrintState();
}, 100);
}
onMounted(() => {
queryParams.value.villageId = villageId;
getList();
getHouseTypeList();
// 全部小区
getCommunitylistAPI().then((res) => {
villageList.value = res.rows;
handleChangeVillage(queryParams.value.villageId);
});
});
// 总 表单
const totalAll = ref({
totalDiscountAmount: 0,
TotalPaymentAmount: 0,
totalAccount: 0
});
// 多选时 统计总金额,优惠总金额,实付总金额
function handleSelectBill(val: billlistType[]) {
totalAll.value.totalAccount = sum(val.map((item) => item.amount ?? 0));
totalAll.value.TotalPaymentAmount = sum(val.map((item) => item.actualAccount ?? 0));
totalAll.value.totalDiscountAmount = sum(val.map((item) => item.singleDiscountAmount ?? 0));
}
function handleChangePayTypeId(val: string, index: number) {
// 【可选兜底校验】防止异常数据出现重复
const repeatCount = usedPayValues.value.filter((v) => v === val).length;
if (repeatCount > 1) {
payList.value[index].payType = null;
}
}
const usedPayValues = computed(() => {
return payList.value.map((item) => item.payType).filter((v) => v !== null && v !== undefined && v !== '');
});
const isOptionDisabled = (optVal: string, currentVal: any) => {
// 当前行自己选中的,不禁用;其他行占用的,禁用
if (optVal === currentVal) return false;
return usedPayValues.value.includes(optVal);
};
// 混合支付 列表
const payList = ref([
{
payType: '',
payAmount: null
}
]);
// 混合支付 总金额
const payListAllAmount = computed(() => {
return sum(payList.value.map((item) => item.payAmount ?? 0));
});
// 添加支付方式
function addPayList() {
payList.value.push({
payType: '',
payAmount: null
});
}
// 移除支付方式
function removePayList(index: number) {
payList.value.splice(index, 1);
}
// 数据框改变时 检测数据是否大于 总金额
function handleChangePay(val: string, index: number = -1) {
if (!checkMoney(val)) {
ElMessage.warning('金额格式错误');
payList.value[index].payAmount = null;
return;
}
if (gtMoney(payListAllAmount.value, totalPayAmount.value)) {
ElMessage.warning('输入金额 总和 不能大于 总实缴金额');
if (index > -1) {
payList.value[index].payAmount = 0;
// 自动回填
payList.value[index].payAmount = sub(totalPayAmount.value, payListAllAmount.value);
}
}
// 已缴纳状态时,输入金额 总和 不能小于 总实缴金额
if (inComeFrom.value.payStatus === '1') {
if (ltMoney(payListAllAmount.value, totalPayAmount.value)) {
ElMessage.warning('输入金额 总和 不能小于 总实缴金额');
}
}
}
function handlePayListIsEmpt() {
if (payList.value.length === 0) {
ElMessage.warning('请添加支付方式');
return false;
}
const invalidIndex = payList.value.findIndex(
(item) =>
item.payType === null ||
item.payType === undefined ||
item.payType === '' ||
item.payAmount === null ||
item.payAmount === undefined ||
item.payAmount === ''
);
if (invalidIndex !== -1) {
return false;
}
return true;
}
// 校验数字
function checkMoney(value: string) {
if (!value) return false;
const reg = /^(0|[1-9]\d*)(\.\d{1,2})?$/;
return reg.test(value);
}
</script>
<style lang="scss" scoped>
.DiscountColorRed {
color: red;
}
.el-table {
height: calc(100% - 80px);
}
.actionsBox {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
height: 50px;
box-sizing: border-box;
padding: 0 20px;
margin-bottom: 10px;
border-radius: 5px;
}
.tabs-box {
display: flex;
align-items: center;
.tabs-items {
width: 70px;
height: 40px;
line-height: 40px;
border-bottom: 2px solid transparent;
text-align: center;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
&:hover {
opacity: 0.5;
color: #1a75ff;
border-bottom-color: #1a75ff;
}
&.active {
border-bottom-color: #1a75ff;
color: #1a75ff;
}
}
}
.payInfo {
color: rgba(0, 0, 0, 1);
margin-top: 20px;
padding-bottom: 10px;
margin-bottom: 30px;
border-bottom: 1px solid #d2d2d2;
& > div {
margin-bottom: 10px;
}
}
.printBox {
.tableBox {
.printTable {
th,
td {
text-align: center;
height: 40px;
border: 1px solid #000;
}
}
}
}
.footerBox {
display: flex;
align-items: center;
justify-content: space-between;
.totalBox {
font-size: 14px;
}
}
</style>