This commit is contained in:
2026-05-15 16:42:18 +08:00
parent 6bd595bd63
commit 9f6384828c
2 changed files with 439 additions and 249 deletions
+209 -1
View File
@@ -1364,6 +1364,74 @@ optgroup {
gap: var(--space-4);
}
.mechanism-visual-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: var(--space-4);
margin-top: var(--space-4);
}
.mechanism-visual-card {
display: grid;
grid-template-columns: 220px minmax(0, 1fr);
gap: 0;
border: 1px solid var(--border-light);
border-radius: var(--radius-xl);
background: var(--bg-primary);
overflow: hidden;
}
.mechanism-visual-svg {
background: linear-gradient(180deg, #eff6ff 0%, #f8fafc 100%);
border-right: 1px solid var(--border-light);
display: flex;
align-items: center;
justify-content: center;
min-height: 180px;
}
.mechanism-svg {
width: 100%;
height: 100%;
}
.mechanism-visual-body {
padding: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.mechanism-kpis {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-3);
}
.mechanism-kpi {
padding: var(--space-3);
border-radius: var(--radius-lg);
background: var(--bg-secondary);
border: 1px solid var(--border-light);
}
.mechanism-kpi-label {
display: block;
font-size: var(--text-xs);
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: var(--space-1);
}
.mechanism-kpi-value {
display: block;
font-size: var(--text-sm);
font-weight: var(--font-semibold);
color: var(--text-primary);
word-break: break-word;
}
.mechanism-card {
border: 1px solid var(--border-light);
border-radius: var(--radius-lg);
@@ -1392,7 +1460,7 @@ optgroup {
}
.mechanism-actions {
margin-top: var(--space-4);
margin-top: auto;
display: flex;
justify-content: flex-end;
}
@@ -1480,6 +1548,19 @@ optgroup {
.info-list {
grid-template-columns: 1fr;
}
.mechanism-visual-card {
grid-template-columns: 1fr;
}
.mechanism-visual-svg {
border-right: 0;
border-bottom: 1px solid var(--border-light);
}
.mechanism-kpis {
grid-template-columns: 1fr;
}
}
.info-item {
@@ -1586,6 +1667,11 @@ optgroup {
background: var(--warning-bg);
}
.recommendation-item.critical {
border-left-color: var(--error);
background: #fef2f2;
}
.recommendation-item.medium {
border-left-color: var(--info);
background: var(--info-bg);
@@ -1612,6 +1698,11 @@ optgroup {
color: white;
}
.priority-badge.critical {
background: var(--error);
color: white;
}
.priority-badge.medium {
background: var(--info);
color: white;
@@ -1628,6 +1719,123 @@ optgroup {
color: var(--text-primary);
}
.dfm-card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: var(--space-4);
}
.dfm-card {
border-radius: var(--radius-xl);
border: 1px solid var(--border-light);
padding: var(--space-4);
background: var(--bg-primary);
}
.dfm-card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
margin-bottom: var(--space-3);
}
.dfm-card-message {
font-size: var(--text-sm);
color: var(--text-primary);
line-height: 1.7;
}
.dfm-card-hint {
margin-top: var(--space-3);
font-size: var(--text-xs);
color: var(--text-tertiary);
}
.dfm-level-critical {
border-color: #fecaca;
background: #fef2f2;
}
.dfm-level-high {
border-color: #fde68a;
background: #fff7ed;
}
.dfm-level-medium {
border-color: #bfdbfe;
background: #eff6ff;
}
.dfm-level-low {
border-color: #bbf7d0;
background: #f0fdf4;
}
.markdown-preview {
color: var(--text-secondary);
font-size: var(--text-sm);
line-height: 1.75;
}
.markdown-preview h1,
.markdown-preview h2,
.markdown-preview h3,
.markdown-preview h4,
.markdown-preview h5,
.markdown-preview h6 {
color: var(--text-primary);
margin: 0 0 var(--space-3);
}
.markdown-preview p,
.markdown-preview ul,
.markdown-preview ol,
.markdown-preview blockquote,
.markdown-preview pre {
margin: 0 0 var(--space-4);
}
.markdown-preview ul,
.markdown-preview ol {
padding-left: 1.25rem;
}
.markdown-preview code {
font-family: var(--font-mono);
background: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: var(--radius-sm);
padding: 0.1rem 0.35rem;
}
.markdown-preview pre {
padding: var(--space-4);
border-radius: var(--radius-lg);
background: var(--gray-900);
overflow-x: auto;
}
.markdown-preview pre code {
background: transparent;
border: 0;
color: #f8fafc;
padding: 0;
}
.markdown-preview blockquote {
padding: var(--space-3) var(--space-4);
border-left: 4px solid var(--primary-300);
background: var(--primary-50);
border-radius: var(--radius-md);
color: var(--text-secondary);
}
.markdown-preview a {
color: var(--primary-700);
text-decoration: underline;
}
.recommendation-reason {
font-size: var(--text-xs);
color: var(--text-tertiary);
+230 -248
View File
@@ -1520,6 +1520,24 @@ const ResultView = {
const sliderMechanisms = computed(() => selectedSideActions.value?.slider_mechanisms || []);
const lifterMechanisms = computed(() => selectedSideActions.value?.lifter_mechanisms || []);
const sideActionRecommendations = computed(() => selectedSideActions.value?.recommendations || []);
const allMechanisms = computed(() => {
const sliders = sliderMechanisms.value.map((item, index) => ({ type: 'slider', index, item }));
const lifters = lifterMechanisms.value.map((item, index) => ({ type: 'lifter', index, item }));
return [...sliders, ...lifters];
});
const dfmLevelSummary = computed(() => {
const base = { critical: 0, high: 0, medium: 0, low: 0 };
(selectedDfmViolations.value || []).forEach((item) => {
const level = String(item.level || 'medium').toLowerCase();
if (base[level] === undefined) {
base.medium += 1;
} else {
base[level] += 1;
}
});
return base;
});
const llmReportHtml = computed(() => renderMarkdownToHtml(state.task?.llm_report || ''));
const stageTimingEntries = computed(() => {
const timings = state.task?.stage_timings || {};
return Object.entries(timings)
@@ -1553,8 +1571,7 @@ const ResultView = {
{ id: 'side-actions', label: '抽芯建议' },
{ id: 'dfm-check', label: 'DFM' },
{ id: 'export-cam', label: '导出/CAM' },
{ id: 'analysis-details', label: '分析详情' },
{ id: 'diagnostic-info', label: '诊断' }
{ id: 'llm-report', label: '设计报告' }
];
const selectScheme = (schemeId) => {
@@ -1627,6 +1644,138 @@ const ResultView = {
if (type === 'lifter') return '斜顶抽芯';
return type || '机构';
};
const formatMechanismTypeShort = (type) => type === 'slider' ? '滑块' : type === 'lifter' ? '斜顶' : '机构';
const normalizeVector = (vector, fallback = [0.8, -0.6, 0.5]) => {
if (!Array.isArray(vector) || vector.length < 3) return fallback;
const values = vector.map(v => Number(v) || 0);
const length = Math.sqrt(values.reduce((acc, val) => acc + val * val, 0));
if (!length) return fallback;
return values.map(v => v / length);
};
const mechanismSchematicSvg = (type, item = {}) => {
const direction = normalizeVector(type === 'slider' ? item.slide_direction : item.location);
const location = normalizeVector(item.location, [0.3, 0.2, 0.5]);
const startX = 120 + (location[0] * 18);
const startY = 90 - (location[1] * 16);
const endX = startX + (direction[0] * 42);
const endY = startY - (direction[2] * 28) + (direction[1] * 12);
const mechanismColor = type === 'slider' ? '#2563eb' : '#7c3aed';
const label = type === 'slider' ? '滑块抽芯' : '斜顶抽芯';
return `
<svg viewBox="0 0 240 180" class="mechanism-svg" xmlns="http://www.w3.org/2000/svg" aria-label="${label}三维示意图">
<defs>
<linearGradient id="cubeTop" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#f8fafc" />
<stop offset="100%" stop-color="#dbeafe" />
</linearGradient>
<linearGradient id="cubeSide" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#cbd5e1" />
<stop offset="100%" stop-color="#94a3b8" />
</linearGradient>
<marker id="arrowHead" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="${mechanismColor}" />
</marker>
</defs>
<rect x="22" y="24" width="196" height="132" rx="18" fill="#f8fafc" stroke="#dbeafe" />
<polygon points="72,60 132,38 184,62 124,84" fill="url(#cubeTop)" stroke="#94a3b8" />
<polygon points="124,84 184,62 184,118 124,140" fill="url(#cubeSide)" stroke="#94a3b8" />
<polygon points="72,60 124,84 124,140 72,116" fill="#e2e8f0" stroke="#94a3b8" />
<circle cx="${startX.toFixed(1)}" cy="${startY.toFixed(1)}" r="8" fill="${mechanismColor}" fill-opacity="0.92" />
<line x1="${startX.toFixed(1)}" y1="${startY.toFixed(1)}" x2="${endX.toFixed(1)}" y2="${endY.toFixed(1)}" stroke="${mechanismColor}" stroke-width="5" stroke-linecap="round" marker-end="url(#arrowHead)" />
<text x="28" y="42" fill="#0f172a" font-size="12" font-weight="700">${label}</text>
<text x="28" y="58" fill="#475569" font-size="11">基于机构方向与位置生成的三维示意</text>
</svg>
`;
};
const escapeHtml = (value) => String(value || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
const applyInlineMarkdown = (text) => {
let html = escapeHtml(text);
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
return html;
};
const renderMarkdownToHtml = (markdown) => {
const source = String(markdown || '').replace(/\r\n/g, '\n').trim();
if (!source) return '';
const lines = source.split('\n');
const html = [];
let inList = false;
let inCode = false;
let codeBuffer = [];
const closeList = () => {
if (inList) {
html.push('</ul>');
inList = false;
}
};
const closeCode = () => {
if (inCode) {
html.push(`<pre><code>${escapeHtml(codeBuffer.join('\n'))}</code></pre>`);
inCode = false;
codeBuffer = [];
}
};
lines.forEach((rawLine) => {
const line = rawLine.trimEnd();
if (line.startsWith('```')) {
closeList();
if (inCode) {
closeCode();
} else {
inCode = true;
codeBuffer = [];
}
return;
}
if (inCode) {
codeBuffer.push(rawLine);
return;
}
const trimmed = line.trim();
if (!trimmed) {
closeList();
html.push('');
return;
}
const headingMatch = trimmed.match(/^(#{1,6})\s+(.*)$/);
if (headingMatch) {
closeList();
const level = headingMatch[1].length;
html.push(`<h${level}>${applyInlineMarkdown(headingMatch[2])}</h${level}>`);
return;
}
if (/^[-*]\s+/.test(trimmed)) {
if (!inList) {
html.push('<ul>');
inList = true;
}
html.push(`<li>${applyInlineMarkdown(trimmed.replace(/^[-*]\s+/, ''))}</li>`);
return;
}
if (/^\d+\.\s+/.test(trimmed)) {
closeList();
html.push(`<p>${applyInlineMarkdown(trimmed)}</p>`);
return;
}
if (/^>\s?/.test(trimmed)) {
closeList();
html.push(`<blockquote>${applyInlineMarkdown(trimmed.replace(/^>\s?/, ''))}</blockquote>`);
return;
}
closeList();
html.push(`<p>${applyInlineMarkdown(trimmed)}</p>`);
});
closeList();
closeCode();
return html.filter(Boolean).join('\n');
};
const openMechanismDetail = (type, index, item) => {
state.activeMechanismDetail = {
type,
@@ -1736,6 +1885,9 @@ const ResultView = {
sliderMechanisms,
lifterMechanisms,
sideActionRecommendations,
allMechanisms,
dfmLevelSummary,
llmReportHtml,
stageTimingEntries,
getWallThicknessSummary,
countFeaturesByTypes,
@@ -1745,6 +1897,8 @@ const ResultView = {
formatVector,
formatMechanismSize,
formatMechanismType,
formatMechanismTypeShort,
mechanismSchematicSvg,
openMechanismDetail,
closeMechanismDetail,
primaryRecommendation,
@@ -2169,63 +2323,37 @@ const ResultView = {
</div>
</div>
</div>
</div>
<div class="result-card" v-if="sliderMechanisms.length">
<h4>滑块抽芯</h4>
<div class="mechanism-list">
<div class="mechanism-card" v-for="(item, idx) in sliderMechanisms" :key="'slider-' + idx">
<div class="mechanism-title">滑块 {{ idx + 1 }}</div>
<div class="info-list">
<div class="info-item">
<span class="info-label">抽芯方向</span>
<span class="info-value">{{ formatVector(item.slide_direction) }}</span>
</div>
<div class="info-item">
<span class="info-label">抽芯行程</span>
<span class="info-value">{{ item.slide_stroke || 'N/A' }} mm</span>
</div>
<div class="info-item">
<span class="info-label">抽芯角度</span>
<span class="info-value">{{ item.slide_angle || 'N/A' }}°</span>
</div>
<div class="info-item">
<span class="info-label">驱动方式</span>
<span class="info-value">{{ item.actuation || 'N/A' }}</span>
</div>
<div class="mechanism-visual-grid" v-if="allMechanisms.length">
<div class="mechanism-visual-card" v-for="mechanism in allMechanisms" :key="mechanism.type + '-' + mechanism.index">
<div class="mechanism-visual-svg" v-html="mechanismSchematicSvg(mechanism.type, mechanism.item)"></div>
<div class="mechanism-visual-body">
<div class="mechanism-title">{{ formatMechanismType(mechanism.type) }} {{ mechanism.index + 1 }}</div>
<div class="mechanism-kpis">
<div class="mechanism-kpi">
<span class="mechanism-kpi-label">位置</span>
<span class="mechanism-kpi-value">{{ formatVector(mechanism.item.location) }}</span>
</div>
<div class="mechanism-actions">
<button class="btn-sm btn-secondary" @click="openMechanismDetail('slider', idx, item)">查看详情</button>
<div class="mechanism-kpi" v-if="mechanism.type === 'slider'">
<span class="mechanism-kpi-label">行程</span>
<span class="mechanism-kpi-value">{{ mechanism.item.slide_stroke || 'N/A' }} mm</span>
</div>
<div class="mechanism-kpi" v-if="mechanism.type === 'slider'">
<span class="mechanism-kpi-label">方向</span>
<span class="mechanism-kpi-value">{{ formatVector(mechanism.item.slide_direction) }}</span>
</div>
<div class="mechanism-kpi" v-if="mechanism.type === 'lifter'">
<span class="mechanism-kpi-label">角度</span>
<span class="mechanism-kpi-value">{{ mechanism.item.lifter_angle || 'N/A' }}°</span>
</div>
<div class="mechanism-kpi" v-if="mechanism.type === 'lifter'">
<span class="mechanism-kpi-label">行程</span>
<span class="mechanism-kpi-value">{{ mechanism.item.lifter_stroke || 'N/A' }} mm</span>
</div>
</div>
</div>
</div>
<div class="result-card" v-if="lifterMechanisms.length">
<h4>斜顶抽芯</h4>
<div class="mechanism-list">
<div class="mechanism-card" v-for="(item, idx) in lifterMechanisms" :key="'lifter-' + idx">
<div class="mechanism-title">斜顶 {{ idx + 1 }}</div>
<div class="info-list">
<div class="info-item">
<span class="info-label">斜顶角度</span>
<span class="info-value">{{ item.lifter_angle || 'N/A' }}°</span>
</div>
<div class="info-item">
<span class="info-label">斜顶行程</span>
<span class="info-value">{{ item.lifter_stroke || 'N/A' }} mm</span>
</div>
<div class="info-item">
<span class="info-label">回位方式</span>
<span class="info-value">{{ item.return_mechanism || 'N/A' }}</span>
</div>
<div class="info-item">
<span class="info-label">导向方式</span>
<span class="info-value">{{ item.guide_type || 'N/A' }}</span>
</div>
</div>
<div class="mechanism-actions">
<button class="btn-sm btn-secondary" @click="openMechanismDetail('lifter', idx, item)">查看详情</button>
</div>
<div class="mechanism-actions">
<button class="btn-sm btn-secondary" @click="openMechanismDetail(mechanism.type, mechanism.index, mechanism.item)">查看详情</button>
</div>
</div>
</div>
@@ -2243,27 +2371,53 @@ const ResultView = {
<div id="dfm-check" class="viewer-section">
<h3>DFM 检查</h3>
<div class="result-grid" v-if="selectedDfmViolations.length">
<div class="result-card">
<h4>风险概览</h4>
<div class="info-list">
<div class="info-item">
<span class="info-label">总问题数</span>
<span class="info-value">{{ selectedDfmViolations.length }}</span>
</div>
<div class="info-item">
<span class="info-label">高风险</span>
<span class="info-value">{{ dfmLevelSummary.critical + dfmLevelSummary.high }}</span>
</div>
<div class="info-item">
<span class="info-label">中风险</span>
<span class="info-value">{{ dfmLevelSummary.medium }}</span>
</div>
<div class="info-item">
<span class="info-label">低风险</span>
<span class="info-value">{{ dfmLevelSummary.low }}</span>
</div>
</div>
</div>
<div class="result-card">
<h4>重点判断</h4>
<div class="recommendations-list">
<div class="recommendation-item high" v-if="dfmLevelSummary.critical + dfmLevelSummary.high > 0">
<div class="recommendation-text">当前方案存在较高工艺风险,建议优先调整壁厚、倒扣或分型方式后再进入制造准备。</div>
</div>
<div class="recommendation-item medium" v-else>
<div class="recommendation-text">当前方案未发现显著高风险项,可结合 3D 预览和抽芯建议继续做工程评审。</div>
</div>
</div>
</div>
</div>
<div v-if="!selectedDfmViolations.length" class="inline-alert inline-alert-warning">
<div class="inline-alert-title">未发现显著 DFM 违规项</div>
<div class="inline-alert-message">当前选中方案未命中高风险规则,可进入下一步工艺评审。</div>
</div>
<div v-else class="result-card full-width">
<table class="data-table">
<thead>
<tr>
<th>规则</th>
<th>级别</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, idx) in selectedDfmViolations" :key="'dfm-' + idx">
<td>{{ item.rule || 'N/A' }}</td>
<td>{{ item.level || 'medium' }}</td>
<td>{{ item.message || 'N/A' }}</td>
</tr>
</tbody>
</table>
<div v-else class="dfm-card-list">
<div class="dfm-card" v-for="(item, idx) in selectedDfmViolations" :key="'dfm-' + idx" :class="'dfm-level-' + (item.level || 'medium')">
<div class="dfm-card-header">
<div class="mechanism-title">{{ item.rule || 'DFM 规则' }}</div>
<span class="priority-badge" :class="(item.level || 'medium').toLowerCase()">{{ getPriorityText((item.level || 'medium').toLowerCase()) }}</span>
</div>
<div class="dfm-card-message">{{ item.message || 'N/A' }}</div>
<div class="dfm-card-hint">建议结合上方 3D 预览与抽芯示意定位具体风险区域。</div>
</div>
</div>
</div>
@@ -2380,185 +2534,13 @@ const ResultView = {
</template>
</div>
<div id="analysis-details" v-if="state.task.analysis_result" class="viewer-section">
<h3>分析结果详情</h3>
<div class="analysis-summary" v-if="state.task.analysis_result.analysis_summary">
<div class="summary-header">
<h4>分析摘要</h4>
<span class="summary-icon">📋</span>
</div>
<div class="summary-content">
{{ state.task.analysis_result.analysis_summary }}
</div>
</div>
<div class="result-grid">
<div class="result-card full-width">
<h4>几何与特征摘要</h4>
<div class="info-list">
<div class="info-item">
<span class="info-label">整体轮廓和尺寸比例</span>
<span class="info-value">{{ state.task.analysis_result?.geometry_data?.bounding_box?.dimensions ?
(state.task.analysis_result.geometry_data.bounding_box.dimensions[0]?.toFixed(1) || 0) + ' × ' +
(state.task.analysis_result.geometry_data.bounding_box.dimensions[1]?.toFixed(1) || 0) + ' × ' +
(state.task.analysis_result.geometry_data.bounding_box.dimensions[2]?.toFixed(1) || 0) + ' mm' : 'N/A' }}</span>
</div>
<div class="info-item">
<span class="info-label">壁厚分布</span>
<span class="info-value">{{ getWallThicknessSummary() }}</span>
</div>
<div class="info-item">
<span class="info-label">加强筋位置和密度</span>
<span class="info-value">{{ countFeaturesByTypes('rib_structure') || 0 }} 个加强筋</span>
</div>
<div class="info-item">
<span class="info-label">柱位和功能特征</span>
<span class="info-value">{{ countFeaturesByTypes('boss_feature') || 0 }} 个柱位/功能特征</span>
</div>
<div class="info-item">
<span class="info-label">倒扣区域检测</span>
<span class="info-value">{{ getUndercutCount() }} 个倒扣区域</span>
</div>
<div class="info-item">
<span class="info-label">对称性分析</span>
<span class="info-value">{{ state.task.analysis_result.geometry_data?.symmetry || '非对称' }}</span>
</div>
<div class="info-item">
<span class="info-label">重心位置</span>
<span class="info-value">{{ state.task.analysis_result.geometry_data?.center_of_mass ?
'(' + (state.task.analysis_result.geometry_data.center_of_mass[0]?.toFixed(2) || 0) + ', ' +
(state.task.analysis_result.geometry_data.center_of_mass[1]?.toFixed(2) || 0) + ', ' +
(state.task.analysis_result.geometry_data.center_of_mass[2]?.toFixed(2) || 0) + ') mm' : 'N/A' }}</span>
</div>
</div>
</div>
<div class="result-card full-width">
<h4>工程建议与质量指标</h4>
<div class="info-list">
<div class="info-item">
<span class="info-label">高优先级工艺建议</span>
<span class="info-value">{{ state.task.analysis_result.design_recommendations?.find(r => r.priority === 'high')?.description || '根据几何分析自动生成' }}</span>
</div>
<div class="info-item">
<span class="info-label">加强筋布局</span>
<span class="info-value">{{ countFeaturesByTypes('rib_structure') ? '已识别加强筋区域,建议校核厚度和脱模方向' : '当前未识别明显加强筋特征' }}</span>
</div>
<div class="info-item">
<span class="info-label">浇口与进胶提示</span>
<span class="info-value">{{ state.task.analysis_result.design_recommendations?.find(r => r.type === 'wall_thickness' || r.type === 'draft_angle')?.reason || '建议结合主分型方向确认浇口位置' }}</span>
</div>
<div class="info-item">
<span class="info-label">冷却设计提示</span>
<span class="info-value">{{ getWallThicknessSummary() !== '待分析' ? '建议按壁厚分布校核冷却均匀性' : '需先完成壁厚分析后再校核冷却' }}</span>
</div>
<div class="info-item">
<span class="info-label">定位结构设计</span>
<span class="info-value">{{ countFeaturesByTypes('boss_feature') || 0 }} 个柱位/定位相关特征</span>
</div>
<div class="info-item">
<span class="info-label">分型面选择</span>
<span class="info-value">基于产品几何:自动推荐最优分型面</span>
</div>
<div class="info-item" v-if="selectedCavityData?.manufacturing_info?.estimated_mold_size">
<span class="info-label">模架尺寸</span>
<span class="info-value">{{ (selectedCavityData.manufacturing_info.estimated_mold_size.length || 0) + ' × ' + (selectedCavityData.manufacturing_info.estimated_mold_size.width || 0) + ' × ' + (selectedCavityData.manufacturing_info.estimated_mold_size.height || 0) + ' mm' }}</span>
</div>
<div class="info-item" v-if="state.task.analysis_result.quality_metrics">
<span class="info-label">体积利用率</span>
<span class="info-value">{{ (state.task.analysis_result.quality_metrics.volume_utilization * 100)?.toFixed(1) || 'N/A' }}%</span>
</div>
<div class="info-item" v-if="state.task.analysis_result.quality_metrics">
<span class="info-label">拓扑复杂度</span>
<span class="info-value">{{ state.task.analysis_result.quality_metrics.topology_complexity?.toFixed(2) || 'N/A' }}</span>
</div>
<div class="info-item" v-if="state.task.analysis_result.quality_metrics">
<span class="info-label">壁厚均匀性</span>
<span class="info-value">{{ (state.task.analysis_result.quality_metrics.wall_uniformity * 100)?.toFixed(1) || 'N/A' }}%</span>
</div>
<div class="info-item" v-if="selectedKeyInfo?.quality_considerations">
<span class="info-label">翘曲风险</span>
<span class="info-value">{{ selectedKeyInfo.quality_considerations.warpage_risk || '低风险' }}</span>
</div>
<div class="info-item" v-if="selectedKeyInfo?.quality_considerations">
<span class="info-label">潜在焊缝线</span>
<span class="info-value">{{ selectedKeyInfo.quality_considerations.potential_weld_lines || 0 }} 条</span>
</div>
<div class="recommendations-section" v-if="sortedRecommendations.length">
<h5>详细设计建议</h5>
<div class="recommendations-list">
<div v-for="rec in sortedRecommendations" :key="rec.type" class="recommendation-item" :class="rec.priority">
<span class="priority-badge" :class="rec.priority">{{ getPriorityText(rec.priority) }}</span>
<span class="recommendation-text">{{ rec.description }}</span>
<span class="recommendation-reason" v-if="rec.reason">({{ rec.reason }})</span>
</div>
</div>
</div>
</div>
</div>
<div id="llm-report" class="viewer-section" v-if="state.task.llm_report">
<h3>LLM 设计报告</h3>
<div class="result-card">
<div class="markdown-preview" v-html="llmReportHtml"></div>
</div>
</div>
<details id="diagnostic-info" class="viewer-section diagnostic-panel" v-if="stageTimingEntries.length || state.task.verification || state.task.llm_report">
<summary>诊断信息</summary>
<div class="diagnostic-panel-body">
<div class="result-card full-width" v-if="stageTimingEntries.length">
<h4>处理耗时</h4>
<table class="data-table">
<thead>
<tr>
<th>阶段</th>
<th>耗时</th>
</tr>
</thead>
<tbody>
<tr v-for="[stage, duration] in stageTimingEntries" :key="'timing-' + stage">
<td>{{ formatStageName(stage) }}</td>
<td>{{ formatTiming(duration) }}</td>
</tr>
</tbody>
</table>
</div>
<div v-if="state.task.verification" class="verification-section">
<div class="verification-summary">
<div class="summary-header">
<h4>FreeCAD 几何验证</h4>
<span :class="['badge', state.task.verification.status === 'passed' ? 'badge-success' : state.task.verification.status === 'failed' ? 'badge-error' : state.task.verification.status === 'error' ? 'badge-warning' : 'badge-info']">
{{ state.task.verification.status === 'passed' ? '验证通过' : state.task.verification.status === 'failed' ? '验证失败' : state.task.verification.status === 'error' ? '验证错误' : state.task.verification.status === 'skipped' ? '已跳过' : '未知' }}
</span>
</div>
<div class="verification-details" v-if="state.task.verification.comparison && (state.task.verification.comparison.volume || state.task.verification.comparison.surface_area)">
<div class="comparison-grid">
<div class="comparison-item" v-if="state.task.verification.comparison.volume">
<span class="comparison-label">体积差异</span>
<span class="comparison-value" :class="{'text-error': state.task.verification.comparison.volume.difference_percent > 1, 'text-success': state.task.verification.comparison.volume.difference_percent <= 1}">
{{ state.task.verification.comparison.volume.difference_percent?.toFixed(4) || 0 }}%
</span>
</div>
<div class="comparison-item" v-if="state.task.verification.comparison.surface_area">
<span class="comparison-label">表面积差异</span>
<span class="comparison-value" :class="{'text-error': state.task.verification.comparison.surface_area.difference_percent > 2, 'text-success': state.task.verification.comparison.surface_area.difference_percent <= 2}">
{{ state.task.verification.comparison.surface_area.difference_percent?.toFixed(4) || 0 }}%
</span>
</div>
</div>
</div>
</div>
</div>
<div class="analysis-summary" v-if="state.task.llm_report">
<div class="summary-header">
<h4>LLM 设计报告</h4>
</div>
<div class="summary-content">
<pre style="white-space: pre-wrap; margin: 0;">{{ state.task.llm_report }}</pre>
</div>
</div>
</div>
</details>
<div v-if="state.activeMechanismDetail" class="modal-overlay" @click.self="closeMechanismDetail">
<div class="modal-content mechanism-modal">
<div class="modal-header">