|
@@ -1,4 +1,6 @@
|
|
|
-<!-- 加载中 -->
|
|
|
+<!-- 售后归档阶段 - 完整模板 -->
|
|
|
+
|
|
|
+<!-- 加载中状态 -->
|
|
|
@if (loading) {
|
|
|
<div class="loading-container">
|
|
|
<div class="spinner">
|
|
@@ -8,467 +10,922 @@
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
-<!-- 售后归档内容 -->
|
|
|
+<!-- 主内容 -->
|
|
|
@if (!loading) {
|
|
|
<div class="stage-aftercare-container">
|
|
|
- <!-- 1. 尾款管理 -->
|
|
|
- <div class="card payment-card">
|
|
|
- <div class="card-header">
|
|
|
- <div class="card-title-wrapper">
|
|
|
- <h3 class="card-title">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M53.12 199.94l400-151.39a8 8 0 0110.33 10.33l-151.39 400a8 8 0 01-15-.34l-67.4-166.09a16 16 0 00-10.11-10.11L53.46 215a8 8 0 01-.34-15.06z"/>
|
|
|
- <circle cx="256" cy="256" r="16"/>
|
|
|
- </svg>
|
|
|
- 尾款管理
|
|
|
- </h3>
|
|
|
- <span class="badge" [class]="'badge-' + getPaymentStatusColor()">
|
|
|
- {{ getPaymentStatusText() }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+
|
|
|
+ <!-- Tab导航 -->
|
|
|
+ <div class="tab-navigation">
|
|
|
+ <div class="tab-buttons">
|
|
|
+ <button
|
|
|
+ class="tab-btn"
|
|
|
+ [class.active]="activeTab === 'overview'"
|
|
|
+ (click)="switchTab('overview')">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <rect x="48" y="48" width="176" height="176" rx="20" ry="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="288" y="48" width="176" height="176" rx="20" ry="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="48" y="288" width="176" height="176" rx="20" ry="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="288" y="288" width="176" height="176" rx="20" ry="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ 概览
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="tab-btn"
|
|
|
+ [class.active]="activeTab === 'payment'"
|
|
|
+ (click)="switchTab('payment')">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <rect x="48" y="96" width="416" height="320" rx="40" ry="40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="60" d="M48 192h416M128 300h48v20h-48z"/>
|
|
|
+ </svg>
|
|
|
+ 尾款
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="tab-btn"
|
|
|
+ [class.active]="activeTab === 'feedback'"
|
|
|
+ (click)="switchTab('feedback')">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M480 208H308L256 48l-52 160H32l140 96-54 160 138-100 138 100-54-160z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ 评价
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="tab-btn"
|
|
|
+ [class.active]="activeTab === 'retrospective'"
|
|
|
+ (click)="switchTab('retrospective')">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <rect x="64" y="320" width="48" height="160" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="288" y="224" width="48" height="256" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="400" y="112" width="48" height="368" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="176" y="32" width="48" height="448" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ 复盘
|
|
|
+ </button>
|
|
|
</div>
|
|
|
- <div class="card-content">
|
|
|
- <div class="payment-summary">
|
|
|
- <div class="summary-item">
|
|
|
- <span class="label">总金额</span>
|
|
|
- <span class="value">¥{{ finalPayment.totalAmount.toLocaleString() }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ============ 概览Tab ============ -->
|
|
|
+ @if (activeTab === 'overview') {
|
|
|
+ <div class="overview-section">
|
|
|
+
|
|
|
+ <!-- 已归档状态 -->
|
|
|
+ @if (archiveStatus.archived) {
|
|
|
+ <div class="card archived-card">
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="archived-status">
|
|
|
+ <svg class="icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M336 176h40a40 40 0 0140 40v208a40 40 0 01-40 40H136a40 40 0 01-40-40V216a40 40 0 0140-40h40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 272l64 64 112-112"/>
|
|
|
+ </svg>
|
|
|
+ <h3>项目已归档</h3>
|
|
|
+ <p>归档时间: {{ archiveStatus.archiveTime | date:'yyyy-MM-dd HH:mm' }}</p>
|
|
|
+ <p>归档人: {{ archiveStatus.archivedBy?.name }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="summary-item">
|
|
|
- <span class="label">已支付</span>
|
|
|
- <span class="value success">¥{{ finalPayment.paidAmount.toLocaleString() }}</span>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 完成度卡片 -->
|
|
|
+ <div class="card completion-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
|
|
|
+ <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
+ </svg>
|
|
|
+ 整体完成度
|
|
|
+ </h3>
|
|
|
</div>
|
|
|
- <div class="summary-item">
|
|
|
- <span class="label">待支付</span>
|
|
|
- <span class="value warning">¥{{ finalPayment.remainingAmount.toLocaleString() }}</span>
|
|
|
+ <div class="card-content">
|
|
|
+ <!-- 圆形进度 -->
|
|
|
+ <div class="circular-progress">
|
|
|
+ <svg class="progress-ring" width="160" height="160">
|
|
|
+ <circle
|
|
|
+ class="progress-ring-circle-bg"
|
|
|
+ stroke="#e0e0e0"
|
|
|
+ stroke-width="12"
|
|
|
+ fill="transparent"
|
|
|
+ r="70"
|
|
|
+ cx="80"
|
|
|
+ cy="80"/>
|
|
|
+ <circle
|
|
|
+ class="progress-ring-circle"
|
|
|
+ stroke="#2dd36f"
|
|
|
+ stroke-width="12"
|
|
|
+ fill="transparent"
|
|
|
+ r="70"
|
|
|
+ cx="80"
|
|
|
+ cy="80"
|
|
|
+ [style.stroke-dasharray]="439.6"
|
|
|
+ [style.stroke-dashoffset]="439.6 - (439.6 * stats.completionRate / 100)"/>
|
|
|
+ <text x="80" y="85" text-anchor="middle" class="progress-text">
|
|
|
+ {{ stats.completionRate }}%
|
|
|
+ </text>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 关键指标 -->
|
|
|
+ <div class="key-metrics">
|
|
|
+ <div class="metric-item">
|
|
|
+ <div class="metric-icon">💰</div>
|
|
|
+ <div class="metric-info">
|
|
|
+ <div class="metric-label">尾款状态</div>
|
|
|
+ <div class="metric-value">{{ getPaymentStatusText() }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-item">
|
|
|
+ <div class="metric-icon">⭐</div>
|
|
|
+ <div class="metric-info">
|
|
|
+ <div class="metric-label">客户满意度</div>
|
|
|
+ <div class="metric-value">{{ stats.averageRating }}/5.0</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="metric-item">
|
|
|
+ <div class="metric-icon">📊</div>
|
|
|
+ <div class="metric-info">
|
|
|
+ <div class="metric-label">复盘分析</div>
|
|
|
+ <div class="metric-value">{{ projectRetrospective ? '已生成' : '未生成' }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 进度条 -->
|
|
|
- <div class="progress-bar">
|
|
|
- <div
|
|
|
- class="progress-fill"
|
|
|
- [style.width.%]="finalPayment.totalAmount > 0 ? (finalPayment.paidAmount / finalPayment.totalAmount) * 100 : 0">
|
|
|
+ <!-- 快捷操作卡片 -->
|
|
|
+ <div class="card quick-actions-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 112l80-80 80 80M255.98 32l.02 448"/>
|
|
|
+ </svg>
|
|
|
+ 快捷操作
|
|
|
+ </h3>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="action-grid">
|
|
|
+ <button class="action-card" (click)="switchTab('payment')" [disabled]="!canEdit">
|
|
|
+ <div class="action-icon payment-icon">💰</div>
|
|
|
+ <div class="action-title">管理尾款</div>
|
|
|
+ <div class="action-desc">{{ finalPayment.status === 'completed' ? '已结清' : '待处理' }}</div>
|
|
|
+ <div class="action-badge" [ngClass]="'badge-' + getPaymentStatusColor()">
|
|
|
+ {{ formatCurrency(finalPayment.remainingAmount) }}
|
|
|
+ </div>
|
|
|
+ </button>
|
|
|
|
|
|
- <!-- 支付凭证列表 -->
|
|
|
- @if (finalPayment.paymentVouchers.length > 0) {
|
|
|
- <div class="vouchers-section">
|
|
|
- <h4>支付凭证</h4>
|
|
|
- <div class="list">
|
|
|
- @for (voucher of finalPayment.paymentVouchers; track $index) {
|
|
|
- <div class="list-item">
|
|
|
- <div class="thumbnail">
|
|
|
- <img [src]="voucher.url" alt="支付凭证" />
|
|
|
- </div>
|
|
|
- <div class="item-content">
|
|
|
- <h3>¥{{ voucher.amount.toLocaleString() }}</h3>
|
|
|
- <p>{{ voucher.paymentMethod }}</p>
|
|
|
- <p class="time">{{ voucher.paymentTime | date:'yyyy-MM-dd HH:mm' }}</p>
|
|
|
- </div>
|
|
|
+ <button class="action-card" (click)="switchTab('feedback')" [disabled]="customerFeedback.submitted">
|
|
|
+ <div class="action-icon feedback-icon">⭐</div>
|
|
|
+ <div class="action-title">客户评价</div>
|
|
|
+ <div class="action-desc">{{ customerFeedback.submitted ? '已提交' : '待评价' }}</div>
|
|
|
+ <div class="action-badge" [class.badge-success]="customerFeedback.submitted">
|
|
|
+ {{ customerFeedback.overallRating || 0 }}/5
|
|
|
</div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
+ </button>
|
|
|
|
|
|
- @if (canEdit && finalPayment.status !== 'completed') {
|
|
|
- <input
|
|
|
- type="file"
|
|
|
- accept="image/*"
|
|
|
- (change)="uploadPaymentVoucher($event)"
|
|
|
- [disabled]="uploading"
|
|
|
- hidden
|
|
|
- #voucherInput />
|
|
|
- <button
|
|
|
- class="btn btn-outline btn-block"
|
|
|
- (click)="voucherInput.click()"
|
|
|
- [disabled]="uploading">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M350.54 148.68l-26.62-42.06C318.31 100.08 310.62 96 302 96h-92c-8.62 0-16.31 4.08-21.92 10.62l-26.62 42.06C155.85 155.23 148.62 160 140 160H80a32 32 0 00-32 32v192a32 32 0 0032 32h352a32 32 0 0032-32V192a32 32 0 00-32-32h-59c-8.65 0-16.85-4.77-22.46-11.32z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- <circle cx="256" cy="272" r="80" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
|
|
|
- <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M124 158v-22h-24v22"/>
|
|
|
- </svg>
|
|
|
- 上传支付凭证
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <button class="action-card" (click)="switchTab('retrospective')">
|
|
|
+ <div class="action-icon retro-icon">📊</div>
|
|
|
+ <div class="action-title">项目复盘</div>
|
|
|
+ <div class="action-desc">{{ projectRetrospective ? '已完成' : '待生成' }}</div>
|
|
|
+ @if (projectRetrospective) {
|
|
|
+ <div class="action-badge badge-success">
|
|
|
+ {{ projectRetrospective.efficiencyAnalysis.grade }}级
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </button>
|
|
|
|
|
|
- <!-- 2. 客户评价 -->
|
|
|
- <div class="card feedback-card">
|
|
|
- <div class="card-header">
|
|
|
- <h3 class="card-title">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M480 208H308L256 48l-52 160H32l140 96-54 160 138-100 138 100-54-160z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- </svg>
|
|
|
- 客户评价
|
|
|
- </h3>
|
|
|
- </div>
|
|
|
- <div class="card-content">
|
|
|
- @if (!customerFeedback.submitted) {
|
|
|
- <div class="feedback-form">
|
|
|
- <!-- 综合评分 -->
|
|
|
- <div class="rating-section">
|
|
|
- <label>综合评分 <span class="required">*</span></label>
|
|
|
- <div class="stars">
|
|
|
- @for (star of [1,2,3,4,5]; track star) {
|
|
|
- <svg
|
|
|
- class="star-icon"
|
|
|
- [class.active]="star <= customerFeedback.overallRating"
|
|
|
- (click)="setRating('rating', star)"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (star <= customerFeedback.overallRating) {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
- } @else {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- }
|
|
|
- </svg>
|
|
|
+ <button class="action-card" (click)="archiveProject()"
|
|
|
+ [disabled]="!canEdit || archiveStatus.archived || finalPayment.status !== 'completed' || !customerFeedback.submitted || !projectRetrospective">
|
|
|
+ <div class="action-icon archive-icon">📁</div>
|
|
|
+ <div class="action-title">归档项目</div>
|
|
|
+ <div class="action-desc">{{ archiveStatus.archived ? '已归档' : '待归档' }}</div>
|
|
|
+ @if (!archiveStatus.archived) {
|
|
|
+ <div class="action-hint">需完成前3项</div>
|
|
|
}
|
|
|
- </div>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 分项评分 -->
|
|
|
- <div class="rating-section">
|
|
|
- <label>服务态度</label>
|
|
|
- <div class="stars">
|
|
|
- @for (star of [1,2,3,4,5]; track star) {
|
|
|
- <svg
|
|
|
- class="star-icon"
|
|
|
- [class.active]="star <= customerFeedback.serviceRating"
|
|
|
- (click)="setRating('serviceRating', star)"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (star <= customerFeedback.serviceRating) {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
- } @else {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <!-- 多空间概览 -->
|
|
|
+ @if (isMultiProductProject && projectProducts.length > 0) {
|
|
|
+ <div class="card products-overview-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M32 192L256 64l224 128-224 128L32 192z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path d="M112 240v128l144 80 144-80V240M480 368L256 496 32 368" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ 空间概览
|
|
|
+ </h3>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="products-grid">
|
|
|
+ @for (product of projectProducts; track product.id) {
|
|
|
+ <div class="product-summary-card">
|
|
|
+ <div class="product-header">
|
|
|
+ <div class="product-icon">{{ productSpaceService.getProductIcon(product.type) }}</div>
|
|
|
+ <div class="product-name">{{ product.name || productSpaceService.getProductTypeName(product.type) }}</div>
|
|
|
+ </div>
|
|
|
+ @if (customerFeedback.productFeedbacks.length > 0) {
|
|
|
+ <div class="product-rating">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <span class="star-small" [class.filled]="star <= getProductRating(product.id)">★</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
}
|
|
|
- </svg>
|
|
|
+ </div>
|
|
|
}
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <div class="rating-section">
|
|
|
- <label>设计质量</label>
|
|
|
- <div class="stars">
|
|
|
- @for (star of [1,2,3,4,5]; track star) {
|
|
|
- <svg
|
|
|
- class="star-icon"
|
|
|
- [class.active]="star <= customerFeedback.qualityRating"
|
|
|
- (click)="setRating('qualityRating', star)"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (star <= customerFeedback.qualityRating) {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
- } @else {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- }
|
|
|
- </svg>
|
|
|
- }
|
|
|
+ <!-- ============ 尾款管理Tab ============ -->
|
|
|
+ @if (activeTab === 'payment') {
|
|
|
+ <div class="payment-section">
|
|
|
+
|
|
|
+ <!-- 支付总览 -->
|
|
|
+ <div class="card payment-overview-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="card-title-wrapper">
|
|
|
+ <h3 class="card-title">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <circle cx="256" cy="184" r="120" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <circle cx="344" cy="328" r="120" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <circle cx="168" cy="328" r="120" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ 尾款总览
|
|
|
+ </h3>
|
|
|
+ <span class="badge" [ngClass]="'badge-' + getPaymentStatusColor()">
|
|
|
+ {{ getPaymentStatusText() }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <!-- 金额统计 -->
|
|
|
+ <div class="payment-stats">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-label">总金额</div>
|
|
|
+ <div class="stat-value primary">{{ formatCurrency(finalPayment.totalAmount) }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-label">已支付</div>
|
|
|
+ <div class="stat-value success">{{ formatCurrency(finalPayment.paidAmount) }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-label">待支付</div>
|
|
|
+ <div class="stat-value warning">{{ formatCurrency(finalPayment.remainingAmount) }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="rating-section">
|
|
|
- <label>交付及时性</label>
|
|
|
- <div class="stars">
|
|
|
- @for (star of [1,2,3,4,5]; track star) {
|
|
|
- <svg
|
|
|
- class="star-icon"
|
|
|
- [class.active]="star <= customerFeedback.timelinessRating"
|
|
|
- (click)="setRating('timelinessRating', star)"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (star <= customerFeedback.timelinessRating) {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
- } @else {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- }
|
|
|
- </svg>
|
|
|
- }
|
|
|
+ <!-- 进度条 -->
|
|
|
+ <div class="payment-progress">
|
|
|
+ <div class="progress-bar">
|
|
|
+ <div class="progress-fill"
|
|
|
+ [style.width.%]="finalPayment.totalAmount > 0 ? (finalPayment.paidAmount / finalPayment.totalAmount) * 100 : 0">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="progress-label">
|
|
|
+ 已完成 {{ finalPayment.totalAmount > 0 ? ((finalPayment.paidAmount / finalPayment.totalAmount) * 100).toFixed(1) : 0 }}%
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 文字评价 -->
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">评价内容</label>
|
|
|
- <textarea
|
|
|
- class="form-textarea"
|
|
|
- [(ngModel)]="customerFeedback.comments"
|
|
|
- rows="4"
|
|
|
- placeholder="请分享您的体验和感受"></textarea>
|
|
|
- </div>
|
|
|
+ <!-- 逾期提醒 -->
|
|
|
+ @if (finalPayment.status === 'overdue') {
|
|
|
+ <div class="alert alert-warning">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M85.57 446.25h340.86a32 32 0 0028.17-47.17L284.18 82.58c-12.09-22.44-44.27-22.44-56.36 0L57.4 399.08a32 32 0 0028.17 47.17z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path d="M250.26 195.39l5.74 122 5.73-121.95a5.74 5.74 0 00-5.79-6h0a5.74 5.74 0 00-5.68 5.95z" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
|
+ <path d="M256 397.25a20 20 0 1120-20 20 20 0 01-20 20z"/>
|
|
|
+ </svg>
|
|
|
+ <span>尾款已逾期 {{ finalPayment.overdueDays }} 天,请尽快处理</span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="form-group">
|
|
|
- <label class="form-label">改进建议</label>
|
|
|
- <textarea
|
|
|
- class="form-textarea"
|
|
|
- [(ngModel)]="customerFeedback.improvements"
|
|
|
- rows="3"
|
|
|
- placeholder="您希望我们改进的地方"></textarea>
|
|
|
+ <!-- 按Product分摊 -->
|
|
|
+ @if (isMultiProductProject && finalPayment.productBreakdown.length > 0) {
|
|
|
+ <div class="card product-breakdown-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144M368 144L144 368"/>
|
|
|
+ </svg>
|
|
|
+ 按空间分摊
|
|
|
+ </h3>
|
|
|
</div>
|
|
|
-
|
|
|
- <div class="checkbox-item">
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- id="recommend-checkbox"
|
|
|
- [(ngModel)]="customerFeedback.wouldRecommend"
|
|
|
- class="checkbox-input" />
|
|
|
- <label for="recommend-checkbox" class="checkbox-label">我愿意推荐给朋友</label>
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="breakdown-list">
|
|
|
+ @for (item of finalPayment.productBreakdown; track item.productId) {
|
|
|
+ <div class="breakdown-item">
|
|
|
+ <div class="breakdown-header">
|
|
|
+ <div class="breakdown-name">{{ item.productName }}</div>
|
|
|
+ <span class="badge" [ngClass]="'badge-' + (item.status === 'completed' ? 'success' : 'warning')">
|
|
|
+ {{ item.percentage.toFixed(1) }}%
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="breakdown-amounts">
|
|
|
+ <span class="amount-total">{{ formatCurrency(item.amount) }}</span>
|
|
|
+ <span class="amount-paid">已付: {{ formatCurrency(item.paidAmount) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="breakdown-progress">
|
|
|
+ <div class="progress-bar-small">
|
|
|
+ <div class="progress-fill"
|
|
|
+ [style.width.%]="item.amount > 0 ? (item.paidAmount / item.amount) * 100 : 0">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <button
|
|
|
- class="btn btn-primary btn-block"
|
|
|
- (click)="submitFeedback()"
|
|
|
- [disabled]="saving">
|
|
|
+ <!-- 支付凭证列表 -->
|
|
|
+ <div class="card vouchers-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
|
|
|
- <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
+ <rect x="128" y="128" width="336" height="336" rx="57" ry="57" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path d="M383.5 128l.5-24a56.16 56.16 0 00-56-56H112a64.19 64.19 0 00-64 64v216a56.16 56.16 0 0056 56h24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <circle cx="296" cy="232" r="24"/>
|
|
|
</svg>
|
|
|
- 提交评价
|
|
|
- </button>
|
|
|
+ 支付凭证
|
|
|
+ <span class="count-badge">{{ finalPayment.paymentVouchers.length }}</span>
|
|
|
+ </h3>
|
|
|
</div>
|
|
|
- } @else {
|
|
|
- <div class="feedback-result">
|
|
|
- <div class="rating-display">
|
|
|
- <div class="stars-large">
|
|
|
- @for (star of [1,2,3,4,5]; track star) {
|
|
|
- <svg
|
|
|
- class="star-icon"
|
|
|
- [class.active]="star <= customerFeedback.overallRating"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (star <= customerFeedback.overallRating) {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
- } @else {
|
|
|
- <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- }
|
|
|
- </svg>
|
|
|
+ <div class="card-content">
|
|
|
+ @if (finalPayment.paymentVouchers.length === 0) {
|
|
|
+ <div class="empty-state">
|
|
|
+ <svg class="icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <rect x="128" y="128" width="336" height="336" rx="57" ry="57" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path d="M383.5 128l.5-24a56.16 56.16 0 00-56-56H112a64.19 64.19 0 00-64 64v216a56.16 56.16 0 0056 56h24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ <p>暂无支付凭证</p>
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="vouchers-list">
|
|
|
+ @for (voucher of finalPayment.paymentVouchers; track voucher.id) {
|
|
|
+ <div class="voucher-item">
|
|
|
+ <div class="voucher-image">
|
|
|
+ <img [src]="voucher.url" alt="支付凭证" />
|
|
|
+ @if (canEdit) {
|
|
|
+ <button class="delete-btn" (click)="deletePaymentVoucher(voucher.id); $event.stopPropagation()">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M112 112l20 320c.95 18.49 14.4 32 32 32h184c17.67 0 30.87-13.51 32-32l20-320" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M80 112h352"/>
|
|
|
+ <path d="M192 112V72h0a23.93 23.93 0 0124-24h80a23.93 23.93 0 0124 24h0v40M256 176v224M184 176l8 224M328 176l-8 224" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="voucher-info">
|
|
|
+ <div class="voucher-amount">
|
|
|
+ @if (canEdit) {
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ class="amount-input"
|
|
|
+ [(ngModel)]="voucher.amount"
|
|
|
+ (change)="updateVoucherAmount(voucher.id, voucher.amount)"
|
|
|
+ placeholder="输入金额" />
|
|
|
+ } @else {
|
|
|
+ <span class="amount-text">{{ formatCurrency(voucher.amount) }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="voucher-method">{{ voucher.paymentMethod }}</div>
|
|
|
+ <div class="voucher-time">{{ voucher.paymentTime | date:'yyyy-MM-dd HH:mm' }}</div>
|
|
|
+ @if (voucher.productId && isMultiProductProject) {
|
|
|
+ <div class="voucher-product">
|
|
|
+ 关联: {{ getProductNameById(voucher.productId) }}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ @if (voucher.ocrResult) {
|
|
|
+ <div class="ocr-badge">
|
|
|
+ <span>OCR识别</span>
|
|
|
+ <span class="confidence">{{ getOCRConfidenceText(voucher.ocrResult.confidence) }}</span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
}
|
|
|
</div>
|
|
|
- <p class="rating-text">{{ customerFeedback.overallRating }}.0 分</p>
|
|
|
- </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 上传按钮 -->
|
|
|
+ @if (canEdit && finalPayment.status !== 'completed') {
|
|
|
+ <div class="upload-section">
|
|
|
+ <input
|
|
|
+ type="file"
|
|
|
+ accept="image/*"
|
|
|
+ multiple
|
|
|
+ (change)="uploadPaymentVoucher($event)"
|
|
|
+ [disabled]="uploading"
|
|
|
+ hidden
|
|
|
+ #voucherInput />
|
|
|
|
|
|
- @if (customerFeedback.comments) {
|
|
|
- <div class="comment-box">
|
|
|
- <p>{{ customerFeedback.comments }}</p>
|
|
|
+ <button class="btn btn-primary btn-block btn-large" (click)="voucherInput.click()" [disabled]="uploading">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M320 367.79h76c55 0 100-29.21 100-83.6s-53-81.47-96-83.6c-8.89-85.06-71-136.8-144-136.8-69 0-113.44 45.79-128 91.2-60 5.7-112 43.88-112 106.4s54 106.4 120 106.4h56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 255.79l-64-64-64 64M256 448.21V207.79"/>
|
|
|
+ </svg>
|
|
|
+ @if (uploading) {
|
|
|
+ <span>上传中...</span>
|
|
|
+ } @else {
|
|
|
+ <span>上传支付凭证</span>
|
|
|
+ }
|
|
|
+ </button>
|
|
|
</div>
|
|
|
}
|
|
|
-
|
|
|
- <div class="badge badge-success badge-with-icon">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
|
|
|
- <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
- </svg>
|
|
|
- 已提交评价
|
|
|
- </div>
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
|
|
|
- <!-- 3. 项目复盘 -->
|
|
|
- <div class="card retrospective-card">
|
|
|
- <div class="card-header">
|
|
|
- <h3 class="card-title">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M104 496H72a24 24 0 01-24-24V328a24 24 0 0124-24h32a24 24 0 0124 24v144a24 24 0 01-24 24zM328 496h-32a24 24 0 01-24-24V232a24 24 0 0124-24h32a24 24 0 0124 24v240a24 24 0 01-24 24zM440 496h-32a24 24 0 01-24-24V120a24 24 0 0124-24h32a24 24 0 0124 24v352a24 24 0 01-24 24zM216 496h-32a24 24 0 01-24-24V40a24 24 0 0124-24h32a24 24 0 0124 24v432a24 24 0 01-24 24z"/>
|
|
|
- </svg>
|
|
|
- 项目复盘
|
|
|
- </h3>
|
|
|
- </div>
|
|
|
- <div class="card-content">
|
|
|
- @if (!projectRetrospective) {
|
|
|
- <div class="empty-state">
|
|
|
- <svg class="icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- <path d="M256 56v120a32 32 0 0032 32h120" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- </svg>
|
|
|
- <p>尚未生成项目复盘</p>
|
|
|
- @if (canEdit) {
|
|
|
- <button
|
|
|
- class="btn btn-primary"
|
|
|
- (click)="generateRetrospective()"
|
|
|
- [disabled]="generating">
|
|
|
- @if (generating) {
|
|
|
- <div class="spinner-small">
|
|
|
- <div class="spinner-circle"></div>
|
|
|
+ <!-- ============ 客户评价Tab ============ -->
|
|
|
+ @if (activeTab === 'feedback') {
|
|
|
+ <div class="feedback-section">
|
|
|
+
|
|
|
+ @if (!customerFeedback.submitted) {
|
|
|
+ <!-- 评价表单 -->
|
|
|
+ <div class="card feedback-form-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M480 208H308L256 48l-52 160H32l140 96-54 160 138-100 138 100-54-160z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ </svg>
|
|
|
+ 客户评价
|
|
|
+ </h3>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <!-- 整体评分 -->
|
|
|
+ <div class="rating-section">
|
|
|
+ <label class="rating-label">
|
|
|
+ 综合评分 <span class="required">*</span>
|
|
|
+ </label>
|
|
|
+ <div class="stars-large">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <svg
|
|
|
+ class="star-icon"
|
|
|
+ [class.active]="star <= customerFeedback.overallRating"
|
|
|
+ (click)="setRating('overallRating', star)"
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.overallRating) {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
+ } @else {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="rating-text">
|
|
|
+ {{ customerFeedback.overallRating > 0 ? customerFeedback.overallRating + ' 星' : '请选择评分' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分维度评分 -->
|
|
|
+ <div class="dimensions-section">
|
|
|
+ <h4 class="section-subtitle">详细评分</h4>
|
|
|
+
|
|
|
+ <div class="dimension-item">
|
|
|
+ <label>设计质量</label>
|
|
|
+ <div class="stars">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <svg class="star-icon" [class.active]="star <= customerFeedback.dimensionRatings.designQuality"
|
|
|
+ (click)="setRating('dimensionRatings.designQuality', star)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.dimensionRatings.designQuality) {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
+ } @else {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="dimension-item">
|
|
|
+ <label>服务态度</label>
|
|
|
+ <div class="stars">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <svg class="star-icon" [class.active]="star <= customerFeedback.dimensionRatings.serviceAttitude"
|
|
|
+ (click)="setRating('dimensionRatings.serviceAttitude', star)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.dimensionRatings.serviceAttitude) {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
+ } @else {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="dimension-item">
|
|
|
+ <label>交付及时性</label>
|
|
|
+ <div class="stars">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <svg class="star-icon" [class.active]="star <= customerFeedback.dimensionRatings.deliveryTimeliness"
|
|
|
+ (click)="setRating('dimensionRatings.deliveryTimeliness', star)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.dimensionRatings.deliveryTimeliness) {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
+ } @else {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="dimension-item">
|
|
|
+ <label>性价比</label>
|
|
|
+ <div class="stars">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <svg class="star-icon" [class.active]="star <= customerFeedback.dimensionRatings.valueForMoney"
|
|
|
+ (click)="setRating('dimensionRatings.valueForMoney', star)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.dimensionRatings.valueForMoney) {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
+ } @else {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ }
|
|
|
</div>
|
|
|
- 生成中...
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="dimension-item">
|
|
|
+ <label>沟通协作</label>
|
|
|
+ <div class="stars">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <svg class="star-icon" [class.active]="star <= customerFeedback.dimensionRatings.communication"
|
|
|
+ (click)="setRating('dimensionRatings.communication', star)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.dimensionRatings.communication) {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
+ } @else {
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 推荐意愿 -->
|
|
|
+ <div class="recommendation-section">
|
|
|
+ <h4 class="section-subtitle">推荐意愿</h4>
|
|
|
+ <label class="checkbox-item">
|
|
|
+ <input type="checkbox" class="checkbox-input" [(ngModel)]="customerFeedback.wouldRecommend" />
|
|
|
+ <span class="checkbox-label">我愿意向他人推荐此服务</span>
|
|
|
+ </label>
|
|
|
+
|
|
|
+ @if (customerFeedback.wouldRecommend) {
|
|
|
+ <div class="nps-section">
|
|
|
+ <label>推荐可能性 (1-10分,10分最可能)</label>
|
|
|
+ <div class="nps-scale">
|
|
|
+ @for (score of [1,2,3,4,5,6,7,8,9,10]; track score) {
|
|
|
+ <button
|
|
|
+ class="nps-btn"
|
|
|
+ [class.active]="score === customerFeedback.recommendationWillingness.score"
|
|
|
+ (click)="setNPSScore(score)">
|
|
|
+ {{ score }}
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 文字评价 -->
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">评价内容</label>
|
|
|
+ <textarea
|
|
|
+ class="form-textarea"
|
|
|
+ [(ngModel)]="customerFeedback.comments"
|
|
|
+ rows="4"
|
|
|
+ placeholder="请分享您的体验和感受..."></textarea>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">改进建议</label>
|
|
|
+ <textarea
|
|
|
+ class="form-textarea"
|
|
|
+ [(ngModel)]="customerFeedback.improvements"
|
|
|
+ rows="3"
|
|
|
+ placeholder="您希望我们在哪些方面做得更好?"></textarea>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 提交按钮 -->
|
|
|
+ <button
|
|
|
+ class="btn btn-success btn-block btn-large"
|
|
|
+ (click)="submitFeedback()"
|
|
|
+ [disabled]="saving || customerFeedback.overallRating === 0">
|
|
|
+ @if (saving) {
|
|
|
+ <span>提交中...</span>
|
|
|
} @else {
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M208 352h-64a96 96 0 010-192h64m96 0h64a96 96 0 010 192h-64m-140.71-96h187.42" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="36"/>
|
|
|
- </svg>
|
|
|
- 生成复盘
|
|
|
+ <span>提交评价</span>
|
|
|
}
|
|
|
</button>
|
|
|
- }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
} @else {
|
|
|
- <div class="retrospective-content">
|
|
|
- <p class="summary">{{ projectRetrospective.summary }}</p>
|
|
|
-
|
|
|
- <div class="section">
|
|
|
- <h4>
|
|
|
+ <!-- 评价结果展示 -->
|
|
|
+ <div class="card feedback-result-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M464 256A208 208 0 1148 256a208 208 0 01416 0z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
|
|
|
- <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 160l-49.38 118.76L208 314.54M256 464c-114.88 0-208-93.12-208-208S141.12 48 256 48s208 93.12 208 208"/>
|
|
|
+ <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
+ <rect x="64" y="64" width="384" height="384" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
</svg>
|
|
|
- 项目亮点
|
|
|
- </h4>
|
|
|
- <ul>
|
|
|
- @for (item of projectRetrospective.highlights; track item) {
|
|
|
- <li>{{ item }}</li>
|
|
|
- }
|
|
|
- </ul>
|
|
|
+ 评价已提交
|
|
|
+ </h3>
|
|
|
+ <span class="badge badge-success">已完成</span>
|
|
|
</div>
|
|
|
+ <div class="card-content">
|
|
|
+ <!-- 整体评分展示 -->
|
|
|
+ <div class="result-rating">
|
|
|
+ <div class="stars-display">
|
|
|
+ @for (star of [1,2,3,4,5]; track star) {
|
|
|
+ <svg class="star-icon-large" [class.active]="star <= customerFeedback.overallRating"
|
|
|
+ xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
|
|
|
+ </svg>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="rating-score">{{ customerFeedback.overallRating }}.0 / 5.0</div>
|
|
|
+ <div class="rating-time">提交时间: {{ customerFeedback.submittedAt | date:'yyyy-MM-dd HH:mm' }}</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="section">
|
|
|
- <h4>
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M256 80c-8.66 0-16.58 7.36-16 16l8 216a8 8 0 008 8h0a8 8 0 008-8l8-216c.58-8.64-7.34-16-16-16z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- <circle cx="256" cy="416" r="16" fill="currentColor"/>
|
|
|
- </svg>
|
|
|
- 遇到的挑战
|
|
|
- </h4>
|
|
|
- <ul>
|
|
|
- @for (item of projectRetrospective.challenges; track item) {
|
|
|
- <li>{{ item }}</li>
|
|
|
- }
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
+ <!-- 维度评分展示 -->
|
|
|
+ <div class="dimensions-chart">
|
|
|
+ <h4 class="section-subtitle">各维度评分</h4>
|
|
|
+ <div class="dimension-bars">
|
|
|
+ <div class="bar-item">
|
|
|
+ <span class="bar-label">设计质量</span>
|
|
|
+ <div class="bar-track">
|
|
|
+ <div class="bar-fill" [style.width.%]="customerFeedback.dimensionRatings.designQuality * 20"></div>
|
|
|
+ </div>
|
|
|
+ <span class="bar-value">{{ customerFeedback.dimensionRatings.designQuality }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="bar-item">
|
|
|
+ <span class="bar-label">服务态度</span>
|
|
|
+ <div class="bar-track">
|
|
|
+ <div class="bar-fill" [style.width.%]="customerFeedback.dimensionRatings.serviceAttitude * 20"></div>
|
|
|
+ </div>
|
|
|
+ <span class="bar-value">{{ customerFeedback.dimensionRatings.serviceAttitude }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="bar-item">
|
|
|
+ <span class="bar-label">交付及时性</span>
|
|
|
+ <div class="bar-track">
|
|
|
+ <div class="bar-fill" [style.width.%]="customerFeedback.dimensionRatings.deliveryTimeliness * 20"></div>
|
|
|
+ </div>
|
|
|
+ <span class="bar-value">{{ customerFeedback.dimensionRatings.deliveryTimeliness }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="bar-item">
|
|
|
+ <span class="bar-label">性价比</span>
|
|
|
+ <div class="bar-track">
|
|
|
+ <div class="bar-fill" [style.width.%]="customerFeedback.dimensionRatings.valueForMoney * 20"></div>
|
|
|
+ </div>
|
|
|
+ <span class="bar-value">{{ customerFeedback.dimensionRatings.valueForMoney }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="bar-item">
|
|
|
+ <span class="bar-label">沟通协作</span>
|
|
|
+ <div class="bar-track">
|
|
|
+ <div class="bar-fill" [style.width.%]="customerFeedback.dimensionRatings.communication * 20"></div>
|
|
|
+ </div>
|
|
|
+ <span class="bar-value">{{ customerFeedback.dimensionRatings.communication }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="section">
|
|
|
- <h4>
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M304 384v-24c0-29 31.54-56.43 52-76 28.84-27.57 44-64.61 44-108 0-80-63.73-144-144-144a143.6 143.6 0 00-144 144c0 41.84 15.81 81.39 44 108 20.35 19.21 52 46.7 52 76v24m16 96h64m-80-48h96m-48-48V256" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- </svg>
|
|
|
- 经验教训
|
|
|
- </h4>
|
|
|
- <ul>
|
|
|
- @for (item of projectRetrospective.lessons; track item) {
|
|
|
- <li>{{ item }}</li>
|
|
|
- }
|
|
|
- </ul>
|
|
|
+ <!-- 评价内容 -->
|
|
|
+ @if (customerFeedback.comments) {
|
|
|
+ <div class="comment-box">
|
|
|
+ <h4 class="section-subtitle">评价内容</h4>
|
|
|
+ <p>{{ customerFeedback.comments }}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ @if (customerFeedback.improvements) {
|
|
|
+ <div class="improvement-box">
|
|
|
+ <h4 class="section-subtitle">改进建议</h4>
|
|
|
+ <p>{{ customerFeedback.improvements }}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 推荐意愿 -->
|
|
|
+ @if (customerFeedback.wouldRecommend) {
|
|
|
+ <div class="recommendation-result">
|
|
|
+ <div class="recommend-badge">
|
|
|
+ <span>愿意推荐</span>
|
|
|
+ @if (customerFeedback.recommendationWillingness.score > 0) {
|
|
|
+ <span class="nps-score">{{ customerFeedback.recommendationWillingness.score }}/10</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <div class="section">
|
|
|
- <h4>
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <circle cx="256" cy="256" r="26" fill="currentColor"/>
|
|
|
- <circle cx="346" cy="256" r="26" fill="currentColor"/>
|
|
|
- <path d="M222 402.67l-75.68-34.55a16 16 0 01-9.05-13.67L120 203.5a16 16 0 0112.63-17.59l140.34-28.07a16 16 0 0117.59 12.63l17.22 150.82a16 16 0 01-13.09 18.11l-71.5 13.67a16 16 0 01-17.46-13.05z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/>
|
|
|
- <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
|
|
|
+ <!-- ============ 项目复盘Tab ============ -->
|
|
|
+ @if (activeTab === 'retrospective') {
|
|
|
+ <div class="retrospective-section">
|
|
|
+
|
|
|
+ @if (!projectRetrospective) {
|
|
|
+ <!-- 未生成状态 -->
|
|
|
+ <div class="card empty-retro-card">
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="empty-state">
|
|
|
+ <svg class="icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <rect x="64" y="320" width="48" height="160" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="288" y="224" width="48" height="256" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="400" y="112" width="48" height="368" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <rect x="176" y="32" width="48" height="448" rx="8" ry="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
</svg>
|
|
|
- 改进建议
|
|
|
- </h4>
|
|
|
- <ul>
|
|
|
- @for (item of projectRetrospective.recommendations; track item) {
|
|
|
- <li>{{ item }}</li>
|
|
|
- }
|
|
|
- </ul>
|
|
|
+ <p>项目复盘尚未生成</p>
|
|
|
+ <p class="hint">系统将基于项目数据自动生成深度分析报告</p>
|
|
|
+ <button
|
|
|
+ class="btn btn-primary btn-large"
|
|
|
+ (click)="generateRetrospective()"
|
|
|
+ [disabled]="generating || !canEdit">
|
|
|
+ @if (generating) {
|
|
|
+ <span>生成中...</span>
|
|
|
+ } @else {
|
|
|
+ <span>生成项目复盘</span>
|
|
|
+ }
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <!-- 复盘内容展示 -->
|
|
|
|
|
|
- @if (canEdit) {
|
|
|
- <button
|
|
|
- class="btn btn-outline btn-block"
|
|
|
- (click)="generateRetrospective()"
|
|
|
- [disabled]="generating">
|
|
|
+ <!-- 执行摘要 -->
|
|
|
+ <div class="card summary-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M320 146s24.36-12-64-12a160 160 0 10160 160" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/>
|
|
|
- <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 58l80 80-80 80"/>
|
|
|
+ <path d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <path d="M256 56v120a32 32 0 0032 32h120M176 288h160M176 368h160" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
</svg>
|
|
|
- 重新生成
|
|
|
- </button>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ 执行摘要
|
|
|
+ </h3>
|
|
|
+ <span class="generated-info">
|
|
|
+ 生成于 {{ projectRetrospective.generatedAt | date:'yyyy-MM-dd HH:mm' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <p class="summary-text">{{ projectRetrospective.summary }}</p>
|
|
|
|
|
|
- <!-- 4. 归档操作 -->
|
|
|
- @if (!archiveStatus.archived) {
|
|
|
- @if (canEdit) {
|
|
|
- <div class="card archive-card">
|
|
|
- <div class="card-content">
|
|
|
- <div class="archive-checklist">
|
|
|
- <h3>归档前检查</h3>
|
|
|
- <div class="checklist">
|
|
|
- <div class="checklist-item">
|
|
|
- <svg
|
|
|
- class="icon check-icon"
|
|
|
- [class.checked]="finalPayment.status === 'completed'"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (finalPayment.status === 'completed') {
|
|
|
- <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
|
|
|
- <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
- } @else {
|
|
|
- <circle cx="256" cy="256" r="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+ <div class="highlights-grid">
|
|
|
+ <div class="highlight-section">
|
|
|
+ <h4>项目亮点</h4>
|
|
|
+ <ul>
|
|
|
+ @for (highlight of projectRetrospective.highlights; track $index) {
|
|
|
+ <li>{{ highlight }}</li>
|
|
|
}
|
|
|
- </svg>
|
|
|
- <span>尾款已结清</span>
|
|
|
+ </ul>
|
|
|
</div>
|
|
|
- <div class="checklist-item">
|
|
|
- <svg
|
|
|
- class="icon check-icon"
|
|
|
- [class.checked]="customerFeedback.submitted"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (customerFeedback.submitted) {
|
|
|
- <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
|
|
|
- <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
- } @else {
|
|
|
- <circle cx="256" cy="256" r="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
+
|
|
|
+ <div class="challenge-section">
|
|
|
+ <h4>遇到的挑战</h4>
|
|
|
+ <ul>
|
|
|
+ @for (challenge of projectRetrospective.challenges; track $index) {
|
|
|
+ <li>{{ challenge }}</li>
|
|
|
}
|
|
|
- </svg>
|
|
|
- <span>客户已评价</span>
|
|
|
+ </ul>
|
|
|
</div>
|
|
|
- <div class="checklist-item">
|
|
|
- <svg
|
|
|
- class="icon check-icon"
|
|
|
- [class.checked]="projectRetrospective"
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
- viewBox="0 0 512 512">
|
|
|
- @if (projectRetrospective) {
|
|
|
- <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
|
|
|
- <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
- } @else {
|
|
|
- <circle cx="256" cy="256" r="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
|
|
|
- }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="lessons-section">
|
|
|
+ <h4>经验教训</h4>
|
|
|
+ <ul>
|
|
|
+ @for (lesson of projectRetrospective.lessons; track $index) {
|
|
|
+ <li>{{ lesson }}</li>
|
|
|
+ }
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="recommendations-section">
|
|
|
+ <h4>改进建议</h4>
|
|
|
+ <ul>
|
|
|
+ @for (rec of projectRetrospective.recommendations; track $index) {
|
|
|
+ <li>{{ rec }}</li>
|
|
|
+ }
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 效率分析卡片 -->
|
|
|
+ <div class="card efficiency-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3 class="card-title">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path d="M400 148l-21.12-24.57A191.43 191.43 0 00240 64C134 64 48 150 48 256s86 192 192 192a192.09 192.09 0 00181.07-128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/>
|
|
|
+ <path d="M464 97.42V208a16 16 0 01-16 16H337.42c-14.26 0-21.4-17.23-11.32-27.31L436.69 86.1C446.77 76 464 83.16 464 97.42z" fill="currentColor"/>
|
|
|
+ </svg>
|
|
|
+ 效率分析
|
|
|
+ </h3>
|
|
|
+ <div class="grade-badge" [ngClass]="'grade-' + projectRetrospective.efficiencyAnalysis.grade.toLowerCase()">
|
|
|
+ {{ projectRetrospective.efficiencyAnalysis.grade }} 级
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <!-- 综合效率评分 -->
|
|
|
+ <div class="efficiency-score">
|
|
|
+ <div class="score-circle">
|
|
|
+ <svg class="score-ring" width="140" height="140">
|
|
|
+ <circle class="ring-bg" cx="70" cy="70" r="60" fill="none" stroke="#e0e0e0" stroke-width="10"></circle>
|
|
|
+ <circle class="ring-progress" cx="70" cy="70" r="60" fill="none"
|
|
|
+ [attr.stroke]="projectRetrospective.efficiencyAnalysis.overallScore >= 80 ? '#2dd36f' : projectRetrospective.efficiencyAnalysis.overallScore >= 60 ? '#3880ff' : '#ffc409'"
|
|
|
+ stroke-width="10"
|
|
|
+ [style.stroke-dasharray]="376.8"
|
|
|
+ [style.stroke-dashoffset]="376.8 - (376.8 * projectRetrospective.efficiencyAnalysis.overallScore / 100)">
|
|
|
+ </circle>
|
|
|
</svg>
|
|
|
- <span>项目复盘已完成</span>
|
|
|
+ <div class="score-label">
|
|
|
+ <div class="score-number">{{ projectRetrospective.efficiencyAnalysis.overallScore }}</div>
|
|
|
+ <div class="score-text">综合效率</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="efficiency-breakdown">
|
|
|
+ <div class="breakdown-item">
|
|
|
+ <span class="item-label">时间效率</span>
|
|
|
+ <div class="item-bar">
|
|
|
+ <div class="bar-fill" [style.width.%]="projectRetrospective.efficiencyAnalysis.timeEfficiency.score"></div>
|
|
|
+ </div>
|
|
|
+ <span class="item-value">{{ projectRetrospective.efficiencyAnalysis.timeEfficiency.score }}%</span>
|
|
|
+ </div>
|
|
|
+ <div class="breakdown-item">
|
|
|
+ <span class="item-label">质量效率</span>
|
|
|
+ <div class="item-bar">
|
|
|
+ <div class="bar-fill" [style.width.%]="projectRetrospective.efficiencyAnalysis.qualityEfficiency.score"></div>
|
|
|
+ </div>
|
|
|
+ <span class="item-value">{{ projectRetrospective.efficiencyAnalysis.qualityEfficiency.score }}%</span>
|
|
|
+ </div>
|
|
|
+ <div class="breakdown-item">
|
|
|
+ <span class="item-label">资源利用</span>
|
|
|
+ <div class="item-bar">
|
|
|
+ <div class="bar-fill" [style.width.%]="projectRetrospective.efficiencyAnalysis.resourceUtilization.score"></div>
|
|
|
+ </div>
|
|
|
+ <span class="item-value">{{ projectRetrospective.efficiencyAnalysis.resourceUtilization.score }}%</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <button
|
|
|
- class="btn btn-success btn-block btn-large"
|
|
|
- (click)="archiveProject()"
|
|
|
- [disabled]="saving || finalPayment.status !== 'completed' || !customerFeedback.submitted || !projectRetrospective">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M64 164v244a56 56 0 0056 56h272a56 56 0 0056-56V164a4 4 0 00-4-4H68a4 4 0 00-4 4zm330 20a26 26 0 11-26 26 26 26 0 0126-26z"/>
|
|
|
- <path d="M479.66 268.7l-32-151.81C441.48 83.77 417.68 64 384 64H128c-16.8 0-31 4.69-42.1 13.94S67.66 100 64.34 116.89L32.34 268.7a16 16 0 00-.34 3.3v144a64 64 0 0064 64h320a64 64 0 0064-64V272a16 16 0 00-.34-3.3zM368 320a16 16 0 01-32 0v-32a16 16 0 0132 0zm0-132a26 26 0 11-26 26 26 26 0 0126-26z"/>
|
|
|
- </svg>
|
|
|
- 归档项目
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- } @else {
|
|
|
- <div class="card archived-card">
|
|
|
- <div class="card-content">
|
|
|
- <div class="archived-status">
|
|
|
- <svg class="icon icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
|
|
|
- <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
|
|
|
- </svg>
|
|
|
- <h3>项目已归档</h3>
|
|
|
- <p>归档人: {{ archiveStatus.archivedBy?.name }}</p>
|
|
|
- <p>归档时间: {{ archiveStatus.archiveTime | date:'yyyy-MM-dd HH:mm' }}</p>
|
|
|
+ <!-- 各阶段效率对比 -->
|
|
|
+ <div class="stage-comparison">
|
|
|
+ <h4 class="subsection-title">各阶段效率对比</h4>
|
|
|
+ <div class="stages-list">
|
|
|
+ @for (stage of projectRetrospective.efficiencyAnalysis.stageMetrics; track stage.stage) {
|
|
|
+ <div class="stage-item" [class.delayed]="stage.status === 'delayed'">
|
|
|
+ <div class="stage-header">
|
|
|
+ <span class="stage-name">{{ stage.stage }}</span>
|
|
|
+ <span class="badge" [ngClass]="'badge-' + getStageStatusColor(stage.status)">
|
|
|
+ {{ getStageStatusText(stage.status) }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="stage-details">
|
|
|
+ <span>计划: {{ stage.plannedDays }}天</span>
|
|
|
+ <span>实际: {{ stage.actualDays }}天</span>
|
|
|
+ <span class="efficiency-badge">效率: {{ stage.efficiency }}%</span>
|
|
|
+ </div>
|
|
|
+ @if (stage.delayReason) {
|
|
|
+ <div class="delay-reason">原因: {{ stage.delayReason }}</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 瓶颈识别 -->
|
|
|
+ @if (projectRetrospective.efficiencyAnalysis.bottlenecks.length > 0) {
|
|
|
+ <div class="bottlenecks-section">
|
|
|
+ <h4 class="subsection-title">瓶颈识别</h4>
|
|
|
+ <div class="bottlenecks-list">
|
|
|
+ @for (bottleneck of projectRetrospective.efficiencyAnalysis.bottlenecks; track $index) {
|
|
|
+ <div class="bottleneck-item" [ngClass]="'severity-' + bottleneck.severity">
|
|
|
+ <div class="bottleneck-header">
|
|
|
+ <span class="severity-icon">{{ getBottleneckSeverityIcon(bottleneck.severity) }}</span>
|
|
|
+ <span class="bottleneck-stage">{{ bottleneck.stage }}</span>
|
|
|
+ <span class="badge" [ngClass]="'badge-' + getBottleneckSeverityColor(bottleneck.severity)">
|
|
|
+ {{ getBottleneckSeverityText(bottleneck.severity) }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="bottleneck-issue">{{ bottleneck.issue }}</div>
|
|
|
+ <div class="bottleneck-suggestion">💡 {{ bottleneck.suggestion }}</div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
}
|
|
|
</div>
|