|
@@ -0,0 +1,475 @@
|
|
|
+<div class="reference-image-manager">
|
|
|
+ <!-- 顶部标题和操作栏 -->
|
|
|
+ <div class="manager-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <h2 class="manager-title">
|
|
|
+ <i class="icon-images"></i>
|
|
|
+ 参考图管理
|
|
|
+ </h2>
|
|
|
+ <span class="image-count">共 {{ referenceImages.length }} 张参考图</span>
|
|
|
+ </div>
|
|
|
+ <div class="header-right">
|
|
|
+ <button class="btn-view-mode" [class.active]="viewMode === 'grid'" (click)="viewMode = 'grid'">
|
|
|
+ <i class="icon-grid"></i>
|
|
|
+ 网格视图
|
|
|
+ </button>
|
|
|
+ <button class="btn-view-mode" [class.active]="viewMode === 'list'" (click)="viewMode = 'list'">
|
|
|
+ <i class="icon-list"></i>
|
|
|
+ 列表视图
|
|
|
+ </button>
|
|
|
+ <button class="btn-upload" (click)="openUploadDialog()">
|
|
|
+ <i class="icon-upload"></i>
|
|
|
+ 上传参考图
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分类筛选标签 -->
|
|
|
+ <div class="category-filters">
|
|
|
+ <button
|
|
|
+ class="filter-tag"
|
|
|
+ [class.active]="activeFilter === 'all'"
|
|
|
+ (click)="filterByCategory('all')">
|
|
|
+ 全部 ({{ referenceImages.length }})
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="filter-tag filter-tag-lighting"
|
|
|
+ [class.active]="activeFilter === 'lighting'"
|
|
|
+ (click)="filterByCategory('lighting')">
|
|
|
+ <i class="icon-lightbulb"></i>
|
|
|
+ 灯光参考 ({{ getCategoryCount('lighting') }})
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="filter-tag filter-tag-furniture"
|
|
|
+ [class.active]="activeFilter === 'furniture'"
|
|
|
+ (click)="filterByCategory('furniture')">
|
|
|
+ <i class="icon-couch"></i>
|
|
|
+ 软装参考 ({{ getCategoryCount('furniture') }})
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="filter-tag filter-tag-atmosphere"
|
|
|
+ [class.active]="activeFilter === 'atmosphere'"
|
|
|
+ (click)="filterByCategory('atmosphere')">
|
|
|
+ <i class="icon-stars"></i>
|
|
|
+ 氛围参考 ({{ getCategoryCount('atmosphere') }})
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="filter-tag filter-tag-structure"
|
|
|
+ [class.active]="activeFilter === 'structure'"
|
|
|
+ (click)="filterByCategory('structure')">
|
|
|
+ <i class="icon-cube"></i>
|
|
|
+ 结构参考 ({{ getCategoryCount('structure') }})
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="filter-tag filter-tag-uncategorized"
|
|
|
+ [class.active]="activeFilter === 'uncategorized'"
|
|
|
+ (click)="filterByCategory('uncategorized')">
|
|
|
+ <i class="icon-alert"></i>
|
|
|
+ 未分类 ({{ getUncategorizedCount() }})
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 网格视图 -->
|
|
|
+ <div class="images-grid" *ngIf="viewMode === 'grid'">
|
|
|
+ <div class="image-card" *ngFor="let image of filteredImages; let i = index"
|
|
|
+ [class.selected]="selectedImageId === image.id"
|
|
|
+ (click)="selectImage(image)">
|
|
|
+ <div class="image-container">
|
|
|
+ <img [src]="image.url" [alt]="image.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <button class="btn-icon" (click)="editImage(image, $event)">
|
|
|
+ <i class="icon-edit"></i>
|
|
|
+ </button>
|
|
|
+ <button class="btn-icon" (click)="deleteImage(image, $event)">
|
|
|
+ <i class="icon-delete"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="image-info">
|
|
|
+ <h4 class="image-name">{{ image.name }}</h4>
|
|
|
+ <div class="image-categories">
|
|
|
+ <span *ngFor="let cat of image.categories"
|
|
|
+ class="category-badge"
|
|
|
+ [class]="'category-' + cat">
|
|
|
+ {{ getCategoryLabel(cat) }}
|
|
|
+ </span>
|
|
|
+ <span *ngIf="!image.categories || image.categories.length === 0"
|
|
|
+ class="category-badge category-uncategorized">
|
|
|
+ 未分类
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <p class="image-description" *ngIf="image.description">
|
|
|
+ {{ image.description }}
|
|
|
+ </p>
|
|
|
+ <button class="btn-ai-assist"
|
|
|
+ *ngIf="!image.description || !image.categories || image.categories.length === 0"
|
|
|
+ (click)="openAIAssistant(image, $event)">
|
|
|
+ <i class="icon-sparkles"></i>
|
|
|
+ AI智能追问
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <div class="empty-state" *ngIf="filteredImages.length === 0">
|
|
|
+ <div class="empty-icon">
|
|
|
+ <i class="icon-image"></i>
|
|
|
+ </div>
|
|
|
+ <h3>暂无参考图</h3>
|
|
|
+ <p>点击上传按钮添加参考图,让沟通更清晰</p>
|
|
|
+ <button class="btn-primary" (click)="openUploadDialog()">
|
|
|
+ <i class="icon-upload"></i>
|
|
|
+ 上传第一张参考图
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 列表视图 -->
|
|
|
+ <div class="images-list" *ngIf="viewMode === 'list'">
|
|
|
+ <div class="list-header">
|
|
|
+ <div class="col-preview">预览</div>
|
|
|
+ <div class="col-name">图片名称</div>
|
|
|
+ <div class="col-categories">分类标签</div>
|
|
|
+ <div class="col-description">描述说明</div>
|
|
|
+ <div class="col-actions">操作</div>
|
|
|
+ </div>
|
|
|
+ <div class="list-row" *ngFor="let image of filteredImages"
|
|
|
+ [class.selected]="selectedImageId === image.id">
|
|
|
+ <div class="col-preview">
|
|
|
+ <img [src]="image.url" [alt]="image.name" (click)="previewImage(image)" />
|
|
|
+ </div>
|
|
|
+ <div class="col-name">{{ image.name }}</div>
|
|
|
+ <div class="col-categories">
|
|
|
+ <span *ngFor="let cat of image.categories"
|
|
|
+ class="category-badge"
|
|
|
+ [class]="'category-' + cat">
|
|
|
+ {{ getCategoryLabel(cat) }}
|
|
|
+ </span>
|
|
|
+ <span *ngIf="!image.categories || image.categories.length === 0"
|
|
|
+ class="category-badge category-uncategorized">
|
|
|
+ 未分类
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="col-description">
|
|
|
+ <p *ngIf="image.description">{{ image.description }}</p>
|
|
|
+ <span class="text-muted" *ngIf="!image.description">暂无描述</span>
|
|
|
+ </div>
|
|
|
+ <div class="col-actions">
|
|
|
+ <button class="btn-icon" (click)="editImage(image, $event)" title="编辑">
|
|
|
+ <i class="icon-edit"></i>
|
|
|
+ </button>
|
|
|
+ <button class="btn-icon" (click)="openAIAssistant(image, $event)" title="AI追问">
|
|
|
+ <i class="icon-sparkles"></i>
|
|
|
+ </button>
|
|
|
+ <button class="btn-icon" (click)="deleteImage(image, $event)" title="删除">
|
|
|
+ <i class="icon-delete"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <div class="empty-state" *ngIf="filteredImages.length === 0">
|
|
|
+ <div class="empty-icon">
|
|
|
+ <i class="icon-image"></i>
|
|
|
+ </div>
|
|
|
+ <h3>暂无{{ getFilterLabel() }}参考图</h3>
|
|
|
+ <p>切换其他分类或上传新的参考图</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 上传对话框 -->
|
|
|
+ <div class="modal-overlay" *ngIf="showUploadDialog" (click)="closeUploadDialog()">
|
|
|
+ <div class="modal-dialog" (click)="$event.stopPropagation()">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>上传参考图</h3>
|
|
|
+ <button class="btn-close" (click)="closeUploadDialog()">
|
|
|
+ <i class="icon-close"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="modal-body">
|
|
|
+ <div class="upload-area"
|
|
|
+ [class.dragging]="isDragging"
|
|
|
+ (drop)="onDrop($event)"
|
|
|
+ (dragover)="onDragOver($event)"
|
|
|
+ (dragleave)="onDragLeave($event)">
|
|
|
+ <i class="icon-cloud-upload"></i>
|
|
|
+ <p>拖拽图片到此处,或点击选择文件</p>
|
|
|
+ <input type="file"
|
|
|
+ #fileInput
|
|
|
+ multiple
|
|
|
+ accept="image/*"
|
|
|
+ (change)="onFileSelected($event)"
|
|
|
+ style="display: none;" />
|
|
|
+ <button class="btn-select-file" (click)="fileInput.click()">
|
|
|
+ 选择文件
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 已选择的文件列表 -->
|
|
|
+ <div class="selected-files" *ngIf="selectedFiles.length > 0">
|
|
|
+ <h4>已选择 {{ selectedFiles.length }} 个文件</h4>
|
|
|
+ <div class="file-item" *ngFor="let file of selectedFiles; let i = index">
|
|
|
+ <img [src]="file.preview" [alt]="file.name" />
|
|
|
+ <div class="file-info">
|
|
|
+ <p class="file-name">{{ file.name }}</p>
|
|
|
+ <p class="file-size">{{ formatFileSize(file.size) }}</p>
|
|
|
+ </div>
|
|
|
+ <button class="btn-remove" (click)="removeSelectedFile(i)">
|
|
|
+ <i class="icon-close"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button class="btn-secondary" (click)="closeUploadDialog()">取消</button>
|
|
|
+ <button class="btn-primary"
|
|
|
+ [disabled]="selectedFiles.length === 0"
|
|
|
+ (click)="uploadFiles()">
|
|
|
+ <i class="icon-upload"></i>
|
|
|
+ 上传 {{ selectedFiles.length }} 张图片
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 编辑对话框 -->
|
|
|
+ <div class="modal-overlay" *ngIf="showEditDialog" (click)="closeEditDialog()">
|
|
|
+ <div class="modal-dialog modal-dialog-large" (click)="$event.stopPropagation()">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>编辑参考图信息</h3>
|
|
|
+ <button class="btn-close" (click)="closeEditDialog()">
|
|
|
+ <i class="icon-close"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="modal-body">
|
|
|
+ <div class="edit-content">
|
|
|
+ <div class="edit-preview">
|
|
|
+ <img [src]="editingImage?.url" [alt]="editingImage?.name" />
|
|
|
+ </div>
|
|
|
+ <div class="edit-form">
|
|
|
+ <div class="form-group">
|
|
|
+ <label>图片名称</label>
|
|
|
+ <input type="text"
|
|
|
+ class="form-control"
|
|
|
+ [(ngModel)]="editingImage!.name"
|
|
|
+ placeholder="输入图片名称" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label>
|
|
|
+ 分类标签
|
|
|
+ <span class="label-tip">可多选</span>
|
|
|
+ </label>
|
|
|
+ <div class="category-checkboxes">
|
|
|
+ <label class="checkbox-label">
|
|
|
+ <input type="checkbox"
|
|
|
+ [checked]="isCategorySelected('lighting')"
|
|
|
+ (change)="toggleCategory('lighting')" />
|
|
|
+ <span class="checkbox-text">
|
|
|
+ <i class="icon-lightbulb"></i>
|
|
|
+ 灯光参考
|
|
|
+ </span>
|
|
|
+ </label>
|
|
|
+ <label class="checkbox-label">
|
|
|
+ <input type="checkbox"
|
|
|
+ [checked]="isCategorySelected('furniture')"
|
|
|
+ (change)="toggleCategory('furniture')" />
|
|
|
+ <span class="checkbox-text">
|
|
|
+ <i class="icon-couch"></i>
|
|
|
+ 软装参考
|
|
|
+ </span>
|
|
|
+ </label>
|
|
|
+ <label class="checkbox-label">
|
|
|
+ <input type="checkbox"
|
|
|
+ [checked]="isCategorySelected('atmosphere')"
|
|
|
+ (change)="toggleCategory('atmosphere')" />
|
|
|
+ <span class="checkbox-text">
|
|
|
+ <i class="icon-stars"></i>
|
|
|
+ 氛围参考
|
|
|
+ </span>
|
|
|
+ </label>
|
|
|
+ <label class="checkbox-label">
|
|
|
+ <input type="checkbox"
|
|
|
+ [checked]="isCategorySelected('structure')"
|
|
|
+ (change)="toggleCategory('structure')" />
|
|
|
+ <span class="checkbox-text">
|
|
|
+ <i class="icon-cube"></i>
|
|
|
+ 结构参考
|
|
|
+ </span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label>
|
|
|
+ 描述说明
|
|
|
+ <span class="label-tip">详细描述参考哪些元素</span>
|
|
|
+ </label>
|
|
|
+ <textarea
|
|
|
+ class="form-control"
|
|
|
+ rows="4"
|
|
|
+ [(ngModel)]="editingImage!.description"
|
|
|
+ placeholder="例如:参考这张图的灯光布局和氛围感,主要关注吊灯的造型和暖色调灯光的运用..."></textarea>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label>关键要素标注</label>
|
|
|
+ <div class="tags-input">
|
|
|
+ <span class="tag" *ngFor="let tag of editingImage!.tags; let i = index">
|
|
|
+ {{ tag }}
|
|
|
+ <button class="tag-close" (click)="removeTag(i)">×</button>
|
|
|
+ </span>
|
|
|
+ <input type="text"
|
|
|
+ class="tag-input"
|
|
|
+ [(ngModel)]="newTag"
|
|
|
+ (keydown.enter)="addTag()"
|
|
|
+ placeholder="输入标签后按回车" />
|
|
|
+ </div>
|
|
|
+ <p class="form-help">例如:吊灯造型、暖色调、木质家具等</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button class="btn-secondary" (click)="closeEditDialog()">取消</button>
|
|
|
+ <button class="btn-primary" (click)="saveEdit()">
|
|
|
+ <i class="icon-check"></i>
|
|
|
+ 保存修改
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- AI智能追问助手 -->
|
|
|
+ <div class="modal-overlay" *ngIf="showAIDialog" (click)="closeAIDialog()">
|
|
|
+ <div class="modal-dialog modal-dialog-ai" (click)="$event.stopPropagation()">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>
|
|
|
+ <i class="icon-sparkles"></i>
|
|
|
+ AI智能追问助手
|
|
|
+ </h3>
|
|
|
+ <button class="btn-close" (click)="closeAIDialog()">
|
|
|
+ <i class="icon-close"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="modal-body">
|
|
|
+ <div class="ai-content">
|
|
|
+ <div class="ai-image-preview">
|
|
|
+ <img [src]="aiImage?.url" [alt]="aiImage?.name" />
|
|
|
+ <div class="image-meta">
|
|
|
+ <p class="image-name">{{ aiImage?.name }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="ai-analysis">
|
|
|
+ <div class="analysis-section">
|
|
|
+ <h4>
|
|
|
+ <i class="icon-info"></i>
|
|
|
+ 当前状态分析
|
|
|
+ </h4>
|
|
|
+ <div class="status-info">
|
|
|
+ <div class="status-item" [class.completed]="aiImage?.categories && (aiImage?.categories?.length ?? 0) > 0">
|
|
|
+ <i [class.icon-check-circle]="aiImage?.categories && (aiImage?.categories?.length ?? 0) > 0"
|
|
|
+ [class.icon-alert-circle]="!aiImage?.categories || (aiImage?.categories?.length ?? 0) === 0"></i>
|
|
|
+ <span>分类标签:{{ aiImage?.categories && (aiImage?.categories?.length ?? 0) > 0 ? '已标注' : '未标注' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="status-item" [class.completed]="aiImage?.description">
|
|
|
+ <i [class.icon-check-circle]="aiImage?.description"
|
|
|
+ [class.icon-alert-circle]="!aiImage?.description"></i>
|
|
|
+ <span>描述说明:{{ aiImage?.description ? '已填写' : '未填写' }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 参考图多的情况 -->
|
|
|
+ <div class="analysis-section" *ngIf="referenceImages.length >= 3">
|
|
|
+ <h4>
|
|
|
+ <i class="icon-help-circle"></i>
|
|
|
+ AI建议追问(参考图较多)
|
|
|
+ </h4>
|
|
|
+ <div class="ai-questions">
|
|
|
+ <div class="question-card" (click)="applyAIQuestion(q)"
|
|
|
+ *ngFor="let q of aiQuestionsMultiple">
|
|
|
+ <div class="question-icon">
|
|
|
+ <i [class]="q.icon"></i>
|
|
|
+ </div>
|
|
|
+ <div class="question-content">
|
|
|
+ <h5>{{ q.title }}</h5>
|
|
|
+ <p>{{ q.description }}</p>
|
|
|
+ <div class="question-example">
|
|
|
+ <strong>追问示例:</strong>{{ q.example }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="btn-apply">
|
|
|
+ <i class="icon-arrow-right"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 参考图少的情况 -->
|
|
|
+ <div class="analysis-section" *ngIf="referenceImages.length < 3">
|
|
|
+ <h4>
|
|
|
+ <i class="icon-help-circle"></i>
|
|
|
+ AI建议追问(参考图较少)
|
|
|
+ </h4>
|
|
|
+ <div class="ai-questions">
|
|
|
+ <div class="question-card" (click)="applyAIQuestion(q)"
|
|
|
+ *ngFor="let q of aiQuestionsFew">
|
|
|
+ <div class="question-icon">
|
|
|
+ <i [class]="q.icon"></i>
|
|
|
+ </div>
|
|
|
+ <div class="question-content">
|
|
|
+ <h5>{{ q.title }}</h5>
|
|
|
+ <p>{{ q.description }}</p>
|
|
|
+ <div class="question-example">
|
|
|
+ <strong>追问示例:</strong>{{ q.example }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="btn-apply">
|
|
|
+ <i class="icon-arrow-right"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 自定义追问 -->
|
|
|
+ <div class="analysis-section">
|
|
|
+ <h4>
|
|
|
+ <i class="icon-message-circle"></i>
|
|
|
+ 自定义追问
|
|
|
+ </h4>
|
|
|
+ <textarea
|
|
|
+ class="form-control"
|
|
|
+ rows="3"
|
|
|
+ [(ngModel)]="customQuestion"
|
|
|
+ placeholder="输入您想要补充询问的问题..."></textarea>
|
|
|
+ <button class="btn-send-question" (click)="sendCustomQuestion()">
|
|
|
+ <i class="icon-send"></i>
|
|
|
+ 发送追问
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button class="btn-secondary" (click)="closeAIDialog()">关闭</button>
|
|
|
+ <button class="btn-primary" (click)="applyAISuggestionsAndEdit()">
|
|
|
+ <i class="icon-edit"></i>
|
|
|
+ 应用建议并编辑
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图片预览对话框 -->
|
|
|
+ <div class="modal-overlay" *ngIf="showPreviewDialog" (click)="closePreviewDialog()">
|
|
|
+ <div class="preview-dialog" (click)="$event.stopPropagation()">
|
|
|
+ <button class="btn-close" (click)="closePreviewDialog()">
|
|
|
+ <i class="icon-close"></i>
|
|
|
+ </button>
|
|
|
+ <img [src]="previewImageUrl" alt="预览图" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|