pay-admin/src/pages/asset/components/modals/ChargeStandardHasHouse.tsx
uiuJun 6edacd4926
All checks were successful
Build and Push Docker Image / build (push) Successful in 3m11s
feat:收费标准0.5 + 房屋绑定0.5
2025-09-01 21:32:29 +08:00

492 lines
14 KiB
TypeScript

import { MyBetaModalFormProps, MyButtons } from '@/common';
import { MyModal } from '@/components/MyModal';
import { Apis } from '@/gen/Apis';
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);
};
// 处理节点选中
const onCheck = (
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);
// 找出新取消选中的节点
const uncheckedKeys = [...prevKeys].filter((key) => !newKeys.has(key));
// 如果有节点被取消选中,同步取消其所有子节点
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);
} else {
setCheckedKeys(keys);
}
// 收集所有选中的房屋
const selectedHousesList: { id: number; name: string }[] = [];
// 遍历树形数据,收集选中节点下的所有房屋
const collectHouses = (nodes: TreeNodeType[], checkedKeys: React.Key[]) => {
nodes.forEach((node) => {
if (checkedKeys.includes(node.key)) {
if (node.isLeaf) {
// 如果是房屋节点,直接添加
selectedHousesList.push({
id: node.id,
name: node.title as string,
});
} 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-')) {
const buildingId = node.id;
await loadUnits(buildingId, node.key as string);
// 如果楼栋被选中,加载并选中其下所有单元和房屋
if (checkedKeys.includes(node.key)) {
const newTreeData = [...treeData];
const buildingNode = newTreeData.find((n) => n.key === node.key);
if (buildingNode?.children) {
// 加载每个单元下的房屋
for (const unit of buildingNode.children) {
await loadHouses(buildingId, unit.id, unit.key as string);
}
// 更新树形数据
setTreeData(newTreeData);
// 收集所有房屋的key
const allKeys: React.Key[] = [];
const collectKeys = (nodes: TreeNodeType[]) => {
nodes.forEach((node) => {
allKeys.push(node.key);
if (node.children) {
collectKeys(node.children);
}
});
};
collectKeys([buildingNode]);
// 更新选中状态
setCheckedKeys(Array.from(new Set([...checkedKeys, ...allKeys])));
}
}
return Promise.resolve();
}
// 加载单元下的房屋
if (node.key.toString().startsWith('unit-')) {
const unitId = node.id;
const buildingId = node.asset_buildings_id as number;
await loadHouses(buildingId, unitId, node.key as string);
// 如果单元被选中,选中其下所有房屋
if (checkedKeys.includes(node.key)) {
const newTreeData = [...treeData];
const buildingNode = newTreeData.find((n) =>
n.children?.some((unit) => unit.key === node.key),
);
const unitNode = buildingNode?.children?.find(
(n) => n.key === node.key,
);
if (unitNode?.children) {
const houseKeys = unitNode.children.map((house) => house.key);
setCheckedKeys(Array.from(new Set([...checkedKeys, ...houseKeys])));
}
}
return Promise.resolve();
}
return Promise.resolve();
};
// 处理全选
const handleSelectAll = (e: CheckboxChangeEvent) => {
setSelectAll(e.target.checked);
if (e.target.checked) {
// 收集所有房屋节点的key
const allHouseKeys: React.Key[] = [];
const allHouses: { id: number; name: string }[] = [];
treeData.forEach((building) => {
building.children?.forEach((unit) => {
unit.children?.forEach((house) => {
if (house.isLeaf) {
allHouseKeys.push(house.key);
allHouses.push({
id: house.id,
name: house.title as string,
});
}
});
});
});
setCheckedKeys(allHouseKeys);
setSelectedHouses(allHouses);
} 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"
size="middle"
title={`${props.title}`}
/>
}
node={
<ProCard
title={
<Alert
message="请选择需要绑定的房屋"
type="info"
showIcon
style={{ margin: 0 }}
/>
}
>
<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>
}
/>
);
}