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:
parent
644a826fa1
commit
720a0df4c4
67
CLAUDE.md
67
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,让错误直接暴露
|
||||
|
||||
## 日志系统扩展
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -185,6 +185,9 @@ class ApiClient {
|
||||
|
||||
// 记录错误日志
|
||||
ApiLogger.log(context.method, context.url, context.requestData, errorResponse, duration)
|
||||
|
||||
// 执行后处理钩子(显示失败通知)
|
||||
await PostProcessManager.process(errorResponse, { ...context, duration })
|
||||
}
|
||||
|
||||
// 便捷方法
|
||||
|
||||
@ -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: '打开模型文件'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user