Files
estate/src/views/system/Sdb/SdbCashier/index.vue
2026-06-22 08:14:35 +08:00

618 lines
20 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 v-model="queryParams.chargeItemId" placeholder="请选择收费项目">
<el-option 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="period">
<el-select v-model="queryParams.period" placeholder="请选择收费项目">
<el-option v-for="(item, index) in com_bill_charge_period" :key="index" :value="item.value" :label="item.label"></el-option>
</el-select>
</el-form-item>
<el-form-item label="业主姓名" prop="residentName">
<el-input v-model.trim="queryParams.residentName"></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)">收费</el-button>
<el-button @click="printPIaoJuByOnly(false)">打印票据</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" width="55" align="center" />
<el-table-column label="收费项目" align="center" prop="billCode">
<template #default="scope">
{{ scope.row.chargeItemName }}
</template>
</el-table-column>
<el-table-column label="收费名称" align="center" prop="detailsName">
<template #default="scope">
<div style="text-align: left">
<span>{{ scope.row.detailsName }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="收费周期" width="80" align="center" prop="chargePeriod">
<template #default="scope">
<dict-tag :options="com_bill_charge_period" :value="scope.row.chargePeriod"></dict-tag>
</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> / </span>
<dict-tag :options="com_charge_item_unit" :value="scope.row.chargePeriod"></dict-tag>
</div>
</template>
</el-table-column>
<el-table-column label="应收金额" width="100" align="center" prop="amount" />
<el-table-column label="缴费状态" width="80" align="center" prop="payStatus">
<template #default="scope">
<DictTag :options="com_pay_status" :value="scope.row.payStatus"></DictTag>
</template>
</el-table-column>
<el-table-column label="应缴金额" width="100" align="center" prop="amount" />
<el-table-column label="实缴金额" width="100" align="center" prop="amount" />
<el-table-column label="缴费人" align="center" prop="residentName" />
<el-table-column label="缴费时间" align="center" prop="" />
<el-table-column label="操作" width="220" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" v-if="scope.row.payStatus === '0'" @click="handleIncome(scope.row)" v-has-permi="['bill:bill:edit']"
>收费</el-button
>
<el-button link type="primary" v-if="scope.row.payStatus === '1'" @click="printPIaoJuByOnly(scope.row)" v-has-permi="['bill:bill:query']"
>打印票据</el-button
>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<div class="payInfo">
<div>
<div>缴费人 {{ inComeFrom.residentName }}</div>
</div>
<div class="flex">
<span>收费名称 </span>
<div>
<div v-for="(item, index) in inComeFrom.billResidentId" :key="index" style="text-wrap: wrap; margin-bottom: 5px">
<span style="text-decoration: underline">{{ item.detailsName }} ;</span>
</div>
</div>
</div>
<div>
<span>应收合计{{ inComeFrom.allCost }}</span>
</div>
<div>
<span>应缴合计{{ inComeFrom.allCost }}</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>
<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 v-for="dict in com_pay_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="支付方式" prop="payType">
<el-select v-model="inComeFrom.payType" placeholder="请选择">
<el-option v-for="dict in com_pay_method" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" :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" :loading="isLoading" @click="conFimPrint">打印</el-button>
</div>
</template>
<div class="printBox" id="printBox-Bill">
<div class="title">{{ print_form.title }}</div>
<div class="nav gridBox">
<div>单据日期{{ print_form.printDate }}</div>
<div>房号{{ print_form.HouseName }}</div>
<div>产权人{{ print_form.residentName }}</div>
<div>No.</div>
</div>
<div class="tableBox">
<table class="printTable">
<thead>
<tr>
<th>收费项目</th>
<th>收费标准</th>
<th>账单时间</th>
<th>面积</th>
<th style="width: 80px">应收金额</th>
<th style="width: 80px">实收金额</th>
<th>收款方式</th>
<th style="width: 80px">备注</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 style="width: 80px">{{ item.account }}</td>
<td style="width: 80px">{{ item.account }}</td>
<td>{{ item.payType }}</td>
<td style="width: 80px">{{ item.remark }}</td>
</tr>
<tr>
<td>合计</td>
<td></td>
<td></td>
<td></td>
<td style="width: 80px">{{ print_form.totalAccount }}</td>
<td style="width: 80px">{{ print_form.totalAccount }}</td>
<td style="width: 80px"></td>
</tr>
</tbody>
</table>
<p style="font-size: 10px">说明本收据收款单位和收款具签章方为有效本收据手写无效</p>
<div class="flex flex-items-center" style="font-size: 12px">
<div class="flex-1">交款人</div>
<div class="flex-1 flex flex-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 { getBuildinglists, 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 { printTicketDiv } from '@/utils/print';
import { moneyAdd } 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';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_bill_charge_period } = toRefs<any>(proxy?.useDict('com_bill_charge_period'));
const { com_charge_item_unit } = toRefs<any>(proxy?.useDict('com_charge_item_unit'));
const { com_pay_status, com_pay_method } = toRefs<any>(proxy?.useDict('com_pay_status', 'com_pay_method'));
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: '已缴纳'
}
];
const queryParams = ref({
pageNum: 1,
pageSize: 10,
payStatus: null,
villageId: undefined,
buildingId: undefined,
unitNoId: undefined,
houseId: undefined,
residentName: undefined,
chargeItemId: undefined,
period: undefined
});
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: ''
});
// 切换 账单类型
function changeTabs(value: number) {
activeTabs.value = value;
queryParams.value.payStatus = value;
getList();
}
// 选择小区
function handleChangeVillage(val: string) {
queryParams.value.unitNoId = undefined;
queryParams.value.buildingId = undefined;
buildingList.value = [];
unitList.value = [];
if (val) {
getBuildinglists({
villageId: val
}).then((res) => {
buildingList.value = res.rows;
handleChargeItem(val);
});
return;
}
}
// 获取收费项目
function handleChargeItem(id: string) {
listChargeItem({
villageId: id
}).then((res) => {
chargeItems.value = res.rows;
});
}
// 选择楼栋
function handleChangeBuilding(val: string): void {
queryParams.value.unitNoId = 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 getList() {
loading.value = true;
billQuery(queryParams.value)
.then((res) => {
tableData.value = res.rows;
total.value = res.total;
})
.finally(() => {
loading.value = false;
});
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value = {
pageNum: 1,
pageSize: 20,
payStatus: null,
villageId: undefined,
buildingId: undefined,
unitNoId: undefined,
houseId: undefined,
chargeItemId: undefined,
period: undefined,
residentName: undefined
};
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: billlistType[]) => {
ids.value = selection.map((item) => item);
};
const buttonLoading = ref(false);
// 收费表单
const inComeFrom = ref({
allCost: null, // 金额
billResidentId: [], // 账单id
payStatus: '0', // 0:未缴 1:已缴
payType: '0', // 0:微信 1:支付宝 2 线下
remark: '',
residentName: '' // 缴费人
});
// 收费 --------------------------------------------------------------
/** 收费 */
function handleIncome(row: billlistType | false) {
console.log(ids.value);
inComeFrom.value.allCost = 0;
inComeFrom.value.billResidentId = [];
inComeFrom.value.residentName = '';
if (row) {
inComeFrom.value.allCost = row.amount;
inComeFrom.value.billResidentId = [row];
inComeFrom.value.residentName = row.residentName;
} else {
const set = new Set();
ids.value.forEach((item) => {
set.add(item.residentId);
});
if (set.size > 1) {
ElMessage.warning('缴费人不一致,重新选择');
return;
} else if (set.size === 1) {
inComeFrom.value.residentName = ids.value[0].residentName;
ids.value.forEach((item) => {
inComeFrom.value.billResidentId.push(item);
inComeFrom.value.allCost = moneyAdd(inComeFrom.value.allCost, item.amount);
});
console.log(inComeFrom.value);
}
}
dialog.visible = true;
}
const closer = () => {
dialog.visible = false;
dialog.title = '';
};
const submitForm = () => {
buttonLoading.value = true;
const obj = {
'detailsId': inComeFrom.value.billResidentId.map((item) => item.detailsId),
'payType': inComeFrom.value.payType,
'payStatus': inComeFrom.value.payStatus
};
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>({
'totalAccount': '',
'residentName': '',
'detailsList': [],
'printDate': '',
'HouseName': '',
'title': ''
});
function printPIaoJuByOnly(row: billlistType | false) {
isPrint.value = false;
inHomelyUse.value = '';
let query = null;
if (row) {
query = {
'residentId': row.residentId,
'detailsIds': [row.detailsId]
};
} else {
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);
function conFimPrint() {
isLoading.value = true;
isPrint.value = true;
setTimeout(() => {
printTicketDiv('printBox-Bill', '票据打印', () => {
isPrint.value = false;
isLoading.value = false;
});
}, 1000);
}
onMounted(() => {
getList();
// 全部小区
getCommunitylistAPI().then((res) => {
villageList.value = res.rows;
});
});
</script>
<style lang="scss" scoped>
.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 {
color: rgba(0, 0, 0, 1);
padding-top: 5px;
.title {
font-weight: bold;
font-size: 1.2rem;
margin-bottom: 10px;
text-indent: 5em;
}
.nav {
font-size: 12px;
&.gridBox {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 10px;
grid-template-rows: 1fr;
}
&.flexbox {
display: flex;
align-items: center;
justify-content: space-between;
}
}
.tableBox {
font-size: 10px;
.printTable {
border-collapse: collapse;
border: 1px solid #000;
th,
td {
text-align: center;
min-width: 140px;
height: 40px;
border: 1px solid #000;
}
}
}
}
</style>