|
|
@@ -1,7 +1,9 @@
|
|
|
<!-- 加载中 -->
|
|
|
@if (loading) {
|
|
|
<div class="loading-container">
|
|
|
- <ion-spinner name="crescent"></ion-spinner>
|
|
|
+ <div class="spinner">
|
|
|
+ <div class="spinner-circle"></div>
|
|
|
+ </div>
|
|
|
<p>加载售后信息...</p>
|
|
|
</div>
|
|
|
}
|
|
|
@@ -10,17 +12,22 @@
|
|
|
@if (!loading) {
|
|
|
<div class="stage-aftercare-container">
|
|
|
<!-- 1. 尾款管理 -->
|
|
|
- <ion-card class="payment-card">
|
|
|
- <ion-card-header>
|
|
|
- <ion-card-title>
|
|
|
- <ion-icon name="cash-outline"></ion-icon>
|
|
|
- 尾款管理
|
|
|
- </ion-card-title>
|
|
|
- <ion-badge [color]="getPaymentStatusColor()">
|
|
|
- {{ getPaymentStatusText() }}
|
|
|
- </ion-badge>
|
|
|
- </ion-card-header>
|
|
|
- <ion-card-content>
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
<div class="payment-summary">
|
|
|
<div class="summary-item">
|
|
|
<span class="label">总金额</span>
|
|
|
@@ -36,28 +43,32 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <ion-progress-bar
|
|
|
- [value]="finalPayment.totalAmount > 0 ? finalPayment.paidAmount / finalPayment.totalAmount : 0"
|
|
|
- color="success"></ion-progress-bar>
|
|
|
+ <!-- 进度条 -->
|
|
|
+ <div class="progress-bar">
|
|
|
+ <div
|
|
|
+ class="progress-fill"
|
|
|
+ [style.width.%]="finalPayment.totalAmount > 0 ? (finalPayment.paidAmount / finalPayment.totalAmount) * 100 : 0">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 支付凭证列表 -->
|
|
|
@if (finalPayment.paymentVouchers.length > 0) {
|
|
|
<div class="vouchers-section">
|
|
|
<h4>支付凭证</h4>
|
|
|
- <ion-list lines="full">
|
|
|
+ <div class="list">
|
|
|
@for (voucher of finalPayment.paymentVouchers; track $index) {
|
|
|
- <ion-item>
|
|
|
- <ion-thumbnail slot="start">
|
|
|
+ <div class="list-item">
|
|
|
+ <div class="thumbnail">
|
|
|
<img [src]="voucher.url" alt="支付凭证" />
|
|
|
- </ion-thumbnail>
|
|
|
- <ion-label>
|
|
|
+ </div>
|
|
|
+ <div class="item-content">
|
|
|
<h3>¥{{ voucher.amount.toLocaleString() }}</h3>
|
|
|
<p>{{ voucher.paymentMethod }}</p>
|
|
|
- <p>{{ voucher.paymentTime | date:'yyyy-MM-dd HH:mm' }}</p>
|
|
|
- </ion-label>
|
|
|
- </ion-item>
|
|
|
+ <p class="time">{{ voucher.paymentTime | date:'yyyy-MM-dd HH:mm' }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
}
|
|
|
- </ion-list>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
@@ -69,27 +80,32 @@
|
|
|
[disabled]="uploading"
|
|
|
hidden
|
|
|
#voucherInput />
|
|
|
- <ion-button
|
|
|
- expand="block"
|
|
|
- fill="outline"
|
|
|
+ <button
|
|
|
+ class="btn btn-outline btn-block"
|
|
|
(click)="voucherInput.click()"
|
|
|
[disabled]="uploading">
|
|
|
- <ion-icon name="camera-outline" slot="start"></ion-icon>
|
|
|
+ <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>
|
|
|
上传支付凭证
|
|
|
- </ion-button>
|
|
|
+ </button>
|
|
|
}
|
|
|
- </ion-card-content>
|
|
|
- </ion-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 2. 客户评价 -->
|
|
|
- <ion-card class="feedback-card">
|
|
|
- <ion-card-header>
|
|
|
- <ion-card-title>
|
|
|
- <ion-icon name="star-outline"></ion-icon>
|
|
|
+ <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>
|
|
|
客户评价
|
|
|
- </ion-card-title>
|
|
|
- </ion-card-header>
|
|
|
- <ion-card-content>
|
|
|
+ </h3>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
@if (!customerFeedback.submitted) {
|
|
|
<div class="feedback-form">
|
|
|
<!-- 综合评分 -->
|
|
|
@@ -97,10 +113,18 @@
|
|
|
<label>综合评分 <span class="required">*</span></label>
|
|
|
<div class="stars">
|
|
|
@for (star of [1,2,3,4,5]; track star) {
|
|
|
- <ion-icon
|
|
|
- [name]="star <= customerFeedback.rating ? 'star' : 'star-outline'"
|
|
|
+ <svg
|
|
|
+ class="star-icon"
|
|
|
[class.active]="star <= customerFeedback.rating"
|
|
|
- (click)="setRating('rating', star)"></ion-icon>
|
|
|
+ (click)="setRating('rating', star)"
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.rating) {
|
|
|
+ <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>
|
|
|
@@ -110,10 +134,18 @@
|
|
|
<label>服务态度</label>
|
|
|
<div class="stars">
|
|
|
@for (star of [1,2,3,4,5]; track star) {
|
|
|
- <ion-icon
|
|
|
- [name]="star <= customerFeedback.serviceRating ? 'star' : 'star-outline'"
|
|
|
+ <svg
|
|
|
+ class="star-icon"
|
|
|
[class.active]="star <= customerFeedback.serviceRating"
|
|
|
- (click)="setRating('serviceRating', star)"></ion-icon>
|
|
|
+ (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"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
}
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -122,10 +154,18 @@
|
|
|
<label>设计质量</label>
|
|
|
<div class="stars">
|
|
|
@for (star of [1,2,3,4,5]; track star) {
|
|
|
- <ion-icon
|
|
|
- [name]="star <= customerFeedback.qualityRating ? 'star' : 'star-outline'"
|
|
|
+ <svg
|
|
|
+ class="star-icon"
|
|
|
[class.active]="star <= customerFeedback.qualityRating"
|
|
|
- (click)="setRating('qualityRating', star)"></ion-icon>
|
|
|
+ (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>
|
|
|
}
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -134,53 +174,77 @@
|
|
|
<label>交付及时性</label>
|
|
|
<div class="stars">
|
|
|
@for (star of [1,2,3,4,5]; track star) {
|
|
|
- <ion-icon
|
|
|
- [name]="star <= customerFeedback.timelinessRating ? 'star' : 'star-outline'"
|
|
|
+ <svg
|
|
|
+ class="star-icon"
|
|
|
[class.active]="star <= customerFeedback.timelinessRating"
|
|
|
- (click)="setRating('timelinessRating', star)"></ion-icon>
|
|
|
+ (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>
|
|
|
</div>
|
|
|
|
|
|
<!-- 文字评价 -->
|
|
|
- <ion-item>
|
|
|
- <ion-label position="stacked">评价内容</ion-label>
|
|
|
- <ion-textarea
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">评价内容</label>
|
|
|
+ <textarea
|
|
|
+ class="form-textarea"
|
|
|
[(ngModel)]="customerFeedback.comments"
|
|
|
rows="4"
|
|
|
- placeholder="请分享您的体验和感受"></ion-textarea>
|
|
|
- </ion-item>
|
|
|
+ placeholder="请分享您的体验和感受"></textarea>
|
|
|
+ </div>
|
|
|
|
|
|
- <ion-item>
|
|
|
- <ion-label position="stacked">改进建议</ion-label>
|
|
|
- <ion-textarea
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">改进建议</label>
|
|
|
+ <textarea
|
|
|
+ class="form-textarea"
|
|
|
[(ngModel)]="customerFeedback.improvements"
|
|
|
rows="3"
|
|
|
- placeholder="您希望我们改进的地方"></ion-textarea>
|
|
|
- </ion-item>
|
|
|
+ placeholder="您希望我们改进的地方"></textarea>
|
|
|
+ </div>
|
|
|
|
|
|
- <ion-item lines="none">
|
|
|
- <ion-checkbox [(ngModel)]="customerFeedback.wouldRecommend"></ion-checkbox>
|
|
|
- <ion-label>我愿意推荐给朋友</ion-label>
|
|
|
- </ion-item>
|
|
|
+ <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>
|
|
|
|
|
|
- <ion-button
|
|
|
- expand="block"
|
|
|
- color="primary"
|
|
|
+ <button
|
|
|
+ class="btn btn-primary btn-block"
|
|
|
(click)="submitFeedback()"
|
|
|
[disabled]="saving">
|
|
|
- <ion-icon name="checkmark-circle-outline" slot="start"></ion-icon>
|
|
|
+ <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"/>
|
|
|
+ </svg>
|
|
|
提交评价
|
|
|
- </ion-button>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
} @else {
|
|
|
<div class="feedback-result">
|
|
|
<div class="rating-display">
|
|
|
<div class="stars-large">
|
|
|
@for (star of [1,2,3,4,5]; track star) {
|
|
|
- <ion-icon
|
|
|
- [name]="star <= customerFeedback.rating ? 'star' : 'star-outline'"
|
|
|
- [class.active]="star <= customerFeedback.rating"></ion-icon>
|
|
|
+ <svg
|
|
|
+ class="star-icon"
|
|
|
+ [class.active]="star <= customerFeedback.rating"
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ viewBox="0 0 512 512">
|
|
|
+ @if (star <= customerFeedback.rating) {
|
|
|
+ <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>
|
|
|
<p class="rating-text">{{ customerFeedback.rating }}.0 分</p>
|
|
|
@@ -192,42 +256,53 @@
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
- <ion-badge color="success">
|
|
|
- <ion-icon name="checkmark-circle"></ion-icon>
|
|
|
+ <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>
|
|
|
已提交评价
|
|
|
- </ion-badge>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
}
|
|
|
- </ion-card-content>
|
|
|
- </ion-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 3. 项目复盘 -->
|
|
|
- <ion-card class="retrospective-card">
|
|
|
- <ion-card-header>
|
|
|
- <ion-card-title>
|
|
|
- <ion-icon name="analytics-outline"></ion-icon>
|
|
|
+ <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>
|
|
|
项目复盘
|
|
|
- </ion-card-title>
|
|
|
- </ion-card-header>
|
|
|
- <ion-card-content>
|
|
|
+ </h3>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
@if (!projectRetrospective) {
|
|
|
<div class="empty-state">
|
|
|
- <ion-icon name="document-text-outline"></ion-icon>
|
|
|
+ <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) {
|
|
|
- <ion-button
|
|
|
- expand="block"
|
|
|
- color="primary"
|
|
|
+ <button
|
|
|
+ class="btn btn-primary"
|
|
|
(click)="generateRetrospective()"
|
|
|
[disabled]="generating">
|
|
|
@if (generating) {
|
|
|
- <ion-spinner name="crescent" slot="start"></ion-spinner>
|
|
|
+ <div class="spinner-small">
|
|
|
+ <div class="spinner-circle"></div>
|
|
|
+ </div>
|
|
|
生成中...
|
|
|
} @else {
|
|
|
- <ion-icon name="sparkles-outline" slot="start"></ion-icon>
|
|
|
+ <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>
|
|
|
生成复盘
|
|
|
}
|
|
|
- </ion-button>
|
|
|
+ </button>
|
|
|
}
|
|
|
</div>
|
|
|
} @else {
|
|
|
@@ -235,7 +310,13 @@
|
|
|
<p class="summary">{{ projectRetrospective.summary }}</p>
|
|
|
|
|
|
<div class="section">
|
|
|
- <h4><ion-icon name="trophy-outline"></ion-icon> 项目亮点</h4>
|
|
|
+ <h4>
|
|
|
+ <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"/>
|
|
|
+ </svg>
|
|
|
+ 项目亮点
|
|
|
+ </h4>
|
|
|
<ul>
|
|
|
@for (item of projectRetrospective.highlights; track item) {
|
|
|
<li>{{ item }}</li>
|
|
|
@@ -244,7 +325,13 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="section">
|
|
|
- <h4><ion-icon name="alert-circle-outline"></ion-icon> 遇到的挑战</h4>
|
|
|
+ <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>
|
|
|
@@ -253,7 +340,12 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="section">
|
|
|
- <h4><ion-icon name="bulb-outline"></ion-icon> 经验教训</h4>
|
|
|
+ <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>
|
|
|
@@ -262,7 +354,15 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="section">
|
|
|
- <h4><ion-icon name="compass-outline"></ion-icon> 改进建议</h4>
|
|
|
+ <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"/>
|
|
|
+ </svg>
|
|
|
+ 改进建议
|
|
|
+ </h4>
|
|
|
<ul>
|
|
|
@for (item of projectRetrospective.recommendations; track item) {
|
|
|
<li>{{ item }}</li>
|
|
|
@@ -271,75 +371,105 @@
|
|
|
</div>
|
|
|
|
|
|
@if (canEdit) {
|
|
|
- <ion-button
|
|
|
- expand="block"
|
|
|
- fill="outline"
|
|
|
+ <button
|
|
|
+ class="btn btn-outline btn-block"
|
|
|
(click)="generateRetrospective()"
|
|
|
[disabled]="generating">
|
|
|
- <ion-icon name="refresh-outline" slot="start"></ion-icon>
|
|
|
+ <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"/>
|
|
|
+ </svg>
|
|
|
重新生成
|
|
|
- </ion-button>
|
|
|
+ </button>
|
|
|
}
|
|
|
</div>
|
|
|
}
|
|
|
- </ion-card-content>
|
|
|
- </ion-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 4. 归档操作 -->
|
|
|
@if (!archiveStatus.archived) {
|
|
|
@if (canEdit) {
|
|
|
- <ion-card class="archive-card">
|
|
|
- <ion-card-content>
|
|
|
+ <div class="card archive-card">
|
|
|
+ <div class="card-content">
|
|
|
<div class="archive-checklist">
|
|
|
<h3>归档前检查</h3>
|
|
|
- <ion-list lines="none">
|
|
|
- <ion-item>
|
|
|
- <ion-icon
|
|
|
- [name]="finalPayment.status === 'completed' ? 'checkmark-circle' : 'ellipse-outline'"
|
|
|
- [color]="finalPayment.status === 'completed' ? 'success' : 'medium'"
|
|
|
- slot="start"></ion-icon>
|
|
|
- <ion-label>尾款已结清</ion-label>
|
|
|
- </ion-item>
|
|
|
- <ion-item>
|
|
|
- <ion-icon
|
|
|
- [name]="customerFeedback.submitted ? 'checkmark-circle' : 'ellipse-outline'"
|
|
|
- [color]="customerFeedback.submitted ? 'success' : 'medium'"
|
|
|
- slot="start"></ion-icon>
|
|
|
- <ion-label>客户已评价</ion-label>
|
|
|
- </ion-item>
|
|
|
- <ion-item>
|
|
|
- <ion-icon
|
|
|
- [name]="projectRetrospective ? 'checkmark-circle' : 'ellipse-outline'"
|
|
|
- [color]="projectRetrospective ? 'success' : 'medium'"
|
|
|
- slot="start"></ion-icon>
|
|
|
- <ion-label>项目复盘已完成</ion-label>
|
|
|
- </ion-item>
|
|
|
- </ion-list>
|
|
|
+ <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"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ <span>尾款已结清</span>
|
|
|
+ </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"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ <span>客户已评价</span>
|
|
|
+ </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"/>
|
|
|
+ }
|
|
|
+ </svg>
|
|
|
+ <span>项目复盘已完成</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <ion-button
|
|
|
- expand="block"
|
|
|
- color="success"
|
|
|
- size="large"
|
|
|
+ <button
|
|
|
+ class="btn btn-success btn-block btn-large"
|
|
|
(click)="archiveProject()"
|
|
|
[disabled]="saving || finalPayment.status !== 'completed' || !customerFeedback.submitted || !projectRetrospective">
|
|
|
- <ion-icon name="archive-outline" slot="start"></ion-icon>
|
|
|
+ <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>
|
|
|
归档项目
|
|
|
- </ion-button>
|
|
|
- </ion-card-content>
|
|
|
- </ion-card>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
}
|
|
|
} @else {
|
|
|
- <ion-card class="archived-card">
|
|
|
- <ion-card-content>
|
|
|
+ <div class="card archived-card">
|
|
|
+ <div class="card-content">
|
|
|
<div class="archived-status">
|
|
|
- <ion-icon name="checkmark-circle" color="success"></ion-icon>
|
|
|
+ <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>
|
|
|
- </ion-card-content>
|
|
|
- </ion-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
}
|
|
|
</div>
|
|
|
}
|