feat: 数据真实化、企业展示墙、全局手势识别与 3D 模型完善

- 数据大屏改用真实数据(xls 统计表 + 宣传册口径);TOKEN/工具调用基于真实锚点测算
- 新增企业展示墙页(/wall,39 家全量档案 + MQTT/admin/ESP32 控制文档)
- 新增全局手势识别(GlobalVision 全局挂载 + dpm:gesture 事件分发)
- 3D 模型墙面逐间修复、去除门/过道顶棚/调试标签、房间资产不穿墙、会议室多小桌
- 侧栏企业分布扩展至 39 家完整信息,分区按官方四大孵化区域
- 语音页多轮对话展示优化、去除页脚与检测横条、背景对齐
This commit is contained in:
Pine
2026-08-18 05:48:56 +08:00
parent 1581b62154
commit 0af159a175
21 changed files with 1690 additions and 588 deletions
+77 -36
View File
@@ -245,9 +245,35 @@ function MeetingTable({ x, z, y = 0, rotY = 0, len = 2.6 }) {
);
}
/* 讲台(会议室讲台 */
function Podium({ x, z, y = 0 }) {
/* 会议室小办公桌(小型方桌 + 座椅,多张布置,不穿墙 */
function MeetingDesk({ x, z, y = 0, rotY = 0 }) {
return (
<group position={[x, y, z]} rotation={[0, rotY, 0]}>
{/* 桌面 */}
<mesh position={[0, 0.42, 0]} castShadow>
<boxGeometry args={[0.9, 0.05, 0.6]} />
<meshStandardMaterial color="#e8dcc4" roughness={0.55} metalness={0.05} />
</mesh>
{/* 桌腿 */}
<mesh position={[0, 0.2, 0]} castShadow>
<boxGeometry args={[0.06, 0.46, 0.5]} />
<meshStandardMaterial color="#d8cbb2" roughness={0.7} />
</mesh>
{/* 座椅(桌前) */}
<mesh position={[0, 0.24, 0.45]} castShadow>
<boxGeometry args={[0.45, 0.08, 0.45]} />
<meshStandardMaterial color="#4f5f7a" roughness={0.7} />
</mesh>
<mesh position={[0, 0.45, 0.45]} castShadow>
<boxGeometry args={[0.45, 0.4, 0.06]} />
<meshStandardMaterial color="#4f5f7a" roughness={0.7} />
</mesh>
</group>
);
}
/* 讲台(会议室讲台) */
function Podium({ x, z, y = 0 }) { return (
<group position={[x, y, z]}>
<mesh position={[0, 0.5, 0]} castShadow>
<boxGeometry args={[0.7, 1.0, 0.5]} />
@@ -391,9 +417,9 @@ function Building() {
// 一楼大厅外墙(高度 = FLOOR2,不穿过二楼楼板)+ 窗户
const hallWalls = [
{ axis: 'z', pos: 0, from: 0, to: 44 },
{ axis: 'z', pos: 27, from: 0, to: 44 },
{ axis: 'x', pos: 0, from: 0, to: 27 },
{ axis: 'x', pos: 44, from: 0, to: 27 },
{ axis: 'z', pos: 41.1, from: 0, to: 44 },
{ axis: 'x', pos: 0, from: 0, to: 41 },
{ axis: 'x', pos: 44, from: 0, to: 41 },
];
hallWalls.forEach((wl, i) => {
const posW = wl.axis === 'z' ? cz(wl.pos) : cx(wl.pos);
@@ -496,29 +522,33 @@ function Building() {
return;
}
// 过道顶棚(右侧竖过道、横向过道
if (r.type === 'hallway') {
roofs.push(
<mesh key={`hr${i}`} position={[cX, FLOOR2 + 0.5, cZ]}>
<boxGeometry args={[w - 0.1, 0.05, d - 0.1]} />
<meshStandardMaterial color="#c3d8f2" transparent opacity={0.3} roughness={0.1} metalness={0.1} />
</mesh>
);
return;
}
// 过道不渲染顶棚(hallway:无顶棚,避免半透明色块干扰地面观感
// 家具按功能区分(地面统一色,靠资产区分)
if (r.type === 'office') {
// 办公室:办公桌(按面积放置多张)
const nX = Math.max(1, Math.floor((r.x[1] - r.x[0]) / 3.5));
const nZ = Math.max(1, Math.floor((r.z[1] - r.z[0]) / 3.5));
const total = Math.min(4, nX * nZ);
let idx = 0;
for (let ix = 0; ix < nX && idx < total; ix++) {
for (let iz = 0; iz < nZ && idx < total; iz++, idx++) {
const dx = (ix + 0.5) / nX - 0.5;
const dz = (iz + 0.5) / nZ - 0.5;
furniture.push(<Desk key={`d${i}_${idx}`} x={cX + dx * w} z={cZ + dz * d} y={FLOOR2} />);
// 办公室:办公桌按房间面积排布,留边距不穿墙
const roomW = r.x[1] - r.x[0];
const roomD = r.z[1] - r.z[0];
// 窄房间(宽 < 2.6):单桌且旋转 90°(桌+椅沿深度方向排布,避免横向穿墙)
const narrow = roomW < 2.6;
if (narrow) {
furniture.push(
<Desk key={`d${i}_0`} x={cX} z={cZ} y={FLOOR2} rotY={Math.PI / 2} />
);
} else {
const nX = Math.max(1, Math.min(3, Math.floor(roomW / 2.4)));
const nZ = Math.max(1, Math.min(2, Math.floor(roomD / 2.4)));
const total = Math.min(4, nX * nZ);
let idx = 0;
for (let ix = 0; ix < nX && idx < total; ix++) {
for (let iz = 0; iz < nZ && idx < total; iz++, idx++) {
// 留边距居中,确保桌面/椅不穿墙
const dx = (ix + 0.5) / nX - 0.5;
const dz = (iz + 0.5) / nZ - 0.5;
furniture.push(
<Desk key={`d${i}_${idx}`} x={cX + dx * (w - 1.6)} z={cZ + dz * (d - 1.6)} y={FLOOR2} />
);
}
}
}
} else if (r.type === 'desk') {
@@ -540,17 +570,27 @@ function Building() {
furniture.push(<Sofa key={`l3${i}`} x={cX} z={cZ + 0.7} y={FLOOR2} rotY={Math.PI} />);
furniture.push(<Table key={`l4${i}`} x={cX} z={cZ} y={FLOOR2} s={0.8} />);
} else if (r.type === 'meeting') {
// 会议室:按面积区分——大会议室(长桌+讲台),小会议室(单张会议桌)
const area = (r.x[1] - r.x[0]) * (r.z[1] - r.z[0]);
if (area >= 40) {
// 大会议室(M01):两张会议长桌 + 讲台
furniture.push(<MeetingTable key={`m1${i}`} x={cX - 0.5} z={cZ} y={FLOOR2} rotY={Math.PI / 2} len={Math.min(3.2, d - 0.8)} />);
furniture.push(<MeetingTable key={`m2${i}`} x={cX + 1.8} z={cZ} y={FLOOR2} rotY={Math.PI / 2} len={2.4} />);
furniture.push(<Podium key={`m3${i}`} x={cX - 2.6} z={cZ} y={FLOOR2} />);
} else {
// 小会议室(M02):单张会议长桌 + 四周座椅
furniture.push(<MeetingTable key={`m1${i}`} x={cX} z={cZ} y={FLOOR2} rotY={Math.PI / 2} len={Math.min(2.0, w - 0.6)} />);
// 会议室:多张小办公桌(MeetingDesk)按房间网格排布,不穿墙且满布
const roomW = r.x[1] - r.x[0];
const roomD = r.z[1] - r.z[0];
const nD = Math.max(1, Math.min(6, Math.floor((roomW - 0.8) / 1.2))); // 沿 X 列(留边距,桌宽0.9+隙)
const nR = Math.max(1, Math.min(4, Math.floor((roomD - 0.8) / 1.2))); // 沿 Z 行
const total = Math.min(12, nD * nR); // 上限 12 张小桌
let idx = 0;
for (let ix = 0; ix < nD && idx < total; ix++) {
for (let iz = 0; iz < nR && idx < total; iz++, idx++) {
// 留边距居中(每侧 0.5 单位),避免穿墙
const dx = (ix + 0.5) / nD - 0.5;
const dz = (iz + 0.5) / nR - 0.5;
furniture.push(
<MeetingDesk key={`md${i}_${idx}`}
x={cX + dx * (w - 1.0)} z={cZ + dz * (d - 1.0)} y={FLOOR2} />
);
}
}
// 大会议室额外加讲台
const aBig = roomW * roomD >= 40;
if (aBig) furniture.push(<Podium key={`m3${i}`} x={cX - (w - 1.0) / 2 + 0.6} z={cZ} y={FLOOR2} />);
} else if (r.type === 'stair') {
// 楼梯间:真实楼梯(二楼 → 一楼),自动按房间宽/深选择梯段方向与级数
const alongX = (x1 - x0) >= (z1 - z0); // 房间宽≥深 → 梯段沿 X 方向
@@ -563,7 +603,8 @@ function Building() {
steps={steps} stepW={Math.min(0.9, (alongX ? d : w) - 0.1)} stepD={sLen / steps} />
);
}
// 标签:每个房间显示编号(编号 + 名称),办公室显示入驻企业名
// 标签:每个房间显示编号(编号 + 名称),办公室显示入驻企业名;过道不显示标签
if (r.type === 'hallway') return;
const noLabel = r.no || '';
const typeLabel =
r.type === 'desk' ? '共享工位区' :