x
This commit is contained in:
+209
-1
@@ -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
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
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">
|
||||
|
||||
Reference in New Issue
Block a user