feat: 接入Creo查看模型功能,重构Store职责分离架构

核心功能:
- 扩展creoApi服务:添加getCurrentModel()和openModelFile()方法
- 重构CAD Store:简化为纯状态管理,删除所有假数据
- 实现CadSidebar交互:组件直接调用API,状态由Store管理
- 修复apiClient通知:确保失败时也显示ElNotification

架构改进:
- Store职责分离:只管理连接状态,不执行API调用
- 一次一连接:连接新CAD时自动断开其他连接
- 通知统一化:operationContext自动生成具体操作通知
- 错误直接暴露:不使用try-catch,错误由apiClient统一处理

文档更新:
- 更新API接入流程:添加实战经验和避坑指南
- 记录职责分离原则和关键实现要点

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sladro 2025-09-16 15:48:49 +08:00
parent 644a826fa1
commit 720a0df4c4
5 changed files with 162 additions and 243 deletions

View File

@ -248,15 +248,70 @@ PDMS: {
}
```
## API接入流程
## API接入流程(实战总结)
今后新增CAD软件API的标准流程
**经验教训**Store只管理状态API调用在组件中执行通知统一处理。
1. **配置定义**: 在 `src/config/cad.js` 中添加软件配置
2. **创建API服务**: 在 `src/services/` 中创建对应API文件
3. **Store集成**: 在 `src/stores/cad.js` 中添加调用逻辑
### 🔧 标准接入步骤
所有API请求通过 `apiClient.js` 统一处理,自动获得日志记录和通知功能。
1. **API服务层**`src/services/xxxApi.js`
```javascript
// 扩展现有API服务或新建
async getCurrentModel() {
const url = buildApiUrl(this.softwareName, 'status')
return await apiClient.get(url, {
operationContext: {
software: 'Creo', // 软件名
operation: '获取当前模型' // 具体操作(用于通知)
}
})
}
```
2. **Store状态管理**`src/stores/cad.js`
```javascript
// 只管理状态不执行API调用
const setCADConnection = (cadName, connected) => {
// 一次只能连接一个CAD
cadConnections.value.forEach(cad => cad.connected = false)
const targetCAD = getCADConnection(cadName)
if (targetCAD) targetCAD.connected = connected
}
```
3. **组件中调用**`src/components/xxx.vue`
```javascript
// 组件负责API调用和状态更新
const handleOperation = async () => {
const result = await creoApi.getCurrentModel()
// 通知自动显示,无需手动处理
// 根据需要更新状态
if (result.success) {
cadStore.setCADConnection('creo', true)
}
}
```
### ⚠️ 关键避坑指南
**❌ 常见错误**
- Store中执行API调用职责混乱
- 组件中手动处理成功/失败通知(重复工作)
- Store管理临时查询结果不是状态
- 允许多个CAD同时连接违反业务逻辑
**✅ 正确做法**
- Store纯状态管理`cadConnections`、`setCADConnection()`
- 组件执行业务API调用、用户交互、状态更新
- 通知自动化:`apiClient.js`统一处理成功/失败通知
- 一次一连接连接新CAD时自动断开其他
### 🎯 核心原则
- **职责分离**Store管状态组件调API
- **通知统一**`operationContext`自动生成通知
- **状态约束**:业务规则在状态管理中体现
- **错误暴露**不用try-catch让错误直接暴露
## 日志系统扩展

View File

@ -50,7 +50,7 @@
</label>
</div>
<div class="project-actions">
<button class="confirm-btn">
<button class="confirm-btn" @click="handleOpenModel">
<i class="fas fa-folder-open"></i>
打开模型
</button>
@ -65,6 +65,7 @@
import { ref, computed } from 'vue'
import { useCADStore } from '@/stores/cad'
import CadSoftwareGrid from '@/components/ui/CadSoftwareGrid.vue'
import creoApi from '@/services/creoApi'
const cadStore = useCADStore()
@ -92,9 +93,44 @@ const selectSoftware = (software) => {
// }
//
const testConnection = (software) => {
// CAD store
cadStore.toggleCADConnection(software)
const testConnection = async (software) => {
//
cadStore.setCADConnecting(software, true)
// Creo
if (software === 'creo' || software === 'Creo') {
const result = await creoApi.testConnection()
cadStore.setCADConnection(software, result.success)
} else {
//
cadStore.setCADConnection(software, false)
}
cadStore.setCADConnecting(software, false)
}
//
const handleOpenModel = async () => {
const connectedCAD = cadStore.currentCAD
if (!connectedCAD) {
return
}
//
const selectedOption = document.querySelector('input[name="project-source"]:checked')?.value
if (selectedOption === 'current-project') {
//
if (connectedCAD.id === 'creo') {
await creoApi.getCurrentModel()
}
} else {
//
const filePath = prompt('请输入模型文件路径:')
if (filePath && connectedCAD.id === 'creo') {
await creoApi.openModelFile(filePath)
}
}
}
</script>

View File

@ -185,6 +185,9 @@ class ApiClient {
// 记录错误日志
ApiLogger.log(context.method, context.url, context.requestData, errorResponse, duration)
// 执行后处理钩子(显示失败通知)
await PostProcessManager.process(errorResponse, { ...context, duration })
}
// 便捷方法

View File

@ -11,7 +11,7 @@ class CreoApiService {
}
/**
* 测试Creo连接 - 唯一实现的功能
* 测试Creo连接
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
*/
async testConnection() {
@ -26,6 +26,39 @@ class CreoApiService {
}
})
}
/**
* 获取当前模型状态
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
*/
async getCurrentModel() {
const url = buildApiUrl(this.softwareName, 'status')
return await apiClient.get(url, {
operationContext: {
software: 'Creo',
operation: '获取当前模型'
}
})
}
/**
* 打开模型文件
* @param {string} filePath - 模型文件路径
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
*/
async openModelFile(filePath) {
const url = buildApiUrl(this.softwareName, 'open')
return await apiClient.post(url, {
filePath: filePath
}, {
operationContext: {
software: 'Creo',
operation: '打开模型文件'
}
})
}
}
// 导出单例实例

View File

@ -1,33 +1,7 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getAllCADDefinitions, getDisplayableCADs } from '@/config/cad'
import creoApi from '@/services/creoApi'
// CAD存储常量配置
const CAD_CONFIG = {
// 延时设置(毫秒)
CONNECTION_TIMEOUT: 1500,
MODEL_OPEN_TIMEOUT: 1000,
MODEL_EXPORT_TIMEOUT: 2000,
MODEL_REFRESH_TIMEOUT: 500,
// 时间计算(毫秒)
TIME_INTERVALS: {
MINUTES_30: 30 * 60 * 1000,
HOURS_2: 2 * 60 * 60 * 1000,
HOURS_24: 24 * 60 * 60 * 1000
},
// 默认文件大小
DEFAULT_FILE_SIZES: {
MECHANISM: '5.2MB',
GEAR: '1.2MB',
HOUSING: '3.8MB'
},
// 默认版本
DEFAULT_MODEL_VERSION: 'V1.0'
}
export const useCADStore = defineStore('cad', () => {
// 从配置生成CAD软件连接状态
@ -57,49 +31,6 @@ export const useCADStore = defineStore('cad', () => {
return cadConnections.value.find(cad => cad.connected)
})
// 已打开的模型列表
const openedModels = ref([
{
id: 1,
title: '机械装配体',
name: 'mechanism.asm',
version: 'V1.2',
source: 'SolidWorks',
openTime: new Date(Date.now() - CAD_CONFIG.TIME_INTERVALS.MINUTES_30),
size: CAD_CONFIG.DEFAULT_FILE_SIZES.MECHANISM,
path: '/models/mechanism.asm'
},
{
id: 2,
title: '齿轮零件',
name: 'gear.prt',
version: 'V2.0',
source: 'AutoCAD',
openTime: new Date(Date.now() - CAD_CONFIG.TIME_INTERVALS.HOURS_2),
size: CAD_CONFIG.DEFAULT_FILE_SIZES.GEAR,
path: '/models/gear.prt'
},
{
id: 3,
title: '外壳设计',
name: 'housing.prt',
version: 'V1.5',
source: 'Creo',
openTime: new Date(Date.now() - CAD_CONFIG.TIME_INTERVALS.HOURS_24),
size: CAD_CONFIG.DEFAULT_FILE_SIZES.HOUSING,
path: '/models/housing.prt'
}
])
// 当前选中的模型
const selectedModel = ref(null)
// 模型操作状态
const modelOperations = ref({
isOpening: false,
isViewing: false,
isExporting: false
})
// 获取可显示的CAD列表根据显示模式
const displayableCADs = computed(() => {
@ -122,184 +53,45 @@ export const useCADStore = defineStore('cad', () => {
return cad ? cad.features.includes(feature) : false
}
// 连接/断开CAD软件
const toggleCADConnection = async (cadName) => {
const cad = getCADConnection(cadName)
if (!cad || cad.connecting) return
// 设置连接中状态
cad.connecting = true
try {
// 只对Creo实现真实连接其他软件不支持
if (cadName === 'creo' || cadName === 'Creo') {
const result = await creoApi.testConnection()
if (result.success) {
cad.connected = true
cad.lastConnected = new Date()
} else {
cad.connected = false
cad.lastConnected = null
}
return { success: result.success, connected: cad.connected, error: result.error }
} else {
// 其他软件直接返回不支持
return { success: false, error: `${cadName} 连接功能暂未实现` }
}
} catch (error) {
return { success: false, error: error.message }
} finally {
// 设置CAD连接状态一次只能连接一个
const setCADConnection = (cadName, connected) => {
// 先断开所有连接
cadConnections.value.forEach(cad => {
cad.connected = false
cad.connecting = false
}
}
// 打开模型
const openModel = async (modelData) => {
modelOperations.value.isOpening = true
try {
// 检查是否有对应的CAD连接
const sourceCAD = getCADConnection(modelData.source)
if (!sourceCAD || !sourceCAD.connected) {
throw new Error(`请先连接 ${modelData.source}`)
if (!connected) {
cad.lastConnected = null
}
// 模拟打开过程
await new Promise(resolve => setTimeout(resolve, CAD_CONFIG.MODEL_OPEN_TIMEOUT))
// 添加新模型
const newModel = {
id: Date.now(),
title: modelData.title || modelData.name,
name: modelData.name,
version: modelData.version || CAD_CONFIG.DEFAULT_MODEL_VERSION,
source: modelData.source,
openTime: new Date(),
size: modelData.size || '0MB',
path: modelData.path || ''
}
openedModels.value.unshift(newModel)
return { success: true, model: newModel }
} catch (error) {
return { success: false, error: error.message }
} finally {
modelOperations.value.isOpening = false
}
}
// 关闭模型
const closeModel = (modelId) => {
const index = openedModels.value.findIndex(m => m.id === modelId)
if (index > -1) {
const closedModel = openedModels.value[index]
openedModels.value.splice(index, 1)
// 清除选中状态
if (selectedModel.value?.id === modelId) {
selectedModel.value = null
}
return { success: true, model: closedModel }
}
return { success: false, error: '模型未找到' }
}
// 选择模型
const selectModel = (modelId) => {
const model = openedModels.value.find(m => m.id === modelId)
selectedModel.value = model || null
return selectedModel.value
}
// 获取模型列表
const getModelsBySource = (source) => {
return openedModels.value.filter(model => model.source === source)
}
// 导出模型
const exportModel = async (modelId, format = 'step') => {
modelOperations.value.isExporting = true
try {
const model = openedModels.value.find(m => m.id === modelId)
if (!model) {
throw new Error('模型未找到')
}
// 模拟导出过程
await new Promise(resolve => setTimeout(resolve, CAD_CONFIG.MODEL_EXPORT_TIMEOUT))
return {
success: true,
model,
exportPath: `/exports/${model.name}.${format}`
}
} catch (error) {
return { success: false, error: error.message }
} finally {
modelOperations.value.isExporting = false
}
}
// 刷新模型列表
const refreshModels = async () => {
// 模拟刷新过程
await new Promise(resolve => setTimeout(resolve, CAD_CONFIG.MODEL_REFRESH_TIMEOUT))
// 更新时间显示
openedModels.value.forEach(() => {
// 保持原有时间,只是触发界面更新
})
return { success: true }
// 设置指定CAD的连接状态
const targetCAD = getCADConnection(cadName)
if (targetCAD) {
targetCAD.connected = connected
if (connected) {
targetCAD.lastConnected = new Date()
}
}
}
// 获取统计信息
const getStatistics = computed(() => {
const totalModels = openedModels.value.length
const bySource = {}
openedModels.value.forEach(model => {
if (!bySource[model.source]) {
bySource[model.source] = 0
}
bySource[model.source]++
})
return {
totalModels,
bySource,
connectedCADs: cadConnections.value.filter(cad => cad.connected).length
// 设置CAD连接中状态
const setCADConnecting = (cadName, connecting) => {
const cad = getCADConnection(cadName)
if (cad) {
cad.connecting = connecting
}
})
}
return {
// 状态
cadConnections,
openedModels,
selectedModel,
currentCAD,
modelOperations,
getStatistics,
// 配置驱动的计算属性
displayableCADs,
// 方法
getCADConnection,
cadSupportsFeature,
toggleCADConnection,
openModel,
closeModel,
selectModel,
getModelsBySource,
exportModel,
refreshModels
setCADConnection,
setCADConnecting
}
})