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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="project-actions">
|
<div class="project-actions">
|
||||||
<button class="confirm-btn">
|
<button class="confirm-btn" @click="handleOpenModel">
|
||||||
<i class="fas fa-folder-open"></i>
|
<i class="fas fa-folder-open"></i>
|
||||||
打开模型
|
打开模型
|
||||||
</button>
|
</button>
|
||||||
@ -65,6 +65,7 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useCADStore } from '@/stores/cad'
|
import { useCADStore } from '@/stores/cad'
|
||||||
import CadSoftwareGrid from '@/components/ui/CadSoftwareGrid.vue'
|
import CadSoftwareGrid from '@/components/ui/CadSoftwareGrid.vue'
|
||||||
|
import creoApi from '@/services/creoApi'
|
||||||
|
|
||||||
const cadStore = useCADStore()
|
const cadStore = useCADStore()
|
||||||
|
|
||||||
@ -92,9 +93,44 @@ const selectSoftware = (software) => {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// 测试连接
|
// 测试连接
|
||||||
const testConnection = (software) => {
|
const testConnection = async (software) => {
|
||||||
// 调用CAD store的连接测试方法
|
// 设置连接中状态
|
||||||
cadStore.toggleCADConnection(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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -185,6 +185,9 @@ class ApiClient {
|
|||||||
|
|
||||||
// 记录错误日志
|
// 记录错误日志
|
||||||
ApiLogger.log(context.method, context.url, context.requestData, errorResponse, duration)
|
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}>}
|
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
|
||||||
*/
|
*/
|
||||||
async testConnection() {
|
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 { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { getAllCADDefinitions, getDisplayableCADs } from '@/config/cad'
|
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', () => {
|
export const useCADStore = defineStore('cad', () => {
|
||||||
// 从配置生成CAD软件连接状态
|
// 从配置生成CAD软件连接状态
|
||||||
@ -57,49 +31,6 @@ export const useCADStore = defineStore('cad', () => {
|
|||||||
return cadConnections.value.find(cad => cad.connected)
|
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列表(根据显示模式)
|
// 获取可显示的CAD列表(根据显示模式)
|
||||||
const displayableCADs = computed(() => {
|
const displayableCADs = computed(() => {
|
||||||
@ -122,184 +53,45 @@ export const useCADStore = defineStore('cad', () => {
|
|||||||
return cad ? cad.features.includes(feature) : false
|
return cad ? cad.features.includes(feature) : false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连接/断开CAD软件
|
// 设置CAD连接状态(一次只能连接一个)
|
||||||
const toggleCADConnection = async (cadName) => {
|
const setCADConnection = (cadName, connected) => {
|
||||||
const cad = getCADConnection(cadName)
|
// 先断开所有连接
|
||||||
if (!cad || cad.connecting) return
|
cadConnections.value.forEach(cad => {
|
||||||
|
|
||||||
// 设置连接中状态
|
|
||||||
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.connected = false
|
||||||
|
cad.connecting = false
|
||||||
|
if (!connected) {
|
||||||
cad.lastConnected = null
|
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.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}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟打开过程
|
|
||||||
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取统计信息
|
// 设置CAD连接中状态
|
||||||
const getStatistics = computed(() => {
|
const setCADConnecting = (cadName, connecting) => {
|
||||||
const totalModels = openedModels.value.length
|
const cad = getCADConnection(cadName)
|
||||||
const bySource = {}
|
if (cad) {
|
||||||
|
cad.connecting = connecting
|
||||||
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
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
cadConnections,
|
cadConnections,
|
||||||
openedModels,
|
|
||||||
selectedModel,
|
|
||||||
currentCAD,
|
currentCAD,
|
||||||
modelOperations,
|
|
||||||
getStatistics,
|
|
||||||
|
|
||||||
// 配置驱动的计算属性
|
|
||||||
displayableCADs,
|
displayableCADs,
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
getCADConnection,
|
getCADConnection,
|
||||||
cadSupportsFeature,
|
cadSupportsFeature,
|
||||||
toggleCADConnection,
|
setCADConnection,
|
||||||
openModel,
|
setCADConnecting
|
||||||
closeModel,
|
|
||||||
selectModel,
|
|
||||||
getModelsBySource,
|
|
||||||
exportModel,
|
|
||||||
refreshModels
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Loading…
Reference in New Issue
Block a user