import { Component, OnInit, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTableModule } from '@angular/material/table'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSortModule } from '@angular/material/sort'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatFormFieldModule } from '@angular/material/form-field'; interface LogEntry { id: string; timestamp: string; user: string; action: string; resource: string; details: string; ipAddress: string; status: 'success' | 'error' | 'warning'; } @Component({ selector: 'app-logs', standalone: true, imports: [ CommonModule, RouterModule, FormsModule, MatButtonModule, MatIconModule, MatTableModule, MatInputModule, MatSelectModule, MatPaginatorModule, MatSortModule, MatDatepickerModule, MatFormFieldModule ], templateUrl: './logs.html', styleUrl: './logs.scss' }) export class Logs implements OnInit { // 日志数据 logs = signal([]); filteredLogs = signal([]); // 筛选条件 searchTerm = ''; actionFilter = ''; resourceFilter = ''; statusFilter = ''; startDate: Date | null = null; endDate: Date | null = null; userFilter = ''; // 排序和分页 sortColumn = 'timestamp'; sortDirection = 'desc'; pageSize = 20; currentPage = 0; // 可用的筛选选项 actionOptions = ['登录', '退出', '创建', '更新', '删除', '查询', '导出', '导入', '上传', '下载']; resourceOptions = ['项目', '用户', '角色', 'SOP模板', '报价规则', '绩效规则', '系统设置', '客户信息', '案例库']; statusOptions = [ { value: 'success', label: '成功' }, { value: 'error', label: '错误' }, { value: 'warning', label: '警告' } ]; userOptions: string[] = []; // 状态颜色映射 statusColors: Record = { 'success': '#00B42A', 'error': '#F53F3F', 'warning': '#FFAA00' }; // 状态图标映射 statusIcons: Record = { 'success': 'check_circle', 'error': 'error', 'warning': 'warning' }; constructor() {} ngOnInit(): void { this.loadLogs(); } loadLogs(): void { // 模拟日志数据 const mockLogs: LogEntry[] = [ { id: '1', timestamp: '2025-09-15 14:30:25', user: '超级管理员', action: '登录', resource: '系统', details: '用户登录成功', ipAddress: '192.168.1.100', status: 'success' }, { id: '2', timestamp: '2025-09-15 14:25:10', user: '客服小李', action: '创建', resource: '项目', details: '创建新项目:现代简约风格三居室设计', ipAddress: '192.168.1.101', status: 'success' }, { id: '3', timestamp: '2025-09-15 14:20:45', user: '设计师小张', action: '更新', resource: '项目', details: '更新项目状态为:进行中', ipAddress: '192.168.1.102', status: 'success' }, { id: '4', timestamp: '2025-09-15 14:15:30', user: '超级管理员', action: '创建', resource: '用户', details: '创建新用户:设计师小陈', ipAddress: '192.168.1.100', status: 'success' }, { id: '5', timestamp: '2025-09-15 14:10:20', user: '财务小陈', action: '导出', resource: '财务报表', details: '导出9月份财务报表', ipAddress: '192.168.1.103', status: 'success' }, { id: '6', timestamp: '2025-09-15 14:05:15', user: '系统', action: '更新', resource: '系统设置', details: '自动备份系统数据', ipAddress: '127.0.0.1', status: 'success' }, { id: '7', timestamp: '2025-09-15 14:00:55', user: '客服小王', action: '上传', resource: '案例库', details: '上传新案例:北欧风格两居室设计', ipAddress: '192.168.1.104', status: 'success' }, { id: '8', timestamp: '2025-09-15 13:55:30', user: '未知用户', action: '登录', resource: '系统', details: '用户登录失败:密码错误', ipAddress: '203.0.113.10', status: 'error' }, { id: '9', timestamp: '2025-09-15 13:50:20', user: '组长老王', action: '审核', resource: '项目', details: '审核通过项目设计方案', ipAddress: '192.168.1.105', status: 'success' }, { id: '10', timestamp: '2025-09-15 13:45:15', user: '设计师小李', action: '更新', resource: '任务', details: '完成任务:深化设计', ipAddress: '192.168.1.106', status: 'success' }, { id: '11', timestamp: '2025-09-15 13:40:40', user: '超级管理员', action: '更新', resource: '角色', details: '修改角色权限:财务', ipAddress: '192.168.1.100', status: 'success' }, { id: '12', timestamp: '2025-09-15 13:35:25', user: '客服小李', action: '查询', resource: '客户信息', details: '查询客户:王先生', ipAddress: '192.168.1.101', status: 'success' }, { id: '13', timestamp: '2025-09-15 13:30:10', user: '系统', action: '警告', resource: '系统资源', details: '磁盘空间不足,请及时清理', ipAddress: '127.0.0.1', status: 'warning' }, { id: '14', timestamp: '2025-09-15 13:25:55', user: '设计师小张', action: '上传', resource: '项目文件', details: '上传设计文件:客厅效果图.png', ipAddress: '192.168.1.102', status: 'success' }, { id: '15', timestamp: '2025-09-15 13:20:30', user: '财务小陈', action: '更新', resource: '项目财务', details: '更新项目预算:+20000元', ipAddress: '192.168.1.103', status: 'success' }, { id: '16', timestamp: '2025-09-15 13:15:20', user: '人事小郑', action: '更新', resource: '用户', details: '更新用户信息:设计师小李', ipAddress: '192.168.1.107', status: 'success' }, { id: '17', timestamp: '2025-09-15 13:10:15', user: '超级管理员', action: '删除', resource: '用户', details: '删除用户:测试账号', ipAddress: '192.168.1.100', status: 'success' }, { id: '18', timestamp: '2025-09-15 13:05:50', user: '系统', action: '错误', resource: '数据库', details: '数据库连接临时中断,已自动恢复', ipAddress: '127.0.0.1', status: 'error' }, { id: '19', timestamp: '2025-09-15 13:00:35', user: '客服小王', action: '更新', resource: '客户信息', details: '更新客户联系方式', ipAddress: '192.168.1.104', status: 'success' }, { id: '20', timestamp: '2025-09-15 12:55:20', user: '组长老王', action: '查询', resource: '团队绩效', details: '查看团队本月绩效报表', ipAddress: '192.168.1.105', status: 'success' } ]; // 生成更多日志数据以模拟大量记录 const generatedLogs: LogEntry[] = []; let idCounter = 21; // 复制模拟数据多次以创建大量日志 for (let i = 0; i < 5; i++) { mockLogs.forEach(log => { // 为每条日志创建一个变体,更改时间戳 const logDate = new Date(log.timestamp); logDate.setHours(logDate.getHours() - i * 2); logDate.setMinutes(Math.floor(Math.random() * 60)); logDate.setSeconds(Math.floor(Math.random() * 60)); generatedLogs.push({ ...log, id: idCounter.toString(), timestamp: logDate.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }).replace(/\//g, '-'), // 随机修改一些细节 details: i % 2 === 0 ? log.details : `${log.details} (变体${i})` }); idCounter++; }); } // 合并原始数据和生成的数据 this.logs.set([...mockLogs, ...generatedLogs].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() )); // 提取唯一的用户列表用于筛选 this.userOptions = Array.from(new Set(this.logs().map(log => log.user))).sort(); // 应用初始筛选 this.applyFilters(); } applyFilters(): void { let result = [...this.logs()]; // 搜索词筛选 if (this.searchTerm) { const term = this.searchTerm.toLowerCase(); result = result.filter(log => log.user.toLowerCase().includes(term) || log.action.toLowerCase().includes(term) || log.resource.toLowerCase().includes(term) || log.details.toLowerCase().includes(term) || log.ipAddress.toLowerCase().includes(term) ); } // 操作类型筛选 if (this.actionFilter) { result = result.filter(log => log.action === this.actionFilter); } // 资源类型筛选 if (this.resourceFilter) { result = result.filter(log => log.resource === this.resourceFilter); } // 状态筛选 if (this.statusFilter) { result = result.filter(log => log.status === this.statusFilter); } // 用户筛选 if (this.userFilter) { result = result.filter(log => log.user === this.userFilter); } // 日期范围筛选 if (this.startDate) { const startDateTime = new Date(this.startDate).setHours(0, 0, 0, 0); result = result.filter(log => new Date(log.timestamp).getTime() >= startDateTime); } if (this.endDate) { const endDateTime = new Date(this.endDate).setHours(23, 59, 59, 999); result = result.filter(log => new Date(log.timestamp).getTime() <= endDateTime); } // 排序 result.sort((a, b) => { if (this.sortColumn === 'timestamp') { return this.sortDirection === 'asc' ? new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() : new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); } else { const valueA = a[this.sortColumn as keyof LogEntry]?.toString().toLowerCase() || ''; const valueB = b[this.sortColumn as keyof LogEntry]?.toString().toLowerCase() || ''; return this.sortDirection === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA); } }); this.filteredLogs.set(result); this.currentPage = 0; // 重置到第一页 } // 筛选相关方法 onSearch(): void { this.applyFilters(); } onFilterChange(): void { this.applyFilters(); } onSort(column: string): void { if (this.sortColumn === column) { this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc'; } else { this.sortColumn = column; this.sortDirection = 'asc'; } this.applyFilters(); } // 分页相关方法 get paginatedLogs(): LogEntry[] { const startIndex = this.currentPage * this.pageSize; return this.filteredLogs().slice(startIndex, startIndex + this.pageSize); } get totalPages(): number { return Math.ceil(this.filteredLogs().length / this.pageSize); } onPageChange(page: number): void { this.currentPage = page; } // 导出日志 exportLogs(): void { // 模拟导出功能 alert(`已导出 ${this.filteredLogs().length} 条日志记录`); } // 清除筛选条件 clearFilters(): void { this.searchTerm = ''; this.actionFilter = ''; this.resourceFilter = ''; this.statusFilter = ''; this.startDate = null; this.endDate = null; this.userFilter = ''; this.applyFilters(); } // 格式化日期显示 formatDate(dateString: string): string { const date = new Date(dateString); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } // 获取状态文本 getStatusText(status: string): string { const statusMap = { 'success': '成功', 'error': '错误', 'warning': '警告' }; return statusMap[status as keyof typeof statusMap] || status; } // 计算成功日志数量 get successLogsCount(): number { return this.logs().filter(log => log.status === 'success').length; } // 计算错误日志数量 get errorLogsCount(): number { return this.logs().filter(log => log.status === 'error').length; } // 计算警告日志数量 get warningLogsCount(): number { return this.logs().filter(log => log.status === 'warning').length; } // 封装Math对象的方法 mathMin(a: number, b: number): number { return Math.min(a, b); } // 获取页码数组 getPageNumbers(): number[] { const pages = []; const totalPages = this.totalPages; const currentPage = this.currentPage; const maxVisiblePages = 5; let startPage = Math.max(0, currentPage - Math.floor(maxVisiblePages / 2)); let endPage = startPage + maxVisiblePages - 1; if (endPage >= totalPages) { endPage = totalPages - 1; startPage = Math.max(0, endPage - maxVisiblePages + 1); } for (let i = startPage; i <= endPage; i++) { pages.push(i); } return pages; } }