|
|
@@ -0,0 +1,814 @@
|
|
|
+<!-- 员工信息侧边栏面板 -->
|
|
|
+@if (visible && employee) {
|
|
|
+ <div class="employee-info-overlay" (click)="onClose()">
|
|
|
+ <div class="employee-info-panel" (click)="stopPropagation($event)">
|
|
|
+
|
|
|
+ <!-- 面板头部 -->
|
|
|
+ <div class="panel-header">
|
|
|
+ <div class="header-top">
|
|
|
+ <h3 class="panel-title">
|
|
|
+ <img
|
|
|
+ [src]="employee.avatar || '/assets/images/default-avatar.svg'"
|
|
|
+ class="employee-avatar-small"
|
|
|
+ alt="员工头像"
|
|
|
+ />
|
|
|
+ <div class="title-content">
|
|
|
+ <span class="employee-name">{{ employee.realname || employee.name }}</span>
|
|
|
+ <span class="employee-role-badge" [attr.data-role]="employee.roleName">
|
|
|
+ {{ employee.roleName }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </h3>
|
|
|
+ <button class="btn-close" (click)="onClose()" title="关闭">
|
|
|
+ <svg 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="panel-tabs">
|
|
|
+ <button
|
|
|
+ class="tab-button"
|
|
|
+ [class.active]="activeTab === 'basic'"
|
|
|
+ (click)="switchTab('basic')">
|
|
|
+ <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ 基本信息
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="tab-button"
|
|
|
+ [class.active]="activeTab === 'workload'"
|
|
|
+ (click)="switchTab('workload')">
|
|
|
+ <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
|
+ <line x1="9" y1="9" x2="15" y2="9"></line>
|
|
|
+ <line x1="9" y1="15" x2="15" y2="15"></line>
|
|
|
+ </svg>
|
|
|
+ 项目负载
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 面板内容 -->
|
|
|
+ <div class="panel-content">
|
|
|
+
|
|
|
+ <!-- ========== 基本信息标签页 ========== -->
|
|
|
+ @if (activeTab === 'basic') {
|
|
|
+ <div class="tab-content basic-tab">
|
|
|
+
|
|
|
+ <!-- 查看模式 -->
|
|
|
+ @if (!editMode) {
|
|
|
+ <div class="view-mode">
|
|
|
+ <!-- 员工头像与基本信息 -->
|
|
|
+ <div class="detail-header">
|
|
|
+ <div class="detail-avatar-section">
|
|
|
+ <img [src]="employee.avatar || '/assets/images/default-avatar.svg'" class="detail-avatar" alt="员工头像"/>
|
|
|
+ <div class="detail-badge-container">
|
|
|
+ <span class="detail-role-badge">{{ employee.roleName }}</span>
|
|
|
+ <span [class]="'detail-status-badge ' + (employee.isDisabled ? 'disabled' : 'active')">
|
|
|
+ {{ employee.isDisabled ? '已禁用' : '在职' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="detail-info-section">
|
|
|
+ <div class="detail-name-block">
|
|
|
+ <h3 class="detail-realname">{{ employee.realname || employee.name }}</h3>
|
|
|
+ <span class="detail-nickname" *ngIf="employee.realname && employee.name">
|
|
|
+ 昵称: {{ employee.name }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-position" *ngIf="employee.position">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
|
+ <line x1="9" y1="9" x2="15" y2="9"></line>
|
|
|
+ </svg>
|
|
|
+ {{ employee.position }}
|
|
|
+ </div>
|
|
|
+ <div class="detail-meta">
|
|
|
+ <div class="detail-meta-item" *ngIf="employee.gender">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ </svg>
|
|
|
+ {{ employee.gender === '1' ? '男' : employee.gender === '2' ? '女' : employee.gender }}
|
|
|
+ </div>
|
|
|
+ <div class="detail-meta-item" *ngIf="employee.joinDate">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ 入职 {{ employee.joinDate }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 联系方式 -->
|
|
|
+ <div class="detail-section">
|
|
|
+ <div class="detail-section-title">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ 联系方式
|
|
|
+ </div>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <label>手机号</label>
|
|
|
+ <div class="detail-value">{{ employee.mobile || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <label>邮箱</label>
|
|
|
+ <div class="detail-value">{{ employee.email || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <label>企微ID</label>
|
|
|
+ <div class="detail-value">{{ employee.userid || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 组织信息 -->
|
|
|
+ <div class="detail-section">
|
|
|
+ <div class="detail-section-title">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ 组织信息
|
|
|
+ </div>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <label>身份</label>
|
|
|
+ <div class="detail-value">
|
|
|
+ <span class="badge">{{ employee.roleName }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <label>部门</label>
|
|
|
+ <div class="detail-value">
|
|
|
+ @if(employee.roleName === "客服") {
|
|
|
+ 客服部
|
|
|
+ } @else if(employee.roleName === "管理员") {
|
|
|
+ 总部
|
|
|
+ } @else {
|
|
|
+ {{ employee.department || '-' }}
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item" *ngIf="employee.level">
|
|
|
+ <label>职级</label>
|
|
|
+ <div class="detail-value">{{ employee.level }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 技能标签 -->
|
|
|
+ @if (employee.skills && employee.skills.length > 0) {
|
|
|
+ <div class="detail-section">
|
|
|
+ <div class="detail-section-title">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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="skill-tags">
|
|
|
+ <span class="skill-tag" *ngFor="let skill of employee.skills">{{ skill }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 工作量统计 -->
|
|
|
+ @if (employee.workload) {
|
|
|
+ <div class="detail-section">
|
|
|
+ <div class="detail-section-title">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="1" x2="12" y2="23"></line>
|
|
|
+ <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
|
|
+ </svg>
|
|
|
+ 工作量统计
|
|
|
+ </div>
|
|
|
+ <div class="workload-grid">
|
|
|
+ <div class="workload-item">
|
|
|
+ <label>当前项目</label>
|
|
|
+ <div class="workload-value">{{ employee.workload.currentProjects || 0 }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="workload-item">
|
|
|
+ <label>已完成</label>
|
|
|
+ <div class="workload-value">{{ employee.workload.completedProjects || 0 }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="workload-item">
|
|
|
+ <label>平均质量</label>
|
|
|
+ <div class="workload-value">{{ employee.workload.averageQuality || 0 }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 编辑按钮 -->
|
|
|
+ <div class="action-bar">
|
|
|
+ <button class="btn btn-primary" (click)="enterEditMode()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
|
|
+ </svg>
|
|
|
+ 编辑基本信息
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 编辑模式 -->
|
|
|
+ @if (editMode) {
|
|
|
+ <div class="edit-mode">
|
|
|
+ <!-- 员工头像 -->
|
|
|
+ <div class="form-avatar-section">
|
|
|
+ <img [src]="employee.avatar || '/assets/images/default-avatar.svg'" class="form-avatar" alt="员工头像"/>
|
|
|
+ <div class="form-avatar-info">
|
|
|
+ <div class="form-avatar-name">{{ employee.name }}</div>
|
|
|
+ <div class="form-avatar-id">ID: {{ employee.userid || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 基本信息 -->
|
|
|
+ <div class="form-section">
|
|
|
+ <div class="form-section-title">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ 基本信息
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label required">真实姓名</label>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ class="form-input"
|
|
|
+ [(ngModel)]="formModel.realname"
|
|
|
+ placeholder="请输入真实姓名(用于正式场合)"
|
|
|
+ />
|
|
|
+ <div class="form-hint">用于正式文档、合同签署等场合</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label required">昵称</label>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ class="form-input"
|
|
|
+ [(ngModel)]="formModel.name"
|
|
|
+ placeholder="请输入昵称(内部沟通用)"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ <div class="form-hint">用于日常沟通,可以是昵称、花名等</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label required">手机号</label>
|
|
|
+ <input
|
|
|
+ type="tel"
|
|
|
+ class="form-input"
|
|
|
+ [(ngModel)]="formModel.mobile"
|
|
|
+ placeholder="请输入手机号"
|
|
|
+ maxlength="11"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">企微ID</label>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ class="form-input"
|
|
|
+ [(ngModel)]="formModel.userid"
|
|
|
+ placeholder="企业微信用户ID"
|
|
|
+ readonly
|
|
|
+ disabled
|
|
|
+ />
|
|
|
+ <div class="form-hint">企微ID由系统同步,不可修改</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 职位信息 -->
|
|
|
+ <div class="form-section">
|
|
|
+ <div class="form-section-title">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
|
+ <line x1="9" y1="9" x2="15" y2="9"></line>
|
|
|
+ </svg>
|
|
|
+ 职位信息
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label required">身份</label>
|
|
|
+ <select class="form-select" [(ngModel)]="formModel.roleName" required>
|
|
|
+ <option value="">请选择身份</option>
|
|
|
+ <option *ngFor="let role of roles" [value]="role">{{ role }}</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">部门</label>
|
|
|
+ <select class="form-select" [(ngModel)]="formModel.departmentId">
|
|
|
+ <option [value]="undefined">未分配</option>
|
|
|
+ <option *ngFor="let dept of departments" [value]="dept.id">{{ dept.name }}</option>
|
|
|
+ </select>
|
|
|
+ <div class="form-hint">客服和管理员无需分配项目组</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 状态管理 -->
|
|
|
+ <div class="form-section">
|
|
|
+ <div class="form-section-title">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2a10 10 0 1 0 0 20 10 10 0 1 0 0-20z"></path>
|
|
|
+ <path d="M12 6v6l4 2"></path>
|
|
|
+ </svg>
|
|
|
+ 状态管理
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">员工状态</label>
|
|
|
+ <div class="status-toggle">
|
|
|
+ <label class="status-option" [class.active]="!formModel.isDisabled">
|
|
|
+ <input
|
|
|
+ type="radio"
|
|
|
+ name="status"
|
|
|
+ [value]="false"
|
|
|
+ [(ngModel)]="formModel.isDisabled"
|
|
|
+ />
|
|
|
+ <span class="status-dot active"></span>
|
|
|
+ <span>正常</span>
|
|
|
+ </label>
|
|
|
+ <label class="status-option" [class.active]="formModel.isDisabled">
|
|
|
+ <input
|
|
|
+ type="radio"
|
|
|
+ name="status"
|
|
|
+ [value]="true"
|
|
|
+ [(ngModel)]="formModel.isDisabled"
|
|
|
+ />
|
|
|
+ <span class="status-dot disabled"></span>
|
|
|
+ <span>已禁用</span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ <div class="form-hint">禁用后该员工将无法登录系统</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 提示信息 -->
|
|
|
+ <div class="form-notice">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <line x1="12" y1="16" x2="12" y2="12"></line>
|
|
|
+ <line x1="12" y1="8" x2="12.01" y2="8"></line>
|
|
|
+ </svg>
|
|
|
+ <div>
|
|
|
+ <div class="form-notice-title">修改说明</div>
|
|
|
+ <div class="form-notice-text">员工数据主要从企业微信同步,姓名和手机号可以在此修改,修改后将保存到后端数据库。</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 按钮组 -->
|
|
|
+ <div class="action-bar-horizontal">
|
|
|
+ <button class="btn btn-default" (click)="cancelEdit()">取消</button>
|
|
|
+ <button class="btn btn-primary" (click)="submitUpdate()">保存更新</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- ========== 项目负载标签页 ========== -->
|
|
|
+ @if (activeTab === 'workload') {
|
|
|
+ <div class="tab-content workload-tab">
|
|
|
+
|
|
|
+ <!-- 负载概况栏 -->
|
|
|
+ <div class="section workload-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
|
+ <line x1="9" y1="9" x2="15" y2="9"></line>
|
|
|
+ <line x1="9" y1="15" x2="15" y2="15"></line>
|
|
|
+ </svg>
|
|
|
+ <h4>负载概况</h4>
|
|
|
+ </div>
|
|
|
+ <div class="workload-info">
|
|
|
+ <div class="workload-stat">
|
|
|
+ <span class="stat-label">当前负责项目数:</span>
|
|
|
+ <span class="stat-value" [class]="(employee.currentProjects || 0) >= 3 ? 'high-workload' : 'normal-workload'">
|
|
|
+ {{ employee.currentProjects || 0 }} 个
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ @if (employee.projectData && employee.projectData.length > 0) {
|
|
|
+ <div class="project-list">
|
|
|
+ <span class="project-label">核心项目:</span>
|
|
|
+ <div class="project-tags">
|
|
|
+ @for (project of employee.projectData; track project.id) {
|
|
|
+ <span class="project-tag clickable"
|
|
|
+ (click)="onProjectClick(project.id)"
|
|
|
+ title="点击查看项目详情">
|
|
|
+ {{ project.name }}
|
|
|
+ <svg class="icon-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M7 17L17 7M17 7H7M17 7V17"/>
|
|
|
+ </svg>
|
|
|
+ </span>
|
|
|
+ }
|
|
|
+ @if ((employee.currentProjects || 0) > employee.projectData.length) {
|
|
|
+ <span class="project-tag more">+{{ (employee.currentProjects || 0) - employee.projectData.length }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 负载详细日历 -->
|
|
|
+ <div class="section calendar-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ <h4>负载详细日历</h4>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (employee.calendarData) {
|
|
|
+ <div class="employee-calendar">
|
|
|
+ <!-- 月份标题 -->
|
|
|
+ <div class="calendar-month-header">
|
|
|
+ <button class="btn-prev-month"
|
|
|
+ (click)="onChangeMonth(-1)"
|
|
|
+ title="上月">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"></polyline>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <span class="month-title">
|
|
|
+ {{ employee.calendarData.currentMonth | date:'yyyy年M月' }}
|
|
|
+ </span>
|
|
|
+ <button class="btn-next-month"
|
|
|
+ (click)="onChangeMonth(1)"
|
|
|
+ title="下月">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="9 18 15 12 9 6"></polyline>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 星期标题 -->
|
|
|
+ <div class="calendar-weekdays">
|
|
|
+ <div class="weekday">日</div>
|
|
|
+ <div class="weekday">一</div>
|
|
|
+ <div class="weekday">二</div>
|
|
|
+ <div class="weekday">三</div>
|
|
|
+ <div class="weekday">四</div>
|
|
|
+ <div class="weekday">五</div>
|
|
|
+ <div class="weekday">六</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 日历网格 -->
|
|
|
+ <div class="calendar-grid">
|
|
|
+ @for (day of employee.calendarData.days; track day.date.getTime()) {
|
|
|
+ <div class="calendar-day"
|
|
|
+ [class.today]="day.isToday"
|
|
|
+ [class.other-month]="!day.isCurrentMonth"
|
|
|
+ [class.has-projects]="day.projectCount > 0"
|
|
|
+ [class.clickable]="day.projectCount > 0 && day.isCurrentMonth"
|
|
|
+ (click)="onCalendarDayClick(day)">
|
|
|
+ <div class="day-number">{{ day.date.getDate() }}</div>
|
|
|
+ @if (day.projectCount > 0) {
|
|
|
+ <div class="day-badge" [class.high-load]="day.projectCount >= 2">
|
|
|
+ {{ day.projectCount }}个项目
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图例 -->
|
|
|
+ <div class="calendar-legend">
|
|
|
+ <div class="legend-item">
|
|
|
+ <span class="legend-dot today-dot"></span>
|
|
|
+ <span class="legend-text">今天</span>
|
|
|
+ </div>
|
|
|
+ <div class="legend-item">
|
|
|
+ <span class="legend-dot project-dot"></span>
|
|
|
+ <span class="legend-text">有项目</span>
|
|
|
+ </div>
|
|
|
+ <div class="legend-item">
|
|
|
+ <span class="legend-dot high-dot"></span>
|
|
|
+ <span class="legend-text">高负载</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 请假明细栏 -->
|
|
|
+ <div class="section leave-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ <h4>请假明细(未来7天)</h4>
|
|
|
+ </div>
|
|
|
+ <div class="leave-table">
|
|
|
+ @if (employee.leaveRecords && employee.leaveRecords.length > 0) {
|
|
|
+ <table>
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>日期</th>
|
|
|
+ <th>状态</th>
|
|
|
+ <th>备注</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ @for (record of employee.leaveRecords; track record.id) {
|
|
|
+ <tr [class]="record.isLeave ? 'leave-day' : 'work-day'">
|
|
|
+ <td>{{ record.date | date:'M月d日' }}</td>
|
|
|
+ <td>
|
|
|
+ <span class="status-badge" [class]="record.isLeave ? 'leave' : 'work'">
|
|
|
+ {{ record.isLeave ? '请假' : '正常' }}
|
|
|
+ </span>
|
|
|
+ </td>
|
|
|
+ <td>{{ record.isLeave ? getLeaveTypeText(record.leaveType) : '-' }}</td>
|
|
|
+ </tr>
|
|
|
+ }
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ } @else {
|
|
|
+ <div class="no-leave">
|
|
|
+ <svg class="no-data-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
|
|
|
+ <line x1="9" y1="9" x2="9.01" y2="9"></line>
|
|
|
+ <line x1="15" y1="9" x2="15.01" y2="9"></line>
|
|
|
+ </svg>
|
|
|
+ <p>未来7天无请假安排</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 红色标记说明 -->
|
|
|
+ @if (employee.redMarkExplanation) {
|
|
|
+ <div class="section explanation-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ <h4>红色标记说明</h4>
|
|
|
+ </div>
|
|
|
+ <div class="explanation-content">
|
|
|
+ <p class="explanation-text">{{ employee.redMarkExplanation }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 能力问卷 -->
|
|
|
+ <div class="section survey-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3"/>
|
|
|
+ </svg>
|
|
|
+ <h4>能力问卷</h4>
|
|
|
+ <button
|
|
|
+ class="btn-refresh-survey"
|
|
|
+ (click)="onRefreshSurvey()"
|
|
|
+ [disabled]="refreshingSurvey"
|
|
|
+ title="刷新问卷状态">
|
|
|
+ <svg viewBox="0 0 24 24" width="16" height="16" [class.rotating]="refreshingSurvey">
|
|
|
+ <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 (employee.surveyCompleted && employee.surveyData) {
|
|
|
+ <div class="survey-content">
|
|
|
+ <div class="survey-status completed">
|
|
|
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="#34c759">
|
|
|
+ <path d="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z"/>
|
|
|
+ </svg>
|
|
|
+ <span>已完成问卷</span>
|
|
|
+ <span class="survey-time">
|
|
|
+ {{ employee.surveyData.createdAt | date:'yyyy-MM-dd HH:mm' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 能力画像摘要 -->
|
|
|
+ @if (!showFullSurvey) {
|
|
|
+ <div class="capability-summary">
|
|
|
+ <h5>能力画像</h5>
|
|
|
+ @if (getCapabilitySummary(employee.surveyData.answers); as summary) {
|
|
|
+ <div class="summary-grid">
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">擅长风格:</span>
|
|
|
+ <span class="value">{{ summary.styles }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">擅长空间:</span>
|
|
|
+ <span class="value">{{ summary.spaces }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">技术优势:</span>
|
|
|
+ <span class="value">{{ summary.advantages }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">项目难度:</span>
|
|
|
+ <span class="value">{{ summary.difficulty }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">周承接量:</span>
|
|
|
+ <span class="value">{{ summary.capacity }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">紧急订单:</span>
|
|
|
+ <span class="value">
|
|
|
+ {{ summary.urgent }}
|
|
|
+ @if (summary.urgentLimit) {
|
|
|
+ <span class="limit-hint">(每月不超过{{summary.urgentLimit}}次)</span>
|
|
|
+ }
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">进度同步:</span>
|
|
|
+ <span class="value">{{ summary.feedback }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-item">
|
|
|
+ <span class="label">沟通方式:</span>
|
|
|
+ <span class="value">{{ summary.communication }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <button class="btn-view-full" (click)="toggleSurveyDisplay()">
|
|
|
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
|
+ <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
|
|
+ </svg>
|
|
|
+ 查看完整问卷(共 {{ employee.surveyData.answers.length }} 道题)
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 完整问卷答案 -->
|
|
|
+ @if (showFullSurvey) {
|
|
|
+ <div class="survey-answers">
|
|
|
+ <h5>完整问卷答案(共 {{ employee.surveyData.answers.length }} 道题):</h5>
|
|
|
+ @for (answer of employee.surveyData.answers; track $index) {
|
|
|
+ <div class="answer-item">
|
|
|
+ <div class="question-text">
|
|
|
+ <strong>Q{{$index + 1}}:</strong> {{ answer.question }}
|
|
|
+ </div>
|
|
|
+ <div class="answer-text">
|
|
|
+ @if (!answer.answer) {
|
|
|
+ <span class="answer-tag empty">未填写(选填)</span>
|
|
|
+ } @else if (answer.type === 'single' || answer.type === 'text' || answer.type === 'textarea' || answer.type === 'number') {
|
|
|
+ <span class="answer-tag single">{{ answer.answer }}</span>
|
|
|
+ } @else if (answer.type === 'multiple') {
|
|
|
+ @if (answer.answer && answer.answer.length) {
|
|
|
+ @for (opt of answer.answer; track opt) {
|
|
|
+ <span class="answer-tag multiple">{{ opt }}</span>
|
|
|
+ }
|
|
|
+ } @else {
|
|
|
+ <span class="answer-tag single">{{ answer.answer }}</span>
|
|
|
+ }
|
|
|
+ } @else if (answer.type === 'scale') {
|
|
|
+ <div class="answer-scale">
|
|
|
+ <div class="scale-bar">
|
|
|
+ <div class="scale-fill" [style.width.%]="(answer.answer / 10) * 100">
|
|
|
+ <span>{{ answer.answer }} / 10</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <span class="answer-tag single">{{ answer.answer }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <button class="btn-collapse" (click)="toggleSurveyDisplay()">
|
|
|
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
|
+ <path d="M19 13H5v-2h14v2z"/>
|
|
|
+ </svg>
|
|
|
+ 收起详情
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ } @else {
|
|
|
+ <div class="survey-empty">
|
|
|
+ <svg class="no-data-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <path d="M8 12h8M12 8v8"/>
|
|
|
+ </svg>
|
|
|
+ <p>该员工尚未完成能力问卷</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+}
|
|
|
+
|
|
|
+<!-- 日历项目列表弹窗 -->
|
|
|
+@if (showCalendarProjectList) {
|
|
|
+ <div class="calendar-project-modal-overlay" (click)="closeCalendarProjectList()">
|
|
|
+ <div class="calendar-project-modal" (click)="stopPropagation($event)">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>
|
|
|
+ <svg class="header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ {{ selectedDate | date:'M月d日' }} 的项目
|
|
|
+ </h3>
|
|
|
+ <button class="btn-close" (click)="closeCalendarProjectList()">
|
|
|
+ <svg 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="project-count-info">
|
|
|
+ 共 <strong>{{ selectedDayProjects.length }}</strong> 个项目
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="project-list">
|
|
|
+ @for (project of selectedDayProjects; track project.id) {
|
|
|
+ <div class="project-item" (click)="onProjectClick(project.id)">
|
|
|
+ <div class="project-info">
|
|
|
+ <svg class="project-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
|
|
+ <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
|
|
+ </svg>
|
|
|
+ <div class="project-details">
|
|
|
+ <h4 class="project-name">{{ project.name }}</h4>
|
|
|
+ @if (project.deadline) {
|
|
|
+ <p class="project-deadline">
|
|
|
+ 截止日期: {{ project.deadline | date:'yyyy-MM-dd' }}
|
|
|
+ </p>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <svg class="arrow-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M5 12h14M12 5l7 7-7 7"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+}
|
|
|
+
|
|
|
+<!-- 设计师详细日历 -->
|
|
|
+@if (showDesignerCalendar) {
|
|
|
+ <div class="calendar-project-modal-overlay" (click)="closeDesignerCalendar()">
|
|
|
+ <div class="calendar-project-modal large" (click)="stopPropagation($event)">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>
|
|
|
+ <svg class="header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <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>
|
|
|
+ 设计师工作日历
|
|
|
+ </h3>
|
|
|
+ <button class="btn-close" (click)="closeDesignerCalendar()">
|
|
|
+ <svg 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">
|
|
|
+ <app-designer-calendar
|
|
|
+ [designers]="calendarDesigners"
|
|
|
+ [showSingleDesigner]="true"
|
|
|
+ [timeRange]="calendarViewMode">
|
|
|
+ </app-designer-calendar>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+}
|
|
|
+
|