feat: 实现层级分析动态树形展开和子组件加载功能
主要功能: • 实现层级分析初始请求使用target_level=0获取顶层模型 • 新增getChildrenComponents API方法支持动态加载子组件 • 重构层级分析结果页面使用响应式数组直接管理组件数据 • 实现点击展开按钮动态加载并插入子组件到正确位置 • 修复子组件层级计算逻辑,确保父组件level+1 • 修复组件选择使用路径作为唯一标识,避免多选问题 • 优化界面显示,移除文件大小列,调整总组件数显示 • 添加componentChildren端点配置支持子组件查询 技术改进: • 从computed改为ref数组提升响应性能 • 使用component.path作为唯一标识解决ID重复问题 • 简化树形视图逻辑,移除复杂的展开状态管理 • 优化API参数处理,支持可选componentId参数 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8bbbcb5383
commit
3c2aab553b
@ -49,7 +49,7 @@
|
||||
<i class="fas fa-cubes"></i>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-value">{{ hierarchyData.length }}</div>
|
||||
<div class="card-value">{{ (analysisResults?.total_components || 0) - 1 }}</div>
|
||||
<div class="card-label">总组件数</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -74,8 +74,8 @@
|
||||
</div>
|
||||
<div class="tree-container">
|
||||
<div
|
||||
v-for="component in sortedHierarchyData"
|
||||
:key="component.id"
|
||||
v-for="(component, index) in hierarchyData"
|
||||
:key="index"
|
||||
v-show="shouldShowNode(component)"
|
||||
class="tree-node"
|
||||
:class="`safety-${component.deletion_safety}`"
|
||||
@ -84,11 +84,11 @@
|
||||
<div
|
||||
class="node-toggle"
|
||||
:class="{
|
||||
expanded: expandedNodes.has(component.id),
|
||||
'has-children': hasChildren(component.id)
|
||||
expanded: component.expanded,
|
||||
'has-children': hasChildren(component)
|
||||
}"
|
||||
@click="toggleNode(component)"
|
||||
v-if="hasChildren(component.id)"
|
||||
@click="toggleNode(component, index)"
|
||||
v-if="hasChildren(component)"
|
||||
>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</div>
|
||||
@ -97,7 +97,7 @@
|
||||
<i class="fas fa-cube"></i>
|
||||
</div>
|
||||
<div class="node-content">
|
||||
<div class="node-name">{{ component.id }}</div>
|
||||
<div class="node-name">{{ component.filename }}</div>
|
||||
<div class="node-info">
|
||||
<span class="node-type">{{ component.type }}</span>
|
||||
<span class="node-level">层级: {{ component.level }}</span>
|
||||
@ -105,8 +105,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-actions">
|
||||
<span class="node-size">{{ component.file_size }}</span>
|
||||
<button class="node-select-btn" :class="{ selected: selectedComponents.has(component.id) }" @click="toggleSelection(component.id)">
|
||||
<button class="node-select-btn" :class="{ selected: selectedComponents.has(component.path) }" @click="toggleSelection(component.path)">
|
||||
<i class="fas fa-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -129,19 +128,18 @@
|
||||
<th>类型</th>
|
||||
<th>层级</th>
|
||||
<th>路径</th>
|
||||
<th>文件大小</th>
|
||||
<th>删除安全性</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="component in hierarchyData" :key="component.id" class="component-row" :class="`safety-${component.deletion_safety}`">
|
||||
<tr v-for="(component, index) in hierarchyData" :key="index" class="component-row" :class="`safety-${component.deletion_safety}`">
|
||||
<td>
|
||||
<input type="checkbox" :checked="selectedComponents.has(component.id)" @change="toggleSelection(component.id)">
|
||||
<input type="checkbox" :checked="selectedComponents.has(component.path)" @change="toggleSelection(component.path)">
|
||||
</td>
|
||||
<td>
|
||||
<div class="component-name">
|
||||
<i class="fas fa-cube"></i>
|
||||
{{ component.name }}
|
||||
{{ component.filename }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ component.type }}</td>
|
||||
@ -149,7 +147,6 @@
|
||||
<td>
|
||||
<div class="component-path">{{ component.path }}</div>
|
||||
</td>
|
||||
<td>{{ component.file_size }}</td>
|
||||
<td>
|
||||
<span class="safety-badge" :class="`safety-${component.deletion_safety}`">
|
||||
{{ getSafetyLabel(component.deletion_safety) }}
|
||||
@ -190,7 +187,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { creoApi } from '@/services/creoApi'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
|
||||
@ -205,7 +202,7 @@ const emit = defineEmits(['show-hierarchy-deletion-params'])
|
||||
|
||||
const currentView = ref('tree')
|
||||
const selectedComponents = ref(new Set())
|
||||
const expandedNodes = ref(new Set())
|
||||
const hierarchyData = ref([]) // 直接存储所有组件数据
|
||||
|
||||
// 直接使用API数据,照抄ShellAnalysisResult的方式
|
||||
const analysisResults = computed(() => {
|
||||
@ -215,8 +212,8 @@ const analysisResults = computed(() => {
|
||||
return props.analysisData.data
|
||||
})
|
||||
|
||||
// 从API数据中提取层级数据
|
||||
const hierarchyData = computed(() => {
|
||||
// 初始化层级数据
|
||||
const initializeHierarchyData = () => {
|
||||
const allData = []
|
||||
|
||||
if (analysisResults.value) {
|
||||
@ -229,7 +226,8 @@ const hierarchyData = computed(() => {
|
||||
level.components.forEach(component => {
|
||||
allData.push({
|
||||
...component,
|
||||
deletion_safety: component.deletion_safety || 'safe'
|
||||
deletion_safety: component.deletion_safety || 'safe',
|
||||
expanded: false // 添加展开状态
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -243,8 +241,12 @@ const hierarchyData = computed(() => {
|
||||
if (recommendations.safe_deletions && Array.isArray(recommendations.safe_deletions)) {
|
||||
recommendations.safe_deletions.forEach(comp => {
|
||||
// 避免重复添加已存在的组件
|
||||
if (!allData.find(existing => existing.id === comp.id)) {
|
||||
allData.push({ ...comp, deletion_safety: 'safe' })
|
||||
if (!allData.find(existing => existing.path === comp.path && existing.id === comp.id)) {
|
||||
allData.push({
|
||||
...comp,
|
||||
deletion_safety: 'safe',
|
||||
expanded: false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -252,16 +254,20 @@ const hierarchyData = computed(() => {
|
||||
if (recommendations.risky_deletions && Array.isArray(recommendations.risky_deletions)) {
|
||||
recommendations.risky_deletions.forEach(comp => {
|
||||
// 避免重复添加已存在的组件
|
||||
if (!allData.find(existing => existing.id === comp.id)) {
|
||||
allData.push({ ...comp, deletion_safety: 'risky' })
|
||||
if (!allData.find(existing => existing.path === comp.path && existing.id === comp.id)) {
|
||||
allData.push({
|
||||
...comp,
|
||||
deletion_safety: 'risky',
|
||||
expanded: false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allData
|
||||
})
|
||||
hierarchyData.value = allData
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -292,59 +298,66 @@ const getParentId = (component) => {
|
||||
return null
|
||||
}
|
||||
|
||||
// 对组件按路径排序(路径自然包含了层级关系)
|
||||
const sortedHierarchyData = computed(() => {
|
||||
return [...hierarchyData.value].sort((a, b) => {
|
||||
// 直接按路径排序,这样父子组件会自然相邻
|
||||
const pathA = a.path || a.id || ''
|
||||
const pathB = b.path || b.id || ''
|
||||
return pathA.localeCompare(pathB)
|
||||
})
|
||||
})
|
||||
// 由于现在直接操作数组顺序,不再需要排序
|
||||
|
||||
// 判断节点是否有子节点
|
||||
const hasChildren = (nodeId) => {
|
||||
return hierarchyData.value.some(comp => getParentId(comp) === nodeId)
|
||||
const hasChildren = (component) => {
|
||||
return component.children_count > 0
|
||||
}
|
||||
|
||||
// 检查节点是否应该显示(所有祖先节点都已展开)
|
||||
// 由于现在直接管理数组顺序,所有在数组中的组件都应该显示
|
||||
const shouldShowNode = (component) => {
|
||||
// 根节点始终显示
|
||||
if (component.level === 0) return true
|
||||
|
||||
// 检查所有祖先节点是否都已展开
|
||||
let current = component
|
||||
while (current && current.level > 0) {
|
||||
const parentId = getParentId(current)
|
||||
if (!parentId) return false // 找不到父节点
|
||||
|
||||
// 如果父节点没有展开,则不显示
|
||||
if (!expandedNodes.value.has(parentId)) return false
|
||||
|
||||
// 继续向上查找
|
||||
current = hierarchyData.value.find(c => c.id === parentId)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const toggleNode = (component) => {
|
||||
const nodeId = component.id
|
||||
|
||||
if (expandedNodes.value.has(nodeId)) {
|
||||
// 收起节点
|
||||
expandedNodes.value.delete(nodeId)
|
||||
const toggleNode = async (component, index) => {
|
||||
if (component.expanded) {
|
||||
// 收起节点:移除子组件
|
||||
component.expanded = false
|
||||
// 找到所有子组件并移除
|
||||
let removeCount = 0
|
||||
for (let i = index + 1; i < hierarchyData.value.length; i++) {
|
||||
const nextComponent = hierarchyData.value[i]
|
||||
if (nextComponent.level > component.level) {
|
||||
removeCount++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (removeCount > 0) {
|
||||
hierarchyData.value.splice(index + 1, removeCount)
|
||||
}
|
||||
} else {
|
||||
// 展开节点
|
||||
expandedNodes.value.add(nodeId)
|
||||
// 展开节点:加载并插入子组件
|
||||
if (component.children_count > 0) {
|
||||
try {
|
||||
const componentId = component.level === 0 ? null : component.id
|
||||
const result = await creoApi.getChildrenComponents(component.path, componentId)
|
||||
if (result.success && result.data.data.children) {
|
||||
// 处理子组件数据
|
||||
const processedChildren = result.data.data.children.map(child => ({
|
||||
...child,
|
||||
level: component.level + 1, // 使用父组件level + 1
|
||||
parent_id: component.id,
|
||||
deletion_safety: child.deletion_safety || 'safe',
|
||||
expanded: false
|
||||
}))
|
||||
// 在当前组件后插入子组件
|
||||
hierarchyData.value.splice(index + 1, 0, ...processedChildren)
|
||||
component.expanded = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载子组件失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSelection = (componentId) => {
|
||||
if (selectedComponents.value.has(componentId)) {
|
||||
selectedComponents.value.delete(componentId)
|
||||
const toggleSelection = (componentPath) => {
|
||||
if (selectedComponents.value.has(componentPath)) {
|
||||
selectedComponents.value.delete(componentPath)
|
||||
} else {
|
||||
selectedComponents.value.add(componentId)
|
||||
selectedComponents.value.add(componentPath)
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,10 +397,8 @@ const deleteSelectedComponents = async () => {
|
||||
}
|
||||
)
|
||||
|
||||
// 收集选中组件的路径
|
||||
// 收集选中组件的路径(selectedComponents已经存储的是路径)
|
||||
const componentPaths = Array.from(selectedComponents.value)
|
||||
.map(id => hierarchyData.value.find(c => c.id === id)?.path)
|
||||
.filter(Boolean)
|
||||
|
||||
// 调用现有API删除组件(通知由apiClient钩子自动处理)
|
||||
const result = await creoApi.deleteComponentsByPath(componentPaths)
|
||||
@ -396,6 +407,16 @@ const deleteSelectedComponents = async () => {
|
||||
selectedComponents.value.clear()
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => {
|
||||
initializeHierarchyData()
|
||||
})
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.analysisData, () => {
|
||||
initializeHierarchyData()
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -89,6 +89,7 @@ const CAD_SOFTWARE_DEFINITIONS = {
|
||||
shellAnalysis: '/api/analysis/shell-analysis',
|
||||
optimization: '/api/creo/shrinkwrap/shell',
|
||||
deleteComponent: '/api/creo/component/delete-by-path',
|
||||
componentChildren: '/api/creo/component/children',
|
||||
export: '/api/export/model'
|
||||
}
|
||||
},
|
||||
|
||||
@ -99,7 +99,8 @@ class CreoApiService {
|
||||
"software_type": SOFTWARE_TYPE,
|
||||
"project_name": "overall_top_design.asm",
|
||||
"max_depth": DEFAULT_MAX_RESULTS,
|
||||
"include_geometry": true
|
||||
"include_geometry": true,
|
||||
"target_level": 0
|
||||
}, {
|
||||
operationContext: {
|
||||
software: 'Creo',
|
||||
@ -241,6 +242,36 @@ class CreoApiService {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定组件的子组件
|
||||
* @param {string} componentPath - 组件路径
|
||||
* @param {number} componentId - 组件ID (Creo Feature ID)
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
|
||||
*/
|
||||
async getChildrenComponents(componentPath, componentId = null) {
|
||||
if (!componentPath) {
|
||||
throw new Error('组件路径不能为空')
|
||||
}
|
||||
|
||||
const url = buildApiUrl(this.softwareName, 'componentChildren')
|
||||
|
||||
const requestData = {
|
||||
"component_path": componentPath
|
||||
}
|
||||
|
||||
// 只有当componentId存在时才添加到请求中
|
||||
if (componentId) {
|
||||
requestData.component_id = componentId
|
||||
}
|
||||
|
||||
return await apiClient.post(url, requestData, {
|
||||
operationContext: {
|
||||
software: 'Creo',
|
||||
operation: '获取子组件'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
||||
Loading…
Reference in New Issue
Block a user