diff --git a/CLAUDE.md b/CLAUDE.md index 28629e8..b3c90a1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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,让错误直接暴露 ## 日志系统扩展 diff --git a/src/components/layout/CadSidebar.vue b/src/components/layout/CadSidebar.vue index 8433441..59a05b2 100644 --- a/src/components/layout/CadSidebar.vue +++ b/src/components/layout/CadSidebar.vue @@ -50,7 +50,7 @@
- @@ -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) + } + } } diff --git a/src/services/apiClient.js b/src/services/apiClient.js index e1196d5..5ac47ea 100644 --- a/src/services/apiClient.js +++ b/src/services/apiClient.js @@ -185,6 +185,9 @@ class ApiClient { // 记录错误日志 ApiLogger.log(context.method, context.url, context.requestData, errorResponse, duration) + + // 执行后处理钩子(显示失败通知) + await PostProcessManager.process(errorResponse, { ...context, duration }) } // 便捷方法 diff --git a/src/services/creoApi.js b/src/services/creoApi.js index dc7d492..49480b3 100644 --- a/src/services/creoApi.js +++ b/src/services/creoApi.js @@ -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: '打开模型文件' + } + }) + } } // 导出单例实例 diff --git a/src/stores/cad.js b/src/stores/cad.js index a9c8720..8bd7e34 100644 --- a/src/stores/cad.js +++ b/src/stores/cad.js @@ -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 } }) \ No newline at end of file