2025-09-02 16:22:57 +08:00
|
|
|
|
import { MyBetaModalFormProps, MyButtons, renderTextHelper } from '@/common';
|
2025-09-01 21:32:29 +08:00
|
|
|
|
import { MyModal } from '@/components/MyModal';
|
|
|
|
|
|
import { Apis } from '@/gen/Apis';
|
2025-09-02 16:22:57 +08:00
|
|
|
|
import {
|
|
|
|
|
|
HouseBillsTypeEnum,
|
|
|
|
|
|
HouseChargeStandardsCalculationModeEnum,
|
|
|
|
|
|
HouseChargeStandardsCalculationPeriodEnum,
|
|
|
|
|
|
} from '@/gen/Enums';
|
2025-09-01 21:32:29 +08:00
|
|
|
|
import { ProCard } from '@ant-design/pro-components';
|
|
|
|
|
|
import {
|
|
|
|
|
|
Alert,
|
|
|
|
|
|
Button,
|
|
|
|
|
|
Checkbox,
|
|
|
|
|
|
message,
|
|
|
|
|
|
Space,
|
|
|
|
|
|
Tree,
|
|
|
|
|
|
Typography,
|
|
|
|
|
|
} from 'antd';
|
|
|
|
|
|
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
|
|
|
|
|
import type { DataNode } from 'antd/es/tree';
|
|
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
const { Title } = Typography;
|
|
|
|
|
|
|
|
|
|
|
|
interface TreeNodeType extends DataNode {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
isLeaf?: boolean;
|
|
|
|
|
|
children?: TreeNodeType[];
|
|
|
|
|
|
asset_buildings_id?: number;
|
|
|
|
|
|
asset_units_id?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 扩展 MyBetaModalFormProps 接口,添加 onCancel 属性
|
|
|
|
|
|
interface ChargeStandardHasHouseProps extends MyBetaModalFormProps {
|
|
|
|
|
|
onCancel?: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function ChargeStandardHasHouse(
|
|
|
|
|
|
props: ChargeStandardHasHouseProps,
|
|
|
|
|
|
) {
|
|
|
|
|
|
const [treeData, setTreeData] = useState<TreeNodeType[]>([]);
|
|
|
|
|
|
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
|
|
|
|
|
|
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
|
|
|
|
|
|
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
|
|
|
|
|
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
|
|
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
|
|
|
|
const [selectAll, setSelectAll] = useState<boolean>(false);
|
|
|
|
|
|
const [selectedHouses, setSelectedHouses] = useState<
|
|
|
|
|
|
{ id: number; name: string }[]
|
|
|
|
|
|
>([]);
|
|
|
|
|
|
const modalRef: any = useRef(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载楼栋数据
|
|
|
|
|
|
const loadBuildings = async () => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await Apis.Asset.AssetBuildings.List({
|
|
|
|
|
|
asset_projects_id: props?.item?.asset_projects_id,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res?.data) {
|
|
|
|
|
|
const buildings = res.data.map((building: any) => ({
|
|
|
|
|
|
id: building.id,
|
|
|
|
|
|
key: `building-${building.id}`,
|
|
|
|
|
|
title: building.name,
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
isLeaf: false,
|
|
|
|
|
|
}));
|
|
|
|
|
|
setTreeData(buildings);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载楼栋失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载单元数据
|
|
|
|
|
|
const loadUnits = async (buildingId: number, buildingKey: string) => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await Apis.Asset.AssetUnits.List({
|
|
|
|
|
|
asset_projects_id: props?.item?.asset_projects_id,
|
|
|
|
|
|
asset_buildings_id: buildingId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (res?.data) {
|
|
|
|
|
|
const units = res.data.map((unit: any) => ({
|
|
|
|
|
|
id: unit.id,
|
|
|
|
|
|
key: `unit-${unit.id}`,
|
|
|
|
|
|
title: unit.name,
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
isLeaf: false,
|
|
|
|
|
|
asset_buildings_id: buildingId,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 更新树形数据
|
|
|
|
|
|
const newTreeData = [...treeData];
|
|
|
|
|
|
const buildingNode = newTreeData.find(
|
|
|
|
|
|
(node) => node.key === buildingKey,
|
|
|
|
|
|
);
|
|
|
|
|
|
if (buildingNode) {
|
|
|
|
|
|
buildingNode.children = units;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setTreeData(newTreeData);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载单元失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载房屋数据
|
|
|
|
|
|
const loadHouses = async (
|
|
|
|
|
|
buildingId: number,
|
|
|
|
|
|
unitId: number,
|
|
|
|
|
|
unitKey: string,
|
|
|
|
|
|
) => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await Apis.Asset.AssetHouses.List({
|
|
|
|
|
|
asset_projects_id: props?.item?.asset_projects_id,
|
|
|
|
|
|
asset_buildings_id: buildingId,
|
|
|
|
|
|
asset_units_id: unitId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (res?.data) {
|
|
|
|
|
|
const houses = res.data.map((house: any) => ({
|
|
|
|
|
|
id: house.id,
|
|
|
|
|
|
key: `house-${house.id}`,
|
|
|
|
|
|
title: `${house.name} (${house.floor}层)`,
|
|
|
|
|
|
isLeaf: true,
|
|
|
|
|
|
asset_buildings_id: buildingId,
|
|
|
|
|
|
asset_units_id: unitId,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 更新树形数据
|
|
|
|
|
|
const newTreeData = [...treeData];
|
|
|
|
|
|
const buildingNode = newTreeData.find(
|
|
|
|
|
|
(node) =>
|
|
|
|
|
|
node.asset_buildings_id === undefined && node.id === buildingId,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (buildingNode && buildingNode.children) {
|
|
|
|
|
|
const unitNode = buildingNode.children.find(
|
|
|
|
|
|
(node) => node.key === unitKey,
|
|
|
|
|
|
);
|
|
|
|
|
|
if (unitNode) {
|
|
|
|
|
|
unitNode.children = houses;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setTreeData(newTreeData);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载房屋失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化加载数据
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (props?.item?.asset_projects_id) {
|
|
|
|
|
|
loadBuildings();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('缺少 asset_projects_id 参数');
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [props?.item?.id]);
|
|
|
|
|
|
|
|
|
|
|
|
// 处理节点展开
|
|
|
|
|
|
const onExpand = (expandedKeysValue: React.Key[]) => {
|
|
|
|
|
|
setExpandedKeys(expandedKeysValue);
|
|
|
|
|
|
setAutoExpandParent(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理节点选中
|
2025-09-02 16:22:57 +08:00
|
|
|
|
const onCheck = async (
|
2025-09-01 21:32:29 +08:00
|
|
|
|
checkedKeysValue:
|
|
|
|
|
|
| React.Key[]
|
|
|
|
|
|
| { checked: React.Key[]; halfChecked: React.Key[] },
|
|
|
|
|
|
) => {
|
|
|
|
|
|
// 处理不同格式的返回值
|
|
|
|
|
|
const keys = Array.isArray(checkedKeysValue)
|
|
|
|
|
|
? checkedKeysValue
|
|
|
|
|
|
: checkedKeysValue.checked;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取之前的选中状态,用于比较变化
|
|
|
|
|
|
const prevKeys = new Set(checkedKeys);
|
|
|
|
|
|
const newKeys = new Set(keys);
|
|
|
|
|
|
|
2025-09-02 16:22:57 +08:00
|
|
|
|
// 找出新选中的节点
|
|
|
|
|
|
const newlyCheckedKeys = [...newKeys].filter((key) => !prevKeys.has(key));
|
|
|
|
|
|
|
2025-09-01 21:32:29 +08:00
|
|
|
|
// 找出新取消选中的节点
|
|
|
|
|
|
const uncheckedKeys = [...prevKeys].filter((key) => !newKeys.has(key));
|
|
|
|
|
|
|
2025-09-02 16:22:57 +08:00
|
|
|
|
// 处理新选中的节点
|
|
|
|
|
|
for (const key of newlyCheckedKeys) {
|
|
|
|
|
|
const keyStr = key.toString();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果选中的是楼栋
|
|
|
|
|
|
if (keyStr.startsWith('building-')) {
|
|
|
|
|
|
const buildingNode = treeData.find((node) => node.key === key);
|
|
|
|
|
|
if (buildingNode) {
|
|
|
|
|
|
// 调用接口获取该楼栋下所有房屋
|
|
|
|
|
|
await loadBuildingHouses(buildingNode.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果选中的是单元
|
|
|
|
|
|
else if (keyStr.startsWith('unit-')) {
|
|
|
|
|
|
// 查找该单元所属的楼栋和单元ID
|
|
|
|
|
|
for (const building of treeData) {
|
|
|
|
|
|
const unitNode = building.children?.find((unit) => unit.key === key);
|
|
|
|
|
|
if (unitNode) {
|
|
|
|
|
|
// 调用接口获取该单元下所有房屋
|
|
|
|
|
|
await loadUnitHouses(building.id, unitNode.id);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-01 21:32:29 +08:00
|
|
|
|
// 如果有节点被取消选中,同步取消其所有子节点
|
|
|
|
|
|
if (uncheckedKeys.length > 0) {
|
|
|
|
|
|
const keysToRemove = new Set<React.Key>();
|
|
|
|
|
|
const findChildKeys = (nodes: TreeNodeType[], parentKey: React.Key) => {
|
|
|
|
|
|
nodes.forEach((node) => {
|
|
|
|
|
|
if (node.key === parentKey) {
|
|
|
|
|
|
// 将当前节点及其所有子节点的key加入待移除集合
|
|
|
|
|
|
const collectKeys = (n: TreeNodeType) => {
|
|
|
|
|
|
keysToRemove.add(n.key);
|
|
|
|
|
|
if (n.children) {
|
|
|
|
|
|
n.children.forEach(collectKeys);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
collectKeys(node);
|
|
|
|
|
|
} else if (node.children) {
|
|
|
|
|
|
findChildKeys(node.children, parentKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
uncheckedKeys.forEach((key) => {
|
|
|
|
|
|
findChildKeys(treeData, key);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 从选中keys中移除所有需要取消的节点
|
|
|
|
|
|
const finalKeys = keys.filter((key) => !keysToRemove.has(key));
|
|
|
|
|
|
setCheckedKeys(finalKeys);
|
2025-09-02 16:22:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新selectedHouses,移除被取消选中的房屋
|
|
|
|
|
|
const houseKeysToRemove = new Set<string>();
|
|
|
|
|
|
keysToRemove.forEach((key) => {
|
|
|
|
|
|
if (key.toString().startsWith('house-')) {
|
|
|
|
|
|
houseKeysToRemove.add(key.toString().replace('house-', ''));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const updatedHouses = selectedHouses.filter(
|
|
|
|
|
|
(house) => !houseKeysToRemove.has(house.id.toString()),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
setSelectedHouses(updatedHouses);
|
2025-09-01 21:32:29 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
setCheckedKeys(keys);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有选中的房屋
|
2025-09-02 16:22:57 +08:00
|
|
|
|
const selectedHousesList: {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
buildingName?: string;
|
|
|
|
|
|
unitName?: string;
|
|
|
|
|
|
}[] = [...selectedHouses];
|
2025-09-01 21:32:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 遍历树形数据,收集选中节点下的所有房屋
|
|
|
|
|
|
const collectHouses = (nodes: TreeNodeType[], checkedKeys: React.Key[]) => {
|
|
|
|
|
|
nodes.forEach((node) => {
|
|
|
|
|
|
if (checkedKeys.includes(node.key)) {
|
|
|
|
|
|
if (node.isLeaf) {
|
2025-09-02 16:22:57 +08:00
|
|
|
|
// 如果是房屋节点,检查是否已经存在
|
|
|
|
|
|
const houseId = node.id;
|
|
|
|
|
|
const exists = selectedHousesList.some(
|
|
|
|
|
|
(house) => house.id === houseId,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!exists) {
|
|
|
|
|
|
// 查找楼栋和单元信息
|
|
|
|
|
|
let buildingName = '';
|
|
|
|
|
|
let unitName = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 查找楼栋和单元
|
|
|
|
|
|
for (const building of treeData) {
|
|
|
|
|
|
if (building.id === node.asset_buildings_id) {
|
|
|
|
|
|
buildingName = building.title as string;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找单元
|
|
|
|
|
|
const unit = building.children?.find(
|
|
|
|
|
|
(u) => u.id === node.asset_units_id,
|
|
|
|
|
|
);
|
|
|
|
|
|
if (unit) {
|
|
|
|
|
|
unitName = unit.title as string;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到选中列表
|
|
|
|
|
|
selectedHousesList.push({
|
|
|
|
|
|
id: houseId,
|
|
|
|
|
|
name: `${buildingName} ${unitName} ${node.title}(${houseId})`,
|
|
|
|
|
|
buildingName,
|
|
|
|
|
|
unitName,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-01 21:32:29 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 如果是楼栋或单元节点,递归收集其下的所有房屋
|
|
|
|
|
|
if (node.children) {
|
|
|
|
|
|
collectHouses(node.children, checkedKeys);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
collectHouses(treeData, keys);
|
|
|
|
|
|
|
|
|
|
|
|
setSelectedHouses(selectedHousesList);
|
|
|
|
|
|
setSelectAll(
|
|
|
|
|
|
selectedHousesList.length > 0 &&
|
|
|
|
|
|
selectedHousesList.length ===
|
|
|
|
|
|
treeData.reduce(
|
|
|
|
|
|
(total, building) =>
|
|
|
|
|
|
total +
|
|
|
|
|
|
(building.children?.reduce(
|
|
|
|
|
|
(unitTotal, unit) => unitTotal + (unit.children?.length || 0),
|
|
|
|
|
|
0,
|
|
|
|
|
|
) || 0),
|
|
|
|
|
|
0,
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理节点选择
|
|
|
|
|
|
const onSelect = (selectedKeysValue: React.Key[]) => {
|
|
|
|
|
|
setSelectedKeys(selectedKeysValue);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理动态加载数据
|
|
|
|
|
|
const onLoadData = async (node: TreeNodeType) => {
|
|
|
|
|
|
if (node.isLeaf) {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载楼栋下的单元
|
|
|
|
|
|
if (node.key.toString().startsWith('building-')) {
|
2025-09-02 16:22:57 +08:00
|
|
|
|
console.log('node.key', node.key);
|
|
|
|
|
|
// 从key中提取buildingId,格式为'building-{id}'
|
|
|
|
|
|
const buildingId = parseInt(
|
|
|
|
|
|
node.key.toString().replace('building-', ''),
|
|
|
|
|
|
10,
|
|
|
|
|
|
);
|
2025-09-01 21:32:29 +08:00
|
|
|
|
await loadUnits(buildingId, node.key as string);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果楼栋被选中,加载并选中其下所有单元和房屋
|
|
|
|
|
|
if (checkedKeys.includes(node.key)) {
|
2025-09-02 16:22:57 +08:00
|
|
|
|
// 直接调用loadBuildingHouses加载该楼栋下所有房屋
|
|
|
|
|
|
await loadBuildingHouses(buildingId);
|
2025-09-01 21:32:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载单元下的房屋
|
|
|
|
|
|
if (node.key.toString().startsWith('unit-')) {
|
2025-09-02 16:22:57 +08:00
|
|
|
|
// 从key中提取unitId,格式为'unit-{id}'
|
|
|
|
|
|
const unitId = parseInt(node.key.toString().replace('unit-', ''), 10);
|
2025-09-01 21:32:29 +08:00
|
|
|
|
const buildingId = node.asset_buildings_id as number;
|
|
|
|
|
|
await loadHouses(buildingId, unitId, node.key as string);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果单元被选中,选中其下所有房屋
|
|
|
|
|
|
if (checkedKeys.includes(node.key)) {
|
2025-09-02 16:22:57 +08:00
|
|
|
|
// 直接调用loadUnitHouses加载该单元下所有房屋
|
|
|
|
|
|
await loadUnitHouses(buildingId, unitId);
|
2025-09-01 21:32:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-02 16:22:57 +08:00
|
|
|
|
// 加载所有房屋数据
|
|
|
|
|
|
const loadAllHouses = async () => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await Apis.Asset.AssetHouses.List({
|
|
|
|
|
|
asset_projects_id: props?.item?.asset_projects_id,
|
|
|
|
|
|
});
|
2025-09-01 21:32:29 +08:00
|
|
|
|
|
2025-09-02 16:22:57 +08:00
|
|
|
|
if (res?.data) {
|
|
|
|
|
|
const allHouseKeys: React.Key[] = [];
|
|
|
|
|
|
const allHouses: {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
buildingName: string;
|
|
|
|
|
|
unitName: string;
|
|
|
|
|
|
}[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 创建映射以快速查找楼栋和单元名称
|
|
|
|
|
|
const buildingMap = new Map();
|
|
|
|
|
|
const unitMap = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
// 填充楼栋映射
|
|
|
|
|
|
treeData.forEach((building) => {
|
|
|
|
|
|
buildingMap.set(building.id, building.title);
|
|
|
|
|
|
building.children?.forEach((unit) => {
|
|
|
|
|
|
unitMap.set(unit.id, unit.title);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
res.data.forEach((house: any) => {
|
|
|
|
|
|
const houseKey = `house-${house.id}`;
|
|
|
|
|
|
allHouseKeys.push(houseKey);
|
|
|
|
|
|
|
|
|
|
|
|
const buildingName = buildingMap.get(house.asset_buildings_id) || '';
|
|
|
|
|
|
const unitName = unitMap.get(house.asset_units_id) || '';
|
|
|
|
|
|
|
|
|
|
|
|
allHouses.push({
|
|
|
|
|
|
id: house.id,
|
|
|
|
|
|
name: `${buildingName} ${unitName} ${house.name}(${house.id})`,
|
|
|
|
|
|
buildingName: buildingName as string,
|
|
|
|
|
|
unitName: unitName as string,
|
2025-09-01 21:32:29 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
2025-09-02 16:22:57 +08:00
|
|
|
|
|
|
|
|
|
|
setCheckedKeys(allHouseKeys);
|
|
|
|
|
|
setSelectedHouses(allHouses);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载所有房屋失败:', error);
|
|
|
|
|
|
message.error('加载所有房屋失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载楼栋下所有房屋
|
|
|
|
|
|
const loadBuildingHouses = async (buildingId: number) => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await Apis.Asset.AssetHouses.List({
|
|
|
|
|
|
asset_projects_id: props?.item?.asset_projects_id,
|
|
|
|
|
|
asset_buildings_id: buildingId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (res?.data) {
|
|
|
|
|
|
const buildingHouseKeys: React.Key[] = [];
|
|
|
|
|
|
const buildingHouses: {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
buildingName: string;
|
|
|
|
|
|
unitName: string;
|
|
|
|
|
|
}[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 获取楼栋名称
|
|
|
|
|
|
const building = treeData.find((b) => b.id === buildingId);
|
|
|
|
|
|
const buildingName = building?.title || '';
|
|
|
|
|
|
|
|
|
|
|
|
// 创建单元映射
|
|
|
|
|
|
const unitMap = new Map();
|
|
|
|
|
|
building?.children?.forEach((unit) => {
|
|
|
|
|
|
unitMap.set(unit.id, unit.title);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
res.data.forEach((house: any) => {
|
|
|
|
|
|
const houseKey = `house-${house.id}`;
|
|
|
|
|
|
buildingHouseKeys.push(houseKey);
|
|
|
|
|
|
|
|
|
|
|
|
const unitName = unitMap.get(house.asset_units_id) || '';
|
|
|
|
|
|
|
|
|
|
|
|
buildingHouses.push({
|
|
|
|
|
|
id: house.id,
|
|
|
|
|
|
name: `${buildingName} ${unitName} ${house.name}(${house.id})`,
|
|
|
|
|
|
buildingName: buildingName as string,
|
|
|
|
|
|
unitName: unitName as string,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 合并当前选中的keys和新的keys
|
|
|
|
|
|
const newCheckedKeys = Array.from(
|
|
|
|
|
|
new Set([...checkedKeys, ...buildingHouseKeys]),
|
|
|
|
|
|
);
|
|
|
|
|
|
setCheckedKeys(newCheckedKeys);
|
|
|
|
|
|
|
|
|
|
|
|
// 合并当前选中的房屋和新的房屋
|
|
|
|
|
|
const existingIds = new Set(selectedHouses.map((h) => h.id));
|
|
|
|
|
|
const newHouses = buildingHouses.filter((h) => !existingIds.has(h.id));
|
|
|
|
|
|
setSelectedHouses([...selectedHouses, ...newHouses]);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载楼栋房屋失败:', error);
|
|
|
|
|
|
message.error('加载楼栋房屋失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载单元下所有房屋
|
|
|
|
|
|
const loadUnitHouses = async (buildingId: number, unitId: number) => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await Apis.Asset.AssetHouses.List({
|
|
|
|
|
|
asset_projects_id: props?.item?.asset_projects_id,
|
|
|
|
|
|
asset_buildings_id: buildingId,
|
|
|
|
|
|
asset_units_id: unitId,
|
2025-09-01 21:32:29 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-02 16:22:57 +08:00
|
|
|
|
if (res?.data) {
|
|
|
|
|
|
const unitHouseKeys: React.Key[] = [];
|
|
|
|
|
|
const unitHouses: {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
buildingName: string;
|
|
|
|
|
|
unitName: string;
|
|
|
|
|
|
}[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 获取楼栋和单元名称
|
|
|
|
|
|
const building = treeData.find((b) => b.id === buildingId);
|
|
|
|
|
|
const buildingName = building?.title || '';
|
|
|
|
|
|
|
|
|
|
|
|
const unit = building?.children?.find((u) => u.id === unitId);
|
|
|
|
|
|
const unitName = unit?.title || '';
|
|
|
|
|
|
|
|
|
|
|
|
res.data.forEach((house: any) => {
|
|
|
|
|
|
const houseKey = `house-${house.id}`;
|
|
|
|
|
|
unitHouseKeys.push(houseKey);
|
|
|
|
|
|
|
|
|
|
|
|
unitHouses.push({
|
|
|
|
|
|
id: house.id,
|
|
|
|
|
|
name: `${buildingName} ${unitName} ${house.name}(${house.id})`,
|
|
|
|
|
|
buildingName: buildingName as string,
|
|
|
|
|
|
unitName: unitName as string,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 合并当前选中的keys和新的keys
|
|
|
|
|
|
const newCheckedKeys = Array.from(
|
|
|
|
|
|
new Set([...checkedKeys, ...unitHouseKeys]),
|
|
|
|
|
|
);
|
|
|
|
|
|
setCheckedKeys(newCheckedKeys);
|
|
|
|
|
|
|
|
|
|
|
|
// 合并当前选中的房屋和新的房屋
|
|
|
|
|
|
const existingIds = new Set(selectedHouses.map((h) => h.id));
|
|
|
|
|
|
const newHouses = unitHouses.filter((h) => !existingIds.has(h.id));
|
|
|
|
|
|
setSelectedHouses([...selectedHouses, ...newHouses]);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载单元房屋失败:', error);
|
|
|
|
|
|
message.error('加载单元房屋失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理全选
|
|
|
|
|
|
const handleSelectAll = async (e: CheckboxChangeEvent) => {
|
|
|
|
|
|
setSelectAll(e.target.checked);
|
|
|
|
|
|
|
|
|
|
|
|
if (e.target.checked) {
|
|
|
|
|
|
// 调用接口获取所有房屋
|
|
|
|
|
|
await loadAllHouses();
|
2025-09-01 21:32:29 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 取消全选
|
|
|
|
|
|
setCheckedKeys([]);
|
|
|
|
|
|
setSelectedHouses([]);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 提交选中的房屋
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
|
if (selectedHouses.length === 0) {
|
|
|
|
|
|
message.warning('请至少选择一个房屋');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
// 将 number[] 转换为 string[]
|
|
|
|
|
|
const houses_ids = selectedHouses.map((house) => house.id.toString());
|
|
|
|
|
|
|
|
|
|
|
|
await Apis.HouseCharage.HouseChargeHasHouses.Store({
|
|
|
|
|
|
house_charge_standards_id: props?.item?.id,
|
|
|
|
|
|
houses_ids,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
message.success('绑定房屋成功');
|
|
|
|
|
|
props?.reload?.();
|
|
|
|
|
|
props?.onCancel?.();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('绑定房屋失败:', error);
|
|
|
|
|
|
message.error('绑定房屋失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<MyModal
|
|
|
|
|
|
title={props.title || '查看'}
|
|
|
|
|
|
width="800px"
|
|
|
|
|
|
myRef={modalRef}
|
|
|
|
|
|
trigger={
|
|
|
|
|
|
<MyButtons.Default
|
|
|
|
|
|
type="primary"
|
2025-09-02 16:22:57 +08:00
|
|
|
|
size="small"
|
2025-09-01 21:32:29 +08:00
|
|
|
|
title={`${props.title}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
}
|
|
|
|
|
|
node={
|
|
|
|
|
|
<ProCard
|
|
|
|
|
|
title={
|
2025-09-02 16:22:57 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<Title level={4} style={{ marginBottom: '16px' }}>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div>收费标准名称:{props?.item?.name || '标准名称'}</div>
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<renderTextHelper.Tag
|
|
|
|
|
|
Enums={HouseBillsTypeEnum}
|
|
|
|
|
|
value={props?.item?.charge_type}
|
|
|
|
|
|
key="type"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<renderTextHelper.Tag
|
|
|
|
|
|
Enums={HouseChargeStandardsCalculationModeEnum}
|
|
|
|
|
|
value={props?.item?.calculation_mode}
|
|
|
|
|
|
key="type"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<renderTextHelper.Tag
|
|
|
|
|
|
Enums={HouseChargeStandardsCalculationPeriodEnum}
|
|
|
|
|
|
value={props?.item?.calculation_period}
|
|
|
|
|
|
key="type"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Title>
|
|
|
|
|
|
<Alert
|
|
|
|
|
|
message="请选择需要绑定的房屋"
|
|
|
|
|
|
type="info"
|
|
|
|
|
|
showIcon
|
|
|
|
|
|
style={{ margin: 0 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-09-01 21:32:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ display: 'flex', height: '500px' }}>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: '50%',
|
|
|
|
|
|
borderRight: '1px solid #f0f0f0',
|
|
|
|
|
|
padding: '0 16px',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ marginBottom: 16 }}>
|
|
|
|
|
|
<Checkbox checked={selectAll} onChange={handleSelectAll}>
|
|
|
|
|
|
全选
|
|
|
|
|
|
</Checkbox>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{loading && <div>加载中...</div>}
|
|
|
|
|
|
|
|
|
|
|
|
<Tree
|
|
|
|
|
|
checkable
|
|
|
|
|
|
checkStrictly={false}
|
|
|
|
|
|
onExpand={onExpand}
|
|
|
|
|
|
expandedKeys={expandedKeys}
|
|
|
|
|
|
autoExpandParent={autoExpandParent}
|
|
|
|
|
|
onCheck={onCheck}
|
|
|
|
|
|
checkedKeys={checkedKeys}
|
|
|
|
|
|
onSelect={onSelect}
|
|
|
|
|
|
selectedKeys={selectedKeys}
|
|
|
|
|
|
loadData={onLoadData}
|
|
|
|
|
|
treeData={treeData}
|
|
|
|
|
|
height={400}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ width: '50%', padding: '0 16px' }}>
|
|
|
|
|
|
<div style={{ marginBottom: 16 }}>
|
|
|
|
|
|
<Title level={5}>已选房屋 ({selectedHouses.length})</Title>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ height: 400, overflow: 'auto' }}>
|
|
|
|
|
|
{selectedHouses.length > 0 ? (
|
|
|
|
|
|
<ul style={{ padding: '0 0 0 20px' }}>
|
|
|
|
|
|
{selectedHouses.map((house) => (
|
|
|
|
|
|
<li key={house.id}>{house.name}</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
color: '#999',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
marginTop: 100,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
暂无选中房屋{' '}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginTop: 16, textAlign: 'right' }}>
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button onClick={props?.onCancel}>取消</Button>
|
|
|
|
|
|
<Button type="primary" loading={loading} onClick={handleSubmit}>
|
|
|
|
|
|
确认绑定
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ProCard>
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|