x
This commit is contained in:
+411
-217
@@ -981,8 +981,6 @@ const UsersView = {
|
||||
const MoldInsightView = {
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const deliveryDate = ref(null);
|
||||
const expectedDate = ref(null);
|
||||
const state = reactive({
|
||||
selectedFile: null,
|
||||
selectedMaterial: 'ABS',
|
||||
@@ -1079,6 +1077,10 @@ const MoldInsightView = {
|
||||
const formData = new FormData();
|
||||
formData.append("file", state.selectedFile);
|
||||
formData.append("material", state.selectedMaterial);
|
||||
formData.append("draft_angle", String(state.moldParams.draftAngle));
|
||||
formData.append("shrinkage_rate", String(state.moldParams.shrinkageRate));
|
||||
formData.append("parting_precision", String(state.moldParams.partingPrecision));
|
||||
formData.append("cavity_match", String(state.moldParams.cavityMatch));
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/upload", {
|
||||
@@ -1154,11 +1156,6 @@ const MoldInsightView = {
|
||||
loadHistory();
|
||||
});
|
||||
|
||||
const isFoamMaterial = (material) => {
|
||||
const foamMaterials = ['AlSi10Mg', 'AlSi12', 'Pure Al Foam', 'AlSi7Mg'];
|
||||
return foamMaterials.includes(material);
|
||||
};
|
||||
|
||||
return {
|
||||
state,
|
||||
handleFileChange,
|
||||
@@ -1168,56 +1165,74 @@ const MoldInsightView = {
|
||||
formatDateTime,
|
||||
formatNumber,
|
||||
toggleFileHistory,
|
||||
viewResult,
|
||||
isFoamMaterial
|
||||
viewResult
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="page-container">
|
||||
<div class="page-header">
|
||||
<h1>MoldInsight</h1>
|
||||
<p>STP 模具几何分析</p>
|
||||
<h1>注塑模 STP 分析</h1>
|
||||
<p>上传 STEP/STP 产品件,完成自动分模、工程建议与导出</p>
|
||||
</div>
|
||||
|
||||
<div class="upload-section">
|
||||
<div
|
||||
:class="['upload-zone', { 'drag-over': state.dragOver }]"
|
||||
@dragover.prevent="state.dragOver = true"
|
||||
@dragleave.prevent="state.dragOver = false"
|
||||
@drop="handleDrop"
|
||||
@click="$refs.fileInput.click()"
|
||||
>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".stp,.step"
|
||||
@change="handleFileChange"
|
||||
hidden
|
||||
/>
|
||||
<div class="upload-icon">📁</div>
|
||||
<div class="upload-text">
|
||||
<span class="upload-title">点击选择或拖拽文件</span>
|
||||
<span class="upload-hint">支持 .stp, .step 格式,最大 100MB</span>
|
||||
|
||||
<div class="moldinsight-intro-grid">
|
||||
<div class="intro-card">
|
||||
<div class="intro-card-title">输入</div>
|
||||
<div class="intro-card-text">STEP/STP 产品件,面向注塑模主流程</div>
|
||||
</div>
|
||||
<div class="intro-card">
|
||||
<div class="intro-card-title">输出</div>
|
||||
<div class="intro-card-text">分模方案、DFM 风险、注塑模系统摘要与 CAD 导出</div>
|
||||
</div>
|
||||
<div class="intro-card">
|
||||
<div class="intro-card-title">目标</div>
|
||||
<div class="intro-card-text">先确认推荐方案,再进入导出与 CAM 准备</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="upload-layout">
|
||||
<div class="upload-main-card">
|
||||
<h2 class="section-title">1. 上传产品件</h2>
|
||||
<div
|
||||
:class="['upload-zone', { 'drag-over': state.dragOver }]"
|
||||
@dragover.prevent="state.dragOver = true"
|
||||
@dragleave.prevent="state.dragOver = false"
|
||||
@drop="handleDrop"
|
||||
@click="$refs.fileInput.click()"
|
||||
>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".stp,.step"
|
||||
@change="handleFileChange"
|
||||
hidden
|
||||
/>
|
||||
<div class="upload-icon">📁</div>
|
||||
<div class="upload-text">
|
||||
<span class="upload-title">点击选择或拖拽 STP/STEP 文件</span>
|
||||
<span class="upload-hint">支持注塑模产品件分析,最大 100MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="state.selectedFile" class="file-info">
|
||||
<span class="file-name">{{ state.selectedFile.name }}</span>
|
||||
<span class="file-size">{{ formatFileSize(state.selectedFile.size) }}</span>
|
||||
<button class="btn-clear" @click="state.selectedFile = null" title="清除文件">×</button>
|
||||
</div>
|
||||
|
||||
<div v-if="state.error" class="error-message">{{ state.error }}</div>
|
||||
|
||||
<div v-if="state.polling" class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: state.progress + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="state.selectedFile" class="file-info">
|
||||
<span class="file-name">{{ state.selectedFile.name }}</span>
|
||||
<span class="file-size">{{ formatFileSize(state.selectedFile.size) }}</span>
|
||||
<button class="btn-clear" @click="state.selectedFile = null" title="清除文件">×</button>
|
||||
</div>
|
||||
|
||||
<!-- 材料选择面板 -->
|
||||
<div v-if="state.selectedFile" class="material-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">📦 材料与参数设置</span>
|
||||
</div>
|
||||
|
||||
<!-- 材料类型选择 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">材料类型</label>
|
||||
<select v-model="state.selectedMaterial" class="form-select">
|
||||
<optgroup label="普通塑料">
|
||||
|
||||
<div class="upload-side-card">
|
||||
<h2 class="section-title">2. 注塑模参数</h2>
|
||||
<div class="material-panel compact-panel">
|
||||
<div class="form-group">
|
||||
<label class="form-label">产品材料</label>
|
||||
<select v-model="state.selectedMaterial" class="form-select">
|
||||
<option value="ABS">ABS (1.05 g/cm³)</option>
|
||||
<option value="PP">PP (0.90 g/cm³)</option>
|
||||
<option value="PE">PE (0.95 g/cm³)</option>
|
||||
@@ -1226,70 +1241,58 @@ const MoldInsightView = {
|
||||
<option value="POM">POM (1.41 g/cm³)</option>
|
||||
<option value="PMMA">PMMA (1.18 g/cm³)</option>
|
||||
<option value="PBT">PBT (1.31 g/cm³)</option>
|
||||
</optgroup>
|
||||
<optgroup label="铝泡沫材料">
|
||||
<option value="AlSi10Mg">AlSi10Mg (0.45 g/cm³) - 常用铝硅泡沫</option>
|
||||
<option value="AlSi12">AlSi12 (0.50 g/cm³) - 高强度铝泡沫</option>
|
||||
<option value="Pure Al Foam">Pure Al Foam (0.35 g/cm³) - 纯铝泡沫</option>
|
||||
<option value="AlSi7Mg">AlSi7Mg (0.40 g/cm³) - 轻质铝镁泡沫</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 铝泡沫参数(仅在选择泡沫材料时显示) -->
|
||||
<div v-if="isFoamMaterial(state.selectedMaterial)" class="foam-params">
|
||||
<div class="param-section">
|
||||
<span class="param-title">⚙️ 铝泡沫专用参数</span>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">拔模角 (°)</label>
|
||||
<input type="range" v-model.number="state.moldParams.draftAngle" min="1" max="10" step="0.5" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.draftAngle }}°</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">收缩率 (%)</label>
|
||||
<input type="range" v-model.number="state.moldParams.shrinkageRate" min="0.5" max="3.0" step="0.1" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.shrinkageRate }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">分型精度 (mm)</label>
|
||||
<input type="range" v-model.number="state.moldParams.partingPrecision" min="0.01" max="1.0" step="0.01" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.partingPrecision }} mm</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">型腔匹配度 (%)</label>
|
||||
<input type="range" v-model.number="state.moldParams.cavityMatch" min="80" max="100" step="1" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.cavityMatch }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<details class="advanced-params">
|
||||
<summary>高级工艺参数</summary>
|
||||
<div class="advanced-params-body">
|
||||
<div class="inline-note">默认值适用于多数注塑件;仅在已知工艺约束时再调整。</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">拔模角 (°)</label>
|
||||
<input type="range" v-model.number="state.moldParams.draftAngle" min="1" max="10" step="0.5" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.draftAngle }}°</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">收缩率 (%)</label>
|
||||
<input type="range" v-model.number="state.moldParams.shrinkageRate" min="0.5" max="3.0" step="0.1" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.shrinkageRate }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">分型精度 (mm)</label>
|
||||
<input type="range" v-model.number="state.moldParams.partingPrecision" min="0.01" max="1.0" step="0.01" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.partingPrecision }} mm</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">型腔匹配度 (%)</label>
|
||||
<input type="range" v-model.number="state.moldParams.cavityMatch" min="80" max="100" step="1" class="form-range">
|
||||
<span class="range-value">{{ state.moldParams.cavityMatch }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="state.error" class="error-message">{{ state.error }}</div>
|
||||
|
||||
<button
|
||||
v-if="state.selectedFile"
|
||||
class="btn-primary"
|
||||
@click="uploadFile"
|
||||
:disabled="state.uploading || state.polling"
|
||||
>
|
||||
{{ state.uploading ? '上传中...' : state.polling ? '分析中...' : '开始分析' }}
|
||||
</button>
|
||||
|
||||
<div v-if="state.polling" class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: state.progress + '%' }"></div>
|
||||
|
||||
<button
|
||||
v-if="state.selectedFile"
|
||||
class="btn-primary upload-submit-btn"
|
||||
@click="uploadFile"
|
||||
:disabled="state.uploading || state.polling"
|
||||
>
|
||||
{{ state.uploading ? '上传中...' : state.polling ? '分析中...' : '3. 开始注塑模分析' }}
|
||||
</button>
|
||||
<div v-else class="inline-note">先选择 STP 文件,再填写材料并开始分析。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="state.history?.files?.length" class="section">
|
||||
<h2 class="section-title">分析历史</h2>
|
||||
<details v-if="state.history?.files?.length" class="history-collapsible section">
|
||||
<summary class="history-summary">分析历史({{ state.history.files.length }} 个文件)</summary>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
@@ -1369,7 +1372,7 @@ const MoldInsightView = {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
@@ -1457,10 +1460,6 @@ const ResultView = {
|
||||
return priorityMap[priority] || priority;
|
||||
};
|
||||
|
||||
const isFoamMaterial = (material) => {
|
||||
return material === 'aluminum_foam';
|
||||
};
|
||||
|
||||
const candidateSchemes = computed(() => state.task?.candidate_schemes || []);
|
||||
const hasSingleScheme = computed(() => candidateSchemes.value.length === 1);
|
||||
const selectedScheme = computed(() => {
|
||||
@@ -1471,6 +1470,33 @@ const ResultView = {
|
||||
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 selectedInjectionSystem = computed(() => selectedCavityData.value?.injection_system || state.task?.plan_result?.injection_system || null);
|
||||
const stageTimingEntries = computed(() => {
|
||||
const timings = state.task?.stage_timings || {};
|
||||
return Object.entries(timings)
|
||||
.filter(([, value]) => typeof value === 'number')
|
||||
.sort((a, b) => b[1] - a[1]);
|
||||
});
|
||||
const sortedRecommendations = computed(() => {
|
||||
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
||||
return [...(state.task?.analysis_result?.design_recommendations || [])].sort((a, b) => {
|
||||
return (priorityOrder[a.priority] ?? 99) - (priorityOrder[b.priority] ?? 99);
|
||||
});
|
||||
});
|
||||
const primaryRecommendation = computed(() => sortedRecommendations.value[0] || null);
|
||||
const selectedDfmCount = computed(() => selectedDfmViolations.value?.length || 0);
|
||||
const selectedCavityCount = computed(() => {
|
||||
const cavityValue = selectedCavityData.value?.mold_cavities?.cavity_count;
|
||||
if (typeof cavityValue === 'number') return cavityValue;
|
||||
const cavityObj = selectedCavityData.value?.mold_cavities || selectedKeyInfo.value?.mold_cavities || {};
|
||||
return cavityObj.cavity_count || Object.keys(cavityObj).filter(key => key.startsWith('cavity_')).length || 1;
|
||||
});
|
||||
const selectedRiskLabel = computed(() => {
|
||||
if (!selectedDfmCount.value) return '低风险';
|
||||
if (selectedDfmCount.value >= 4) return '高风险';
|
||||
if (selectedDfmCount.value >= 2) return '中风险';
|
||||
return '低风险';
|
||||
});
|
||||
|
||||
const selectScheme = (schemeId) => {
|
||||
state.selectedSchemeId = schemeId;
|
||||
@@ -1495,6 +1521,40 @@ const ResultView = {
|
||||
return `[${dir.map(v => Number(v).toFixed(2)).join(', ')}]`;
|
||||
};
|
||||
|
||||
const analysisFeatures = computed(() => state.task?.analysis_result?.detected_features || []);
|
||||
const getFeatureByTypes = (...types) => analysisFeatures.value.find(f => types.includes(f.feature_type));
|
||||
const countFeaturesByTypes = (...types) => analysisFeatures.value.filter(f => types.includes(f.feature_type)).length;
|
||||
const getWallThicknessSummary = () => {
|
||||
const feature = getFeatureByTypes('thin_wall', 'thick_wall', 'wall_non_uniform');
|
||||
if (!feature) return '待分析';
|
||||
const params = feature.parameters || {};
|
||||
if (params.min_thickness != null && params.max_thickness != null) {
|
||||
return `${Number(params.min_thickness).toFixed(2)} - ${Number(params.max_thickness).toFixed(2)} mm`;
|
||||
}
|
||||
if (params.average_thickness != null) {
|
||||
return `平均 ${Number(params.average_thickness).toFixed(2)} mm`;
|
||||
}
|
||||
return feature.recommendations?.[0] || '已完成分析';
|
||||
};
|
||||
const getUndercutCount = () => {
|
||||
const fromScheme = selectedScheme.value?.undercut_regions?.length || selectedCavityData.value?.undercut_regions?.length || 0;
|
||||
return fromScheme || countFeaturesByTypes('undercut');
|
||||
};
|
||||
const formatStageName = (name) => {
|
||||
const stageNameMap = {
|
||||
parse_stp: 'STP解析',
|
||||
generate_mesh: '网格生成',
|
||||
generate_cavity: '分模与型腔生成',
|
||||
build_plan_result: '方案结果组装',
|
||||
persist_artifacts: '结果持久化',
|
||||
analyze_design: '几何分析',
|
||||
verify_geometry: '几何验证',
|
||||
generate_llm_report: 'LLM报告生成'
|
||||
};
|
||||
return stageNameMap[name] || name;
|
||||
};
|
||||
const formatTiming = (value) => `${Number(value || 0).toFixed(3)} s`;
|
||||
|
||||
const exportCAD = async (format) => {
|
||||
try {
|
||||
const taskId = route.params.taskId;
|
||||
@@ -1503,6 +1563,7 @@ const ResultView = {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
task_id: taskId,
|
||||
scheme_id: selectedScheme.value?.scheme_id || state.selectedSchemeId,
|
||||
formats: [format],
|
||||
components: ['cavity', 'core', 'parting_surface']
|
||||
})
|
||||
@@ -1586,11 +1647,22 @@ const ResultView = {
|
||||
selectedKeyInfo,
|
||||
selectedHtmlFile,
|
||||
selectedDfmViolations,
|
||||
selectedInjectionSystem,
|
||||
stageTimingEntries,
|
||||
getWallThicknessSummary,
|
||||
countFeaturesByTypes,
|
||||
getUndercutCount,
|
||||
formatStageName,
|
||||
formatTiming,
|
||||
primaryRecommendation,
|
||||
sortedRecommendations,
|
||||
selectedDfmCount,
|
||||
selectedCavityCount,
|
||||
selectedRiskLabel,
|
||||
formatFileSize,
|
||||
formatDateTime,
|
||||
formatNumber,
|
||||
getPriorityText,
|
||||
isFoamMaterial,
|
||||
exportCAD,
|
||||
generateCamPlan,
|
||||
camSteelOptions,
|
||||
@@ -1623,28 +1695,90 @@ const ResultView = {
|
||||
<span :class="['badge', state.task.status === 'completed' ? 'badge-success' : 'badge-error']">
|
||||
{{ 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>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('stl')" title="导出STL网格格式(3D打印预览)">
|
||||
导出 STL
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('iges')" title="导出IGES格式(兼容旧系统)">
|
||||
导出 IGES
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('brep')" title="导出BRep格式(FreeCAD原生)">
|
||||
导出 BRep
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="result-grid result-priority-grid">
|
||||
<div class="result-card result-card-highlight">
|
||||
<div class="summary-header">
|
||||
<h3>推荐方案</h3>
|
||||
<span class="badge badge-info">{{ selectedScheme?.title || selectedScheme?.scheme_id || '方案待定' }}</span>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">分型方向</span>
|
||||
<span class="info-value">{{ formatSchemeDirection(selectedScheme) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">分型面位置</span>
|
||||
<span class="info-value">{{ selectedScheme?.offset_label || '中面' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">方案总分</span>
|
||||
<span class="info-value">{{ formatNumber(selectedScheme?.score || 0) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">方案可信度</span>
|
||||
<span class="info-value">{{ formatNumber(selectedScheme?.confidence_score || 0) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">主要理由</span>
|
||||
<span class="info-value">{{ selectedScheme?.summary || primaryRecommendation?.description || '基于当前几何与制造约束自动推荐' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">高优先级建议</span>
|
||||
<span class="info-value">{{ primaryRecommendation?.description || '未发现高优先级工艺建议' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-card result-card-highlight">
|
||||
<div class="summary-header">
|
||||
<h3>关键操作</h3>
|
||||
<span :class="['badge', selectedRiskLabel === '高风险' ? 'badge-error' : selectedRiskLabel === '中风险' ? 'badge-warning' : 'badge-success']">
|
||||
{{ selectedRiskLabel }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">DFM 风险数</span>
|
||||
<span class="info-value">{{ selectedDfmCount }} 项</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">推荐型腔数</span>
|
||||
<span class="info-value">{{ selectedCavityCount }} 腔</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">预计成型周期</span>
|
||||
<span class="info-value">{{ selectedInjectionSystem?.overall_assessment?.estimated_cycle_time || 'N/A' }} s</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">下一步</span>
|
||||
<span class="info-value">{{ selectedDfmCount ? '先处理 DFM 风险,再确认导出或 CAM' : '可进入 3D 复核、导出与 CAM 准备' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="state.task.status === 'completed'" class="export-buttons export-buttons-block">
|
||||
<button class="btn-sm btn-primary" @click="exportCAD('step')" title="导出STEP格式(UG/FreeCAD/SolidWorks通用)">
|
||||
导出 STEP
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('iges')" title="导出IGES格式(兼容旧系统)">
|
||||
导出 IGES
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('stl')" title="导出STL网格格式(3D打印预览)">
|
||||
导出 STL
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('brep')" title="导出BRep格式(FreeCAD原生)">
|
||||
导出 BRep
|
||||
</button>
|
||||
<button class="btn-sm btn-primary" :disabled="state.camLoading" @click="generateCamPlan()" title="基于当前选中方案生成CAM工艺计划">
|
||||
{{ state.camLoading ? '生成中...' : '生成 CAM 计划' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-grid">
|
||||
|
||||
<div class="result-grid compact-metrics-grid">
|
||||
<div class="result-card">
|
||||
<h3>文件信息</h3>
|
||||
<h3>任务信息</h3>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">文件大小</span>
|
||||
@@ -1662,7 +1796,7 @@ const ResultView = {
|
||||
</div>
|
||||
|
||||
<div class="result-card">
|
||||
<h3>几何数据</h3>
|
||||
<h3>几何概览</h3>
|
||||
<div class="info-list">
|
||||
<div class="info-item" v-if="state.task.mesh_summary">
|
||||
<span class="info-label">顶点数</span>
|
||||
@@ -1690,6 +1824,7 @@ const ResultView = {
|
||||
|
||||
<div v-if="candidateSchemes.length" class="viewer-section">
|
||||
<h3>候选分模方案</h3>
|
||||
<div class="inline-note">先切换并确认推荐方案,再进入后续工程判断、导出与 CAM。</div>
|
||||
<div :class="['result-grid', hasSingleScheme ? 'single-scheme-grid' : '']">
|
||||
<div
|
||||
v-for="scheme in candidateSchemes"
|
||||
@@ -1765,9 +1900,9 @@ const ResultView = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="candidateSchemes.length > 1" class="viewer-section">
|
||||
<h3>方案对比</h3>
|
||||
<div class="result-card full-width">
|
||||
<details v-if="candidateSchemes.length > 1" class="viewer-section diagnostic-panel">
|
||||
<summary>方案对比</summary>
|
||||
<div class="result-card full-width diagnostic-panel-body">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -1801,13 +1936,13 @@ const ResultView = {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div v-if="selectedCavityData || selectedKeyInfo" class="viewer-section">
|
||||
<h3>模具信息</h3>
|
||||
<h3>工程摘要</h3>
|
||||
<div class="result-grid">
|
||||
<div class="result-card">
|
||||
<h4>基本信息</h4>
|
||||
<h4>方案与零件</h4>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">零件名称</span>
|
||||
@@ -1824,11 +1959,44 @@ const ResultView = {
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-card" v-if="selectedCavityData?.mold_cavities || selectedKeyInfo?.mold_cavities">
|
||||
<h4>型腔信息</h4>
|
||||
<h4>型腔与锁模</h4>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">型腔数量</span>
|
||||
<span class="info-value">{{ Object.keys(selectedCavityData?.mold_cavities || selectedKeyInfo?.mold_cavities || {}).length }}</span>
|
||||
<span class="info-value">{{ selectedCavityCount }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">预估锁模力</span>
|
||||
<span class="info-value">{{ selectedCavityData?.manufacturing_info?.estimated_clamping_force || '自动计算' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-card" v-if="selectedInjectionSystem">
|
||||
<h4>注塑模系统</h4>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">预计成型周期</span>
|
||||
<span class="info-value">{{ selectedInjectionSystem?.overall_assessment?.estimated_cycle_time || 'N/A' }} s</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">冷却时间</span>
|
||||
<span class="info-value">{{ selectedInjectionSystem?.cooling?.cooling_time || 'N/A' }} s</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">水路数量</span>
|
||||
<span class="info-value">{{ selectedInjectionSystem?.cooling?.thermal_check?.channel_count || 0 }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">冷却流量</span>
|
||||
<span class="info-value">{{ selectedInjectionSystem?.cooling?.flow_rate?.flow_rate_lpm || 'N/A' }} L/min</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">浇口类型</span>
|
||||
<span class="info-value">{{ selectedInjectionSystem?.gating?.gate_type || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">流道形式</span>
|
||||
<span class="info-value">{{ selectedInjectionSystem?.gating?.runner?.type || 'N/A' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1861,8 +2029,31 @@ const ResultView = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedHtmlFile" class="viewer-section">
|
||||
<h3>3D 预览</h3>
|
||||
<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 class="viewer-section">
|
||||
<h3>CAM 准备包</h3>
|
||||
<h3>导出与 CAM</h3>
|
||||
<div class="result-card" style="margin-bottom: var(--space-4);">
|
||||
<h4>CAM 参数</h4>
|
||||
<div class="info-list">
|
||||
@@ -1973,62 +2164,9 @@ const ResultView = {
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedHtmlFile" class="viewer-section">
|
||||
<h3>3D 预览</h3>
|
||||
<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">
|
||||
<h3>分析结果详情</h3>
|
||||
|
||||
<!-- FreeCAD 验证结果 -->
|
||||
<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.analysis_result.analysis_summary">
|
||||
<div class="summary-header">
|
||||
<h4>分析摘要</h4>
|
||||
@@ -2038,7 +2176,7 @@ const ResultView = {
|
||||
{{ state.task.analysis_result.analysis_summary }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="result-grid">
|
||||
<div class="result-card full-width">
|
||||
<h4>1. 产品特征识别</h4>
|
||||
@@ -2052,19 +2190,19 @@ const ResultView = {
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">壁厚分布</span>
|
||||
<span class="info-value">{{ state.task.analysis_result.detected_features?.find(f => f.feature_type === 'wall_thickness')?.description || '待分析' }}</span>
|
||||
<span class="info-value">{{ getWallThicknessSummary() }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">加强筋位置和密度</span>
|
||||
<span class="info-value">{{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === 'rib').length || 0 }} 个加强筋</span>
|
||||
<span class="info-value">{{ countFeaturesByTypes('rib_structure') || 0 }} 个加强筋</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">孔洞和凹槽位置</span>
|
||||
<span class="info-value">{{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === 'hole' || f.feature_type === 'pocket').length || 0 }} 个孔洞/凹槽</span>
|
||||
<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">{{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === 'undercut').length || 0 }} 个倒扣区域</span>
|
||||
<span class="info-value">{{ getUndercutCount() }} 个倒扣区域</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">对称性分析</span>
|
||||
@@ -2081,27 +2219,27 @@ const ResultView = {
|
||||
</div>
|
||||
|
||||
<div class="result-card full-width">
|
||||
<h4>2. 泡沫包装设计决策</h4>
|
||||
<h4>2. 注塑模设计决策</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')?.recommendation || '根据产品重量和脆弱程度自动计算' }}</span>
|
||||
<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">基于产品薄弱区域自动布置</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.geometry_data?.center_of_mass ? '自动优化' : '手动设置' }}</span>
|
||||
<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">防止真空吸附:建议在产品最大平面区域设置通风孔</span>
|
||||
<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">{{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === '定位').length || 0 }} 个定位特征</span>
|
||||
<span class="info-value">{{ countFeaturesByTypes('boss_feature') || 0 }} 个柱位/定位相关特征</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">分型面选择</span>
|
||||
@@ -2109,13 +2247,10 @@ const ResultView = {
|
||||
</div>
|
||||
|
||||
<!-- 设计建议列表 -->
|
||||
<div class="recommendations-section" v-if="state.task.analysis_result.design_recommendations?.length">
|
||||
<div class="recommendations-section" v-if="sortedRecommendations.length">
|
||||
<h5>详细设计建议</h5>
|
||||
<div class="recommendations-list">
|
||||
<div v-for="rec in state.task.analysis_result.design_recommendations.sort((a, b) => {
|
||||
const priorityOrder = { 'critical': 0, 'high': 1, 'medium': 2, 'low': 3 };
|
||||
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
||||
})" :key="rec.type" class="recommendation-item" :class="rec.priority">
|
||||
<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>
|
||||
@@ -2179,6 +2314,65 @@ const ResultView = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details 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>
|
||||
</div>
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user