| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- <div class="project-list-container">
- <!-- 右侧主要内容 -->
- <div class="project-content">
- <!-- 页面标题和操作 -->
- <div class="page-header">
- <h2>项目看板</h2>
- <div class="header-actions">
- <div class="search-container">
- <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="搜索项目名称或客户..."
- [value]="searchTerm()"
- (input)="searchTerm.set($any($event.target).value)"
- (keyup.enter)="onSearch()"
- >
- <button class="search-btn" (click)="onSearch()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <line x1="5" y1="12" x2="19" y2="12"></line>
- <polyline points="12 5 19 12 12 19"></polyline>
- </svg>
- </button>
- </div>
- </div>
- <div class="view-toggle">
- <button [class.active]="viewMode() === 'card'" (click)="toggleView('card')">卡片</button>
- <button [class.active]="viewMode() === 'list'" (click)="toggleView('list')">列表</button>
- <button [class.active]="viewMode() === 'dashboard'" (click)="toggleView('dashboard')">监控大盘</button>
- </div>
- <button class="add-project-btn" (click)="navigateToCreateOrder()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <line x1="12" y1="5" x2="12" y2="19"></line>
- <line x1="5" y1="12" x2="19" y2="12"></line>
- </svg>
- 添加项目
- </button>
- </div>
- </div>
- <!-- 筛选和排序工具栏 -->
- <div class="filter-toolbar">
- <div class="filter-group">
- <label>状态筛选</label>
- <select (change)="onStatusChange($event)" [value]="statusFilter()">
- @for (option of statusOptions; track option.value) {
- <option [value]="option.value">{{ option.label }}</option>
- }
- </select>
- </div>
- <div class="filter-group">
- <label>阶段筛选</label>
- <select (change)="onStageChange($event)" [value]="stageFilter()">
- @for (option of stageOptions; track option.value) {
- <option [value]="option.value">{{ option.label }}</option>
- }
- </select>
- </div>
- <div class="filter-group">
- <label>排序方式</label>
- <select (change)="onSortChange($event)" [value]="sortBy()">
- @for (option of sortOptions; track option.value) {
- <option [value]="option.value">{{ option.label }}</option>
- }
- </select>
- </div>
- <div class="filter-results">
- <span>共 {{ projects().length }} 个项目</span>
- </div>
- </div>
- <!-- 加载状态 -->
- @if (isLoading()) {
- <div class="loading-container">
- <div class="loading-spinner"></div>
- <p>正在加载项目数据...</p>
- </div>
- }
- <!-- 错误状态 -->
- @if (loadError()) {
- <div class="error-container">
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <circle cx="12" cy="12" r="10"></circle>
- <line x1="12" y1="8" x2="12" y2="12"></line>
- <line x1="12" y1="16" x2="12.01" y2="16"></line>
- </svg>
- <p>{{ loadError() }}</p>
- <button class="retry-btn" (click)="loadProjects()">重试</button>
- </div>
- }
- <!-- 视图:卡片模式(看板) -->
- @if (viewMode() === 'card' && !isLoading() && !loadError()) {
- <div class="kanban-container">
- <div class="kanban-scroll">
- <!-- 列头 -->
- <div class="kanban-header">
- @for (col of columns; track col.id) {
- <div class="kanban-column-header" [attr.data-col]="col.id">
- <h3 class="column-title">{{ col.name }}</h3>
- <span class="stage-count">{{ getProjectsByColumn(col.id).length }}</span>
- </div>
- }
- </div>
- <!-- 列体 -->
- <div class="kanban-body">
- @for (col of columns; track col.id) {
- <div class="kanban-column" [attr.data-col]="col.id">
- @for (project of getProjectsByColumn(col.id); track project.id) {
- <div class="kanban-card" (click)="navigateToProject(project, col.id)">
- <div class="kanban-card-header">
- <div class="left">
- <h4 class="project-name">{{ project.name }}</h4>
- <span class="project-id">#{{ project.id }}</span>
- </div>
- <div class="right">
- @if (col.id === 'order') {
- <span class="pending-badge">待分配</span>
- }
- <span class="project-tag">{{ project.tagDisplayText }}</span>
- @if (project.isUrgent) {
- <span class="urgent-tag">紧急</span>
- }
- </div>
- </div>
- <div class="kanban-card-content">
- <p class="customer">客户:{{ project.customerName }}</p>
- <p class="assignee">设计师:{{ project.assigneeName || '未分配' }}</p>
- <p class="stage">阶段:<span class="stage-badge" [class]="getStageClass(project.currentStage)">{{ project.currentStage }}</span></p>
- <div class="progress-line">
- <div class="progress-bar">
- <div class="progress-fill" [style.width.percent]="project.progress"></div>
- </div>
- <span class="progress-text">{{ project.progress }}%</span>
- </div>
- <p class="deadline" [class.overdue]="project.daysUntilDeadline < 0" [class.urgent]="project.isUrgent">
- 截止:{{ formatDate(project.deadline) }}
- </p>
- </div>
- <div class="kanban-card-footer">
- <button class="btn-link" (click)="$event.stopPropagation(); navigateToProject(project, col.id)">进入</button>
- <button class="btn-link" (click)="$event.stopPropagation(); navigateToMessages(project)">沟通管理</button>
- </div>
- </div>
- }
- @if (getProjectsByColumn(col.id).length === 0) {
- <div class="empty-column">
- <span class="empty-icon">📦</span>
- <p>暂无项目</p>
- </div>
- }
- </div>
- }
- </div>
- </div>
- </div>
- }
- <!-- 视图:列表模式(沿用原卡片列表 + 分页) -->
- @if (viewMode() === 'list') {
- <div class="project-grid">
- @for (project of paginatedProjects(); track project.id) {
- <div class="project-card">
- <div class="card-header">
- <div class="card-title-section">
- <h3 class="project-name">{{ project.name }}</h3>
- <span class="project-id">#{{ project.id }}</span>
- </div>
- <div class="card-tags">
- <span class="project-tag">{{ project.tagDisplayText }}</span>
- @if (project.isUrgent) { <span class="urgent-tag">紧急</span> }
- </div>
- </div>
- <div class="card-content">
- <div class="info-item">
- <span class="info-label">客户</span>
- <span class="info-value">{{ project.customerName }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">设计师</span>
- <span class="info-value">{{ project.assigneeName || '未分配' }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">状态</span>
- <span class="info-value status-badge" [class]="getStatusClass(project.status)">{{ project.status }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">阶段</span>
- <span class="info-value stage-badge" [class]="getStageClass(project.currentStage)">{{ project.currentStage }}</span>
- </div>
- <div class="progress-section">
- <div class="progress-header">
- <span class="progress-label">进度</span>
- <span class="progress-percentage">{{ project.progress }}%</span>
- </div>
- <div class="progress-bar">
- <div class="progress-fill" [style.width.percent]="project.progress" [style.backgroundColor]="project.status === '进行中' ? '#1976d2' : '#757575'"></div>
- </div>
- </div>
- <div class="timeline-info">
- <div class="time-item">
- <span class="time-label">创建时间</span>
- <span class="time-value">{{ formatDate(project.createdAt) }}</span>
- </div>
- <div class="time-item">
- <span class="time-label">截止日期</span>
- <span class="time-value deadline" [class.overdue]="project.daysUntilDeadline < 0" [class.urgent]="project.isUrgent">
- {{ formatDate(project.deadline) }} ({{ project.daysUntilDeadline >= 0 ? '还有' + project.daysUntilDeadline + '天' : '已逾期' + getAbsValue(project.daysUntilDeadline) + '天' }})
- </span>
- </div>
- </div>
- @if (project.highPriorityNeeds && project.highPriorityNeeds.length > 0) {
- <div class="needs-section">
- <span class="needs-label">高优先级需求:</span>
- <ul class="needs-list">
- @for (need of project.highPriorityNeeds; track $index) {
- <li>
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <circle cx="12" cy="12" r="10"></circle>
- <line x1="12" y1="8" x2="12" y2="12"></line>
- <line x1="12" y1="16" x2="12.01" y2="16"></line>
- </svg>
- {{ need }}
- </li>
- }
- </ul>
- </div>
- }
- </div>
- <div class="card-footer">
- <button class="secondary-btn card-action" (click)="navigateToMessages(project)">
- <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>
- <span>沟通管理</span>
- </button>
- <button class="primary-btn card-action" (click)="navigateToProject(project, getColumnIdForProject(project))">
- <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>
- <span>查看详情</span>
- </button>
- </div>
- </div>
- }
- </div>
- <!-- 分页控件 -->
- @if (totalPages() > 1) {
- <div class="pagination">
- <button class="pagination-btn" (click)="prevPage()" [disabled]="currentPage() === 1">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <line x1="15" y1="18" x2="9" y2="12"></line>
- <line x1="9" y1="18" x2="15" y2="12"></line>
- </svg>
- </button>
- @for (page of pageNumbers(); track page) {
- <button class="pagination-btn" [class.active]="page === currentPage()" (click)="goToPage(page)">{{ page }}</button>
- }
- @if (totalPages() > 5) { <span class="pagination-ellipsis">...</span> }
- @if (totalPages() > 5) {
- <button class="pagination-btn" (click)="goToPage(totalPages())">{{ totalPages() }}</button>
- }
- <button class="pagination-btn" (click)="nextPage()" [disabled]="currentPage() === totalPages()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <line x1="9" y1="18" x2="15" y2="12"></line>
- <line x1="15" y1="6" x2="9" y2="12"></line>
- </svg>
- </button>
- </div>
- }
- }
- <!-- 视图:监控大盘模式 -->
- @if (viewMode() === 'dashboard') {
- <div class="dashboard-container">
- <!-- 使用iframe嵌入组长端监控大盘,通过CSS隐藏待办任务栏 -->
- <iframe
- src="/team-leader/dashboard"
- frameborder="0"
- width="100%"
- height="100%"
- style="min-height: 800px;"
- title="项目监控大盘"
- onload="const doc = this.contentDocument; doc.querySelector('.todo-section').style.display = 'none'; const header = doc.querySelector('.dashboard-header h1'); if (header && header.textContent.includes('设计组长工作台')) { header.style.display = 'none'; doc.querySelector('.dashboard-metrics').style.marginTop = '20px'; }">
- </iframe>
- </div>
- }
- </div>
|