This commit is contained in:
2026-05-02 00:39:04 +08:00
parent f9acdb767b
commit d1a3f22785
16 changed files with 1181 additions and 113 deletions
+283 -5
View File
@@ -1221,9 +1221,36 @@ const ResultView = {
task: null,
loading: true,
error: '',
selectedSchemeId: null
selectedSchemeId: null,
previewStatus: 'idle',
camLoading: false,
camError: '',
camPlan: null,
camForm: {
mold_steel: 'P20',
surface_quality: 'standard',
controller: 'fanuc',
include_gcode: false
}
});
const camSteelOptions = [
{ value: 'P20', label: 'P20' },
{ value: '718H', label: '718H' },
{ value: 'NAK80', label: 'NAK80' },
{ value: 'S136', label: 'S136' },
{ value: 'H13', label: 'H13' }
];
const camSurfaceOptions = [
{ value: 'standard', label: '标准' },
{ value: 'fine', label: '精细' },
{ value: 'mirror', label: '镜面' }
];
const camControllerOptions = [
{ value: 'fanuc', label: 'Fanuc' },
{ value: 'siemens', label: 'Siemens' }
];
const loadTask = async () => {
console.log('[ResultView] loadTask 开始, taskId:', route.params.taskId);
try {
@@ -1238,6 +1265,12 @@ const ResultView = {
} else if (state.task?.candidate_schemes?.length) {
state.selectedSchemeId = state.task.candidate_schemes[0].scheme_id;
}
const prefs = state.task?.cam_preferences || {};
state.camForm.mold_steel = prefs.mold_steel || 'P20';
state.camForm.surface_quality = prefs.surface_quality || 'standard';
state.camForm.controller = prefs.controller || 'fanuc';
state.camForm.include_gcode = Boolean(prefs.include_gcode || false);
state.previewStatus = 'loading';
} catch (e) {
state.error = handleApiError(e, '加载任务详情');
} finally {
@@ -1268,6 +1301,7 @@ const ResultView = {
};
const candidateSchemes = computed(() => state.task?.candidate_schemes || []);
const hasSingleScheme = computed(() => candidateSchemes.value.length === 1);
const selectedScheme = computed(() => {
if (!candidateSchemes.value.length) return null;
return candidateSchemes.value.find(s => s.scheme_id === state.selectedSchemeId) || candidateSchemes.value[0];
@@ -1275,9 +1309,21 @@ const ResultView = {
const selectedCavityData = computed(() => selectedScheme.value?.cavity_data || state.task?.cavity_data || null);
const selectedKeyInfo = computed(() => selectedScheme.value?.key_info || state.task?.key_info || null);
const selectedHtmlFile = computed(() => selectedScheme.value?.html_file || state.task?.html_file || '');
const selectedDfmViolations = computed(() => selectedScheme.value?.dfm_violations || []);
const selectScheme = (schemeId) => {
state.selectedSchemeId = schemeId;
state.previewStatus = 'loading';
state.camPlan = null;
state.camError = '';
};
const onPreviewLoad = () => {
state.previewStatus = 'loaded';
};
const onPreviewError = () => {
state.previewStatus = 'error';
};
const formatSchemeDirection = (scheme) => {
@@ -1319,21 +1365,64 @@ const ResultView = {
addNotification(`导出失败: ${e.message}`, 'error');
}
};
const generateCamPlan = async () => {
try {
state.camLoading = true;
state.camError = '';
const taskId = route.params.taskId;
const payload = {
task_id: taskId,
scheme_id: selectedScheme.value?.scheme_id,
mold_steel: state.camForm.mold_steel,
surface_quality: state.camForm.surface_quality,
controller: state.camForm.controller,
include_gcode: state.camForm.include_gcode
};
const result = await apiRequest('/api/cam/plan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
state.camPlan = result?.data || null;
if (result?.cam_preferences) {
state.camForm.mold_steel = result.cam_preferences.mold_steel || state.camForm.mold_steel;
state.camForm.surface_quality = result.cam_preferences.surface_quality || state.camForm.surface_quality;
state.camForm.controller = result.cam_preferences.controller || state.camForm.controller;
state.camForm.include_gcode = Boolean(result.cam_preferences.include_gcode);
}
addNotification('CAM 计划生成完成', 'success');
} catch (e) {
state.camPlan = null;
state.camError = e.message || 'CAM 计划生成失败';
addNotification(`CAM 计划生成失败: ${state.camError}`, 'error');
} finally {
state.camLoading = false;
}
};
return {
state,
candidateSchemes,
hasSingleScheme,
selectedScheme,
selectedCavityData,
selectedKeyInfo,
selectedHtmlFile,
selectedDfmViolations,
formatFileSize,
formatDateTime,
formatNumber,
getPriorityText,
isFoamMaterial,
exportCAD,
generateCamPlan,
camSteelOptions,
camSurfaceOptions,
camControllerOptions,
selectScheme,
onPreviewLoad,
onPreviewError,
formatSchemeDirection
};
},
@@ -1359,6 +1448,9 @@ const ResultView = {
{{ state.task.status }}
</span>
<div v-if="state.task.status === 'completed'" class="export-buttons">
<button class="btn-sm btn-primary" :disabled="state.camLoading" @click="generateCamPlan()" title="基于当前选中方案生成CAM工艺计划">
{{ state.camLoading ? '生成中...' : '生成 CAM 计划' }}
</button>
<button class="btn-sm btn-primary" @click="exportCAD('step')" title="导出STEP格式(UG/FreeCAD/SolidWorks通用)">
导出 STEP
</button>
@@ -1422,11 +1514,11 @@ const ResultView = {
<div v-if="candidateSchemes.length" class="viewer-section">
<h3>候选分模方案</h3>
<div class="result-grid">
<div :class="['result-grid', hasSingleScheme ? 'single-scheme-grid' : '']">
<div
v-for="scheme in candidateSchemes"
:key="scheme.scheme_id"
class="result-card"
:class="['result-card', hasSingleScheme ? 'single-scheme-card' : '']"
:style="state.selectedSchemeId === scheme.scheme_id ? 'border: 2px solid var(--primary-color);' : ''"
>
<div class="summary-header">
@@ -1438,6 +1530,10 @@ const ResultView = {
<span class="info-label">分型方向</span>
<span class="info-value">{{ formatSchemeDirection(scheme) }}</span>
</div>
<div class="info-item">
<span class="info-label">模具结构</span>
<span class="info-value">{{ scheme.mold_structure_type === 'two_half_cavity' ? '两板半腔(无独立模芯)' : '型腔 + 模芯' }}</span>
</div>
<div class="info-item">
<span class="info-label">分型面位置</span>
<span class="info-value">{{ scheme.offset_label || '中面' }}</span>
@@ -1470,8 +1566,25 @@ const ResultView = {
<span class="info-label">风险得分</span>
<span class="info-value">{{ formatNumber(scheme.score_breakdown?.risk || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">DFM 违规项</span>
<span class="info-value">{{ scheme.dfm_violation_count || 0 }}</span>
</div>
<div class="info-item">
<span class="info-label">方案可信度</span>
<span class="info-value">{{ formatNumber(scheme.confidence_score || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">回退标记</span>
<span class="info-value">{{ scheme.is_fallback ? '是' : '否' }}</span>
</div>
<div v-if="scheme.fallback_reason" class="info-item">
<span class="info-label">回退原因</span>
<span class="info-value">{{ scheme.fallback_reason }}</span>
</div>
</div>
<button class="btn-sm btn-primary" @click="selectScheme(scheme.scheme_id)">查看此方案</button>
<button v-if="candidateSchemes.length > 1" class="btn-sm btn-primary" @click="selectScheme(scheme.scheme_id)">查看此方案</button>
<div v-else class="inline-note">当前仅生成 1 套可用分模方案</div>
</div>
</div>
</div>
@@ -1486,6 +1599,8 @@ const ResultView = {
<th>方向</th>
<th>位置</th>
<th>总分</th>
<th>可信度</th>
<th>回退</th>
<th>法向匹配</th>
<th>可制造性</th>
<th>分型质量</th>
@@ -1499,6 +1614,8 @@ const ResultView = {
<td>{{ formatSchemeDirection(scheme) }}</td>
<td>{{ scheme.offset_label || '中面' }}</td>
<td>{{ formatNumber(scheme.score) }}</td>
<td>{{ formatNumber(scheme.confidence_score || 0) }}</td>
<td>{{ scheme.is_fallback ? '是' : '否' }}</td>
<td>{{ formatNumber(scheme.normal_alignment_score || 0) }}</td>
<td>{{ formatNumber(scheme.score_breakdown?.manufacturability || 0) }}</td>
<td>{{ formatNumber(scheme.score_breakdown?.parting_quality || 0) }}</td>
@@ -1524,6 +1641,10 @@ const ResultView = {
<span class="info-label">材料</span>
<span class="info-value">{{ selectedCavityData?.metadata?.material || selectedKeyInfo?.metadata?.material || 'N/A' }}</span>
</div>
<div class="info-item">
<span class="info-label">结构判定</span>
<span class="info-value">{{ selectedScheme?.mold_structure_type === 'two_half_cavity' ? '两板半腔(无独立模芯)' : '型腔 + 模芯' }}</span>
</div>
</div>
</div>
<div class="result-card" v-if="selectedCavityData?.mold_cavities || selectedKeyInfo?.mold_cavities">
@@ -1537,10 +1658,167 @@ const ResultView = {
</div>
</div>
</div>
<div class="viewer-section">
<h3>DFM 检查</h3>
<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>
</div>
<div class="viewer-section">
<h3>CAM 准备包</h3>
<div class="result-card" style="margin-bottom: var(--space-4);">
<h4>CAM 参数</h4>
<div class="info-list">
<div class="info-item">
<span class="info-label">模具钢</span>
<select v-model="state.camForm.mold_steel" class="form-select">
<option v-for="opt in camSteelOptions" :key="'steel-' + opt.value" :value="opt.value">{{ opt.label }}</option>
</select>
</div>
<div class="info-item">
<span class="info-label">表面质量</span>
<select v-model="state.camForm.surface_quality" class="form-select">
<option v-for="opt in camSurfaceOptions" :key="'surf-' + opt.value" :value="opt.value">{{ opt.label }}</option>
</select>
</div>
<div class="info-item">
<span class="info-label">控制器</span>
<select v-model="state.camForm.controller" class="form-select">
<option v-for="opt in camControllerOptions" :key="'ctrl-' + opt.value" :value="opt.value">{{ opt.label }}</option>
</select>
</div>
<div class="info-item">
<span class="info-label">包含G代码</span>
<label class="checkbox-group">
<input type="checkbox" class="checkbox-input" v-model="state.camForm.include_gcode" />
<span class="checkbox-label">返回G代码文本</span>
</label>
</div>
</div>
</div>
<div v-if="state.camError" class="inline-alert inline-alert-warning">
<div class="inline-alert-title">CAM 计划生成失败</div>
<div class="inline-alert-message">{{ state.camError }}</div>
</div>
<div v-else-if="!state.camPlan" class="inline-alert inline-alert-warning">
<div class="inline-alert-title">尚未生成 CAM 计划</div>
<div class="inline-alert-message">点击上方“生成 CAM 计划”后,将返回工序计划、刀具建议与制造风险提示。</div>
</div>
<template v-else>
<div class="result-grid">
<div class="result-card">
<h4>计划摘要</h4>
<div class="info-list">
<div class="info-item">
<span class="info-label">方案ID</span>
<span class="info-value">{{ state.camPlan.scheme_id || 'N/A' }}</span>
</div>
<div class="info-item">
<span class="info-label">总工序数</span>
<span class="info-value">{{ state.camPlan.summary?.total_operations || 0 }}</span>
</div>
<div class="info-item">
<span class="info-label">预计总时长</span>
<span class="info-value">{{ formatNumber(state.camPlan.summary?.total_estimated_time_min || 0) }} min</span>
</div>
<div class="info-item">
<span class="info-label">方案可信度</span>
<span class="info-value">{{ formatNumber(state.camPlan.confidence?.score || 0) }}</span>
</div>
</div>
</div>
<div class="result-card">
<h4>刀具建议</h4>
<div class="info-list">
<div class="info-item">
<span class="info-label">粗加工刀具</span>
<span class="info-value">{{ state.camPlan.tooling_suggestion?.roughing_tool?.tool_id || 'N/A' }}</span>
</div>
<div class="info-item">
<span class="info-label">精加工刀具</span>
<span class="info-value">{{ state.camPlan.tooling_suggestion?.finishing_tool?.tool_id || 'N/A' }}</span>
</div>
</div>
</div>
</div>
<div class="result-card full-width">
<h4>工序计划</h4>
<table class="data-table">
<thead>
<tr>
<th>序号</th>
<th>工序</th>
<th>刀具</th>
<th>主轴转速</th>
<th>进给率</th>
<th>预计时长(min)</th>
</tr>
</thead>
<tbody>
<tr v-for="op in (state.camPlan.process_plan || [])" :key="'cam-op-' + op.seq">
<td>{{ op.seq }}</td>
<td>{{ op.operation }}</td>
<td>{{ op.tool_id || 'N/A' }}</td>
<td>{{ formatNumber(op.spindle_speed_rpm || 0) }}</td>
<td>{{ formatNumber(op.feed_rate_mm_min || 0) }}</td>
<td>{{ formatNumber(op.estimated_time_min || 0) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="result-card full-width">
<h4>制造风险提示</h4>
<div class="recommendations-list">
<div class="recommendation-item medium" v-for="(warn, idx) in (state.camPlan.manufacturing_warnings || [])" :key="'cam-warn-' + idx">
<div class="recommendation-text">{{ warn }}</div>
</div>
</div>
</div>
</template>
</div>
<div v-if="selectedHtmlFile" class="viewer-section">
<h3>3D 预览</h3>
<iframe :src="selectedHtmlFile" class="viewer-frame"></iframe>
<div v-if="state.previewStatus === 'error'" class="inline-alert inline-alert-warning" style="margin-bottom: var(--space-3);">
<div class="inline-alert-title">预览加载失败</div>
<div class="inline-alert-message">HTML 已生成但加载异常,请检查该链接是否可访问:{{ selectedHtmlFile }}</div>
</div>
<iframe
:key="selectedHtmlFile"
:src="selectedHtmlFile"
class="viewer-frame"
@load="onPreviewLoad"
@error="onPreviewError"
></iframe>
</div>
<div v-else class="viewer-section">
<h3>3D 预览</h3>
<div class="inline-alert inline-alert-warning">
<div class="inline-alert-title">预览未生成</div>
<div class="inline-alert-message">当前任务没有返回 HTML 预览链接,属于未生成状态。</div>
</div>
</div>
<div v-if="state.task.analysis_result" class="viewer-section">