feat: 完善用户界面,添加绑定与解除绑定的功能

This commit is contained in:
2025-07-12 17:46:23 +08:00
parent 0fb0c4e395
commit f253a33c83
11 changed files with 1654 additions and 185 deletions

View File

@@ -84,6 +84,13 @@
@add-template="handleAddTemplate"
@close="showComponentsMenu = false"
/>
<!-- 实验板申请对话框 -->
<RequestBoardDialog
:open="showRequestBoardDialog"
@close="handleRequestBoardClose"
@success="handleRequestBoardSuccess"
/>
</div>
</template>
@@ -96,10 +103,13 @@ import ComponentSelector from "@/components/LabCanvas/ComponentSelector.vue";
import PropertyPanel from "@/components/PropertyPanel.vue";
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
import BottomBar from "@/views/Project/BottomBar.vue";
import RequestBoardDialog from "@/views/Project/RequestBoardDialog.vue";
import { useProvideComponentManager } from "@/components/LabCanvas";
import type { DiagramData } from "@/components/LabCanvas";
import { useAlertStore } from "@/components/Alert";
import { AuthManager } from "@/utils/AuthManager";
import { useEquipments } from "@/stores/equipments";
import type { Board } from "@/APIClient";
import { useRoute } from "vue-router";
const route = useRoute();
@@ -108,8 +118,14 @@ const router = useRouter();
// 提供组件管理服务
const componentManager = useProvideComponentManager();
// 设备管理store
const equipments = useEquipments();
const alert = useAlertStore();
// --- 实验板申请对话框 ---
const showRequestBoardDialog = ref(false);
// --- 文档面板控制 ---
const showDocPanel = ref(false);
const documentContent = ref("");
@@ -208,6 +224,62 @@ function updateComponentDirectProp(
componentManager.updateComponentDirectProp(componentId, propName, value);
}
// --- 实验板管理 ---
// 检查并初始化用户实验板
async function checkAndInitializeBoard() {
try {
const client = AuthManager.createAuthenticatedDataClient();
const userInfo = await client.getUserInfo();
if (userInfo.boardID && userInfo.boardID.trim() !== '') {
// 用户已绑定实验板获取实验板信息并更新到equipment
try {
const board = await client.getBoardByID(userInfo.boardID);
updateEquipmentFromBoard(board);
alert?.show(`实验板 ${board.boardName} 已连接`, "success");
} catch (boardError) {
console.error('获取实验板信息失败:', boardError);
alert?.show("获取实验板信息失败", "error");
showRequestBoardDialog.value = true;
}
} else {
// 用户未绑定实验板,显示申请对话框
showRequestBoardDialog.value = true;
}
} catch (error) {
console.error('检查用户实验板失败:', error);
alert?.show("检查用户信息失败", "error");
showRequestBoardDialog.value = true;
}
}
// 根据实验板信息更新equipment store
function updateEquipmentFromBoard(board: Board) {
equipments.setAddr(board.ipAddr);
equipments.setPort(board.port);
console.log(`实验板信息已更新到equipment store:`, {
address: board.ipAddr,
port: board.port,
boardName: board.boardName,
boardId: board.id
});
}
// 处理申请实验板对话框关闭
function handleRequestBoardClose() {
showRequestBoardDialog.value = false;
// 如果用户取消申请,可以选择返回上一页或显示警告
router.push('/');
}
// 处理申请实验板成功
function handleRequestBoardSuccess(board: Board) {
showRequestBoardDialog.value = false;
updateEquipmentFromBoard(board);
alert?.show(`实验板 ${board.boardName} 申请成功!`, "success");
}
// --- 生命周期钩子 ---
onMounted(async () => {
// 验证用户身份
@@ -224,6 +296,9 @@ onMounted(async () => {
return;
}
// 检查并初始化用户实验板
await checkAndInitializeBoard();
// 检查是否有例程参数,如果有则自动打开文档面板
if (route.query.tutorial) {
showDocPanel.value = true;

View File

@@ -0,0 +1,179 @@
<template>
<div
v-if="open"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
>
<div class="bg-base-100 rounded-lg shadow-xl max-w-md w-full mx-4">
<div class="p-6">
<h2 class="text-xl font-bold mb-4">申请实验板</h2>
<div v-if="!loading && !hasBoard" class="space-y-4">
<p class="text-base-content">
检测到您尚未绑定实验板请申请一个可用的实验板以继续实验
</p>
<div class="flex justify-end space-x-2">
<button
@click="$emit('close')"
class="btn btn-ghost"
:disabled="requesting"
>
取消
</button>
<button
@click="requestBoard"
class="btn btn-primary"
:disabled="requesting"
>
<span
v-if="requesting"
class="loading loading-spinner loading-sm"
></span>
{{ requesting ? "申请中..." : "申请实验板" }}
</button>
</div>
</div>
<div v-else-if="loading" class="text-center py-8">
<span class="loading loading-spinner loading-lg"></span>
<p class="mt-2">检查实验板状态中...</p>
</div>
<div v-else-if="hasBoard" class="space-y-4">
<div class="alert alert-success">
<CheckCircle class="shrink-0 h-6 w-6" />
<span>实验板绑定成功</span>
</div>
<div class="bg-base-200 p-4 rounded">
<h3 class="font-semibold mb-2">实验板信息</h3>
<div class="space-y-1 text-sm">
<p>
<span class="font-medium">名称:</span>
{{ boardInfo?.boardName }}
</p>
<p><span class="font-medium">ID:</span> {{ boardInfo?.id }}</p>
<p>
<span class="font-medium">地址:</span>
{{ boardInfo?.ipAddr }}:{{ boardInfo?.port }}
</p>
<p>
<span class="font-medium">状态:</span>
<span class="badge badge-success">{{
boardInfo?.status === 1 ? "可用" : "忙碌"
}}</span>
</p>
</div>
</div>
<div class="flex justify-end">
<button
v-if="boardInfo"
@click="$emit('success', boardInfo)"
class="btn btn-primary"
>
开始实验
</button>
</div>
</div>
<div v-if="error" class="alert alert-error mt-4">
<XCircle class="shrink-0 h-6 w-6" />
<span>{{ error }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { CheckCircle, XCircle } from "lucide-vue-next";
import { AuthManager } from "@/utils/AuthManager";
import type { Board } from "@/APIClient";
interface Props {
open: boolean;
}
interface Emits {
(e: "close"): void;
(e: "success", board: Board): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const loading = ref(false);
const requesting = ref(false);
const hasBoard = ref(false);
const boardInfo = ref<Board | null>(null);
const error = ref<string>("");
// 监听对话框打开状态,自动检查用户信息
watch(
() => props.open,
async (newOpen) => {
if (newOpen) {
await checkUserBoard();
}
},
);
// 检查用户是否已绑定实验板
async function checkUserBoard() {
loading.value = true;
error.value = "";
hasBoard.value = false;
boardInfo.value = null;
try {
const client = AuthManager.createAuthenticatedDataClient();
const userInfo = await client.getUserInfo();
if (userInfo.boardID && userInfo.boardID.trim() !== "") {
// 用户已绑定实验板,获取实验板信息
try {
const board = await client.getBoardByID(userInfo.boardID);
boardInfo.value = board;
hasBoard.value = true;
} catch (boardError) {
console.error("获取实验板信息失败:", boardError);
error.value = "获取实验板信息失败,请重试";
}
}
} catch (err) {
console.error("检查用户信息失败:", err);
error.value = "检查用户信息失败,请重试";
} finally {
loading.value = false;
}
}
// 申请实验板
async function requestBoard() {
requesting.value = true;
error.value = "";
try {
const client = AuthManager.createAuthenticatedDataClient();
const board = await client.getAvailableBoard();
if (board) {
boardInfo.value = board;
hasBoard.value = true;
} else {
error.value = "当前没有可用的实验板,请稍后重试";
}
} catch (err: any) {
console.error("申请实验板失败:", err);
if (err.status === 404) {
error.value = "当前没有可用的实验板,请稍后重试";
} else {
error.value = "申请实验板失败,请重试";
}
} finally {
requesting.value = false;
}
}
</script>

View File

@@ -14,10 +14,9 @@
</li>
</ul>
<div class="divider divider-horizontal h-full"></div>
<div class="card bg-base-200 w-300 rounded-2xl p-7">
<div class="card bg-base-300 w-300 rounded-2xl p-7">
<div v-if="activePage === 1">
<h2 class="card-title">用户信息</h2>
<p>这里是用户信息页面的内容</p>
<UserInfo />
</div>
<div v-else-if="activePage === 100">
<BoardTable />
@@ -31,6 +30,7 @@ import BoardTable from "./BoardTable.vue";
import { toNumber } from "lodash";
import { onMounted, ref } from "vue";
import { AuthManager } from "@/utils/AuthManager";
import UserInfo from "./UserInfo.vue";
const activePage = ref(1);
const isAdmin = ref(false);
@@ -40,9 +40,9 @@ function setActivePage(event: Event) {
activePage.value = toNumber(target.id);
}
onMounted(async ()=>{
onMounted(async () => {
isAdmin.value = await AuthManager.verifyAdminAuth();
})
});
</script>
<style scoped>

590
src/views/User/UserInfo.vue Normal file
View File

@@ -0,0 +1,590 @@
<template>
<div class="space-y-6">
<!-- 页面标题 -->
<div class="flex items-center gap-3">
<User class="w-8 h-8 text-primary" />
<h1 class="text-3xl font-bold">用户信息</h1>
<!-- 刷新按钮图标 -->
<button
@click="refreshAllInfo"
class="btn btn-ghost btn-sm ml-auto"
:disabled="loading"
title="刷新信息"
>
<RefreshCw class="w-5 h-5" :class="{ 'animate-spin': loading }" />
</button>
</div>
<!-- 全局加载状态 -->
<div v-if="loading" class="flex justify-center items-center py-12">
<span class="loading loading-spinner loading-lg text-primary m-2"> </span>
加载中...
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="alert alert-error">
<AlertCircle class="w-5 h-5" />
<span>{{ error }}</span>
<button @click="refreshAllInfo" class="btn btn-sm btn-outline">
<RefreshCw class="w-4 h-4" />
重试
</button>
</div>
<!-- 用户信息内容 -->
<div v-else class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 用户基本信息卡片 -->
<div class="card bg-base-200 shadow-lg">
<div class="card-body">
<div class="flex items-center gap-3 mb-4">
<UserCircle class="w-6 h-6 text-primary" />
<h2 class="card-title">基本信息</h2>
</div>
<div class="space-y-4">
<!-- 用户ID -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<IdCard class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">用户ID</div>
<div class="font-mono text-sm">{{ userInfo?.id || "N/A" }}</div>
</div>
<button
@click="copyToClipboard(userInfo?.id)"
class="btn btn-ghost btn-sm"
title="复制ID"
>
<Copy class="w-4 h-4" />
</button>
</div>
<!-- 用户名 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<User class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">用户名</div>
<div class="font-semibold">{{ userInfo?.name || "N/A" }}</div>
</div>
</div>
<!-- 邮箱 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<Mail class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">邮箱地址</div>
<div class="font-mono text-sm">
{{ userInfo?.eMail || "N/A" }}
</div>
</div>
<button
@click="copyToClipboard(userInfo?.eMail)"
class="btn btn-ghost btn-sm"
title="复制邮箱"
>
<Copy class="w-4 h-4" />
</button>
</div>
<!-- 账户状态 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<Shield class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">账户状态</div>
<div class="badge badge-success">已认证</div>
</div>
</div>
<!-- 绑定过期时间 -->
<div
v-if="userInfo?.boardExpireTime"
class="flex items-center gap-3 p-3 bg-base-100 rounded-lg"
>
<Clock class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">绑定过期时间</div>
<div class="font-mono text-sm">
{{ formatExpireTime(userInfo.boardExpireTime) }}
</div>
<div
class="text-xs mt-1"
:class="getExpireTimeStatusClass(userInfo.boardExpireTime)"
>
{{ getExpireTimeStatus(userInfo.boardExpireTime) }}
</div>
</div>
<div
class="badge badge-sm"
:class="getExpireTimeBadgeClass(userInfo.boardExpireTime)"
>
{{ getTimeRemaining(userInfo.boardExpireTime) }}
</div>
</div>
</div>
</div>
</div>
<!-- 实验板信息卡片 -->
<div class="card bg-base-200 shadow-lg">
<div class="card-body">
<div class="flex items-center justify-between gap-3 mb-4">
<div class="flex items-center gap-3">
<Cpu class="w-6 h-6 text-primary" />
<h2 class="card-title">绑定实验板</h2>
</div>
<!-- 操作按钮 - 只有在有绑定实验板时才显示 -->
<div v-if="boardInfo" class="flex items-center gap-3">
<button
@click="testBoardConnection"
class="btn btn-secondary btn-sm"
:disabled="testingConnection"
>
<Zap
class="w-4 h-4"
:class="{ 'animate-pulse': testingConnection }"
/>
{{ testingConnection ? "测试中..." : "测试连接" }}
</button>
<button
@click="unbindBoard"
class="btn btn-error btn-outline btn-sm"
:disabled="unbindingBoard"
>
<Unlink2
class="w-4 h-4"
:class="{ 'animate-pulse': unbindingBoard }"
/>
{{ unbindingBoard ? "解绑中..." : "解绑实验板" }}
</button>
</div>
</div>
<!-- 无实验板绑定 -->
<div v-if="!boardInfo" class="text-center py-8">
<Unlink class="w-12 h-12 text-base-content/50 mx-auto mb-4" />
<div class="text-base-content/70 mb-4">暂无绑定的实验板</div>
<!-- 申请实验板按钮 -->
<button
@click="applyBoard"
class="btn btn-primary"
:disabled="applyingBoard"
>
<Plus
class="w-4 h-4"
:class="{ 'animate-pulse': applyingBoard }"
/>
{{ applyingBoard ? "申请中..." : "申请实验板" }}
</button>
</div>
<!-- 实验板信息 -->
<div v-else class="space-y-4">
<!-- 实验板ID -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<IdCard class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">实验板ID</div>
<div class="font-mono text-sm">
{{ boardInfo?.id || "N/A" }}
</div>
</div>
<button
@click="copyToClipboard(boardInfo?.id)"
class="btn btn-ghost btn-sm"
title="复制ID"
>
<Copy class="w-4 h-4" />
</button>
</div>
<!-- 实验板名称 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<Tag class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">实验板名称</div>
<div class="font-semibold">
{{ boardInfo?.boardName || "N/A" }}
</div>
</div>
</div>
<!-- IP地址 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<Globe class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">IP地址</div>
<div class="font-mono text-sm">
{{ boardInfo?.ipAddr || "N/A" }}
</div>
</div>
<button
@click="copyToClipboard(boardInfo?.ipAddr)"
class="btn btn-ghost btn-sm"
title="复制IP地址"
>
<Copy class="w-4 h-4" />
</button>
</div>
<!-- 端口 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<Server class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">端口</div>
<div class="font-mono text-sm">
{{ boardInfo?.port || "N/A" }}
</div>
</div>
</div>
<!-- 状态 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<Activity class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">状态</div>
<div
class="badge"
:class="getBoardStatusClass(boardInfo?.status)"
>
{{ getBoardStatusText(boardInfo?.status) }}
</div>
</div>
</div>
<!-- 固件版本 -->
<div class="flex items-center gap-3 p-3 bg-base-100 rounded-lg">
<Settings class="w-5 h-5 text-secondary" />
<div class="flex-1">
<div class="text-sm text-base-content/70">固件版本</div>
<div class="font-mono text-sm">
{{ boardInfo?.firmVersion || "N/A" }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 使用自定义 Alert 组件 -->
<Alert />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { AuthManager } from "@/utils/AuthManager";
import { UserInfo, Board, BoardStatus } from "@/APIClient";
import { Alert, useAlertStore } from "@/components/Alert";
import {
User,
UserCircle,
Mail,
IdCard,
Copy,
Shield,
Cpu,
Globe,
Server,
Activity,
Settings,
Tag,
RefreshCw,
AlertCircle,
Unlink,
Unlink2,
Zap,
Plus,
Clock,
} from "lucide-vue-next";
// 响应式数据
const loading = ref(false);
const error = ref("");
const userInfo = ref<UserInfo | null>(null);
const boardInfo = ref<Board | null>(null);
// 操作状态
const testingConnection = ref(false);
const unbindingBoard = ref(false);
const applyingBoard = ref(false);
// 使用自定义 Alert
const alertStore = useAlertStore();
// 加载实验板信息
const loadBoardInfo = async () => {
if (!userInfo.value?.boardID) {
boardInfo.value = null;
return;
}
try {
const client = AuthManager.createAuthenticatedDataClient();
boardInfo.value = await client.getBoardByID(userInfo.value.boardID);
} catch (err) {
console.error("加载实验板信息失败:", err);
boardInfo.value = null;
}
};
// 统一的信息加载函数(合并了原来的 loadUserInfo 和 refreshAllInfo
const loadUserInfo = async (showSuccessMessage = false) => {
loading.value = true;
error.value = "";
try {
await new Promise((resolve) => setTimeout(resolve, 200));
const client = AuthManager.createAuthenticatedDataClient();
userInfo.value = await client.getUserInfo();
// 如果有绑定的实验板ID加载实验板信息
if (userInfo.value?.boardID) {
await loadBoardInfo();
} else {
boardInfo.value = null;
}
if (showSuccessMessage) {
alertStore?.success("信息刷新成功");
}
} catch (err) {
console.error("加载用户信息失败:", err);
error.value = "加载用户信息失败,请检查网络连接或重新登录";
if (showSuccessMessage) {
alertStore?.error("刷新信息失败,请检查网络连接");
}
} finally {
loading.value = false;
}
};
// 刷新所有信息(调用统一的加载函数,显示成功消息)
const refreshAllInfo = async () => {
await loadUserInfo(true);
};
// 申请实验板
const applyBoard = async () => {
applyingBoard.value = true;
alertStore?.info("正在申请实验板...");
try {
const client = AuthManager.createAuthenticatedDataClient();
// 获取可用的实验板
const availableBoard = await client.getAvailableBoard(undefined);
if (availableBoard) {
alertStore?.success(`成功申请到实验板: ${availableBoard.boardName}`);
// 重新加载用户信息以获取最新的绑定状态
await loadUserInfo();
} else {
alertStore?.warn("当前没有可用的实验板,请稍后再试");
}
} catch (err: any) {
console.error("申请实验板失败:", err);
// 根据错误状态码提供更友好的错误信息
if (err?.status === 404) {
alertStore?.warn("当前没有可用的实验板,请稍后再试");
} else if (err?.status === 400) {
alertStore?.error("您已经绑定了实验板,无需重复申请");
} else {
alertStore?.error("申请实验板失败,请检查网络连接或稍后重试");
}
} finally {
applyingBoard.value = false;
}
};
// 测试实验板连接
const testBoardConnection = async () => {
if (!boardInfo.value) return;
testingConnection.value = true;
alertStore?.info("正在测试连接...");
try {
const jtagClient = AuthManager.createAuthenticatedJtagClient();
// 使用JTAG客户端读取设备ID Code
const idCode = await jtagClient.getDeviceIDCode(
boardInfo.value.ipAddr,
boardInfo.value.port,
);
// 检查ID Code是否有效非0xFFFFFFFF表示连接成功
if (idCode !== 0xffffffff && idCode !== 0) {
alertStore?.success(
`连接测试成功设备ID: 0x${idCode.toString(16).toUpperCase()}`,
);
} else {
alertStore?.warn("连接测试失败,未检测到有效设备");
}
} catch (err) {
console.error("连接测试失败:", err);
alertStore?.error("连接测试失败,请检查实验板是否在线");
} finally {
testingConnection.value = false;
}
};
// 解绑实验板
const unbindBoard = async () => {
if (!boardInfo.value) return;
// 确认对话框
if (!confirm("确定要解绑当前实验板吗?解绑后需要重新绑定才能使用。")) {
return;
}
unbindingBoard.value = true;
alertStore?.info("正在解绑实验板...");
try {
const client = AuthManager.createAuthenticatedDataClient();
const success = await client.unbindBoard();
if (success) {
alertStore?.success("实验板解绑成功");
// 清空实验板信息并重新加载用户信息
boardInfo.value = null;
await loadUserInfo();
} else {
alertStore?.error("实验板解绑失败");
}
} catch (err) {
console.error("解绑实验板失败:", err);
alertStore?.error("解绑实验板失败,请稍后重试");
} finally {
unbindingBoard.value = false;
}
};
// 复制到剪贴板
const copyToClipboard = async (text?: string) => {
if (!text) return;
try {
await navigator.clipboard.writeText(text);
alertStore?.success("已复制到剪贴板");
} catch (err) {
alertStore?.error("复制失败");
}
};
// 时间相关的工具函数
const formatExpireTime = (expireTime: Date) => {
return new Date(expireTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
};
const getTimeRemaining = (expireTime: Date) => {
const now = new Date();
const expire = new Date(expireTime);
const timeDiff = expire.getTime() - now.getTime();
if (timeDiff <= 0) {
return "已过期";
}
const hours = Math.floor(timeDiff / (1000 * 60 * 60));
const minutes = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `剩余 ${hours}小时${minutes}分钟`;
} else {
return `剩余 ${minutes}分钟`;
}
};
// 获取过期时间相关状态的统一函数
const getExpireTimeInfo = (expireTime: Date) => {
const now = new Date();
const expire = new Date(expireTime);
const timeDiff = expire.getTime() - now.getTime();
if (timeDiff <= 0) {
return {
status: "已过期",
statusClass: "text-error",
badgeClass: "badge-error",
};
} else if (timeDiff <= 30 * 60 * 1000) {
return {
status: "即将过期",
statusClass: "text-warning",
badgeClass: "badge-warning",
};
} else if (timeDiff <= 60 * 60 * 1000) {
return {
status: "临近过期",
statusClass: "text-warning",
badgeClass: "badge-warning",
};
} else {
return {
status: "正常",
statusClass: "text-success",
badgeClass: "badge-success",
};
}
};
// 使用统一函数的便捷方法
const getExpireTimeStatus = (expireTime: Date) =>
getExpireTimeInfo(expireTime).status;
const getExpireTimeStatusClass = (expireTime: Date) =>
getExpireTimeInfo(expireTime).statusClass;
const getExpireTimeBadgeClass = (expireTime: Date) =>
getExpireTimeInfo(expireTime).badgeClass;
// 获取实验板状态相关信息的统一函数
const getBoardStatusInfo = (status?: BoardStatus) => {
switch (status) {
case BoardStatus.Available:
return { text: "可用", class: "badge-success" };
case BoardStatus.Busy:
return { text: "使用中", class: "badge-warning" };
default:
return { text: "未知", class: "badge-neutral" };
}
};
// 使用统一函数的便捷方法
const getBoardStatusClass = (status?: BoardStatus) =>
getBoardStatusInfo(status).class;
const getBoardStatusText = (status?: BoardStatus) =>
getBoardStatusInfo(status).text;
// 组件挂载时加载数据
onMounted(() => {
loadUserInfo();
});
</script>
<style scoped>
/* 添加一些自定义样式优化 */
.card {
transition: all 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
}
/* 响应式优化 */
@media (max-width: 768px) {
.grid-cols-1.lg\:grid-cols-2 {
grid-template-columns: 1fr;
}
}
</style>