init
This commit is contained in:
+281
-198
@@ -1,16 +1,17 @@
|
||||
/**
|
||||
* STP模具几何分析中心 - Vue3单页应用
|
||||
* 优化版本:统一前端实现,增强用户体验
|
||||
* 版本: 4.0.0 - 暖白色主题 + 增强动画效果
|
||||
*/
|
||||
|
||||
const { createApp, ref, computed, onMounted, reactive } = Vue;
|
||||
const { createApp, ref, computed, onMounted, reactive, watch, nextTick } = Vue;
|
||||
const { createRouter, createWebHistory, useRoute, useRouter } = VueRouter;
|
||||
|
||||
// 全局状态管理
|
||||
const appState = reactive({
|
||||
health: null,
|
||||
loading: false,
|
||||
notifications: []
|
||||
notifications: [],
|
||||
theme: 'warm'
|
||||
});
|
||||
|
||||
// 通用工具函数
|
||||
@@ -48,7 +49,6 @@ function statusText(status) {
|
||||
return map[status] || status || "未知";
|
||||
}
|
||||
|
||||
// 状态样式映射
|
||||
function getStatusClass(status) {
|
||||
const classMap = {
|
||||
pending: "status-pending",
|
||||
@@ -59,26 +59,33 @@ function getStatusClass(status) {
|
||||
return classMap[status] || "status-pending";
|
||||
}
|
||||
|
||||
// 添加通知
|
||||
let notificationId = 0;
|
||||
|
||||
function addNotification(message, type = 'info') {
|
||||
const id = ++notificationId;
|
||||
const notification = {
|
||||
id: Date.now(),
|
||||
id,
|
||||
message,
|
||||
type,
|
||||
timestamp: new Date()
|
||||
timestamp: new Date(),
|
||||
visible: true
|
||||
};
|
||||
appState.notifications.push(notification);
|
||||
|
||||
// 自动移除通知
|
||||
setTimeout(() => {
|
||||
const index = appState.notifications.findIndex(n => n.id === notification.id);
|
||||
const index = appState.notifications.findIndex(n => n.id === id);
|
||||
if (index > -1) {
|
||||
appState.notifications.splice(index, 1);
|
||||
appState.notifications[index].visible = false;
|
||||
setTimeout(() => {
|
||||
const idx = appState.notifications.findIndex(n => n.id === id);
|
||||
if (idx > -1) {
|
||||
appState.notifications.splice(idx, 1);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
function handleApiError(error, context = '') {
|
||||
console.error(`API错误 [${context}]:`, error);
|
||||
const message = error.message || '请求失败,请稍后重试';
|
||||
@@ -110,13 +117,18 @@ const App = {
|
||||
const dismissNotification = (id) => {
|
||||
const index = appState.notifications.findIndex(n => n.id === id);
|
||||
if (index > -1) {
|
||||
appState.notifications.splice(index, 1);
|
||||
appState.notifications[index].visible = false;
|
||||
setTimeout(() => {
|
||||
const idx = appState.notifications.findIndex(n => n.id === id);
|
||||
if (idx > -1) {
|
||||
appState.notifications.splice(idx, 1);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadHealth();
|
||||
// 定时检查健康状态(每30秒)
|
||||
setInterval(loadHealth, 30000);
|
||||
});
|
||||
|
||||
@@ -134,14 +146,19 @@ const App = {
|
||||
<div class="app-shell">
|
||||
<!-- 通知区域 -->
|
||||
<div class="notification-container" v-if="appState.notifications.length > 0">
|
||||
<div
|
||||
v-for="notification in appState.notifications"
|
||||
:key="notification.id"
|
||||
:class="['notification', 'notification-' + notification.type]"
|
||||
>
|
||||
<span class="notification-message">{{ notification.message }}</span>
|
||||
<button class="notification-close" @click="dismissNotification(notification.id)">×</button>
|
||||
</div>
|
||||
<TransitionGroup name="notification">
|
||||
<div
|
||||
v-for="notification in appState.notifications"
|
||||
:key="notification.id"
|
||||
:class="['notification', 'notification-' + notification.type, { 'notification-hidden': !notification.visible }]"
|
||||
>
|
||||
<span class="notification-icon">
|
||||
{{ notification.type === 'success' ? '✓' : notification.type === 'error' ? '✕' : notification.type === 'warning' ? '⚠' : 'ℹ' }}
|
||||
</span>
|
||||
<span class="notification-message">{{ notification.message }}</span>
|
||||
<button class="notification-close" @click="dismissNotification(notification.id)">×</button>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<header class="app-header">
|
||||
@@ -154,29 +171,42 @@ const App = {
|
||||
</div>
|
||||
<nav class="app-nav">
|
||||
<router-link :class="['nav-link', { active: isActive('/').value }]" to="/">
|
||||
📊 仪表盘
|
||||
<span class="nav-icon">📊</span>
|
||||
<span>仪表盘</span>
|
||||
</router-link>
|
||||
<router-link :class="['nav-link', { active: isActive('/history').value }]" to="/history">
|
||||
📋 历史记录
|
||||
<span class="nav-icon">📋</span>
|
||||
<span>历史记录</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="app-main">
|
||||
<router-view />
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade-slide" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</main>
|
||||
|
||||
<footer class="app-footer">
|
||||
<div class="footer-left">
|
||||
<span>PythonOCC: <strong :class="appState.health?.pythonocc ? 'status-success' : 'status-error'">
|
||||
{{ appState.health?.pythonocc ? '可用' : '未知' }}
|
||||
</strong></span>
|
||||
<span v-if="appState.health"> | 当前任务数: {{ appState.health.total_tasks }}</span>
|
||||
<span class="footer-item">
|
||||
<span class="footer-label">PythonOCC:</span>
|
||||
<strong :class="appState.health?.pythonocc ? 'status-success' : 'status-error'">
|
||||
{{ appState.health?.pythonocc ? '可用' : '未知' }}
|
||||
</strong>
|
||||
</span>
|
||||
<span v-if="appState.health" class="footer-divider">|</span>
|
||||
<span v-if="appState.health" class="footer-item">
|
||||
<span class="footer-label">当前任务数:</span>
|
||||
<strong>{{ appState.health.total_tasks }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span>版本: 3.0.0</span>
|
||||
<span> | </span>
|
||||
<span>模具几何分析系统</span>
|
||||
<span class="footer-item">版本: <strong>4.0.0</strong></span>
|
||||
<span class="footer-divider">|</span>
|
||||
<span class="footer-item">模具几何分析系统</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -195,7 +225,8 @@ const DashboardView = {
|
||||
currentTask: null,
|
||||
polling: false,
|
||||
historySummary: null,
|
||||
dragOver: false
|
||||
dragOver: false,
|
||||
progress: 0
|
||||
});
|
||||
|
||||
const totalFiles = computed(() => state.historySummary?.total_files || 0);
|
||||
@@ -256,6 +287,7 @@ const DashboardView = {
|
||||
if (!state.selectedFile) return;
|
||||
state.uploading = true;
|
||||
state.error = "";
|
||||
state.progress = 0;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", state.selectedFile);
|
||||
@@ -287,16 +319,18 @@ const DashboardView = {
|
||||
|
||||
const startPolling = async (taskId) => {
|
||||
state.polling = true;
|
||||
state.progress = 10;
|
||||
let pollCount = 0;
|
||||
const maxPolls = 300; // 最多轮询5分钟(300秒)
|
||||
const maxPolls = 300;
|
||||
|
||||
const poll = async () => {
|
||||
try {
|
||||
pollCount++;
|
||||
state.progress = Math.min(90, 10 + pollCount * 0.5);
|
||||
|
||||
const res = await fetch(`/status/${taskId}`, { method: "POST" });
|
||||
|
||||
if (res.status === 404) {
|
||||
// 任务不存在,停止轮询
|
||||
state.polling = false;
|
||||
state.error = "任务不存在或已被删除";
|
||||
addNotification("任务不存在或已被删除", 'error');
|
||||
@@ -312,18 +346,20 @@ const DashboardView = {
|
||||
|
||||
if (task.status === "completed") {
|
||||
state.polling = false;
|
||||
state.progress = 100;
|
||||
addNotification(`分析完成,正在跳转到结果页面...`, 'success');
|
||||
setTimeout(() => {
|
||||
router.push(`/result/${task.task_id}`);
|
||||
}, 1000);
|
||||
} else if (task.status === "failed") {
|
||||
state.polling = false;
|
||||
state.progress = 0;
|
||||
const errorMsg = task.error || "未知错误";
|
||||
state.error = `分析失败: ${errorMsg}`;
|
||||
addNotification(`分析失败: ${errorMsg}`, 'error');
|
||||
} else if (pollCount >= maxPolls) {
|
||||
// 超时停止轮询
|
||||
state.polling = false;
|
||||
state.progress = 0;
|
||||
state.error = "任务处理超时,请稍后查看结果";
|
||||
addNotification("任务处理超时,请稍后查看结果", 'warning');
|
||||
} else {
|
||||
@@ -331,6 +367,7 @@ const DashboardView = {
|
||||
}
|
||||
} catch (e) {
|
||||
state.polling = false;
|
||||
state.progress = 0;
|
||||
const errorMsg = handleApiError(e, '任务轮询');
|
||||
state.error = errorMsg;
|
||||
}
|
||||
@@ -378,14 +415,42 @@ const DashboardView = {
|
||||
},
|
||||
template: `
|
||||
<div class="dashboard">
|
||||
<section class="grid-2">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">📁</div>
|
||||
<div class="stat-value">{{ totalFiles }}</div>
|
||||
<div class="stat-label">已分析文件</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">📊</div>
|
||||
<div class="stat-value">{{ totalRecords }}</div>
|
||||
<div class="stat-label">处理记录</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">⚡</div>
|
||||
<div class="stat-value">{{ state.polling ? '分析中' : '就绪' }}</div>
|
||||
<div class="stat-label">系统状态</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">🎯</div>
|
||||
<div class="stat-value">{{ latestFile ? '1' : '0' }}</div>
|
||||
<div class="stat-label">最近文件</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<!-- 上传区域 -->
|
||||
<div class="card upload-card">
|
||||
<h2>📁 上传 STP / STEP 文件</h2>
|
||||
<h2>
|
||||
<span class="card-icon">📤</span>
|
||||
上传 STP / STEP 文件
|
||||
</h2>
|
||||
<p class="card-subtitle">上传后系统会自动解析几何、生成模具型腔并计算关键工艺参数。</p>
|
||||
|
||||
<div class="upload-panel">
|
||||
<div class="upload-section">
|
||||
<label
|
||||
class="upload-dropzone"
|
||||
class="upload-area"
|
||||
:class="{ 'drag-over': state.dragOver }"
|
||||
@dragover.prevent="handleDragOver"
|
||||
@dragleave.prevent="handleDragLeave"
|
||||
@@ -393,80 +458,95 @@ const DashboardView = {
|
||||
>
|
||||
<input type="file" accept=".stp,.step" @change="handleFileChange" hidden />
|
||||
<div class="upload-icon">📂</div>
|
||||
<div v-if="!state.selectedFile">
|
||||
<div v-if="!state.selectedFile" class="upload-content">
|
||||
<h3>点击选择或拖拽文件到此处</h3>
|
||||
<p>最大 100MB,支持 .stp / .step</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h3>已选择文件:</h3>
|
||||
<p><strong>{{ state.selectedFile.name }}</strong></p>
|
||||
<p>大小:{{ formatFileSize(state.selectedFile.size) }}</p>
|
||||
<button class="clear-file-btn" @click.stop="clearFile">清除</button>
|
||||
<div v-else class="upload-selected">
|
||||
<h3>已选择文件</h3>
|
||||
<p class="filename"><strong>{{ state.selectedFile.name }}</strong></p>
|
||||
<p class="filesize">大小:{{ formatFileSize(state.selectedFile.size) }}</p>
|
||||
<button class="btn btn-secondary clear-btn" @click.stop="clearFile">清除选择</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<button
|
||||
class="upload-btn"
|
||||
class="btn btn-primary upload-btn"
|
||||
:disabled="!state.selectedFile || state.uploading"
|
||||
@click="uploadFile"
|
||||
>
|
||||
<span v-if="!state.uploading">开始分析</span>
|
||||
<span v-else>正在上传...</span>
|
||||
<span v-if="!state.uploading">🚀 开始分析</span>
|
||||
<span v-else>⏳ 正在上传...</span>
|
||||
</button>
|
||||
|
||||
<p v-if="state.error" class="error-text">{{ state.error }}</p>
|
||||
<p v-if="state.error" class="error-message">{{ state.error }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="state.currentTask" class="card current-task">
|
||||
<h3>当前任务</h3>
|
||||
<div class="info-row">
|
||||
<span>任务 ID:</span><span>{{ state.currentTask.task_id }}</span>
|
||||
<!-- 当前进度 -->
|
||||
<div v-if="state.currentTask" class="current-task-card">
|
||||
<h4>📋 当前任务</h4>
|
||||
<div class="task-info">
|
||||
<div class="data-item">
|
||||
<span class="data-label">任务 ID</span>
|
||||
<span class="data-value">{{ state.currentTask.task_id }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">文件名</span>
|
||||
<span class="data-value">{{ state.currentTask.filename || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">文件大小</span>
|
||||
<span class="data-value">{{ state.currentTask.file_size ? formatFileSize(state.currentTask.file_size) : 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">状态</span>
|
||||
<span class="status-badge" :class="getStatusClass(state.currentTask.status)">
|
||||
{{ statusText(state.currentTask.status) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span>文件名:</span><span>{{ state.currentTask.filename || 'N/A' }}</span>
|
||||
|
||||
<div v-if="state.polling" class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: state.progress + '%' }"></div>
|
||||
</div>
|
||||
<p class="progress-text">正在后台分析,请稍候...</p>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span>文件大小:</span><span>{{ state.currentTask.file_size ? formatFileSize(state.currentTask.file_size) : 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span>状态:</span>
|
||||
<span class="status-pill" :class="getStatusClass(state.currentTask.status)">
|
||||
{{ statusText(state.currentTask.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="state.polling" class="small-hint">正在后台分析,请稍候,完成后会跳转到结果页。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目总览 -->
|
||||
<div class="card summary-card">
|
||||
<h2>📊 项目总览</h2>
|
||||
<div class="summary-grid">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">已分析文件数</div>
|
||||
<div class="summary-value">{{ totalFiles }}</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">总处理记录数</div>
|
||||
<div class="summary-value">{{ totalRecords }}</div>
|
||||
</div>
|
||||
<div class="summary-item" v-if="latestFile">
|
||||
<div class="summary-label">最近上传文件</div>
|
||||
<div class="summary-value text-left">
|
||||
<div class="filename">{{ latestFile.filename }}</div>
|
||||
<div class="meta">
|
||||
最近上传:{{ formatDateTime(latestFile.last_upload) }}<br />
|
||||
历史记录:{{ latestFile.record_count }} 条
|
||||
<h2>
|
||||
<span class="card-icon">📊</span>
|
||||
项目总览
|
||||
</h2>
|
||||
|
||||
<div v-if="latestFile" class="latest-file">
|
||||
<h4>最近上传文件</h4>
|
||||
<div class="file-preview">
|
||||
<span class="file-icon">📄</span>
|
||||
<div class="file-details">
|
||||
<div class="file-name">{{ latestFile.filename }}</div>
|
||||
<div class="file-meta">
|
||||
<span>📅 {{ formatDateTime(latestFile.last_upload) }}</span>
|
||||
<span>📊 {{ latestFile.record_count }} 条记录</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="summary-empty">
|
||||
还没有历史记录,先上传一个 STP 文件试试吧。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-state">
|
||||
<div class="empty-state-icon">📁</div>
|
||||
<h3>暂无历史记录</h3>
|
||||
<p>上传一个 STP 文件开始分析吧</p>
|
||||
</div>
|
||||
|
||||
<router-link class="link-btn" to="/history">查看详细历史记录 →</router-link>
|
||||
<router-link class="btn btn-secondary link-btn" to="/history">
|
||||
查看详细历史记录 →
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -497,7 +577,6 @@ const HistoryView = {
|
||||
|
||||
const toggleExpand = async (filename) => {
|
||||
expanded.value[filename] = !expanded.value[filename];
|
||||
// 首次展开时加载该文件的所有记录
|
||||
if (expanded.value[filename]) {
|
||||
const file = files.value.find((f) => f.filename === filename);
|
||||
if (!file.records) {
|
||||
@@ -535,22 +614,26 @@ const HistoryView = {
|
||||
},
|
||||
template: `
|
||||
<div class="history-view">
|
||||
<h2>📋 历史记录</h2>
|
||||
<h2>
|
||||
<span class="card-icon">📋</span>
|
||||
历史记录
|
||||
</h2>
|
||||
<p class="card-subtitle">按文件名聚合展示所有上传与分析任务。</p>
|
||||
|
||||
<div v-if="loading" class="center-block">
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载历史记录...</p>
|
||||
<p class="loading-text">正在加载历史记录...</p>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="error-text">{{ error }}</p>
|
||||
<p v-if="error" class="error-message">{{ error }}</p>
|
||||
|
||||
<div v-if="!loading && files.length === 0 && !error" class="center-block">
|
||||
<div style="font-size: 40px; margin-bottom: 12px;">📁</div>
|
||||
<p>暂无历史记录,先在仪表盘上传一个文件吧。</p>
|
||||
<div v-if="!loading && files.length === 0 && !error" class="empty-state">
|
||||
<div class="empty-state-icon">📁</div>
|
||||
<h3>暂无历史记录</h3>
|
||||
<p>先在仪表盘上传一个文件吧。</p>
|
||||
</div>
|
||||
|
||||
<div class="file-list" v-if="files.length">
|
||||
<TransitionGroup name="list" tag="div" class="file-list" v-if="files.length">
|
||||
<div
|
||||
v-for="file in files"
|
||||
:key="file.filename"
|
||||
@@ -558,48 +641,51 @@ const HistoryView = {
|
||||
:class="{ expanded: expanded[file.filename] }"
|
||||
>
|
||||
<div class="file-header" @click="toggleExpand(file.filename)">
|
||||
<div>
|
||||
<div class="file-info">
|
||||
<div class="file-name">📄 {{ file.filename }}</div>
|
||||
<div class="file-meta">
|
||||
总记录:{{ file.record_count }} 条
|
||||
<span v-if="file.last_upload"> | 最近上传:{{ formatDateTime(file.last_upload) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="expand-btn">
|
||||
<button class="btn btn-secondary expand-btn">
|
||||
{{ expanded[file.filename] ? '收起' : '展开' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="record-list" v-if="expanded[file.filename]">
|
||||
<div v-if="!file.records" class="center-block">
|
||||
<div class="spinner small"></div>
|
||||
<p>正在加载记录...</p>
|
||||
</div>
|
||||
<div v-else-if="file.records.length === 0" class="center-block">
|
||||
暂无详细记录
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-for="record in file.records"
|
||||
:key="record.task_id"
|
||||
class="record-item"
|
||||
@click="openResult(record.task_id)"
|
||||
>
|
||||
<div class="record-header">
|
||||
<span>📅 {{ formatDateTime(record.upload_time) }}</span>
|
||||
<span class="status-pill" :class="'status-' + record.status">
|
||||
{{ statusText(record.status) }}
|
||||
</span>
|
||||
<transition name="expand">
|
||||
<div class="record-list" v-if="expanded[file.filename]">
|
||||
<div v-if="!file.records" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p class="loading-text">正在加载记录...</p>
|
||||
</div>
|
||||
<div class="record-meta">
|
||||
<span>任务 ID:{{ record.task_id }}</span>
|
||||
<span>文件大小:{{ formatFileSize(record.file_size) }}</span>
|
||||
<span v-if="record.completed_at">完成时间:{{ formatDateTime(record.completed_at) }}</span>
|
||||
<div v-else-if="file.records.length === 0" class="empty-state">
|
||||
<p>暂无详细记录</p>
|
||||
</div>
|
||||
<TransitionGroup name="list" tag="div" v-else>
|
||||
<div
|
||||
v-for="record in file.records"
|
||||
:key="record.task_id"
|
||||
class="record-item"
|
||||
@click="openResult(record.task_id)"
|
||||
>
|
||||
<div class="record-header">
|
||||
<span class="record-date">📅 {{ formatDateTime(record.upload_time) }}</span>
|
||||
<span class="status-badge" :class="'status-' + record.status">
|
||||
{{ statusText(record.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="record-meta">
|
||||
<span>任务 ID:{{ record.task_id }}</span>
|
||||
<span>文件大小:{{ formatFileSize(record.file_size) }}</span>
|
||||
<span v-if="record.completed_at">完成时间:{{ formatDateTime(record.completed_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -651,67 +737,72 @@ const ResultView = {
|
||||
},
|
||||
template: `
|
||||
<div class="result-view">
|
||||
<h2>📊 分析结果详情</h2>
|
||||
<h2>
|
||||
<span class="card-icon">📊</span>
|
||||
分析结果详情
|
||||
</h2>
|
||||
|
||||
<div v-if="loading" class="center-block">
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载任务 {{ taskId }} 的分析结果...</p>
|
||||
<p class="loading-text">正在加载任务 {{ taskId }} 的分析结果...</p>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="error-text">{{ error }}</p>
|
||||
<p v-if="error" class="error-message">{{ error }}</p>
|
||||
|
||||
<div v-if="task && !loading" class="result-layout">
|
||||
<section class="card task-card">
|
||||
<!-- 任务信息卡片 -->
|
||||
<section class="result-card task-card">
|
||||
<h3>📝 任务信息</h3>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">任务 ID</span>
|
||||
<span class="info-value">{{ task.task_id || 'N/A' }}</span>
|
||||
<div class="task-info">
|
||||
<div class="data-item">
|
||||
<span class="data-label">任务 ID</span>
|
||||
<span class="data-value">{{ task.task_id || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">文件名</span>
|
||||
<span class="info-value">{{ task.filename || 'N/A' }}</span>
|
||||
<div class="data-item">
|
||||
<span class="data-label">文件名</span>
|
||||
<span class="data-value">{{ task.filename || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">文件大小</span>
|
||||
<span class="info-value">{{ task.file_size ? formatFileSize(task.file_size) : 'N/A' }}</span>
|
||||
<div class="data-item">
|
||||
<span class="data-label">文件大小</span>
|
||||
<span class="data-value">{{ task.file_size ? formatFileSize(task.file_size) : 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">状态</span>
|
||||
<span class="info-value">
|
||||
<span class="status-pill" :class="'status-' + task.status">
|
||||
{{ statusText(task.status) }}
|
||||
</span>
|
||||
<div class="data-item">
|
||||
<span class="data-label">状态</span>
|
||||
<span class="status-badge" :class="'status-' + task.status">
|
||||
{{ statusText(task.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">上传时间</span>
|
||||
<span class="info-value">{{ task.upload_time ? formatDateTime(task.upload_time) : 'N/A' }}</span>
|
||||
<div class="data-item">
|
||||
<span class="data-label">上传时间</span>
|
||||
<span class="data-value">{{ task.upload_time ? formatDateTime(task.upload_time) : 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">完成时间</span>
|
||||
<span class="info-value">{{ task.completed_at ? formatDateTime(task.completed_at) : 'N/A' }}</span>
|
||||
<div class="data-item">
|
||||
<span class="data-label">完成时间</span>
|
||||
<span class="data-value">{{ task.completed_at ? formatDateTime(task.completed_at) : 'N/A' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="task.error" class="error-box">
|
||||
<div v-if="task.error" class="error-message">
|
||||
<strong>错误信息:</strong>{{ task.error }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 几何属性 -->
|
||||
<section class="grid-3">
|
||||
<div class="card">
|
||||
<div class="result-card">
|
||||
<h3>📐 几何属性</h3>
|
||||
<div v-if="geometry" class="data-grid">
|
||||
<div v-if="geometry" class="geometry-data">
|
||||
<div class="data-item">
|
||||
<span class="data-label">体积</span>
|
||||
<span class="data-value">
|
||||
{{ geometry.volume ? formatNumber(geometry.volume) + ' mm³' : 'N/A' }}
|
||||
{{ geometry.volume ? formatNumber(geometry.volume) : 'N/A' }}
|
||||
<span class="data-unit">mm³</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">表面积</span>
|
||||
<span class="data-value">
|
||||
{{ geometry.surface_area ? formatNumber(geometry.surface_area) + ' mm²' : 'N/A' }}
|
||||
{{ geometry.surface_area ? formatNumber(geometry.surface_area) : 'N/A' }}
|
||||
<span class="data-unit">mm²</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
@@ -725,13 +816,15 @@ const ResultView = {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="placeholder">无几何数据</div>
|
||||
<div v-else class="empty-state">
|
||||
<p>无几何数据</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="result-card">
|
||||
<h3>📦 边界框</h3>
|
||||
<div v-if="geometry?.bounding_box" class="data-grid">
|
||||
<div class="data-item">
|
||||
<div v-if="geometry?.bounding_box" class="bounding-box-data">
|
||||
<div class="data-item coordinate-item">
|
||||
<span class="data-label">尺寸 (mm)</span>
|
||||
<span class="data-value">
|
||||
{{ geometry.bounding_box.dimensions[0].toFixed(2) }} ×
|
||||
@@ -739,29 +832,31 @@ const ResultView = {
|
||||
{{ geometry.bounding_box.dimensions[2].toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-item coordinate-item">
|
||||
<span class="data-label">最小坐标</span>
|
||||
<span class="data-value">
|
||||
<span class="data-value coordinate-value">
|
||||
X: {{ geometry.bounding_box.min[0].toFixed(2) }},
|
||||
Y: {{ geometry.bounding_box.min[1].toFixed(2) }},
|
||||
Z: {{ geometry.bounding_box.min[2].toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-item coordinate-item">
|
||||
<span class="data-label">最大坐标</span>
|
||||
<span class="data-value">
|
||||
<span class="data-value coordinate-value">
|
||||
X: {{ geometry.bounding_box.max[0].toFixed(2) }},
|
||||
Y: {{ geometry.bounding_box.max[1].toFixed(2) }},
|
||||
Z: {{ geometry.bounding_box.max[2].toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="placeholder">无边界框数据</div>
|
||||
<div v-else class="empty-state">
|
||||
<p>无边界框数据</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="result-card">
|
||||
<h3>🔺 拓扑结构</h3>
|
||||
<div v-if="geometry?.topology" class="data-grid">
|
||||
<div v-if="geometry?.topology" class="topology-data">
|
||||
<div class="data-item">
|
||||
<span class="data-label">面数</span>
|
||||
<span class="data-value">{{ geometry.topology.faces || 0 }}</span>
|
||||
@@ -775,35 +870,30 @@ const ResultView = {
|
||||
<span class="data-value">{{ geometry.topology.vertices || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="placeholder">无拓扑数据</div>
|
||||
<div v-else class="empty-state">
|
||||
<p>无拓扑数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 模具工艺信息 -->
|
||||
<section class="grid-2">
|
||||
<div class="card">
|
||||
<div class="result-card">
|
||||
<h3>🔧 模具 / 工艺关键信息</h3>
|
||||
<div v-if="keyInfo" class="data-grid">
|
||||
<div v-if="keyInfo" class="metrics-data">
|
||||
<div class="data-item">
|
||||
<span class="data-label">收缩率</span>
|
||||
<span class="data-value">
|
||||
{{ keyInfo.metadata?.shrinkage_rate ?? 'N/A' }}
|
||||
</span>
|
||||
<span class="data-value">{{ keyInfo.metadata?.shrinkage_rate ?? 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">拔模角</span>
|
||||
<span class="data-value">
|
||||
{{
|
||||
keyInfo.metadata?.draft_angle !== undefined
|
||||
? keyInfo.metadata.draft_angle + '°'
|
||||
: 'N/A'
|
||||
}}
|
||||
{{ keyInfo.metadata?.draft_angle !== undefined ? keyInfo.metadata.draft_angle + '°' : 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">预估夹紧力</span>
|
||||
<span class="data-value">
|
||||
{{ keyInfo.manufacturing_info?.estimated_clamping_force || 'N/A' }}
|
||||
</span>
|
||||
<span class="data-value">{{ keyInfo.manufacturing_info?.estimated_clamping_force || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">模具尺寸 (mm)</span>
|
||||
@@ -822,34 +912,28 @@ const ResultView = {
|
||||
<div class="data-item">
|
||||
<span class="data-label">产品重量</span>
|
||||
<span class="data-value">
|
||||
{{
|
||||
keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics
|
||||
?.product_weight || 'N/A'
|
||||
}}
|
||||
{{ keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics?.product_weight || 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">壁厚范围</span>
|
||||
<span class="data-value">
|
||||
{{
|
||||
keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics
|
||||
?.wall_thickness_range || 'N/A'
|
||||
}}
|
||||
{{ keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics?.wall_thickness_range || 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<span class="data-label">预估成型周期</span>
|
||||
<span class="data-value">
|
||||
{{ keyInfo.manufacturing_info?.estimated_cycle_time || 'N/A' }}
|
||||
</span>
|
||||
<span class="data-value">{{ keyInfo.manufacturing_info?.estimated_cycle_time || 'N/A' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="placeholder">无型腔 / 工艺数据</div>
|
||||
<div v-else class="empty-state">
|
||||
<p>无型腔 / 工艺数据</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="result-card">
|
||||
<h3>🧱 网格摘要</h3>
|
||||
<div v-if="meshSummary" class="data-grid">
|
||||
<div v-if="meshSummary" class="metrics-data">
|
||||
<div class="data-item">
|
||||
<span class="data-label">网格质量等级</span>
|
||||
<span class="data-value">{{ meshSummary.quality }}</span>
|
||||
@@ -867,8 +951,8 @@ const ResultView = {
|
||||
<span class="data-value">{{ meshSummary.point_count ?? 'N/A' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="placeholder">
|
||||
当前任务未生成网格摘要,或网格生成过程中出现问题。
|
||||
<div v-else class="empty-state">
|
||||
<p>当前任务未生成网格摘要</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -891,4 +975,3 @@ const router = createRouter({
|
||||
|
||||
// 挂载应用
|
||||
createApp(App).use(router).mount("#app");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user