feat: 实现Revit IFC导出功能并修复导出页面路由逻辑
- 新增RevitExportTools组件,支持完整IFC导出参数配置 - 在revitApi.js中添加exportIFC方法,调用/api/export/ifc端点 - 修复DashboardView导出页面路由,根据连接软件显示对应导出工具 - 修复组件样式问题,正确显示Revit绿色主题而非黑白灰 - 添加未连接CAD时的友好提示信息 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5389e37ffe
commit
7785b1f1e4
492
src/components/pages/RevitExportTools.vue
Normal file
492
src/components/pages/RevitExportTools.vue
Normal file
@ -0,0 +1,492 @@
|
||||
<template>
|
||||
<div class="export-tools-container revit-theme">
|
||||
<!-- 导出工具头部 -->
|
||||
<div class="export-header">
|
||||
<div class="header-content">
|
||||
<div class="header-icon">
|
||||
<i class="fas fa-download"></i>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2>Revit IFC导出工具</h2>
|
||||
<p>将当前Revit模型导出为IFC格式文件</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前软件信息 -->
|
||||
<div class="export-card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<h3>当前连接信息</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="connected-software-info">
|
||||
<div class="software-display">
|
||||
<div class="software-icon-large">
|
||||
<i class="fas fa-building"></i>
|
||||
</div>
|
||||
<div class="software-details">
|
||||
<h4>Revit</h4>
|
||||
<p>连接状态: <span class="status-connected">已连接</span></p>
|
||||
<p class="no-project">准备导出</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IFC导出设置 -->
|
||||
<div class="export-card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-cog"></i>
|
||||
<h3>IFC导出设置</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>配置IFC导出的参数和选项</p>
|
||||
|
||||
<div class="export-options">
|
||||
<div class="option-group">
|
||||
<label for="output-path">输出路径</label>
|
||||
<input
|
||||
type="text"
|
||||
id="output-path"
|
||||
v-model="exportForm.outputPath"
|
||||
placeholder="D:\export.ifc"
|
||||
class="form-input"
|
||||
>
|
||||
<small class="form-hint">请输入完整的文件路径,包含文件名和.ifc扩展名</small>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label for="ifc-version">IFC版本</label>
|
||||
<select id="ifc-version" v-model="exportForm.version">
|
||||
<option value="0">IFC2x3</option>
|
||||
<option value="1">IFC4</option>
|
||||
</select>
|
||||
<small class="form-hint">选择要导出的IFC格式版本</small>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label for="export-range">导出范围</label>
|
||||
<select id="export-range" v-model="exportForm.range">
|
||||
<option value="0">当前视图</option>
|
||||
<option value="1">整个项目</option>
|
||||
</select>
|
||||
<small class="form-hint">选择要导出的模型范围</small>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label for="file-prefix">文件名前缀</label>
|
||||
<input
|
||||
type="text"
|
||||
id="file-prefix"
|
||||
v-model="exportForm.fileNamePrefix"
|
||||
placeholder="MyExport"
|
||||
class="form-input"
|
||||
>
|
||||
<small class="form-hint">可选的文件名前缀</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-export btn-lg btn-full" :disabled="!canExport" @click="startExport">
|
||||
<i class="fas fa-download"></i>
|
||||
开始导出IFC
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Revit高级选项 -->
|
||||
<div class="export-card revit-advanced-options">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-building"></i>
|
||||
<h3>Revit高级导出选项</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>配置Revit专属的IFC导出高级参数</p>
|
||||
|
||||
<div class="export-options">
|
||||
<div class="option-group">
|
||||
<label>
|
||||
<input type="checkbox" v-model="exportForm.includeSpaceBoundaries">
|
||||
包含空间边界
|
||||
</label>
|
||||
<small class="form-hint">在IFC文件中包含空间边界信息</small>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label>
|
||||
<input type="checkbox" v-model="exportForm.splitWallsAndColumns">
|
||||
分割墙和柱
|
||||
</label>
|
||||
<small class="form-hint">将墙和柱按层分割为独立元素</small>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label>
|
||||
<input type="checkbox" v-model="exportForm.include2DElements">
|
||||
包含2D元素
|
||||
</label>
|
||||
<small class="form-hint">导出2D图形元素和注释</small>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label for="level-names">指定层级</label>
|
||||
<input
|
||||
type="text"
|
||||
id="level-names"
|
||||
v-model="levelNamesText"
|
||||
@input="updateLevelNames"
|
||||
placeholder="Level 1, Level 2"
|
||||
class="form-input"
|
||||
>
|
||||
<small class="form-hint">用逗号分隔的层级名称,留空则导出所有层级</small>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label for="categories">指定类别</label>
|
||||
<input
|
||||
type="text"
|
||||
id="categories"
|
||||
v-model="categoriesText"
|
||||
@input="updateCategories"
|
||||
placeholder="Walls, Doors, Windows"
|
||||
class="form-input"
|
||||
>
|
||||
<small class="form-hint">用逗号分隔的类别名称,留空则导出所有类别</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed, ref } from 'vue'
|
||||
import revitApi from '@/services/revitApi'
|
||||
|
||||
// 导出表单数据
|
||||
const exportForm = reactive({
|
||||
outputPath: 'D:\\export.ifc',
|
||||
version: 0,
|
||||
range: 0,
|
||||
includeSpaceBoundaries: false,
|
||||
splitWallsAndColumns: false,
|
||||
levelNames: [],
|
||||
categories: [],
|
||||
include2DElements: false,
|
||||
fileNamePrefix: 'MyExport'
|
||||
})
|
||||
|
||||
// 文本输入字段
|
||||
const levelNamesText = ref('')
|
||||
const categoriesText = ref('')
|
||||
|
||||
// 计算属性:是否可以开始导出
|
||||
const canExport = computed(() => {
|
||||
return exportForm.outputPath && exportForm.outputPath.trim()
|
||||
})
|
||||
|
||||
// 更新层级名称数组
|
||||
const updateLevelNames = () => {
|
||||
exportForm.levelNames = levelNamesText.value
|
||||
.split(',')
|
||||
.map(name => name.trim())
|
||||
.filter(name => name)
|
||||
}
|
||||
|
||||
// 更新类别数组
|
||||
const updateCategories = () => {
|
||||
exportForm.categories = categoriesText.value
|
||||
.split(',')
|
||||
.map(cat => cat.trim())
|
||||
.filter(cat => cat)
|
||||
}
|
||||
|
||||
// 开始导出
|
||||
const startExport = async () => {
|
||||
if (!canExport.value) return
|
||||
|
||||
await revitApi.exportIFC({
|
||||
outputPath: exportForm.outputPath,
|
||||
version: exportForm.version,
|
||||
range: exportForm.range,
|
||||
includeSpaceBoundaries: exportForm.includeSpaceBoundaries,
|
||||
splitWallsAndColumns: exportForm.splitWallsAndColumns,
|
||||
levelNames: exportForm.levelNames,
|
||||
categories: exportForm.categories,
|
||||
include2DElements: exportForm.include2DElements,
|
||||
fileNamePrefix: exportForm.fileNamePrefix
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Revit导出工具样式 - 复用主题变量,使用Revit绿色主题 */
|
||||
|
||||
.export-tools-container {
|
||||
padding: var(--model-viewer-padding);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.export-header {
|
||||
margin-bottom: var(--spacing-3xl);
|
||||
}
|
||||
|
||||
.export-header .header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--model-viewer-header-gap);
|
||||
}
|
||||
|
||||
.export-header .header-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: linear-gradient(135deg, #2E8B57 0%, #20B2AA 100%);
|
||||
border-radius: var(--size-border-radius-card);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-icon);
|
||||
color: var(--color-text-primary);
|
||||
box-shadow: 0 4px 12px rgba(46, 139, 87, 0.3);
|
||||
}
|
||||
|
||||
.export-header .header-text h2 {
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 var(--spacing-sm) 0;
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.export-header .header-text p {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
/* 连接软件信息显示 */
|
||||
.connected-software-info {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.software-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
padding: var(--spacing-lg);
|
||||
background: rgba(46, 139, 87, 0.1);
|
||||
border: 1px solid rgba(46, 139, 87, 0.3);
|
||||
border-radius: var(--size-border-radius);
|
||||
}
|
||||
|
||||
.software-icon-large {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: linear-gradient(135deg, #2E8B57 0%, #20B2AA 100%);
|
||||
border-radius: var(--size-border-radius-card);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-3xl);
|
||||
color: var(--color-text-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.software-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.software-details h4 {
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 var(--spacing-sm) 0;
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.software-details p {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0 0 var(--spacing-xs) 0;
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
color: var(--color-success);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.no-project {
|
||||
color: #2E8B57;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.export-card {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--size-border-radius-card);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.export-card .card-header {
|
||||
background: var(--color-bg-tertiary);
|
||||
padding: var(--spacing-xl);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.export-card .card-header i {
|
||||
font-size: var(--font-size-icon);
|
||||
color: #2E8B57;
|
||||
}
|
||||
|
||||
.export-card .card-header h3 {
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.export-card .card-content {
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.export-card .card-content p {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.export-options {
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.option-group {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.option-group label {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--color-bg-input);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--size-border-radius);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-base);
|
||||
box-sizing: border-box;
|
||||
transition: all var(--transition-duration) var(--transition-timing);
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #2E8B57;
|
||||
box-shadow: 0 0 0 2px rgba(46, 139, 87, 0.1);
|
||||
background: var(--color-white-rgb-05);
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
color: var(--color-text-tertiary);
|
||||
margin-top: var(--spacing-xs);
|
||||
display: block;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.option-group select {
|
||||
width: 200px;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--color-bg-input);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--size-border-radius);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-base);
|
||||
transition: all var(--transition-duration) var(--transition-timing);
|
||||
}
|
||||
|
||||
.option-group select:focus {
|
||||
outline: none;
|
||||
border-color: #2E8B57;
|
||||
box-shadow: 0 0 0 2px rgba(46, 139, 87, 0.1);
|
||||
}
|
||||
|
||||
.option-group input[type="checkbox"] {
|
||||
margin-right: var(--spacing-sm);
|
||||
accent-color: #2E8B57;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-xl);
|
||||
border: none;
|
||||
border-radius: var(--size-border-radius-button);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-duration) var(--transition-timing);
|
||||
user-select: none;
|
||||
min-height: 40px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #2E8B57 0%, #20B2AA 100%);
|
||||
border: none;
|
||||
box-shadow: 0 4px 15px rgba(46, 139, 87, 0.3);
|
||||
transition: all var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1);
|
||||
white-space: nowrap;
|
||||
min-width: 150px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.btn-export:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(46, 139, 87, 0.4);
|
||||
}
|
||||
|
||||
.btn-export:disabled {
|
||||
background: var(--color-bg-quaternary);
|
||||
box-shadow: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: var(--spacing-md) var(--spacing-2xl);
|
||||
min-height: 48px;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.btn-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.revit-advanced-options .card-header i {
|
||||
color: #2E8B57;
|
||||
}
|
||||
</style>
|
||||
@ -62,6 +62,22 @@ class RevitApiService {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出IFC文件
|
||||
* @param {Object} exportOptions - 导出选项
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
|
||||
*/
|
||||
async exportIFC(exportOptions) {
|
||||
const url = buildApiUrl(this.softwareName, 'exportIfc')
|
||||
|
||||
return await apiClient.post(url, exportOptions, {
|
||||
operationContext: {
|
||||
software: 'Revit',
|
||||
operation: 'IFC导出'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
||||
@ -125,7 +125,8 @@
|
||||
<!-- 导出工具页面 -->
|
||||
<div v-else-if="currentPage === PAGE_TYPES.EXPORT_TOOLS" class="page-content">
|
||||
<CreoExportTools v-if="currentConnectedSoftware === 'creo'" />
|
||||
<CreoExportTools v-else />
|
||||
<RevitExportTools v-else-if="currentConnectedSoftware === 'revit'" />
|
||||
<div v-else class="no-connection">请先连接CAD软件</div>
|
||||
</div>
|
||||
|
||||
<!-- 格式转换页面 -->
|
||||
@ -273,6 +274,7 @@ import RevitModelViewer from '@/components/model/RevitModelViewer.vue'
|
||||
import RevitAnalysisDashboard from '@/components/pages/RevitAnalysisDashboard.vue'
|
||||
import CreoModelAnalysis from '@/components/pages/CreoModelAnalysis.vue'
|
||||
import CreoExportTools from '@/components/pages/CreoExportTools.vue'
|
||||
import RevitExportTools from '@/components/pages/RevitExportTools.vue'
|
||||
import UniversalConverter from '@/components/pages/UniversalConverter.vue'
|
||||
import ShellAnalysisResult from '@/components/pages/ShellAnalysisResult.vue'
|
||||
import HierarchyAnalysisResult from '@/components/pages/HierarchyAnalysisResult.vue'
|
||||
@ -401,6 +403,16 @@ const closeInfoPanel = () => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-connection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 连接页面样式 */
|
||||
.connection-page {
|
||||
display: flex;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user