|
|
@@ -82,9 +82,13 @@
|
|
|
<!-- 步骤1: 上传图片和描述 -->
|
|
|
@if (!aiDesignAnalysisResult) {
|
|
|
<div class="upload-section">
|
|
|
- <!-- 已上传的文件 -->
|
|
|
+ <!-- 已上传的文件 - 支持拖拽 -->
|
|
|
@if (aiDesignUploadedFiles.length > 0) {
|
|
|
- <div class="uploaded-files">
|
|
|
+ <div class="uploaded-files"
|
|
|
+ (drop)="onAIFileDrop($event)"
|
|
|
+ (dragover)="onAIFileDragOver($event)"
|
|
|
+ (dragleave)="onAIFileDragLeave($event)"
|
|
|
+ [class.drag-over]="aiDesignDragOver">
|
|
|
@for (file of aiDesignUploadedFiles; track file.url; let i = $index) {
|
|
|
<div class="file-item" [class.is-image]="file.extension && ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.extension)">
|
|
|
@if (file.extension && ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.extension)) {
|
|
|
@@ -116,6 +120,18 @@
|
|
|
<div class="add-more" (click)="triggerAIDialogFileInput()">
|
|
|
<div class="add-icon">+</div>
|
|
|
<div class="add-text">继续添加</div>
|
|
|
+ <div class="add-hint">或拖拽文件到此</div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 拖拽提示遮罩层 -->
|
|
|
+ @if (aiDesignDragOver) {
|
|
|
+ <div class="drag-overlay">
|
|
|
+ <div class="drag-hint-content">
|
|
|
+ <div class="drag-icon">📥</div>
|
|
|
+ <p>松开鼠标即可添加</p>
|
|
|
+ <p class="drag-support">支持:图片、文字、URL</p>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
}
|
|
|
</div>
|
|
|
@@ -560,588 +576,39 @@
|
|
|
|
|
|
<!-- 全局需求 (始终显示) -->
|
|
|
<div class="global-requirements">
|
|
|
- <!-- 空间需求管理 - 新布局 -->
|
|
|
- <div class="card space-requirements-card">
|
|
|
- <div class="card-header">
|
|
|
- <h3 class="card-title">
|
|
|
- <span class="icon">🏠</span>
|
|
|
- 空间需求管理
|
|
|
- </h3>
|
|
|
- <p class="card-subtitle">按空间管理参考图片、CAD文件和特殊需求</p>
|
|
|
- </div>
|
|
|
- <div class="card-content">
|
|
|
- <div class="spaces-container">
|
|
|
- @for (space of projectProducts; track space.id) {
|
|
|
- <div class="space-item" [class.expanded]="isSpaceExpanded(space.id)">
|
|
|
- <!-- 空间头部 - 折叠时显示 -->
|
|
|
- <div class="space-header" (click)="toggleSpaceExpansion(space.id)">
|
|
|
- <div class="space-name-section">
|
|
|
- {{ getSpaceDisplayName(space) }}
|
|
|
- <span class="reference-count-badge">
|
|
|
- 参考 {{ getTotalSpaceFileCount(space.id) }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 特殊需求显示 -->
|
|
|
- @if (getSpaceSpecialRequirements(space.id)) {
|
|
|
- <div class="special-requirements-box">
|
|
|
- <span class="requirements-label">特殊要求:</span>
|
|
|
- <span class="requirements-text">{{ getSpaceSpecialRequirements(space.id) | slice:0:30 }}{{ getSpaceSpecialRequirements(space.id).length > 30 ? '...' : '' }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 操作按钮 -->
|
|
|
- <div class="header-actions">
|
|
|
- @if (canEdit) {
|
|
|
- <button class="btn-icon-small btn-ai" title="AI设计分析" (click)="openAIDesignDialog(space); $event.stopPropagation()">
|
|
|
- <span class="icon-text">🤖</span>
|
|
|
- </button>
|
|
|
- <button class="btn-icon-small btn-edit" title="编辑特殊要求" (click)="toggleSpaceExpansion(space.id); $event.stopPropagation()">
|
|
|
- <span class="icon-text">✏️</span>
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 展开/收起图标 -->
|
|
|
- <div class="expand-icon" [class.expanded]="isSpaceExpanded(space.id)">
|
|
|
- <svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
|
|
|
- <path d="M7 10l5 5 5-5z"/>
|
|
|
- </svg>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 空间内容 - 展开时显示 -->
|
|
|
- @if (isSpaceExpanded(space.id)) {
|
|
|
- <div class="space-content">
|
|
|
- <!-- 拖拽上传区域 -->
|
|
|
- <div class="drag-drop-zone"
|
|
|
- (dragover)="onDragOver($event, space.id)"
|
|
|
- (dragleave)="onDragLeave($event)"
|
|
|
- (drop)="onDrop($event, space.id)"
|
|
|
- [class.drag-over]="isDragOver && dragOverSpaceId === space.id">
|
|
|
- <div class="drag-drop-content">
|
|
|
- <div class="drag-drop-icon">
|
|
|
- <ion-icon name="cloud-upload-outline"></ion-icon>
|
|
|
- </div>
|
|
|
- <h4>拖拽参考图片到此</h4>
|
|
|
- <p>或点击下方按钮上传</p>
|
|
|
- <p class="drag-hint">AI将自动分析图片并智能分类</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 图片类型导航标签 -->
|
|
|
- <div class="image-type-tabs">
|
|
|
- @for (type of imageTypes; track type.id) {
|
|
|
- <button
|
|
|
- class="tab-button"
|
|
|
- [class.active]="activeImageTab[space.id] === type.id"
|
|
|
- (click)="selectImageTab(space.id, type.id)">
|
|
|
- <span class="tab-label">{{ type.name }}</span>
|
|
|
- @if (getImageCountByType(space.id, type.id) > 0) {
|
|
|
- <span class="tab-badge">{{ getImageCountByType(space.id, type.id) }}</span>
|
|
|
- }
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 图片展示区域 -->
|
|
|
- <div class="images-section">
|
|
|
- @if (activeImageTab[space.id] === 'all') {
|
|
|
- <!-- 全部图片和CAD文件 -->
|
|
|
- <div class="section-header">
|
|
|
- <h5>所有参考文件</h5>
|
|
|
- @if (canEdit) {
|
|
|
- <input
|
|
|
- type="file"
|
|
|
- accept="image/*"
|
|
|
- multiple
|
|
|
- (change)="uploadReferenceImage($event, space.id)"
|
|
|
- [disabled]="uploading"
|
|
|
- hidden
|
|
|
- [id]="'spaceImageInput_' + space.id" />
|
|
|
- <button
|
|
|
- class="btn btn-sm btn-outline"
|
|
|
- (click)="triggerFileClick('spaceImageInput_' + space.id)"
|
|
|
- [disabled]="uploading">
|
|
|
- <ion-icon name="add"></ion-icon>
|
|
|
- 上传参考图
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- <div class="section-content">
|
|
|
- @if (getSpaceReferenceImages(space.id).length > 0) {
|
|
|
- <div class="images-grid">
|
|
|
- @for (image of getSpaceReferenceImages(space.id); track image.id) {
|
|
|
- <div class="image-item">
|
|
|
- <img [src]="image.url" [alt]="image.name" (click)="viewImageColorAnalysis(image.id)" />
|
|
|
- <div class="image-overlay">
|
|
|
- <div class="overlay-top">
|
|
|
- <span class="badge" [class]="getImageTypeBadgeClass(image.type)">
|
|
|
- {{ getImageTypeLabel(image.type) }}
|
|
|
- </span>
|
|
|
- @if (hasImageAnalysis(image.id)) {
|
|
|
- <span class="badge badge-success">
|
|
|
- <ion-icon name="sparkles"></ion-icon>
|
|
|
- </span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- <div class="overlay-actions">
|
|
|
- <button
|
|
|
- class="btn-icon btn-primary"
|
|
|
- (click)="viewImageColorAnalysis(image.id); $event.stopPropagation()"
|
|
|
- title="查看色彩分析">
|
|
|
- <ion-icon name="color-palette"></ion-icon>
|
|
|
- </button>
|
|
|
- @if (canEdit) {
|
|
|
- <button
|
|
|
- class="btn-icon btn-danger"
|
|
|
- (click)="deleteReferenceImage(image.id); $event.stopPropagation()">
|
|
|
- <ion-icon name="trash"></ion-icon>
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- } @else {
|
|
|
- <div class="empty-state">
|
|
|
- <ion-icon name="image-outline"></ion-icon>
|
|
|
- <p>暂无参考图片</p>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- CAD文件显示 -->
|
|
|
- @if (getSpaceCADFiles(space.id).length > 0) {
|
|
|
- <div class="section-header">
|
|
|
- <h5>CAD文件</h5>
|
|
|
- </div>
|
|
|
- <div class="section-content">
|
|
|
- <div class="cad-files-list">
|
|
|
- @for (cadFile of getSpaceCADFiles(space.id); track cadFile.id) {
|
|
|
- <div class="cad-file-item">
|
|
|
- <div class="cad-icon">
|
|
|
- <ion-icon name="document"></ion-icon>
|
|
|
- </div>
|
|
|
- <div class="cad-info">
|
|
|
- <div class="cad-name">{{ cadFile.name }}</div>
|
|
|
- <div class="cad-meta">
|
|
|
- @if (hasImageAnalysis(cadFile.id)) {
|
|
|
- <span class="badge badge-success">
|
|
|
- <ion-icon name="sparkles"></ion-icon>
|
|
|
- 已分析
|
|
|
- </span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- @if (canEdit) {
|
|
|
- <button
|
|
|
- class="btn-icon btn-danger"
|
|
|
- (click)="deleteCADFile(cadFile.id); $event.stopPropagation()">
|
|
|
- <ion-icon name="trash"></ion-icon>
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- AI分析结果展示 - 新布局 -->
|
|
|
- @if (getImageAnalysisResults(space.id).length > 0) {
|
|
|
- <div class="section-divider"></div>
|
|
|
- <div class="ai-analysis-section">
|
|
|
- <div class="section-header">
|
|
|
- <h5>
|
|
|
- <ion-icon name="sparkles"></ion-icon>
|
|
|
- AI分析结果
|
|
|
- </h5>
|
|
|
- </div>
|
|
|
- <div class="analysis-results-list">
|
|
|
- @for (analysis of getImageAnalysisResults(space.id); track analysis.imageId) {
|
|
|
- <div class="analysis-item-card">
|
|
|
- <!-- 左侧:图片 -->
|
|
|
- <div class="analysis-image-section">
|
|
|
- <div class="analysis-image">
|
|
|
- <img [src]="getImageUrl(analysis.imageId)" [alt]="'Analysis ' + analysis.imageId" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 右侧:分析结果 -->
|
|
|
- <div class="analysis-results-section">
|
|
|
- <!-- 参考类型和用户要求(按图1格式) -->
|
|
|
- <div class="analysis-header-box">
|
|
|
- <div class="header-info">
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">参考类型:</span>
|
|
|
- <span class="info-value">{{ getImageTypeLabel(analysis.imageType) }}</span>
|
|
|
- </div>
|
|
|
- <div class="info-divider">|</div>
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">备注用户要求</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- AI分析结果内容 -->
|
|
|
- <div class="analysis-details-content">
|
|
|
- <!-- 原有分析维度 -->
|
|
|
- @if (analysis.originalAnalysis) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>原有分析维度</h6>
|
|
|
- <div class="analysis-grid">
|
|
|
- @if (analysis.originalAnalysis.quality) {
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="label">质量评分:</span>
|
|
|
- <span class="value">{{ analysis.originalAnalysis.quality.score }}/100 ({{ analysis.originalAnalysis.quality.level }})</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.originalAnalysis.content) {
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="label">内容分类:</span>
|
|
|
- <span class="value">{{ analysis.originalAnalysis.content.category }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.originalAnalysis.technical) {
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="label">像素:</span>
|
|
|
- <span class="value">{{ analysis.originalAnalysis.technical.megapixels }}MP</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 新增分析维度 -->
|
|
|
- @if (analysis.enhancedAnalysis) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>设计分析维度</h6>
|
|
|
- <div class="analysis-grid">
|
|
|
- @if (analysis.enhancedAnalysis.style) {
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="label">风格:</span>
|
|
|
- <span class="value">{{ analysis.enhancedAnalysis.style }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.enhancedAnalysis.atmosphere) {
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="label">氛围:</span>
|
|
|
- <span class="value">{{ analysis.enhancedAnalysis.atmosphere }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.enhancedAnalysis.material) {
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="label">材质:</span>
|
|
|
- <span class="value">{{ analysis.enhancedAnalysis.material }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.enhancedAnalysis.texture) {
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="label">纹理:</span>
|
|
|
- <span class="value">{{ analysis.enhancedAnalysis.texture }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 风格元素分析(新增) -->
|
|
|
- @if (analysis.styleElements) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>风格元素</h6>
|
|
|
- <div class="analysis-tags">
|
|
|
- @for (keyword of analysis.styleElements.styleKeywords; track keyword) {
|
|
|
- <span class="tag">{{ keyword }}</span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 色彩搭配分析(新增) -->
|
|
|
- @if (analysis.colorScheme) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>色彩搭配</h6>
|
|
|
- <div class="color-scheme-display">
|
|
|
- @if (analysis.colorScheme.primaryColors && analysis.colorScheme.primaryColors.length > 0) {
|
|
|
- <div class="color-row">
|
|
|
- <span class="color-label">主色调:</span>
|
|
|
- <div class="color-samples">
|
|
|
- @for (color of analysis.colorScheme.primaryColors; track color) {
|
|
|
- <div class="color-sample" [style.backgroundColor]="color" [title]="color"></div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.colorScheme.secondaryColors && analysis.colorScheme.secondaryColors.length > 0) {
|
|
|
- <div class="color-row">
|
|
|
- <span class="color-label">辅助色:</span>
|
|
|
- <div class="color-samples">
|
|
|
- @for (color of analysis.colorScheme.secondaryColors; track color) {
|
|
|
- <div class="color-sample" [style.backgroundColor]="color" [title]="color"></div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.colorScheme.accentColors && analysis.colorScheme.accentColors.length > 0) {
|
|
|
- <div class="color-row">
|
|
|
- <span class="color-label">点缀色:</span>
|
|
|
- <div class="color-samples">
|
|
|
- @for (color of analysis.colorScheme.accentColors; track color) {
|
|
|
- <div class="color-sample" [style.backgroundColor]="color" [title]="color"></div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 材质分析(新增) -->
|
|
|
- @if (analysis.materialAnalysis) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>材质分析</h6>
|
|
|
- <div class="analysis-tags">
|
|
|
- @for (material of analysis.materialAnalysis.materials; track material) {
|
|
|
- <span class="tag tag-material">{{ material }}</span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 布局特征分析(新增) -->
|
|
|
- @if (analysis.layoutFeatures) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>布局特征</h6>
|
|
|
- <div class="analysis-tags">
|
|
|
- @for (feature of analysis.layoutFeatures.features; track feature) {
|
|
|
- <span class="tag tag-layout">{{ feature }}</span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 空间氛围分析(新增) -->
|
|
|
- @if (analysis.atmosphereAnalysis) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>空间氛围</h6>
|
|
|
- <div class="analysis-row">
|
|
|
- <span class="value">{{ analysis.atmosphereAnalysis.atmosphere }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 色彩解析报告 -->
|
|
|
- @if (analysis.colorAnalysis) {
|
|
|
- <div class="analysis-group">
|
|
|
- <h6>色彩解析报告</h6>
|
|
|
- <div class="color-analysis-content">
|
|
|
- @if (analysis.colorAnalysis.brightness) {
|
|
|
- <div class="color-item">
|
|
|
- <span class="color-label">明度:</span>
|
|
|
- <span class="color-value">{{ analysis.colorAnalysis.brightness }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.colorAnalysis.hue) {
|
|
|
- <div class="color-item">
|
|
|
- <span class="color-label">色相:</span>
|
|
|
- <span class="color-value">{{ analysis.colorAnalysis.hue }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.colorAnalysis.saturation) {
|
|
|
- <div class="color-item">
|
|
|
- <span class="color-label">饱和度:</span>
|
|
|
- <span class="color-value">{{ analysis.colorAnalysis.saturation }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (analysis.colorAnalysis.openness) {
|
|
|
- <div class="color-item">
|
|
|
- <span class="color-label">色彩开放度:</span>
|
|
|
- <span class="color-value">{{ analysis.colorAnalysis.openness }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 颜色样本 -->
|
|
|
- @if (analysis.colorAnalysis.extracted && analysis.colorAnalysis.extracted.length > 0) {
|
|
|
- <div class="color-swatches-group">
|
|
|
- <span class="color-label">提取颜色:</span>
|
|
|
- <div class="color-swatches">
|
|
|
- @for (color of analysis.colorAnalysis.extracted; track color) {
|
|
|
- <div class="color-swatch" [style.backgroundColor]="color" [title]="color"></div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- @if (analysis.colorAnalysis.organized && analysis.colorAnalysis.organized.length > 0) {
|
|
|
- <div class="color-org-group">
|
|
|
- <span class="color-label">组织颜色 (主次):</span>
|
|
|
- <div class="color-organization">
|
|
|
- @for (org of analysis.colorAnalysis.organized; track org.color) {
|
|
|
- <div class="color-org-item">
|
|
|
- <div class="color-org-swatch" [style.backgroundColor]="org.color"></div>
|
|
|
- <span class="color-org-label">{{ org.role }}: {{ org.percentage }}%</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 用户需求备注 -->
|
|
|
- <div class="section-divider"></div>
|
|
|
- <div class="user-notes-section">
|
|
|
- <div class="section-header">
|
|
|
- <h5>用户需求备注</h5>
|
|
|
- </div>
|
|
|
- <div class="section-content">
|
|
|
- <textarea
|
|
|
- class="form-textarea"
|
|
|
- [(ngModel)]="spaceSpecialRequirements[space.id]"
|
|
|
- (ngModelChange)="setSpaceSpecialRequirements(space.id, $event)"
|
|
|
- [disabled]="!canEdit"
|
|
|
- rows="4"
|
|
|
- [placeholder]="'描述' + getSpaceDisplayName(space) + '的特殊要求和注意事项'"></textarea>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- } @else if (activeImageTab[space.id] === 'cad') {
|
|
|
- <!-- CAD文件 -->
|
|
|
- <div class="section-header">
|
|
|
- <h5>CAD文件</h5>
|
|
|
- @if (canEdit) {
|
|
|
- <input
|
|
|
- type="file"
|
|
|
- accept=".dwg,.dxf,.pdf"
|
|
|
- multiple
|
|
|
- (change)="uploadCAD($event, space.id)"
|
|
|
- [disabled]="uploading"
|
|
|
- hidden
|
|
|
- [id]="'spaceCADInput_' + space.id" />
|
|
|
- <button
|
|
|
- class="btn btn-sm btn-outline"
|
|
|
- (click)="triggerFileClick('spaceCADInput_' + space.id)"
|
|
|
- [disabled]="uploading">
|
|
|
- <ion-icon name="add"></ion-icon>
|
|
|
- 上传CAD
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- <div class="section-content">
|
|
|
- @if (getSpaceCADFiles(space.id).length > 0) {
|
|
|
- <div class="file-list">
|
|
|
- @for (file of getSpaceCADFiles(space.id); track file.id) {
|
|
|
- <div class="file-item">
|
|
|
- <ion-icon name="document-text" class="file-icon"></ion-icon>
|
|
|
- <div class="file-info">
|
|
|
- <h6>{{ file.name }}</h6>
|
|
|
- <p>{{ formatFileSize(file.size) }} · {{ file.uploadTime | date:'MM-dd HH:mm' }}</p>
|
|
|
- @if (hasImageAnalysis(file.id)) {
|
|
|
- <span class="badge badge-success">
|
|
|
- <ion-icon name="sparkles"></ion-icon>
|
|
|
- 已分析
|
|
|
- </span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- @if (canEdit) {
|
|
|
- <button
|
|
|
- class="btn-icon btn-danger"
|
|
|
- (click)="deleteCAD(file.id)">
|
|
|
- <ion-icon name="trash"></ion-icon>
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- } @else {
|
|
|
- <div class="empty-state">
|
|
|
- <ion-icon name="document-outline"></ion-icon>
|
|
|
- <p>暂无CAD文件</p>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- } @else {
|
|
|
- <!-- 按类型过滤的图片 -->
|
|
|
- <div class="section-header">
|
|
|
- <h5>{{ getImageTypeLabel(activeImageTab[space.id]) }}图片</h5>
|
|
|
- @if (canEdit) {
|
|
|
- <input
|
|
|
- type="file"
|
|
|
- accept="image/*"
|
|
|
- multiple
|
|
|
- (change)="uploadReferenceImageWithType($event, space.id, activeImageTab[space.id])"
|
|
|
- [disabled]="uploading"
|
|
|
- hidden
|
|
|
- [id]="'spaceImageInput_' + space.id + '_' + activeImageTab[space.id]" />
|
|
|
- <button
|
|
|
- class="btn btn-sm btn-outline"
|
|
|
- (click)="triggerFileClick('spaceImageInput_' + space.id + '_' + activeImageTab[space.id])"
|
|
|
- [disabled]="uploading">
|
|
|
- <ion-icon name="add"></ion-icon>
|
|
|
- 上传{{ getImageTypeLabel(activeImageTab[space.id]) }}图
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- <div class="section-content">
|
|
|
- @if (getImagesByType(space.id, activeImageTab[space.id]).length > 0) {
|
|
|
- <div class="images-grid">
|
|
|
- @for (image of getImagesByType(space.id, activeImageTab[space.id]); track image.id) {
|
|
|
- <div class="image-item">
|
|
|
- <img [src]="image.url" [alt]="image.name" (click)="viewImageColorAnalysis(image.id)" />
|
|
|
- <div class="image-overlay">
|
|
|
- <div class="overlay-top">
|
|
|
- <span class="badge" [class]="getImageTypeBadgeClass(image.type)">
|
|
|
- {{ getImageTypeLabel(image.type) }}
|
|
|
- </span>
|
|
|
- @if (hasImageAnalysis(image.id)) {
|
|
|
- <span class="badge badge-success">
|
|
|
- <ion-icon name="sparkles"></ion-icon>
|
|
|
- </span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- <div class="overlay-actions">
|
|
|
- <button
|
|
|
- class="btn-icon btn-primary"
|
|
|
- (click)="viewImageColorAnalysis(image.id); $event.stopPropagation()"
|
|
|
- title="查看色彩分析">
|
|
|
- <ion-icon name="color-palette"></ion-icon>
|
|
|
- </button>
|
|
|
- @if (canEdit) {
|
|
|
- <button
|
|
|
- class="btn-icon btn-danger"
|
|
|
- (click)="deleteReferenceImage(image.id); $event.stopPropagation()">
|
|
|
- <ion-icon name="trash"></ion-icon>
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- } @else {
|
|
|
- <div class="empty-state">
|
|
|
- <ion-icon name="image-outline"></ion-icon>
|
|
|
- <p>暂无{{ getImageTypeLabel(activeImageTab[space.id]) }}图片</p>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <!-- 空间需求管理 - 已提取为独立组件 -->
|
|
|
+ <app-space-requirements-management
|
|
|
+ [projectProducts]="projectProducts"
|
|
|
+ [canEdit]="canEdit"
|
|
|
+ [uploading]="uploading"
|
|
|
+ [spaceSpecialRequirements]="spaceSpecialRequirements"
|
|
|
+ [expandedSpaces]="expandedSpaces"
|
|
|
+ [activeImageTab]="activeImageTab"
|
|
|
+ [isDragOver]="isDragOver"
|
|
|
+ [dragOverSpaceId]="dragOverSpaceId"
|
|
|
+
|
|
|
+ [getTotalSpaceFileCount]="getTotalSpaceFileCount.bind(this)"
|
|
|
+ [getSpaceSpecialRequirements]="getSpaceSpecialRequirements.bind(this)"
|
|
|
+ [getSpaceReferenceImages]="getSpaceReferenceImages.bind(this)"
|
|
|
+ [getSpaceCADFiles]="getSpaceCADFiles.bind(this)"
|
|
|
+ [getImageCountByType]="getImageCountByType.bind(this)"
|
|
|
+ [getImagesByType]="getImagesByType.bind(this)"
|
|
|
+ [hasImageAnalysis]="hasImageAnalysis.bind(this)"
|
|
|
+ [getImageAnalysisResults]="getImageAnalysisResults.bind(this)"
|
|
|
+ [getImageUrl]="getImageUrl.bind(this)"
|
|
|
+
|
|
|
+ (spaceExpanded)="toggleSpaceExpansion($event)"
|
|
|
+ (imageTabChanged)="selectImageTab($event.spaceId, $event.tabId)"
|
|
|
+ (fileUpload)="handleComponentFileUpload($event)"
|
|
|
+ (imageDelete)="deleteReferenceImage($event)"
|
|
|
+ (cadDelete)="deleteCADFile($event)"
|
|
|
+ (imageView)="viewImageColorAnalysis($event)"
|
|
|
+ (specialRequirementsChange)="setSpaceSpecialRequirements($event.spaceId, $event.value)"
|
|
|
+ (aiDesignOpen)="openAIDesignDialog($event)"
|
|
|
+ (dragOverChange)="onDragOver($event.event, $event.spaceId)"
|
|
|
+ (dragLeaveChange)="onDragLeave($event)"
|
|
|
+ (dropChange)="onDrop($event.event, $event.spaceId)">
|
|
|
+ </app-space-requirements-management>
|
|
|
|
|
|
<!-- 风格偏好 - 已隐藏 -->
|
|
|
<!--
|
|
|
@@ -1153,104 +620,7 @@
|
|
|
</h3>
|
|
|
</div>
|
|
|
<div class="card-content">
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">风格偏好 <span class="required">*</span></label>
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- class="form-input"
|
|
|
- [(ngModel)]="globalRequirements.stylePreference"
|
|
|
- [disabled]="!canEdit"
|
|
|
- placeholder="如:现代简约、北欧、轻奢等" />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="color-scheme">
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">色彩氛围</label>
|
|
|
- <select
|
|
|
- class="form-select"
|
|
|
- [(ngModel)]="globalRequirements.colorScheme.atmosphere"
|
|
|
- [disabled]="!canEdit">
|
|
|
- <option value="">请选择</option>
|
|
|
- <option value="温馨">温馨</option>
|
|
|
- <option value="高级">高级</option>
|
|
|
- <option value="简约">简约</option>
|
|
|
- <option value="时尚">时尚</option>
|
|
|
- <option value="北欧">北欧</option>
|
|
|
- <option value="中式">中式</option>
|
|
|
- <option value="欧式">欧式</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="color-inputs">
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">主色调</label>
|
|
|
- <input
|
|
|
- type="color"
|
|
|
- class="form-color-input"
|
|
|
- [(ngModel)]="globalRequirements.colorScheme.primary"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- </div>
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">副色调</label>
|
|
|
- <input
|
|
|
- type="color"
|
|
|
- class="form-color-input"
|
|
|
- [(ngModel)]="globalRequirements.colorScheme.secondary"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- </div>
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">点缀色</label>
|
|
|
- <input
|
|
|
- type="color"
|
|
|
- class="form-color-input"
|
|
|
- [(ngModel)]="globalRequirements.colorScheme.accent"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="quality-level">
|
|
|
- <label class="form-label">质量等级</label>
|
|
|
- <div class="radio-group">
|
|
|
- <label class="radio-item">
|
|
|
- <input
|
|
|
- type="radio"
|
|
|
- name="qualityLevel"
|
|
|
- value="standard"
|
|
|
- [(ngModel)]="globalRequirements.qualityLevel"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- <span class="radio-label">{{ getQualityLevelName('standard') }}</span>
|
|
|
- </label>
|
|
|
- <label class="radio-item">
|
|
|
- <input
|
|
|
- type="radio"
|
|
|
- name="qualityLevel"
|
|
|
- value="premium"
|
|
|
- [(ngModel)]="globalRequirements.qualityLevel"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- <span class="radio-label">{{ getQualityLevelName('premium') }}</span>
|
|
|
- </label>
|
|
|
- <label class="radio-item">
|
|
|
- <input
|
|
|
- type="radio"
|
|
|
- name="qualityLevel"
|
|
|
- value="luxury"
|
|
|
- [(ngModel)]="globalRequirements.qualityLevel"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- <span class="radio-label">{{ getQualityLevelName('luxury') }}</span>
|
|
|
- </label>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">特殊需求</label>
|
|
|
- <textarea
|
|
|
- class="form-textarea"
|
|
|
- [(ngModel)]="globalRequirements.specialRequirements"
|
|
|
- [disabled]="!canEdit"
|
|
|
- rows="3"
|
|
|
- placeholder="描述任何特殊需求或注意事项"></textarea>
|
|
|
- </div>
|
|
|
+ ...
|
|
|
</div>
|
|
|
</div>
|
|
|
-->
|