123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- 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<LogEntry[]>([]);
- filteredLogs = signal<LogEntry[]>([]);
-
- // 筛选条件
- 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<string, string> = {
- 'success': '#00B42A',
- 'error': '#F53F3F',
- 'warning': '#FFAA00'
- };
-
- // 状态图标映射
- statusIcons: Record<string, string> = {
- '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;
- }
- }
|