Files
estate/src/components/Holder/user.vue
2026-09-20 08:57:53 +08:00

411 lines
12 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>
<slot name="default" />
<el-dialog v-model="dialog.dialogVisible" :title="dialog.dialogTitle" append-to-body width="1050">
<nav class="flex flex-items-center mb-8 justify-between">
<div class="flex flex-items-center">
<div class="flex flex-items-center">
<div style="width: 50px; text-align: right; padding-right: 10px">楼栋</div>
<div class="flex-1"></div>
<el-select v-model="queryParams.buildingId" clearable style="width: 120px" @change="handleChangeBuilding">
<el-option v-for="(item, index) in buildingList" :key="index" :label="item.buildingName" :value="item.id"></el-option>
</el-select>
</div>
<div class="flex flex-items-center mr-20px">
<div style="width: 50px; text-align: right; padding-right: 10px">单元</div>
<div class="flex-1">
<el-select v-model="queryParams.unitNoId" clearable style="width: 120px" @change="handleChangeUnit">
<el-option v-for="(item, index) in unitList" :key="index" :label="item.unitNoName" :value="item.unitNoId"></el-option>
</el-select>
</div>
</div>
<div class="flex flex-items-center mr-20px">
<div style="width: 60px; text-align: right; padding-right: 10px">房屋号</div>
<div class="flex-1">
<el-input v-model="queryParams.houseNo" clearable placeholder="请输入房屋号" style="width: 120px" @clear="handleClear"></el-input>
</div>
</div>
<div class="flex flex-items-center mr-20px">
<div style="width: 80px; text-align: right; padding-right: 10px">业主名称</div>
<div class="flex-1">
<el-input v-model="queryParams.residentName" clearable placeholder="请输入业主名称" style="width: 120px" @clear="handleClear"></el-input>
</div>
</div>
</div>
<div>
<el-button type="primary" @click="getList">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
</nav>
<el-table ref="multipleTableRef" v-loading="loading" :data="tableData" border>
<el-table-column align="center" width="55">
<template #header>
<el-checkbox v-model="isSelectAll" :indeterminate="isIndeterminate" @change="handleSelectAllChange" />
</template>
<template #default="scope">
<el-checkbox :model-value="isRowSelected(scope.row)" @change="(val: boolean) => handleRowCheckChange(scope.row, val)" />
</template>
</el-table-column>
<el-table-column align="center" label="序号" type="index" width="55" />
<el-table-column align="center" label="楼栋号">
<template #default="scope">
<span>{{ scope.row.house_name ? scope.row.house_name.split('-')[0] : '---' }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="单元">
<template #default="scope">
<span>{{ scope.row.house_name ? scope.row.house_name.split('-')[1] : '---' }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="房间">
<template #default="scope">
<span>{{ scope.row.house_name ? scope.row.house_name.split('-')[2] : '---' }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="业主">
<template #default="scope">
<span>{{ scope.row.residentName ? scope.row.residentName : '---' }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="性别">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" label="手机号" prop="phone" />
</el-table>
<template #footer>
<div class="dialog-footer flex flex-items-center flex-justify-between">
<pagination
v-show="total > 0"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNum"
:total="total"
style="margin-top: 0"
@pagination="getList"
/>
<div>
<el-button @click="conback">退出</el-button>
<el-button :loading="loading" type="primary" @click="confirm"> 确定 </el-button>
</div>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { getBuildinglistAPI, getHouselistAPI } from '@/api/system/house';
import { BillUserlist, queryResidentList_holder } from '@/api/system/SdbBill';
import { ComponentInternalInstance, getCurrentInstance, ref, toRefs } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
const props = defineProps({
returnvalue: {
type: String,
default: 'residentId'
},
titleName: {
type: String,
default: '选择业主'
},
/** 是否多选 false 单选 */
isMultiple: {
type: Boolean,
default: false
}
});
const { titleName, returnvalue } = toRefs(props);
const userid = ref([]);
// ✅ 手动设置默认值(替代 withDefaults)
const emit = defineEmits<{
change: [value: string | number | (string | number)[] | BillUserlist | BillUserlist[]]; // 支持返回数组
}>();
const dialog = ref({
dialogVisible: false,
dialogTitle: titleName.value
});
const queryParams = ref({
pageNum: 1,
pageSize: 10,
buildingId: null,
residentName: null,
houseNo: null,
unitNoId: null
});
const multipleTableRef = ref();
const tableData = ref<BillUserlist[]>([]);
const total = ref(0);
const loading = ref(false);
/** 存储所有选中项(跨页累计,非全选模式使用) */
const multipleSelection = ref<BillUserlist[]>([]);
/** 是否已跨页全选(标记模式,不实际持有全部数据) */
const isSelectAll = ref(false);
/** 表头复选框半选状态(部分选中时为 true) */
const isIndeterminate = ref(false);
/** 全选模式下被手动取消勾选的 residentId 列表 */
const deselectedIds = ref<string[]>([]);
const isMultiple = computed(() => props.isMultiple ?? false);
/** 判断某行是否已选中 */
function isRowSelected(row: BillUserlist): boolean {
if (isSelectAll.value) {
// 全选模式:只要不在取消列表中就算选中
return !deselectedIds.value.includes(row.residentId);
}
return multipleSelection.value.some((item) => item.residentId === row.residentId);
}
/** 更新表头复选框状态:全选 / 半选 / 空 */
function updateIndeterminate() {
if (isSelectAll.value) {
// 全选模式下:有取消勾选的行 → 半选,否则 → 全选
isIndeterminate.value = deselectedIds.value.length > 0;
return;
}
const selectedCount = multipleSelection.value.length;
const totalCount = total.value;
if (selectedCount === 0) {
isIndeterminate.value = false;
} else if (totalCount > 0 && selectedCount >= totalCount) {
// 手动逐行全选 → 切换为全选标记模式,释放 multipleSelection 内存
isSelectAll.value = true;
isIndeterminate.value = false;
deselectedIds.value = [];
multipleSelection.value = [];
} else {
isIndeterminate.value = true;
}
}
/** 跨页全选 / 取消全选(仅设标记,不拉取数据) */
function handleSelectAllChange(val: boolean) {
if (val) {
isSelectAll.value = true;
deselectedIds.value = [];
multipleSelection.value = [];
} else {
isSelectAll.value = false;
deselectedIds.value = [];
multipleSelection.value = [];
}
updateIndeterminate();
}
/** 单行勾选 / 取消勾选 */
function handleRowCheckChange(row: BillUserlist, checked: boolean) {
if (isSelectAll.value) {
// 全选模式:维护取消列表
if (checked) {
deselectedIds.value = deselectedIds.value.filter((id) => id !== row.residentId);
} else {
if (!deselectedIds.value.includes(row.residentId)) {
deselectedIds.value.push(row.residentId);
}
}
} else {
// 非全选模式:维护选中列表
if (checked) {
if (!isRowSelected(row)) {
multipleSelection.value.push(row);
}
} else {
multipleSelection.value = multipleSelection.value.filter((item) => item.residentId !== row.residentId);
}
}
updateIndeterminate();
}
// ====================== 方法 ======================
// 获取列表
function getList() {
loading.value = true;
queryResidentList_holder(queryParams.value)
.then((res) => {
tableData.value = res.rows;
total.value = res.total;
updateIndeterminate();
})
.catch((error) => {
console.error('获取用户列表失败:', error);
})
.finally(() => {
loading.value = false;
});
}
/** 楼栋列表 */
const buildingList = ref([]);
/** 单元列表 */
const unitList = ref([]);
/** 获取楼栋 列表 */
function handleChangeHouse() {
getBuildinglistAPI().then((res) => {
buildingList.value = res.data;
});
}
function handleClear() {
getList();
}
/** 单元列表 */
function handleChangeBuilding(val: string) {
queryParams.value.unitNoId = null;
unitList.value = [];
if (queryParams.value.buildingId) {
getHouselistAPI(val).then((res) => {
unitList.value = res.data.units;
getList();
});
}
getList();
}
function handleChangeUnit() {
getList();
}
// 重置查询
function resetQuery() {
queryParams.value = {
pageNum: 1,
pageSize: 10,
buildingId: null,
unitNoId: null,
residentName: null,
houseNo: null
};
resetSelection();
getList();
}
// 退出
function conback() {
dialog.value.dialogVisible = false;
resetQuery();
}
/** 重置所有选中状态 */
function resetSelection() {
multipleSelection.value = [];
isSelectAll.value = false;
isIndeterminate.value = false;
deselectedIds.value = [];
}
/** 根据 returnvalue 发射选中结果 */
function emitResult(data: BillUserlist[]) {
if (isMultiple.value) {
if (returnvalue.value === '*') {
emit('change', data);
} else {
emit(
'change',
data.map((item) => item[returnvalue.value as keyof BillUserlist])
);
}
} else {
if (returnvalue.value === '*') {
emit('change', data[0]);
} else {
emit('change', data[0]?.[returnvalue.value as keyof BillUserlist]);
}
}
}
/** 确定:全选模式下按需拉取全部数据,非全选模式直接使用已选列表 */
async function confirm() {
if (isSelectAll.value) {
// 全选模式:此时才真正拉取全部数据,排除已取消勾选的行
loading.value = true;
try {
const res = await queryResidentList_holder({
...queryParams.value,
pageNum: 1,
pageSize: total.value || undefined
});
const allData: BillUserlist[] = res.rows || [];
const filteredData = allData.filter((item) => !deselectedIds.value.includes(item.residentId));
dialog.value.dialogVisible = false;
emitResult(filteredData);
} catch (error) {
console.error('获取全部数据失败:', error);
} finally {
loading.value = false;
}
} else {
// 非全选模式:直接使用已收集的选中列表
dialog.value.dialogVisible = false;
emitResult(multipleSelection.value);
}
resetSelection();
}
defineExpose({
open: (arr: string[] = []) => {
userid.value = arr;
// 重置查询条件,避免残留上次的筛选条件
queryParams.value = {
pageNum: 1,
pageSize: 10,
buildingId: null,
residentName: null,
houseNo: null,
unitNoId: null
};
// 立即清空旧列表数据,避免闪烁
tableData.value = [];
total.value = 0;
// 重置全选标记
isSelectAll.value = false;
deselectedIds.value = [];
// 根据传入的 ID 数组初始化选中状态
if (arr && arr.length > 0) {
multipleSelection.value = arr.map(
(id) =>
({
residentId: id
}) as BillUserlist
);
isIndeterminate.value = true;
} else {
multipleSelection.value = [];
isIndeterminate.value = false;
}
getList();
handleChangeHouse();
dialog.value.dialogVisible = true;
}
});
</script>
<style lang="scss" scoped>
.residentBox {
width: 385px;
height: 35px;
line-height: 35px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 0 10px;
.defaultbox {
display: flex;
align-items: center;
justify-content: space-between;
}
cursor: pointer;
&:hover {
background-color: #f4f8ff;
}
}
</style>