| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- <div class="modal-overlay" [class.visible]="visible" (click)="closeModal()">
- <div class="modal-container" (click)="$event.stopPropagation()">
- <div class="modal-header">
- <h2>设计师组分配</h2>
- <button class="close-btn" (click)="closeModal()">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <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="modal-body">
- <!-- 项目组选择 -->
- <div class="team-selection-section">
- <h3>选择项目组</h3>
- <div class="team-grid">
- @for (team of projectTeams; track team.id) {
- <div
- class="team-card"
- [class.selected]="internalSelectedTeamId === team.id"
- (click)="selectTeam(team.id)"
- >
- <div class="team-header">
- <h4>{{ team.name }}</h4>
- <span class="team-leader">组长:{{ team.leaderName }}</span>
- </div>
- <div class="team-description">{{ team.description }}</div>
- <div class="team-stats">
- <div class="stat-item">
- <span class="stat-label">成员</span>
- <span class="stat-value">{{ team.members.length }}人</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">空闲</span>
- <span class="stat-value idle">{{ getIdleDesignersCount(team) }}人</span>
- </div>
- </div>
- </div>
- }
- </div>
- </div>
- <!-- 设计师列表 -->
- @if (internalSelectedTeamId) {
- <div class="designer-selection-section">
- <div class="section-header">
- <h3>{{ getSelectedTeam()?.name }} - 设计师列表</h3>
- <div class="selection-summary">
- 已选择:{{ internalSelectedDesigners.length + internalCrossTeamCollaborators.length }}人
- </div>
- </div>
- <!-- 推荐设计师 -->
- @if (getRecommendedDesigners().length > 0) {
- <div class="recommended-section">
- <h4>
- <span class="recommend-icon">⭐</span>
- 推荐分配(长期闲置优先)
- </h4>
- <div class="designer-grid">
- @for (designer of getRecommendedDesigners(); track designer.id) {
- <div
- class="designer-card recommended"
- [class.selected]="isDesignerSelected(designer)"
- (click)="toggleDesignerSelection(designer)"
- >
- <div class="designer-avatar">
- @if (designer.avatar) {
- <img [src]="designer.avatar" [alt]="designer.name">
- } @else {
- <div class="avatar-placeholder">{{ designer.name.charAt(0) }}</div>
- }
- <div class="status-dot" [style.background-color]="getDesignerStatusColor(designer.status)"></div>
- </div>
-
- <div class="designer-info">
- <div class="designer-name">
- {{ designer.name }}
- @if (designer.isTeamLeader) {
- <span class="leader-badge">组长</span>
- }
- </div>
-
- <div class="designer-status">
- <span class="status-text" [style.color]="getDesignerStatusColor(designer.status)">
- {{ getDesignerStatusText(designer.status) }}
- </span>
- <span class="workload" [class]="getWorkloadClass(designer.workload)">
- {{ designer.workload }}%
- </span>
- </div>
- <div class="designer-metrics">
- <div class="metric-item" [class]="getIdleDaysClass(designer.idleDays)">
- <span class="metric-label">{{ getRecentOrdersText(designer) }}</span>
- </div>
-
- @if (designer.availableDates.length > 0) {
- <div class="metric-item">
- <span class="metric-label">{{ getAvailableDatesText(designer) }}</span>
- </div>
- }
- @if (designer.reviewDates.length > 0) {
- <div class="metric-item review-dates">
- <span class="metric-label">对图日期:{{ designer.reviewDates.join(', ') }}</span>
- </div>
- }
- </div>
- <div class="designer-skills">
- @for (skill of designer.skills.slice(0, 2); track skill) {
- <span class="skill-tag">{{ skill }}</span>
- }
- @if (designer.skills.length > 2) {
- <span class="skill-more">+{{ designer.skills.length - 2 }}</span>
- }
- </div>
- </div>
- <div class="designer-actions">
- <button
- class="calendar-btn"
- (click)="$event.stopPropagation(); showDesignerEmployeeDetail(designer)"
- title="查看设计师详情"
- >
- 👤 详情
- </button>
- @if (enableSpaceAssignment && spaceScenes.length > 0) {
- <button
- class="space-assign-btn"
- (click)="$event.stopPropagation(); openSpaceAssignment(designer)"
- title="分配空间"
- >
- 🏠
- </button>
- }
- </div>
- @if (enableSpaceAssignment && isDesignerSelected(designer)) {
- <div class="designer-spaces-info">
- <span class="spaces-label">负责空间:</span>
- <span class="spaces-value">{{ getDesignerSpacesText(designer.id) }}</span>
- </div>
- }
- </div>
- }
- </div>
- </div>
- }
- <!-- 所有团队成员 -->
- <div class="all-members-section">
- <h4>所有团队成员</h4>
- <div class="designer-grid">
- @for (designer of getSelectedTeam()?.members; track designer.id) {
- <div
- class="designer-card"
- [class.selected]="isDesignerSelected(designer)"
- [class.busy]="designer.status === 'busy'"
- [class.reviewing]="designer.status === 'reviewing'"
- (click)="toggleDesignerSelection(designer)"
- >
- <div class="designer-avatar">
- @if (designer.avatar) {
- <img [src]="designer.avatar" [alt]="designer.name">
- } @else {
- <div class="avatar-placeholder">{{ designer.name.charAt(0) }}</div>
- }
- <div class="status-dot" [style.background-color]="getDesignerStatusColor(designer.status)"></div>
- </div>
-
- <div class="designer-info">
- <div class="designer-name">
- {{ designer.name }}
- @if (designer.isTeamLeader) {
- <span class="leader-badge">组长</span>
- }
- </div>
-
- <div class="designer-status">
- <span class="status-text" [style.color]="getDesignerStatusColor(designer.status)">
- {{ getDesignerStatusText(designer.status) }}
- </span>
- <span class="workload" [class]="getWorkloadClass(designer.workload)">
- {{ designer.workload }}%
- </span>
- </div>
- <div class="designer-metrics">
- <div class="metric-item" [class]="getIdleDaysClass(designer.idleDays)">
- <span class="metric-label">{{ getRecentOrdersText(designer) }}</span>
- </div>
-
- @if (designer.availableDates.length > 0) {
- <div class="metric-item">
- <span class="metric-label">{{ getAvailableDatesText(designer) }}</span>
- </div>
- }
- @if (designer.reviewDates.length > 0) {
- <div class="metric-item review-dates">
- <span class="metric-label">对图日期:{{ designer.reviewDates.join(', ') }}</span>
- </div>
- }
- </div>
- <div class="designer-skills">
- @for (skill of designer.skills.slice(0, 2); track skill) {
- <span class="skill-tag">{{ skill }}</span>
- }
- @if (designer.skills.length > 2) {
- <span class="skill-more">+{{ designer.skills.length - 2 }}</span>
- }
- </div>
- </div>
- <div class="designer-actions">
- <button
- class="calendar-btn"
- (click)="$event.stopPropagation(); showDesignerEmployeeDetail(designer)"
- title="查看设计师详情"
- >
- 👤 详情
- </button>
- @if (enableSpaceAssignment && spaceScenes.length > 0) {
- <button
- class="space-assign-btn"
- (click)="$event.stopPropagation(); openSpaceAssignment(designer)"
- title="分配空间"
- >
- 🏠
- </button>
- }
- </div>
- @if (enableSpaceAssignment && isDesignerSelected(designer)) {
- <div class="designer-spaces-info">
- <span class="spaces-label">负责空间:</span>
- <span class="spaces-value">{{ getDesignerSpacesText(designer.id) }}</span>
- </div>
- }
- </div>
- }
- </div>
- </div>
- <!-- 跨组合作选项 -->
- <div class="cross-team-section">
- <div class="cross-team-header">
- <label class="checkbox-label">
- <input
- type="checkbox"
- [(ngModel)]="allowCrossTeamSelection"
- >
- <span class="checkmark"></span>
- 允许跨组合作
- </label>
- <span class="cross-team-hint">可从其他项目组选择成员参与协作</span>
- </div>
- @if (allowCrossTeamSelection) {
- <div class="cross-team-designers">
- <h4>其他项目组成员</h4>
- <div class="designer-grid">
- @for (designer of getOtherTeamDesigners(); track designer.id) {
- <div
- class="designer-card cross-team"
- [class.selected]="isCrossTeamCollaborator(designer)"
- (click)="toggleCrossTeamCollaborator(designer)"
- >
- <div class="designer-avatar">
- @if (designer.avatar) {
- <img [src]="designer.avatar" [alt]="designer.name">
- } @else {
- <div class="avatar-placeholder">{{ designer.name.charAt(0) }}</div>
- }
- <div class="status-dot" [style.background-color]="getDesignerStatusColor(designer.status)"></div>
- </div>
-
- <div class="designer-info">
- <div class="designer-name">
- {{ designer.name }}
- <span class="team-tag">{{ designer.teamName }}</span>
- </div>
-
- <div class="designer-status">
- <span class="status-text" [style.color]="getDesignerStatusColor(designer.status)">
- {{ getDesignerStatusText(designer.status) }}
- </span>
- <span class="workload" [class]="getWorkloadClass(designer.workload)">
- {{ designer.workload }}%
- </span>
- </div>
- <div class="designer-metrics">
- <div class="metric-item" [class]="getIdleDaysClass(designer.idleDays)">
- <span class="metric-label">{{ getRecentOrdersText(designer) }}</span>
- </div>
- </div>
- </div>
- <div class="designer-actions">
- <button
- class="calendar-btn"
- (click)="$event.stopPropagation(); showDesignerEmployeeDetail(designer)"
- title="查看设计师详情"
- >
- 👤 详情
- </button>
- </div>
- </div>
- }
- </div>
- </div>
- }
- </div>
- </div>
- }
- </div>
- <div class="modal-footer">
- <div class="selection-summary">
- @if (selectedDesigners.length > 0) {
- <div class="summary-item">
- <span class="summary-label">主要团队:</span>
- <span class="summary-value">{{ getSelectedDesignersNames() }}</span>
- </div>
- }
- @if (internalCrossTeamCollaborators.length > 0) {
- <div class="summary-item">
- <span class="summary-label">跨组合作:</span>
- <span class="summary-value">{{ getCrossTeamCollaboratorsNames() }}</span>
- </div>
- }
- </div>
-
- <div class="modal-actions">
- <button class="btn-secondary" (click)="closeModal()">取消</button>
- <button
- class="btn-primary"
- [disabled]="!canConfirmAssignment()"
- (click)="confirmAssignment()"
- >
- 确认分配
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- 设计师日历弹窗 -->
- @if (showDesignerCalendar) {
- <div class="designer-calendar-modal">
- <div class="modal-mask" (click)="closeDesignerCalendar()"></div>
- <div class="modal-content">
- <h3>设计师工作日历</h3>
- <button class="close-btn" (click)="closeDesignerCalendar()">关闭</button>
- <!-- 统一使用控制流指令:@if 与 @for -->
- @if (selectedCalendarDesigners.length > 0) {
- <app-designer-calendar
- [designers]="selectedCalendarDesigners">
- </app-designer-calendar>
- } @else {
- <div class="empty">暂无数据</div>
- }
- </div>
- </div>
- }
- <!-- 设计师详细日历弹窗 -->
- @if (showDesignerCalendar && selectedDesignerForCalendar) {
- <div class="calendar-modal-overlay" (click)="closeDesignerCalendar()">
- <div class="calendar-modal-container" (click)="$event.stopPropagation()">
- <div class="calendar-modal-header">
- <h3>{{ selectedDesignerForCalendar.name }} - 详细日历</h3>
- <button class="close-btn" (click)="closeDesignerCalendar()">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <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="calendar-modal-body">
- <app-designer-calendar
- [designers]="selectedCalendarDesigners"
- [showSingleDesigner]="true"
- [timeRange]="calendarViewMode"
- ></app-designer-calendar>
- </div>
- </div>
- </div>
- }
- <!-- 空间分配弹窗 -->
- @if (selectedDesignerForSpaceAssignment) {
- <div class="space-assignment-overlay" (click)="closeSpaceAssignment()">
- <div class="space-assignment-container" (click)="$event.stopPropagation()">
- <div class="space-assignment-header">
- <div class="designer-preview">
- <div class="designer-avatar">
- @if (selectedDesignerForSpaceAssignment.avatar) {
- <img [src]="selectedDesignerForSpaceAssignment.avatar"
- [alt]="selectedDesignerForSpaceAssignment.name">
- } @else {
- <div class="avatar-placeholder">
- {{ selectedDesignerForSpaceAssignment.name.charAt(0) }}
- </div>
- }
- </div>
- <div class="designer-name">{{ selectedDesignerForSpaceAssignment.name }}</div>
- </div>
- <button class="close-btn" (click)="closeSpaceAssignment()">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <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="modal-content">
- <div class="space-selection-section">
- <h4 class="form-label">
- 指派空间场景
- <span class="required">*</span>
- </h4>
- <p class="form-help">请选择该设计师负责的空间(从Product表加载)</p>
-
- <!-- 加载状态 -->
- @if (loadingSpaces) {
- <div class="space-loading">
- <div class="spinner"></div>
- <span>正在加载空间数据...</span>
- </div>
- }
-
- <!-- 加载错误 -->
- @if (spaceLoadError && !loadingSpaces) {
- <div class="space-error">
- <svg class="icon-warning" viewBox="0 0 24 24">
- <path fill="currentColor" d="M12 2L1 21h22L12 2zm0 3.5L19.5 19h-15L12 5.5zM11 10v4h2v-4h-2zm0 6v2h2v-2h-2z"/>
- </svg>
- <span>{{ spaceLoadError }}</span>
- </div>
- }
-
- <!-- 空间列表 -->
- @if (!loadingSpaces && !spaceLoadError) {
- <div class="space-checkbox-list">
- @if (spaceScenes.length === 0) {
- <div class="space-empty">
- <svg class="icon-empty" viewBox="0 0 24 24">
- <path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/>
- </svg>
- <p>该项目暂无空间数据</p>
- <small>请先在项目中创建空间产品(Product)</small>
- </div>
- } @else {
- @for (space of spaceScenes; track space.id) {
- <label class="space-checkbox-item">
- <input
- type="checkbox"
- [checked]="isSpaceSelected(selectedDesignerForSpaceAssignment.id, space.id)"
- (change)="toggleSpaceSelection(selectedDesignerForSpaceAssignment.id, space.id)"
- >
- <span class="checkbox-custom"></span>
- <div class="space-info">
- <span class="space-name">{{ space.name }}</span>
- @if (space.area) {
- <span class="space-area">{{ space.area }}㎡</span>
- }
- @if (space.description) {
- <span class="space-desc">{{ space.description }}</span>
- }
- </div>
- </label>
- }
- }
- </div>
- }
- </div>
- </div>
- <div class="space-assignment-footer">
- <button class="btn-secondary" (click)="closeSpaceAssignment()">取消</button>
- <button class="btn-primary" (click)="closeSpaceAssignment()">确认</button>
- </div>
- </div>
- </div>
- }
- <!-- 员工详情面板(复用组长端) -->
- @if (showEmployeeDetailPanel && employeeDetailData) {
- <app-employee-detail-panel
- [visible]="true"
- [employeeDetail]="employeeDetailData"
- (close)="closeEmployeeDetailPanel()"
- (projectClick)="onEmployeeDetailProjectClick($event)">
- </app-employee-detail-panel>
- }
|