feat: 模型库页面添加拖拽打开模型功能并优化提示文字显示
- 添加拖拽文件到界面打开模型功能 - 添加拖拽时的视觉反馈效果(蓝色虚线边框和背景高亮) - 重构文件加载逻辑,提取loadModelFiles函数复用代码 - 支持拖拽和点击两种方式打开模型文件 - 优化空状态提示文字颜色,从次要文字色改为主文字色,提升可读性 - 添加文件格式验证,只加载支持的3D模型格式 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a8cf0d1329
commit
5ba9231473
@ -1,106 +1,425 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'ModelGalleryPage'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gallery-container">
|
||||
<!-- 页面头部 -->
|
||||
<div class="gallery-header">
|
||||
<div class="header-content">
|
||||
<div class="header-icon">
|
||||
<i class="fas fa-images"></i>
|
||||
<div class="model-gallery-page">
|
||||
<!-- 工具栏 -->
|
||||
<div class="toolbar">
|
||||
<button class="toolbar-btn toolbar-btn-primary" @click="openModel">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<span>打开模型</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 模型树侧边栏 -->
|
||||
<div v-if="hasModel && modelTree" class="model-tree-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<i class="fas fa-sitemap"></i>
|
||||
<span>模型结构树</span>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2>模型库</h2>
|
||||
<p>浏览和管理您的模型资源</p>
|
||||
<div class="tree-container">
|
||||
<ModelTreeNode :node="modelTree" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3D查看器容器 -->
|
||||
<div
|
||||
class="viewer-wrapper"
|
||||
:class="{ dragging: isDragging }"
|
||||
@dragover="handleDragOver"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDrop"
|
||||
>
|
||||
<div v-if="!hasModel" class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<i class="fas fa-cube"></i>
|
||||
</div>
|
||||
<h3>未加载模型</h3>
|
||||
<p>点击"打开模型"按钮选择3D模型文件</p>
|
||||
<p>或将模型文件拖拽到此处</p>
|
||||
<p class="supported-formats">支持格式: OBJ, STL, GLTF, GLB, PLY, 3DS, FBX</p>
|
||||
</div>
|
||||
<div ref="viewerContainer" class="viewer-container" :class="{ hidden: !hasModel }"></div>
|
||||
<div v-if="isLoading" class="loading-overlay">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>加载模型中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<div class="gallery-content">
|
||||
<div class="placeholder-section">
|
||||
<div class="placeholder-icon">
|
||||
<i class="fas fa-cube"></i>
|
||||
</div>
|
||||
<h3>模型库功能开发中</h3>
|
||||
<p>这里将展示所有已保存的模型文件,方便您快速查看和管理</p>
|
||||
|
||||
<div class="feature-preview">
|
||||
<div class="preview-item">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<span>模型分类浏览</span>
|
||||
</div>
|
||||
<div class="preview-item">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>快速搜索定位</span>
|
||||
</div>
|
||||
<div class="preview-item">
|
||||
<i class="fas fa-download"></i>
|
||||
<span>一键导入导出</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 模型信息面板 -->
|
||||
<div v-if="modelInfo" class="model-info-panel">
|
||||
<div class="info-item">
|
||||
<i class="fas fa-file"></i>
|
||||
<span class="info-label">文件名:</span>
|
||||
<span class="info-value">{{ modelInfo.fileName }}</span>
|
||||
</div>
|
||||
<div class="info-divider"></div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-cube"></i>
|
||||
<span class="info-label">顶点数:</span>
|
||||
<span class="info-value">{{ modelInfo.vertices?.toLocaleString() || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-divider"></div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-shapes"></i>
|
||||
<span class="info-label">面数:</span>
|
||||
<span class="info-value">{{ modelInfo.faces?.toLocaleString() || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-divider"></div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
<span class="info-label">网格数:</span>
|
||||
<span class="info-value">{{ modelInfo.meshes || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 模型库页面 - 暂时只显示占位内容
|
||||
import * as OV from 'online-3d-viewer'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import ModelTreeNode from '@/components/ui/ModelTreeNode.vue'
|
||||
|
||||
// 查看器引用
|
||||
const viewerContainer = ref(null)
|
||||
let viewer = null
|
||||
|
||||
// 状态管理
|
||||
const hasModel = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const modelInfo = ref(null)
|
||||
const modelTree = ref(null) // 模型树数据
|
||||
const isDragging = ref(false) // 拖拽状态
|
||||
|
||||
// 初始化查看器
|
||||
onMounted(() => {
|
||||
if (viewerContainer.value) {
|
||||
viewer = new OV.EmbeddedViewer(viewerContainer.value, {
|
||||
backgroundColor: new OV.RGBAColor(220, 222, 225, 255),
|
||||
defaultColor: new OV.RGBColor(150, 150, 150),
|
||||
edgeSettings: new OV.EdgeSettings(false, new OV.RGBColor(80, 80, 80), 1),
|
||||
onModelLoaded: handleModelLoaded
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 清理资源
|
||||
onBeforeUnmount(() => {
|
||||
if (viewer) {
|
||||
viewer.Destroy()
|
||||
viewer = null
|
||||
}
|
||||
})
|
||||
|
||||
// 打开模型文件
|
||||
const openModel = () => {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '.obj,.stl,.gltf,.glb,.ply,.3ds,.fbx,.ifc,.off,.3dm'
|
||||
input.multiple = true
|
||||
input.onchange = async (e) => {
|
||||
const files = Array.from(e.target.files)
|
||||
if (files.length > 0) {
|
||||
await loadModelFiles(files)
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
// 加载模型文件(复用逻辑)
|
||||
const loadModelFiles = async (files) => {
|
||||
isLoading.value = true
|
||||
hasModel.value = false
|
||||
modelInfo.value = null
|
||||
|
||||
try {
|
||||
await viewer.LoadModelFromFileList(files)
|
||||
hasModel.value = true
|
||||
modelInfo.value = {
|
||||
fileName: files[0].name
|
||||
}
|
||||
} catch (error) {
|
||||
hasModel.value = false
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 支持的文件扩展名
|
||||
const supportedExtensions = ['.obj', '.stl', '.gltf', '.glb', '.ply', '.3ds', '.fbx', '.ifc', '.off', '.3dm']
|
||||
|
||||
// 拖拽进入
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
isDragging.value = true
|
||||
}
|
||||
|
||||
// 拖拽离开
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
// 只有离开整个容器才取消高亮
|
||||
if (e.target === e.currentTarget) {
|
||||
isDragging.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽释放
|
||||
const handleDrop = async (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
isDragging.value = false
|
||||
|
||||
const files = Array.from(e.dataTransfer.files)
|
||||
if (files.length === 0) return
|
||||
|
||||
// 验证文件格式
|
||||
const validFiles = files.filter((file) => {
|
||||
const ext = '.' + file.name.split('.').pop().toLowerCase()
|
||||
return supportedExtensions.includes(ext)
|
||||
})
|
||||
|
||||
if (validFiles.length > 0) {
|
||||
await loadModelFiles(validFiles)
|
||||
}
|
||||
}
|
||||
|
||||
// 构建模型树结构
|
||||
const buildModelTree = (model) => {
|
||||
if (!model) return null
|
||||
|
||||
// 递归构建节点树
|
||||
const buildNodeTree = (node, depth = 0) => {
|
||||
const nodeName = node.GetName() || (node.HasParent() ? '未命名节点' : '根节点')
|
||||
const nodeData = {
|
||||
name: nodeName,
|
||||
type: 'node',
|
||||
depth: depth,
|
||||
children: [],
|
||||
expanded: depth < 2 // 默认展开前两层
|
||||
}
|
||||
|
||||
// 添加子节点
|
||||
for (const childNode of node.GetChildNodes()) {
|
||||
nodeData.children.push(buildNodeTree(childNode, depth + 1))
|
||||
}
|
||||
|
||||
// 添加网格
|
||||
for (const meshIndex of node.GetMeshIndices()) {
|
||||
const mesh = model.GetMesh(meshIndex)
|
||||
const meshName = mesh.GetName() || `网格 ${meshIndex}`
|
||||
nodeData.children.push({
|
||||
name: meshName,
|
||||
type: 'mesh',
|
||||
depth: depth + 1,
|
||||
meshIndex: meshIndex,
|
||||
vertexCount: mesh.VertexCount(),
|
||||
triangleCount: mesh.TriangleCount()
|
||||
})
|
||||
}
|
||||
|
||||
return nodeData
|
||||
}
|
||||
|
||||
const rootNode = model.GetRootNode()
|
||||
return buildNodeTree(rootNode)
|
||||
}
|
||||
|
||||
// 模型加载完成回调
|
||||
const handleModelLoaded = () => {
|
||||
isLoading.value = false
|
||||
|
||||
// 强制调整canvas尺寸,确保模型正确显示
|
||||
setTimeout(() => {
|
||||
if (viewer) {
|
||||
viewer.Resize()
|
||||
}
|
||||
}, 100)
|
||||
|
||||
// 获取模型统计信息
|
||||
try {
|
||||
const model = viewer.GetModel()
|
||||
if (model) {
|
||||
let totalVertices = 0
|
||||
let totalFaces = 0
|
||||
let meshCount = 0
|
||||
|
||||
// 遍历模型获取统计信息
|
||||
model.EnumerateMeshes((mesh) => {
|
||||
totalVertices += mesh.VertexCount()
|
||||
totalFaces += mesh.TriangleCount()
|
||||
meshCount++
|
||||
})
|
||||
|
||||
if (modelInfo.value) {
|
||||
modelInfo.value.vertices = totalVertices
|
||||
modelInfo.value.faces = totalFaces
|
||||
modelInfo.value.meshes = meshCount
|
||||
}
|
||||
|
||||
// 构建模型树
|
||||
modelTree.value = buildModelTree(model)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取模型统计信息失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gallery-container {
|
||||
padding: var(--spacing-2xl);
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
.model-gallery-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
.gallery-header {
|
||||
margin-bottom: var(--spacing-3xl);
|
||||
/* 主内容区域样式 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
/* 工具栏样式 */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--color-bg-secondary);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
width: var(--size-64);
|
||||
height: var(--size-64);
|
||||
background: var(--color-primary-gradient);
|
||||
border-radius: var(--size-border-radius-card);
|
||||
.toolbar-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-4xl);
|
||||
color: white;
|
||||
box-shadow: 0 4px 16px var(--color-primary-rgb);
|
||||
}
|
||||
|
||||
.header-text h2 {
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--size-border-radius-button);
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 var(--spacing-sm) 0;
|
||||
font-size: var(--font-size-5xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-size-base);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-all);
|
||||
}
|
||||
|
||||
.header-text p {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
.toolbar-btn:hover:not(:disabled) {
|
||||
background: var(--color-bg-hover);
|
||||
border-color: var(--color-primary);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.toolbar-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.toolbar-btn-primary {
|
||||
background: var(--color-primary-gradient);
|
||||
border-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.toolbar-btn-primary:hover:not(:disabled) {
|
||||
background: var(--color-primary-gradient-hover);
|
||||
box-shadow: 0 4px 15px var(--color-primary-rgb-3);
|
||||
}
|
||||
|
||||
.toolbar-btn i {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.gallery-content {
|
||||
.toolbar-divider {
|
||||
width: 1px;
|
||||
height: var(--spacing-3xl);
|
||||
background: var(--color-border-primary);
|
||||
margin: 0 var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* 模型树侧边栏样式 */
|
||||
.model-tree-sidebar {
|
||||
width: 300px;
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--size-border-radius-card);
|
||||
padding: var(--spacing-5xl);
|
||||
border-right: 1px solid var(--color-border-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.placeholder-section {
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--color-bg-tertiary);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
color: var(--color-text-primary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.sidebar-header i {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* 查看器容器样式 */
|
||||
.viewer-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--color-bg-viewer-light);
|
||||
transition: var(--transition-all);
|
||||
}
|
||||
|
||||
/* 拖拽高亮样式 */
|
||||
.viewer-wrapper.dragging {
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
border: 3px dashed var(--color-primary);
|
||||
box-shadow: inset 0 0 20px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.viewer-wrapper.dragging .empty-state {
|
||||
opacity: 0.7;
|
||||
transform: scale(0.95);
|
||||
transition: var(--transition-all);
|
||||
}
|
||||
|
||||
.viewer-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.viewer-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
padding: var(--spacing-4xl) var(--spacing-2xl);
|
||||
padding: var(--spacing-3xl);
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
.empty-icon {
|
||||
width: var(--size-96);
|
||||
height: var(--size-96);
|
||||
background: var(--color-primary-gradient);
|
||||
@ -108,93 +427,167 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-6xl);
|
||||
color: white;
|
||||
margin: 0 auto var(--spacing-2xl);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
box-shadow: 0 8px 32px var(--color-primary-rgb-3);
|
||||
}
|
||||
|
||||
.placeholder-section h3 {
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 var(--spacing-md) 0;
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
.empty-icon i {
|
||||
font-size: var(--font-size-6xl);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.placeholder-section p {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0 0 var(--spacing-3xl) 0;
|
||||
.empty-state h3 {
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0 0 var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-lg);
|
||||
margin: 0 0 var(--spacing-sm) 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feature-preview {
|
||||
display: flex;
|
||||
gap: var(--spacing-2xl);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: var(--spacing-3xl);
|
||||
.supported-formats {
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: var(--font-size-sm);
|
||||
margin-top: var(--spacing-lg);
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: var(--size-border-radius);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
/* 加载状态样式 */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(30, 35, 45, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: var(--size-64);
|
||||
height: var(--size-64);
|
||||
border: 4px solid var(--color-border-primary);
|
||||
border-top-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay p {
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-lg);
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* 模型信息面板样式 */
|
||||
.model-info-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-xl);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--size-border-radius);
|
||||
min-width: var(--size-150);
|
||||
transition: var(--transition-all);
|
||||
padding: var(--spacing-lg) var(--spacing-xl);
|
||||
background: var(--color-bg-secondary);
|
||||
border-top: 1px solid var(--color-border-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-item:hover {
|
||||
background: var(--color-bg-hover);
|
||||
border-color: var(--color-primary);
|
||||
transform: translateY(-2px);
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.preview-item i {
|
||||
font-size: var(--font-size-3xl);
|
||||
.info-item i {
|
||||
color: var(--color-primary);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.preview-item span {
|
||||
.info-label {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.info-divider {
|
||||
width: 1px;
|
||||
height: var(--spacing-2xl);
|
||||
background: var(--color-border-primary);
|
||||
margin: 0 var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.gallery-container {
|
||||
padding: var(--spacing-lg);
|
||||
.main-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
gap: var(--spacing-md);
|
||||
.model-tree-sidebar {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
width: var(--size-48);
|
||||
height: var(--size-48);
|
||||
font-size: var(--font-size-2xl);
|
||||
.toolbar {
|
||||
padding: var(--spacing-md);
|
||||
gap: var(--spacing-xs);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-text h2 {
|
||||
font-size: var(--font-size-3xl);
|
||||
.toolbar-btn span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
.toolbar-btn {
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.model-info-panel {
|
||||
flex-wrap: wrap;
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex: 1 1 45%;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: var(--size-64);
|
||||
height: var(--size-64);
|
||||
}
|
||||
|
||||
.empty-icon i {
|
||||
font-size: var(--font-size-4xl);
|
||||
}
|
||||
|
||||
.feature-preview {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
.empty-state h3 {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user