|
@@ -64,18 +64,16 @@
|
|
|
<div class="record-section">
|
|
|
<h4>过往咨询记录</h4>
|
|
|
<div class="consultation-list">
|
|
|
- @for (record of consultationRecords(); track $index) {
|
|
|
- <div class="consultation-item">
|
|
|
- <div class="consultation-date">{{ formatDate(record.date) }}</div>
|
|
|
- <div class="consultation-content">{{ record.content }}</div>
|
|
|
- <div class="consultation-status"
|
|
|
- [class.status-processed]="record.status === '已解决' || record.status === '成功'"
|
|
|
- [class.status-processing]="record.status === '处理中'"
|
|
|
- [class.status-pending]="record.status === '待处理'">
|
|
|
- {{ record.status }}
|
|
|
- </div>
|
|
|
+ <div class="consultation-item" *ngFor="let record of consultationRecords()">
|
|
|
+ <div class="consultation-date">{{ formatDate(record.date) }}</div>
|
|
|
+ <div class="consultation-content">{{ record.content }}</div>
|
|
|
+ <div class="consultation-status"
|
|
|
+ [class.status-processed]="record.status === '已解决' || record.status === '成功'"
|
|
|
+ [class.status-processing]="record.status === '处理中'"
|
|
|
+ [class.status-pending]="record.status === '待处理'">
|
|
|
+ {{ record.status }}
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
@@ -83,14 +81,12 @@
|
|
|
<div class="record-section">
|
|
|
<h4>合作项目</h4>
|
|
|
<div class="projects-list">
|
|
|
- @for (proj of cooperationProjects(); track $index) {
|
|
|
- <div class="project-item">
|
|
|
- <div class="project-name">{{ proj.name }}</div>
|
|
|
- <div class="project-period">{{ formatDate(proj.startDate) }} - {{ formatDate(proj.endDate) }}</div>
|
|
|
- <div class="project-description">{{ proj.description }}</div>
|
|
|
- <div class="project-status">{{ proj.status }}</div>
|
|
|
- </div>
|
|
|
- }
|
|
|
+ <div class="project-item" *ngFor="let proj of cooperationProjects()">
|
|
|
+ <div class="project-name">{{ proj.name }}</div>
|
|
|
+ <div class="project-period">{{ formatDate(proj.startDate) }} - {{ formatDate(proj.endDate) }}</div>
|
|
|
+ <div class="project-description">{{ proj.description }}</div>
|
|
|
+ <div class="project-status">{{ proj.status }}</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
@@ -98,24 +94,18 @@
|
|
|
<div class="record-section">
|
|
|
<h4>历史反馈/评价</h4>
|
|
|
<div class="feedback-list">
|
|
|
- @for (feedback of historicalFeedbacks(); track $index) {
|
|
|
- <div class="feedback-item">
|
|
|
- <div class="feedback-date">{{ formatDate(feedback.date) }}</div>
|
|
|
- <div class="feedback-rating">
|
|
|
- @for (star of [1,2,3,4,5]; track $index) {
|
|
|
- <span>
|
|
|
- <i class="fa" [ngClass]="{ 'fa-star': star <= feedback.rating, 'fa-star-o': star > feedback.rating }"></i>
|
|
|
- </span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- <div class="feedback-content">{{ feedback.content }}</div>
|
|
|
- @if (feedback.response) {
|
|
|
- <div class="feedback-response">
|
|
|
- <strong>回复:</strong>{{ feedback.response }}
|
|
|
- </div>
|
|
|
- }
|
|
|
+ <div class="feedback-item" *ngFor="let feedback of historicalFeedbacks()">
|
|
|
+ <div class="feedback-date">{{ formatDate(feedback.date) }}</div>
|
|
|
+ <div class="feedback-rating">
|
|
|
+ <span *ngFor="let star of [1,2,3,4,5]">
|
|
|
+ <i class="fa" [ngClass]="{ 'fa-star': star <= feedback.rating, 'fa-star-o': star > feedback.rating }"></i>
|
|
|
+ </span>
|
|
|
</div>
|
|
|
- }
|
|
|
+ <div class="feedback-content">{{ feedback.content }}</div>
|
|
|
+ <div class="feedback-response" *ngIf="feedback.response">
|
|
|
+ <strong>回复:</strong>{{ feedback.response }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -124,44 +114,34 @@
|
|
|
<div class="card timeline-card">
|
|
|
<h3 class="card-title">项目阶段时间轴</h3>
|
|
|
<div class="project-timeline">
|
|
|
- @for (stage of projectStages; let i = $index; track $index) {
|
|
|
- <div class="timeline-item" [class.stage-completed]="stage.completed" [class.stage-in-progress]="stage.inProgress">
|
|
|
- <div class="timeline-icon" [class.icon-completed]="stage.completed" [class.icon-in-progress]="stage.inProgress">
|
|
|
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <circle cx="12" cy="12" r="9"></circle>
|
|
|
- @if (stage.completed) {
|
|
|
- <path d="m5 12 5 5 10-10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
|
- }
|
|
|
- @if (stage.inProgress) {
|
|
|
- <circle cx="12" cy="12" r="5"></circle>
|
|
|
- }
|
|
|
- </svg>
|
|
|
+ <div *ngFor="let stage of projectStages; index as i" class="timeline-item" [class.stage-completed]="stage.completed" [class.stage-in-progress]="stage.inProgress">
|
|
|
+ <div class="timeline-icon" [class.icon-completed]="stage.completed" [class.icon-in-progress]="stage.inProgress">
|
|
|
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <circle cx="12" cy="12" r="9"></circle>
|
|
|
+ <path *ngIf="stage.completed" d="m5 12 5 5 10-10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
|
+ <circle *ngIf="stage.inProgress" cx="12" cy="12" r="5"></circle>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="timeline-line" *ngIf="i < projectStages.length - 1" [class.line-completed]="stage.completed && projectStages[i+1].completed"></div>
|
|
|
+ <div class="timeline-content">
|
|
|
+ <div class="timeline-header">
|
|
|
+ <h4 class="stage-title">{{ stage.name }}</h4>
|
|
|
+ <span class="stage-status">
|
|
|
+ {{ stage.completed ? '已完成' : stage.inProgress ? '进行中' : '未开始' }}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
- @if (i < projectStages.length - 1) {
|
|
|
- <div class="timeline-line" [class.line-completed]="stage.completed && projectStages[i+1].completed"></div>
|
|
|
- }
|
|
|
- <div class="timeline-content">
|
|
|
- <div class="timeline-header">
|
|
|
- <h4 class="stage-title">{{ stage.name }}</h4>
|
|
|
- <span class="stage-status">
|
|
|
- {{ stage.completed ? '已完成' : stage.inProgress ? '进行中' : '未开始' }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div class="timeline-meta">
|
|
|
- <div class="stage-dates">
|
|
|
- @if (stage.startDate) { <span class="date-item">开始:{{ formatDate(stage.startDate) }}</span> }
|
|
|
- @if (stage.endDate) { <span class="date-item">完成:{{ formatDate(stage.endDate) }}</span> }
|
|
|
- </div>
|
|
|
- <div class="stage-responsible">负责人:{{ stage.responsible || '未分配' }}</div>
|
|
|
+ <div class="timeline-meta">
|
|
|
+ <div class="stage-dates">
|
|
|
+ <span *ngIf="stage.startDate" class="date-item">开始:{{ formatDate(stage.startDate) }}</span>
|
|
|
+ <span *ngIf="stage.endDate" class="date-item">完成:{{ formatDate(stage.endDate) }}</span>
|
|
|
</div>
|
|
|
- @if (stage.details) {
|
|
|
- <div class="stage-details">
|
|
|
- <p>{{ stage.details }}</p>
|
|
|
- </div>
|
|
|
- }
|
|
|
+ <div class="stage-responsible">负责人:{{ stage.responsible || '未分配' }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="stage-details" *ngIf="stage.details">
|
|
|
+ <p>{{ stage.details }}</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
@@ -202,142 +182,262 @@
|
|
|
</svg>
|
|
|
<span>文件</span>
|
|
|
</button>
|
|
|
- <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'members'" (click)="switchTab('members')">
|
|
|
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
|
- <circle cx="9" cy="7" r="4"></circle>
|
|
|
- <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
|
|
- <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
|
|
- </svg>
|
|
|
- <span>组员</span>
|
|
|
- </button>
|
|
|
- <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'requirements'" (click)="switchTab('requirements')">
|
|
|
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M9 11l3 3L22 4"></path>
|
|
|
- <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
|
|
- </svg>
|
|
|
- <span>需求</span>
|
|
|
- </button>
|
|
|
</div>
|
|
|
|
|
|
- @if (activeTab() === 'requirements') {
|
|
|
- <div class="tab-content">
|
|
|
- <div class="requirements-header" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
|
- <h3 style="margin:0;">需求补充</h3>
|
|
|
- <button class="secondary-btn btn-hover-effect" (click)="saveRequirementDraft()">保存草稿</button>
|
|
|
- </div>
|
|
|
- <div class="incomplete-hints" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px;">
|
|
|
- @if (!hasBlueprint()) {
|
|
|
- <div class="hint-card" style="border:1px dashed #f39c12;background:#fffaf0;padding:10px 12px;border-radius:8px;display:flex;align-items:center;gap:8px;">
|
|
|
- <span style="color:#e67e22;">未上传施工图纸</span>
|
|
|
- <label class="primary-btn btn-hover-effect" style="margin-left:4px;">
|
|
|
- <input type="file" accept=".pdf,.png,.jpg,.jpeg" style="display:none" (change)="uploadBlueprint($event)">
|
|
|
- 上传图纸
|
|
|
- </label>
|
|
|
+ <!-- 消息标签内容 -->
|
|
|
+ <div *ngIf="activeTab() === 'messages'" class="tab-content">
|
|
|
+ <div class="messages-container">
|
|
|
+ <div class="messages-list">
|
|
|
+ <div *ngFor="let message of messages()" class="message-item">
|
|
|
+ <div class="message-avatar">
|
|
|
+ {{ message.sender.charAt(0) }}
|
|
|
</div>
|
|
|
- }
|
|
|
- @if (!hasStylePreference()) {
|
|
|
- <div class="hint-card" style="border:1px dashed #8e44ad;background:#faf5ff;padding:10px 12px;border-radius:8px;display:flex;align-items:center;gap:8px;">
|
|
|
- <span style="color:#8e44ad;">未填写风格偏好</span>
|
|
|
- <button class="secondary-btn btn-hover-effect" (click)="showStyleEdit.set(true); tempStylePreference = ''">去填写</button>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-header">
|
|
|
+ <span class="message-sender">{{ message.sender }}</span>
|
|
|
+ <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="message-text">{{ message.content }}</div>
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="message-input-area">
|
|
|
+ <textarea
|
|
|
+ [value]="newMessage()"
|
|
|
+ (input)="onMessageInput($event)"
|
|
|
+ placeholder="输入消息内容..."
|
|
|
+ rows="3"
|
|
|
+ (keydown.enter.shift)="$event.preventDefault()"
|
|
|
+ (keydown.enter)="sendMessage()"
|
|
|
+ ></textarea>
|
|
|
+ <div class="message-actions">
|
|
|
+ <button class="secondary-btn btn-hover-effect">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
|
|
|
+ <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
+ </svg>
|
|
|
+ <span>上传文件</span>
|
|
|
+ </button>
|
|
|
+ <button class="primary-btn btn-hover-effect" (click)="sendMessage()" [disabled]="!newMessage().trim()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <line x1="22" y1="2" x2="11" y2="13"></line>
|
|
|
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
|
+ </svg>
|
|
|
+ <span>发送</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- @if (showStyleEdit()) {
|
|
|
- <div class="style-editor" style="padding:12px;border:1px solid #eee;border-radius:8px;margin-bottom:16px;background:#fff;">
|
|
|
- <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
|
- <span>风格选择:</span>
|
|
|
- <select [(ngModel)]="tempStylePreference">
|
|
|
- <option value="" disabled selected>请选择风格</option>
|
|
|
- @for (opt of styleOptions; track $index) {
|
|
|
- <option [value]="opt">{{ opt }}</option>
|
|
|
- }
|
|
|
- </select>
|
|
|
- <button class="primary-btn btn-hover-effect" (click)="saveStylePreference()" [disabled]="!tempStylePreference">保存</button>
|
|
|
- <button class="secondary-btn btn-hover-effect" (click)="showStyleEdit.set(false)">取消</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 概览标签内容 -->
|
|
|
+ <div *ngIf="activeTab() === 'overview'" class="tab-content">
|
|
|
+ <div class="overview-grid">
|
|
|
+ <!-- 客户信息卡片 -->
|
|
|
+ <div class="info-card">
|
|
|
+ <h4 class="card-title">客户信息</h4>
|
|
|
+ <div class="customer-info">
|
|
|
+ <div class="info-item">
|
|
|
+ <label>客户姓名</label>
|
|
|
+ <span>{{ project()?.customerName || '王先生' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <label>联系方式</label>
|
|
|
+ <span>138****5678</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <label>标签</label>
|
|
|
+ <div class="tag-container">
|
|
|
+ <span class="tag">朋友圈</span>
|
|
|
+ <span class="tag">软装</span>
|
|
|
+ <span class="tag">现代风格</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <label>高优先级需求</label>
|
|
|
+ <ul class="need-list">
|
|
|
+ <li *ngFor="let need of project()?.highPriorityNeeds || ['客厅光线充足', '储物空间充足', '环保材料']">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="20 6 9 17 4 12"></polyline>
|
|
|
+ </svg>
|
|
|
+ {{ need }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
- }
|
|
|
- <div class="requirements-form">
|
|
|
- <div class="form-row">
|
|
|
- <label>需求摘要</label>
|
|
|
- <textarea [formControl]="requirementForm.controls.summary" rows="3" placeholder="例如:偏好现代简约风,客厅以明亮色调为主…"></textarea>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 项目团队卡片 -->
|
|
|
+ <div class="info-card">
|
|
|
+ <h4 class="card-title">项目团队</h4>
|
|
|
+ <div class="team-info">
|
|
|
+ <div class="team-member">
|
|
|
+ <div class="member-avatar" title="客服小李">IMG</div>
|
|
|
+ <div class="member-details">
|
|
|
+ <div class="member-name">客服小李</div>
|
|
|
+ <div class="member-role">客户经理</div>
|
|
|
+ </div>
|
|
|
+ <button class="message-btn">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="team-member">
|
|
|
+ <div class="member-avatar" title="张设计师">IMG</div>
|
|
|
+ <div class="member-details">
|
|
|
+ <div class="member-name">张设计师</div>
|
|
|
+ <div class="member-role">主设计师</div>
|
|
|
+ </div>
|
|
|
+ <button class="message-btn">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="form-row">
|
|
|
- <label>重点诉求</label>
|
|
|
- <textarea [formControl]="requirementForm.controls.priorityPoints" rows="3"></textarea>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 最近反馈卡片 -->
|
|
|
+ <div class="info-card">
|
|
|
+ <h4 class="card-title">客户反馈</h4>
|
|
|
+ <div class="feedback-list">
|
|
|
+ <div *ngFor="let feedback of feedbacks()" class="feedback-item">
|
|
|
+ <div class="feedback-item">
|
|
|
+ <div class="feedback-header">
|
|
|
+ <div class="feedback-author">{{ getFeedbackCustomerName(feedback) }}</div>
|
|
|
+ <div class="feedback-rating">
|
|
|
+ <span class="rating-stars">★★★★☆</span>
|
|
|
+ <span class="rating-number">{{ getFeedbackRating(feedback) }}.0</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feedback-content">{{ feedback?.content || '' }}</div>
|
|
|
+ <div class="feedback-response" *ngIf="feedback?.response">
|
|
|
+ <div class="response-label">客服回复:</div>
|
|
|
+ <div class="response-text">{{ feedback.response }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="feedback-meta">
|
|
|
+ <span class="feedback-date">{{ formatDate(feedback?.createdAt) }}</span>
|
|
|
+ <span class="feedback-status" [class.status-processed]="feedback?.status === '已解决'" [class.status-pending]="feedback?.status === '待处理'" [class.status-processing]="feedback?.status === '处理中'">
|
|
|
+ {{ feedback?.status || '未知状态' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="view-all-btn btn-hover-effect" *ngIf="feedbacks().length > 0">查看全部反馈</button>
|
|
|
</div>
|
|
|
- <div class="form-row">
|
|
|
- <label>约束条件</label>
|
|
|
- <textarea [formControl]="requirementForm.controls.constraints" rows="3"></textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 里程碑标签内容 -->
|
|
|
+ <div *ngIf="activeTab() === 'milestones'" class="tab-content">
|
|
|
+ <div class="milestones-timeline">
|
|
|
+ <div *ngFor="let milestone of milestones(); index as i" class="milestone-item">
|
|
|
+ <div class="milestone-dot" [class.completed]="milestone.isCompleted"></div>
|
|
|
+ <div class="milestone-line" *ngIf="i < milestones().length - 1" [class.completed]="milestone.isCompleted && milestones()[i+1].isCompleted"></div>
|
|
|
+ <div class="milestone-content">
|
|
|
+ <div class="milestone-header">
|
|
|
+ <h4 class="milestone-title">{{ milestone.title }}</h4>
|
|
|
+ <span class="milestone-status" [class.status-completed]="milestone.isCompleted" [class.status-pending]="!milestone.isCompleted">
|
|
|
+ {{ milestone.isCompleted ? '已完成' : '进行中' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <p class="milestone-description">{{ milestone.description }}</p>
|
|
|
+ <div class="milestone-dates">
|
|
|
+ <div class="date-item">
|
|
|
+ <label>截止日期</label>
|
|
|
+ <span>{{ formatDate(milestone.dueDate) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="date-item" *ngIf="milestone.completedDate">
|
|
|
+ <label>完成日期</label>
|
|
|
+ <span>{{ formatDate(milestone.completedDate) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="milestone-actions" *ngIf="!milestone.isCompleted">
|
|
|
+ <button class="primary-btn small btn-hover-effect" (click)="completeMilestone(milestone.id)">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="20 6 9 17 4 12"></polyline>
|
|
|
+ </svg>
|
|
|
+ <span>标记完成</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="form-row">
|
|
|
- <label>预算</label>
|
|
|
- <input type="text" [formControl]="requirementForm.controls.budget" placeholder="如:20万以内">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 任务标签内容 -->
|
|
|
+ <div *ngIf="activeTab() === 'tasks'" class="tab-content">
|
|
|
+ <div class="tasks-filter">
|
|
|
+ <div class="filter-options">
|
|
|
+ <button class="filter-btn active">全部任务</button>
|
|
|
+ <button class="filter-btn">进行中</button>
|
|
|
+ <button class="filter-btn">已完成</button>
|
|
|
+ <button class="filter-btn">逾期</button>
|
|
|
+ </div>
|
|
|
+ <div class="search-box">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <circle cx="11" cy="11" r="8"></circle>
|
|
|
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
|
+ </svg>
|
|
|
+ <input type="text" placeholder="搜索任务...">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="tasks-list">
|
|
|
+ <!-- 修复任务列表中的状态显示,确保安全访问 -->
|
|
|
+ <div *ngFor="let task of tasks()" class="task-item">
|
|
|
+ <div class="task-checkbox">
|
|
|
+ <input type="checkbox" [checked]="task.isCompleted" (change)="task.isCompleted ? null : completeTask(task.id)">
|
|
|
</div>
|
|
|
- <div class="form-actions">
|
|
|
- <button class="primary-btn btn-hover-effect" (click)="submitRequirements()">提交需求</button>
|
|
|
+ <div class="task-content">
|
|
|
+ <h4 class="task-title" [class.completed]="task.isCompleted">{{ task.title || '未命名任务' }}</h4>
|
|
|
+ <p class="task-description">{{ task.description || '' }}</p>
|
|
|
+ <div class="task-meta">
|
|
|
+ <span class="task-assignee">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
|
+ <circle cx="9" cy="7" r="4"></circle>
|
|
|
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
|
|
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
|
|
+ </svg>
|
|
|
+ {{ task.assignee || '未分配' }}
|
|
|
+ </span>
|
|
|
+ <span class="task-deadline" [class.overdue]="task.isOverdue">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <polyline points="12 6 12 12 16 14"></polyline>
|
|
|
+ </svg>
|
|
|
+ {{ formatDate(task.deadline) }}
|
|
|
+ </span>
|
|
|
+ <span class="task-priority" [class.priority-high]="task.priority === 'high'" [class.priority-medium]="task.priority === 'medium'" [class.priority-low]="task.priority === 'low'">
|
|
|
+ {{ task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 消息标签内容 -->
|
|
|
- @if (activeTab() === 'messages') {
|
|
|
- <div class="tab-content">
|
|
|
- <div class="messages-toolbar" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;gap:8px;">
|
|
|
- <div style="display:flex;align-items:center;gap:8px;">
|
|
|
- <button class="primary-btn btn-hover-effect" (click)="createGroup()" [disabled]="creatingGroup() || !!chatGroup()">{{ creatingGroup() ? '拉群中…' : (chatGroup() ? '已创建群' : '拉群') }}</button>
|
|
|
- @if (chatGroup()) { <a class="secondary-btn btn-hover-effect" [href]="chatGroup()!.link" target="_blank">群聊入口</a> }
|
|
|
- </div>
|
|
|
- <div class="tips" style="color:#888;font-size:12px;">在这里与客户与设计师进行项目沟通</div>
|
|
|
- </div>
|
|
|
- <div class="messages-container">
|
|
|
- <div class="messages-list">
|
|
|
- @for (message of messages(); track $index) {
|
|
|
- <div class="message-item">
|
|
|
- <div class="message-avatar">
|
|
|
- {{ message.sender.charAt(0) }}
|
|
|
- </div>
|
|
|
- <div class="message-content">
|
|
|
- <div class="message-header">
|
|
|
- <span class="message-sender">{{ message.sender }}</span>
|
|
|
- <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
|
|
|
- </div>
|
|
|
- <div class="message-text">{{ message.content }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- <div class="message-input-area">
|
|
|
- <textarea
|
|
|
- [value]="newMessage()"
|
|
|
- (input)="onMessageInput($event)"
|
|
|
- placeholder="输入消息内容..."
|
|
|
- rows="3"
|
|
|
- (keydown.enter.shift)="$event.preventDefault()"
|
|
|
- (keydown.enter)="sendMessage()"
|
|
|
- ></textarea>
|
|
|
- <div class="message-actions">
|
|
|
- <button class="secondary-btn btn-hover-effect">
|
|
|
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
|
|
|
- <polyline points="14 2 14 8 20 8"></polyline>
|
|
|
- </svg>
|
|
|
- <span>上传文件</span>
|
|
|
- </button>
|
|
|
- <button class="primary-btn btn-hover-effect" (click)="sendMessage()" [disabled]="!newMessage().trim()">
|
|
|
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
- <line x1="22" y1="2" x2="11" y2="13"></line>
|
|
|
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
|
- </svg>
|
|
|
- <span>发送</span>
|
|
|
- </button>
|
|
|
+ <div *ngIf="activeTab() === 'messages'" class="tab-content">
|
|
|
+ <div class="messages-container">
|
|
|
+ <div class="messages-list">
|
|
|
+ <div *ngFor="let message of messages()" class="message-item">
|
|
|
+ <div class="message-avatar">
|
|
|
+ {{ message.sender.charAt(0) }}
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-header">
|
|
|
+ <span class="message-sender">{{ message.sender }}</span>
|
|
|
+ <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
|
|
|
</div>
|
|
|
+ <div class="message-text">{{ message.content }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 右侧边栏 - 企业微信聊天集成 -->
|
|
|
<div class="wechat-sidebar ios-sidebar">
|
|
@@ -361,20 +461,18 @@
|
|
|
|
|
|
<!-- 聊天消息列表 -->
|
|
|
<div class="wechat-messages" #wechatMessages>
|
|
|
- @for (msg of wechatMessagesList; track $index) {
|
|
|
- <div class="wechat-message-item">
|
|
|
- <div class="message-avatar">
|
|
|
- {{ msg.sender.charAt(0) }}
|
|
|
- </div>
|
|
|
- <div class="message-content">
|
|
|
- <div class="message-header">
|
|
|
- <span class="message-sender">{{ msg.sender }}</span>
|
|
|
- <span class="message-time">{{ formatTime(msg.timestamp) }}</span>
|
|
|
- </div>
|
|
|
- <div class="message-text">{{ msg.content }}</div>
|
|
|
+ <div *ngFor="let msg of wechatMessagesList" class="wechat-message-item">
|
|
|
+ <div class="message-avatar">
|
|
|
+ {{ msg.sender.charAt(0) }}
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-header">
|
|
|
+ <span class="message-sender">{{ msg.sender }}</span>
|
|
|
+ <span class="message-time">{{ formatTime(msg.timestamp) }}</span>
|
|
|
</div>
|
|
|
+ <div class="message-text">{{ msg.content }}</div>
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 消息输入框 -->
|