徐福静0235668 1 день назад
Родитель
Сommit
9f8bf9e375

+ 2 - 2
src/app/app.routes.ts

@@ -39,7 +39,7 @@ import { HrLayout } from './pages/hr/hr-layout/hr-layout';
 import { Dashboard as HrDashboard } from './pages/hr/dashboard/dashboard';
 import { EmployeeRecords } from './pages/hr/employee-records/employee-records';
 import { Attendance } from './pages/hr/attendance/attendance';
-import { Assets } from './pages/hr/assets/assets';
+
 import { DesignerProfile } from './pages/hr/designer-profile/designer-profile';
 
 // 管理员页面
@@ -121,7 +121,7 @@ export const routes: Routes = [
       { path: 'dashboard', component: HrDashboard, title: '人事看板' },
       { path: 'employee-records', component: EmployeeRecords, title: '花名册与档案库' },
       { path: 'attendance', component: Attendance, title: '考勤统计' },
-      { path: 'assets', component: Assets, title: '资产管理' },
+
       { path: 'designer-profile/:id', component: DesignerProfile, title: '设计师详情' }
     ]
   },

+ 1 - 1
src/app/pages/auth/login/login.ts

@@ -40,7 +40,7 @@ export class LoginPage {
       'designer': '/designer/dashboard',
       'team-leader': '/team-leader/dashboard',
       'finance': '/finance/dashboard',
-      'hr': '/hr/assets',
+      'hr': '/hr/dashboard',
       'admin': '/admin/dashboard'
     };
 

+ 0 - 429
src/app/pages/hr/assets/assets-stats.html

@@ -1,429 +0,0 @@
-<div class="assets-stats-container">
-  <header class="page-header">
-    <h1>资产管理与统计</h1>
-    <p class="page-description">管理企业全类型资产,查看资产使用状态和统计数据</p>
-  </header>
-
-  <!-- 资产分类筛选栏 -->
-  <div class="filter-bar">
-    <div class="search-container">
-      <mat-icon class="search-icon">search</mat-icon>
-      <input 
-        matInput 
-        placeholder="搜索资产名称、序列号、使用人..."
-        [value]="searchTerm()"
-        (input)="searchTerm.set($event.target.value)"
-        class="search-input"
-      >
-      @if (searchTerm()) {
-        <button mat-icon-button (click)="searchTerm.set('')" class="clear-search">
-          <mat-icon>close</mat-icon>
-        </button>
-      }
-    </div>
-    
-    <div class="filter-controls">
-      <div class="filter-group">
-        <label>资产类型:</label>
-        <select [value]="typeFilter()" (change)="typeFilter.set($any($event.target).value)" class="filter-select">
-          <option value="">全部类型</option>
-          @for (type of assetTypes(); track type) {
-            <option [value]="type">{{ type }}</option>
-          }
-        </select>
-      </div>
-      
-      <div class="filter-group">
-        <label>资产状态:</label>
-        <select [value]="statusFilter()" (change)="statusFilter.set($any($event.target).value)" class="filter-select">
-          <option value="">全部状态</option>
-          <option value="空闲">空闲</option>
-          <option value="占用">占用</option>
-          <option value="故障">故障</option>
-          <option value="报修中">报修中</option>
-        </select>
-      </div>
-      
-      <div class="filter-group">
-        <label>所属部门:</label>
-        <select [value]="departmentFilter()" (change)="departmentFilter.set($any($event.target).value)" class="filter-select">
-          <option value="">全部部门</option>
-          @for (dept of departments(); track dept) {
-            <option [value]="dept">{{ dept }}</option>
-          }
-        </select>
-      </div>
-      
-      <button mat-button color="primary" (click)="resetFilters()" class="reset-btn">
-        <mat-icon>refresh</mat-icon>
-        重置筛选
-      </button>
-      
-      <button mat-raised-button color="primary" (click)="exportAssetLedger()" class="export-btn">
-        <mat-icon>file_download</mat-icon>
-        导出台账
-      </button>
-    </div>
-  </div>
-
-  <!-- 视图切换和统计卡片 -->
-  <div class="view-stats-section">
-    <div class="view-toggle">
-      <button 
-        mat-button 
-        [class.active]="selectedView() === 'grid'"
-        (click)="switchView('grid')"
-        class="view-btn"
-      >
-        <mat-icon>grid_view</mat-icon>
-        卡片视图
-      </button>
-      <button 
-        mat-button 
-        [class.active]="selectedView() === 'list'"
-        (click)="switchView('list')"
-        class="view-btn"
-      >
-        <mat-icon>list</mat-icon>
-        列表视图
-      </button>
-      <div class="asset-count">
-        共 {{ filteredAssets().length }} 项资产
-      </div>
-    </div>
-    
-    <div class="stats-cards">
-      <div class="stat-card">
-        <div class="stat-icon">
-          <mat-icon>category</mat-icon>
-        </div>
-        <div class="stat-content">
-          <div class="stat-value">{{ assetStats().total }}</div>
-          <div class="stat-label">资产总数</div>
-        </div>
-      </div>
-      <div class="stat-card occupied">
-        <div class="stat-icon">
-          <mat-icon>person</mat-icon>
-        </div>
-        <div class="stat-content">
-          <div class="stat-value">{{ assetStats().occupied }}</div>
-          <div class="stat-label">占用中</div>
-          <div class="stat-percentage">{{ Math.round(assetStats().occupied / assetStats().total * 100) }}%</div>
-        </div>
-      </div>
-      <div class="stat-card idle">
-        <div class="stat-icon">
-          <mat-icon>schedule</mat-icon>
-        </div>
-        <div class="stat-content">
-          <div class="stat-value">{{ assetStats().idle }}</div>
-          <div class="stat-label">空闲</div>
-          <div class="stat-percentage">{{ Math.round(assetStats().idle / assetStats().total * 100) }}%</div>
-        </div>
-      </div>
-      <div class="stat-card faulty">
-        <div class="stat-icon">
-          <mat-icon>error</mat-icon>
-        </div>
-        <div class="stat-content">
-          <div class="stat-value">{{ assetStats().faulty }}</div>
-          <div class="stat-label">故障</div>
-          <div class="stat-percentage">{{ Math.round(assetStats().faulty / assetStats().total * 100) }}%</div>
-        </div>
-      </div>
-      <div class="stat-card repairing">
-        <div class="stat-icon">
-          <mat-icon>build</mat-icon>
-        </div>
-        <div class="stat-content">
-          <div class="stat-value">{{ assetStats().repairing }}</div>
-          <div class="stat-label">报修中</div>
-          <div class="stat-percentage">{{ Math.round(assetStats().repairing / assetStats().total * 100) }}%</div>
-        </div>
-      </div>
-      <div class="stat-card value">
-        <div class="stat-icon">
-          <mat-icon>monetization_on</mat-icon>
-        </div>
-        <div class="stat-content">
-          <div class="stat-value">{{ formatCurrency(assetStats().totalValue) }}</div>
-          <div class="stat-label">资产总值</div>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <!-- 资产列表/网格视图 -->
-  <div class="assets-view">
-    <!-- 网格视图 -->
-    @if (selectedView() === 'grid') {
-      <div class="assets-grid">
-        @for (asset of filteredAssets(); track asset.id) {
-          <div 
-            class="asset-card"
-            [class.faulty]="asset.status === '故障' || asset.status === '报修中'"
-          >
-            <div class="asset-header">
-              <div class="asset-type-icon" [class]="asset.type">
-                <mat-icon>{{ getTypeIcon(asset.type) }}</mat-icon>
-              </div>
-              <div class="asset-status" [class]="getStatusClass(asset.status)">
-                {{ asset.status }}
-              </div>
-            </div>
-            <div class="asset-content">
-              <h3 class="asset-name">{{ asset.name }}</h3>
-              <div class="asset-info">
-                <div class="info-item">
-                  <span class="info-label">序列号:</span>
-                  <span class="info-value">{{ asset.serialNumber }}</span>
-                </div>
-                <div class="info-item">
-                  <span class="info-label">购买日期:</span>
-                  <span class="info-value">{{ formatDate(asset.purchaseDate) }}</span>
-                </div>
-                <div class="info-item">
-                  <span class="info-label">价值:</span>
-                  <span class="info-value">{{ formatCurrency(asset.value) }}</span>
-                </div>
-                <div class="info-item">
-                  <span class="info-label">部门:</span>
-                  <span class="info-value">{{ asset.department }}</span>
-                </div>
-                @if (asset.assignedToName) {
-                  <div class="info-item">
-                    <span class="info-label">使用人:</span>
-                    <span class="info-value">{{ asset.assignedToName }}</span>
-                  </div>
-                }
-                @if (asset.status === '故障' || asset.status === '报修中') {
-                  <div class="info-item faulty-notice">
-                    <span class="info-label">故障描述:</span>
-                    <span class="info-value">需要维修</span>
-                  </div>
-                }
-              </div>
-            </div>
-            <div class="asset-actions">
-              <button mat-icon-button matTooltip="查看详情" class="action-btn">
-                <mat-icon>visibility</mat-icon>
-              </button>
-              <button 
-                mat-icon-button 
-                matTooltip="编辑信息"
-                class="action-btn"
-              >
-                <mat-icon>edit</mat-icon>
-              </button>
-              @if (asset.status === '空闲') {
-                <button 
-                  mat-icon-button 
-                  matTooltip="分配资产"
-                  class="action-btn primary"
-                  (click)="openAssignmentDialog(asset)"
-                >
-                  <mat-icon>assignment_ind</mat-icon>
-                </button>
-              }
-              @if (asset.status === '占用') {
-                <button 
-                  mat-icon-button 
-                  matTooltip="归还资产"
-                  class="action-btn primary"
-                  (click)="openAssignmentDialog(asset, true)"
-                >
-                  <mat-icon>undo</mat-icon>
-                </button>
-              }
-              @if (asset.status === '故障') {
-                <button 
-                  mat-icon-button 
-                  matTooltip="申请报修"
-                  class="action-btn warning"
-                  (click)="openRepairDialog(asset)"
-                >
-                  <mat-icon>build</mat-icon>
-                </button>
-              }
-            </div>
-          </div>
-        }
-      </div>
-    }
-    
-    <!-- 列表视图 -->
-    @if (selectedView() === 'list') {
-      <div class="assets-list">
-        <table mat-table [dataSource]="filteredAssets()" class="assets-table">
-          <ng-container matColumnDef="name">
-            <th mat-header-cell *matHeaderCellDef>资产名称</th>
-            <td mat-cell *matCellDef="let asset">
-              <div class="asset-name-list">
-                <div class="asset-type-icon-small" [class]="asset.type">
-                  <mat-icon>{{ getTypeIcon(asset.type) }}</mat-icon>
-                </div>
-                <span>{{ asset.name }}</span>
-              </div>
-            </td>
-          </ng-container>
-          <ng-container matColumnDef="type">
-            <th mat-header-cell *matHeaderCellDef>类型</th>
-            <td mat-cell *matCellDef="let asset">{{ asset.type }}</td>
-          </ng-container>
-          <ng-container matColumnDef="status">
-            <th mat-header-cell *matHeaderCellDef>状态</th>
-            <td mat-cell *matCellDef="let asset">
-              <span class="status-badge" [class]="getStatusClass(asset.status)">
-                {{ asset.status }}
-              </span>
-            </td>
-          </ng-container>
-          <ng-container matColumnDef="department">
-            <th mat-header-cell *matHeaderCellDef>部门</th>
-            <td mat-cell *matCellDef="let asset">{{ asset.department }}</td>
-          </ng-container>
-          <ng-container matColumnDef="assignedTo">
-            <th mat-header-cell *matHeaderCellDef>使用人</th>
-            <td mat-cell *matCellDef="let asset">{{ asset.assignedToName || '-' }}</td>
-          </ng-container>
-          <ng-container matColumnDef="purchaseDate">
-            <th mat-header-cell *matHeaderCellDef>购买日期</th>
-            <td mat-cell *matCellDef="let asset">{{ formatDate(asset.purchaseDate) }}</td>
-          </ng-container>
-          <ng-container matColumnDef="value">
-            <th mat-header-cell *matHeaderCellDef>价值</th>
-            <td mat-cell *matCellDef="let asset">{{ formatCurrency(asset.value) }}</td>
-          </ng-container>
-          <ng-container matColumnDef="actions">
-            <th mat-header-cell *matHeaderCellDef>操作</th>
-            <td mat-cell *matCellDef="let asset" class="actions-column">
-              <div class="action-buttons-list">
-                <button mat-icon-button matTooltip="查看详情" class="action-btn">
-                  <mat-icon>visibility</mat-icon>
-                </button>
-                <button mat-icon-button matTooltip="编辑信息" class="action-btn">
-                  <mat-icon>edit</mat-icon>
-                </button>
-                @if (asset.status === '空闲') {
-                  <button 
-                    mat-icon-button 
-                    matTooltip="分配资产"
-                    class="action-btn primary"
-                    (click)="openAssignmentDialog(asset)"
-                  >
-                    <mat-icon>assignment_ind</mat-icon>
-                  </button>
-                }
-                @if (asset.status === '占用') {
-                  <button 
-                    mat-icon-button 
-                    matTooltip="归还资产"
-                    class="action-btn primary"
-                    (click)="openAssignmentDialog(asset, true)"
-                  >
-                    <mat-icon>undo</mat-icon>
-                  </button>
-                }
-                @if (asset.status === '故障') {
-                  <button 
-                    mat-icon-button 
-                    matTooltip="申请报修"
-                    class="action-btn warning"
-                    (click)="openRepairDialog(asset)"
-                  >
-                    <mat-icon>build</mat-icon>
-                  </button>
-                }
-              </div>
-            </td>
-          </ng-container>
-          
-          <tr mat-header-row *matHeaderRowDef="['name', 'type', 'status', 'department', 'assignedTo', 'purchaseDate', 'value', 'actions']"></tr>
-          <tr mat-row *matRowDef="let row; columns: ['name', 'type', 'status', 'department', 'assignedTo', 'purchaseDate', 'value', 'actions']"></tr>
-          
-          <tr class="mat-row" *matNoDataRow>
-            <td class="mat-cell" colspan="8" class="no-data">
-              <div class="empty-state">
-                <mat-icon>search_off</mat-icon>
-                <p>没有找到符合条件的资产</p>
-              </div>
-            </td>
-          </tr>
-        </table>
-      </div>
-    }
-  </div>
-
-  <!-- 统计图表区 -->
-  <div class="stats-section">
-    <!-- 按类型统计 -->
-    <div class="stats-card">
-      <div class="card-header">
-        <h2>资产类型统计</h2>
-      </div>
-      <div class="chart-content">
-        <div class="type-stats">
-          @for (typeStat of assetStats().typeStatsArray; track typeStat.type) {
-            <div class="type-stat-item">
-              <div class="type-info">
-                <span class="type-name">{{ typeStat.type }}</span>
-                <span class="type-count">{{ typeStat.count }}台</span>
-              </div>
-              <div class="progress-bar">
-                <div class="progress-fill" [style.width]="(typeStat.count / assetStats().total * 100) + '%'"></div>
-              </div>
-              <div class="type-value">{{ formatCurrency(typeStat.value) }}</div>
-            </div>
-          }
-        </div>
-      </div>
-    </div>
-    
-    <!-- 按部门统计 -->
-    <div class="stats-card">
-      <div class="card-header">
-        <h2>部门资产统计</h2>
-      </div>
-      <div class="chart-content">
-        <div class="department-stats">
-          @for (deptStat of assetStats().departmentStatsArray; track deptStat.department) {
-            <div class="department-stat-item">
-              <div class="department-info">
-                <span class="department-name">{{ deptStat.department }}</span>
-                <span class="department-count">{{ deptStat.count }}台</span>
-              </div>
-              <div class="progress-bar">
-                <div class="progress-fill" [style.width]="(deptStat.count / assetStats().total * 100) + '%'" ></div>
-              </div>
-              <div class="department-value">{{ formatCurrency(deptStat.value) }}</div>
-            </div>
-          }
-        </div>
-      </div>
-    </div>
-    
-    <!-- 资产使用时长统计 -->
-    <div class="stats-card">
-      <div class="card-header">
-        <h2>资产平均使用时长(小时/月)</h2>
-      </div>
-      <div class="chart-content">
-        <div class="usage-stats">
-          @for (usage of assetStats().usageStats; track usage.type) {
-            <div class="usage-stat-item">
-              <div class="usage-info">
-                <span class="usage-type">{{ usage.type }}</span>
-              </div>
-              <div class="bar-container">
-                <div class="usage-bar" [style.height]="(usage.avgHours / 200 * 100) + '%'">
-                  <span class="usage-value">{{ usage.avgHours }}h</span>
-                </div>
-              </div>
-            </div>
-          }
-        </div>
-      </div>
-    </div>
-  </div>
-</div>

