feat: 添加Revit模型轻量化处理功能,包括专用界面、API服务和分析仪表盘。
This commit is contained in:
parent
fd4c9fefa1
commit
29904d4479
6
.agents/rules/rule.md
Normal file
6
.agents/rules/rule.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
这个项目通过不同的cad插件,来管理不同的cad软件。这个项目属于前端项目,来连接不同的cad插件;
|
||||
项目如果接入新的接口,要注意1)接口的标准接入流程是什么,按照项目现有的接入流程来;2)接口是针对哪个cad插件来的,不要搞错
|
||||
@ -57,23 +57,24 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="revit-strategy-card disabled"
|
||||
data-strategy="custom"
|
||||
class="revit-strategy-card"
|
||||
data-strategy="envelope-optimization"
|
||||
@click="openEnvelopeOptimization"
|
||||
>
|
||||
<div class="strategy-icon">
|
||||
<i class="fas fa-sliders-h"></i>
|
||||
<i class="fas fa-compress-arrows-alt"></i>
|
||||
</div>
|
||||
<h5>自定义分析</h5>
|
||||
<h5>模型轻量化处理</h5>
|
||||
<p>
|
||||
根据项目特点自定义分析参数,手动选择需要优化的建筑构件类型和优化级别。
|
||||
对Revit模型进行薄壳轻量化,保留主要外部轮廓,删除多余内部模型,显著减少文件体积。
|
||||
</p>
|
||||
<div class="features">
|
||||
<span>参数调节</span>
|
||||
<span>构件选择</span>
|
||||
<span>精细控制</span>
|
||||
<span>专业定制</span>
|
||||
<span>保留轮廓</span>
|
||||
<span>删除内部</span>
|
||||
<span>建筑专用</span>
|
||||
<span>模型减重</span>
|
||||
</div>
|
||||
<div class="status-badge">开发中</div>
|
||||
<div class="status-badge" style="background: var(--color-success)">新功能</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -235,6 +236,10 @@ const openProject = () => {
|
||||
emit('page-change', 'connection')
|
||||
}
|
||||
|
||||
const openEnvelopeOptimization = () => {
|
||||
emit('page-change', 'revit-envelope-optimization')
|
||||
}
|
||||
|
||||
const refreshConnection = () => {
|
||||
ElNotification({
|
||||
title: '刷新连接',
|
||||
|
||||
681
src/components/pages/RevitEnvelopeOptimization.vue
Normal file
681
src/components/pages/RevitEnvelopeOptimization.vue
Normal file
@ -0,0 +1,681 @@
|
||||
<template>
|
||||
<div class="revit-envelope-optimization revit-theme">
|
||||
<div class="header-actions">
|
||||
<button class="icon-btn" @click="goBack" title="返回">
|
||||
<i class="fas fa-arrow-left"></i> 返回分析中心
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="optimization-header">
|
||||
<h2>
|
||||
<i class="fas fa-compress-arrows-alt"></i>
|
||||
Revit模型轻量化处理
|
||||
</h2>
|
||||
<p class="subtitle">保留建筑模型的外部轮廓,自动删除内部构件,显著减少文件体积并加速渲染。</p>
|
||||
</div>
|
||||
|
||||
<!-- 任务参数设置区 -->
|
||||
<div class="settings-panel" v-if="taskStatus === 'idle' || taskStatus === 'failed' || taskStatus === 'cancelled'">
|
||||
<div class="setting-group">
|
||||
<label>处理模式 (Mode)</label>
|
||||
<div class="mode-options">
|
||||
<label class="mode-card" :class="{ active: formData.mode === 'Conservative' }">
|
||||
<input type="radio" v-model="formData.mode" value="Conservative" />
|
||||
<div class="mode-content">
|
||||
<h5>保守 保留</h5>
|
||||
<p>最大程度保留构件</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="mode-card" :class="{ active: formData.mode === 'Standard' }">
|
||||
<input type="radio" v-model="formData.mode" value="Standard" />
|
||||
<div class="mode-content">
|
||||
<h5>标准 (推荐)</h5>
|
||||
<p>平衡显示效果与体积</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="mode-card" :class="{ active: formData.mode === 'Aggressive' }">
|
||||
<input type="radio" v-model="formData.mode" value="Aggressive" />
|
||||
<div class="mode-content">
|
||||
<h5>激进 删除</h5>
|
||||
<p>尽可能多地移除内部细节</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="mode-card" :class="{ active: formData.mode === 'EnvelopeOnly' }">
|
||||
<input type="radio" v-model="formData.mode" value="EnvelopeOnly" />
|
||||
<div class="mode-content">
|
||||
<h5>仅保留轮廓</h5>
|
||||
<p>严格只保留最外层表皮</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-group toggle-group">
|
||||
<label>安全设置</label>
|
||||
<div class="toggle-control">
|
||||
<span>操作前备份原始文件?</span>
|
||||
<el-switch v-model="formData.backupOriginal" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-bar">
|
||||
<button class="primary-btn" @click="startTask" :disabled="isStarting">
|
||||
<i class="fas" :class="isStarting ? 'fa-spinner fa-spin' : 'fa-play'"></i>
|
||||
{{ isStarting ? '正在初始化...' : '开始执行轻量化' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMessage" class="error-message">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务执行中进度区 -->
|
||||
<div class="execution-panel" v-else-if="taskStatus === 'Running' || taskStatus === 'Pending'">
|
||||
<h3>
|
||||
<i class="fas fa-cog fa-spin"></i>
|
||||
正在处理模型,请耐心等待...
|
||||
</h3>
|
||||
<p class="status-desc">当前状态: {{ taskStatus === 'Pending' ? '排队中' : '运行中' }}</p>
|
||||
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill active"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="execution-actions">
|
||||
<button class="cancel-btn" @click="cancelTask">
|
||||
<i class="fas fa-times"></i> 取消任务
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务结果区 -->
|
||||
<div class="result-panel" v-else-if="taskStatus === 'Completed' && taskResult">
|
||||
<div class="success-header">
|
||||
<i class="fas fa-check-circle success-icon"></i>
|
||||
<h3>模型轻量化完成</h3>
|
||||
</div>
|
||||
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon"><i class="fas fa-trash-alt"></i></div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">{{ taskResult.removedCount }}</div>
|
||||
<div class="metric-label">移除内部构件数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon"><i class="fas fa-compress"></i></div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value highlight">{{ taskResult.reduction }}</div>
|
||||
<div class="metric-label">体积减小比例</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon"><i class="fas fa-stopwatch"></i></div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">{{ taskResult.processingTimeSeconds }}s</div>
|
||||
<div class="metric-label">处理耗时</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="details-box">
|
||||
<h4>文件大小对比</h4>
|
||||
<div class="size-comparison">
|
||||
<div class="size-item original">
|
||||
<span>原始大小</span>
|
||||
<strong>{{ taskResult.originalSize }}</strong>
|
||||
</div>
|
||||
<div class="size-arrow">
|
||||
<i class="fas fa-long-arrow-alt-right"></i>
|
||||
</div>
|
||||
<div class="size-item optimized">
|
||||
<span>优化后大小</span>
|
||||
<strong>{{ taskResult.optimizedSize }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="taskResult.backupPath" class="backup-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
原文件已备份至: <span>{{ taskResult.backupPath }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-actions">
|
||||
<button class="primary-btn" @click="resetTask">
|
||||
<i class="fas fa-redo"></i> 再次处理其他参数
|
||||
</button>
|
||||
<button class="secondary-btn" @click="goBack">
|
||||
<i class="fas fa-home"></i> 返回分析中心
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onUnmounted } from 'vue'
|
||||
import { ElNotification, ElSwitch } from 'element-plus'
|
||||
import revitApi from '@/services/revitApi'
|
||||
|
||||
const emit = defineEmits(['page-change'])
|
||||
|
||||
// 状态定义
|
||||
// idle | Pending | Running | Completed | Failed | Cancelled
|
||||
const taskStatus = ref('idle')
|
||||
const currentTaskId = ref(null)
|
||||
const isStarting = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const taskResult = ref(null)
|
||||
|
||||
// 轮询定时器
|
||||
let pollingTimer = null
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
mode: 'EnvelopeOnly',
|
||||
backupOriginal: true
|
||||
})
|
||||
|
||||
// 返回函数
|
||||
const goBack = () => {
|
||||
emit('page-change', 'analysis-tools')
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
const resetTask = () => {
|
||||
taskStatus.value = 'idle'
|
||||
currentTaskId.value = null
|
||||
taskResult.value = null
|
||||
errorMessage.value = ''
|
||||
}
|
||||
|
||||
// 停止轮询
|
||||
const stopPolling = () => {
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer)
|
||||
pollingTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 启动任务
|
||||
const startTask = async () => {
|
||||
isStarting.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
const res = await revitApi.executeShellOptimization(formData.mode, formData.backupOriginal)
|
||||
if (res.success && res.data && res.data.code === 202) {
|
||||
currentTaskId.value = res.data.data.taskId
|
||||
taskStatus.value = 'Pending'
|
||||
// 启动轮询
|
||||
startPolling()
|
||||
ElNotification({
|
||||
title: '任务已创建',
|
||||
message: '轻量化任务已提交,后台开始处理。',
|
||||
type: 'success'
|
||||
})
|
||||
} else {
|
||||
taskStatus.value = 'failed'
|
||||
errorMessage.value = res.error || '创建任务失败,返回数据异常。'
|
||||
}
|
||||
} catch (err) {
|
||||
taskStatus.value = 'failed'
|
||||
errorMessage.value = err.message || '网络请求错误'
|
||||
} finally {
|
||||
isStarting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询逻辑
|
||||
const startPolling = () => {
|
||||
stopPolling() // 确保没有重复的定时器
|
||||
pollingTimer = setInterval(async () => {
|
||||
if (!currentTaskId.value) return
|
||||
|
||||
try {
|
||||
const res = await revitApi.getTaskStatus(currentTaskId.value)
|
||||
if (res.success && res.data && res.data.data) {
|
||||
const data = res.data.data
|
||||
taskStatus.value = data.status
|
||||
|
||||
if (data.status === 'Completed') {
|
||||
stopPolling()
|
||||
taskResult.value = data.result
|
||||
ElNotification({
|
||||
title: '处理完成',
|
||||
message: '模型轻量化处理已成功执行完成!',
|
||||
type: 'success'
|
||||
})
|
||||
} else if (data.status === 'Failed') {
|
||||
stopPolling()
|
||||
errorMessage.value = data.errorMessage || '处理过程中出现未知错误'
|
||||
ElNotification({
|
||||
title: '处理失败',
|
||||
message: errorMessage.value,
|
||||
type: 'error'
|
||||
})
|
||||
} else if (data.status === 'Cancelled') {
|
||||
stopPolling()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Task status polling failed:', err)
|
||||
// 如果网络短暂断开,可以允许几次失败,这里为简单直接报错
|
||||
stopPolling()
|
||||
taskStatus.value = 'failed'
|
||||
errorMessage.value = '轮询状态失败: ' + err.message
|
||||
}
|
||||
}, 2000) // 每2秒查询一次
|
||||
}
|
||||
|
||||
// 取消任务
|
||||
const cancelTask = async () => {
|
||||
if (!currentTaskId.value) return
|
||||
|
||||
try {
|
||||
const res = await revitApi.cancelTask(currentTaskId.value)
|
||||
if (res.success) {
|
||||
taskStatus.value = 'Cancelled'
|
||||
stopPolling()
|
||||
ElNotification({
|
||||
title: '已取消',
|
||||
message: '任务已被成功取消。',
|
||||
type: 'info'
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
ElNotification({
|
||||
title: '取消失败',
|
||||
message: err.message,
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 页面销毁时清理状态
|
||||
onUnmounted(() => {
|
||||
stopPolling()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.revit-envelope-optimization {
|
||||
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-primary) 100%);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
animation: slideInUp 0.4s ease-out;
|
||||
color: var(--color-text-primary);
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-primary);
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.icon-btn:hover {
|
||||
transform: translateX(-4px);
|
||||
color: #5580ff;
|
||||
}
|
||||
|
||||
.optimization-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.optimization-header h2 {
|
||||
color: var(--color-primary);
|
||||
font-size: 1.8em;
|
||||
font-weight: 600;
|
||||
margin: 0 0 12px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.optimization-header .subtitle {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1em;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 设定区 */
|
||||
.settings-panel {
|
||||
background: var(--color-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.setting-group {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.setting-group > label {
|
||||
display: block;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.mode-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mode-card {
|
||||
display: block;
|
||||
position: relative;
|
||||
border: 2px solid var(--color-border-primary);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
.mode-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.mode-card.active {
|
||||
border-color: var(--color-primary);
|
||||
background: linear-gradient(135deg, rgba(58, 114, 255, 0.1) 0%, rgba(58, 114, 255, 0.05) 100%);
|
||||
box-shadow: 0 4px 15px rgba(58, 114, 255, 0.2);
|
||||
}
|
||||
.mode-card input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
.mode-content h5 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1.1em;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.mode-content p {
|
||||
margin: 0;
|
||||
font-size: 0.85em;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.toggle-group {
|
||||
background: var(--color-bg-secondary);
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.toggle-control {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
text-align: center;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background: linear-gradient(135deg, #3a72ff 0%, #2f5ce6 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 14px 40px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 15px rgba(58, 114, 255, 0.4);
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.primary-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(58, 114, 255, 0.6);
|
||||
}
|
||||
.primary-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 8px;
|
||||
padding: 14px 40px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.secondary-btn:hover {
|
||||
background: var(--color-white-rgb-1);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 20px;
|
||||
padding: 12px;
|
||||
background: rgba(255, 73, 73, 0.1);
|
||||
color: #ff4949;
|
||||
border-left: 4px solid #ff4949;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 执行进度区 */
|
||||
.execution-panel {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.execution-panel h3 {
|
||||
color: var(--color-primary);
|
||||
font-size: 1.6em;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.status-desc {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
max-width: 500px;
|
||||
margin: 0 auto 40px auto;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
background: var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #3a72ff, #00d2ff);
|
||||
width: 50%;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.progress-fill.active {
|
||||
animation: progressIndeterminate 2s infinite ease-in-out;
|
||||
transform-origin: 0% 50%;
|
||||
}
|
||||
|
||||
@keyframes progressIndeterminate {
|
||||
0% { transform: translateX(-100%) scaleX(0.2); }
|
||||
50% { transform: translateX(0) scaleX(1); }
|
||||
100% { transform: translateX(200%) scaleX(0.2); }
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: transparent;
|
||||
color: #ff4949;
|
||||
border: 1px solid #ff4949;
|
||||
border-radius: 6px;
|
||||
padding: 10px 24px;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.cancel-btn:hover {
|
||||
background: rgba(255, 73, 73, 0.1);
|
||||
}
|
||||
|
||||
/* 结果区 */
|
||||
.result-panel {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.success-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.success-icon {
|
||||
font-size: 64px;
|
||||
color: #13ce66;
|
||||
margin-bottom: 16px;
|
||||
text-shadow: 0 4px 15px rgba(19, 206, 102, 0.3);
|
||||
}
|
||||
.success-header h3 {
|
||||
font-size: 1.8em;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.metric-icon {
|
||||
font-size: 32px;
|
||||
color: var(--color-primary);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: rgba(58, 114, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.metric-info {
|
||||
flex: 1;
|
||||
}
|
||||
.metric-value {
|
||||
font-size: 1.8em;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.metric-value.highlight {
|
||||
color: #13ce66;
|
||||
}
|
||||
.metric-label {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9em;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.details-box {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.details-box h4 {
|
||||
margin: 0 0 20px 0;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.size-comparison {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
background: var(--color-bg-secondary);
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.size-item {
|
||||
text-align: center;
|
||||
}
|
||||
.size-item span {
|
||||
display: block;
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.size-item strong {
|
||||
font-size: 1.6em;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.size-item.optimized strong {
|
||||
color: #13ce66;
|
||||
}
|
||||
|
||||
.size-arrow i {
|
||||
font-size: 24px;
|
||||
color: var(--color-border-primary);
|
||||
}
|
||||
|
||||
.backup-info {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.95em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.backup-info span {
|
||||
color: var(--color-primary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
@ -111,7 +111,9 @@ const CAD_SOFTWARE_DEFINITIONS = {
|
||||
connect: '/api/health',
|
||||
overview: '/api/overview',
|
||||
shellAnalysis: '/api/shell/analyze',
|
||||
exportIfc: '/api/export/ifc'
|
||||
exportIfc: '/api/export/ifc',
|
||||
shellExecute: '/api/shell/execute',
|
||||
taskStatus: '/api/task'
|
||||
}
|
||||
},
|
||||
PDMS: {
|
||||
|
||||
@ -12,12 +12,14 @@ export const PAGE_TYPES = {
|
||||
GEOMETRY_COMPLEXITY_RESULT: 'geometry-complexity-result',
|
||||
GEOMETRY_OPTIMIZATION_PARAMS: 'geometry-optimization-params',
|
||||
GEOMETRY_OPTIMIZATION_RESULT: 'geometry-optimization-result',
|
||||
FILE_MANAGEMENT: 'file-management'
|
||||
FILE_MANAGEMENT: 'file-management',
|
||||
REVIT_ENVELOPE_OPTIMIZATION: 'revit-envelope-optimization'
|
||||
}
|
||||
|
||||
// 需要CAD连接的页面
|
||||
export const CAD_REQUIRED_PAGES = [
|
||||
PAGE_TYPES.MODEL_VIEWER,
|
||||
PAGE_TYPES.ANALYSIS_TOOLS,
|
||||
PAGE_TYPES.EXPORT_TOOLS
|
||||
PAGE_TYPES.EXPORT_TOOLS,
|
||||
PAGE_TYPES.REVIT_ENVELOPE_OPTIMIZATION
|
||||
]
|
||||
@ -78,6 +78,58 @@ class RevitApiService {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起删除任务(保留轮廓)
|
||||
* @param {string} mode - Conservative | Standard | Aggressive | EnvelopeOnly
|
||||
* @param {boolean} backupOriginal - 是否备份原文件(true/false)
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
|
||||
*/
|
||||
async executeShellOptimization(mode = 'EnvelopeOnly', backupOriginal = true) {
|
||||
const url = buildApiUrl(this.softwareName, 'shellExecute')
|
||||
return await apiClient.post(url, {
|
||||
mode,
|
||||
backupOriginal
|
||||
}, {
|
||||
operationContext: {
|
||||
software: 'Revit',
|
||||
operation: '模型轻量化处理'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询任务状态
|
||||
* @param {string} taskId - 任务ID
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
|
||||
*/
|
||||
async getTaskStatus(taskId) {
|
||||
const baseUrl = buildApiUrl(this.softwareName, 'taskStatus')
|
||||
const url = `${baseUrl}/${taskId}`
|
||||
return await apiClient.get(url, {
|
||||
operationContext: {
|
||||
software: 'Revit',
|
||||
operation: '获取当前任务状态',
|
||||
silent: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消任务
|
||||
* @param {string} taskId - 任务ID
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: string}>}
|
||||
*/
|
||||
async cancelTask(taskId) {
|
||||
const baseUrl = buildApiUrl(this.softwareName, 'taskStatus')
|
||||
const url = `${baseUrl}/${taskId}`
|
||||
return await apiClient.delete(url, null, {
|
||||
operationContext: {
|
||||
software: 'Revit',
|
||||
operation: '取消轻量化任务'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
||||
@ -65,6 +65,7 @@ import HierarchyDeletionParamsPage from '@/components/pages/HierarchyDeletionPar
|
||||
import GeometryComplexityResult from '@/components/pages/GeometryComplexityResult.vue'
|
||||
import GeometryOptimizationParams from '@/components/pages/GeometryOptimizationParams.vue'
|
||||
import GeometryOptimizationResult from '@/components/pages/GeometryOptimizationResult.vue'
|
||||
import RevitEnvelopeOptimization from '@/components/pages/RevitEnvelopeOptimization.vue'
|
||||
import FileManagementPage from '@/components/pages/FileManagementPage.vue'
|
||||
import InfoManagementPanel from '@/components/layout/InfoManagementPanel.vue'
|
||||
import { PAGE_TYPES } from '@/config/pages'
|
||||
@ -110,7 +111,8 @@ const pageComponentMap = {
|
||||
[PAGE_TYPES.GEOMETRY_COMPLEXITY_RESULT]: GeometryComplexityResult,
|
||||
[PAGE_TYPES.GEOMETRY_OPTIMIZATION_PARAMS]: GeometryOptimizationParams,
|
||||
[PAGE_TYPES.GEOMETRY_OPTIMIZATION_RESULT]: GeometryOptimizationResult,
|
||||
[PAGE_TYPES.FILE_MANAGEMENT]: FileManagementPage
|
||||
[PAGE_TYPES.FILE_MANAGEMENT]: FileManagementPage,
|
||||
[PAGE_TYPES.REVIT_ENVELOPE_OPTIMIZATION]: RevitEnvelopeOptimization
|
||||
}
|
||||
|
||||
// 当前组件computed
|
||||
|
||||
Loading…
Reference in New Issue
Block a user