|
@@ -1,4 +1,4 @@
|
|
|
-<!-- 只展示修改处,未变更部分用占位注释表示 -->
|
|
|
+<!-- 只展示修改处,未变更部分用占位注释表示 -->
|
|
|
<div class="project-detail-container designer-page">
|
|
|
<!-- 项目标题栏 -->
|
|
|
<div class="project-header card">
|
|
@@ -362,10 +362,10 @@
|
|
|
<div class="detail-section">
|
|
|
<div class="section-header">
|
|
|
<h4>材质规格与分类</h4>
|
|
|
- <span class="section-count">{{ proposalAnalysis.materials.length }} 类材质</span>
|
|
|
+ <span class="section-count">{{ proposalAnalysis?.materials?.length || 0 }} 类材质</span>
|
|
|
</div>
|
|
|
<div class="materials-grid">
|
|
|
- @for (material of proposalAnalysis.materials; track material.category) {
|
|
|
+ @for (material of proposalAnalysis?.materials || []; track material.category) {
|
|
|
<div class="material-card" [class]="material.usage.priority">
|
|
|
<div class="material-header">
|
|
|
<h5>{{ material.category }}</h5>
|
|
@@ -398,10 +398,10 @@
|
|
|
<div class="detail-section">
|
|
|
<div class="section-header">
|
|
|
<h4>设计风格特征</h4>
|
|
|
- <span class="style-name">{{ proposalAnalysis.designStyle.primaryStyle }}</span>
|
|
|
+ <span class="style-name">{{ proposalAnalysis?.designStyle?.primaryStyle || '未定义' }}</span>
|
|
|
</div>
|
|
|
<div class="style-elements">
|
|
|
- @for (element of proposalAnalysis.designStyle.styleElements; track element.element) {
|
|
|
+ @for (element of proposalAnalysis?.designStyle?.styleElements || []; track element.element) {
|
|
|
<div class="style-element">
|
|
|
<div class="element-header">
|
|
|
<span class="element-name">{{ element.element }}</span>
|
|
@@ -415,7 +415,7 @@
|
|
|
}
|
|
|
</div>
|
|
|
<div class="style-characteristics">
|
|
|
- @for (char of proposalAnalysis.designStyle.characteristics; track char.feature) {
|
|
|
+ @for (char of proposalAnalysis?.designStyle?.characteristics || []; track char.feature) {
|
|
|
<div class="characteristic-item" [class]="char.importance">
|
|
|
<span class="char-feature">{{ char.feature }}</span>
|
|
|
<span class="char-value">{{ char.value }}</span>
|
|
@@ -428,10 +428,10 @@
|
|
|
<div class="detail-section">
|
|
|
<div class="section-header">
|
|
|
<h4>色彩搭配方案及占比分析</h4>
|
|
|
- <span class="harmony-type">{{ proposalAnalysis.colorScheme.harmony.type }}</span>
|
|
|
+ <span class="harmony-type">{{ proposalAnalysis?.colorScheme?.harmony?.type || '未定义' }}</span>
|
|
|
</div>
|
|
|
<div class="color-palette">
|
|
|
- @for (color of proposalAnalysis.colorScheme.palette; track color.hex) {
|
|
|
+ @for (color of proposalAnalysis?.colorScheme?.palette || []; track color.hex) {
|
|
|
<div class="color-item" [class]="color.role">
|
|
|
<div class="color-swatch" [style.background-color]="color.hex"></div>
|
|
|
<div class="color-info">
|
|
@@ -449,11 +449,11 @@
|
|
|
<div class="color-psychology">
|
|
|
<div class="psychology-item">
|
|
|
<span class="label">氛围营造</span>
|
|
|
- <span class="value">{{ proposalAnalysis.colorScheme.psychology.mood }}</span>
|
|
|
+ <span class="value">{{ proposalAnalysis?.colorScheme?.psychology?.mood || '未定义' }}</span>
|
|
|
</div>
|
|
|
<div class="psychology-item">
|
|
|
<span class="label">空间感受</span>
|
|
|
- <span class="value">{{ proposalAnalysis.colorScheme.psychology.atmosphere }}</span>
|
|
|
+ <span class="value">{{ proposalAnalysis?.colorScheme?.psychology?.atmosphere || '未定义' }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -462,28 +462,28 @@
|
|
|
<div class="detail-section">
|
|
|
<div class="section-header">
|
|
|
<h4>空间尺寸数据及功能分区</h4>
|
|
|
- <span class="total-area">总面积 {{ proposalAnalysis.spaceLayout.dimensions.area }}㎡</span>
|
|
|
+ <span class="total-area">总面积 {{ proposalAnalysis?.spaceLayout?.dimensions?.area || 0 }}㎡</span>
|
|
|
</div>
|
|
|
<div class="space-dimensions">
|
|
|
<div class="dimension-item">
|
|
|
<span class="dim-label">长度</span>
|
|
|
- <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.length }}m</span>
|
|
|
+ <span class="dim-value">{{ proposalAnalysis?.spaceLayout?.dimensions?.length || 0 }}m</span>
|
|
|
</div>
|
|
|
<div class="dimension-item">
|
|
|
<span class="dim-label">宽度</span>
|
|
|
- <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.width }}m</span>
|
|
|
+ <span class="dim-value">{{ proposalAnalysis?.spaceLayout?.dimensions?.width || 0 }}m</span>
|
|
|
</div>
|
|
|
<div class="dimension-item">
|
|
|
<span class="dim-label">层高</span>
|
|
|
- <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.height }}m</span>
|
|
|
+ <span class="dim-value">{{ proposalAnalysis?.spaceLayout?.dimensions?.height || 0 }}m</span>
|
|
|
</div>
|
|
|
<div class="dimension-item">
|
|
|
<span class="dim-label">体积</span>
|
|
|
- <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.volume }}m³</span>
|
|
|
+ <span class="dim-value">{{ proposalAnalysis?.spaceLayout?.dimensions?.volume || 0 }}m³</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="functional-zones">
|
|
|
- @for (zone of proposalAnalysis.spaceLayout.functionalZones; track zone.zone) {
|
|
|
+ @for (zone of proposalAnalysis?.spaceLayout?.functionalZones || []; track zone.zone) {
|
|
|
<div class="zone-card">
|
|
|
<div class="zone-header">
|
|
|
<h5>{{ zone.zone }}</h5>
|
|
@@ -517,10 +517,10 @@
|
|
|
<div class="detail-section">
|
|
|
<div class="section-header">
|
|
|
<h4>预算与时间线</h4>
|
|
|
- <span class="total-budget">总预算 ¥{{ proposalAnalysis.budget.total.toLocaleString() }}</span>
|
|
|
+ <span class="total-budget">总预算 ¥{{ (proposalAnalysis?.budget?.total || 0).toLocaleString() }}</span>
|
|
|
</div>
|
|
|
<div class="budget-breakdown">
|
|
|
- @for (item of proposalAnalysis.budget.breakdown; track item.category) {
|
|
|
+ @for (item of proposalAnalysis?.budget?.breakdown || []; track item.category) {
|
|
|
<div class="budget-item">
|
|
|
<div class="budget-category">{{ item.category }}</div>
|
|
|
<div class="budget-amount">¥{{ item.amount.toLocaleString() }}</div>
|
|
@@ -532,7 +532,7 @@
|
|
|
}
|
|
|
</div>
|
|
|
<div class="timeline">
|
|
|
- @for (phase of proposalAnalysis.timeline; track phase.phase) {
|
|
|
+ @for (phase of proposalAnalysis?.timeline || []; track phase.phase) {
|
|
|
<div class="timeline-item">
|
|
|
<div class="phase-name">{{ phase.phase }}</div>
|
|
|
<div class="phase-duration">{{ phase.duration }}天</div>
|
|
@@ -986,238 +986,606 @@
|
|
|
}
|
|
|
</div>
|
|
|
} @else if (stage === '建模') {
|
|
|
- <div class="upload-section">
|
|
|
- <div class="upload-header">
|
|
|
- <h4>上传白模图片</h4>
|
|
|
- <span class="hint">支持:JPG/PNG,不强制4K</span>
|
|
|
+ <!-- 建模阶段:直接显示建模相关内容 -->
|
|
|
+ <div class="modeling-stage-panel">
|
|
|
+ <div class="stage-description">
|
|
|
+ <h4>建模阶段</h4>
|
|
|
+ <p>上传白模图片,进行模型差异检查</p>
|
|
|
</div>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <div class="upload-dropzone"
|
|
|
- (click)="whiteModelImages.length === 0 ? triggerFileInput('whiteModel') : null"
|
|
|
- (dragover)="whiteModelImages.length === 0 ? onDragOver($event) : null"
|
|
|
- (dragleave)="whiteModelImages.length === 0 ? onDragLeave($event) : null"
|
|
|
- (drop)="whiteModelImages.length === 0 ? onFileDrop($event, 'whiteModel') : null"
|
|
|
- [class.drag-over]="isDragOver && whiteModelImages.length === 0"
|
|
|
- [class.has-images]="whiteModelImages.length > 0">
|
|
|
- @if (whiteModelImages.length === 0) {
|
|
|
- <div class="upload-icon"></div>
|
|
|
- <div class="upload-text">点击此处或拖拽文件到此处上传</div>
|
|
|
- <div class="upload-hint">支持 JPG、PNG 格式,单个文件最大 10MB</div>
|
|
|
- } @else {
|
|
|
- <div class="uploaded-images-grid">
|
|
|
- @for (img of whiteModelImages; track img.id) {
|
|
|
- <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
- <img [src]="img.url" [alt]="img.name" />
|
|
|
- <div class="image-overlay">
|
|
|
- <div class="image-name">{{ img.name }}</div>
|
|
|
- <div class="image-actions">
|
|
|
- <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
|
|
- <circle cx="12" cy="12" r="3"></circle>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <button class="remove-btn" (click)="$event.stopPropagation(); removeWhiteModelImage(img.id)">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <line x1="18" y1="6" x2="6" y2="18"></line>
|
|
|
- <line x1="6" y1="6" x2="18" y2="18"></line>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <!-- 空间列表 -->
|
|
|
+ <div class="space-list-container">
|
|
|
+ <div class="space-list-header">
|
|
|
+ <h4>空间列表</h4>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="add-space-btn"
|
|
|
+ (click)="showAddSpaceInput['modeling'] = true">
|
|
|
+ <span class="add-icon">+</span>
|
|
|
+ <span>添加空间</span>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 添加空间输入框 -->
|
|
|
+ @if (showAddSpaceInput['modeling']) {
|
|
|
+ <div class="add-space-input-container">
|
|
|
+ <input type="text"
|
|
|
+ class="space-name-input"
|
|
|
+ placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
|
|
|
+ [(ngModel)]="newSpaceName['modeling']"
|
|
|
+ (keyup.enter)="addSpace('modeling')"
|
|
|
+ #spaceInput>
|
|
|
+ <div class="input-actions">
|
|
|
+ <button class="confirm-btn" (click)="addSpace('modeling')">确认</button>
|
|
|
+ <button class="cancel-btn" (click)="cancelAddSpace('modeling')">取消</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 空间卡片列表 -->
|
|
|
+ <div class="space-cards">
|
|
|
+ @for (space of getActiveProcessSpaces(); track space.id) {
|
|
|
+ <div class="space-card" [class.expanded]="space.isExpanded">
|
|
|
+ <div class="space-header" (click)="toggleSpace('modeling', space.id)">
|
|
|
+ <div class="space-info">
|
|
|
+ <span class="space-name">{{ space.name }}</span>
|
|
|
+ <span class="space-progress">{{ getSpaceProgress('modeling', space.id) }}%</span>
|
|
|
+ </div>
|
|
|
+ <div class="space-actions">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-space-btn"
|
|
|
+ (click)="$event.stopPropagation(); removeSpace('modeling', space.id)"
|
|
|
+ title="删除空间">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (space.isExpanded) {
|
|
|
+ <div class="space-content">
|
|
|
+ <!-- 图片上传区域 -->
|
|
|
+ <div class="upload-section">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <div class="upload-dropzone"
|
|
|
+ (click)="triggerSpaceFileInput('modeling', space.id)"
|
|
|
+ (dragover)="onDragOver($event)"
|
|
|
+ (dragleave)="onDragLeave($event)"
|
|
|
+ (drop)="onSpaceFileDrop($event, 'modeling', space.id)"
|
|
|
+ [class.drag-over]="isDragOver">
|
|
|
+ @if (getSpaceImages('modeling', space.id).length === 0) {
|
|
|
+ <div class="upload-placeholder">
|
|
|
+ <div class="upload-icon">📁</div>
|
|
|
+ <div class="upload-text">点击上传或拖拽文件到此处</div>
|
|
|
+ <div class="upload-hint">
|
|
|
+ 支持 JPG、PNG 格式,单个文件最大 10MB
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('modeling', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ <div class="image-actions">
|
|
|
+ <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <circle cx="11" cy="11" r="8"></circle>
|
|
|
+ <path d="m21 21-4.35-4.35"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('modeling', space.id, img.id)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="readonly-images">
|
|
|
+ @if (getSpaceImages('modeling', space.id).length > 0) {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('modeling', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="empty-tip">暂无上传的图片</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 备注区域 -->
|
|
|
+ <div class="notes-section">
|
|
|
+ <label class="notes-label">备注</label>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <textarea #modelingNotes
|
|
|
+ class="notes-textarea"
|
|
|
+ placeholder="请输入备注信息..."
|
|
|
+ [value]="getSpaceNotes('modeling', space.id)"
|
|
|
+ (blur)="updateSpaceNotes('modeling', space.id, modelingNotes.value || '')">
|
|
|
+ </textarea>
|
|
|
+ } @else {
|
|
|
+ <div class="notes-readonly">
|
|
|
+ {{ getSpaceNotes('modeling', space.id) || '暂无备注' }}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
</div>
|
|
|
}
|
|
|
- <div class="add-more-btn" (click)="triggerFileInput('whiteModel')">
|
|
|
- <div class="add-icon">+</div>
|
|
|
- <div class="add-text">添加更多</div>
|
|
|
- </div>
|
|
|
</div>
|
|
|
}
|
|
|
- <input type="file"
|
|
|
- id="whiteModelFileInput"
|
|
|
- accept="{{allowedImageTypes}}"
|
|
|
- multiple
|
|
|
- (change)="onWhiteModelSelected($event)"
|
|
|
- style="display: none;" />
|
|
|
</div>
|
|
|
- }
|
|
|
- <div class="upload-actions">
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <button class="primary-btn" [disabled]="whiteModelImages.length===0" (click)="confirmWhiteModelUpload()">确认上传</button>
|
|
|
- }
|
|
|
- @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('white')">同步图片信息</button> }
|
|
|
- @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="model-check-section">
|
|
|
- <h4>模型差异检查清单</h4>
|
|
|
- <div class="checklist">
|
|
|
- @for (item of modelCheckItems; track item.id) {
|
|
|
- <div class="checklist-item">
|
|
|
- <label class="checklist-label">
|
|
|
- <input type="checkbox" class="custom-checkbox" [checked]="item.isPassed" (change)="updateModelCheckItem(item.id, $any($event.target).checked)" [disabled]="!canEditSection('delivery')">
|
|
|
- <span class="checklist-text">{{ item.name }}</span>
|
|
|
- </label>
|
|
|
- <span class="check-status">{{ item.isPassed ? '已通过' : '待处理' }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
</div>
|
|
|
</div>
|
|
|
} @else if (stage === '软装') {
|
|
|
- <div class="softdecor-section">
|
|
|
- <h4>软装清单素材</h4>
|
|
|
- <div class="upload-section">
|
|
|
- <div class="upload-header">
|
|
|
- <h4>上传软装小图</h4>
|
|
|
- <span class="hint">建议 ≤1MB 的 JPG/PNG 小图</span>
|
|
|
+ <!-- 软装阶段:直接显示软装相关内容 -->
|
|
|
+ <div class="soft-decor-stage-panel">
|
|
|
+ <div class="stage-description">
|
|
|
+ <h4>软装阶段</h4>
|
|
|
+ <p>上传软装清单素材</p>
|
|
|
+ </div>
|
|
|
+ <!-- 空间列表 -->
|
|
|
+ <div class="space-list-container">
|
|
|
+ <div class="space-list-header">
|
|
|
+ <h4>空间列表</h4>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="add-space-btn"
|
|
|
+ (click)="showAddSpaceInput['softDecor'] = true">
|
|
|
+ <span class="add-icon">+</span>
|
|
|
+ <span>添加空间</span>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
</div>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <div class="upload-dropzone"
|
|
|
- (click)="softDecorImages.length === 0 ? triggerFileInput('softDecor') : null"
|
|
|
- (dragover)="softDecorImages.length === 0 ? onDragOver($event) : null"
|
|
|
- (dragleave)="softDecorImages.length === 0 ? onDragLeave($event) : null"
|
|
|
- (drop)="softDecorImages.length === 0 ? onFileDrop($event, 'softDecor') : null"
|
|
|
- [class.drag-over]="isDragOver && softDecorImages.length === 0"
|
|
|
- [class.has-images]="softDecorImages.length > 0">
|
|
|
- @if (softDecorImages.length === 0) {
|
|
|
- <div class="upload-icon"></div>
|
|
|
- <div class="upload-text">点击此处或拖拽文件到此处上传</div>
|
|
|
- <div class="upload-hint">支持 JPG、PNG 格式,建议文件大小 ≤1MB</div>
|
|
|
- } @else {
|
|
|
- <div class="uploaded-images-grid">
|
|
|
- @for (img of softDecorImages; track img.id) {
|
|
|
- <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
- <img [src]="img.url" [alt]="img.name" />
|
|
|
- <div class="image-overlay">
|
|
|
- <div class="image-name">{{ img.name }}</div>
|
|
|
- <div class="image-actions">
|
|
|
- <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
|
|
- <circle cx="12" cy="12" r="3"></circle>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <button class="remove-btn" (click)="$event.stopPropagation(); removeSoftDecorImage(img.id)">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <line x1="18" y1="6" x2="6" y2="18"></line>
|
|
|
- <line x1="6" y1="6" x2="18" y2="18"></line>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
+
|
|
|
+ <!-- 添加空间输入框 -->
|
|
|
+ @if (showAddSpaceInput['softDecor']) {
|
|
|
+ <div class="add-space-input-container">
|
|
|
+ <input type="text"
|
|
|
+ class="space-name-input"
|
|
|
+ placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
|
|
|
+ [(ngModel)]="newSpaceName['softDecor']"
|
|
|
+ (keyup.enter)="addSpace('softDecor')"
|
|
|
+ #spaceInput>
|
|
|
+ <div class="input-actions">
|
|
|
+ <button class="confirm-btn" (click)="addSpace('softDecor')">确认</button>
|
|
|
+ <button class="cancel-btn" (click)="cancelAddSpace('softDecor')">取消</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 空间卡片列表 -->
|
|
|
+ <div class="space-cards">
|
|
|
+ @for (space of getActiveProcessSpaces(); track space.id) {
|
|
|
+ <div class="space-card" [class.expanded]="space.isExpanded">
|
|
|
+ <div class="space-header" (click)="toggleSpace('softDecor', space.id)">
|
|
|
+ <div class="space-info">
|
|
|
+ <span class="space-name">{{ space.name }}</span>
|
|
|
+ <span class="space-progress">{{ getSpaceProgress('softDecor', space.id) }}%</span>
|
|
|
+ </div>
|
|
|
+ <div class="space-actions">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-space-btn"
|
|
|
+ (click)="$event.stopPropagation(); removeSpace('softDecor', space.id)"
|
|
|
+ title="删除空间">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (space.isExpanded) {
|
|
|
+ <div class="space-content">
|
|
|
+ <!-- 图片上传区域 -->
|
|
|
+ <div class="upload-section">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <div class="upload-dropzone"
|
|
|
+ (click)="triggerSpaceFileInput('softDecor', space.id)"
|
|
|
+ (dragover)="onDragOver($event)"
|
|
|
+ (dragleave)="onDragLeave($event)"
|
|
|
+ (drop)="onSpaceFileDrop($event, 'softDecor', space.id)"
|
|
|
+ [class.drag-over]="isDragOver">
|
|
|
+ @if (getSpaceImages('softDecor', space.id).length === 0) {
|
|
|
+ <div class="upload-placeholder">
|
|
|
+ <div class="upload-icon">📁</div>
|
|
|
+ <div class="upload-text">点击上传或拖拽文件到此处</div>
|
|
|
+ <div class="upload-hint">
|
|
|
+ 建议 ≤1MB 的 JPG/PNG 小图
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('softDecor', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ <div class="image-actions">
|
|
|
+ <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <circle cx="11" cy="11" r="8"></circle>
|
|
|
+ <path d="m21 21-4.35-4.35"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('softDecor', space.id, img.id)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="readonly-images">
|
|
|
+ @if (getSpaceImages('softDecor', space.id).length > 0) {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('softDecor', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="empty-tip">暂无上传的图片</div>
|
|
|
+ }
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 备注区域 -->
|
|
|
+ <div class="notes-section">
|
|
|
+ <label class="notes-label">备注</label>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <textarea #softDecorNotes
|
|
|
+ class="notes-textarea"
|
|
|
+ placeholder="请输入备注信息..."
|
|
|
+ [value]="getSpaceNotes('softDecor', space.id)"
|
|
|
+ (blur)="updateSpaceNotes('softDecor', space.id, softDecorNotes.value || '')">
|
|
|
+ </textarea>
|
|
|
+ } @else {
|
|
|
+ <div class="notes-readonly">
|
|
|
+ {{ getSpaceNotes('softDecor', space.id) || '暂无备注' }}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
- }
|
|
|
- <div class="add-more-btn" (click)="triggerFileInput('softDecor')">
|
|
|
- <div class="add-icon">+</div>
|
|
|
- <div class="add-text">添加更多</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <input type="file"
|
|
|
- id="softDecorFileInput"
|
|
|
- accept="{{allowedImageTypes}}"
|
|
|
- multiple
|
|
|
- (change)="onSoftDecorSmallPicsSelected($event)"
|
|
|
- style="display: none;" />
|
|
|
- </div>
|
|
|
- }
|
|
|
- <div class="upload-actions">
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <button class="primary-btn" [disabled]="softDecorImages.length===0" (click)="confirmSoftDecorUpload()">确认上传</button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
}
|
|
|
- @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('soft')">同步图片信息</button> }
|
|
|
- @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
} @else if (stage === '渲染') {
|
|
|
- <div class="render-progress-section">
|
|
|
- @if (isLoadingRenderProgress) {
|
|
|
- <div class="loading-state">
|
|
|
- <div class="loading-spinner"></div>
|
|
|
- <div>正在加载渲染进度...</div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (errorLoadingRenderProgress) {
|
|
|
- <div class="error-state">
|
|
|
- <div>渲染进度加载失败</div>
|
|
|
- <button class="secondary-btn" (click)="retryLoadRenderProgress()">重试</button>
|
|
|
- </div>
|
|
|
- }
|
|
|
- @if (!isLoadingRenderProgress && !errorLoadingRenderProgress && renderProgress) {
|
|
|
- <div class="progress-info" style="display:flex;gap:16px;align-items:center;margin:12px 0;">
|
|
|
- <span>状态:{{ renderProgress.status }}</span>
|
|
|
- <span>完成度:{{ renderProgress.completionRate }}%</span>
|
|
|
- <span>预计剩余:{{ renderProgress.estimatedTimeRemaining }} 小时</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <div class="upload-section">
|
|
|
- <div class="upload-header">
|
|
|
- <h4>上传渲染大图</h4>
|
|
|
- <span class="hint">需满足4K标准(最长边 ≥ 4000px)</span>
|
|
|
+ <!-- 渲染阶段:直接显示渲染相关内容 -->
|
|
|
+ <div class="rendering-stage-panel">
|
|
|
+ <div class="stage-description">
|
|
|
+ <h4>渲染阶段</h4>
|
|
|
+ <p>上传渲染大图,满足4K标准</p>
|
|
|
+ </div>
|
|
|
+ <!-- 空间列表 -->
|
|
|
+ <div class="space-list-container">
|
|
|
+ <div class="space-list-header">
|
|
|
+ <h4>空间列表</h4>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="add-space-btn"
|
|
|
+ (click)="showAddSpaceInput['rendering'] = true">
|
|
|
+ <span class="add-icon">+</span>
|
|
|
+ <span>添加空间</span>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
</div>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <div class="upload-dropzone"
|
|
|
- (click)="renderLargeImages.length === 0 ? triggerFileInput('render') : null"
|
|
|
- (dragover)="renderLargeImages.length === 0 ? onDragOver($event) : null"
|
|
|
- (dragleave)="renderLargeImages.length === 0 ? onDragLeave($event) : null"
|
|
|
- (drop)="renderLargeImages.length === 0 ? onFileDrop($event, 'render') : null"
|
|
|
- [class.drag-over]="isDragOver && renderLargeImages.length === 0"
|
|
|
- [class.has-images]="renderLargeImages.length > 0">
|
|
|
- @if (renderLargeImages.length === 0) {
|
|
|
- <div class="upload-icon"></div>
|
|
|
- <div class="upload-text">点击此处或拖拽文件到此处上传</div>
|
|
|
- <div class="upload-hint">支持 JPG、PNG 格式,需满足4K标准(最长边 ≥ 4000px)</div>
|
|
|
- } @else {
|
|
|
- <div class="uploaded-images-grid">
|
|
|
- @for (img of renderLargeImages; track img.id) {
|
|
|
- <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
- <img [src]="img.url" [alt]="img.name" />
|
|
|
- <div class="image-overlay">
|
|
|
- <div class="image-name">{{ img.name }}</div>
|
|
|
- <div class="image-actions">
|
|
|
- <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
|
|
- <circle cx="12" cy="12" r="3"></circle>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <button class="remove-btn" (click)="$event.stopPropagation(); removeRenderLargeImage(img.id)">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <line x1="18" y1="6" x2="6" y2="18"></line>
|
|
|
- <line x1="6" y1="6" x2="18" y2="18"></line>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <button class="download-btn" (click)="$event.stopPropagation(); downloadImage(img)" [disabled]="img.locked">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M12 3v12m0 0l-4-4m4 4l4-4" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
|
- <path d="M5 21h14a2 2 0 0 0 2-2v-4" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
+
|
|
|
+ <!-- 添加空间输入框 -->
|
|
|
+ @if (showAddSpaceInput['rendering']) {
|
|
|
+ <div class="add-space-input-container">
|
|
|
+ <input type="text"
|
|
|
+ class="space-name-input"
|
|
|
+ placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
|
|
|
+ [(ngModel)]="newSpaceName['rendering']"
|
|
|
+ (keyup.enter)="addSpace('rendering')"
|
|
|
+ #spaceInput>
|
|
|
+ <div class="input-actions">
|
|
|
+ <button class="confirm-btn" (click)="addSpace('rendering')">确认</button>
|
|
|
+ <button class="cancel-btn" (click)="cancelAddSpace('rendering')">取消</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 空间卡片列表 -->
|
|
|
+ <div class="space-cards">
|
|
|
+ @for (space of getActiveProcessSpaces(); track space.id) {
|
|
|
+ <div class="space-card" [class.expanded]="space.isExpanded">
|
|
|
+ <div class="space-header" (click)="toggleSpace('rendering', space.id)">
|
|
|
+ <div class="space-info">
|
|
|
+ <span class="space-name">{{ space.name }}</span>
|
|
|
+ <span class="space-progress">{{ getSpaceProgress('rendering', space.id) }}%</span>
|
|
|
+ </div>
|
|
|
+ <div class="space-actions">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-space-btn"
|
|
|
+ (click)="$event.stopPropagation(); removeSpace('rendering', space.id)"
|
|
|
+ title="删除空间">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (space.isExpanded) {
|
|
|
+ <div class="space-content">
|
|
|
+ <!-- 图片上传区域 -->
|
|
|
+ <div class="upload-section">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <div class="upload-dropzone"
|
|
|
+ (click)="triggerSpaceFileInput('rendering', space.id)"
|
|
|
+ (dragover)="onDragOver($event)"
|
|
|
+ (dragleave)="onDragLeave($event)"
|
|
|
+ (drop)="onSpaceFileDrop($event, 'rendering', space.id)"
|
|
|
+ [class.drag-over]="isDragOver">
|
|
|
+ @if (getSpaceImages('rendering', space.id).length === 0) {
|
|
|
+ <div class="upload-placeholder">
|
|
|
+ <div class="upload-icon">📁</div>
|
|
|
+ <div class="upload-text">点击上传或拖拽文件到此处</div>
|
|
|
+ <div class="upload-hint">
|
|
|
+ 需满足4K标准(最长边 ≥ 4000px)
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('rendering', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ <div class="image-actions">
|
|
|
+ <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <circle cx="11" cy="11" r="8"></circle>
|
|
|
+ <path d="m21 21-4.35-4.35"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('rendering', space.id, img.id)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ } @else {
|
|
|
+ <div class="readonly-images">
|
|
|
+ @if (getSpaceImages('rendering', space.id).length > 0) {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('rendering', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="empty-tip">暂无上传的图片</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 备注区域 -->
|
|
|
+ <div class="notes-section">
|
|
|
+ <label class="notes-label">备注</label>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <textarea #renderingNotes
|
|
|
+ class="notes-textarea"
|
|
|
+ placeholder="请输入备注信息..."
|
|
|
+ [value]="getSpaceNotes('rendering', space.id)"
|
|
|
+ (blur)="updateSpaceNotes('rendering', space.id, renderingNotes.value || '')">
|
|
|
+ </textarea>
|
|
|
+ } @else {
|
|
|
+ <div class="notes-readonly">
|
|
|
+ {{ getSpaceNotes('rendering', space.id) || '暂无备注' }}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
- }
|
|
|
- <div class="add-more-btn" (click)="triggerFileInput('render')">
|
|
|
- <div class="add-icon">+</div>
|
|
|
- <div class="add-text">添加更多</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <input type="file"
|
|
|
- id="renderFileInput"
|
|
|
- accept="{{allowedImageTypes}}"
|
|
|
- multiple
|
|
|
- (change)="onRenderLargePicsSelected($event)"
|
|
|
- style="display: none;" />
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ } @else if (stage === '后期') {
|
|
|
+ <!-- 后期阶段:直接显示后期相关内容 -->
|
|
|
+ <div class="post-production-stage-panel">
|
|
|
+ <div class="stage-description">
|
|
|
+ <h4>后期阶段</h4>
|
|
|
+ <p>上传后期处理图片,进行最终调整</p>
|
|
|
+ </div>
|
|
|
+ <!-- 空间列表 -->
|
|
|
+ <div class="space-list-container">
|
|
|
+ <div class="space-list-header">
|
|
|
+ <h4>空间列表</h4>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="add-space-btn"
|
|
|
+ (click)="showAddSpaceInput['postProduction'] = true">
|
|
|
+ <span class="add-icon">+</span>
|
|
|
+ <span>添加空间</span>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 添加空间输入框 -->
|
|
|
+ @if (showAddSpaceInput['postProduction']) {
|
|
|
+ <div class="add-space-input-container">
|
|
|
+ <input type="text"
|
|
|
+ class="space-name-input"
|
|
|
+ placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
|
|
|
+ [(ngModel)]="newSpaceName['postProduction']"
|
|
|
+ (keyup.enter)="addSpace('postProduction')"
|
|
|
+ #spaceInput>
|
|
|
+ <div class="input-actions">
|
|
|
+ <button class="confirm-btn" (click)="addSpace('postProduction')">确认</button>
|
|
|
+ <button class="cancel-btn" (click)="cancelAddSpace('postProduction')">取消</button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
}
|
|
|
- <div class="upload-actions">
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <button class="primary-btn" [disabled]="renderLargeImages.length===0" (click)="confirmRenderUpload()">确认上传</button>
|
|
|
+
|
|
|
+ <!-- 空间卡片列表 -->
|
|
|
+ <div class="space-cards">
|
|
|
+ @for (space of getActiveProcessSpaces(); track space.id) {
|
|
|
+ <div class="space-card" [class.expanded]="space.isExpanded">
|
|
|
+ <div class="space-header" (click)="toggleSpace('postProduction', space.id)">
|
|
|
+ <div class="space-info">
|
|
|
+ <span class="space-name">{{ space.name }}</span>
|
|
|
+ <span class="space-progress">{{ getSpaceProgress('postProduction', space.id) }}%</span>
|
|
|
+ </div>
|
|
|
+ <div class="space-actions">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-space-btn"
|
|
|
+ (click)="$event.stopPropagation(); removeSpace('postProduction', space.id)"
|
|
|
+ title="删除空间">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (space.isExpanded) {
|
|
|
+ <div class="space-content">
|
|
|
+ <!-- 图片上传区域 -->
|
|
|
+ <div class="upload-section">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <div class="upload-dropzone"
|
|
|
+ (click)="triggerSpaceFileInput('postProduction', space.id)"
|
|
|
+ (dragover)="onDragOver($event)"
|
|
|
+ (dragleave)="onDragLeave($event)"
|
|
|
+ (drop)="onSpaceFileDrop($event, 'postProduction', space.id)"
|
|
|
+ [class.drag-over]="isDragOver">
|
|
|
+ @if (getSpaceImages('postProduction', space.id).length === 0) {
|
|
|
+ <div class="upload-placeholder">
|
|
|
+ <div class="upload-icon">📁</div>
|
|
|
+ <div class="upload-text">点击上传或拖拽文件到此处</div>
|
|
|
+ <div class="upload-hint">
|
|
|
+ 支持 JPG、PNG 格式,后期处理图片
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('postProduction', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ <div class="image-actions">
|
|
|
+ <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <circle cx="11" cy="11" r="8"></circle>
|
|
|
+ <path d="m21 21-4.35-4.35"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('postProduction', space.id, img.id)">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3,6 5,6 21,6"></polyline>
|
|
|
+ <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="readonly-images">
|
|
|
+ @if (getSpaceImages('postProduction', space.id).length > 0) {
|
|
|
+ <div class="uploaded-images-grid">
|
|
|
+ @for (img of getSpaceImages('postProduction', space.id); track img.id) {
|
|
|
+ <div class="uploaded-image-item" (click)="previewImage(img)">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="image-overlay">
|
|
|
+ <div class="image-name">{{ img.name }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="empty-tip">暂无上传的图片</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 备注区域 -->
|
|
|
+ <div class="notes-section">
|
|
|
+ <label class="notes-label">备注</label>
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <textarea #postProductionNotes
|
|
|
+ class="notes-textarea"
|
|
|
+ placeholder="请输入备注信息..."
|
|
|
+ [value]="getSpaceNotes('postProduction', space.id)"
|
|
|
+ (blur)="updateSpaceNotes('postProduction', space.id, postProductionNotes.value || '')">
|
|
|
+ </textarea>
|
|
|
+ } @else {
|
|
|
+ <div class="notes-readonly">
|
|
|
+ {{ getSpaceNotes('postProduction', space.id) || '暂无备注' }}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
}
|
|
|
- @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('render')">同步图片信息</button> }
|
|
|
- @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -1230,8 +1598,6 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- }
|
|
|
|
|
|
|
|
|
<!-- 项目人员标签页 -->
|
|
@@ -1334,3 +1700,4 @@
|
|
|
</div>
|
|
|
}
|
|
|
</div>
|
|
|
+}
|