项目初始化

This commit is contained in:
leilei
2025-09-19 17:24:46 +08:00
commit 293951a610
107 changed files with 10222 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
<template>
<div class="wrapper-content">
<div v-if="showLogin">
<!-- 登录界面 -->
<Login @loginSuccess="handleLoginSuccess" />
</div>
<div v-else>
<!-- 未加入时显示按钮 -->
<div v-if="!hasJoined" class="login-button-container">
<el-button type="primary" size="large" round plain @click="joinWhiteboard">
加入互动画板
</el-button>
</div>
<!-- 已加入时显示白板 -->
<div v-else class="whiteboard-wrapper">
<ToolBox v-if="canvas" class="toolbox" :canvas="canvas" />
<canvas id="whiteboard" class="whiteboard-canvas"></canvas>
</div>
</div>
</div>
</template>
<script setup>
import { ref, nextTick, onUnmounted, onMounted } from "vue";
import { ElLoading, ElMessage } from "element-plus";
import { v4 as uuidv4 } from "uuid";
import { useRoute } from "vue-router";
import { mqttClient } from "@/utils/mqtt";
import { WhiteboardSync } from "@/utils/whiteboardSync";
import ToolBox from "@/components/ToolBox/index.vue";
import Login from "@/components/Login/index.vue";
import Canvas from "@/core/index.js";
import { getInfo } from "@/api/login";
const showLogin = ref(false); // 是否显示登录页面
const hasJoined = ref(false); // 是否加入白板
const canvas = ref(null);
const route = useRoute();
/** 进入白板 */
async function joinWhiteboard() {
const loading = ElLoading.service({
lock: true,
text: "正在进入互动画板...",
background: "rgba(0, 0, 0, 0.4)",
});
try {
const clientId = `whiteboard-${uuidv4()}`;
await mqttClient.connect(clientId);
console.log("✅ 已连接 MQTT:", clientId);
hasJoined.value = true;
// 等待 DOM 更新后再初始化画布
await nextTick();
initWhiteboard();
ElMessage.success("已进入互动画板 🎉");
} catch (err) {
console.error("❌ 连接失败:", err);
ElMessage.error("连接白板失败,请重试");
} finally {
loading.close();
}
}
/** 登录成功回调 */
function handleLoginSuccess() {
showLogin.value = false;
}
/** 初始化白板 */
function initWhiteboard() {
const el = document.getElementById("whiteboard");
if (!el) {
console.error("⚠️ 找不到 canvas 元素");
return;
}
// 设置画布宽高保持16:9
const { width, height } = getCanvasSize(el.parentElement);
Object.assign(el, { width, height });
Object.assign(el.style, {
width: `${width}px`,
height: `${height}px`,
border: "2px solid #000",
});
// 初始化画布
canvas.value = new Canvas("whiteboard");
// 获取房间号
const roomUid = route.query.room_uid || "default-room";
// 初始化多人同步
WhiteboardSync.init(canvas.value, roomUid);
}
/** 计算画布大小保持16:9 */
function getCanvasSize(container) {
const containerWidth = container.offsetWidth;
const containerHeight = container.offsetHeight;
let width = containerWidth;
let height = Math.floor((width * 9) / 16);
if (height > containerHeight) {
height = containerHeight;
width = Math.floor((height * 16) / 9);
}
return { width, height };
}
onMounted(async () => {
try {
await getInfo("self");
showLogin.value = false;
} catch (err) {
if (err.code === 401) {
showLogin.value = true;
} else {
showLogin.value = false;
console.warn("⚠️ 用户信息校验失败:", err);
}
}
});
onUnmounted(() => {
if (canvas.value) canvas.value.destroy();
});
</script>
<style scoped>
/* 外层容器全屏居中 */
.wrapper-content {
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
background: #fff;
position: relative;
}
/* 登录按钮容器居中 */
.login-button-container {
display: flex;
justify-content: center;
align-items: center;
}
/* 白板容器 */
.whiteboard-wrapper {
position: relative;
width: 90vw;
max-width: 1280px;
aspect-ratio: 16 / 9;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
/* 画布占满白板容器 */
.whiteboard-canvas {
width: 100%;
height: 100%;
display: block;
}
/* 工具栏左侧垂直居中 */
.toolbox {
position: absolute;
top: 50%;
left: 10px;
transform: translateY(-50%);
z-index: 1000;
}
</style>