pay-admin/src/pages/asset/components/modals/ChargeStandardHasHouse.tsx

731 lines
22 KiB
TypeScript
Raw Normal View History

2025-09-02 16:22:57 +08:00
import { MyBetaModalFormProps, MyButtons, renderTextHelper } from '@/common';
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';
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 (
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));
// 找出新取消选中的节点
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;
}
}
}
}
// 如果有节点被取消选中,同步取消其所有子节点
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);
} else {
setCheckedKeys(keys);
}
// 收集所有选中的房屋
2025-09-02 16:22:57 +08:00
const selectedHousesList: {
id: number;
name: string;
buildingName?: string;
unitName?: string;
}[] = [...selectedHouses];
// 遍历树形数据,收集选中节点下的所有房屋
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,
});
}
} 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,
);
await loadUnits(buildingId, node.key as string);
// 如果楼栋被选中,加载并选中其下所有单元和房屋
if (checkedKeys.includes(node.key)) {
2025-09-02 16:22:57 +08:00
// 直接调用loadBuildingHouses加载该楼栋下所有房屋
await loadBuildingHouses(buildingId);
}
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);
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);
}
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-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-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-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();
} 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"
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>
}
>
<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>
}
/>
);
}