logs.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. import { Component, OnInit, signal } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { RouterModule } from '@angular/router';
  4. import { FormsModule } from '@angular/forms';
  5. import { MatButtonModule } from '@angular/material/button';
  6. import { MatIconModule } from '@angular/material/icon';
  7. import { MatTableModule } from '@angular/material/table';
  8. import { MatInputModule } from '@angular/material/input';
  9. import { MatSelectModule } from '@angular/material/select';
  10. import { MatPaginatorModule } from '@angular/material/paginator';
  11. import { MatSortModule } from '@angular/material/sort';
  12. import { MatDatepickerModule } from '@angular/material/datepicker';
  13. import { MatFormFieldModule } from '@angular/material/form-field';
  14. interface LogEntry {
  15. id: string;
  16. timestamp: string;
  17. user: string;
  18. action: string;
  19. resource: string;
  20. details: string;
  21. ipAddress: string;
  22. status: 'success' | 'error' | 'warning';
  23. }
  24. @Component({
  25. selector: 'app-logs',
  26. standalone: true,
  27. imports: [
  28. CommonModule,
  29. RouterModule,
  30. FormsModule,
  31. MatButtonModule,
  32. MatIconModule,
  33. MatTableModule,
  34. MatInputModule,
  35. MatSelectModule,
  36. MatPaginatorModule,
  37. MatSortModule,
  38. MatDatepickerModule,
  39. MatFormFieldModule
  40. ],
  41. templateUrl: './logs.html',
  42. styleUrl: './logs.scss'
  43. })
  44. export class Logs implements OnInit {
  45. // 日志数据
  46. logs = signal<LogEntry[]>([]);
  47. filteredLogs = signal<LogEntry[]>([]);
  48. // 筛选条件
  49. searchTerm = '';
  50. actionFilter = '';
  51. resourceFilter = '';
  52. statusFilter = '';
  53. startDate: Date | null = null;
  54. endDate: Date | null = null;
  55. userFilter = '';
  56. // 排序和分页
  57. sortColumn = 'timestamp';
  58. sortDirection = 'desc';
  59. pageSize = 20;
  60. currentPage = 0;
  61. // 可用的筛选选项
  62. actionOptions = ['登录', '退出', '创建', '更新', '删除', '查询', '导出', '导入', '上传', '下载'];
  63. resourceOptions = ['项目', '用户', '角色', 'SOP模板', '报价规则', '绩效规则', '系统设置', '客户信息', '案例库'];
  64. statusOptions = [
  65. { value: 'success', label: '成功' },
  66. { value: 'error', label: '错误' },
  67. { value: 'warning', label: '警告' }
  68. ];
  69. userOptions: string[] = [];
  70. // 状态颜色映射
  71. statusColors: Record<string, string> = {
  72. 'success': '#00B42A',
  73. 'error': '#F53F3F',
  74. 'warning': '#FFAA00'
  75. };
  76. // 状态图标映射
  77. statusIcons: Record<string, string> = {
  78. 'success': 'check_circle',
  79. 'error': 'error',
  80. 'warning': 'warning'
  81. };
  82. constructor() {}
  83. ngOnInit(): void {
  84. this.loadLogs();
  85. }
  86. loadLogs(): void {
  87. // 模拟日志数据
  88. const mockLogs: LogEntry[] = [
  89. {
  90. id: '1',
  91. timestamp: '2025-09-15 14:30:25',
  92. user: '超级管理员',
  93. action: '登录',
  94. resource: '系统',
  95. details: '用户登录成功',
  96. ipAddress: '192.168.1.100',
  97. status: 'success'
  98. },
  99. {
  100. id: '2',
  101. timestamp: '2025-09-15 14:25:10',
  102. user: '客服小李',
  103. action: '创建',
  104. resource: '项目',
  105. details: '创建新项目:现代简约风格三居室设计',
  106. ipAddress: '192.168.1.101',
  107. status: 'success'
  108. },
  109. {
  110. id: '3',
  111. timestamp: '2025-09-15 14:20:45',
  112. user: '设计师小张',
  113. action: '更新',
  114. resource: '项目',
  115. details: '更新项目状态为:进行中',
  116. ipAddress: '192.168.1.102',
  117. status: 'success'
  118. },
  119. {
  120. id: '4',
  121. timestamp: '2025-09-15 14:15:30',
  122. user: '超级管理员',
  123. action: '创建',
  124. resource: '用户',
  125. details: '创建新用户:设计师小陈',
  126. ipAddress: '192.168.1.100',
  127. status: 'success'
  128. },
  129. {
  130. id: '5',
  131. timestamp: '2025-09-15 14:10:20',
  132. user: '财务小陈',
  133. action: '导出',
  134. resource: '财务报表',
  135. details: '导出9月份财务报表',
  136. ipAddress: '192.168.1.103',
  137. status: 'success'
  138. },
  139. {
  140. id: '6',
  141. timestamp: '2025-09-15 14:05:15',
  142. user: '系统',
  143. action: '更新',
  144. resource: '系统设置',
  145. details: '自动备份系统数据',
  146. ipAddress: '127.0.0.1',
  147. status: 'success'
  148. },
  149. {
  150. id: '7',
  151. timestamp: '2025-09-15 14:00:55',
  152. user: '客服小王',
  153. action: '上传',
  154. resource: '案例库',
  155. details: '上传新案例:北欧风格两居室设计',
  156. ipAddress: '192.168.1.104',
  157. status: 'success'
  158. },
  159. {
  160. id: '8',
  161. timestamp: '2025-09-15 13:55:30',
  162. user: '未知用户',
  163. action: '登录',
  164. resource: '系统',
  165. details: '用户登录失败:密码错误',
  166. ipAddress: '203.0.113.10',
  167. status: 'error'
  168. },
  169. {
  170. id: '9',
  171. timestamp: '2025-09-15 13:50:20',
  172. user: '组长老王',
  173. action: '审核',
  174. resource: '项目',
  175. details: '审核通过项目设计方案',
  176. ipAddress: '192.168.1.105',
  177. status: 'success'
  178. },
  179. {
  180. id: '10',
  181. timestamp: '2025-09-15 13:45:15',
  182. user: '设计师小李',
  183. action: '更新',
  184. resource: '任务',
  185. details: '完成任务:深化设计',
  186. ipAddress: '192.168.1.106',
  187. status: 'success'
  188. },
  189. {
  190. id: '11',
  191. timestamp: '2025-09-15 13:40:40',
  192. user: '超级管理员',
  193. action: '更新',
  194. resource: '角色',
  195. details: '修改角色权限:财务',
  196. ipAddress: '192.168.1.100',
  197. status: 'success'
  198. },
  199. {
  200. id: '12',
  201. timestamp: '2025-09-15 13:35:25',
  202. user: '客服小李',
  203. action: '查询',
  204. resource: '客户信息',
  205. details: '查询客户:王先生',
  206. ipAddress: '192.168.1.101',
  207. status: 'success'
  208. },
  209. {
  210. id: '13',
  211. timestamp: '2025-09-15 13:30:10',
  212. user: '系统',
  213. action: '警告',
  214. resource: '系统资源',
  215. details: '磁盘空间不足,请及时清理',
  216. ipAddress: '127.0.0.1',
  217. status: 'warning'
  218. },
  219. {
  220. id: '14',
  221. timestamp: '2025-09-15 13:25:55',
  222. user: '设计师小张',
  223. action: '上传',
  224. resource: '项目文件',
  225. details: '上传设计文件:客厅效果图.png',
  226. ipAddress: '192.168.1.102',
  227. status: 'success'
  228. },
  229. {
  230. id: '15',
  231. timestamp: '2025-09-15 13:20:30',
  232. user: '财务小陈',
  233. action: '更新',
  234. resource: '项目财务',
  235. details: '更新项目预算:+20000元',
  236. ipAddress: '192.168.1.103',
  237. status: 'success'
  238. },
  239. {
  240. id: '16',
  241. timestamp: '2025-09-15 13:15:20',
  242. user: '人事小郑',
  243. action: '更新',
  244. resource: '用户',
  245. details: '更新用户信息:设计师小李',
  246. ipAddress: '192.168.1.107',
  247. status: 'success'
  248. },
  249. {
  250. id: '17',
  251. timestamp: '2025-09-15 13:10:15',
  252. user: '超级管理员',
  253. action: '删除',
  254. resource: '用户',
  255. details: '删除用户:测试账号',
  256. ipAddress: '192.168.1.100',
  257. status: 'success'
  258. },
  259. {
  260. id: '18',
  261. timestamp: '2025-09-15 13:05:50',
  262. user: '系统',
  263. action: '错误',
  264. resource: '数据库',
  265. details: '数据库连接临时中断,已自动恢复',
  266. ipAddress: '127.0.0.1',
  267. status: 'error'
  268. },
  269. {
  270. id: '19',
  271. timestamp: '2025-09-15 13:00:35',
  272. user: '客服小王',
  273. action: '更新',
  274. resource: '客户信息',
  275. details: '更新客户联系方式',
  276. ipAddress: '192.168.1.104',
  277. status: 'success'
  278. },
  279. {
  280. id: '20',
  281. timestamp: '2025-09-15 12:55:20',
  282. user: '组长老王',
  283. action: '查询',
  284. resource: '团队绩效',
  285. details: '查看团队本月绩效报表',
  286. ipAddress: '192.168.1.105',
  287. status: 'success'
  288. }
  289. ];
  290. // 生成更多日志数据以模拟大量记录
  291. const generatedLogs: LogEntry[] = [];
  292. let idCounter = 21;
  293. // 复制模拟数据多次以创建大量日志
  294. for (let i = 0; i < 5; i++) {
  295. mockLogs.forEach(log => {
  296. // 为每条日志创建一个变体,更改时间戳
  297. const logDate = new Date(log.timestamp);
  298. logDate.setHours(logDate.getHours() - i * 2);
  299. logDate.setMinutes(Math.floor(Math.random() * 60));
  300. logDate.setSeconds(Math.floor(Math.random() * 60));
  301. generatedLogs.push({
  302. ...log,
  303. id: idCounter.toString(),
  304. timestamp: logDate.toLocaleString('zh-CN', {
  305. year: 'numeric',
  306. month: '2-digit',
  307. day: '2-digit',
  308. hour: '2-digit',
  309. minute: '2-digit',
  310. second: '2-digit'
  311. }).replace(/\//g, '-'),
  312. // 随机修改一些细节
  313. details: i % 2 === 0 ? log.details : `${log.details} (变体${i})`
  314. });
  315. idCounter++;
  316. });
  317. }
  318. // 合并原始数据和生成的数据
  319. this.logs.set([...mockLogs, ...generatedLogs].sort((a, b) =>
  320. new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
  321. ));
  322. // 提取唯一的用户列表用于筛选
  323. this.userOptions = Array.from(new Set(this.logs().map(log => log.user))).sort();
  324. // 应用初始筛选
  325. this.applyFilters();
  326. }
  327. applyFilters(): void {
  328. let result = [...this.logs()];
  329. // 搜索词筛选
  330. if (this.searchTerm) {
  331. const term = this.searchTerm.toLowerCase();
  332. result = result.filter(log =>
  333. log.user.toLowerCase().includes(term) ||
  334. log.action.toLowerCase().includes(term) ||
  335. log.resource.toLowerCase().includes(term) ||
  336. log.details.toLowerCase().includes(term) ||
  337. log.ipAddress.toLowerCase().includes(term)
  338. );
  339. }
  340. // 操作类型筛选
  341. if (this.actionFilter) {
  342. result = result.filter(log => log.action === this.actionFilter);
  343. }
  344. // 资源类型筛选
  345. if (this.resourceFilter) {
  346. result = result.filter(log => log.resource === this.resourceFilter);
  347. }
  348. // 状态筛选
  349. if (this.statusFilter) {
  350. result = result.filter(log => log.status === this.statusFilter);
  351. }
  352. // 用户筛选
  353. if (this.userFilter) {
  354. result = result.filter(log => log.user === this.userFilter);
  355. }
  356. // 日期范围筛选
  357. if (this.startDate) {
  358. const startDateTime = new Date(this.startDate).setHours(0, 0, 0, 0);
  359. result = result.filter(log => new Date(log.timestamp).getTime() >= startDateTime);
  360. }
  361. if (this.endDate) {
  362. const endDateTime = new Date(this.endDate).setHours(23, 59, 59, 999);
  363. result = result.filter(log => new Date(log.timestamp).getTime() <= endDateTime);
  364. }
  365. // 排序
  366. result.sort((a, b) => {
  367. if (this.sortColumn === 'timestamp') {
  368. return this.sortDirection === 'asc'
  369. ? new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
  370. : new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
  371. } else {
  372. const valueA = a[this.sortColumn as keyof LogEntry]?.toString().toLowerCase() || '';
  373. const valueB = b[this.sortColumn as keyof LogEntry]?.toString().toLowerCase() || '';
  374. return this.sortDirection === 'asc'
  375. ? valueA.localeCompare(valueB)
  376. : valueB.localeCompare(valueA);
  377. }
  378. });
  379. this.filteredLogs.set(result);
  380. this.currentPage = 0; // 重置到第一页
  381. }
  382. // 筛选相关方法
  383. onSearch(): void {
  384. this.applyFilters();
  385. }
  386. onFilterChange(): void {
  387. this.applyFilters();
  388. }
  389. onSort(column: string): void {
  390. if (this.sortColumn === column) {
  391. this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
  392. } else {
  393. this.sortColumn = column;
  394. this.sortDirection = 'asc';
  395. }
  396. this.applyFilters();
  397. }
  398. // 分页相关方法
  399. get paginatedLogs(): LogEntry[] {
  400. const startIndex = this.currentPage * this.pageSize;
  401. return this.filteredLogs().slice(startIndex, startIndex + this.pageSize);
  402. }
  403. get totalPages(): number {
  404. return Math.ceil(this.filteredLogs().length / this.pageSize);
  405. }
  406. onPageChange(page: number): void {
  407. this.currentPage = page;
  408. }
  409. // 导出日志
  410. exportLogs(): void {
  411. // 模拟导出功能
  412. alert(`已导出 ${this.filteredLogs().length} 条日志记录`);
  413. }
  414. // 清除筛选条件
  415. clearFilters(): void {
  416. this.searchTerm = '';
  417. this.actionFilter = '';
  418. this.resourceFilter = '';
  419. this.statusFilter = '';
  420. this.startDate = null;
  421. this.endDate = null;
  422. this.userFilter = '';
  423. this.applyFilters();
  424. }
  425. // 格式化日期显示
  426. formatDate(dateString: string): string {
  427. const date = new Date(dateString);
  428. return date.toLocaleString('zh-CN', {
  429. year: 'numeric',
  430. month: '2-digit',
  431. day: '2-digit',
  432. hour: '2-digit',
  433. minute: '2-digit',
  434. second: '2-digit'
  435. });
  436. }
  437. // 获取状态文本
  438. getStatusText(status: string): string {
  439. const statusMap = {
  440. 'success': '成功',
  441. 'error': '错误',
  442. 'warning': '警告'
  443. };
  444. return statusMap[status as keyof typeof statusMap] || status;
  445. }
  446. // 计算成功日志数量
  447. get successLogsCount(): number {
  448. return this.logs().filter(log => log.status === 'success').length;
  449. }
  450. // 计算错误日志数量
  451. get errorLogsCount(): number {
  452. return this.logs().filter(log => log.status === 'error').length;
  453. }
  454. // 计算警告日志数量
  455. get warningLogsCount(): number {
  456. return this.logs().filter(log => log.status === 'warning').length;
  457. }
  458. // 封装Math对象的方法
  459. mathMin(a: number, b: number): number {
  460. return Math.min(a, b);
  461. }
  462. // 获取页码数组
  463. getPageNumbers(): number[] {
  464. const pages = [];
  465. const totalPages = this.totalPages;
  466. const currentPage = this.currentPage;
  467. const maxVisiblePages = 5;
  468. let startPage = Math.max(0, currentPage - Math.floor(maxVisiblePages / 2));
  469. let endPage = startPage + maxVisiblePages - 1;
  470. if (endPage >= totalPages) {
  471. endPage = totalPages - 1;
  472. startPage = Math.max(0, endPage - maxVisiblePages + 1);
  473. }
  474. for (let i = startPage; i <= endPage; i++) {
  475. pages.push(i);
  476. }
  477. return pages;
  478. }
  479. }