+ 0 - 1119
src/app/pages/hr/assets/assets-stats.scss

@@ -1,1119 +0,0 @@
-// 自定义主题
-$primary-color: #1e40af; // 深蓝主色,传递可靠感
-$primary-light: #3b82f6; // 浅蓝色,用于悬停效果
-$secondary-color: #0d9488; // 薄荷绿,作为强调色
-$success-color: #10b981; // 成功色
-$warning-color: #f59e0b; // 警告色
-$error-color: #ef4444; // 错误色
-$info-color: #3b82f6; // 信息色
-$text-primary: #1f2937; // 主要文本色
-$text-secondary: #4b5563; // 次要文本色
-$text-tertiary: #9ca3af; // 辅助文本色
-$bg-primary: #ffffff; // 主背景色
-$bg-secondary: #f9fafb; // 次要背景色
-$bg-tertiary: #f3f4f6; // 辅助背景色
-$border-color: #e5e7eb; // 边框色
-$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
-$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
-$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
-$border-radius: 8px;
-$transition: all 0.2s ease;
-
-// 主容器样式
-.assets-stats-container {
-  padding: 24px;
-  min-height: 100vh;
-  background-color: $bg-secondary;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-}
-
-// 页面标题
-.page-header {
-  margin-bottom: 32px;
-  text-align: center;
-
-  h1 {
-    font-size: 32px;
-    font-weight: 700;
-    color: $text-primary;
-    margin: 0 0 8px 0;
-    letter-spacing: -0.5px;
-  }
-
-  .page-description {
-    font-size: 16px;
-    color: $text-secondary;
-    margin: 0;
-  }
-}
-
-// 资产分类筛选栏
-.filter-bar {
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-  padding: 20px;
-  margin-bottom: 24px;
-  transition: $transition;
-
-  &:hover {
-    box-shadow: $shadow-md;
-  }
-
-  .search-container {
-    position: relative;
-    max-width: 400px;
-    margin: 0 auto 20px;
-
-    .search-icon {
-      position: absolute;
-      left: 12px;
-      top: 50%;
-      transform: translateY(-50%);
-      color: $text-tertiary;
-      z-index: 1;
-    }
-
-    .search-input {
-      width: 100%;
-      padding: 12px 12px 12px 40px;
-      border: 1px solid $border-color;
-      border-radius: $border-radius;
-      font-size: 14px;
-      color: $text-primary;
-      background-color: $bg-primary;
-      transition: $transition;
-      box-sizing: border-box;
-
-      &:focus {
-        outline: none;
-        border-color: $primary-color;
-        box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
-        transform: scale(1.01);
-      }
-
-      &::placeholder {
-        color: $text-tertiary;
-      }
-    }
-
-    .clear-search {
-      position: absolute;
-      right: 8px;
-      top: 50%;
-      transform: translateY(-50%);
-      color: $text-tertiary;
-      padding: 4px;
-      transition: $transition;
-
-      &:hover {
-        color: $text-primary;
-        background-color: $bg-tertiary;
-        border-radius: 50%;
-      }
-    }
-  }
-
-  .filter-controls {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 16px;
-    justify-content: center;
-    align-items: end;
-
-    .filter-group {
-      display: flex;
-      flex-direction: column;
-      gap: 6px;
-      min-width: 150px;
-
-      label {
-        font-size: 12px;
-        font-weight: 500;
-        color: $text-secondary;
-        text-transform: uppercase;
-        letter-spacing: 0.5px;
-      }
-
-      .filter-select {
-        padding: 10px 12px;
-        border: 1px solid $border-color;
-        border-radius: $border-radius;
-        font-size: 14px;
-        color: $text-primary;
-        background-color: $bg-primary;
-        transition: $transition;
-        cursor: pointer;
-        min-width: 150px;
-
-        &:focus {
-          outline: none;
-          border-color: $primary-color;
-          box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
-        }
-
-        &:hover {
-          border-color: $primary-light;
-        }
-      }
-    }
-
-    .reset-btn {
-      padding: 8px 16px;
-      border-radius: $border-radius;
-      font-size: 14px;
-      font-weight: 500;
-      color: $primary-color;
-      transition: $transition;
-
-      &:hover {
-        background-color: color-mix(in srgb, $primary-color 5%, transparent);
-      }
-    }
-
-    .export-btn {
-      padding: 8px 16px;
-      border-radius: $border-radius;
-      font-size: 14px;
-      font-weight: 500;
-      background-color: $primary-color;
-      color: white;
-      transition: $transition;
-
-      &:hover {
-        background-color: $primary-light;
-        transform: translateY(-1px);
-        box-shadow: $shadow-md;
-      }
-
-      &:active {
-        transform: scale(0.98);
-        box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1);
-      }
-    }
-  }
-}
-
-// 视图切换和统计卡片
-.view-stats-section {
-  margin-bottom: 24px;
-}
-
-.view-toggle {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  margin-bottom: 24px;
-  padding: 12px 20px;
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-
-  .view-btn {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    padding: 8px 16px;
-    border-radius: $border-radius;
-    font-size: 14px;
-    font-weight: 500;
-    color: $text-secondary;
-    transition: $transition;
-
-    &.active {
-      background-color: $primary-color;
-      color: white;
-      box-shadow: $shadow-sm;
-    }
-
-    &:not(.active):hover {
-      background-color: $bg-tertiary;
-      color: $text-primary;
-    }
-  }
-
-  .asset-count {
-    margin-left: auto;
-    font-size: 14px;
-    color: $text-secondary;
-    font-weight: 500;
-  }
-}
-
-.stats-cards {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
-  gap: 16px;
-
-  .stat-card {
-    background-color: $bg-primary;
-    border-radius: $border-radius;
-    padding: 20px;
-    box-shadow: $shadow-sm;
-    display: flex;
-    align-items: center;
-    gap: 16px;
-    transition: $transition;
-    position: relative;
-    overflow: hidden;
-
-    &::before {
-      content: '';
-      position: absolute;
-      top: 0;
-      left: 0;
-      width: 4px;
-      height: 100%;
-      background-color: $primary-color;
-    }
-
-    &:hover {
-      transform: translateY(-4px);
-      box-shadow: $shadow-md;
-    }
-
-    .stat-icon {
-      width: 40px;
-      height: 40px;
-      border-radius: 50%;
-      background-color: color-mix(in srgb, $primary-color 10%, transparent);
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      color: $primary-color;
-      flex-shrink: 0;
-    }
-
-    .stat-content {
-      flex: 1;
-
-      .stat-value {
-        font-size: 24px;
-        font-weight: 700;
-        color: $text-primary;
-        margin-bottom: 4px;
-        line-height: 1;
-      }
-
-      .stat-label {
-        font-size: 12px;
-        color: $text-secondary;
-        font-weight: 500;
-        text-transform: uppercase;
-        letter-spacing: 0.5px;
-      }
-
-      .stat-percentage {
-        font-size: 12px;
-        color: $text-tertiary;
-        margin-top: 4px;
-      }
-    }
-
-    &.occupied {
-      &::before {
-        background-color: $primary-color;
-      }
-
-      .stat-icon {
-        background-color: color-mix(in srgb, $primary-color 10%, transparent);
-        color: $primary-color;
-      }
-    }
-
-    &.idle {
-      &::before {
-        background-color: $secondary-color;
-      }
-
-      .stat-icon {
-        background-color: color-mix(in srgb, $secondary-color 10%, transparent);
-        color: $secondary-color;
-      }
-    }
-
-    &.faulty {
-      &::before {
-        background-color: $error-color;
-      }
-
-      .stat-icon {
-        background-color: color-mix(in srgb, $error-color 10%, transparent);
-        color: $error-color;
-      }
-    }
-
-    &.repairing {
-      &::before {
-        background-color: $warning-color;
-      }
-
-      .stat-icon {
-        background-color: color-mix(in srgb, $warning-color 10%, transparent);
-        color: $warning-color;
-      }
-    }
-
-    &.value {
-      &::before {
-        background-color: $success-color;
-      }
-
-      .stat-icon {
-        background-color: color-mix(in srgb, $success-color 10%, transparent);
-        color: $success-color;
-      }
-    }
-  }
-}
-
-// 资产列表/网格视图
-.assets-view {
-  margin-bottom: 32px;
-}
-
-// 网格视图
-.assets-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
-  gap: 20px;
-}
-
-.asset-card {
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-  padding: 20px;
-  transition: $transition;
-  position: relative;
-  overflow: hidden;
-
-  &:hover {
-    transform: translateY(-4px);
-    box-shadow: $shadow-md;
-  }
-
-  &.faulty {
-    animation: faulty-pulse 2s infinite;
-  }
-
-  .asset-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 16px;
-
-    .asset-type-icon {
-      width: 48px;
-      height: 48px;
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 20px;
-      background-color: $bg-tertiary;
-      color: $primary-color;
-      transition: $transition;
-
-      &.电脑 {
-        background-color: color-mix(in srgb, $primary-color 10%, $bg-tertiary);
-        color: $primary-color;
-      }
-
-      &.外设 {
-        background-color: color-mix(in srgb, $secondary-color 10%, $bg-tertiary);
-        color: $secondary-color;
-      }
-
-      &.软件账号 {
-        background-color: color-mix(in srgb, $info-color 10%, $bg-tertiary);
-        color: $info-color;
-      }
-
-      &.域名 {
-        background-color: color-mix(in srgb, $warning-color 10%, $bg-tertiary);
-        color: $warning-color;
-      }
-
-      &.其他 {
-        background-color: color-mix(in srgb, $text-secondary 10%, $bg-tertiary);
-        color: $text-secondary;
-      }
-    }
-
-    .asset-status {
-      padding: 6px 12px;
-      border-radius: 16px;
-      font-size: 12px;
-      font-weight: 500;
-      text-align: center;
-      min-width: 60px;
-      transition: $transition;
-
-      &.status-idle {
-        background-color: color-mix(in srgb, $secondary-color 15%, transparent);
-        color: $secondary-color;
-      }
-
-      &.status-occupied {
-        background-color: color-mix(in srgb, $primary-color 15%, transparent);
-        color: $primary-color;
-      }
-
-      &.status-faulty {
-        background-color: color-mix(in srgb, $error-color 15%, transparent);
-        color: $error-color;
-      }
-
-      &.status-repairing {
-        background-color: color-mix(in srgb, $warning-color 15%, transparent);
-        color: $warning-color;
-      }
-    }
-  }
-
-  .asset-content {
-    margin-bottom: 16px;
-
-    .asset-name {
-      font-size: 18px;
-      font-weight: 600;
-      color: $text-primary;
-      margin: 0 0 12px 0;
-      line-height: 1.3;
-    }
-
-    .asset-info {
-      display: flex;
-      flex-direction: column;
-      gap: 8px;
-
-      .info-item {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        padding: 8px 0;
-        border-bottom: 1px solid $border-color;
-
-        .info-label {
-          font-size: 12px;
-          color: $text-tertiary;
-          font-weight: 500;
-        }
-
-        .info-value {
-          font-size: 12px;
-          color: $text-secondary;
-          font-weight: 500;
-        }
-      }
-
-      .info-item.faulty-notice {
-        background-color: color-mix(in srgb, $error-color 5%, transparent);
-        border-radius: 6px;
-        padding: 8px 12px;
-        border: none;
-
-        .info-value {
-          color: $error-color;
-        }
-      }
-    }
-  }
-
-  .asset-actions {
-    display: flex;
-    justify-content: flex-end;
-    gap: 8px;
-    padding-top: 16px;
-    border-top: 1px solid $border-color;
-
-    .action-btn {
-      width: 36px;
-      height: 36px;
-      border-radius: 50%;
-      color: $text-secondary;
-      background-color: $bg-tertiary;
-      transition: $transition;
-
-      &:hover {
-        background-color: $border-color;
-        color: $text-primary;
-        transform: scale(1.1);
-      }
-
-      &.primary {
-        color: $primary-color;
-        background-color: color-mix(in srgb, $primary-color 10%, $bg-tertiary);
-
-        &:hover {
-          background-color: $primary-color;
-          color: white;
-        }
-      }
-
-      &.warning {
-        color: $error-color;
-        background-color: color-mix(in srgb, $error-color 10%, $bg-tertiary);
-
-        &:hover {
-          background-color: $error-color;
-          color: white;
-        }
-      }
-    }
-  }
-}
-
-// 列表视图
-.assets-list {
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-  overflow: hidden;
-}
-
-.assets-table {
-  width: 100%;
-  min-width: 800px;
-
-  .mat-header-row {
-    background-color: $bg-tertiary;
-
-    th {
-      font-size: 12px;
-      font-weight: 600;
-      color: $text-secondary;
-      text-transform: uppercase;
-      letter-spacing: 0.5px;
-      padding: 12px 16px;
-      text-align: left;
-      border-bottom: 1px solid $border-color;
-    }
-  }
-
-  .mat-row {
-    transition: $transition;
-
-    &:hover {
-      background-color: $bg-tertiary;
-      transform: translateX(2px);
-    }
-
-    td {
-      padding: 12px 16px;
-      border-bottom: 1px solid $border-color;
-      font-size: 14px;
-      color: $text-primary;
-    }
-
-    &:last-child td {
-      border-bottom: none;
-    }
-  }
-
-  .asset-name-list {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-
-    .asset-type-icon-small {
-      width: 24px;
-      height: 24px;
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 14px;
-      background-color: $bg-tertiary;
-      color: $primary-color;
-
-      &.电脑 {
-        background-color: color-mix(in srgb, $primary-color 10%, $bg-tertiary);
-        color: $primary-color;
-      }
-
-      &.外设 {
-        background-color: color-mix(in srgb, $secondary-color 10%, $bg-tertiary);
-        color: $secondary-color;
-      }
-
-      &.软件账号 {
-        background-color: color-mix(in srgb, $info-color 10%, $bg-tertiary);
-        color: $info-color;
-      }
-
-      &.域名 {
-        background-color: color-mix(in srgb, $warning-color 10%, $bg-tertiary);
-        color: $warning-color;
-      }
-
-      &.其他 {
-        background-color: color-mix(in srgb, $text-secondary 10%, $bg-tertiary);
-        color: $text-secondary;
-      }
-    }
-  }
-
-  .status-badge {
-    padding: 4px 12px;
-    border-radius: 16px;
-    font-size: 12px;
-    font-weight: 500;
-    text-align: center;
-    display: inline-block;
-    transition: $transition;
-
-    &.status-idle {
-      background-color: color-mix(in srgb, $secondary-color 15%, transparent);
-      color: $secondary-color;
-    }
-
-    &.status-occupied {
-      background-color: color-mix(in srgb, $primary-color 15%, transparent);
-      color: $primary-color;
-    }
-
-    &.status-faulty {
-      background-color: color-mix(in srgb, $error-color 15%, transparent);
-      color: $error-color;
-    }
-
-    &.status-repairing {
-      background-color: color-mix(in srgb, $warning-color 15%, transparent);
-      color: $warning-color;
-    }
-  }
-
-  .actions-column {
-    text-align: right;
-  }
-
-  .action-buttons-list {
-    display: flex;
-    justify-content: flex-end;
-    gap: 4px;
-
-    .action-btn {
-      width: 32px;
-      height: 32px;
-      border-radius: 50%;
-      color: $text-secondary;
-      background-color: $bg-tertiary;
-      transition: $transition;
-
-      &:hover {
-        background-color: $border-color;
-        color: $text-primary;
-        transform: scale(1.1);
-      }
-
-      &.primary {
-        color: $primary-color;
-        background-color: color-mix(in srgb, $primary-color 10%, $bg-tertiary);
-
-        &:hover {
-          background-color: $primary-color;
-          color: white;
-        }
-      }
-
-      &.warning {
-        color: $error-color;
-        background-color: color-mix(in srgb, $error-color 10%, $bg-tertiary);
-
-        &:hover {
-          background-color: $error-color;
-          color: white;
-        }
-      }
-    }
-  }
-
-  .no-data {
-    text-align: center;
-    padding: 60px 20px;
-
-    .empty-state {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-
-      mat-icon {
-        font-size: 48px;
-        color: $text-tertiary;
-        margin-bottom: 16px;
-      }
-
-      p {
-        color: $text-secondary;
-        font-size: 16px;
-        margin: 0;
-      }
-    }
-  }
-}
-
-// 统计图表区
-.stats-section {
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
-  gap: 24px;
-}
-
-.stats-card {
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-  padding: 24px;
-  transition: $transition;
-
-  &:hover {
-    transform: translateY(-2px);
-    box-shadow: $shadow-md;
-  }
-
-  .card-header {
-    margin-bottom: 24px;
-
-    h2 {
-      font-size: 20px;
-      font-weight: 600;
-      color: $text-primary;
-      margin: 0;
-    }
-  }
-
-  .chart-content {
-    min-height: 200px;
-  }
-
-  // 类型统计
-  .type-stats {
-    display: flex;
-    flex-direction: column;
-    gap: 16px;
-
-    .type-stat-item {
-      display: flex;
-      align-items: center;
-      gap: 12px;
-
-      .type-info {
-        flex: 0 0 120px;
-
-        .type-name {
-          font-size: 14px;
-          font-weight: 500;
-          color: $text-primary;
-          display: block;
-        }
-
-        .type-count {
-          font-size: 12px;
-          color: $text-tertiary;
-          display: block;
-          margin-top: 2px;
-        }
-      }
-
-      .progress-bar {
-        flex: 1;
-        height: 12px;
-        background-color: $bg-tertiary;
-        border-radius: 6px;
-        overflow: hidden;
-        position: relative;
-
-        .progress-fill {
-          height: 100%;
-          background-color: $primary-color;
-          border-radius: 6px;
-          transition: width 0.8s ease-out;
-          position: relative;
-          overflow: hidden;
-
-          &::after {
-            content: '';
-            position: absolute;
-            top: 0;
-            left: 0;
-            right: 0;
-            bottom: 0;
-            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
-            animation: progressAnimation 1.5s infinite;
-          }
-        }
-      }
-
-      .type-value {
-        font-size: 14px;
-        font-weight: 600;
-        color: $text-primary;
-        min-width: 100px;
-        text-align: right;
-      }
-    }
-  }
-
-  // 部门统计
-  .department-stats {
-    display: flex;
-    flex-direction: column;
-    gap: 16px;
-
-    .department-stat-item {
-      display: flex;
-      align-items: center;
-      gap: 12px;
-
-      .department-info {
-        flex: 0 0 150px;
-
-        .department-name {
-          font-size: 14px;
-          font-weight: 500;
-          color: $text-primary;
-          display: block;
-        }
-
-        .department-count {
-          font-size: 12px;
-          color: $text-tertiary;
-          display: block;
-          margin-top: 2px;
-        }
-      }
-
-      .progress-bar {
-        flex: 1;
-        height: 12px;
-        background-color: $bg-tertiary;
-        border-radius: 6px;
-        overflow: hidden;
-        position: relative;
-
-        .progress-fill {
-          height: 100%;
-          background-color: $secondary-color;
-          border-radius: 6px;
-          transition: width 0.8s ease-out;
-          position: relative;
-          overflow: hidden;
-
-          &::after {
-            content: '';
-            position: absolute;
-            top: 0;
-            left: 0;
-            right: 0;
-            bottom: 0;
-            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
-            animation: progressAnimation 1.5s infinite;
-          }
-        }
-      }
-
-      .department-value {
-        font-size: 14px;
-        font-weight: 600;
-        color: $text-primary;
-        min-width: 100px;
-        text-align: right;
-      }
-    }
-  }
-
-  // 使用时长统计
-  .usage-stats {
-    display: flex;
-    align-items: flex-end;
-    gap: 16px;
-    height: 200px;
-    padding-top: 20px;
-
-    .usage-stat-item {
-      flex: 1;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      gap: 8px;
-
-      .usage-info {
-        text-align: center;
-
-        .usage-type {
-          font-size: 12px;
-          color: $text-secondary;
-          font-weight: 500;
-        }
-      }
-
-      .bar-container {
-        position: relative;
-        width: 100%;
-        height: 100%;
-        display: flex;
-        align-items: flex-end;
-        justify-content: center;
-      }
-
-      .usage-bar {
-        width: 40px;
-        background-color: $primary-color;
-        border-radius: 4px 4px 0 0;
-        position: relative;
-        overflow: hidden;
-        transition: height 1s ease-out;
-        cursor: pointer;
-
-        &::after {
-          content: '';
-          position: absolute;
-          top: 0;
-          left: 0;
-          right: 0;
-          bottom: 0;
-          background: linear-gradient(180deg, rgba(255, 255, 255, 0.2), transparent);
-        }
-
-        &:hover {
-          background-color: $primary-light;
-          transform: scaleY(1.02);
-        }
-
-        .usage-value {
-          position: absolute;
-          top: -24px;
-          left: 50%;
-          transform: translateX(-50%);
-          font-size: 12px;
-          font-weight: 600;
-          color: $text-primary;
-        }
-      }
-    }
-  }
-}
-
-// 动画定义
-@keyframes faulty-pulse {
-  0%, 100% {
-    box-shadow: $shadow-sm, 0 0 0 0 rgba(239, 68, 68, 0);
-  }
-  50% {
-    box-shadow: $shadow-md, 0 0 0 4px rgba(239, 68, 68, 0.3);
-  }
-}
-
-@keyframes progressAnimation {
-  0% {
-    transform: translateX(-100%);
-  }
-  100% {
-    transform: translateX(100%);
-  }
-}
-
-// 响应式设计
-@media (max-width: 1200px) {
-  .assets-stats-container {
-    padding: 16px;
-  }
-
-  .stats-section {
-    grid-template-columns: 1fr;
-  }
-
-  .assets-grid {
-    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
-  }
-}
-
-@media (max-width: 768px) {
-  .assets-stats-container {
-    padding: 12px;
-  }
-
-  .page-header {
-    margin-bottom: 20px;
-
-    h1 {
-      font-size: 24px;
-    }
-  }
-
-  .filter-bar {
-    padding: 16px;
-  }
-
-  .filter-controls {
-    flex-direction: column;
-    align-items: stretch;
-
-    .filter-group {
-      min-width: auto;
-    }
-  }
-
-  .view-toggle {
-    flex-wrap: wrap;
-    justify-content: center;
-
-    .asset-count {
-      margin-left: 0;
-      width: 100%;
-      text-align: center;
-    }
-  }
-
-  .stats-cards {
-    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
-    gap: 12px;
-  }
-
-  .asset-card {
-    padding: 16px;
-
-    .asset-header {
-      flex-direction: column;
-      align-items: flex-start;
-      gap: 12px;
-    }
-
-    .asset-actions {
-      justify-content: center;
-    }
-  }
-
-  .assets-list {
-    overflow-x: auto;
-  }
-
-  .stats-section {
-    gap: 16px;
-  }
-
-  .stats-card {
-    padding: 16px;
-  }
-
-  .usage-stats {
-    height: 150px;
-    gap: 8px;
-  }
-
-  .usage-bar {
-    width: 24px;
-  }
-}
-
-@media (max-width: 480px) {
-  .stats-cards {
-    grid-template-columns: 1fr 1fr;
-  }
-
-  .assets-grid {
-    grid-template-columns: 1fr;
-  }
-}

