init
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vue.js Inline Rendering Canvas Demo</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@latest/dist/browser/signalr.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.demo-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
position: relative;
|
||||
height: 600px;
|
||||
border: 1px solid #ddd;
|
||||
margin: 20px 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.rendering-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.rendering-canvas:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.view-button {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #2196F3;
|
||||
background: white;
|
||||
color: #2196F3;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.view-button:hover {
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.status.connected {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.status.disconnected {
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="demo-container">
|
||||
<h1>Vue.js 內嵌式渲染畫布示範</h1>
|
||||
|
||||
<div class="status" :class="isConnected ? 'connected' : 'disconnected'">
|
||||
{{ isConnected ? '已連接' : '未連接' }}
|
||||
<span v-if="sessionId">- Session: {{ sessionId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="view-button" @click="setView('front')">前視圖</button>
|
||||
<button class="view-button" @click="setView('back')">後視圖</button>
|
||||
<button class="view-button" @click="setView('left')">左視圖</button>
|
||||
<button class="view-button" @click="setView('right')">右視圖</button>
|
||||
<button class="view-button" @click="setView('top')">頂視圖</button>
|
||||
<button class="view-button" @click="setView('bottom')">底視圖</button>
|
||||
<button class="view-button" @click="setView('isometric')">等角視圖</button>
|
||||
<button class="view-button" @click="setView('home')">主視圖</button>
|
||||
</div>
|
||||
|
||||
<div class="canvas-wrapper">
|
||||
<canvas ref="canvas" class="rendering-canvas"></canvas>
|
||||
</div>
|
||||
|
||||
<p>提示:使用滑鼠拖曳旋轉視圖,滾輪縮放,鍵盤 F1-F4 切換視圖</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
sessionId: null,
|
||||
isConnected: false,
|
||||
canvas: null,
|
||||
ctx: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.canvas = this.$refs.canvas;
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.connect();
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.connection) {
|
||||
this.connection.stop();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async connect() {
|
||||
try {
|
||||
this.connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl("/renderingHub")
|
||||
.configureLogging(signalR.LogLevel.Information)
|
||||
.build();
|
||||
|
||||
// 設置消息處理器
|
||||
this.connection.on("ImageUpdate", (compressedData, originalLength, width, height) => {
|
||||
this.handleImageUpdate(compressedData, originalLength, width, height);
|
||||
});
|
||||
|
||||
this.connection.on("CanvasInitialized", (id) => {
|
||||
this.sessionId = id;
|
||||
console.log("畫布初始化完成,會話ID:", id);
|
||||
this.loadTestObjects();
|
||||
});
|
||||
|
||||
// 連接到服務器
|
||||
await this.connection.start();
|
||||
this.isConnected = true;
|
||||
console.log("已連接到服務器");
|
||||
|
||||
// 初始化畫布
|
||||
await this.initializeCanvas();
|
||||
|
||||
} catch (err) {
|
||||
console.error("連接失敗:", err);
|
||||
this.isConnected = false;
|
||||
setTimeout(() => this.connect(), 5000);
|
||||
}
|
||||
},
|
||||
|
||||
async initializeCanvas() {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const width = Math.floor(rect.width);
|
||||
const height = Math.floor(rect.height);
|
||||
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
|
||||
await this.connection.invoke("InitializeCanvas", width, height);
|
||||
|
||||
// 設置事件處理
|
||||
this.setupEvents();
|
||||
},
|
||||
|
||||
setupEvents() {
|
||||
// 滑鼠事件
|
||||
let isMouseDown = false;
|
||||
let mouseButton = 0;
|
||||
|
||||
this.canvas.addEventListener('mousedown', async (e) => {
|
||||
isMouseDown = true;
|
||||
mouseButton = e.button;
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
await this.connection.invoke("HandleMouseDown", x, y, e.button);
|
||||
});
|
||||
|
||||
this.canvas.addEventListener('mousemove', async (e) => {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
const buttonMask = isMouseDown ? (1 << mouseButton) : 0;
|
||||
await this.connection.invoke("HandleMouseMove", x, y, buttonMask);
|
||||
});
|
||||
|
||||
this.canvas.addEventListener('mouseup', async (e) => {
|
||||
isMouseDown = false;
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
await this.connection.invoke("HandleMouseUp", x, y, e.button);
|
||||
});
|
||||
|
||||
this.canvas.addEventListener('wheel', async (e) => {
|
||||
e.preventDefault();
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
await this.connection.invoke("HandleMouseWheel", x, y, e.deltaX, e.deltaY, "chrome");
|
||||
});
|
||||
|
||||
this.canvas.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// 鍵盤事件
|
||||
this.canvas.tabIndex = 0;
|
||||
this.canvas.addEventListener('keydown', async (e) => {
|
||||
e.preventDefault();
|
||||
await this.connection.invoke("HandleKeyDown", e.key, e.code, e.ctrlKey, e.shiftKey, e.altKey);
|
||||
});
|
||||
},
|
||||
|
||||
async handleImageUpdate(compressedData, originalLength, width, height) {
|
||||
try {
|
||||
// 解壓縮數據
|
||||
const imageData = await this.decompressData(compressedData, originalLength);
|
||||
|
||||
// 創建 ImageData 對象並繪製到畫布
|
||||
const imgData = new ImageData(new Uint8ClampedArray(imageData), width, height);
|
||||
this.ctx.putImageData(imgData, 0, 0);
|
||||
|
||||
} catch (err) {
|
||||
console.error("處理圖像更新時出錯:", err);
|
||||
}
|
||||
},
|
||||
|
||||
async decompressData(compressedData, originalLength) {
|
||||
let bytes;
|
||||
|
||||
if (typeof compressedData === 'string') {
|
||||
// Base64 字符串轉換
|
||||
const binaryString = atob(compressedData);
|
||||
bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
} else {
|
||||
bytes = new Uint8Array(compressedData);
|
||||
}
|
||||
|
||||
// 使用 DecompressionStream 解壓縮
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(bytes);
|
||||
controller.close();
|
||||
}
|
||||
});
|
||||
|
||||
const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip'));
|
||||
const reader = decompressedStream.getReader();
|
||||
const chunks = [];
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
// 合併所有塊
|
||||
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async setView(viewType) {
|
||||
if (this.connection && this.connection.state === 'Connected') {
|
||||
try {
|
||||
await this.connection.invoke('SetView', viewType);
|
||||
console.log(`視圖已切換到: ${viewType}`);
|
||||
} catch (err) {
|
||||
console.error(`切換視圖失敗: ${err}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async loadTestObjects() {
|
||||
try {
|
||||
const response = await fetch(`/api/rendering/test-objects/${this.sessionId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log('測試對象載入成功');
|
||||
} else {
|
||||
console.error('載入測試對象失敗');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('載入測試對象時出錯:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user