上节实现了尺寸标注:

这节我们开始做户型切换、家具的导入和编辑。

我们把左侧的菜单分为两个列表,一个是户型列表,可以切换户型,一个是家具列表,可以导入家具。
用 antd 的 Segmented 组件:

改下 components/Menu/index.tsx

Segmented 的options 里是这两个选项对应的 label 和值。
用一个状态保存 key,Segmented 的onChange 里 setKey
当 key 为户型和家具的时候,分别展示不同的内容。
这里还用到了 @ant-design/icons 的 icon
安装下:
npm install --save @ant-design/icons
import { HomeOutlined, UngroupOutlined } from "@ant-design/icons";
import { Segmented } from "antd";
import { useState } from "react";
function Menu() {
const [left, setLeft] = useState(0);
const [key, setKey] = useState('户型');
return <div className="Menu" style={{left: left}}>
<Segmented value={key} onChange={setKey} block options={[
{
label: <div>
<HomeOutlined />
<span style={{padding: 10}}>户型</span>
</div>,
value: '户型',
},
{
label: <div>
<UngroupOutlined />
<span style={{padding: 10}}>家具</span>
</div>,
value: '家具',
},
]} />
{
key === '户型' ? <div>
111
</div> : null
}
{ key === '家具' ? <div>
222
</div> : null
}
<div className="drawer-bar" onClick={() => {
setLeft(left === 0 ? -300 : 0);
}}></div>
</div>
}
export default Menu;
顺便把样式里的背景色改成白色:

看下效果:

然后写一下户型列表:
现在只有两个户型:


把这两张图放到 public 目录下:

然后写一下家具列表:
用 antd 的 Card 组件:

import { Card, Image, Segmented } from "antd";
{
key === '户型' ? <div>
<Card
hoverable
style={{ width: 200, margin: 20 }}
cover={<Image
width={200}
src="./house1.png"
/>}
>
<Meta title="1室1厅0厨0卫" description="" />
</Card>
<Card
hoverable
style={{ width: 200, margin: 20 }}
cover={<Image
width={200}
src="./house2.png"
/>}
>
<Meta title="1室2厅0厨0卫" description="" />
</Card>
</div> : null
}

然后点击的时候清空当前户型数据,设置为新的户型。
酷家乐里也是这样:

切换户型会清空当前数据。
我们给 store 添加一个方法:

这里要添加一个 Action 类型,然后 setData 里返回新的 state。
export interface Action {
setData(data: State['data']): void;
}
const useHouseStore = create<State & Action>((set, get) => {
return {
data: data,
setData(data) {
set(state => {
return {
...state,
data: data
}
})
}
}
});
在 Menu 组件里点击户型的时候调用 setData:


import data1 from "../../store/house1";
import data2 from "../../store/house2";
const {setData} = useHouseStore();
onClick={() => setData(data1)}
onClick={() => setData(data2)}
试下效果:

新的户型数据确实设置好了,但之前的场景没清空。
我们处理下:


house.name = 'house';
给两个场景的 hosue 加个 name。
然后 data 变化的时候把 house 删掉:

useEffect(() => {
const scene1 = scene2DRef.current;
const scene2 = scene3DRef.current;
const house1 = scene1?.getObjectByName('house');
const house2 = scene2?.getObjectByName('house');
house1?.parent?.remove(house1);
house2?.parent?.remove(house2);
house1?.traverse(item => {
let obj = item as THREE.Mesh;
if(obj.isMesh) {
obj.geometry.dispose();
}
})
}, [data])
根据 name 查找到 house,把它删掉,并且遍历释放所有 geometry 的 gpu 资源。
试下效果:

有一个问题,重新渲染之后,模型不会重新加载:

去掉之前的缓存逻辑,每次重新加载就好了:


async function loadWindow() {
const group = new THREE.Group();
const loader = new GLTFLoader();
const gltf = await loader.loadAsync("./window.glb");
group.add(gltf.scene);
const box = new THREE.Box3();
box.expandByObject(gltf.scene);
const size = box.getSize(new THREE.Vector3());
return {
model: group,
size
};
}
async function loadDoor() {
const group = new THREE.Group();
const loader = new GLTFLoader();
const gltf = await loader.loadAsync("./door.glb");
group.add(gltf.scene);
const box = new THREE.Box3();
box.expandByObject(gltf.scene);
const size = box.getSize(new THREE.Vector3());
return {
model: group,
size
}
}

最后我们还要加一个提醒:


用 antd 的 PopConfirm 组件来实现。
{
key === '户型' ? <div>
<Popconfirm
title="提醒"
description="切换户型将清空数据,是否继续?"
onConfirm={() => setData(data1)}
okText="是"
cancelText="否"
>
<Card
hoverable
style={{ width: 200, margin: 20 }}
cover={<Image
width={200}
src="./house1.png"
/>}
>
<Meta title="1室1厅0厨0卫" description="" />
</Card>
</Popconfirm>
<Popconfirm
title="提醒"
description="切换户型将清空数据,是否继续?"
onConfirm={() => setData(data2)}
okText="是"
cancelText="否"
>
<Card
hoverable
style={{ width: 200, margin: 20 }}
cover={<Image
width={200}
src="./house2.png"
/>}
>
<Meta title="1室2厅0厨0卫" description="" />
</Card>
</Popconfirm>
</div> : null
}
试一下:

这样,户型列表和切换就做好了。
最后我们解决下这个的位置问题:

把 left 改为 320px:

试一下:

案例代码上传了小册仓库
总结
这节我们实现了户型切换。
首先我们用 Segmented、Card 组件实现了户型列表的布局,点击的时候修改 store 里的数据来实现户型切换。
需要注意的是切换户型的时候,要找到之前 house 对象,把它删掉。
户型列表和切换实现后,下节就可以来做家具列表和家具导入了。