+ 0 - 823
src/app/pages/hr/assets/assets-stats.ts

@@ -1,823 +0,0 @@
-import { Component, OnInit, signal, computed, Inject } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { MatButtonModule } from '@angular/material/button';
-import { MatCardModule } from '@angular/material/card';
-import { MatIconModule } from '@angular/material/icon';
-import { MatDialogModule, MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
-import { MatTabsModule } from '@angular/material/tabs';
-import { MatTooltipModule } from '@angular/material/tooltip';
-import { MatTableModule } from '@angular/material/table';
-import { Asset, AssetType, AssetStatus, AssetAssignment, Employee } from '../../../models/hr.model';
-
-// 资产分配对话框组件
-@Component({
-  selector: 'app-asset-assignment-dialog',
-  standalone: true,
-  imports: [
-    CommonModule,
-    FormsModule,
-    MatButtonModule,
-    MatIconModule
-  ],
-  template: `
-    <div class="dialog-header">
-      <h2>{{ isEdit ? '修改资产分配' : '分配资产' }}</h2>
-      <button class="close-btn" (click)="dialogRef.close()">
-        <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="dialog-content">
-      <div class="info-item">
-        <label>资产名称:</label>
-        <span>{{ asset.name }}</span>
-      </div>
-      <div class="form-group">
-        <label>选择员工:</label>
-        <input 
-          type="text" 
-          [(ngModel)]="selectedEmployeeName"
-          (input)="filterEmployees($event.target.value)"
-          placeholder="搜索员工姓名或工号..."
-          class="employee-search"
-          [disabled]="isReturning"
-        >
-        <div class="employee-dropdown" *ngIf="showEmployeeDropdown && !isReturning">
-          <div *ngFor="let employee of filteredEmployees" 
-               class="employee-item" 
-               (click)="selectEmployee(employee)">
-            <span class="employee-name">{{ employee.name }}</span>
-            <span class="employee-id">{{ employee.employeeId }}</span>
-          </div>
-        </div>
-      </div>
-      <div class="form-group" *ngIf="!isReturning">
-        <label>分配开始日期:</label>
-        <input type="date" [(ngModel)]="startDate" class="date-input">
-      </div>
-      <div class="form-group" *ngIf="!isReturning">
-        <label>预计归还日期:</label>
-        <input type="date" [(ngModel)]="endDate" class="date-input">
-      </div>
-      <div class="form-group" *ngIf="isReturning">
-        <label>实际归还日期:</label>
-        <input type="date" [(ngModel)]="returnDate" class="date-input">
-      </div>
-      <div class="form-group">
-        <label>备注:</label>
-        <textarea 
-          [(ngModel)]="notes" 
-          placeholder="请输入备注信息..." 
-          rows="3"
-          class="notes-input"
-        ></textarea>
-      </div>
-    </div>
-    <div class="dialog-actions">
-      <button mat-button (click)="dialogRef.close()">取消</button>
-      <button mat-raised-button color="primary" (click)="submit()">
-        {{ isReturning ? '确认归还' : '确认分配' }}
-      </button>
-    </div>
-  `,
-  styles: [`
-    .dialog-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 24px;
-      padding-bottom: 16px;
-      border-bottom: 1px solid #e5e7eb;
-    }
-    .close-btn {
-      background: none;
-      border: none;
-      cursor: pointer;
-      color: #6b7280;
-      padding: 4px;
-    }
-    .dialog-content {
-      max-width: 500px;
-    }
-    .info-item {
-      display: flex;
-      justify-content: space-between;
-      margin-bottom: 16px;
-      padding: 8px 0;
-      border-bottom: 1px solid #f3f4f6;
-    }
-    .form-group {
-      margin-bottom: 20px;
-      position: relative;
-    }
-    label {
-      display: block;
-      margin-bottom: 4px;
-      font-weight: 500;
-      color: #374151;
-    }
-    .employee-search,
-    .date-input {
-      width: 100%;
-      padding: 8px 12px;
-      border: 1px solid #e5e7eb;
-      border-radius: 6px;
-      font-size: 14px;
-    }
-    .employee-dropdown {
-      position: absolute;
-      top: 100%;
-      left: 0;
-      right: 0;
-      background-color: white;
-      border: 1px solid #e5e7eb;
-      border-radius: 6px;
-      max-height: 200px;
-      overflow-y: auto;
-      box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
-      z-index: 1000;
-    }
-    .employee-item {
-      padding: 10px 12px;
-      cursor: pointer;
-      transition: background-color 0.2s;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-    }
-    .employee-item:hover {
-      background-color: #f3f4f6;
-    }
-    .employee-name {
-      font-weight: 500;
-      color: #1f2937;
-    }
-    .employee-id {
-      font-size: 12px;
-      color: #6b7280;
-    }
-    .notes-input {
-      width: 100%;
-      padding: 8px 12px;
-      border: 1px solid #e5e7eb;
-      border-radius: 6px;
-      font-size: 14px;
-      resize: vertical;
-    }
-    .dialog-actions {
-      display: flex;
-      justify-content: flex-end;
-      gap: 12px;
-      margin-top: 24px;
-    }
-  `]
-}) class AssetAssignmentDialog {
-  asset: Asset;
-  employees: Employee[] = [];
-  filteredEmployees: Employee[] = [];
-  showEmployeeDropdown = false;
-  selectedEmployeeId = '';
-  selectedEmployeeName = '';
-  startDate = new Date().toISOString().split('T')[0];
-  endDate = '';
-  returnDate = new Date().toISOString().split('T')[0];
-  notes = '';
-  isEdit = false;
-  isReturning = false;
-  
-  constructor(
-    public dialogRef: MatDialogRef<AssetAssignmentDialog>,
-    @Inject(MAT_DIALOG_DATA) public data: any
-  ) {
-    this.asset = data.asset;
-    this.employees = data.employees;
-    this.filteredEmployees = [...this.employees];
-    this.isEdit = data.isEdit || false;
-    this.isReturning = data.isReturning || false;
-    
-    if (this.isEdit && data.assignment) {
-      const assignment = data.assignment;
-      const employee = this.employees.find(e => e.id === assignment.employeeId);
-      if (employee) {
-        this.selectedEmployeeId = employee.id;
-        this.selectedEmployeeName = employee.name;
-      }
-      if (assignment.startDate) {
-        this.startDate = new Date(assignment.startDate).toISOString().split('T')[0];
-      }
-      if (assignment.endDate) {
-        this.endDate = new Date(assignment.endDate).toISOString().split('T')[0];
-      }
-    }
-  }
-  
-  filterEmployees(query: string) {
-    if (!query.trim()) {
-      this.filteredEmployees = [...this.employees];
-    } else {
-      const term = query.toLowerCase();
-      this.filteredEmployees = this.employees.filter(emp => 
-        emp.name.toLowerCase().includes(term) ||
-        emp.employeeId.toLowerCase().includes(term)
-      );
-    }
-    this.showEmployeeDropdown = true;
-  }
-  
-  selectEmployee(employee: Employee) {
-    this.selectedEmployeeId = employee.id;
-    this.selectedEmployeeName = employee.name;
-    this.showEmployeeDropdown = false;
-  }
-  
-  submit() {
-    if (!this.selectedEmployeeId && !this.isReturning) {
-      alert('请选择员工');
-      return;
-    }
-    
-    this.dialogRef.close({
-      employeeId: this.selectedEmployeeId,
-      startDate: this.startDate,
-      endDate: this.isReturning ? this.returnDate : this.endDate,
-      notes: this.notes
-    });
-  }
-}
-
-// 报修对话框组件
-@Component({
-  selector: 'app-asset-repair-dialog',
-  standalone: true,
-  imports: [
-    CommonModule,
-    FormsModule,
-    MatButtonModule,
-    MatIconModule
-  ],
-  template: `
-    <div class="dialog-header">
-      <h2>资产报修</h2>
-      <button class="close-btn" (click)="dialogRef.close()">
-        <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="dialog-content">
-      <div class="info-item">
-        <label>资产名称:</label>
-        <span>{{ asset.name }}</span>
-      </div>
-      <div class="info-item">
-        <label>资产类型:</label>
-        <span>{{ asset.type }}</span>
-      </div>
-      <div class="info-item">
-        <label>序列号:</label>
-        <span>{{ asset.serialNumber || '无' }}</span>
-      </div>
-      <div class="form-group">
-        <label>故障描述 *:</label>
-        <textarea 
-          [(ngModel)]="problemDescription" 
-          placeholder="请详细描述故障情况..." 
-          rows="4"
-          class="description-input"
-          required
-        ></textarea>
-      </div>
-      <div class="form-group">
-        <label>期望修复时间:</label>
-        <input type="date" [(ngModel)]="expectedFixDate" class="date-input">
-      </div>
-      <div class="form-group">
-        <label>联系人:</label>
-        <input type="text" [(ngModel)]="contactPerson" class="contact-input" placeholder="请输入联系人姓名">
-      </div>
-      <div class="form-group">
-        <label>联系电话:</label>
-        <input type="text" [(ngModel)]="contactPhone" class="phone-input" placeholder="请输入联系电话">
-      </div>
-    </div>
-    <div class="dialog-actions">
-      <button mat-button (click)="dialogRef.close()">取消</button>
-      <button mat-raised-button color="primary" (click)="submit()" [disabled]="!problemDescription.trim()">
-        提交报修申请
-      </button>
-    </div>
-  `,
-  styles: [`
-    .dialog-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 24px;
-      padding-bottom: 16px;
-      border-bottom: 1px solid #e5e7eb;
-    }
-    .close-btn {
-      background: none;
-      border: none;
-      cursor: pointer;
-      color: #6b7280;
-      padding: 4px;
-    }
-    .dialog-content {
-      max-width: 500px;
-    }
-    .info-item {
-      display: flex;
-      justify-content: space-between;
-      margin-bottom: 16px;
-      padding: 8px 0;
-      border-bottom: 1px solid #f3f4f6;
-    }
-    .form-group {
-      margin-bottom: 20px;
-    }
-    label {
-      display: block;
-      margin-bottom: 4px;
-      font-weight: 500;
-      color: #374151;
-    }
-    .description-input,
-    .date-input,
-    .contact-input,
-    .phone-input {
-      width: 100%;
-      padding: 8px 12px;
-      border: 1px solid #e5e7eb;
-      border-radius: 6px;
-      font-size: 14px;
-    }
-    .description-input {
-      resize: vertical;
-    }
-    .dialog-actions {
-      display: flex;
-      justify-content: flex-end;
-      gap: 12px;
-      margin-top: 24px;
-    }
-  `]
-}) class AssetRepairDialog {
-  asset: Asset;
-  problemDescription = '';
-  expectedFixDate = new Date().toISOString().split('T')[0];
-  contactPerson = '';
-  contactPhone = '';
-  
-  constructor(
-    public dialogRef: MatDialogRef<AssetRepairDialog>,
-    @Inject(MAT_DIALOG_DATA) public data: any
-  ) {
-    this.asset = data.asset;
-  }
-  
-  submit() {
-    if (!this.problemDescription.trim()) {
-      alert('请填写故障描述');
-      return;
-    }
-    
-    this.dialogRef.close({
-      problemDescription: this.problemDescription,
-      expectedFixDate: this.expectedFixDate,
-      contactPerson: this.contactPerson,
-      contactPhone: this.contactPhone
-    });
-  }
-}
-
-// 生成模拟资产数据
-const generateMockAssets = (): Asset[] => {
-  const assets: Asset[] = [];
-  const types: AssetType[] = ['电脑', '外设', '软件账号', '域名', '其他'];
-  const statuses: AssetStatus[] = ['空闲', '占用', '故障', '报修中'];
-  const departments = ['设计部', '客户服务部', '财务部', '人力资源部'];
-  const computerModels = ['MacBook Pro', 'Dell XPS', 'HP EliteBook', 'Lenovo ThinkPad', 'Surface Laptop'];
-  const peripheralTypes = ['显示器', '键盘', '鼠标', '打印机', '扫描仪', '投影仪'];
-  const softwareTypes = ['Adobe Creative Cloud', 'AutoCAD', 'Office 365', '渲染农场账号', '素材库账号'];
-  const otherAssets = ['办公桌', '办公椅', '服务器', '网络设备', '空调'];
-  
-  for (let i = 1; i <= 30; i++) {
-    const type = types[Math.floor(Math.random() * types.length)];
-    const status = statuses[Math.floor(Math.random() * statuses.length)];
-    const purchaseDate = new Date();
-    purchaseDate.setMonth(purchaseDate.getMonth() - Math.floor(Math.random() * 36));
-    
-    let name = '';
-    let serialNumber = `SN${Math.floor(Math.random() * 1000000)}`;
-    
-    switch (type) {
-      case '电脑':
-        name = `${computerModels[Math.floor(Math.random() * computerModels.length)]} ${2020 + Math.floor(Math.random() * 4)}`;
-        break;
-      case '外设':
-        name = peripheralTypes[Math.floor(Math.random() * peripheralTypes.length)];
-        break;
-      case '软件账号':
-        name = softwareTypes[Math.floor(Math.random() * softwareTypes.length)];
-        serialNumber = '无';
-        break;
-      case '域名':
-        name = `example-${i}.com`;
-        serialNumber = '无';
-        break;
-      case '其他':
-        name = otherAssets[Math.floor(Math.random() * otherAssets.length)];
-        break;
-    }
-    
-    const value = Math.floor(Math.random() * 10000) + 500;
-    const assignedTo = status === '占用' ? `emp-${Math.floor(Math.random() * 10) + 1}` : undefined;
-    const assignedToName = assignedTo ? `员工${Math.floor(Math.random() * 20) + 1}` : undefined;
-    
-    // 随机设置保修截止日期
-    const warrantyExpiry = new Date(purchaseDate);
-    warrantyExpiry.setFullYear(warrantyExpiry.getFullYear() + (Math.random() > 0.5 ? 1 : 2));
-    
-    assets.push({
-      id: `asset-${i}`,
-      name,
-      type,
-      status,
-      purchaseDate,
-      value,
-      assignedTo,
-      assignedToName,
-      department: departments[Math.floor(Math.random() * departments.length)],
-      description: `这是一台${name},用于日常办公和项目开发。`,
-      serialNumber,
-      warrantyExpiry
-    });
-  }
-  
-  return assets;
-};
-
-// 生成模拟员工数据
-const generateMockEmployees = (): Employee[] => {
-  const employees: Employee[] = [];
-  const departments = ['设计部', '客户服务部', '财务部', '人力资源部'];
-  const positions = ['设计师', '客服专员', '财务专员', '人事专员', '经理', '助理'];
-  const names = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十'];
-  
-  for (let i = 1; i <= 20; i++) {
-    employees.push({
-      id: `emp-${i}`,
-      name: names[i % names.length] + i,
-      department: departments[Math.floor(Math.random() * departments.length)],
-      position: positions[Math.floor(Math.random() * positions.length)],
-      employeeId: `EMP2023${String(i).padStart(3, '0')}`,
-      phone: `138${Math.floor(Math.random() * 100000000)}`,
-      email: `employee${i}@example.com`,
-      gender: i % 2 === 0 ? '女' : '男',
-      birthDate: new Date(1980 + Math.floor(Math.random() * 20), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
-      hireDate: new Date(2020 + Math.floor(Math.random() * 3), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
-      status: '在职'
-    });
-  }
-  
-  return employees;
-};
-
-// 生成模拟资产分配记录
-const generateMockAssignments = (): AssetAssignment[] => {
-  const assignments: AssetAssignment[] = [];
-  
-  for (let i = 1; i <= 15; i++) {
-    const startDate = new Date();
-    startDate.setMonth(startDate.getMonth() - Math.floor(Math.random() * 6));
-    
-    const endDate = Math.random() > 0.5 ? new Date(startDate) : undefined;
-    if (endDate) {
-      endDate.setMonth(endDate.getMonth() + Math.floor(Math.random() * 3) + 1);
-    }
-    
-    assignments.push({
-      id: `assignment-${i}`,
-      assetId: `asset-${Math.floor(Math.random() * 20) + 1}`,
-      employeeId: `emp-${Math.floor(Math.random() * 15) + 1}`,
-      startDate,
-      endDate,
-      status: endDate ? '已归还' : '进行中'
-    });
-  }
-  
-  return assignments;
-};
-
-// 主组件
-@Component({
-  selector: 'app-assets-stats',
-  standalone: true,
-  imports: [
-    CommonModule,
-    FormsModule,
-    MatButtonModule,
-    MatCardModule,
-    MatIconModule,
-    MatDialogModule,
-    MatTabsModule,
-    MatTooltipModule,
-    MatTableModule
-  ],
-  templateUrl: './assets-stats.html',
-  styleUrl: './assets-stats.scss'
-}) export class AssetsStats implements OnInit {
-  // 暴露Math对象给模板使用
-  readonly Math = Math;
-  
-  // 数据
-  assets = signal<Asset[]>([]);
-  employees = signal<Employee[]>([]);
-  assignments = signal<AssetAssignment[]>([]);
-  selectedView = signal<'grid' | 'list'>('grid');
-  searchTerm = signal('');
-  typeFilter = signal<AssetType | ''>('');
-  statusFilter = signal<AssetStatus | ''>('');
-  departmentFilter = signal('');
-  
-  // 计算属性
-  filteredAssets = computed(() => {
-    let filtered = this.assets();
-    
-    // 按搜索词筛选
-    if (this.searchTerm()) {
-      const term = this.searchTerm().toLowerCase();
-      filtered = filtered.filter(asset => 
-        asset.name.toLowerCase().includes(term) ||
-        (asset.serialNumber && asset.serialNumber.toLowerCase().includes(term)) ||
-        (asset.department && asset.department.toLowerCase().includes(term)) ||
-        (asset.assignedToName && asset.assignedToName.toLowerCase().includes(term))
-      );
-    }
-    
-    // 按类型筛选
-    if (this.typeFilter()) {
-      filtered = filtered.filter(asset => asset.type === this.typeFilter());
-    }
-    
-    // 按状态筛选
-    if (this.statusFilter()) {
-      filtered = filtered.filter(asset => asset.status === this.statusFilter());
-    }
-    
-    // 按部门筛选
-    if (this.departmentFilter()) {
-      filtered = filtered.filter(asset => asset.department === this.departmentFilter());
-    }
-    
-    return filtered;
-  });
-  
-  // 资产统计
-  assetStats = computed(() => {
-    const total = this.assets().length;
-    const occupied = this.assets().filter(a => a.status === '占用').length;
-    const idle = this.assets().filter(a => a.status === '空闲').length;
-    const faulty = this.assets().filter(a => a.status === '故障').length;
-    const repairing = this.assets().filter(a => a.status === '报修中').length;
-    
-    const totalValue = this.assets().reduce((sum, asset) => sum + asset.value, 0);
-    
-    // 按类型统计
-    const typeStats = new Map<AssetType, { count: number, value: number }>();
-    this.assets().forEach(asset => {
-      const current = typeStats.get(asset.type) || { count: 0, value: 0 };
-      current.count++;
-      current.value += asset.value;
-      typeStats.set(asset.type, current);
-    });
-    
-    // 转换为数组格式以便在模板中使用
-    const typeStatsArray = Array.from(typeStats.entries()).map(([key, value]) => ({
-      type: key,
-      ...value
-    }));
-    
-    // 按部门统计
-    const departmentStats = new Map<string, { count: number, value: number }>();
-    this.assets().forEach(asset => {
-      const department = asset.department || '未知部门'; // 提供默认值
-      const current = departmentStats.get(department) || { count: 0, value: 0 };
-      current.count++;
-      current.value += asset.value;
-      departmentStats.set(department, current);
-    });
-    
-    // 转换为数组格式以便在模板中使用
-    const departmentStatsArray = Array.from(departmentStats.entries()).map(([key, value]) => ({
-      department: key,
-      ...value
-    }));
-    
-    // 使用时长统计(模拟数据)
-    const usageStats = [
-      { type: '电脑', avgHours: Math.floor(Math.random() * 40) + 100 },
-      { type: '外设', avgHours: Math.floor(Math.random() * 30) + 80 },
-      { type: '软件账号', avgHours: Math.floor(Math.random() * 50) + 120 },
-      { type: '域名', avgHours: Math.floor(Math.random() * 20) + 60 },
-      { type: '其他', avgHours: Math.floor(Math.random() * 20) + 40 }
-    ];
-    
-    return {
-      total,
-      occupied,
-      idle,
-      faulty,
-      repairing,
-      totalValue,
-      typeStats,
-      typeStatsArray,
-      departmentStats,
-      departmentStatsArray,
-      usageStats
-    };
-  });
-  
-  // 获取资产类型列表
-  assetTypes = computed(() => {
-    return Array.from(new Set(this.assets().map(asset => asset.type)));
-  });
-  
-  // 获取部门列表
-  departments = computed(() => {
-    return Array.from(new Set(this.assets().map(asset => asset.department)));
-  });
-  
-  constructor(private dialog: MatDialog) {}
-  
-  ngOnInit() {
-    // 加载模拟数据
-    this.assets.set(generateMockAssets());
-    this.employees.set(generateMockEmployees());
-    this.assignments.set(generateMockAssignments());
-  }
-  
-  // 切换视图(网格/列表)
-  switchView(view: 'grid' | 'list') {
-    this.selectedView.set(view);
-  }
-  
-  // 格式化日期
-  formatDate(date: Date): string {
-    if (!date) return '';
-    const d = new Date(date);
-    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
-  }
-  
-  // 格式化金额
-  formatCurrency(amount: number): string {
-    return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount);
-  }
-  
-  // 获取状态样式类
-  getStatusClass(status: string): string {
-    switch (status) {
-      case '空闲':
-        return 'status-idle';
-      case '占用':
-        return 'status-occupied';
-      case '故障':
-        return 'status-faulty';
-      case '报修中':
-        return 'status-repairing';
-      default:
-        return '';
-    }
-  }
-  
-  // 获取类型图标
-  getTypeIcon(type: AssetType): string {
-    switch (type) {
-      case '电脑':
-        return 'laptop';
-      case '外设':
-        return 'devices';
-      case '软件账号':
-        return 'cloud';
-      case '域名':
-        return 'link';
-      case '其他':
-        return 'category';
-      default:
-        return 'help';
-    }
-  }
-  
-  // 打开资产分配对话框
-  openAssignmentDialog(asset: Asset, isReturning: boolean = false) {
-    let assignment: AssetAssignment | undefined;
-    if (isReturning) {
-      assignment = this.assignments().find(a => a.assetId === asset.id && a.status === '进行中');
-    }
-    
-    const dialogRef = this.dialog.open(AssetAssignmentDialog, {
-      width: '500px',
-      maxWidth: '90vw',
-      disableClose: true,
-      data: {
-        asset,
-        employees: this.employees(),
-        isEdit: !!assignment,
-        isReturning,
-        assignment
-      }
-    });
-    
-    dialogRef.afterClosed().subscribe(result => {
-      if (result) {
-        if (isReturning && assignment) {
-          // 更新归还信息
-          this.assignments.update(assignments => 
-            assignments.map(a => 
-              a.id === assignment!.id ? { ...a, endDate: result.endDate, status: '已归还' } : a
-            )
-          );
-          
-          // 更新资产状态为空闲
-          this.assets.update(assets => 
-            assets.map(a => 
-              a.id === asset.id ? { ...a, status: '空闲', assignedTo: undefined, assignedToName: undefined } : a
-            )
-          );
-        } else {
-          // 创建新分配记录
-          const newAssignment: AssetAssignment = {
-            id: `assignment-${Date.now()}`,
-            assetId: asset.id,
-            employeeId: result.employeeId,
-            startDate: new Date(result.startDate),
-            endDate: result.endDate ? new Date(result.endDate) : undefined,
-            status: '进行中'
-          };
-          
-          this.assignments.update(assignments => [newAssignment, ...assignments]);
-          
-          // 更新资产状态为占用
-          const employee = this.employees().find(e => e.id === result.employeeId);
-          this.assets.update(assets => 
-            assets.map(a => 
-              a.id === asset.id ? { ...a, status: '占用', assignedTo: result.employeeId, assignedToName: employee?.name } : a
-            )
-          );
-        }
-        alert(isReturning ? '资产归还成功' : '资产分配成功');
-      }
-    });
-  }
-  
-  // 打开报修对话框
-  openRepairDialog(asset: Asset) {
-    const dialogRef = this.dialog.open(AssetRepairDialog, {
-      width: '500px',
-      maxWidth: '90vw',
-      disableClose: true,
-      data: { asset }
-    });
-    
-    dialogRef.afterClosed().subscribe(result => {
-      if (result) {
-        // 更新资产状态为报修中
-        this.assets.update(assets => 
-          assets.map(a => 
-            a.id === asset.id ? { ...a, status: '报修中' } : a
-          )
-        );
-        alert('报修申请已提交');
-      }
-    });
-  }
-  
-  // 导出资产台账
-  exportAssetLedger() {
-    alert('资产台账导出功能待实现');
-  }
-  
-  // 重置筛选条件
-  resetFilters() {
-    this.searchTerm.set('');
-    this.typeFilter.set('');
-    this.statusFilter.set('');
-    this.departmentFilter.set('');
-  }
-  
-  // 获取资产使用情况
-  getAssetUsage(assetId: string): AssetAssignment | undefined {
-    return this.assignments().find(a => a.assetId === assetId && a.status === '进行中');
-  }
-  
-  // 获取员工姓名
-  getEmployeeName(employeeId: string): string {
-    const employee = this.employees().find(e => e.id === employeeId);
-    return employee ? employee.name : '未知员工';
-  }
-}

+ 0 - 239
src/app/pages/hr/assets/assets.html

@@ -1,239 +0,0 @@
-<div class="assets-container">
-  <header class="page-header">
-    <h1>花名册与档案库</h1>
-    <p class="page-description">管理员工全维度信息,包括基本信息、合同管理和证件管理</p>
-  </header>
-
-  <!-- 顶部操作栏 -->
-  <div class="action-bar">
-    <div class="action-buttons">
-      <button mat-raised-button color="primary" class="add-btn" (click)="openAddEmployeeDialog()">
-        <mat-icon>add</mat-icon>
-        新增员工
-      </button>
-      
-      <div class="batch-actions" [class.hidden]="selectedEmployees().length === 0">
-        <button mat-button color="warn" (click)="batchDelete()" class="batch-btn">
-          <mat-icon>delete</mat-icon>
-          批量删除
-        </button>
-        <div class="export-dropdown">
-          <button mat-button class="batch-btn" [matMenuTriggerFor]="exportMenu">
-            <mat-icon>file_download</mat-icon>
-            导出
-            <mat-icon>expand_more</mat-icon>
-          </button>
-          <mat-menu #exportMenu="matMenu">
-            <button mat-menu-item (click)="exportData('excel')">
-              <mat-icon>insert_drive_file</mat-icon>
-              Excel
-            </button>
-            <button mat-menu-item (click)="exportData('pdf')">
-              <mat-icon>picture_as_pdf</mat-icon>
-              PDF
-            </button>
-          </mat-menu>
-        </div>
-      </div>
-    </div>
-    
-    <div class="search-filters">
-      <div class="search-container">
-        <mat-icon class="search-icon">search</mat-icon>
-        <input 
-          matInput 
-          placeholder="搜索员工姓名、工号、手机号或邮箱..."
-          [value]="searchTerm()"
-          (input)="searchTerm.set($event.target.value)"
-          (keyup.enter)="applyFilters()"
-          class="search-input"
-        >
-        @if (searchTerm()) {
-          <button mat-icon-button (click)="searchTerm.set('')" class="clear-search">
-            <mat-icon>close</mat-icon>
-          </button>
-        }
-      </div>
-      
-      <div class="filter-container">
-        <mat-select placeholder="部门" [value]="departmentFilter()" (selectionChange)="departmentFilter.set($event.value); applyFilters()">
-          <mat-option value="">全部部门</mat-option>
-          @for (dept of departments; track dept.name) {
-            <mat-option [value]="dept.name">{{ dept.name }}</mat-option>
-          }
-        </mat-select>
-      </div>
-      
-      <div class="filter-container">
-        <mat-select placeholder="状态" [value]="statusFilter()" (selectionChange)="statusFilter.set($event.value); applyFilters()">
-          <mat-option value="">全部状态</mat-option>
-          @for (status of statuses; track status) {
-            <mat-option [value]="status">{{ status }}</mat-option>
-          }
-        </mat-select>
-      </div>
-      
-      <button mat-raised-button (click)="applyFilters()" class="filter-btn">
-        <mat-icon>filter_list</mat-icon>
-        筛选
-      </button>
-    </div>
-  </div>
-
-  <!-- 数据表格 -->
-  <div class="table-container">
-    <table mat-table [dataSource]="filteredEmployees()" class="employee-table">
-      <!-- 复选框列 -->
-      <ng-container matColumnDef="select">
-        <th mat-header-cell *matHeaderCellDef>
-          <mat-checkbox 
-            [checked]="isAllSelected()"
-            (change)="toggleSelectAll()"
-            [indeterminate]="selectedEmployees().length > 0 && selectedEmployees().length < filteredEmployees().length"
-          ></mat-checkbox>
-        </th>
-        <td mat-cell *matCellDef="let employee">
-          <mat-checkbox 
-            [checked]="selectedEmployees().includes(employee.id)"
-            (change)="toggleEmployeeSelection(employee.id)"
-          ></mat-checkbox>
-        </td>
-      </ng-container>
-
-      <!-- 姓名列 -->
-      <ng-container matColumnDef="name">
-        <th mat-header-cell *matHeaderCellDef class="name-column">姓名</th>
-        <td mat-cell *matCellDef="let employee" class="name-column">
-          <div class="employee-info">
-            <img [src]="employee.avatar" alt="员工头像" class="employee-avatar">
-            <span class="employee-name">{{ employee.name }}</span>
-          </div>
-        </td>
-      </ng-container>
-
-      <!-- 工号列 -->
-      <ng-container matColumnDef="employeeId">
-        <th mat-header-cell *matHeaderCellDef>工号</th>
-        <td mat-cell *matCellDef="let employee">{{ employee.employeeId }}</td>
-      </ng-container>
-
-      <!-- 部门列 -->
-      <ng-container matColumnDef="department">
-        <th mat-header-cell *matHeaderCellDef>部门</th>
-        <td mat-cell *matCellDef="let employee">{{ employee.department }}</td>
-      </ng-container>
-
-      <!-- 岗位列 -->
-      <ng-container matColumnDef="position">
-        <th mat-header-cell *matHeaderCellDef>岗位</th>
-        <td mat-cell *matCellDef="let employee">{{ employee.position }}</td>
-      </ng-container>
-
-      <!-- 手机号列 -->
-      <ng-container matColumnDef="phone">
-        <th mat-header-cell *matHeaderCellDef>手机号</th>
-        <td mat-cell *matCellDef="let employee">{{ employee.phone }}</td>
-      </ng-container>
-
-      <!-- 邮箱列 -->
-      <ng-container matColumnDef="email">
-        <th mat-header-cell *matHeaderCellDef>邮箱</th>
-        <td mat-cell *matCellDef="let employee">{{ employee.email }}</td>
-      </ng-container>
-
-      <!-- 入职日期列 -->
-      <ng-container matColumnDef="hireDate">
-        <th mat-header-cell *matHeaderCellDef>入职日期</th>
-        <td mat-cell *matCellDef="let employee">{{ formatDate(employee.hireDate) }}</td>
-      </ng-container>
-
-      <!-- 状态列 -->
-      <ng-container matColumnDef="status">
-        <th mat-header-cell *matHeaderCellDef>状态</th>
-        <td mat-cell *matCellDef="let employee">
-          <mat-select [value]="employee.status" (selectionChange)="changeEmployeeStatus(employee, $event.value)" class="status-select">
-            @for (status of statuses; track status) {
-              <mat-option [value]="status">{{ status }}</mat-option>
-            }
-          </mat-select>
-        </td>
-      </ng-container>
-
-      <!-- 合同列 -->
-      <ng-container matColumnDef="contract">
-        <th mat-header-cell *matHeaderCellDef>合同</th>
-        <td mat-cell *matCellDef="let employee">
-          @if (employee.contract) {
-            <div class="contract-info">
-              <div class="contract-date">{{ formatDate(employee.contract.startDate) }} - {{ formatDate(employee.contract.endDate) }}</div>
-              @if (employee.contract.isExpiringSoon) {
-                <div class="expiring-soon" matTooltip="合同即将到期">
-                  ⚠️ 即将到期
-                </div>
-              }
-              <button mat-icon-button class="contract-btn" matTooltip="查看合同">
-                <mat-icon>description</mat-icon>
-              </button>
-            </div>
-          } @else {
-            <div class="no-contract">
-              无合同信息
-            </div>
-          }
-        </td>
-      </ng-container>
-
-      <!-- 操作列 -->
-      <ng-container matColumnDef="actions">
-        <th mat-header-cell *matHeaderCellDef>操作</th>
-        <td mat-cell *matCellDef="let employee" class="actions-column">
-          <div class="action-buttons">
-            <button mat-icon-button class="edit-btn" matTooltip="编辑" (click)="editEmployee(employee)">
-              <mat-icon>edit</mat-icon>
-            </button>
-            <button mat-icon-button class="delete-btn" matTooltip="删除" (click)="deleteEmployee(employee.id)">
-              <mat-icon>delete</mat-icon>
-            </button>
-            <button mat-icon-button class="view-btn" matTooltip="查看详情">
-              <mat-icon>visibility</mat-icon>
-            </button>
-          </div>
-        </td>
-      </ng-container>
-
-      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
-      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
-
-      <!-- 空数据状态 -->
-      @if (filteredEmployees().length === 0) {
-        <tr class="mat-row">
-          <td class="mat-cell empty-state" [attr.colspan]="displayedColumns.length">
-            <div class="empty-icon">
-              <mat-icon>search_off</mat-icon>
-            </div>
-            <p>没有找到符合条件的员工</p>
-            <button mat-button (click)="searchTerm.set(''); departmentFilter.set(''); statusFilter.set(''); applyFilters()">
-              清除筛选条件
-            </button>
-          </td>
-        </tr>
-      }
-    </table>
-  </div>
-
-  <!-- 分页组件 -->
-  <div class="pagination">
-    <div class="pagination-info">
-      共 {{ filteredEmployees().length }} 条记录,当前显示第 {{ pageIndex * pageSize + 1 }} - 
-      {{ Math.min((pageIndex + 1) * pageSize, filteredEmployees().length) }} 条
-    </div>
-    <mat-paginator
-      [length]="filteredEmployees().length"
-      [pageSize]="pageSize"
-      [pageSizeOptions]="[10, 20, 50]"
-      [pageIndex]="pageIndex"
-      (page)="onPageChange($event)"
-      showFirstLastButtons
-    ></mat-paginator>
-  </div>
-</div>

+ 0 - 725
src/app/pages/hr/assets/assets.scss

@@ -1,725 +0,0 @@
-// 自定义主题
-$primary-color: #1e40af; // 深蓝主色,传递可靠感
-$primary-light: #3b82f6; // 浅蓝色,用于悬停效果
-$secondary-color: #0d9488; // 薄荷绿,作为强调色
-$success-color: #10b981; // 成功色
-$warning-color: #f59e0b; // 警告色(浅橙)
-$error-color: #ef4444; // 错误色
-$text-primary: #1f2937; // 主要文本色
-$text-secondary: #4b5563; // 次要文本色
-$text-tertiary: #9ca3af; // 辅助文本色
-$bg-primary: #ffffff; // 主背景色
-$bg-secondary: #f9fafb; // 次要背景色
-$bg-tertiary: #f3f4f6; // 辅助背景色
-$border-color: #e5e7eb; // 边框色
-$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
-$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
-$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
-$border-radius: 8px;
-$transition: all 0.2s ease;
-
-// 主容器样式
-.assets-container {
-  padding: 24px;
-  min-height: 100vh;
-  background-color: $bg-secondary;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-}
-
-// 页面标题
-.page-header {
-  margin-bottom: 24px;
-  padding-bottom: 16px;
-  border-bottom: 2px solid $border-color;
-
-  h1 {
-    font-size: 28px;
-    font-weight: 700;
-    color: $text-primary;
-    margin: 0 0 8px 0;
-  }
-
-  .page-description {
-    font-size: 16px;
-    color: $text-secondary;
-    margin: 0;
-  }
-}
-
-// 顶部操作栏
-.action-bar {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 24px;
-  padding: 16px 20px;
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-  flex-wrap: wrap;
-  gap: 16px;
-
-  .action-buttons {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    flex-wrap: wrap;
-  }
-
-  .add-btn {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    padding: 10px 20px;
-    background-color: $primary-color;
-    color: white;
-    border: none;
-    border-radius: $border-radius;
-    font-size: 16px;
-    font-weight: 500;
-    cursor: pointer;
-    transition: $transition;
-
-    &:hover {
-      background-color: $primary-light;
-      transform: translateY(-1px);
-      box-shadow: $shadow-md;
-    }
-
-    &:active {
-      transform: translateY(0);
-      box-shadow: $shadow-sm;
-    }
-  }
-
-  .batch-actions {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    animation: slideIn 0.3s ease-out;
-
-    &.hidden {
-      display: none;
-    }
-  }
-
-  .batch-btn {
-    display: flex;
-    align-items: center;
-    gap: 6px;
-    padding: 8px 12px;
-    border: 1px solid $border-color;
-    border-radius: $border-radius;
-    background-color: $bg-primary;
-    color: $text-secondary;
-    font-size: 14px;
-    cursor: pointer;
-    transition: $transition;
-
-    &:hover {
-      background-color: $bg-tertiary;
-      border-color: $text-tertiary;
-    }
-
-    &:active {
-      transform: scale(0.98);
-    }
-  }
-
-  .search-filters {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    flex-wrap: wrap;
-  }
-
-  .search-container {
-    position: relative;
-    display: flex;
-    align-items: center;
-    min-width: 280px;
-
-    .search-icon {
-      position: absolute;
-      left: 12px;
-      color: $text-tertiary;
-      z-index: 1;
-    }
-
-    .search-input {
-      width: 100%;
-      padding: 10px 12px 10px 40px;
-      border: 1px solid $border-color;
-      border-radius: $border-radius;
-      font-size: 14px;
-      color: $text-primary;
-      background-color: $bg-primary;
-      transition: $transition;
-
-      &:focus {
-        outline: none;
-        border-color: $primary-color;
-        box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
-      }
-
-      &::placeholder {
-        color: $text-tertiary;
-      }
-    }
-
-    .clear-search {
-      position: absolute;
-      right: 8px;
-      color: $text-tertiary;
-      padding: 4px;
-      transition: $transition;
-
-      &:hover {
-        color: $text-primary;
-      }
-    }
-  }
-
-  .filter-container {
-    min-width: 150px;
-  }
-
-  .filter-btn {
-    display: flex;
-    align-items: center;
-    gap: 6px;
-    padding: 10px 16px;
-    background-color: $secondary-color;
-    color: white;
-    border: none;
-    border-radius: $border-radius;
-    font-size: 14px;
-    font-weight: 500;
-    cursor: pointer;
-    transition: $transition;
-
-    &:hover {
-      background-color: #0f766e;
-      transform: translateY(-1px);
-      box-shadow: $shadow-md;
-    }
-
-    &:active {
-      transform: translateY(0);
-      box-shadow: $shadow-sm;
-    }
-  }
-}
-
-// 表格容器
-.table-container {
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-  overflow: hidden;
-  margin-bottom: 24px;
-}
-
-// 员工表格
-.employee-table {
-  width: 100%;
-
-  .mat-header-row {
-    background-color: $bg-tertiary;
-    font-weight: 600;
-    color: $text-primary;
-
-    .mat-header-cell {
-      padding: 16px;
-      font-size: 14px;
-      color: $text-primary;
-      font-weight: 600;
-      border-bottom: 1px solid $border-color;
-    }
-  }
-
-  .mat-row {
-    transition: $transition;
-    cursor: pointer;
-
-    &:hover {
-      background-color: color-mix(in srgb, $primary-color 2%, transparent);
-      transform: translateY(-1px);
-    }
-
-    .mat-cell {
-      padding: 16px;
-      font-size: 14px;
-      color: $text-secondary;
-      border-bottom: 1px solid $border-color;
-      vertical-align: middle;
-    }
-
-    &:last-child .mat-cell {
-      border-bottom: none;
-    }
-  }
-
-  .name-column {
-    min-width: 150px;
-
-    .employee-info {
-      display: flex;
-      align-items: center;
-      gap: 10px;
-    }
-
-    .employee-avatar {
-      width: 36px;
-      height: 36px;
-      border-radius: 50%;
-      object-fit: cover;
-    }
-
-    .employee-name {
-      font-weight: 500;
-      color: $text-primary;
-    }
-  }
-
-  .actions-column {
-    min-width: 120px;
-
-    .action-buttons {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-    }
-
-    .edit-btn,
-    .delete-btn,
-    .view-btn {
-      color: $text-tertiary;
-      transition: $transition;
-      opacity: 0.8;
-
-      &:hover {
-        opacity: 1;
-        transform: scale(1.1);
-      }
-
-      &.edit-btn:hover {
-        color: $primary-color;
-      }
-
-      &.delete-btn:hover {
-        color: $error-color;
-      }
-
-      &.view-btn:hover {
-        color: $secondary-color;
-      }
-    }
-  }
-
-  // 合同信息样式
-  .contract-info {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-
-    .contract-date {
-      font-size: 12px;
-      color: $text-secondary;
-    }
-
-    .expiring-soon {
-      background-color: color-mix(in srgb, $warning-color 15%, transparent);
-      color: $warning-color;
-      padding: 2px 8px;
-      border-radius: 12px;
-      font-size: 11px;
-      font-weight: 500;
-      animation: pulse 2s infinite;
-    }
-
-    .contract-btn {
-      color: $primary-color;
-      transition: $transition;
-
-      &:hover {
-        color: $primary-light;
-        transform: scale(1.1);
-      }
-    }
-  }
-
-  .no-contract {
-    color: $text-tertiary;
-    font-size: 12px;
-    font-style: italic;
-  }
-
-  // 状态选择器样式
-  .status-select {
-    min-width: 100px;
-    .mat-select-trigger {
-      font-size: 14px;
-    }
-  }
-
-  // 空状态样式
-  .empty-state {
-    text-align: center;
-    padding: 60px 20px;
-
-    .empty-icon {
-      font-size: 48px;
-      color: $text-tertiary;
-      margin-bottom: 16px;
-    }
-
-    p {
-      color: $text-secondary;
-      font-size: 16px;
-      margin-bottom: 24px;
-    }
-  }
-}
-
-// 分页组件
-.pagination {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 16px;
-  background-color: $bg-primary;
-  border-radius: $border-radius;
-  box-shadow: $shadow-sm;
-
-  .pagination-info {
-    font-size: 14px;
-    color: $text-secondary;
-  }
-
-  .mat-paginator {
-    display: flex;
-    justify-content: flex-end;
-    flex: 1;
-  }
-}
-
-// 自定义对话框样式 - 以深蓝色和白色为主色调
-.add-employee-dialog-container {
-  .mat-dialog-container {
-    background-color: #1e293b;
-    color: white;
-    border-radius: 12px;
-    padding: 0;
-    overflow: hidden;
-    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
-  }
-
-  .mat-dialog-title {
-    color: white;
-    font-size: 24px;
-    font-weight: 600;
-    padding: 24px 24px 0;
-    margin: 0;
-  }
-
-  .mat-dialog-content {
-    padding: 24px;
-    background-color: white;
-    color: #1e293b;
-    margin: 0;
-  }
-
-  .mat-dialog-actions {
-    padding: 16px 24px;
-    background-color: #1e293b;
-    justify-content: flex-end;
-    margin: 0;
-  }
-}
-
-.custom-dialog {
-  // 对话框容器样式
-  .mat-dialog-container {
-    border-radius: $border-radius;
-    box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.15), 0 10px 15px -6px rgba(0, 0, 0, 0.1);
-    overflow: hidden;
-    background-color: $bg-primary; // 使用白色作为主背景
-    border: 2px solid $primary-color; // 深蓝色边框
-    max-width: 700px; // 增加对话框宽度
-    width: 95vw;
-    animation: slideIn 0.3s ease-out; // 添加动画效果
-  }
-  
-  // 对话框标题样式
-  h2 {
-    font-size: 24px; // 增大标题字体
-    font-weight: 700;
-    color: $primary-color; // 使用深蓝色标题
-    margin: 0;
-  }
-  
-  // 输入框样式
-  .mat-input-element {
-    color: $text-primary;
-    font-size: 16px; // 增大输入框字体
-    font-weight: 500;
-  }
-  
-  .mat-form-field {
-    width: 100%;
-    transition: all 0.2s ease;
-  }
-  
-  .mat-form-field-appearance-fill .mat-form-field-flex {
-    background-color: $bg-primary; // 输入框白色背景
-    border-radius: $border-radius;
-    border: 1px solid $border-color;
-    transition: all 0.2s ease;
-  }
-  
-  .mat-form-field-appearance-fill .mat-form-field-outline {
-    border: none;
-  }
-  
-  .mat-form-field-appearance-fill .mat-form-field-underline {
-    display: none;
-  }
-  
-  // 输入框聚焦效果增强
-  .mat-form-field-appearance-fill .mat-form-field-focus-overlay {
-    background-color: rgba(30, 64, 175, 0.08);
-  }
-  
-  .mat-form-field:focus-within .mat-form-field-flex {
-    border-color: $primary-color;
-    box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
-  }
-  
-  // 选择框样式增强
-  .mat-select {
-    color: $text-primary;
-    font-size: 16px;
-  }
-  
-  .mat-select-panel {
-    background-color: $bg-primary;
-    border-radius: $border-radius;
-    box-shadow: $shadow-lg;
-    margin-top: 8px;
-    border: 1px solid $border-color;
-    animation: slideIn 0.2s ease-out;
-  }
-  
-  .mat-option {
-    color: $text-primary;
-    padding: 12px 20px; // 增加选项间距
-    font-size: 16px; // 增大选项字体
-    font-weight: 500;
-    transition: all 0.2s ease;
-    border-radius: $border-radius;
-    margin: 2px 8px;
-    
-    &:hover {
-      background-color: rgba(30, 64, 175, 0.1);
-      transform: translateX(4px); // 悬停时轻微移动
-    }
-    
-    &.mat-selected {
-      background-color: color-mix(in srgb, $primary-color 20%, transparent);
-      color: $primary-color;
-      font-weight: 600;
-    }
-    
-    &:active {
-      transform: scale(0.98);
-    }
-  }
-  
-  // 按钮样式优化
-  button[mat-button] {
-    color: $text-secondary;
-    font-size: 16px; // 增大按钮字体
-    font-weight: 600;
-    padding: 10px 24px;
-    transition: all 0.2s ease;
-    border-radius: $border-radius;
-    
-    &:hover {
-      color: $primary-color;
-      background-color: rgba(30, 64, 175, 0.08);
-      transform: translateY(-1px);
-    }
-    
-    &:active {
-      transform: translateY(0);
-    }
-  }
-  
-  button[mat-raised-button][color="primary"] {
-    background-color: $primary-color;
-    color: white;
-    font-size: 16px; // 增大按钮字体
-    font-weight: 600;
-    border-radius: $border-radius;
-    padding: 12px 32px;
-    transition: all 0.2s ease;
-    box-shadow: 0 4px 12px rgba(30, 64, 175, 0.3);
-    
-    &:hover {
-      background-color: $primary-light;
-      transform: translateY(-2px);
-      box-shadow: 0 6px 16px rgba(30, 64, 175, 0.4);
-    }
-    
-    &:active {
-      transform: translateY(0);
-      box-shadow: 0 4px 10px rgba(30, 64, 175, 0.3);
-    }
-    
-    &:disabled {
-      background-color: $text-tertiary;
-      transform: none;
-      box-shadow: none;
-    }
-  }
-  
-  // 日期选择器样式增强
-  .mat-datepicker-toggle {
-    color: $text-tertiary;
-    transition: all 0.2s ease;
-    
-    &:hover {
-      color: $primary-color;
-      transform: scale(1.1);
-    }
-  }
-  
-  .mat-datepicker-content {
-    border-radius: $border-radius;
-    overflow: hidden;
-    border: 1px solid $border-color;
-  }
-  
-  // 表单标签样式增强
-  label {
-    color: $text-primary;
-    font-weight: 600;
-    font-size: 16px;
-    margin-bottom: 8px;
-    display: block;
-  }
-  
-  // 必填标记样式
-  .required-mark {
-    color: $error-color;
-    font-size: 16px;
-    margin-left: 4px;
-  }
-  
-  // 错误状态样式增强
-  .mat-error {
-    color: $error-color;
-    font-size: 14px;
-    margin-top: 6px;
-    display: block;
-    font-weight: 500;
-  }
-  
-  .mat-form-field-invalid .mat-form-field-flex {
-    border-color: $error-color;
-    background-color: rgba(239, 68, 68, 0.05);
-  }
-  
-  .mat-form-field-invalid .mat-input-element {
-    color: $error-color;
-  }
-}
-
-// 自定义通知消息样式
-.success-snackbar {
-  background-color: $success-color;
-  color: white;
-}
-
-.error-snackbar {
-  background-color: $error-color;
-  color: white;
-}
-
-// 动画定义
-@keyframes slideIn {
-  from {
-    opacity: 0;
-    transform: translateY(-10px);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
-}
-
-@keyframes pulse {
-  0%, 100% {
-    opacity: 1;
-  }
-  50% {
-    opacity: 0.7;
-  }
-}
-
-// 响应式设计
-@media (max-width: 1200px) {
-  .assets-container {
-    padding: 16px;
-  }
-
-  .action-bar {
-    flex-direction: column;
-    align-items: stretch;
-
-    .search-filters {
-      justify-content: space-between;
-    }
-  }
-}
-
-@media (max-width: 768px) {
-  .assets-container {
-    padding: 12px;
-  }
-
-  .page-header {
-    text-align: center;
-  }
-
-  .action-bar {
-    padding: 12px;
-
-    .search-filters {
-      flex-direction: column;
-      align-items: stretch;
-
-      .search-container,
-      .filter-container,
-      .filter-btn {
-        width: 100%;
-        min-width: auto;
-      }
-    }
-  }
-
-  .table-container {
-    overflow-x: auto;
-  }
-
-  .pagination {
-    flex-direction: column;
-    gap: 12px;
-    align-items: center;
-
-    .mat-paginator {
-      justify-content: center;
-    }
-  }
-}

+ 0 - 522
src/app/pages/hr/assets/assets.ts

@@ -1,522 +0,0 @@
-import { Component, OnInit, signal, Inject } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
-import { MatButtonModule } from '@angular/material/button';
-import { MatInputModule } from '@angular/material/input';
-import { MatSelectModule } from '@angular/material/select';
-import { MatDatepickerModule } from '@angular/material/datepicker';
-import { MatNativeDateModule } from '@angular/material/core';
-import { MatDialogModule, MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
-import { MatTableModule } from '@angular/material/table';
-import { MatPaginatorModule } from '@angular/material/paginator';
-import { MatCheckboxModule } from '@angular/material/checkbox';
-import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
-import { MatIconModule } from '@angular/material/icon';
-import { MatTooltipModule } from '@angular/material/tooltip';
-import { MatMenuModule } from '@angular/material/menu';
-import { Employee, Department, Position, Contract, Certificate, EmployeeStatus } from '../../../models/hr.model';
-
-// 创建新增员工对话框组件
-@Component({
-  selector: 'app-add-employee-dialog',
-  standalone: true,
-  imports: [
-    CommonModule,
-    ReactiveFormsModule,
-    MatInputModule,
-    MatSelectModule,
-    MatDatepickerModule,
-    MatNativeDateModule,
-    MatButtonModule
-  ],
-  template: `
-    <div class="dialog-header">
-      <h2>新增员工</h2>
-      <button class="close-btn" (click)="dialogRef.close()">
-        <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>
-    <form [formGroup]="employeeForm" (ngSubmit)="onSubmit()" class="employee-form">
-      <div class="form-row">
-        <div class="form-group">
-          <label>员工姓名 <span class="required-mark">*</span></label>
-          <input matInput formControlName="name" placeholder="请输入姓名">
-        </div>
-        <div class="form-group">
-          <label>工号 <span class="required-mark">*</span></label>
-          <input matInput formControlName="employeeId" placeholder="请输入工号">
-        </div>
-      </div>
-      <div class="form-row">
-        <div class="form-group">
-          <label>部门 <span class="required-mark">*</span></label>
-          <mat-select formControlName="department">
-            <mat-option *ngFor="let dept of departments" [value]="dept.name">
-              {{ dept.name }}
-            </mat-option>
-          </mat-select>
-        </div>
-        <div class="form-group">
-          <label>岗位 <span class="required-mark">*</span></label>
-          <input matInput formControlName="position" placeholder="请输入岗位">
-        </div>
-      </div>
-      <div class="form-row">
-        <div class="form-group">
-          <label>手机号码 <span class="required-mark">*</span></label>
-          <input matInput formControlName="phone" placeholder="请输入手机号码">
-        </div>
-        <div class="form-group">
-          <label>邮箱 <span class="required-mark">*</span></label>
-          <input matInput formControlName="email" placeholder="请输入邮箱">
-        </div>
-      </div>
-      <div class="form-row">
-        <div class="form-group">
-          <label>性别</label>
-          <mat-select formControlName="gender">
-            <mat-option value="男">男</mat-option>
-            <mat-option value="女">女</mat-option>
-          </mat-select>
-        </div>
-        <div class="form-group">
-          <label>出生日期</label>
-          <input matInput [matDatepicker]="birthDatePicker" formControlName="birthDate">
-          <mat-datepicker-toggle matSuffix [for]="birthDatePicker"></mat-datepicker-toggle>
-          <mat-datepicker #birthDatePicker></mat-datepicker>
-        </div>
-      </div>
-      <div class="form-row">
-        <div class="form-group">
-          <label>入职日期 <span class="required-mark">*</span></label>
-          <input matInput [matDatepicker]="hireDatePicker" formControlName="hireDate">
-          <mat-datepicker-toggle matSuffix [for]="hireDatePicker"></mat-datepicker-toggle>
-          <mat-datepicker #hireDatePicker></mat-datepicker>
-        </div>
-        <div class="form-group">
-          <label>状态 <span class="required-mark">*</span></label>
-          <mat-select formControlName="status">
-            <mat-option value="在职">在职</mat-option>
-            <mat-option value="试用期">试用期</mat-option>
-            <mat-option value="离职">离职</mat-option>
-          </mat-select>
-        </div>
-      </div>
-      <div class="dialog-actions">
-        <button type="button" mat-button (click)="dialogRef.close()">取消</button>
-        <button type="submit" mat-raised-button color="primary" [disabled]="!employeeForm.valid">保存</button>
-      </div>
-    </form>
-  `,
-  styles: [`
-    .dialog-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 32px;
-      padding-bottom: 20px;
-      border-bottom: 2px solid #e5e7eb;
-    }
-    
-    .close-btn {
-      background: none;
-      border: none;
-      cursor: pointer;
-      color: #9ca3af;
-      padding: 6px;
-      border-radius: 50%;
-      transition: all 0.2s ease;
-      
-      &:hover {
-        color: #1f2937;
-        background-color: #f3f4f6;
-        transform: scale(1.15);
-      }
-      
-      &:active {
-        transform: scale(0.95);
-      }
-    }
-    
-    .employee-form {
-      max-width: 100%;
-    }
-    
-    .form-row {
-      display: flex;
-      gap: 24px;
-      margin-bottom: 24px;
-      flex-wrap: wrap;
-    }
-    
-    .form-group {
-      flex: 1;
-      min-width: 250px;
-    }
-    
-    label {
-      display: block;
-      margin-bottom: 8px;
-      font-weight: 600;
-      color: #374151;
-      font-size: 16px;
-    }
-    
-    .required-mark {
-      color: #ef4444;
-      font-size: 16px;
-    }
-    
-    .dialog-actions {
-      display: flex;
-      justify-content: flex-end;
-      gap: 16px;
-      margin-top: 32px;
-      padding-top: 20px;
-      border-top: 1px solid #e5e7eb;
-    }
-    
-    // 表单元素聚焦效果
-    .mat-form-field-appearance-fill .mat-form-field-focus-overlay {
-      background-color: rgba(30, 64, 175, 0.05);
-    }
-    
-    // 错误状态样式
-    .mat-error {
-      color: #ef4444;
-      font-size: 14px;
-      margin-top: 4px;
-      display: block;
-    }
-    
-    .mat-form-field-invalid .mat-input-element {
-      color: #ef4444;
-    }
-  `]
-}) class AddEmployeeDialog {
-  employeeForm: FormGroup;
-  departments = departmentsMock;
-  
-  constructor(
-    private fb: FormBuilder,
-    public dialogRef: MatDialogRef<AddEmployeeDialog>,
-    @Inject(MAT_DIALOG_DATA) public data: any
-  ) {
-    this.employeeForm = this.fb.group({
-      name: ['', Validators.required],
-      employeeId: ['', Validators.required],
-      department: ['', Validators.required],
-      position: ['', Validators.required],
-      phone: ['', [Validators.required, Validators.pattern(/^1[3-9]\d{9}$/)]],
-      email: ['', [Validators.required, Validators.email]],
-      gender: ['男'],
-      birthDate: [null],
-      hireDate: [new Date(), Validators.required],
-      status: ['在职', Validators.required]
-    });
-  }
-  
-  onSubmit() {
-    if (this.employeeForm.valid) {
-      this.dialogRef.close(this.employeeForm.value);
-    }
-  }
-}
-
-// 模拟数据
-const departmentsMock: Department[] = [
-  { id: '1', name: '管理层', employeeCount: 5 },
-  { id: '2', name: '设计部', employeeCount: 20 },
-  { id: '3', name: '客户服务部', employeeCount: 15 },
-  { id: '4', name: '财务部', employeeCount: 8 },
-  { id: '5', name: '人力资源部', employeeCount: 5 }
-];
-
-// 生成模拟员工数据
-const generateMockEmployees = (): Employee[] => {
-  const statuses: EmployeeStatus[] = ['在职', '试用期', '离职'];
-  const genders = ['男', '女'];
-  const departments = departmentsMock.map(d => d.name);
-  const positions = ['经理', '设计师', '客服专员', '财务专员', '人事专员', '助理'];
-  const employees: Employee[] = [];
-  
-  for (let i = 1; i <= 30; i++) {
-    const status = statuses[Math.floor(Math.random() * statuses.length)];
-    const hireDate = new Date();
-    hireDate.setMonth(hireDate.getMonth() - Math.floor(Math.random() * 36));
-    
-    // 随机生成合同信息
-    const contractEndDate = new Date(hireDate);
-    contractEndDate.setFullYear(contractEndDate.getFullYear() + 1);
-    
-    // 判断合同是否即将到期(7天内)
-    const today = new Date();
-    const daysUntilExpiry = Math.ceil((contractEndDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
-    const isExpiringSoon = daysUntilExpiry <= 7 && daysUntilExpiry >= 0;
-    
-    employees.push({
-      id: `emp-${i}`,
-      name: `员工${i}`,
-      employeeId: `EMP${2023}${String(i).padStart(3, '0')}`,
-      department: departments[Math.floor(Math.random() * departments.length)],
-      position: positions[Math.floor(Math.random() * positions.length)],
-      phone: `138${Math.floor(Math.random() * 100000000)}`,
-      email: `employee${i}@example.com`,
-      gender: genders[Math.floor(Math.random() * genders.length)],
-      birthDate: new Date(1980 + Math.floor(Math.random() * 20), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
-      hireDate,
-      status,
-      avatar: `https://picsum.photos/seed/emp${i}/40/40`,
-      contract: {
-        id: `contract-${i}`,
-        startDate: hireDate,
-        endDate: contractEndDate,
-        type: '劳动合同',
-        isExpiringSoon
-      },
-      certificates: i % 3 === 0 ? [
-        {
-          id: `cert-${i}-1`,
-          name: '身份证',
-          type: '身份证件',
-          number: `110101${Math.floor(Math.random() * 1000000000000000000)}`,
-          issueDate: new Date(2010 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
-          expiryDate: new Date(2030 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1)
-        }
-      ] : undefined
-    });
-  }
-  
-  return employees;
-};
-
-// 主组件
-@Component({
-  selector: 'app-assets',
-  standalone: true,
-  imports: [
-    CommonModule,
-    FormsModule,
-    ReactiveFormsModule,
-    MatButtonModule,
-    MatInputModule,
-    MatSelectModule,
-    MatDialogModule,
-    MatTableModule,
-    MatPaginatorModule,
-    MatCheckboxModule,
-    MatSnackBarModule,
-    MatIconModule,
-    MatTooltipModule,
-    MatMenuModule
-  ],
-  templateUrl: './assets.html',
-  styleUrl: './assets.scss'
-}) export class Assets implements OnInit {
-  // 暴露Math对象给模板使用
-  readonly Math = Math;
-  
-  // 员工数据
-  employees = signal<Employee[]>([]);
-  filteredEmployees = signal<Employee[]>([]);
-  selectedEmployees = signal<string[]>([]);
-  
-  // 搜索和筛选
-  searchTerm = signal('');
-  departmentFilter = signal('');
-  statusFilter = signal('');
-  
-  // 分页
-  pageSize = 10;
-  pageIndex = 0;
-  
-  // 表格列
-  displayedColumns: string[] = ['select', 'name', 'employeeId', 'department', 'position', 'phone', 'email', 'hireDate', 'status', 'contract', 'actions'];
-  
-  // 部门和状态选项
-  departments = departmentsMock;
-  statuses: EmployeeStatus[] = ['在职', '试用期', '离职'];
-  
-  constructor(
-    private dialog: MatDialog,
-    private snackBar: MatSnackBar
-  ) {}
-  
-  ngOnInit() {
-    // 加载模拟数据
-    this.employees.set(generateMockEmployees());
-    this.applyFilters();
-  }
-  
-  // 应用筛选
-  applyFilters() {
-    let filtered = this.employees();
-    
-    // 搜索筛选
-    if (this.searchTerm()) {
-      const term = this.searchTerm().toLowerCase();
-      filtered = filtered.filter(emp => 
-        emp.name.toLowerCase().includes(term) ||
-        emp.employeeId.toLowerCase().includes(term) ||
-        emp.phone.includes(term) ||
-        emp.email.toLowerCase().includes(term)
-      );
-    }
-    
-    // 部门筛选
-    if (this.departmentFilter()) {
-      filtered = filtered.filter(emp => emp.department === this.departmentFilter());
-    }
-    
-    // 状态筛选
-    if (this.statusFilter()) {
-      filtered = filtered.filter(emp => emp.status === this.statusFilter());
-    }
-    
-    this.filteredEmployees.set(filtered);
-  }
-  
-  // 打开新增员工对话框
-  openAddEmployeeDialog() {
-    const dialogRef = this.dialog.open(AddEmployeeDialog, {
-      width: '600px',
-      maxWidth: '90vw',
-      disableClose: true,
-      panelClass: 'custom-dialog'
-    });
-    
-    dialogRef.afterClosed().subscribe(result => {
-      if (result) {
-        const newEmployee: Employee = {
-          id: `emp-${Date.now()}`,
-          ...result,
-          avatar: `https://picsum.photos/seed/emp${Date.now()}/40/40`,
-          contract: {
-            id: `contract-${Date.now()}`,
-            startDate: result.hireDate,
-            endDate: new Date(result.hireDate),
-            type: '劳动合同',
-            isExpiringSoon: false
-          }
-        };
-        
-        // 更新合同结束日期(假设一年合同)
-        if (newEmployee.contract) {
-          newEmployee.contract.endDate.setFullYear(newEmployee.contract.endDate.getFullYear() + 1);
-        }
-        
-        this.employees.update(emps => [newEmployee, ...emps]);
-        this.applyFilters();
-        this.showSuccessMessage('员工添加成功');
-      }
-    });
-  }
-  
-  // 编辑员工
-  editEmployee(employee: Employee) {
-    // 在实际应用中,这里会打开编辑对话框
-    this.showSuccessMessage('编辑功能待实现');
-  }
-  
-  // 删除员工
-  deleteEmployee(id: string) {
-    if (confirm('确定要删除该员工吗?')) {
-      this.employees.update(emps => emps.filter(emp => emp.id !== id));
-      this.applyFilters();
-      this.showSuccessMessage('员工删除成功');
-    }
-  }
-  
-  // 批量删除
-  batchDelete() {
-    if (this.selectedEmployees().length === 0) {
-      this.showErrorMessage('请先选择要删除的员工');
-      return;
-    }
-    
-    if (confirm(`确定要删除选中的 ${this.selectedEmployees().length} 名员工吗?`)) {
-      this.employees.update(emps => emps.filter(emp => !this.selectedEmployees().includes(emp.id)));
-      this.selectedEmployees.set([]);
-      this.applyFilters();
-      this.showSuccessMessage('批量删除成功');
-    }
-  }
-  
-  // 导出数据
-  exportData(format: 'excel' | 'pdf') {
-    this.showSuccessMessage(`${format.toUpperCase()} 导出功能待实现`);
-  }
-  
-  // 切换员工选择
-  toggleEmployeeSelection(id: string) {
-    this.selectedEmployees.update(selected => {
-      if (selected.includes(id)) {
-        return selected.filter(selectedId => selectedId !== id);
-      } else {
-        return [...selected, id];
-      }
-    });
-  }
-  
-  // 全选/取消全选
-  toggleSelectAll() {
-    if (this.isAllSelected()) {
-      this.selectedEmployees.set([]);
-    } else {
-      this.selectedEmployees.set(this.filteredEmployees().map(emp => emp.id));
-    }
-  }
-  
-  // 判断是否全选
-  isAllSelected() {
-    return this.selectedEmployees().length > 0 && 
-           this.selectedEmployees().length === this.filteredEmployees().length;
-  }
-  
-  // 切换员工状态
-  changeEmployeeStatus(employee: Employee, newStatus: EmployeeStatus) {
-    if (employee.status === newStatus) return;
-    
-    this.employees.update(emps => 
-      emps.map(emp => 
-        emp.id === employee.id ? { ...emp, status: newStatus } : emp
-      )
-    );
-    this.applyFilters();
-    this.showSuccessMessage(`员工状态已更新为${newStatus}`);
-  }
-  
-  // 格式化日期
-  formatDate(date: Date | string): string {
-    if (!date) return '';
-    const d = new Date(date);
-    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
-  }
-  
-  // 显示成功消息
-  showSuccessMessage(message: string) {
-    this.snackBar.open(message, '关闭', {
-      duration: 3000,
-      verticalPosition: 'top',
-      panelClass: 'success-snackbar'
-    });
-  }
-  
-  // 显示错误消息
-  showErrorMessage(message: string) {
-    this.snackBar.open(message, '关闭', {
-      duration: 3000,
-      verticalPosition: 'top',
-      panelClass: 'error-snackbar'
-    });
-  }
-  
-  // 分页事件处理
-  onPageChange(event: any) {
-    this.pageIndex = event.pageIndex;
-    this.pageSize = event.pageSize;
-  }
-}

+ 1 - 5
src/app/pages/hr/hr-layout/hr-layout.html

@@ -37,11 +37,7 @@
           <span matListItemTitle *ngIf="isExpanded">考勤统计</span>
         </a>
 
-        <!-- 资产管理 -->
-        <a mat-list-item routerLink="/hr/assets" routerLinkActive="active-link">
-          <mat-icon matListItemIcon>inventory</mat-icon>
-          <span matListItemTitle *ngIf="isExpanded">资产管理</span>
-        </a>
+
       </mat-nav-list>
 
       <!-- 导航栏底部 -->