|
@@ -61,97 +61,43 @@
|
|
|
<!-- 项目进度标签页 -->
|
|
|
@if (isActiveTab('progress')) {
|
|
|
<div class="progress-tab-content">
|
|
|
- <!-- 主要内容布局 - 左侧三分之一,右侧三分之二 -->
|
|
|
<div class="main-content-layout">
|
|
|
- <!-- 左侧三分之一 - 项目信息和客户画像 -->
|
|
|
+ <!-- 左侧保留 -->
|
|
|
<div class="left-column">
|
|
|
- <!-- 项目基本信息 -->
|
|
|
<div class="project-info-card card">
|
|
|
- <h2>项目基本信息</h2>
|
|
|
- <div class="info-grid">
|
|
|
- <div class="info-item">
|
|
|
- <label>项目名称</label>
|
|
|
- <span>{{ project?.name || '加载中...' }}</span>
|
|
|
- </div>
|
|
|
- <div class="info-item">
|
|
|
- <label>客户姓名</label>
|
|
|
- <span>{{ project?.customerName || '加载中...' }}</span>
|
|
|
- </div>
|
|
|
- <div class="info-item">
|
|
|
- <label>当前阶段</label>
|
|
|
- <span class="stage-tag">{{ project?.currentStage || '加载中...' }}</span>
|
|
|
- </div>
|
|
|
- @if (project) {
|
|
|
- <div class="info-item">
|
|
|
- <label>预计交付日期</label>
|
|
|
- <span>{{ project.deadline | date:'yyyy-MM-dd' }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 客户画像 -->
|
|
|
- <div class="customer-profile-card card">
|
|
|
- <h2>客户画像</h2>
|
|
|
-
|
|
|
- <!-- 技能匹配度警告 -->
|
|
|
- @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>
|
|
|
- }
|
|
|
-
|
|
|
+ <h2>客户信息</h2>
|
|
|
@if (project) {
|
|
|
- <div class="tags-container">
|
|
|
+ <div class="info-grid">
|
|
|
+ <div class="info-item key-info"><label>客户姓名</label><span>{{ project.customerName }}</span></div>
|
|
|
+ <div class="info-item key-info"><label>项目负责人</label><span>{{ project.assigneeName }}</span></div>
|
|
|
+ <div class="info-item"><label>项目创建</label><span>{{ formatDate(project.createdAt) }}</span></div>
|
|
|
+ <div class="info-item"><label>截止日期</label><span>{{ formatDate(project.deadline) }}</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="tags-container" style="margin-top: 12px;">
|
|
|
<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>
|
|
|
+ <h3>客户标签</h3>
|
|
|
+ <div class="tags">
|
|
|
+ @for (tag of project.customerTags; track $index) {
|
|
|
+ <span class="tag">{{ tag.source }} · {{ tag.needType }} · {{ tag.preference }} · {{ tag.colorAtmosphere }}</span>
|
|
|
}
|
|
|
+ @if (project.customerTags?.length === 0) { <span class="desc">暂无标签</span> }
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
<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 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>
|
|
|
+ <h3>高优先级需求</h3>
|
|
|
+ <ul class="need-list">
|
|
|
+ @for (need of project.highPriorityNeeds; track $index) {
|
|
|
+ <li>{{ need }}</li>
|
|
|
+ }
|
|
|
+ @if (project.highPriorityNeeds?.length === 0) { <li class="desc">无</li> }
|
|
|
+ </ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ } @else {
|
|
|
+ <div class="loading-state">
|
|
|
+ <div class="loading-spinner"></div>
|
|
|
+ <div>正在加载客户信息...</div>
|
|
|
+ </div>
|
|
|
}
|
|
|
</div>
|
|
|
</div>
|
|
@@ -161,269 +107,273 @@
|
|
|
<div class="process-card card">
|
|
|
<h2>制作流程进度</h2>
|
|
|
|
|
|
- <!-- 串式流程:10个阶段横向排列,可展开专属卡片 -->
|
|
|
- <!-- 已按需求移除:每个分阶段的展开按钮 -->
|
|
|
-
|
|
|
- <div class="stage-progress-container">
|
|
|
- <div class="stage-progress-wrapper">
|
|
|
- <div class="stage-progress">
|
|
|
- @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>
|
|
|
- </div>
|
|
|
+ <!-- 新增:四大板块矩形按钮 -->
|
|
|
+ <div class="sections-toolbar">
|
|
|
+ @for (sec of sections; track sec.key) {
|
|
|
+ <button class="section-btn"
|
|
|
+ [class.completed]="getSectionStatus(sec.key) === 'completed'"
|
|
|
+ [class.active]="getSectionStatus(sec.key) === 'active'"
|
|
|
+ [class.pending]="getSectionStatus(sec.key) === 'pending'"
|
|
|
+ (click)="toggleSection(sec.key)">
|
|
|
+ <span class="section-label">{{ sec.label }}</span>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
</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>
|
|
|
+ <!-- 串式流程:10个阶段横向排列(保持) -->
|
|
|
+ <div class="stage-progress-container">
|
|
|
+ <!-- ... existing code ... -->
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 针对不同阶段,展示对应卡片模块(示例:建模/软装/渲染/后期/尾款结算) -->
|
|
|
- @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 class="sections-content">
|
|
|
+ @for (sec of sections; track sec.key) {
|
|
|
+ @if (expandedSection === sec.key) {
|
|
|
+ <div class="section-panel" [attr.data-key]="sec.key">
|
|
|
+ <!-- 交付执行:三阶段横向排列、内容竖向展开、空间平分 -->
|
|
|
+ @if (sec.key === 'delivery') {
|
|
|
+ <div class="delivery-grid">
|
|
|
+ @for (stage of sec.stages; track stage) {
|
|
|
+ <div class="delivery-col" [class.active]="getStageStatus(stage) === 'active'">
|
|
|
+ <div class="delivery-stage-header">
|
|
|
+ <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
|
|
|
+ <h3>{{ stage }}</h3>
|
|
|
</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>
|
|
|
+ <!-- 直接复用原阶段内容卡片:按stage匹配显示 -->
|
|
|
+ <div class="delivery-stage-body">
|
|
|
+ @if (stage === '建模') { <!-- 引用原建模块 -->
|
|
|
+ <!-- BEGIN: reuse 建模阶段内容 -->
|
|
|
+ @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 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 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 (isDesignerView()) { <button class="link danger" (click)="removeWhiteModelImage(img.id)">移除</button> }
|
|
|
+ @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>
|
|
|
+ <!-- END: reuse 建模阶段内容 -->
|
|
|
}
|
|
|
- </div>
|
|
|
- }
|
|
|
- </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>
|
|
|
+ @if (stage === '软装') { <!-- 引用原软装块 -->
|
|
|
+ <!-- BEGIN: reuse 软装阶段内容 -->
|
|
|
+ <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="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 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 (isDesignerView()) { <button class="link danger" (click)="removeSoftDecorImage(img.id)">移除</button> }
|
|
|
- </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="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>
|
|
|
+ @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>
|
|
|
- @if (isDesignerView()) { <button class="link danger" (click)="removeRenderLargeImage(img.id)">移除</button> }
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- END: reuse 软装阶段内容 -->
|
|
|
+ }
|
|
|
+
|
|
|
+ @if (stage === '渲染') { <!-- 引用原渲染块 -->
|
|
|
+ <!-- BEGIN: reuse 渲染阶段内容(简化容器) -->
|
|
|
+ <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>
|
|
|
+ <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>
|
|
|
+ <!-- END: reuse 渲染阶段内容 -->
|
|
|
}
|
|
|
</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> }
|
|
|
+ <!-- 订单创建、确认需求、售后:统一纵向内容容器(保留原功能区) -->
|
|
|
+ @if (sec.key !== 'delivery') {
|
|
|
+ <div class="section-vertical">
|
|
|
+ @for (stage of sec.stages; track stage) {
|
|
|
+ <div class="vertical-stage-block" [class.active]="getStageStatus(stage) === 'active'">
|
|
|
+ <div class="vertical-stage-header">
|
|
|
+ <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
|
|
|
+ <h3>{{ stage }}</h3>
|
|
|
+ </div>
|
|
|
+ <div class="vertical-stage-body">
|
|
|
+ <!-- 复用原模板各阶段的具体内容(示例:尾款结算等) -->
|
|
|
+ @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 class="feedback-content">{{ fb.content }}</div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- @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>
|
|
|
+ }
|
|
|
+ @if (stage === '客户评价') {
|
|
|
+ <div class="empty">此处可扩展客户评价表单/列表</div>
|
|
|
+ }
|
|
|
+ @if (stage === '投诉处理') {
|
|
|
+ <div class="empty">此处可扩展投诉处理流程/记录</div>
|
|
|
+ }
|
|
|
+ @if (stage === '订单创建' || stage === '需求沟通' || stage === '方案确认') {
|
|
|
+ <div class="empty">该阶段的详细表单与内容保持与现有功能一致,后续可按需接入</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
}
|
|
|
}
|
|
|
- </div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 保留原下方按当前激活阶段显示的旧详情网格(可逐步淘汰) -->
|
|
|
+ <!-- ... existing code ... -->
|
|
|
+
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
}
|
|
|
|
|
|
<!-- 项目成员标签页 -->
|
|
@@ -433,3 +383,41 @@
|
|
|
<!-- ... existing code ... -->
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+<!-- 渲染阶段:上传大图 弹窗(基于 @if 控制显示) -->
|
|
|
+@if (showRenderUploadModal) {
|
|
|
+ <div class="modal-backdrop" (click)="closeRenderUploadModal()">
|
|
|
+ <div class="modal" (click)="$event.stopPropagation()">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>上传渲染大图</h3>
|
|
|
+ <button class="close-button" (click)="closeRenderUploadModal()">×</button>
|
|
|
+ </div>
|
|
|
+ <div class="modal-body">
|
|
|
+ <div style="display:flex; gap:12px; align-items:center; margin-bottom:12px;">
|
|
|
+ <input #renderFileInput type="file" accept="{{allowedImageTypes}}" multiple (change)="onRenderLargePicsSelected($event)" style="display:none" />
|
|
|
+ <button class="secondary-btn" (click)="renderFileInput.click()">选择文件</button>
|
|
|
+ <span class="hint">支持 {{allowedImageTypes}};将校验4K标准(最大边≥4000px)</span>
|
|
|
+ </div>
|
|
|
+ @if (pendingRenderLargeItems.length > 0) {
|
|
|
+ <div class="thumb-list" style="max-height:320px; overflow:auto;">
|
|
|
+ @for (item of pendingRenderLargeItems; track item.id) {
|
|
|
+ <div class="thumb-item">
|
|
|
+ <img [src]="item.url" [alt]="item.name" />
|
|
|
+ <div class="thumb-meta">
|
|
|
+ <span class="name" [title]="item.name">{{ item.name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ @if (pendingRenderLargeItems.length === 0) {
|
|
|
+ <div class="empty-state" style="padding:12px;color:#6b7280;">尚未选择文件</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button class="secondary-btn" (click)="closeRenderUploadModal()">取消</button>
|
|
|
+ <button class="primary-btn" [disabled]="pendingRenderLargeItems.length === 0" (click)="confirmRenderUpload()">确认上传</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+}
|