| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722 |
- <!-- 欢迎区域 -->
- <section class="welcome-section">
- <div class="welcome-header">
- <div>
- <h2>您好,{{currentUser?.name}} 👋</h2>
- <p>今天是 {{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }},祝您工作顺利!</p>
- </div>
- <button class="attendance-view-btn" (click)="viewAttendance()" title="查看人员考勤">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
- <line x1="16" y1="2" x2="16" y2="6"></line>
- <line x1="8" y1="2" x2="8" y2="6"></line>
- <line x1="3" y1="10" x2="21" y2="10"></line>
- </svg>
- 查看考勤
- </button>
- </div>
- </section>
- <!-- 数据看板 -->
- <section class="stats-dashboard">
- <div class="stats-grid">
- <!-- 项目总数 -->
- <div class="stat-card" (click)="handleTotalProjectsClick()" title="点击查看所有项目">
- <div class="stat-icon primary">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <path d="M3 3h7v7H3z"></path>
- <path d="M14 3h7v7h-7z"></path>
- <path d="M14 14h7v7h-7z"></path>
- <path d="M3 14h7v7H3z"></path>
- </svg>
- </div>
- <div class="stat-content">
- <div class="stat-value">{{ stats.totalProjects() }}</div>
- <div class="stat-label">项目总数</div>
- </div>
- </div>
- <!-- 新咨询数 - 已隐藏 -->
- <!-- <div class="stat-card" (click)="handleNewConsultationsClick()" title="点击查看新咨询详情">
- <div class="stat-icon secondary">
- <svg width="24" height="24" 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>
- </div>
- <div class="stat-content">
- <div class="stat-value">{{ stats.newConsultations() }}</div>
- <div class="stat-label">新咨询数</div>
- </div>
- </div> -->
- <!-- 待分配项目数 -->
- <div class="stat-card" (click)="handlePendingAssignmentsClick()" title="点击查看待分配项目详情">
- <div class="stat-icon warning">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
- <polyline points="22 4 12 14.01 9 11.01"></polyline>
- </svg>
- </div>
- <div class="stat-content">
- <div class="stat-value">{{ stats.pendingAssignments() }}</div>
- <div class="stat-label">待分配项目数</div>
- </div>
- </div>
- <!-- 异常项目 -->
- <div class="stat-card" (click)="handleExceptionProjectsClick()" title="点击查看异常项目详情">
- <div class="stat-icon danger">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
- <line x1="12" y1="9" x2="12" y2="13"></line>
- <line x1="12" y1="17" x2="12.01" y2="17"></line>
- </svg>
- </div>
- <div class="stat-content">
- <div class="stat-value">{{ stats.exceptionProjects() }}</div>
- <div class="stat-label">异常项目</div>
- </div>
- </div>
- <!-- 售后服务 -->
- <div class="stat-card" (click)="handleAfterSalesClick()" title="点击查看售后服务详情">
- <div class="stat-icon success">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path>
- </svg>
- </div>
- <div class="stat-content">
- <div class="stat-value">{{ stats.afterSalesCount() }}</div>
- <div class="stat-label">售后服务</div>
- </div>
- </div>
- </div>
- </section>
- <!-- 新客户触达 与 老客户回访 - 暂时隐藏,等待后续功能开发 -->
- <!-- <section class="crm-queues">
- <div class="crm-grid">
- <div class="crm-card">
- <div class="crm-header">
- <div class="crm-title-section">
- <h3>新客户触达</h3>
- <div class="crm-stats">
- <div class="stat-item">
- <span class="stat-number">0</span>
- <span class="stat-label">待触达</span>
- </div>
- <div class="stat-item">
- <span class="stat-number success">0%</span>
- <span class="stat-label">转化率</span>
- </div>
- </div>
- </div>
- <a class="view-all-link" (click)="goToConsultationList()">查看全部</a>
- </div>
- <div class="crm-list">
- <div class="empty-state small">暂无待触达客户</div>
- </div>
- </div>
- <div class="crm-card">
- <div class="crm-header">
- <div class="crm-title-section">
- <h3>老客户回访</h3>
- <div class="crm-stats">
- <div class="stat-item">
- <span class="stat-number">0</span>
- <span class="stat-label">待回访</span>
- </div>
- <div class="stat-item">
- <span class="stat-number warning">0%</span>
- <span class="stat-label">留存率</span>
- </div>
- </div>
- </div>
- <a class="view-all-link" (click)="goToConsultationList()">查看全部</a>
- </div>
- <div class="crm-list">
- <div class="empty-state small">暂无待回访客户</div>
- </div>
- </div>
- </div>
- </section> -->
- <!-- 新增:待跟进尾款项目列表 -->
- <section class="pending-final-payment-section">
- <div class="section-header">
- <h3>待跟进尾款项目</h3>
- <div class="section-stats">
- <span class="stat-badge urgent">{{ pendingFinalPaymentProjects().length }}</span>
- <span class="stat-label">个项目待跟进</span>
- </div>
- </div>
-
- <div class="final-payment-list">
- @if (pendingFinalPaymentProjects().length === 0) {
- <div class="empty-state">
- <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <circle cx="12" cy="12" r="10"></circle>
- <path d="M12 6v6l4 2"></path>
- </svg>
- <p>暂无待跟进尾款项目</p>
- </div>
- }
-
- @for (project of pendingFinalPaymentProjects(); track project.id) {
- <div class="final-payment-item" [class.overdue]="project.status === '已逾期'" [class.warning]="project.status === '待创建'">
- <div class="project-info">
- <div class="project-header">
- <h4 class="project-name">{{ project.projectName }}</h4>
- <div class="payment-summary">
- <span class="payment-amount remaining" title="剩余未付">¥{{ project.finalPaymentAmount | number:'1.0-0' }}</span>
- <span class="payment-details">
- <small>订单总额: ¥{{ project.totalAmount | number:'1.0-0' }}</small>
- <small>已付: ¥{{ project.paidAmount | number:'1.0-0' }}</small>
- </span>
- </div>
- </div>
- <div class="customer-info">
- <div class="customer-details">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" class="icon-user">
- <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
- <circle cx="12" cy="7" r="4"></circle>
- </svg>
- <span class="customer-name">{{ project.customerName }}</span>
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" class="icon-phone">
- <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
- </svg>
- <span class="customer-phone">{{ project.customerPhone }}</span>
- </div>
- <div class="project-meta">
- <span class="due-date">
- <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>
- 应付时间:{{ project.dueDate | date:'yyyy-MM-dd' }}
- </span>
- <span class="status-badge"
- [ngClass]="{
- 'overdue': project.status === '已逾期',
- 'pending': project.status === '待付款',
- 'warning': project.status === '待创建'
- }">
- {{ project.status }}
- @if (project.overdueDay > 0) {
- <span class="overdue-days">(逾期{{ project.overdueDay }}天)</span>
- }
- </span>
- </div>
- </div>
- <!-- 进度条显示 -->
- <div class="payment-progress-bar">
- <div class="progress-info">
- <span class="progress-label">付款进度</span>
- <span class="progress-percent">{{ (project.paidAmount / project.totalAmount * 100) | number:'1.0-0' }}%</span>
- </div>
- <div class="progress-track">
- <div class="progress-fill" [style.width.%]="(project.paidAmount / project.totalAmount * 100)"></div>
- </div>
- </div>
- </div>
- <div class="payment-actions">
- <button
- class="btn-primary mini"
- (click)="followUpFinalPayment(project.projectId)"
- title="开始跟进客户尾款"
- >
- <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>
- <button
- class="btn-secondary mini"
- (click)="viewProjectDetail(project.projectId)"
- title="查看项目详情"
- >
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
- <circle cx="12" cy="12" r="3"></circle>
- </svg>
- 查看详情
- </button>
- </div>
- </div>
- }
- </div>
- </section>
- <!-- 紧急事件和待办任务流 -->
- <div class="content-grid">
- <!-- 紧急事件列表(⭐ 使用可复用组件) -->
- <section class="urgent-tasks-section">
- <div class="section-header">
- <h3>紧急事件</h3>
- <div style="display: flex; gap: 12px; align-items: center;">
- <button
- class="btn-primary"
- (click)="showTaskForm()"
- style="font-size: 14px; padding: 6px 16px;"
- >
- 添加紧急事项
- </button>
- <a href="/customer-service/project-list" class="view-all-link">查看全部</a>
- </div>
- </div>
-
- <div class="tasks-list">
- @if (loadingUrgentEvents()) {
- <div class="loading-state">
- <svg class="spinner" viewBox="0 0 50 50">
- <circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle>
- </svg>
- <p>计算紧急事件中...</p>
- </div>
- }
- @if (!loadingUrgentEvents() && urgentEventsList().length === 0) {
- <div class="empty-state">
- <svg viewBox="0 0 24 24" width="64" height="64" fill="#d1d5db">
- <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
- </svg>
- <p>暂无紧急事件</p>
- <p class="hint">所有项目时间节点正常 ✅</p>
- </div>
- }
- @if (!loadingUrgentEvents() && urgentEventsList().length > 0) {
- <div class="todo-list-compact urgent-list">
- @for (event of urgentEventsList(); track event.id) {
- <div class="todo-item-compact urgent-item" [attr.data-urgency]="event.urgencyLevel">
- <div class="urgency-indicator" [attr.data-urgency]="event.urgencyLevel"></div>
- <div class="task-content">
- <div class="task-header">
- <span class="task-title">{{ event.title }}</span>
- <div class="task-badges">
- <span class="badge badge-urgency" [attr.data-urgency]="event.urgencyLevel">
- @if (event.urgencyLevel === 'critical') { 🔴 紧急 }
- @else if (event.urgencyLevel === 'high') { 🟠 重要 }
- @else { 🟡 注意 }
- </span>
- <span class="badge badge-event-type">
- @if (event.eventType === 'review') { 对图 }
- @else if (event.eventType === 'delivery') { 交付 }
- @else if (event.eventType === 'phase_deadline') { {{ event.phaseName }} }
- </span>
- </div>
- </div>
- <div class="task-description">{{ event.description }}</div>
- <div class="task-meta">
- <span class="project-info">
- <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
- <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
- </svg>
- 项目: {{ event.projectName }}
- </span>
- @if (event.designerName) {
- <span class="designer-info">
- <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
- <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
- </svg>
- 设计师: {{ event.designerName }}
- </span>
- }
- </div>
- <div class="task-footer">
- <span class="deadline-info" [class.overdue]="event.overdueDays && event.overdueDays > 0">
- <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
- <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>
- </svg>
- 截止: {{ event.deadline | date:'MM-dd HH:mm' }}
- @if (event.overdueDays && event.overdueDays > 0) { <span class="overdue-label">(逾期{{ event.overdueDays }}天)</span> }
- @else if (event.overdueDays && event.overdueDays < 0) { <span class="upcoming-label">(还剩{{ -event.overdueDays }}天)</span> }
- @else { <span class="today-label">(今天)</span> }
- </span>
- @if (event.completionRate !== undefined) {
- <span class="completion-info">
- <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
- <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
- </svg>
- 完成率: {{ event.completionRate }}%
- </span>
- }
- </div>
- </div>
- <div class="task-actions">
- <button class="btn-action btn-view" (click)="onUrgentEventViewProject(event.projectId)">查看项目</button>
- </div>
- </div>
- }
- </div>
- }
- </div>
- </section>
- <!-- iOS风格的添加紧急事项面板 -->
- @if (isTaskFormVisible()) {
- <div class="ios-modal-overlay" (click)="hideTaskForm()">
- <div class="ios-panel" (click)="$event.stopPropagation()">
- <div class="ios-panel-header">
- <h3>添加紧急事项</h3>
- <button class="ios-close-button" (click)="hideTaskForm()">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <line x1="18" y1="6" x2="6" y2="18"></line>
- <line x1="6" y1="6" x2="18" y2="18"></line>
- </svg>
- </button>
- </div>
-
- <div class="ios-panel-content">
- <form (ngSubmit)="handleAddTaskSubmit()">
- <div class="form-group">
- <label for="taskTitle">任务标题 *</label>
- <input
- type="text"
- id="taskTitle"
- [(ngModel)]="newTask.title"
- [ngModelOptions]="{standalone: true}"
- placeholder="请输入任务标题"
- required
- class="ios-input"
- >
- </div>
-
- <div class="form-group">
- <label for="projectSelect">选择项目 *</label>
- <select
- id="projectSelect"
- [(ngModel)]="newTask.projectId"
- [ngModelOptions]="{standalone: true}"
- (change)="onProjectChange($any($event.target).value)"
- required
- class="ios-select"
- >
- <option value="">-- 请选择项目 --</option>
- @for (project of projectList(); track project.id) {
- <option [value]="project.id">{{ project.title }}</option>
- }
- </select>
- </div>
-
- <div class="form-group">
- <label for="spaceSelect">选择空间(可选)</label>
- <select
- id="spaceSelect"
- [(ngModel)]="newTask.spaceId"
- [ngModelOptions]="{standalone: true}"
- class="ios-select"
- [disabled]="!newTask.projectId"
- >
- <option value="">-- 请选择空间 --</option>
- @for (space of spaceList(); track space.id) {
- <option [value]="space.id">{{ space.title }}</option>
- }
- </select>
- </div>
-
- <div class="form-group">
- <label for="projectStage">项目阶段 *</label>
- <select
- id="projectStage"
- [(ngModel)]="newTask.stage"
- [ngModelOptions]="{standalone: true}"
- required
- class="ios-select"
- >
- <option value="订单分配">订单分配</option>
- <option value="慎设需求">慎设需求</option>
- <option value="交付执行">交付执行</option>
- <option value="售后">售后</option>
- </select>
- </div>
-
- <div class="form-group">
- <label for="region">区域/位置(可选)</label>
- <input
- type="text"
- id="region"
- [(ngModel)]="newTask.region"
- [ngModelOptions]="{standalone: true}"
- placeholder="例如:客厅、主卧、厨房"
- class="ios-input"
- >
- </div>
-
- <div class="form-group">
- <label for="taskDeadline">截止时间 *</label>
-
- <!-- 显示当前选择的截止时间 -->
- <div class="deadline-display ios-input" (click)="deadlineDropdownVisible = !deadlineDropdownVisible">
- {{ getDisplayDeadline() || '请选择截止时间' }}
- <span class="dropdown-arrow" [class.rotate]="deadlineDropdownVisible">▼</span>
- </div>
-
- <!-- 预设时长下拉选择框 -->
- <div class="deadline-dropdown" [class.visible]="deadlineDropdownVisible">
- @for (preset of timePresets; track preset.hours) {
- <div class="dropdown-option"
- [class.selected]="selectedPreset === preset.hours.toString()"
- (click)="handlePresetSelection(preset.hours.toString())">
- {{ preset.label }}
- </div>
- }
-
- <!-- 当天24:00前选项 -->
- <div class="dropdown-option"
- [class.selected]="selectedPreset === 'today'"
- (click)="handlePresetSelection('today')">
- 今日24:00前
- </div>
-
- <!-- 自定义时间选项 -->
- <div class="dropdown-divider"></div>
- <div class="dropdown-option custom-option" (click)="handlePresetSelection('custom')">
- 自定义时间
- </div>
- </div>
-
- <!-- 错误提示信息 -->
- @if (deadlineError) {
- <div class="error-message">{{ deadlineError }}</div>
- }
- </div>
-
- <!-- 自定义时间选择弹窗 -->
- @if (isCustomTimeVisible) {
- <div class="custom-time-modal">
- <div class="modal-backdrop"></div>
- <div class="modal-content">
- <div class="modal-header">
- <h3>选择自定义时间</h3>
- <button class="close-button" (click)="closeCustomTimePicker()">×</button>
- </div>
-
- <div class="modal-body">
- <!-- 日期选择 -->
- <div class="date-picker">
- <label>日期</label>
- <input
- type="date"
- [(ngModel)]="customDate"
- min="{{ todayDate }}"
- max="{{ sevenDaysLaterDate }}"
- class="ios-input"
- />
- </div>
-
- <!-- 时间选择 -->
- <div class="time-picker">
- <label>时间</label>
- <input
- type="time"
- [(ngModel)]="customTime"
- class="ios-input"
- />
- </div>
-
- <!-- 错误提示信息 -->
- @if (deadlineError) {
- <div class="error-message">{{ deadlineError }}</div>
- }
- </div>
-
- <div class="modal-footer">
- <button class="cancel-button" (click)="closeCustomTimePicker()">取消</button>
- <button class="confirm-button" (click)="handleCustomTimeSelection()">确定</button>
- </div>
- </div>
- </div>
- }
-
- <div class="form-group">
- <label for="assigneeSelect">指派给(可选)</label>
- <select
- id="assigneeSelect"
- [(ngModel)]="newTask.assigneeId"
- [ngModelOptions]="{standalone: true}"
- class="ios-select"
- >
- <option value="">-- 暂不指派 --</option>
- @for (member of teamMembers(); track member.id) {
- <option [value]="member.id">{{ member.name }}{{ member.roleName ? ' (' + member.roleName + ')' : '' }}</option>
- }
- </select>
- </div>
-
- <div class="form-group">
- <label for="taskPriority">优先级 *</label>
- <select
- id="taskPriority"
- [(ngModel)]="newTask.priority"
- [ngModelOptions]="{standalone: true}"
- class="ios-select"
- >
- <option value="high">🔴 高优先级(紧急)</option>
- <option value="medium">🟡 中优先级(普通)</option>
- <option value="low">🟢 低优先级</option>
- </select>
- </div>
-
- <div class="form-group">
- <label for="taskDescription">详细描述(可选)</label>
- <textarea
- id="taskDescription"
- [(ngModel)]="newTask.description"
- [ngModelOptions]="{standalone: true}"
- placeholder="请详细描述需要紧急处理的问题..."
- rows="4"
- class="ios-textarea"
- ></textarea>
- </div>
- </form>
- </div>
-
- <div class="ios-panel-footer">
- <button type="button" class="ios-cancel-button" (click)="hideTaskForm()">取消</button>
- <button type="button" class="ios-submit-button" (click)="handleAddTaskSubmit()" [disabled]="isSubmitDisabled">确定</button>
- </div>
- </div>
- </div>
- }
- <!-- 待办任务流(复用组长端设计) -->
- <section class="project-updates-section todo-section-customer-service">
- <div class="section-header">
- <h2>
- 待办任务
- @if (todoTasksFromIssues().length > 0) {
- <span class="task-count">({{ todoTasksFromIssues().length }})</span>
- }
- </h2>
- <button
- class="btn-refresh"
- (click)="onRefreshTodoTasks()"
- [disabled]="loadingTodoTasks()"
- title="刷新待办任务">
- <svg viewBox="0 0 24 24" width="16" height="16" [class.rotating]="loadingTodoTasks()">
- <path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
- </svg>
- </button>
- </div>
-
- <!-- 加载状态 -->
- @if (loadingTodoTasks()) {
- <div class="loading-state">
- <svg class="spinner" viewBox="0 0 50 50">
- <circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle>
- </svg>
- <p>加载待办任务中...</p>
- </div>
- }
-
- <!-- 错误状态 -->
- @if (!loadingTodoTasks() && todoTaskError()) {
- <div class="error-state">
- <svg viewBox="0 0 24 24" width="48" height="48" fill="#ef4444">
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
- </svg>
- <p>{{ todoTaskError() }}</p>
- <button class="btn-retry" (click)="onRefreshTodoTasks()">重试</button>
- </div>
- }
-
- <!-- 空状态 -->
- @if (!loadingTodoTasks() && !todoTaskError() && todoTasksFromIssues().length === 0) {
- <div class="empty-state">
- <svg viewBox="0 0 24 24" width="64" height="64" fill="#d1d5db">
- <path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
- </svg>
- <p>暂无待办任务</p>
- <p class="hint">所有项目问题都已处理完毕 🎉</p>
- </div>
- }
-
- <!-- 待办任务列表 -->
- @if (!loadingTodoTasks() && !todoTaskError() && todoTasksFromIssues().length > 0) {
- <div class="todo-list-compact">
- @for (task of todoTasksFromIssues(); track task.id) {
- <div class="todo-item-compact" [attr.data-priority]="task.priority">
- <!-- 左侧优先级色条 -->
- <div class="priority-indicator" [attr.data-priority]="task.priority"></div>
-
- <!-- 任务内容 -->
- <div class="task-content">
- <!-- 标题行 -->
- <div class="task-header">
- <span class="task-title">{{ task.title }}</span>
- <div class="task-badges">
- <span class="badge badge-priority" [attr.data-priority]="task.priority">
- {{ getPriorityConfig(task.priority).label }}
- </span>
- <span class="badge badge-type">{{ getIssueTypeLabel(task.type) }}</span>
- </div>
- </div>
-
- <!-- 项目信息行 -->
- <div class="task-meta">
- <span class="project-info">
- <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
- <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
- </svg>
- 项目: {{ task.projectName }}
- @if (task.relatedSpace) {
- | {{ task.relatedSpace }}
- }
- @if (task.relatedStage) {
- | {{ task.relatedStage }}
- }
- </span>
- </div>
-
- <!-- 底部信息行 -->
- <div class="task-footer">
- <span class="time-info" [title]="formatExactTime(task.createdAt)">
- <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
- <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>
- </svg>
- 创建于 {{ formatRelativeTime(task.createdAt) }}
- </span>
-
- <span class="assignee-info">
- <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
- <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
- </svg>
- 指派给: {{ task.assigneeName }}
- </span>
- </div>
- </div>
-
- <!-- 右侧操作按钮(⭐ 使用新的事件处理方法) -->
- <div class="task-actions">
- <button
- class="btn-action btn-view"
- (click)="onTodoTaskViewDetails(task)"
- title="查看详情">
- <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
- <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
- </svg>
- 查看详情
- </button>
- <button
- class="btn-action btn-mark-read"
- (click)="onTodoTaskMarkAsRead(task)"
- title="标记已读">
- <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
- <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
- </svg>
- 标记已读
- </button>
- </div>
- </div>
- }
- </div>
- }
- </section>
- <!-- 回到顶部按钮 -->
- <button class="back-to-top" (click)="scrollToTop()" [class.visible]="showBackToTop()">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="18 15 12 9 6 15"></polyline>
- </svg>
- </button>
|