添加一些数据
This commit is contained in:
262
demo-next/components/BimViewer.tsx
Normal file
262
demo-next/components/BimViewer.tsx
Normal file
@@ -0,0 +1,262 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import ControlPanel from './ControlPanel';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
IflowEngine: any;
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_MODEL_URL = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/8634e556-a94e-4ba7-be3e-2ea1507cced5/';
|
||||
|
||||
export default function BimViewer() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const engineRef = useRef<any>(null);
|
||||
const [engineStatus, setEngineStatus] = useState<string>('未初始化');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const updateStatus = useCallback((status: string) => {
|
||||
setEngineStatus(status);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || typeof window === 'undefined') return;
|
||||
|
||||
if (!window.IflowEngine) {
|
||||
console.error('SDK not found');
|
||||
updateStatus('SDK 未加载');
|
||||
return;
|
||||
}
|
||||
|
||||
const Engine = window.IflowEngine.BimEngine;
|
||||
const engine = new Engine(containerRef.current, { locale: 'zh-CN' });
|
||||
engineRef.current = engine;
|
||||
|
||||
engine.rightKey.registerHandler((event: any) => {
|
||||
const { x, y } = event;
|
||||
return [
|
||||
{
|
||||
id: 'home',
|
||||
label: 'toolbar.home',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>',
|
||||
onClick: () => {
|
||||
console.log('Go Home');
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'log-pos',
|
||||
label: '打印坐标',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>',
|
||||
onClick: () => {
|
||||
console.log(`Clicked at: ${x}, ${y}`);
|
||||
}
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (engineRef.current) {
|
||||
engineRef.current.destroy();
|
||||
engineRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [updateStatus]);
|
||||
|
||||
const initEngine = useCallback(() => {
|
||||
const engine = engineRef.current;
|
||||
if (!engine || !engine.engine) {
|
||||
alert('引擎未创建,请先等待页面加载完成');
|
||||
return;
|
||||
}
|
||||
|
||||
if (engine.engine.isInitialized()) {
|
||||
alert('3D 引擎已经初始化过了');
|
||||
updateStatus('已初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const success = engine.engine.initialize({
|
||||
backgroundColor: 0x333333,
|
||||
version: 'v2',
|
||||
showStats: false,
|
||||
showViewCube: true
|
||||
});
|
||||
|
||||
if (success) {
|
||||
updateStatus('已初始化');
|
||||
console.log('3D 引擎初始化成功');
|
||||
} else {
|
||||
updateStatus('初始化失败');
|
||||
console.error('3D 引擎初始化失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
updateStatus('初始化错误');
|
||||
console.error('3D 引擎初始化错误:', error);
|
||||
}
|
||||
}, [updateStatus]);
|
||||
|
||||
const loadModel = useCallback((url?: string) => {
|
||||
const engine = engineRef.current;
|
||||
if (!engine || !engine.engine) {
|
||||
alert('引擎未创建');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!engine.engine.isInitialized()) {
|
||||
alert('请先初始化 3D 引擎!');
|
||||
return;
|
||||
}
|
||||
|
||||
const modelUrl = url || DEFAULT_MODEL_URL;
|
||||
if (!modelUrl.trim()) {
|
||||
alert('请输入模型 URL');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
engine.engine.loadModel([modelUrl], {
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0],
|
||||
scale: [1, 1, 1]
|
||||
});
|
||||
console.log('模型加载请求已发送:', modelUrl);
|
||||
} catch (error: any) {
|
||||
console.error('模型加载错误:', error);
|
||||
} finally {
|
||||
setTimeout(() => setIsLoading(false), 2000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setLang = useCallback((lang: 'zh-CN' | 'en-US') => {
|
||||
if (engineRef.current) {
|
||||
engineRef.current.setLocale(lang);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setTheme = useCallback((themeName: 'dark' | 'light') => {
|
||||
if (engineRef.current) {
|
||||
engineRef.current.setTheme(themeName);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setCustomTheme = useCallback(() => {
|
||||
if (!engineRef.current) return;
|
||||
engineRef.current.setCustomTheme({
|
||||
name: 'red-alert',
|
||||
primary: '#d32f2f',
|
||||
primaryHover: '#b71c1c',
|
||||
background: '#ffebee',
|
||||
panelBackground: 'rgba(255, 255, 255, 0.9)',
|
||||
textPrimary: '#b71c1c',
|
||||
textSecondary: '#e57373',
|
||||
border: '#ffcdd2',
|
||||
icon: '#d32f2f',
|
||||
iconActive: '#b71c1c',
|
||||
componentBackground: 'rgba(255, 205, 210, 0.3)',
|
||||
componentHover: 'rgba(255, 205, 210, 0.8)',
|
||||
componentActive: '#e57373'
|
||||
});
|
||||
}, []);
|
||||
|
||||
const openTestDialog = useCallback(() => {
|
||||
if (!engineRef.current || !engineRef.current.dialog) return;
|
||||
engineRef.current.dialog.create({
|
||||
title: 'dialog.testTitle',
|
||||
width: 350,
|
||||
height: 200,
|
||||
position: 'center',
|
||||
draggable: true,
|
||||
resizable: true
|
||||
});
|
||||
}, []);
|
||||
|
||||
const openInfoDialog = useCallback(() => {
|
||||
if (!engineRef.current || !engineRef.current.dialog) return;
|
||||
engineRef.current.dialog.showInfoDialog();
|
||||
}, []);
|
||||
|
||||
const openTreeDialog = useCallback(() => {
|
||||
if (!engineRef.current || !engineRef.current.modelTree) return;
|
||||
engineRef.current.modelTree.showStructTree();
|
||||
}, []);
|
||||
|
||||
const toggleToolbar = useCallback((visible: boolean) => {
|
||||
if (!engineRef.current || !engineRef.current.toolbar) return;
|
||||
engineRef.current.toolbar.setVisible(visible);
|
||||
}, []);
|
||||
|
||||
const toggleLabel = useCallback((visible: boolean) => {
|
||||
if (!engineRef.current || !engineRef.current.toolbar) return;
|
||||
engineRef.current.toolbar.setShowLabel(visible);
|
||||
}, []);
|
||||
|
||||
const pauseRendering = useCallback(() => {
|
||||
if (!engineRef.current?.engine?.isInitialized()) {
|
||||
alert('请先初始化 3D 引擎!');
|
||||
return;
|
||||
}
|
||||
engineRef.current.engine.pauseRendering();
|
||||
console.log('渲染已暂停');
|
||||
}, []);
|
||||
|
||||
const resumeRendering = useCallback(() => {
|
||||
if (!engineRef.current?.engine?.isInitialized()) {
|
||||
alert('请先初始化 3D 引擎!');
|
||||
return;
|
||||
}
|
||||
engineRef.current.engine.resumeRendering();
|
||||
console.log('渲染已恢复');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', width: '100%', height: '100%', overflow: 'hidden' }}>
|
||||
<ControlPanel
|
||||
engineStatus={engineStatus}
|
||||
isLoading={isLoading}
|
||||
onInitEngine={initEngine}
|
||||
onLoadModel={loadModel}
|
||||
onSetLang={setLang}
|
||||
onSetTheme={setTheme}
|
||||
onSetCustomTheme={setCustomTheme}
|
||||
onOpenTestDialog={openTestDialog}
|
||||
onOpenInfoDialog={openInfoDialog}
|
||||
onOpenTreeDialog={openTreeDialog}
|
||||
onToggleToolbar={toggleToolbar}
|
||||
onToggleLabel={toggleLabel}
|
||||
onPauseRendering={pauseRendering}
|
||||
onResumeRendering={resumeRendering}
|
||||
/>
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
background: '#1a1a1a',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
/>
|
||||
{isLoading && (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
background: 'rgba(0, 0, 0, 0.8)',
|
||||
color: '#fff',
|
||||
padding: '20px 40px',
|
||||
borderRadius: '8px',
|
||||
zIndex: 9999,
|
||||
fontSize: '16px'
|
||||
}}
|
||||
>
|
||||
模型加载中...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
224
demo-next/components/ControlPanel.tsx
Normal file
224
demo-next/components/ControlPanel.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface ControlPanelProps {
|
||||
engineStatus: string;
|
||||
isLoading: boolean;
|
||||
onInitEngine: () => void;
|
||||
onLoadModel: (url?: string) => void;
|
||||
onSetLang: (lang: 'zh-CN' | 'en-US') => void;
|
||||
onSetTheme: (theme: 'dark' | 'light') => void;
|
||||
onSetCustomTheme: () => void;
|
||||
onOpenTestDialog: () => void;
|
||||
onOpenInfoDialog: () => void;
|
||||
onOpenTreeDialog: () => void;
|
||||
onToggleToolbar: (visible: boolean) => void;
|
||||
onToggleLabel: (visible: boolean) => void;
|
||||
onPauseRendering: () => void;
|
||||
onResumeRendering: () => void;
|
||||
}
|
||||
|
||||
export default function ControlPanel({
|
||||
engineStatus,
|
||||
onInitEngine,
|
||||
onLoadModel,
|
||||
onSetLang,
|
||||
onSetTheme,
|
||||
onSetCustomTheme,
|
||||
onOpenTestDialog,
|
||||
onOpenInfoDialog,
|
||||
onOpenTreeDialog,
|
||||
onToggleToolbar,
|
||||
onToggleLabel,
|
||||
onPauseRendering,
|
||||
onResumeRendering
|
||||
}: ControlPanelProps) {
|
||||
const [modelUrl, setModelUrl] = useState('');
|
||||
const [isToolbarVisible, setIsToolbarVisible] = useState(true);
|
||||
const [isLabelVisible, setIsLabelVisible] = useState(true);
|
||||
|
||||
const handleToggleToolbar = () => {
|
||||
const newVisible = !isToolbarVisible;
|
||||
setIsToolbarVisible(newVisible);
|
||||
onToggleToolbar(newVisible);
|
||||
};
|
||||
|
||||
const handleToggleLabel = () => {
|
||||
const newVisible = !isLabelVisible;
|
||||
setIsLabelVisible(newVisible);
|
||||
onToggleLabel(newVisible);
|
||||
};
|
||||
|
||||
const handleLoadModel = () => {
|
||||
const url = modelUrl.trim();
|
||||
onLoadModel(url || undefined);
|
||||
};
|
||||
|
||||
const getStatusColor = () => {
|
||||
switch (engineStatus) {
|
||||
case '已初始化':
|
||||
return '#28a745';
|
||||
case '初始化失败':
|
||||
case '初始化错误':
|
||||
case 'SDK 未加载':
|
||||
return '#dc3545';
|
||||
default:
|
||||
return '#666';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<aside
|
||||
style={{
|
||||
width: '320px',
|
||||
background: '#fff',
|
||||
borderRight: '1px solid #e0e0e0',
|
||||
padding: '20px',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
boxShadow: '2px 0 5px rgba(0, 0, 0, 0.05)',
|
||||
zIndex: 10
|
||||
}}
|
||||
>
|
||||
<h1 style={{ fontSize: '1.4rem', color: '#333', marginBottom: '10px' }}>
|
||||
iFlow Engine Demo
|
||||
</h1>
|
||||
<p style={{ fontSize: '0.85rem', color: '#666', marginBottom: '10px' }}>
|
||||
Next.js + React
|
||||
</p>
|
||||
|
||||
<ControlGroup title="🌍 语言 (Language)">
|
||||
<ButtonGroup>
|
||||
<button className="btn-primary" onClick={() => onSetLang('zh-CN')}>中文</button>
|
||||
<button className="btn-primary" onClick={() => onSetLang('en-US')}>English</button>
|
||||
</ButtonGroup>
|
||||
</ControlGroup>
|
||||
|
||||
<ControlGroup title="🪟 弹窗 (Dialog)">
|
||||
<ButtonGroup>
|
||||
<button onClick={onOpenTestDialog}>测试弹窗</button>
|
||||
<button onClick={onOpenInfoDialog}>信息弹窗</button>
|
||||
<button onClick={onOpenTreeDialog}>树组件</button>
|
||||
</ButtonGroup>
|
||||
</ControlGroup>
|
||||
|
||||
<ControlGroup title="🛠️ 工具栏 (Toolbar)">
|
||||
<ButtonGroup>
|
||||
<button onClick={handleToggleToolbar}>
|
||||
{isToolbarVisible ? '隐藏工具栏' : '显示工具栏'}
|
||||
</button>
|
||||
<button onClick={handleToggleLabel}>
|
||||
{isLabelVisible ? '隐藏标签' : '显示标签'}
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
</ControlGroup>
|
||||
|
||||
<ControlGroup title="🎨 样式 (Theme)">
|
||||
<ButtonGroup>
|
||||
<button onClick={() => onSetTheme('dark')}>深色</button>
|
||||
<button onClick={() => onSetTheme('light')}>浅色</button>
|
||||
<button onClick={onSetCustomTheme}>自定义红</button>
|
||||
</ButtonGroup>
|
||||
</ControlGroup>
|
||||
|
||||
<ControlGroup title="🎮 3D 引擎 (Engine)">
|
||||
<ButtonGroup>
|
||||
<button className="btn-primary" onClick={onInitEngine}>初始化引擎</button>
|
||||
<button className="btn-primary" onClick={handleLoadModel}>加载模型</button>
|
||||
</ButtonGroup>
|
||||
<input
|
||||
type="text"
|
||||
value={modelUrl}
|
||||
onChange={(e) => setModelUrl(e.target.value)}
|
||||
placeholder="输入模型 URL(可选)"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '6px 8px',
|
||||
fontSize: '0.85rem',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
fontFamily: 'Consolas, Monaco, monospace',
|
||||
outline: 'none',
|
||||
marginTop: '8px'
|
||||
}}
|
||||
/>
|
||||
<ButtonGroup style={{ marginTop: '8px' }}>
|
||||
<button onClick={onPauseRendering}>暂停渲染</button>
|
||||
<button onClick={onResumeRendering}>恢复渲染</button>
|
||||
</ButtonGroup>
|
||||
<div style={{ marginTop: '10px', fontSize: '0.85rem', color: '#666' }}>
|
||||
状态: <span style={{ color: getStatusColor() }}>{engineStatus}</span>
|
||||
</div>
|
||||
</ControlGroup>
|
||||
|
||||
<style jsx>{`
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid #ddd;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
button:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #ccc;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #0078d4;
|
||||
color: white;
|
||||
border-color: #0063b1;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #0063b1;
|
||||
}
|
||||
`}</style>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function ControlGroup({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: '#f9f9f9',
|
||||
padding: '15px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #eee'
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
style={{
|
||||
fontSize: '1rem',
|
||||
color: '#555',
|
||||
marginBottom: '10px',
|
||||
paddingBottom: '5px',
|
||||
borderBottom: '2px solid #e0e0e0'
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ButtonGroup({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px',
|
||||
...style
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user