|
|
@@ -1,4 +1,4 @@
|
|
|
-<!-- 只展示修改处,未变更部分用占位注释表示 -->
|
|
|
+<!-- 只展示修改处,未变更部分用占位注释表示 -->
|
|
|
<div class="project-detail-container designer-page">
|
|
|
<!-- 项目标题栏 -->
|
|
|
<div class="project-header card">
|
|
|
@@ -16,11 +16,16 @@
|
|
|
|
|
|
<!-- 导航按钮区域 - 移动到标题左侧 -->
|
|
|
<div class="header-nav">
|
|
|
- <app-vertical-nav
|
|
|
- [activeTab]="activeTab"
|
|
|
- (tabChange)="switchTab($event)"
|
|
|
- class="header-nav-tabs">
|
|
|
- </app-vertical-nav>
|
|
|
+ <div class="header-nav-tabs">
|
|
|
+ @for (tab of tabs; track tab.id) {
|
|
|
+ <button
|
|
|
+ class="nav-tab"
|
|
|
+ [class.active]="activeTab === tab.id"
|
|
|
+ (click)="switchTab(tab.id)">
|
|
|
+ {{ tab.name }}
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 四个环节圆圈导航 -->
|
|
|
@@ -180,6 +185,33 @@
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
+<!-- 设计师日历弹窗 -->
|
|
|
+@if (showDesignerCalendar) {
|
|
|
+ <div class="image-preview-modal" (click)="closeDesignerCalendar()">
|
|
|
+ <div class="modal-backdrop"></div>
|
|
|
+ <div class="modal-content" (click)="$event.stopPropagation()">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>选择设计师与排期</h3>
|
|
|
+ <button class="close-btn" (click)="closeDesignerCalendar()">
|
|
|
+ <svg width="24" height="24" 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="modal-body">
|
|
|
+ <app-designer-calendar
|
|
|
+ [designers]="calendarDesigners"
|
|
|
+ [selectedDate]="selectedCalendarDate"
|
|
|
+ [projectGroups]="calendarGroups"
|
|
|
+ (designerSelected)="onCalendarDesignerSelected($event)"
|
|
|
+ (assignmentRequested)="onCalendarAssignmentRequested($event)"
|
|
|
+ ></app-designer-calendar>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+}
|
|
|
+
|
|
|
<!-- 提醒消息弹窗 -->
|
|
|
@if (reminderMessage) {
|
|
|
<div class="reminder-popup">
|
|
|
@@ -593,13 +625,14 @@
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
- <!-- 小图时间 -->
|
|
|
- @if (getEstimatedSmallImageTime()) {
|
|
|
- <div class="info-item">
|
|
|
- <label>预计时间</label>
|
|
|
- <span>{{ getEstimatedSmallImageTime() }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
+ <!-- 订单金额 -->
|
|
|
+ <!-- 订单金额 -->
|
|
|
+ <div class="info-item">
|
|
|
+ <label>订单金额</label>
|
|
|
+ <span>¥{{ orderAmount || 0 }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
</div>
|
|
|
|
|
|
<!-- 开始分析按钮 -->
|
|
|
@@ -627,7 +660,197 @@
|
|
|
<div class="right-column">
|
|
|
<!-- 移除顶部四个圆圈,直接展示阶段内容 -->
|
|
|
|
|
|
- <!-- 串式流程:10个阶段横向排列(保持) -->
|
|
|
+ <!-- 新增:客户信息右侧 2x2 售后模块布局 -->
|
|
|
+ @if (expandedSection === 'aftercare') {
|
|
|
+ <div class="aftercare-grid">
|
|
|
+
|
|
|
+ <!-- 1/4:尾款结算 -->
|
|
|
+ <div class="aftercare-module settlement-module card">
|
|
|
+ <div class="module-header">
|
|
|
+ <h2>💰 尾款结算</h2>
|
|
|
+ <p class="module-description">技术完成验收后自动发起尾款结算流程,支持多种支付方式自动化处理</p>
|
|
|
+ </div>
|
|
|
+ <div class="settlement-automation">
|
|
|
+ <div class="automation-features">
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">🤖</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>自动触发流程</h4>
|
|
|
+ <p>技术验收完成后自动发起尾款结算申请</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">🔓</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>自动解密发送</h4>
|
|
|
+ <p>小程序支付自动解密并发送大图给客户</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">📱</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>凭证智能识别</h4>
|
|
|
+ <p>上传微信/支付宝截图自动提取金额和支付方式</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">🔔</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>自动通知</h4>
|
|
|
+ <p>支付完成后自动发送"尾款已到账,大图已解锁"通知</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="settlement-status">
|
|
|
+ <app-settlement-card [settlements]="settlements"></app-settlement-card>
|
|
|
+ </div>
|
|
|
+ @if (canEditSection('aftercare')) {
|
|
|
+ <div class="automation-actions">
|
|
|
+ <button
|
|
|
+ class="primary-btn automation-btn"
|
|
|
+ (click)="initiateAutoSettlement()"
|
|
|
+ [disabled]="isAutoSettling">
|
|
|
+ @if (isAutoSettling) { <span class="loading-spinner"></span> 自动化处理中... } @else { 🚀 启动自动化结算 }
|
|
|
+ </button>
|
|
|
+ <button class="secondary-btn" (click)="uploadPaymentProof()"><span class="upload-icon">📎</span> 上传支付凭证</button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 2/4:全景图合成 -->
|
|
|
+ <div class="aftercare-module panoramic-module card">
|
|
|
+ <div class="module-header">
|
|
|
+ <h2>🖼️ 全景图合成</h2>
|
|
|
+ <p class="module-description">集成内部全景图合成服务器,技术上传图片自动生成漫游式全景链接</p>
|
|
|
+ </div>
|
|
|
+ <div class="panoramic-synthesis">
|
|
|
+ <div class="panoramic-features">
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">🖥️</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>KR Panel集成</h4>
|
|
|
+ <p>集成专业全景图合成工具,确保合成质量</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">📸</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>智能空间标注</h4>
|
|
|
+ <p>技术上传图片并标注空间名称(如"客厅-角度1")</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">🔗</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>自动生成链接</h4>
|
|
|
+ <p>自动生成漫游式全景链接并发送给客户</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="recent-syntheses">
|
|
|
+ @if (panoramicSyntheses && panoramicSyntheses.length > 0) {
|
|
|
+ <div class="panoramic-card-list">
|
|
|
+ @for (item of panoramicSyntheses; track item.id) {
|
|
|
+ <app-panoramic-synthesis-card
|
|
|
+ [synthesis]="item"
|
|
|
+ [showActions]="true">
|
|
|
+ </app-panoramic-synthesis-card>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="desc">暂无最近合成记录</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ @if (canEditSection('aftercare')) {
|
|
|
+ <div class="panoramic-actions">
|
|
|
+ <button class="primary-btn" (click)="startPanoramicSynthesis()">开始合成</button>
|
|
|
+ <button class="secondary-btn" (click)="viewPanoramicGallery()">查看全景图库</button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 3/4:客户评价 -->
|
|
|
+ <div class="aftercare-module review-module card">
|
|
|
+ <div class="module-header">
|
|
|
+ <h2>⭐ 客户评价</h2>
|
|
|
+ <p class="module-description">邀请客户进行多维度评价并生成评价链接</p>
|
|
|
+ </div>
|
|
|
+ <div class="customer-review-enhanced">
|
|
|
+ <div class="review-form-container">
|
|
|
+ <app-customer-review-form
|
|
|
+ [projectName]="project?.name || ''"
|
|
|
+ [designerName]="getCurrentDesignerName()"
|
|
|
+ (reviewSubmitted)="onReviewSubmitted($event)"
|
|
|
+ (reviewSaved)="onReviewSaved($event)">
|
|
|
+ </app-customer-review-form>
|
|
|
+ </div>
|
|
|
+ <div class="review-card-container" style="margin-top:12px;">
|
|
|
+ <app-customer-review-card [feedbacks]="feedbacks"></app-customer-review-card>
|
|
|
+ </div>
|
|
|
+ @if (canEditSection('aftercare')) {
|
|
|
+ <div class="review-actions">
|
|
|
+ <button class="primary-btn" (click)="confirmCustomerReview()">✅ 确认评价完成</button>
|
|
|
+ <button class="secondary-btn" (click)="generateReviewLink()">🔗 生成评价链接</button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 4/4:投诉处理 -->
|
|
|
+ <div class="aftercare-module complaint-module card">
|
|
|
+ <div class="module-header">
|
|
|
+ <h2>📋 投诉处理</h2>
|
|
|
+ <p class="module-description">支持人工创建和关键词自动抓取投诉,提升处理效率</p>
|
|
|
+ </div>
|
|
|
+ <div class="complaint-management-enhanced">
|
|
|
+ <div class="complaint-features">
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">👥</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>人工创建</h4>
|
|
|
+ <p>组长或客服人工创建投诉记录</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">🔍</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>关键词抓取</h4>
|
|
|
+ <p>自动监测企业微信群关键词(不满意、投诉、退款)</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">🏷️</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>智能标注</h4>
|
|
|
+ <p>自动标注投诉环节和核心问题</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feature-card">
|
|
|
+ <div class="feature-icon">📊</div>
|
|
|
+ <div class="feature-content">
|
|
|
+ <h4>实时更新</h4>
|
|
|
+ <p>处理进度实时更新至系统</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="complaint-content">
|
|
|
+ <app-complaint-card [complaints]="exceptionHistories"></app-complaint-card>
|
|
|
+ </div>
|
|
|
+ @if (canEditSection('aftercare')) {
|
|
|
+ <div class="complaint-actions">
|
|
|
+ <button class="primary-btn" (click)="createComplaintManually()">📝 人工创建投诉</button>
|
|
|
+ <button class="secondary-btn" (click)="setupKeywordMonitoring()">⚙️ 设置关键词监测</button>
|
|
|
+ <button class="primary-btn" (click)="confirmComplaint()">✅ 确认投诉处理完成</button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 串式流程:10个阶段横向排列(保持) -->
|
|
|
<div class="stage-progress-container">
|
|
|
@for (stage of getVisibleStages(); track stage) {
|
|
|
<div class="vertical-stage-block" [attr.id]="stageToAnchor(stage)" [class.active]="getStageStatus(stage) === 'active'">
|
|
|
@@ -645,6 +868,24 @@
|
|
|
(orderCreated)="onConsultationOrderSubmit($event)"
|
|
|
(projectCreated)="onProjectCreated($event)"
|
|
|
></app-consultation-order-panel>
|
|
|
+ <div class="order-creation-extra" style="margin-top:16px;">
|
|
|
+ <div class="info-item">
|
|
|
+ <label>订单金额</label>
|
|
|
+ <span>¥{{ orderAmount || 0 }}</span>
|
|
|
+ </div>
|
|
|
+ <app-quotation-details
|
|
|
+ [initialData]="quotationData"
|
|
|
+ (dataChange)="onQuotationDataChange($event)"
|
|
|
+ ></app-quotation-details>
|
|
|
+ <div class="designer-assignment-container" style="margin-top:16px;">
|
|
|
+ <app-designer-assignment
|
|
|
+ [quotationItems]="quotationData.items"
|
|
|
+ [initialAssignment]="designerAssignmentData"
|
|
|
+ (assignmentChange)="onDesignerAssignmentChange($event)"
|
|
|
+ (designerClick)="onDesignerClick($event)"
|
|
|
+ ></app-designer-assignment>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
} @else if (stage === '需求沟通') {
|
|
|
<app-requirements-confirm-card
|
|
|
(requirementConfirmed)="syncRequirementKeyInfo($event)"
|
|
|
@@ -744,398 +985,244 @@
|
|
|
<div class="empty-tip" style="color:#888;">暂无色彩分析结果</div>
|
|
|
}
|
|
|
</div>
|
|
|
- } @else if (stage === '建模' || stage === '软装' || stage === '渲染' || stage === '后期') {
|
|
|
- <!-- 横向折叠面板布局 -->
|
|
|
- <div class="delivery-execution-panel">
|
|
|
- <div class="delivery-processes-container">
|
|
|
- @for (process of deliveryProcesses; track process.name) {
|
|
|
- @if ((stage === '建模' && process.type === 'modeling') ||
|
|
|
- (stage === '软装' && process.type === 'softDecor') ||
|
|
|
- (stage === '渲染' && process.type === 'rendering') ||
|
|
|
- (stage === '后期' && process.type === 'postProcess')) {
|
|
|
- <div class="delivery-process-card" [class.expanded]="process.isExpanded">
|
|
|
- <!-- 流程标题 -->
|
|
|
- <div class="process-header" (click)="toggleProcess(process.id)">
|
|
|
- <h4>{{ process.name }}</h4>
|
|
|
- <div class="expand-icon" [class.rotated]="process.isExpanded">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <polyline points="6,9 12,15 18,9"></polyline>
|
|
|
- </svg>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 空间列表 -->
|
|
|
- <div class="spaces-list">
|
|
|
- @for (space of process.spaces; track space.id) {
|
|
|
- <div class="space-item"
|
|
|
- [class.active]="space.isExpanded"
|
|
|
- (click)="toggleSpace(process.id, space.id)">
|
|
|
- <span class="space-name">{{ space.name }}</span>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <button class="remove-space-btn"
|
|
|
- (click)="$event.stopPropagation(); removeSpace(process.id, space.id)"
|
|
|
- title="删除空间">
|
|
|
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ } @else if (stage === '建模') {
|
|
|
+ <div class="upload-section">
|
|
|
+ <div class="upload-header">
|
|
|
+ <h4>上传白模图片</h4>
|
|
|
+ <span class="hint">支持:JPG/PNG,不强制4K</span>
|
|
|
+ </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>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 添加空间按钮 -->
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- @if (showAddSpaceInput[process.id]) {
|
|
|
- <div class="add-space-input">
|
|
|
- <input type="text"
|
|
|
- [(ngModel)]="newSpaceName[process.id]"
|
|
|
- placeholder="输入空间名称"
|
|
|
- (keyup.enter)="addSpace(process.id)"
|
|
|
- (keyup.escape)="cancelAddSpace(process.id)"
|
|
|
- #spaceInput>
|
|
|
- <button class="confirm-btn" (click)="addSpace(process.id)">确认</button>
|
|
|
- <button class="cancel-btn" (click)="cancelAddSpace(process.id)">取消</button>
|
|
|
</div>
|
|
|
- } @else {
|
|
|
- <button class="add-space-btn" (click)="showAddSpaceForm(process.id)">
|
|
|
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <line x1="12" y1="5" x2="12" y2="19"></line>
|
|
|
- <line x1="5" y1="12" x2="19" y2="12"></line>
|
|
|
- </svg>
|
|
|
- 添加空间
|
|
|
- </button>
|
|
|
- }
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 展开的内容区域 -->
|
|
|
- @if (process.isExpanded) {
|
|
|
- <div class="process-content">
|
|
|
- @for (space of process.spaces; track space.id) {
|
|
|
- @if (space.isExpanded) {
|
|
|
- <div class="space-content">
|
|
|
- <div class="space-content-header">
|
|
|
- <h5>{{ space.name }} - {{ process.name }}</h5>
|
|
|
- </div>
|
|
|
-
|
|
|
- @if (process.type === 'modeling') {
|
|
|
- <!-- 建模内容 -->
|
|
|
- <div class="modeling-content">
|
|
|
- <div class="upload-section">
|
|
|
- <div class="upload-header">
|
|
|
- <h6>上传白模图片</h6>
|
|
|
- <span class="hint">支持:JPG/PNG,不强制4K</span>
|
|
|
- </div>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <div class="upload-dropzone"
|
|
|
- (click)="!process.content[space.id]?.images || process.content[space.id].images.length === 0 ? triggerSpaceFileInput(process.id, space.id) : null"
|
|
|
- [class.has-images]="(process.content[space.id]?.images?.length || 0) > 0">
|
|
|
- @if (!process.content[space.id]?.images || process.content[space.id].images.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 process.content[space.id].images; 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(); removeSpaceImage(process.id, space.id, 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>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <div class="add-more-btn" (click)="triggerSpaceFileInput(process.id, space.id)">
|
|
|
- <div class="add-icon">+</div>
|
|
|
- <div class="add-text">添加更多</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
-
|
|
|
- <!-- 确认上传按钮 -->
|
|
|
- @if (canEditSection('delivery') && (process.content[space.id]?.images?.length || 0) > 0) {
|
|
|
- <div class="upload-actions">
|
|
|
- <button class="primary-btn" (click)="confirmWhiteModelUpload()">
|
|
|
- 确认上传
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- } @else if (process.type === 'softDecor') {
|
|
|
- <!-- 软装内容 -->
|
|
|
- <div class="softdecor-content">
|
|
|
- <div class="upload-section">
|
|
|
- <div class="upload-header">
|
|
|
- <h6>上传软装小图</h6>
|
|
|
- <span class="hint">建议 ≤1MB 的 JPG/PNG 小图</span>
|
|
|
- </div>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <div class="upload-dropzone"
|
|
|
- (click)="!process.content[space.id]?.images || process.content[space.id].images.length === 0 ? triggerSpaceFileInput(process.id, space.id) : null"
|
|
|
- [class.has-images]="(process.content[space.id]?.images?.length || 0) > 0">
|
|
|
- @if (!process.content[space.id]?.images || process.content[space.id].images.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 process.content[space.id].images; 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(); removeSpaceImage(process.id, space.id, 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>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <div class="add-more-btn" (click)="triggerSpaceFileInput(process.id, space.id)">
|
|
|
- <div class="add-icon">+</div>
|
|
|
- <div class="add-text">添加更多</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 软装阶段确认上传按钮 -->
|
|
|
- @if (canEditSection('delivery') && (process.content[space.id]?.images?.length || 0) > 0) {
|
|
|
- <div class="upload-actions">
|
|
|
- <button class="primary-btn" (click)="confirmSoftDecorUpload()">
|
|
|
- 确认上传
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- } @else if (process.type === 'rendering') {
|
|
|
- <!-- 渲染内容 -->
|
|
|
- <div class="rendering-content">
|
|
|
- <div class="upload-section">
|
|
|
- <div class="upload-header">
|
|
|
- <h6>上传渲染图片</h6>
|
|
|
- <span class="hint">建议 ≥4K 分辨率的 JPG/PNG 图片</span>
|
|
|
- </div>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <div class="upload-dropzone"
|
|
|
- (click)="!process.content[space.id]?.images || process.content[space.id].images.length === 0 ? triggerSpaceFileInput(process.id, space.id) : null"
|
|
|
- [class.has-images]="(process.content[space.id]?.images?.length || 0) > 0">
|
|
|
- @if (!process.content[space.id]?.images || process.content[space.id].images.length === 0) {
|
|
|
- <div class="upload-icon"></div>
|
|
|
- <div class="upload-text">点击此处上传渲染图片</div>
|
|
|
- <div class="upload-hint">支持 JPG、PNG 格式,建议 4K 分辨率</div>
|
|
|
- } @else {
|
|
|
- <div class="uploaded-images-grid">
|
|
|
- @for (img of process.content[space.id].images; 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(); removeSpaceImage(process.id, space.id, 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>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <div class="add-more-btn" (click)="triggerSpaceFileInput(process.id, space.id)">
|
|
|
- <div class="add-icon">+</div>
|
|
|
- <div class="add-text">添加更多</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="render-progress-info">
|
|
|
- <h6>渲染进度</h6>
|
|
|
- <div class="progress-details">
|
|
|
- <div class="progress-item">
|
|
|
- <span class="label">状态:</span>
|
|
|
- <span class="value">{{ process.content[space.id]?.status || '待开始' }}</span>
|
|
|
- </div>
|
|
|
- <div class="progress-item">
|
|
|
- <span class="label">完成度:</span>
|
|
|
- <span class="value">{{ process.content[space.id]?.progress || 0 }}%</span>
|
|
|
- </div>
|
|
|
- <div class="progress-item">
|
|
|
- <span class="label">更新时间:</span>
|
|
|
- <span class="value">{{ process.content[space.id]?.lastUpdated || '暂无' }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- @if (process.content[space.id]?.notes) {
|
|
|
- <div class="progress-notes">
|
|
|
- <span class="label">备注:</span>
|
|
|
- <span class="value">{{ process.content[space.id].notes }}</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 渲染阶段确认上传按钮 -->
|
|
|
- @if (canEditSection('delivery') && (process.content[space.id]?.images?.length || 0) > 0) {
|
|
|
- <div class="upload-actions">
|
|
|
- <button class="primary-btn" (click)="confirmRenderUpload()">
|
|
|
- 确认上传
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- } @else if (process.type === 'postProcess') {
|
|
|
- <!-- 后期内容 -->
|
|
|
- <div class="postprocess-content">
|
|
|
- <div class="upload-section">
|
|
|
- <div class="upload-header">
|
|
|
- <h6>上传后期图片</h6>
|
|
|
- <span class="hint">建议 ≥4K 分辨率的 JPG/PNG 图片</span>
|
|
|
- </div>
|
|
|
- @if (canEditSection('delivery')) {
|
|
|
- <div class="upload-dropzone"
|
|
|
- (click)="!process.content[space.id]?.images || process.content[space.id].images.length === 0 ? triggerSpaceFileInput(process.id, space.id) : null"
|
|
|
- [class.has-images]="(process.content[space.id]?.images?.length || 0) > 0">
|
|
|
- @if (!process.content[space.id]?.images || process.content[space.id].images.length === 0) {
|
|
|
- <div class="upload-icon"></div>
|
|
|
- <div class="upload-text">点击此处上传后期图片</div>
|
|
|
- <div class="upload-hint">支持 JPG、PNG 格式,建议 4K 分辨率</div>
|
|
|
- } @else {
|
|
|
- <div class="uploaded-images-grid">
|
|
|
- @for (img of process.content[space.id].images; 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(); removeSpaceImage(process.id, space.id, 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>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <div class="add-more-btn" (click)="triggerSpaceFileInput(process.id, space.id)">
|
|
|
- <div class="add-icon">+</div>
|
|
|
- <div class="add-text">添加更多</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 后期阶段确认上传按钮 -->
|
|
|
- @if (canEditSection('delivery') && (process.content[space.id]?.images?.length || 0) > 0) {
|
|
|
- <div class="upload-actions">
|
|
|
- <button class="primary-btn" (click)="confirmPostProcessUpload()">
|
|
|
- 确认上传
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </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>
|
|
|
- } @else if (stage === '尾款结算') {
|
|
|
- <div class="aftercare-section-container">
|
|
|
- <app-settlement-card [settlements]="settlements"></app-settlement-card>
|
|
|
+ <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>
|
|
|
- @if (canEditSection('aftercare')) {
|
|
|
- <div class="upload-actions">
|
|
|
- <button
|
|
|
- class="primary-btn"
|
|
|
- [class.completed]="isSettlementCompleted"
|
|
|
- [disabled]="isConfirmingSettlement"
|
|
|
- (click)="confirmSettlement()">
|
|
|
- @if (isConfirmingSettlement) {
|
|
|
- <span class="loading-spinner"></span>
|
|
|
- 确认中...
|
|
|
- } @else if (isSettlementCompleted) {
|
|
|
- <span class="check-icon">✓</span>
|
|
|
- 已确认完成
|
|
|
- } @else {
|
|
|
- 确认尾款结算完成
|
|
|
+ } @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>
|
|
|
+ @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>
|
|
|
+ </div>
|
|
|
+ </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>
|
|
|
}
|
|
|
- </button>
|
|
|
- <button class="secondary-btn" (click)="uploadPaymentProof()">
|
|
|
- <span class="upload-icon">📎</span>
|
|
|
- 上传支付凭证
|
|
|
- </button>
|
|
|
+ @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('soft')">同步图片信息</button> }
|
|
|
+ @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- }
|
|
|
- } @else if (stage === '客户评价') {
|
|
|
- <div class="aftercare-section-container">
|
|
|
- <app-customer-review-card [feedbacks]="feedbacks"></app-customer-review-card>
|
|
|
</div>
|
|
|
- @if (canEditSection('aftercare')) {
|
|
|
- <div class="upload-actions">
|
|
|
- <button class="primary-btn" (click)="confirmCustomerReview()">确认客户评价完成</button>
|
|
|
+ } @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>
|
|
|
+ @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>
|
|
|
+ </div>
|
|
|
+ </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 class="upload-actions">
|
|
|
+ @if (canEditSection('delivery')) {
|
|
|
+ <button class="primary-btn" [disabled]="renderLargeImages.length===0" (click)="confirmRenderUpload()">确认上传</button>
|
|
|
+ }
|
|
|
+ @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('render')">同步图片信息</button> }
|
|
|
+ @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- }
|
|
|
- } @else if (stage === '投诉处理') {
|
|
|
- <div class="aftercare-section-container">
|
|
|
- <app-complaint-card [complaints]="exceptionHistories"></app-complaint-card>
|
|
|
</div>
|
|
|
- @if (canEditSection('aftercare')) {
|
|
|
- <div class="upload-actions">
|
|
|
- <button class="primary-btn" (click)="confirmComplaint()">确认投诉处理完成</button>
|
|
|
- </div>
|
|
|
- }
|
|
|
+ } @else if (stage === '尾款结算') {
|
|
|
+ <!-- 原尾款结算售后 tab 容器已移除,根据用户要求不再使用 -->
|
|
|
}
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -1145,7 +1232,8 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
<!-- 项目人员标签页 -->
|
|
|
@if (isActiveTab('members')) {
|
|
|
<div class="members-tab-content">
|