feat: 新增 Revit 模型查看器组件、报告日志页面及 CAD 状态管理模块。
This commit is contained in:
parent
b53f9d37e5
commit
e01376de57
@ -37,6 +37,14 @@
|
||||
<span class="info-label">文件大小:</span>
|
||||
<span class="info-value">{{ projectData.FileSizeDisplay }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">多边形面片数:</span>
|
||||
<span class="info-value">{{ projectData.PolygonCount.toLocaleString() }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">特征总数:</span>
|
||||
<span class="info-value">{{ projectData.FeatureCount.toLocaleString() }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">中心文件:</span>
|
||||
<span class="info-value">{{ projectData.IsCentralFile ? '是' : '否' }}</span>
|
||||
@ -171,9 +179,11 @@ const projectData = computed(() => {
|
||||
ProjectName: project.name || 'Unknown Project',
|
||||
FilePath: project.filePath || 'Unknown Path',
|
||||
RevitVersion: project.revitVersion || 'Unknown Version',
|
||||
FileSizeDisplay: project.fileSizeDisplay || 'Unknown Size',
|
||||
FileSizeDisplay: project.fileSizeDisplay || project.fileSize || 'Unknown Size',
|
||||
PolygonCount: project.polygonCount || 0,
|
||||
FeatureCount: project.featureCount || 0,
|
||||
IsCentralFile: project.isCentralFile || false,
|
||||
IsLoaded: project.status === '活动'
|
||||
IsLoaded: project.status === '已保存' || project.status === '活动'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -25,18 +25,18 @@
|
||||
</div>
|
||||
|
||||
<ul class="report-list">
|
||||
<li v-for="log in filteredLogs" :key="log.reportId"
|
||||
<li v-for="log in filteredLogs" :key="log.id || log.reportId"
|
||||
class="report-item"
|
||||
:class="{ active: currentReport?.reportId === log.reportId }"
|
||||
:class="{ active: currentReport?.id === log.id || (currentReport?.reportId && currentReport?.reportId === log.reportId) }"
|
||||
@click="selectReport(log)">
|
||||
<div class="item-icon" :class="log.status">
|
||||
<i :class="log.status === 'success' ? 'fas fa-check-circle' : 'fas fa-exclamation-circle'"></i>
|
||||
<i :class="(log.status === 'success' || !log.status) ? 'fas fa-check-circle' : 'fas fa-exclamation-circle'"></i>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="item-title">{{ log.operation }}</div>
|
||||
<div class="item-title">{{ log.operation || '系统操作' }}</div>
|
||||
<div class="item-meta">
|
||||
<span class="meta-id">{{ log.reportId }}</span>
|
||||
<span class="meta-time">{{ log.createdAt }}</span>
|
||||
<span class="meta-id">{{ log.operation_category || log.log_type || '系统日志' }}</span>
|
||||
<span class="meta-time">{{ formatTime(log.timestamp || log.createdAt || log.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@ -45,72 +45,79 @@
|
||||
|
||||
<!-- 右侧报告详情 -->
|
||||
<div class="detail-section card-panel" v-if="currentReport">
|
||||
<div class="detail-header">
|
||||
<div class="report-id-badge">
|
||||
<i class="fas fa-hashtag"></i>
|
||||
<span>{{ currentReport.reportId }}</span>
|
||||
<el-tag :type="currentReport.status === 'success' ? 'success' : 'danger'" size="small" style="margin-left: 10px">
|
||||
{{ currentReport.status === 'success' ? '执行成功' : '执行异常' }}
|
||||
<div class="detail-header overview-custom-header">
|
||||
<div class="header-left">
|
||||
<el-tag effect="dark" :type="currentReport.status === 'success' ? 'success' : (currentReport.status === 'pending' ? 'warning' : 'danger')" size="large" class="status-tag">
|
||||
<i :class="currentReport.status === 'success' ? 'fas fa-check-circle' : (currentReport.status === 'pending' ? 'fas fa-spinner fa-spin' : 'fas fa-times-circle')"></i>
|
||||
{{ currentReport.status === 'success' ? '执行成功' : (currentReport.status === 'pending' ? '处理中' : '执行异常') }}
|
||||
</el-tag>
|
||||
<span class="report-id-text" :title="currentReport.id || currentReport.reportId"><i class="fas fa-fingerprint"></i> ID: {{ currentReport.id || currentReport.reportId || 'LOG-000' }}</span>
|
||||
</div>
|
||||
<div class="report-time">
|
||||
<i class="fas fa-clock"></i> 耗时: {{ currentReport.duration }}s
|
||||
<div class="report-time" v-if="currentReport.duration">
|
||||
<i class="fas fa-clock"></i> 耗时: {{ currentReport.duration }}ms
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="report-tabs">
|
||||
<el-tab-pane label="执行概览" name="overview">
|
||||
<div class="detail-body">
|
||||
<!-- 统计摘要 -->
|
||||
<div class="stat-summary">
|
||||
<div class="summary-box">
|
||||
<span class="box-label">分析模型数</span>
|
||||
<span class="box-value accent">{{ currentReport.resultMetrics.modelCount }}</span>
|
||||
</div>
|
||||
<div class="summary-box">
|
||||
<span class="box-label">识别特征总数</span>
|
||||
<span class="box-value accent">{{ currentReport.resultMetrics.featureCount }}</span>
|
||||
</div>
|
||||
<div class="summary-box">
|
||||
<span class="box-label">处理部件数</span>
|
||||
<span class="box-value accent">{{ currentReport.resultMetrics.processedCount }}</span>
|
||||
<div class="detail-body styled-body">
|
||||
|
||||
<div class="overview-title-card">
|
||||
<div class="overview-title-icon" :class="'icon-' + getCategoryColor(currentReport.operation_category)">
|
||||
<i :class="getCategoryIconClass(currentReport.operation_category)"></i>
|
||||
</div>
|
||||
<h2 class="overview-title-text">{{ currentReport.operation || '系统操作' }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- 特征详情表格 -->
|
||||
<div class="detail-group">
|
||||
<h4 class="group-title"><i class="fas fa-cube"></i> 特征结果详情</h4>
|
||||
<el-table :data="currentReport.featureDetails" style="width: 100%" border>
|
||||
<el-table-column prop="featureName" label="特征名称" width="150" />
|
||||
<el-table-column prop="featureType" label="类型" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag size="small">{{ scope.row.featureType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="recommendedAction" label="建议动作" width="120">
|
||||
<template #default="scope">
|
||||
<span :class="getActionClass(scope.row.recommendedAction)">
|
||||
{{ scope.row.recommendedAction }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="后续步骤">
|
||||
<template #default="scope">
|
||||
<div v-for="(step, idx) in scope.row.recommendedSteps" :key="idx" class="step-text">
|
||||
{{ idx + 1 }}. {{ step }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="overview-info-card">
|
||||
<el-descriptions class="custom-descriptions" :column="3" border>
|
||||
<el-descriptions-item>
|
||||
<template #label><div class="desc-label"><i class="fas fa-sitemap"></i> 所属分类</div></template>
|
||||
<el-tag size="small" type="info" effect="plain">{{ currentReport.operation_category || currentReport.log_type || '系统日志' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><div class="desc-label"><i class="far fa-clock"></i> 执行时间</div></template>
|
||||
<span class="mono">{{ formatTime(currentReport.timestamp || currentReport.createdAt || currentReport.created_at) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><div class="desc-label"><i class="fas fa-desktop"></i> 操作节点</div></template>
|
||||
<span class="mono">{{ currentReport.client_ip || currentReport.ip_address || '本地系统/客户端' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentReport.user_id || currentReport.client_id">
|
||||
<template #label><div class="desc-label"><i class="fas fa-user-circle"></i> 操作人员</div></template>
|
||||
{{ currentReport.user_id || currentReport.client_id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentReport.level">
|
||||
<template #label><div class="desc-label"><i class="fas fa-shield-alt"></i> 安全级别</div></template>
|
||||
<el-tag size="small" :type="currentReport.level === 'error' ? 'danger' : 'success'">{{ currentReport.level }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentReport.action_type">
|
||||
<template #label><div class="desc-label"><i class="fas fa-bolt"></i> 动作分发</div></template>
|
||||
<span class="mono">{{ currentReport.action_type }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 异常信息区 -->
|
||||
<div class="detail-group error-group" v-if="currentReport.errorInfo">
|
||||
<h4 class="group-title error-text"><i class="fas fa-exclamation-triangle"></i> 异常日志</h4>
|
||||
<div class="error-console">
|
||||
{{ currentReport.errorInfo }}
|
||||
</div>
|
||||
<!-- 详细信息区 -->
|
||||
<div class="detail-group mt-4" v-if="currentReport.details || currentReport.errorInfo || currentReport.error_message">
|
||||
<h4 class="group-title"><i class="fas fa-align-left text-primary"></i> 日志详细信息 / 异常信息</h4>
|
||||
<el-alert
|
||||
:title="currentReport.details || currentReport.errorInfo || currentReport.error_message"
|
||||
:type="currentReport.status === 'success' ? 'info' : 'error'"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="custom-alert-box"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 业务数据 -->
|
||||
<div class="detail-group mt-4" v-if="hasExtraData(currentReport)">
|
||||
<h4 class="group-title"><i class="fas fa-laptop-code text-warning"></i> 业务执行对象负荷 (Payload)</h4>
|
||||
<div class="json-viewer-box">
|
||||
<pre><code>{{ formatExtraData(currentReport) }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@ -118,9 +125,8 @@
|
||||
<div class="comparison-view">
|
||||
<div class="comparison-mode-switch">
|
||||
<el-radio-group v-model="comparisonMode" size="small">
|
||||
<el-radio-button label="live">实时穿透 (当前CAD模型)</el-radio-button>
|
||||
<el-radio-button label="auto">分析报告 (当前记录)</el-radio-button>
|
||||
<el-radio-button label="manual">离线文件 (手动选择)</el-radio-button>
|
||||
<el-radio-button label="live">实时穿透 (当前模型流程)</el-radio-button>
|
||||
<el-radio-button label="manual">离线文件 (当前模型 vs 离线文件)</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
@ -128,86 +134,59 @@
|
||||
<div v-if="comparisonMode === 'live'" class="live-comparison">
|
||||
<div class="live-flow-container">
|
||||
<!-- 步骤 1: 捕获基准 -->
|
||||
<div class="flow-step" :class="{ 'step-active': liveStep === 1, 'step-done': liveStep > 1 }">
|
||||
<div class="flow-step" :class="{ 'step-active': cadStore.liveComparisonState.step === 1, 'step-done': cadStore.liveComparisonState.step > 1 }">
|
||||
<div class="step-header">
|
||||
<div class="step-num">1</div>
|
||||
<h3>记录当前模型基准状态</h3>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<el-button v-if="liveStep === 1" type="primary" :loading="isCapturingBaseline" @click="captureBaseline" class="live-btn">
|
||||
<el-button v-if="cadStore.liveComparisonState.step === 1" type="primary" :loading="isCapturingBaseline" @click="captureBaseline" class="live-btn">
|
||||
<i class="fas fa-crosshairs"></i> 一键读取 CAD 当前基准参数
|
||||
</el-button>
|
||||
<div v-else class="captured-data card-panel">
|
||||
<h4><i class="fas fa-cube text-info"></i> 基准原始模型</h4>
|
||||
<ul class="data-list">
|
||||
<li><span class="lbl">模型文件:</span> <span class="val">{{ baselineData.name }}</span></li>
|
||||
<li><span class="lbl">内存占用:</span> <span class="val">{{ baselineData.mem }}</span></li>
|
||||
<li><span class="lbl">面片数量:</span> <span class="val">{{ baselineData.poly }}</span></li>
|
||||
<li><span class="lbl">特征数量:</span> <span class="val">{{ baselineData.features }}</span></li>
|
||||
<li><span class="lbl">模型文件:</span> <span class="val">{{ cadStore.liveComparisonState.baselineData?.name }}</span></li>
|
||||
<li><span class="lbl">内存占用:</span> <span class="val">{{ cadStore.liveComparisonState.baselineData?.mem }}</span></li>
|
||||
<li><span class="lbl">面片数量:</span> <span class="val">{{ cadStore.liveComparisonState.baselineData?.poly }}</span></li>
|
||||
<li><span class="lbl">特征数量:</span> <span class="val">{{ cadStore.liveComparisonState.baselineData?.features }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤 2: 执行轻量化 -->
|
||||
<div class="flow-step" :class="{ 'step-active': liveStep === 2, 'step-done': liveStep > 2 }">
|
||||
<!-- 步骤 2: 捕获当前状态并比对 -->
|
||||
<div class="flow-step" :class="{ 'step-active': cadStore.liveComparisonState.step === 2, 'step-done': cadStore.liveComparisonState.step > 2 }">
|
||||
<div class="step-header">
|
||||
<div class="step-num">2</div>
|
||||
<h3>执行轻量化处理</h3>
|
||||
<h3>捕获当前状态并生成对比</h3>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div v-if="liveStep <= 2" class="wait-msg">
|
||||
<i class="fas fa-info-circle"></i> 记录基准后,请在对应 CAD 插件中直接执行处理,或由系统在此处触发:
|
||||
<div v-if="cadStore.liveComparisonState.step === 2" class="wait-msg">
|
||||
<i class="fas fa-info-circle"></i> 记录基准后,您可以前往其他模块或在 CAD 中自由执行各种操作。操作完成后返回此处点击下方按钮:
|
||||
</div>
|
||||
<el-button v-if="liveStep === 2" type="warning" :loading="isLightweighting" @click="executeLightweight" class="live-btn">
|
||||
<i class="fas fa-magic"></i> 发送轻量化指令并等待
|
||||
</el-button>
|
||||
<div v-if="liveStep > 2" class="success-msg">
|
||||
<i class="fas fa-check-circle text-success"></i> 轻量化操作已完成
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤 3: 捕获轻量化后状态 -->
|
||||
<div class="flow-step" :class="{ 'step-active': liveStep === 3, 'step-done': liveStep > 3 }">
|
||||
<div class="step-header">
|
||||
<div class="step-num">3</div>
|
||||
<h3>捕获结果并生成对比</h3>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<el-button v-if="liveStep === 3" type="success" :loading="isCapturingResult" @click="captureResult" class="live-btn" plain>
|
||||
<i class="fas fa-sync-alt"></i> 读取轻量化后状态并对比
|
||||
<el-button v-if="cadStore.liveComparisonState.step === 2" type="success" :loading="isCapturingResult" @click="captureResult" class="live-btn" plain>
|
||||
<i class="fas fa-sync-alt"></i> 读取当前最新状态并对比
|
||||
</el-button>
|
||||
|
||||
<transition name="el-zoom-in-top">
|
||||
<div v-if="liveStep === 4" class="comparison-results mt-4">
|
||||
<div class="compress-rates">
|
||||
<div class="rate-card">
|
||||
<div class="rate-title">内存占用降低</div>
|
||||
<div class="rate-value text-success">-65.4%</div>
|
||||
</div>
|
||||
<div class="rate-card">
|
||||
<div class="rate-title">面片数量缩减</div>
|
||||
<div class="rate-value text-success">-72.1%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="cadStore.liveComparisonState.step === 3" class="comparison-results mt-4">
|
||||
<div class="comparison-columns mt-4">
|
||||
<div class="comp-col source-col">
|
||||
<h4><i class="fas fa-cube"></i> 基准状态</h4>
|
||||
<ul class="prop-list">
|
||||
<li><span class="prop-label">内存占用</span> <span class="prop-value">{{ baselineData.mem }}</span></li>
|
||||
<li><span class="prop-label">面片数量</span> <span class="prop-value">{{ baselineData.poly }}</span></li>
|
||||
<li><span class="prop-label">特征总数</span> <span class="prop-value">{{ baselineData.features }}</span></li>
|
||||
<li><span class="prop-label">内存占用</span> <span class="prop-value">{{ cadStore.liveComparisonState.baselineData?.mem }}</span></li>
|
||||
<li><span class="prop-label">面片数量</span> <span class="prop-value">{{ cadStore.liveComparisonState.baselineData?.poly }}</span></li>
|
||||
<li><span class="prop-label">特征总数</span> <span class="prop-value">{{ cadStore.liveComparisonState.baselineData?.features }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="vs-divider">VS</div>
|
||||
<div class="comp-col target-col">
|
||||
<h4><i class="fas fa-leaf text-success"></i> 轻量化后</h4>
|
||||
<h4><i class="fas fa-leaf text-success"></i> 最新状态</h4>
|
||||
<ul class="prop-list">
|
||||
<li><span class="prop-label">内存占用</span> <span class="prop-value text-success">{{ resultData.mem }}</span></li>
|
||||
<li><span class="prop-label">面片数量</span> <span class="prop-value text-success">{{ resultData.poly }}</span></li>
|
||||
<li><span class="prop-label">特征总数</span> <span class="prop-value text-success">{{ resultData.features }}</span></li>
|
||||
<li><span class="prop-label">内存占用</span> <span class="prop-value text-success">{{ cadStore.liveComparisonState.resultData?.mem }}</span></li>
|
||||
<li><span class="prop-label">面片数量</span> <span class="prop-value text-success">{{ cadStore.liveComparisonState.resultData?.poly }}</span></li>
|
||||
<li><span class="prop-label">特征总数</span> <span class="prop-value text-success">{{ cadStore.liveComparisonState.resultData?.features }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -222,70 +201,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动模式内容 -->
|
||||
<div v-else-if="comparisonMode === 'auto'" class="auto-comparison">
|
||||
<div v-if="!currentReport.comparisonData" class="empty-data">
|
||||
<i class="fas fa-info-circle"></i> 当前报告未包含比对数据
|
||||
</div>
|
||||
<div v-else class="comparison-dashboard">
|
||||
<div class="result-badges">
|
||||
<el-tag effect="dark" type="info">{{ currentReport.comparisonData.sourceFormat }}</el-tag>
|
||||
<i class="fas fa-arrow-right badge-arrow"></i>
|
||||
<el-tag effect="dark" type="success">{{ currentReport.comparisonData.targetFormat }}</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="compress-rates">
|
||||
<div class="rate-card">
|
||||
<div class="rate-title">文件体积减小</div>
|
||||
<div class="rate-value text-success">{{ currentReport.comparisonData.sizeReduction }}</div>
|
||||
</div>
|
||||
<div class="rate-card">
|
||||
<div class="rate-title">多边形面片缩减</div>
|
||||
<div class="rate-value text-success">{{ currentReport.comparisonData.polygonReduction }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-columns">
|
||||
<div class="comp-col source-col">
|
||||
<h4><i class="fas fa-cube"></i> 基准原始模型</h4>
|
||||
<ul class="prop-list">
|
||||
<li v-for="(val, key) in currentReport.comparisonData.sourceProps" :key="key">
|
||||
<span class="prop-label">{{ key }}</span>
|
||||
<span class="prop-value">{{ val }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="vs-divider">VS</div>
|
||||
<div class="comp-col target-col">
|
||||
<h4><i class="fas fa-leaf"></i> 轻量化结果模型</h4>
|
||||
<ul class="prop-list">
|
||||
<li v-for="(val, key) in currentReport.comparisonData.targetProps" :key="key">
|
||||
<span class="prop-label">{{ key }}</span>
|
||||
<span class="prop-value text-success">{{ val }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 手动模式内容 -->
|
||||
<!-- 手动/混合模式内容 -->
|
||||
<div v-else class="manual-comparison">
|
||||
<div class="upload-panels">
|
||||
<div class="upload-box">
|
||||
<i class="fas fa-cloud-upload-alt"></i>
|
||||
<p>请选择基准源文件 (.asm, .rvt...)</p>
|
||||
<el-button size="small">选择基准文件</el-button>
|
||||
<div class="upload-box baseline-source">
|
||||
<div class="status-indicator">
|
||||
<i class="fas fa-desktop text-primary"></i>
|
||||
</div>
|
||||
<p class="source-title">当前 CAD 打开模型</p>
|
||||
<p class="source-desc">将自动抓取当前插件会话中的模型参数作为基准</p>
|
||||
</div>
|
||||
|
||||
<div class="vs-icon">VS</div>
|
||||
|
||||
<div class="upload-box target">
|
||||
<i class="fas fa-cloud-upload-alt"></i>
|
||||
<p>请选择轻量化目标 (.prt, .ifc...)</p>
|
||||
<el-button size="small" type="primary">选择目标文件</el-button>
|
||||
<i class="fas fa-file-upload"></i>
|
||||
<p>请选择比对目标离线文件 (.prt, .ifc, .rvt...)</p>
|
||||
<el-button size="small" type="primary">选择本地文件</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manual-actions">
|
||||
<el-button type="success" icon="el-icon-video-play" size="large">开始比对分析</el-button>
|
||||
<el-button type="success" icon="el-icon-video-play" size="large">开始混合比对分析</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -303,62 +239,169 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useCADStore } from '@/stores/cad'
|
||||
import creoApi from '@/services/creoApi'
|
||||
import revitApi from '@/services/revitApi'
|
||||
import pdmsApi from '@/services/pdmsApi'
|
||||
import websocketService from '@/services/websocketService'
|
||||
|
||||
const searchQuery = ref('')
|
||||
const activeTab = ref('overview')
|
||||
const comparisonMode = ref('live')
|
||||
|
||||
const liveStep = ref(1) // 1: 捕获基准, 2: 执行轻量化, 3: 捕获结果, 4: 显示报告
|
||||
const isCapturingBaseline = ref(false)
|
||||
const isLightweighting = ref(false)
|
||||
const isCapturingResult = ref(false)
|
||||
const baselineData = ref(null)
|
||||
const resultData = ref(null)
|
||||
|
||||
const captureBaseline = () => {
|
||||
const cadStore = useCADStore()
|
||||
|
||||
const captureBaseline = async () => {
|
||||
const connectedCAD = cadStore.currentCAD
|
||||
if (!connectedCAD) {
|
||||
ElMessage.warning('请先连接并打开对应的 CAD 插件和模型')
|
||||
return
|
||||
}
|
||||
|
||||
isCapturingBaseline.value = true
|
||||
setTimeout(() => {
|
||||
try {
|
||||
let result;
|
||||
let stats = {
|
||||
mem: 'API暂未支持',
|
||||
poly: 'API暂未支持',
|
||||
features: 'API暂未支持'
|
||||
}
|
||||
|
||||
if (connectedCAD.id === 'creo') {
|
||||
result = await creoApi.getCurrentModel()
|
||||
// 如果有可用的层级统计接口,我们用它来获取特征/部件总数
|
||||
try {
|
||||
const statResult = await creoApi.getHierarchyStatistics()
|
||||
if (statResult.success && statResult.data?.data?.statistics) {
|
||||
let totalFeatures = 0
|
||||
Object.values(statResult.data.data.statistics).forEach(v => totalFeatures += v)
|
||||
if(totalFeatures > 0) stats.features = `${totalFeatures} +`
|
||||
}
|
||||
} catch(e) { /* 忽略层级统计失败 */ }
|
||||
|
||||
} else if (connectedCAD.id === 'revit') {
|
||||
result = await revitApi.getCurrentModel()
|
||||
} else if (connectedCAD.id === 'pdms') {
|
||||
result = await pdmsApi.getCurrentModel()
|
||||
}
|
||||
|
||||
if (result && result.success) {
|
||||
const respData = result.data?.data || {}
|
||||
let fileName = '当前活动模型'
|
||||
|
||||
if (connectedCAD.id === 'creo' && respData.fileName) {
|
||||
fileName = respData.fileName
|
||||
} else if (connectedCAD.id === 'revit' && respData.project?.name) {
|
||||
fileName = respData.project.name
|
||||
} else if (connectedCAD.id === 'pdms' && respData.ProjectInfo?.ProjectName) {
|
||||
fileName = respData.ProjectInfo.ProjectName
|
||||
}
|
||||
|
||||
// 提取现有接口可能返回的真实数据大小、面片等(做兼容读取)
|
||||
if (respData.fileSize) stats.mem = respData.fileSize
|
||||
if (respData.memoryUsage) stats.mem = respData.memoryUsage
|
||||
if (respData.polygonCount) stats.poly = respData.polygonCount
|
||||
if (respData.featureCount) stats.features = respData.featureCount
|
||||
if (respData.componentCount) stats.features = respData.componentCount
|
||||
|
||||
// Revit 新的数据结构专门适配 (data.data.project.xxx)
|
||||
if (connectedCAD.id === 'revit' && respData.project) {
|
||||
if (respData.project.fileSizeDisplay) stats.mem = respData.project.fileSizeDisplay
|
||||
if (respData.project.polygonCount !== undefined) stats.poly = respData.project.polygonCount
|
||||
if (respData.project.featureCount !== undefined) stats.features = respData.project.featureCount
|
||||
}
|
||||
|
||||
cadStore.liveComparisonState.baselineData = {
|
||||
name: fileName,
|
||||
mem: stats.mem,
|
||||
poly: stats.poly,
|
||||
features: stats.features
|
||||
}
|
||||
cadStore.liveComparisonState.step = 2
|
||||
ElMessage.success('基准状态参数读取成功')
|
||||
} else {
|
||||
throw new Error(result?.error || '未能获取到开启的模型信息')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(`获取基准数据失败: ${error.message}`)
|
||||
} finally {
|
||||
isCapturingBaseline.value = false
|
||||
baselineData.value = {
|
||||
name: 'Engine_Assembly_V8_Final.asm',
|
||||
mem: '1420 MB',
|
||||
poly: '2,450,120',
|
||||
features: '12,054'
|
||||
}
|
||||
liveStep.value = 2
|
||||
ElMessage.success('基准状态捕获成功')
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
const executeLightweight = () => {
|
||||
isLightweighting.value = true
|
||||
setTimeout(() => {
|
||||
isLightweighting.value = false
|
||||
liveStep.value = 3
|
||||
ElMessage.success('轻量化指令执行完成')
|
||||
}, 2000)
|
||||
}
|
||||
const captureResult = async () => {
|
||||
const connectedCAD = cadStore.currentCAD
|
||||
if (!connectedCAD) {
|
||||
ElMessage.warning('请先连接对应的 CAD 插件')
|
||||
return
|
||||
}
|
||||
|
||||
const captureResult = () => {
|
||||
isCapturingResult.value = true
|
||||
setTimeout(() => {
|
||||
isCapturingResult.value = false
|
||||
resultData.value = {
|
||||
mem: '490 MB',
|
||||
poly: '682,100',
|
||||
features: '342'
|
||||
try {
|
||||
let result;
|
||||
let stats = {
|
||||
mem: 'API暂未支持',
|
||||
poly: 'API暂未支持',
|
||||
features: 'API暂未支持'
|
||||
}
|
||||
liveStep.value = 4
|
||||
ElMessage.success('最新状态捕获及对比报告已生成')
|
||||
}, 1000)
|
||||
|
||||
if (connectedCAD.id === 'creo') {
|
||||
result = await creoApi.getCurrentModel()
|
||||
try {
|
||||
const statResult = await creoApi.getHierarchyStatistics()
|
||||
if (statResult.success && statResult.data?.data?.statistics) {
|
||||
let totalFeatures = 0
|
||||
Object.values(statResult.data.data.statistics).forEach(v => totalFeatures += v)
|
||||
if(totalFeatures > 0) stats.features = `${totalFeatures} +`
|
||||
}
|
||||
} catch(e) { }
|
||||
} else if (connectedCAD.id === 'revit') {
|
||||
result = await revitApi.getCurrentModel()
|
||||
} else if (connectedCAD.id === 'pdms') {
|
||||
result = await pdmsApi.getCurrentModel()
|
||||
}
|
||||
|
||||
if (result && result.success) {
|
||||
const respData = result.data?.data || {}
|
||||
|
||||
// 提取现有接口可能返回的真实数据大小、面片等(做兼容读取)
|
||||
if (respData.fileSize) stats.mem = respData.fileSize
|
||||
if (respData.memoryUsage) stats.mem = respData.memoryUsage
|
||||
if (respData.polygonCount) stats.poly = respData.polygonCount
|
||||
if (respData.featureCount) stats.features = respData.featureCount
|
||||
if (respData.componentCount) stats.features = respData.componentCount
|
||||
|
||||
// Revit 新的数据结构专门适配 (data.data.project.xxx)
|
||||
if (connectedCAD.id === 'revit' && respData.project) {
|
||||
if (respData.project.fileSizeDisplay) stats.mem = respData.project.fileSizeDisplay
|
||||
if (respData.project.polygonCount !== undefined) stats.poly = respData.project.polygonCount
|
||||
if (respData.project.featureCount !== undefined) stats.features = respData.project.featureCount
|
||||
}
|
||||
|
||||
cadStore.liveComparisonState.resultData = {
|
||||
mem: stats.mem,
|
||||
poly: stats.poly,
|
||||
features: stats.features
|
||||
}
|
||||
cadStore.liveComparisonState.step = 3
|
||||
ElMessage.success('新状态已成功捕获并生成对比报告')
|
||||
} else {
|
||||
throw new Error(result?.error || '未能获取到轻量化后模型信息')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(`获取轻量化结果失败: ${error.message}`)
|
||||
} finally {
|
||||
isCapturingResult.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetLiveFlow = () => {
|
||||
liveStep.value = 1
|
||||
baselineData.value = null
|
||||
resultData.value = null
|
||||
cadStore.resetLiveComparison()
|
||||
}
|
||||
|
||||
// 模拟报告数据
|
||||
@ -424,25 +467,119 @@ const mockReports = [
|
||||
}
|
||||
]
|
||||
|
||||
const logs = ref([...mockReports])
|
||||
const logs = ref([...mockReports]) // 初始使用 mock, 等待 ws 同步
|
||||
const currentReport = ref(logs.value[0])
|
||||
|
||||
// --- WebSocket 日志同步逻辑开始 --- //
|
||||
let listeners = []
|
||||
|
||||
const initListeners = () => {
|
||||
const onLogUpdate = (logData) => {
|
||||
let newLogs = []
|
||||
if (Array.isArray(logData)) {
|
||||
newLogs = [...logData].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
|
||||
} else if (logData.logs) {
|
||||
newLogs = [...logData.logs].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
|
||||
}
|
||||
|
||||
// 如果获取到了真实的后台日志,我们就直接覆盖 mock
|
||||
if (newLogs.length > 0) {
|
||||
logs.value = newLogs
|
||||
// 若当前没有选中日志,或者选中的日志在新的列表里找不到
|
||||
if (!currentReport.value || !logs.value.find(l => l.id === currentReport.value.id)) {
|
||||
currentReport.value = logs.value[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
websocketService.on('onLogUpdate', onLogUpdate)
|
||||
listeners.push(['onLogUpdate', onLogUpdate])
|
||||
|
||||
// 组件挂载时主动查询一次日志
|
||||
websocketService.queryLogs()
|
||||
}
|
||||
|
||||
const removeListeners = () => {
|
||||
listeners.forEach(([event, callback]) => {
|
||||
websocketService.off(event, callback)
|
||||
})
|
||||
listeners = []
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initListeners()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
removeListeners()
|
||||
})
|
||||
|
||||
// --- WebSocket 日志同步逻辑结束 --- //
|
||||
|
||||
const filteredLogs = computed(() => {
|
||||
if (!searchQuery.value) return logs.value
|
||||
const q = searchQuery.value.toLowerCase()
|
||||
return logs.value.filter(log => log.reportId.toLowerCase().includes(q) || log.operation.toLowerCase().includes(q))
|
||||
return logs.value.filter(log =>
|
||||
(log.operation && log.operation.toLowerCase().includes(q)) ||
|
||||
(log.operation_category && log.operation_category.toLowerCase().includes(q))
|
||||
)
|
||||
})
|
||||
|
||||
const selectReport = (log) => {
|
||||
currentReport.value = log
|
||||
}
|
||||
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return ''
|
||||
try {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
} catch {
|
||||
return timestamp
|
||||
}
|
||||
}
|
||||
|
||||
const getActionClass = (action) => {
|
||||
if (!action) return ''
|
||||
if (action.includes('删除')) return 'text-success'
|
||||
if (action.includes('确认')) return 'text-warning'
|
||||
return 'text-info'
|
||||
}
|
||||
|
||||
const getCategoryColor = (category) => {
|
||||
if (category === 'CAD操作') return 'primary'
|
||||
if (category === '软件控制') return 'success'
|
||||
if (category === '系统服务') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
const getCategoryIconClass = (category) => {
|
||||
if (category === 'CAD操作') return 'fas fa-cogs'
|
||||
if (category === '软件控制') return 'fas fa-desktop'
|
||||
if (category === '系统服务') return 'fas fa-server'
|
||||
return 'fas fa-gear'
|
||||
}
|
||||
|
||||
const hasExtraData = (log) => {
|
||||
const omitKeys = ['id', 'reportId', 'log_id', 'operation', 'operation_category', 'log_type', 'timestamp', 'createdAt', 'created_at', 'status', 'duration', 'details', 'errorInfo', 'error_message', 'user_id', 'client_id', 'client_ip', 'ip_address', 'resultMetrics', 'featureDetails', 'comparisonData', 'level', 'action_type']
|
||||
return Object.keys(log).some(key => !omitKeys.includes(key))
|
||||
}
|
||||
|
||||
const formatExtraData = (log) => {
|
||||
const omitKeys = ['id', 'reportId', 'log_id', 'operation', 'operation_category', 'log_type', 'timestamp', 'createdAt', 'created_at', 'status', 'duration', 'details', 'errorInfo', 'error_message', 'user_id', 'client_id', 'client_ip', 'ip_address', 'resultMetrics', 'featureDetails', 'comparisonData', 'level', 'action_type']
|
||||
const extra = {}
|
||||
Object.keys(log).forEach(key => {
|
||||
if (!omitKeys.includes(key)) extra[key] = log[key]
|
||||
})
|
||||
return JSON.stringify(extra, null, 2)
|
||||
}
|
||||
|
||||
const exportSelectedReport = () => {
|
||||
if (!currentReport.value) return ElMessage.warning('没有可导出的报告')
|
||||
// 模拟导出 PDF/JSON
|
||||
@ -878,6 +1015,44 @@ const exportSelectedReport = () => {
|
||||
background: rgba(var(--color-success-rgb), 0.05);
|
||||
}
|
||||
|
||||
.upload-box.baseline-source {
|
||||
cursor: default;
|
||||
border-style: solid;
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(var(--color-primary-rgb), 0.02);
|
||||
}
|
||||
|
||||
.baseline-source .status-indicator {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: rgba(var(--color-primary-rgb), 0.1);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.baseline-source .status-indicator i {
|
||||
font-size: 28px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.baseline-source .source-title {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.baseline-source .source-desc {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-tertiary);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
.upload-box i {
|
||||
font-size: 48px;
|
||||
color: var(--color-text-tertiary);
|
||||
@ -1029,4 +1204,144 @@ const exportSelectedReport = () => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 新增美化:执行概览 */
|
||||
.overview-custom-header {
|
||||
padding: 16px 24px !important;
|
||||
background: var(--color-bg-primary) !important;
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 12px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.report-id-text {
|
||||
font-family: var(--font-family-mono);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.styled-body {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.overview-title-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px dashed var(--color-border-primary);
|
||||
}
|
||||
|
||||
.overview-title-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.overview-title-icon.icon-primary { background: rgba(var(--color-primary-rgb), 0.1); color: var(--color-primary); }
|
||||
.overview-title-icon.icon-success { background: rgba(var(--color-success-rgb), 0.1); color: var(--color-success); }
|
||||
.overview-title-icon.icon-warning { background: rgba(var(--color-warning-rgb), 0.1); color: var(--color-warning); }
|
||||
.overview-title-icon.icon-info { background: rgba(255, 255, 255, 0.05); color: var(--color-text-secondary); }
|
||||
|
||||
.overview-title-text {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.overview-info-card {
|
||||
border-radius: var(--size-border-radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label.el-descriptions__cell.is-bordered-label) {
|
||||
background-color: var(--color-bg-tertiary, #2b2b2b);
|
||||
color: var(--color-text-primary);
|
||||
border-color: var(--color-border-primary);
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__content.el-descriptions__cell.is-bordered-content) {
|
||||
background-color: var(--color-bg-secondary, #1f1f1f);
|
||||
color: var(--color-text-secondary);
|
||||
border-color: var(--color-border-primary);
|
||||
}
|
||||
|
||||
:deep(.el-descriptions) {
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--size-border-radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions .el-descriptions__table) {
|
||||
border-radius: var(--size-border-radius-lg);
|
||||
}
|
||||
|
||||
.desc-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: var(--font-family-mono);
|
||||
color: var(--color-text-regular);
|
||||
}
|
||||
|
||||
.custom-alert-box {
|
||||
border-radius: var(--size-border-radius-lg);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
padding: 16px;
|
||||
align-items: flex-start;
|
||||
background-color: rgba(var(--color-primary-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--color-primary-rgb), 0.2);
|
||||
}
|
||||
|
||||
:deep(.custom-alert-box .el-alert__title) {
|
||||
color: var(--color-text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.json-viewer-box {
|
||||
background: #1e1e1e;
|
||||
border-radius: var(--size-border-radius-lg);
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.json-viewer-box pre {
|
||||
margin: 0;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #9cdcfe;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -97,6 +97,22 @@ export const useCADStore = defineStore('cad', () => {
|
||||
draggedFile.value = file
|
||||
}
|
||||
|
||||
// 实时穿透对比状态 (全局保留)
|
||||
const liveComparisonState = ref({
|
||||
step: 1, // 1: 准备抓取基准, 2: 已抓取基准(等待抓取结果), 3: 完成对比
|
||||
baselineData: null,
|
||||
resultData: null
|
||||
})
|
||||
|
||||
// 重置实时穿透对比状态
|
||||
const resetLiveComparison = () => {
|
||||
liveComparisonState.value = {
|
||||
step: 1,
|
||||
baselineData: null,
|
||||
resultData: null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
cadConnections,
|
||||
@ -111,6 +127,8 @@ export const useCADStore = defineStore('cad', () => {
|
||||
setCADConnection,
|
||||
setCADConnecting,
|
||||
setCurrentProjectName,
|
||||
setDraggedFile
|
||||
setDraggedFile,
|
||||
liveComparisonState,
|
||||
resetLiveComparison
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user