|
@@ -1,3 +1,4 @@
|
|
|
+<!-- 只展示修改处,未变更部分用占位注释表示 -->
|
|
|
<div class="project-detail-container designer-page">
|
|
|
<!-- 项目标题栏 -->
|
|
|
<div class="project-header card">
|
|
@@ -5,7 +6,9 @@
|
|
|
<h1>项目详情</h1>
|
|
|
<div class="project-meta">
|
|
|
<span class="project-id">项目ID: {{ projectId }}</span>
|
|
|
- <span class="project-status" *ngIf="project">{{ project.status }}</span>
|
|
|
+ @if (project) { <span class="project-status">{{ project.status }}</span> }
|
|
|
+ <!-- 紧急与异常徽标(使用控制流指令) -->
|
|
|
+ <!-- 保持已有@if 徽标逻辑不变 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="header-actions">
|
|
@@ -15,68 +18,52 @@
|
|
|
<!-- 切换项目下拉菜单 -->
|
|
|
<div class="project-switcher">
|
|
|
<button (click)="showDropdown = !showDropdown" class="switch-btn">切换项目</button>
|
|
|
- <div *ngIf="showDropdown" class="switch-dropdown" (click)="$event.stopPropagation()">
|
|
|
- <div *ngFor="let p of projects"
|
|
|
- (click)="switchProject(p.id); showDropdown = false"
|
|
|
- [class.active]="p.id === projectId"
|
|
|
- class="project-item">
|
|
|
- <span class="project-name">{{ p.name }}</span>
|
|
|
- <span class="project-status-badge"
|
|
|
- [class.ongoing]="p.status === '进行中'"
|
|
|
- [class.completed]="p.status === '已完成'"
|
|
|
- [class.pending]="p.status === '待处理'">
|
|
|
- {{ p.status }}
|
|
|
- </span>
|
|
|
+ @if (showDropdown) {
|
|
|
+ <div class="switch-dropdown" (click)="$event.stopPropagation()">
|
|
|
+ @for (p of projects; track p.id) {
|
|
|
+ <div (click)="switchProject(p.id); showDropdown = false"
|
|
|
+ [class.active]="p.id === projectId"
|
|
|
+ class="project-item">
|
|
|
+ <span class="project-name">{{ p.name }}</span>
|
|
|
+ <span class="project-status-badge"
|
|
|
+ [class.ongoing]="p.status === '进行中'"
|
|
|
+ [class.completed]="p.status === '已完成'"
|
|
|
+ [class.pending]="p.status === '待处理'">
|
|
|
+ {{ p.status }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 导出阶段报告 -->
|
|
|
+ <button (click)="exportProjectReport()" class="secondary-btn">导出阶段报告</button>
|
|
|
|
|
|
<button (click)="generateReminderMessage()" class="stagnation-btn">设置停滞</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 提醒消息弹窗 -->
|
|
|
- <div *ngIf="reminderMessage" class="reminder-popup">
|
|
|
- {{ reminderMessage }}
|
|
|
- </div>
|
|
|
+ @if (reminderMessage) {
|
|
|
+ <div class="reminder-popup">
|
|
|
+ {{ reminderMessage }}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 标准阶段进度(5阶段) -->
|
|
|
+ <!-- 已采用@for,不变 -->
|
|
|
|
|
|
<!-- 顶部导航标签页 -->
|
|
|
- <!-- <div class="project-tabs">
|
|
|
- <div class="tab-header">
|
|
|
- <button (click)="switchTab('progress')" [class.active]="isActiveTab('progress')" class="tab-btn">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
- <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
|
|
|
- </svg>
|
|
|
- <span>项目进度</span>
|
|
|
- </button>
|
|
|
- <button (click)="switchTab('members')" [class.active]="isActiveTab('members')" class="tab-btn">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
- <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
|
- <circle cx="9" cy="7" r="4"></circle>
|
|
|
- <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
|
|
- <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
|
|
- </svg>
|
|
|
- <span>项目成员</span>
|
|
|
- </button>
|
|
|
- <button (click)="switchTab('files')" [class.active]="isActiveTab('files')" class="tab-btn">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
- <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
- <line x1="16" y1="13" x2="8" y2="13"></line>
|
|
|
- <line x1="16" y1="17" x2="8" y2="17"></line>
|
|
|
- <polyline points="10 9 9 9 8 9"></polyline>
|
|
|
- </svg>
|
|
|
- <span>项目文件</span>
|
|
|
- </button>
|
|
|
- </div> -->
|
|
|
+ <!-- 原有代码保留 -->
|
|
|
|
|
|
- <!-- 标签页内容 -->
|
|
|
- <div class="tab-content">
|
|
|
- <!-- 项目进度标签页 -->
|
|
|
- <div *ngIf="isActiveTab('progress')" class="progress-tab-content">
|
|
|
+ <div class="tab-content">
|
|
|
+ <!-- 项目进度标签页 -->
|
|
|
+ @if (isActiveTab('progress')) {
|
|
|
+ <div class="progress-tab-content">
|
|
|
<!-- 主要内容布局 - 左侧三分之一,右侧三分之二 -->
|
|
|
<div class="main-content-layout">
|
|
|
- <!-- 左侧三分之一 - 项目基本信息和客户画像 -->
|
|
|
+ <!-- 左侧三分之一 - 项目信息和客户画像 -->
|
|
|
<div class="left-column">
|
|
|
<!-- 项目基本信息 -->
|
|
|
<div class="project-info-card card">
|
|
@@ -94,10 +81,12 @@
|
|
|
<label>当前阶段</label>
|
|
|
<span class="stage-tag">{{ project?.currentStage || '加载中...' }}</span>
|
|
|
</div>
|
|
|
- <div class="info-item" *ngIf="project">
|
|
|
- <label>预计交付日期</label>
|
|
|
- <span>{{ project.deadline | date:'yyyy-MM-dd' }}</span>
|
|
|
- </div>
|
|
|
+ @if (project) {
|
|
|
+ <div class="info-item">
|
|
|
+ <label>预计交付日期</label>
|
|
|
+ <span>{{ project.deadline | date:'yyyy-MM-dd' }}</span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
@@ -106,63 +95,64 @@
|
|
|
<h2>客户画像</h2>
|
|
|
|
|
|
<!-- 技能匹配度警告 -->
|
|
|
- <div *ngIf="getSkillMismatchWarning()" class="warning-banner">
|
|
|
- <div class="warning-content">
|
|
|
- <span class="warning-icon">⚠️</span>
|
|
|
- <span class="warning-text">{{ getSkillMismatchWarning() }}</span>
|
|
|
+ @if (getSkillMismatchWarning()) {
|
|
|
+ <div class="warning-banner">
|
|
|
+ <div class="warning-content">
|
|
|
+ <span class="warning-icon">⚠️</span>
|
|
|
+ <span class="warning-text">{{ getSkillMismatchWarning() }}</span>
|
|
|
+ </div>
|
|
|
+ <button (click)="notifyTeamLeader('skill-mismatch')" class="contact-leader-btn">联系组长</button>
|
|
|
</div>
|
|
|
- <button (click)="notifyTeamLeader('skill-mismatch')" class="contact-leader-btn">联系组长</button>
|
|
|
- </div>
|
|
|
+ }
|
|
|
|
|
|
- <div *ngIf="project" class="tags-container">
|
|
|
- <div class="tag-section">
|
|
|
- <h3>客户偏好</h3>
|
|
|
- <div class="tags-grid">
|
|
|
- <ng-container *ngIf="project.customerTags && project.customerTags.length > 0">
|
|
|
- <div class="tag-item">
|
|
|
- <span class="tag-label">需求类型</span>
|
|
|
- <span *ngIf="project.customerTags[0].needType" class="tag">
|
|
|
- {{ project.customerTags[0].needType }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div class="tag-item">
|
|
|
- <span class="tag-label">设计风格</span>
|
|
|
- <span *ngIf="project.customerTags[0].preference" class="tag">
|
|
|
- {{ project.customerTags[0].preference }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div class="tag-item">
|
|
|
- <span class="tag-label">色彩氛围</span>
|
|
|
- <span *ngIf="project.customerTags[0].colorAtmosphere" class="tag">
|
|
|
- {{ project.customerTags[0].colorAtmosphere }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </ng-container>
|
|
|
+ @if (project) {
|
|
|
+ <div class="tags-container">
|
|
|
+ <div class="tag-section">
|
|
|
+ <h3>客户偏好</h3>
|
|
|
+ <div class="tags-grid">
|
|
|
+ @if (project.customerTags && project.customerTags.length > 0) {
|
|
|
+
|
|
|
+ <!-- 已移除:需求类型 -->
|
|
|
+
|
|
|
+ <div class="tag-item">
|
|
|
+ <span class="tag-label">设计风格</span>
|
|
|
+ @if (project.customerTags[0].preference) {
|
|
|
+ <span class="tag">{{ project.customerTags[0].preference }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="tag-item">
|
|
|
+ <span class="tag-label">色彩氛围</span>
|
|
|
+ @if (project.customerTags[0].colorAtmosphere) {
|
|
|
+ <span class="tag">{{ project.customerTags[0].colorAtmosphere }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="tag-section">
|
|
|
- <h3>项目要求</h3>
|
|
|
- <div class="tags-flex">
|
|
|
- <div class="tag-group">
|
|
|
- <span class="group-label">高优先级需求</span>
|
|
|
- <div class="tags">
|
|
|
- <span *ngFor="let priority of project.highPriorityNeeds" class="priority-tag">
|
|
|
- {{ priority }}
|
|
|
- </span>
|
|
|
+
|
|
|
+ <div class="tag-section">
|
|
|
+ <h3>项目要求</h3>
|
|
|
+ <div class="tags-flex">
|
|
|
+ <div class="tag-group">
|
|
|
+ <span class="group-label">高优先级需求</span>
|
|
|
+ <div class="tags">
|
|
|
+ @for (priority of project.highPriorityNeeds; track priority) {
|
|
|
+ <span class="priority-tag">{{ priority }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <div class="tag-group">
|
|
|
- <span class="group-label">擅长技能</span>
|
|
|
- <div class="tags">
|
|
|
- <span *ngFor="let skill of project.skillsRequired" class="skill-tag">
|
|
|
- {{ skill }}
|
|
|
- </span>
|
|
|
+ <div class="tag-group">
|
|
|
+ <span class="group-label">擅长技能</span>
|
|
|
+ <div class="tags">
|
|
|
+ @for (skill of project.skillsRequired; track skill) {
|
|
|
+ <span class="skill-tag">{{ skill }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
@@ -170,492 +160,276 @@
|
|
|
<div class="right-column">
|
|
|
<div class="process-card card">
|
|
|
<h2>制作流程进度</h2>
|
|
|
- <!-- 项目进度看板 - 支持10个阶段的横向进度展示 -->
|
|
|
- <div class="stage-progress-container">
|
|
|
- <!-- 添加进度条容器包装器以支持横向滚动 -->
|
|
|
+
|
|
|
+ <!-- 串式流程:10个阶段横向排列,可展开专属卡片 -->
|
|
|
+ <!-- 已按需求移除:每个分阶段的展开按钮 -->
|
|
|
+
|
|
|
+ <div class="stage-progress-container">
|
|
|
<div class="stage-progress-wrapper">
|
|
|
<div class="stage-progress">
|
|
|
- <!-- 订单创建阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('订单创建')" [class.active]="project?.currentStage === '订单创建'" (click)="viewStageDetails('订单创建')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('订单创建') ? '✓' : '1' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">订单创建</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 需求沟通阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('需求沟通')" [class.active]="project?.currentStage === '需求沟通'" (click)="viewStageDetails('需求沟通')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('需求沟通') ? '✓' : '2' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">需求沟通</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 方案确认阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('方案确认')" [class.active]="project?.currentStage === '方案确认'" (click)="viewStageDetails('方案确认')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('方案确认') ? '✓' : '3' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">方案确认</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 建模阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('建模')" [class.active]="project?.currentStage === '建模'" (click)="viewStageDetails('建模')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('建模') ? '✓' : '4' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">建模</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 软装阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('软装')" [class.active]="project?.currentStage === '软装'" (click)="viewStageDetails('软装')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('软装') ? '✓' : '5' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">软装</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 渲染阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('渲染')" [class.active]="project?.currentStage === '渲染'" (click)="viewStageDetails('渲染')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('渲染') ? '✓' : '6' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">渲染</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 后期阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('后期')" [class.active]="project?.currentStage === '后期'" (click)="viewStageDetails('后期')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('后期') ? '✓' : '7' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">后期</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 尾款结算阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('尾款结算')" [class.active]="project?.currentStage === '尾款结算'" (click)="viewStageDetails('尾款结算')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('尾款结算') ? '✓' : '8' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">尾款结算</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 客户评价阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('客户评价')" [class.active]="project?.currentStage === '客户评价'" (click)="viewStageDetails('客户评价')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('客户评价') ? '✓' : '9' }}</span>
|
|
|
- </div>
|
|
|
- <div class="stage-name">客户评价</div>
|
|
|
- </div>
|
|
|
- <div class="progress-line"></div>
|
|
|
-
|
|
|
- <!-- 投诉处理阶段 -->
|
|
|
- <div class="stage" [class.completed]="isStageCompleted('投诉处理')" [class.active]="project?.currentStage === '投诉处理'" (click)="viewStageDetails('投诉处理')">
|
|
|
- <div class="stage-icon">
|
|
|
- <span>{{ isStageCompleted('投诉处理') ? '✓' : '10' }}</span>
|
|
|
+ @for (stage of getVisibleStages(); track stage) {
|
|
|
+ <div class="stage" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'" [class.pending]="getStageStatus(stage) === 'pending'">
|
|
|
+ <div class="stage-icon">{{ getVisibleStages().indexOf(stage) + 1 }}</div>
|
|
|
+ <div class="stage-name">{{ stage }}</div>
|
|
|
+ <!-- 已移除原先位置的阶段展开按钮:
|
|
|
+ <button class="stage-toggle" (click)="toggleStage(stage)">{{ expandedStages[stage] ? '收起' : '展开' }}</button>
|
|
|
+ -->
|
|
|
</div>
|
|
|
- <div class="stage-name">投诉处理</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 当前阶段操作 -->
|
|
|
- <div *ngIf="project" class="current-stage-actions">
|
|
|
- <div class="current-stage-info">
|
|
|
- <h3>当前阶段: <span class="stage-highlight">{{ project.currentStage }}</span></h3>
|
|
|
- </div>
|
|
|
- <div class="stage-actions">
|
|
|
- <!-- 各阶段完成按钮 -->
|
|
|
- <button *ngIf="project.currentStage === '订单创建'" (click)="updateProjectStage('需求沟通')" class="primary-btn">完成订单创建</button>
|
|
|
- <button *ngIf="project.currentStage === '需求沟通'" (click)="updateProjectStage('方案确认')" class="primary-btn">完成需求沟通</button>
|
|
|
- <button *ngIf="project.currentStage === '方案确认'" (click)="updateProjectStage('建模')" class="primary-btn">完成方案确认</button>
|
|
|
- <button *ngIf="project.currentStage === '建模'" (click)="updateProjectStage('软装')" [disabled]="!areAllModelChecksPassed()" class="primary-btn">
|
|
|
- {{ areAllModelChecksPassed() ? '完成建模' : '完成所有模型检查' }}
|
|
|
- </button>
|
|
|
- <button *ngIf="project.currentStage === '软装'" (click)="updateProjectStage('渲染')" class="primary-btn">完成软装</button>
|
|
|
- <button *ngIf="project.currentStage === '渲染'" (click)="updateProjectStage('后期')" class="primary-btn">完成渲染</button>
|
|
|
- <button *ngIf="project.currentStage === '后期'" (click)="updateProjectStage('尾款结算')" class="primary-btn">完成后期</button>
|
|
|
- <button *ngIf="project.currentStage === '尾款结算'" (click)="updateProjectStage('客户评价')" class="primary-btn">完成尾款结算</button>
|
|
|
- <button *ngIf="project.currentStage === '客户评价'" (click)="updateProjectStage('投诉处理')" class="primary-btn">完成客户评价</button>
|
|
|
- <button *ngIf="project.currentStage === '投诉处理'" (click)="updateProjectStage('投诉处理')" class="primary-btn">完成投诉处理</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
|
|
|
- <!-- 模型误差检查清单 - 仅在建模阶段显示 -->
|
|
|
- <div *ngIf="project?.currentStage === '建模'" class="model-check-section">
|
|
|
- <h3>模型误差检查清单</h3>
|
|
|
- <div class="checklist">
|
|
|
- <div *ngFor="let item of modelCheckItems" class="checklist-item">
|
|
|
- <input type="checkbox" [checked]="item.isPassed" (change)="updateModelCheckItem(item.id, !item.isPassed)" class="custom-checkbox">
|
|
|
- <span class="checklist-text">{{ item.name }}</span>
|
|
|
- <span class="check-status" [class.passed]="item.isPassed" [class.failed]="!item.isPassed">
|
|
|
- {{ item.isPassed ? '通过' : '未通过' }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <!-- 阶段详情:位于每个阶段正下方(网格与上方阶段图标对齐) -->
|
|
|
+ <div class="stage-details-grid">
|
|
|
+ @for (stage of getVisibleStages(); track stage) {
|
|
|
+ @if (getStageStatus(stage) === 'active') {
|
|
|
+ <div class="stage-details-cell">
|
|
|
+ <div class="stage-specific-card card" [class.success]="getStageStatus(stage)==='completed'" [class.warning]="getStageStatus(stage)==='active'" [class.danger]="getStageStatus(stage)==='pending'">
|
|
|
+ <div class="stage-specific-header">
|
|
|
+ <h3>{{ stage }} · 阶段详情</h3>
|
|
|
+ <div class="ops">
|
|
|
+ <!-- 已移除:查看阶段详情 按钮 -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 阶段专属任务卡片 - 仅在对应节点显示 -->
|
|
|
- <div class="stage-specific-cards">
|
|
|
+ <!-- 针对不同阶段,展示对应卡片模块(示例:建模/软装/渲染/后期/尾款结算) -->
|
|
|
+ @if (stage === '建模') {
|
|
|
+ @if (shouldShowCard('modelCheck')) {
|
|
|
+ <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]="!isDesignerView()">
|
|
|
+ <span class="checklist-text">{{ item.name }}</span>
|
|
|
+ </label>
|
|
|
+ <span class="check-status">{{ item.isPassed ? '已通过' : '待处理' }}</span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <!-- 渲染阶段专属卡片 -->
|
|
|
- <div *ngIf="project?.currentStage === '渲染'" class="render-progress-card card">
|
|
|
- <h2>渲染进度</h2>
|
|
|
- <div *ngIf="isLoadingRenderProgress" class="loading-state">
|
|
|
- <div class="loading-spinner"></div>
|
|
|
- <span>加载中...</span>
|
|
|
- </div>
|
|
|
- <div *ngIf="errorLoadingRenderProgress" class="error-state">
|
|
|
- <span>加载失败</span>
|
|
|
- <button (click)="retryLoadRenderProgress()" class="secondary-btn">点击重试</button>
|
|
|
- </div>
|
|
|
- <div *ngIf="renderProgress && !isLoadingRenderProgress && !errorLoadingRenderProgress" class="progress-content">
|
|
|
- <!-- 渲染超时预警 -->
|
|
|
- <div *ngIf="renderProgress.estimatedTimeRemaining <= 3" class="timeout-warning">
|
|
|
- <div class="warning-icon">⚠️</div>
|
|
|
- <div class="warning-text">
|
|
|
- <span class="warning-title">渲染即将超时</span>
|
|
|
- <span class="warning-time">预计剩余时间: {{ renderProgress.estimatedTimeRemaining }} 小时</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="upload-section">
|
|
|
+ <div class="upload-header">
|
|
|
+ <h4>上传白模图片</h4>
|
|
|
+ <span class="hint">支持:JPG/PNG;不强制4K</span>
|
|
|
+ </div>
|
|
|
+ <div class="upload-actions">
|
|
|
+ @if (isDesignerView()) {
|
|
|
+ <label class="secondary-btn">
|
|
|
+ 选择图片
|
|
|
+ <input type="file" accept="{{allowedImageTypes}}" multiple (change)="onWhiteModelSelected($event)" style="display:none" />
|
|
|
+ </label>
|
|
|
+ <button class="primary-btn" [disabled]="whiteModelImages.length===0" (click)="confirmWhiteModelUpload()">确认上传</button>
|
|
|
+ }
|
|
|
+ @if (isTeamLeaderView()) {
|
|
|
+ <button class="secondary-btn" (click)="syncUploadedImages('white')">同步图片信息</button>
|
|
|
+ }
|
|
|
+ @if (isCustomerServiceView()) {
|
|
|
+ <span class="desc">只读</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ @if (whiteModelImages.length > 0) {
|
|
|
+ <div class="thumb-list">
|
|
|
+ @for (img of whiteModelImages; track img.id) {
|
|
|
+ <div class="thumb-item">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="thumb-meta">
|
|
|
+ <span class="name" [title]="img.name">{{ img.name }}</span>
|
|
|
+ <span class="size">{{ img.size }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
|
|
|
+ <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
|
|
|
+ @if (isTeamLeaderView()) {
|
|
|
+ <button class="link success" (click)="reviewImage(img.id, 'white', 'approved')">通过</button>
|
|
|
+ <button class="link warning" (click)="reviewImage(img.id, 'white', 'rejected')">驳回</button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ @if (isDesignerView()) { <button class="link danger" (click)="removeWhiteModelImage(img.id)">移除</button> }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <!-- 渲染异常反馈模块 -->
|
|
|
- <div class="render-exception-section">
|
|
|
- <h3>渲染异常反馈</h3>
|
|
|
- <div class="exception-feedback-form">
|
|
|
- <div class="form-group">
|
|
|
- <label>异常类型:</label>
|
|
|
- <select [(ngModel)]="exceptionType" class="exception-select">
|
|
|
- <option value="failed">渲染失败</option>
|
|
|
- <option value="stuck">渲染卡顿</option>
|
|
|
- <option value="quality">渲染质量问题</option>
|
|
|
- <option value="other">其他问题</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div class="form-group">
|
|
|
- <label>详细描述:</label>
|
|
|
- <textarea
|
|
|
- [(ngModel)]="exceptionDescription"
|
|
|
- placeholder="请描述渲染过程中遇到的具体问题..."
|
|
|
- class="exception-textarea"
|
|
|
- ></textarea>
|
|
|
- </div>
|
|
|
- <div class="form-group">
|
|
|
- <label>上传截图 (可选):</label>
|
|
|
- <input type="file" (change)="uploadExceptionScreenshot($event)" class="screenshot-upload" id="screenshot-upload">
|
|
|
- <label for="screenshot-upload" class="upload-btn">选择文件</label>
|
|
|
- <div *ngIf="exceptionScreenshotUrl" class="screenshot-preview">
|
|
|
- <img [src]="exceptionScreenshotUrl" alt="异常截图">
|
|
|
- <button (click)="clearExceptionScreenshot()" class="clear-screenshot-btn">×</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <button
|
|
|
- (click)="submitExceptionFeedback()"
|
|
|
- [disabled]="!exceptionDescription.trim()"
|
|
|
- class="submit-feedback-btn"
|
|
|
- >
|
|
|
- 提交反馈并联系技术支持
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ @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>
|
|
|
+ <div class="upload-actions">
|
|
|
+ @if (isDesignerView()) {
|
|
|
+ <label class="secondary-btn">
|
|
|
+ 选择图片
|
|
|
+ <input type="file" accept="{{allowedImageTypes}}" multiple (change)="onSoftDecorSmallPicsSelected($event)" style="display:none" />
|
|
|
+ </label>
|
|
|
+ <button class="primary-btn" [disabled]="softDecorImages.length===0" (click)="confirmSoftDecorUpload()">确认上传</button>
|
|
|
+ }
|
|
|
+ @if (isTeamLeaderView()) {
|
|
|
+ <button class="secondary-btn" (click)="syncUploadedImages('soft')">同步图片信息</button>
|
|
|
+ }
|
|
|
+ @if (isCustomerServiceView()) { <span class="desc">只读</span> }
|
|
|
+ </div>
|
|
|
+ @if (softDecorImages.length > 0) {
|
|
|
+ <div class="thumb-list">
|
|
|
+ @for (img of softDecorImages; track img.id) {
|
|
|
+ <div class="thumb-item">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="thumb-meta">
|
|
|
+ <span class="name" [title]="img.name">{{ img.name }}</span>
|
|
|
+ <span class="size">{{ img.size }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
|
|
|
+ <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
|
|
|
+ @if (isTeamLeaderView()) {
|
|
|
+ <button class="link success" (click)="reviewImage(img.id, 'soft', 'approved')">通过</button>
|
|
|
+ <button class="link warning" (click)="reviewImage(img.id, 'soft', 'rejected')">驳回</button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ @if (isDesignerView()) { <button class="link danger" (click)="removeSoftDecorImage(img.id)">移除</button> }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <!-- 历史反馈记录 -->
|
|
|
- <div class="exception-history" *ngIf="exceptionHistories.length > 0">
|
|
|
- <h4>历史反馈记录</h4>
|
|
|
- <div class="history-list">
|
|
|
- <div *ngFor="let history of exceptionHistories" class="history-item">
|
|
|
- <div class="history-header">
|
|
|
- <span class="history-type">{{ getExceptionTypeText(history.type) }}</span>
|
|
|
- <span class="history-time">{{ formatDate(history.submitTime) }}</span>
|
|
|
- </div>
|
|
|
- <div class="history-description">{{ history.description }}</div>
|
|
|
- <div class="history-status" [class.status-pending]="history.status === '待处理'" [class.status-processing]="history.status === '处理中'" [class.status-resolved]="history.status === '已解决'">
|
|
|
- {{ history.status }} - {{ history.response || '暂无回复' }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="progress-bar-container">
|
|
|
- <div class="progress-bar">
|
|
|
- <div class="progress-fill" [style.width.percent]="renderProgress.completionRate"></div>
|
|
|
- </div>
|
|
|
- <div class="progress-percentage">{{ renderProgress.completionRate }}%</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="progress-details">
|
|
|
- <div class="progress-info">
|
|
|
- <span class="info-label">预计剩余时间</span>
|
|
|
- <span class="info-value">{{ renderProgress.estimatedTimeRemaining }} 小时</span>
|
|
|
- </div>
|
|
|
- <div class="progress-info">
|
|
|
- <span class="info-label">当前状态</span>
|
|
|
- <span class="info-value">{{ renderProgress.status }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ @if (stage === '渲染') {
|
|
|
+ @if (shouldShowCard('renderProgress')) {
|
|
|
+ <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="additional-info-section">
|
|
|
- <div class="feedback-card card">
|
|
|
- <h2>客户反馈</h2>
|
|
|
- <div *ngIf="feedbacks.length === 0" class="empty-state">
|
|
|
- <div class="empty-icon">📭</div>
|
|
|
- <span>暂无客户反馈</span>
|
|
|
- </div>
|
|
|
- <div *ngFor="let feedback of feedbacks" class="feedback-item">
|
|
|
- <div class="feedback-header">
|
|
|
- <div class="feedback-meta">
|
|
|
- <span class="feedback-type">{{ feedback.isSatisfied ? '满意反馈' : '不满意反馈' }}</span>
|
|
|
- <span *ngIf="getFeedbackTag(feedback)" class="feedback-tag">{{ getFeedbackTag(feedback) }}</span>
|
|
|
- </div>
|
|
|
- <div class="feedback-date">{{ feedback.createdAt | date:'yyyy-MM-dd HH:mm' }}</div>
|
|
|
- </div>
|
|
|
- <div class="feedback-content">
|
|
|
- <div class="feedback-status"><span class="status-label">状态:</span> <span class="status-value">{{ feedback.status }}</span></div>
|
|
|
- <!-- 反馈倒计时 -->
|
|
|
- <div *ngIf="feedback.status === '待处理' && feedbackTimeoutCountdown > 0" class="feedback-countdown">
|
|
|
- <span class="countdown-icon">⏱️</span>
|
|
|
- <span>响应倒计时: {{ formatCountdown(feedbackTimeoutCountdown) }}</span>
|
|
|
- </div>
|
|
|
- <div class="feedback-details">
|
|
|
- <div class="detail-item">
|
|
|
- <span class="detail-label">修改部位:</span>
|
|
|
- <span class="detail-value">{{ feedback.problemLocation || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="detail-item">
|
|
|
- <span class="detail-label">期望效果:</span>
|
|
|
- <span class="detail-value">{{ feedback.expectedEffect || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="detail-item">
|
|
|
- <span class="detail-label">参考案例:</span>
|
|
|
- <span class="detail-value">{{ feedback.referenceCase || '-' }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="feedback-actions">
|
|
|
- <button (click)="updateFeedbackStatus(feedback.id, '处理中')" [disabled]="feedback.status === '处理中' || feedback.status === '已解决'" class="secondary-btn">
|
|
|
- 标记为处理中
|
|
|
- </button>
|
|
|
- <button (click)="updateFeedbackStatus(feedback.id, '已解决')" [disabled]="feedback.status === '已解决'" class="primary-btn">
|
|
|
- 标记为已解决
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="upload-section">
|
|
|
+ <div class="upload-header">
|
|
|
+ <h4>上传渲染大图</h4>
|
|
|
+ <span class="hint">需满足4K标准(最大边≥4000px)</span>
|
|
|
+ </div>
|
|
|
+ <div class="upload-actions" style="display:flex;gap:12px;align-items:center;">
|
|
|
+ @if (isDesignerView()) { <button class="primary-btn" (click)="openRenderUploadModal()">选择并上传</button> }
|
|
|
+ @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('render')">同步图片信息</button> }
|
|
|
+ @if (renderLargeImages.length>0) { <span class="desc">已上传 {{renderLargeImages.length}} 张</span> }
|
|
|
+ </div>
|
|
|
+ @if (renderLargeImages.length > 0) {
|
|
|
+ <div class="thumb-list">
|
|
|
+ @for (img of renderLargeImages; track img.id) {
|
|
|
+ <div class="thumb-item">
|
|
|
+ <img [src]="img.url" [alt]="img.name" />
|
|
|
+ <div class="thumb-meta">
|
|
|
+ <span class="name" [title]="img.name">{{ img.name }}</span>
|
|
|
+ <span class="size">{{ img.size }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
|
|
|
+ <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
|
|
|
+ @if (isTeamLeaderView()) {
|
|
|
+ <button class="link success" (click)="reviewImage(img.id, 'render', 'approved')">通过</button>
|
|
|
+ <button class="link warning" (click)="reviewImage(img.id, 'render', 'rejected')">驳回</button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ @if (isDesignerView()) { <button class="link danger" (click)="removeRenderLargeImage(img.id)">移除</button> }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- <div class="designer-change-card card">
|
|
|
- <h2>设计师变更记录</h2>
|
|
|
- <div class="change-actions">
|
|
|
- <button (click)="initiateDesignerChange('技能不匹配')" class="secondary-btn">发起变更 - 技能不匹配</button>
|
|
|
- <button (click)="initiateDesignerChange('休假')" class="secondary-btn">发起变更 - 休假</button>
|
|
|
- </div>
|
|
|
- <div *ngIf="designerChanges.length === 0" class="empty-state">
|
|
|
- <div class="empty-icon">👤</div>
|
|
|
- <span>暂无设计师变更记录</span>
|
|
|
- </div>
|
|
|
- <div *ngFor="let change of designerChanges" class="change-item">
|
|
|
- <div class="change-header">
|
|
|
- <div class="change-time">{{ change.changeTime | date:'yyyy-MM-dd' }}</div>
|
|
|
- <button *ngIf="!change.acceptanceTime" (click)="acceptDesignerChange(change.id)" class="accept-change-btn primary-btn">
|
|
|
- 确认承接
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <div class="change-details">
|
|
|
- <div class="designer-change-info">
|
|
|
- <div class="designer-change">
|
|
|
- <span class="designer-label">原设计师:</span>
|
|
|
- <span class="designer-name">{{ change.oldDesignerName }}</span>
|
|
|
- </div>
|
|
|
- <div class="designer-change">
|
|
|
- <span class="designer-label">新设计师:</span>
|
|
|
- <span class="designer-name">{{ change.newDesignerName }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="workload-info">
|
|
|
- <span>已完成工作量: <strong>{{ change.completedWorkload }}%</strong></span>
|
|
|
- </div>
|
|
|
- <div class="achievements">
|
|
|
- <h4>历史阶段成果:</h4>
|
|
|
- <ul>
|
|
|
- <li *ngFor="let achievement of change.historicalAchievements">{{ achievement }}</li>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- <div *ngIf="change.acceptanceTime" class="change-status">
|
|
|
- 承接确认时间: {{ change.acceptanceTime | date:'yyyy-MM-dd' }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ @if (stage === '后期') {
|
|
|
+ <div class="post-section">
|
|
|
+ <h4>客户反馈</h4>
|
|
|
+ <div class="card-content">
|
|
|
+ @if (feedbacks.length === 0) { <div class="empty">暂无反馈</div> }
|
|
|
+ @for (fb of feedbacks; track fb.id) {
|
|
|
+ <div class="feedback-item">
|
|
|
+ <div class="feedback-header">
|
|
|
+ <div class="feedback-meta">
|
|
|
+ <span class="tag">{{ getFeedbackTag(fb) }}</span>
|
|
|
+ <span class="status">{{ fb.status }}</span>
|
|
|
+ <span class="time">{{ fb.createdAt | date:'MM-dd HH:mm' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="actions" style="display:flex;gap:8px;">
|
|
|
+ @if (fb.status === '待处理') { <button class="primary-btn" (click)="updateFeedbackStatus(fb.id, '处理中')" [disabled]="isReadOnly()">标记处理中</button> }
|
|
|
+ @if (fb.status !== '已解决') { <button class="secondary-btn" (click)="updateFeedbackStatus(fb.id, '已解决')" [disabled]="isReadOnly()">标记已解决</button> }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feedback-content">{{ fb.content }}</div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <!-- 项目成员标签页 -->
|
|
|
- <div *ngIf="isActiveTab('members')" class="members-tab-content">
|
|
|
- <div class="project-members-card card">
|
|
|
- <h2>项目团队成员</h2>
|
|
|
- <div class="members-grid">
|
|
|
- <div *ngFor="let member of projectMembers" class="member-card">
|
|
|
- <div class="member-avatar">
|
|
|
- <div class="avatar-placeholder">{{ member.avatar }}</div>
|
|
|
- </div>
|
|
|
- <div class="member-info">
|
|
|
- <div class="member-name">{{ member.name }}</div>
|
|
|
- <div class="member-role">{{ member.role }}</div>
|
|
|
- <div class="member-skills">
|
|
|
- <span *ngIf="member.skillMatch >= 90" class="skill-badge">技能匹配良好</span>
|
|
|
- <span *ngIf="member.progress >= 80" class="skill-badge">进度领先</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="member-actions">
|
|
|
- <button class="message-btn">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="members-timeline-card card">
|
|
|
- <h2>团队协作时间轴</h2>
|
|
|
- <div class="timeline-entries">
|
|
|
- <div *ngFor="let event of timelineEvents" class="timeline-entry">
|
|
|
- <div class="timeline-dot"></div>
|
|
|
- <div class="timeline-content">
|
|
|
- <div class="timeline-header">
|
|
|
- <span class="timeline-author">{{ getEventAuthor(event.action) }}</span>
|
|
|
- <span class="timeline-time">{{ event.time }}</span>
|
|
|
+ @if (stage === '尾款结算') {
|
|
|
+ @if (settlements.length > 0) {
|
|
|
+ <div class="settlement-table">
|
|
|
+ <table>
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>阶段</th>
|
|
|
+ <th>比例</th>
|
|
|
+ <th>金额</th>
|
|
|
+ <th>状态</th>
|
|
|
+ <th>时间</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ @for (st of settlements; track st.id) {
|
|
|
+ <tr>
|
|
|
+ <td>{{ st.stage }}</td>
|
|
|
+ <td>{{ st.percentage }}%</td>
|
|
|
+ <td>¥{{ st.amount | number:'1.0-0' }}</td>
|
|
|
+ <td>{{ st.status }}</td>
|
|
|
+ <td>{{ (st.settledAt || st.createdAt) | date:'MM-dd HH:mm' }}</td>
|
|
|
+ </tr>
|
|
|
+ }
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ }
|
|
|
</div>
|
|
|
- <div class="timeline-text">{{ event.description }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
|
|
|
- <!-- 项目文件标签页 -->
|
|
|
- <div *ngIf="isActiveTab('files')" class="files-tab-content">
|
|
|
- <div class="file-actions">
|
|
|
- <button class="upload-btn primary-btn">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
|
- <polyline points="17 8 12 3 7 8"></polyline>
|
|
|
- <line x1="12" y1="3" x2="12" y2="15"></line>
|
|
|
- </svg>
|
|
|
- <span>上传文件</span>
|
|
|
- </button>
|
|
|
- <div class="file-filter">
|
|
|
- <select class="file-filter-select">
|
|
|
- <option value="all">全部文件</option>
|
|
|
- <option value="images">图片</option>
|
|
|
- <option value="documents">文档</option>
|
|
|
- <option value="models">模型文件</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="files-grid">
|
|
|
- <div *ngFor="let file of projectFiles" class="file-card">
|
|
|
- <div class="file-icon">
|
|
|
- <svg *ngIf="file.type.includes('pdf')" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#EA4335" stroke-width="2">
|
|
|
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
- <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
- <line x1="16" y1="13" x2="8" y2="13"></line>
|
|
|
- <line x1="16" y1="17" x2="8" y2="17"></line>
|
|
|
- <polyline points="10 9 9 9 8 9"></polyline>
|
|
|
- </svg>
|
|
|
- <svg *ngIf="file.type.includes('jpg') || file.type.includes('png')" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#34A853" stroke-width="2">
|
|
|
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
|
- <circle cx="8.5" cy="8.5" r="1.5"></circle>
|
|
|
- <polyline points="21 15 16 10 5 21"></polyline>
|
|
|
- </svg>
|
|
|
- <svg *ngIf="file.type.includes('docx') || file.type.includes('doc')" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#4285F4" stroke-width="2">
|
|
|
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
- <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
- <line x1="16" y1="13" x2="8" y2="13"></line>
|
|
|
- <line x1="16" y1="17" x2="8" y2="17"></line>
|
|
|
- <polyline points="10 9 9 9 8 9"></polyline>
|
|
|
- </svg>
|
|
|
- <svg *ngIf="file.type.includes('rar') || file.type.includes('zip')" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#FBBC05" stroke-width="2">
|
|
|
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
- <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
- <line x1="16" y1="13" x2="8" y2="13"></line>
|
|
|
- <line x1="16" y1="17" x2="8" y2="17"></line>
|
|
|
- <polyline points="10 9 9 9 8 9"></polyline>
|
|
|
- </svg>
|
|
|
- <svg *ngIf="file.type.includes('max') || file.type.includes('obj')" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#9C27B0" stroke-width="2">
|
|
|
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
- <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
- <line x1="16" y1="13" x2="8" y2="13"></line>
|
|
|
- <line x1="16" y1="17" x2="8" y2="17"></line>
|
|
|
- <polyline points="10 9 9 9 8 9"></polyline>
|
|
|
- </svg>
|
|
|
- </div>
|
|
|
- <div class="file-info">
|
|
|
- <div class="file-name">{{ file.name }}</div>
|
|
|
- <div class="file-meta">
|
|
|
- <span class="file-size">{{ file.size }}</span>
|
|
|
- <span class="file-date">{{ file.date }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <button class="file-action-btn">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <circle cx="12" cy="12" r="1"></circle>
|
|
|
- <circle cx="19" cy="12" r="1"></circle>
|
|
|
- <circle cx="5" cy="12" r="1"></circle>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <div class="file-icon">
|
|
|
- <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#FBBC05" stroke-width="2">
|
|
|
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
- <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
- <line x1="16" y1="13" x2="8" y2="13"></line>
|
|
|
- <line x1="16" y1="17" x2="8" y2="17"></line>
|
|
|
- <polyline points="10 9 9 9 8 9"></polyline>
|
|
|
- </svg>
|
|
|
- </div>
|
|
|
- <div class="file-info">
|
|
|
- <div class="file-name">色彩方案.xlsx</div>
|
|
|
- <div class="file-meta">
|
|
|
- <span class="file-size">0.9MB</span>
|
|
|
- <span class="file-date">2025-09-04</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <button class="file-action-btn">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <circle cx="12" cy="12" r="1"></circle>
|
|
|
- <circle cx="19" cy="12" r="1"></circle>
|
|
|
- <circle cx="5" cy="12" r="1"></circle>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="file-storage-info">
|
|
|
- <div class="storage-bar">
|
|
|
- <div class="storage-used" style="width: 45%"></div>
|
|
|
- </div>
|
|
|
- <div class="storage-text">
|
|
|
- <span>已使用 450MB / 1GB</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <!-- 项目成员标签页 -->
|
|
|
+ <!-- ... existing code ... -->
|
|
|
|
|
|
- <!-- 分阶段结算记录卡片:已按需求移除 -->
|
|
|
+ <!-- 项目文件标签页 -->
|
|
|
+ <!-- ... existing code ... -->
|
|
|
+ </div>
|
|
|
+</div>
|