405 lines
13 KiB
JavaScript
405 lines
13 KiB
JavaScript
|
class SystemLog extends HTMLElement {
|
||
|
constructor() {
|
||
|
super();
|
||
|
this.attachShadow({ mode: 'open' });
|
||
|
}
|
||
|
|
||
|
connectedCallback() {
|
||
|
this.render();
|
||
|
this.addEventListeners();
|
||
|
this.loadLogs();
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
this.shadowRoot.innerHTML = `
|
||
|
<style>
|
||
|
:host {
|
||
|
display: block;
|
||
|
height: 100%;
|
||
|
background: #fff;
|
||
|
padding: 20px;
|
||
|
box-sizing: border-box;
|
||
|
}
|
||
|
|
||
|
.log-container {
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
height: 100%;
|
||
|
gap: 16px;
|
||
|
}
|
||
|
|
||
|
.toolbar {
|
||
|
display: flex;
|
||
|
gap: 16px;
|
||
|
padding: 16px;
|
||
|
background: #f5f7fa;
|
||
|
border-radius: 8px;
|
||
|
align-items: center;
|
||
|
}
|
||
|
|
||
|
.search-box {
|
||
|
flex: 1;
|
||
|
position: relative;
|
||
|
}
|
||
|
|
||
|
.search-box input {
|
||
|
width: 100%;
|
||
|
padding: 8px 12px 8px 36px;
|
||
|
border: 1px solid #dcdfe6;
|
||
|
border-radius: 4px;
|
||
|
font-size: 14px;
|
||
|
transition: all 0.3s;
|
||
|
}
|
||
|
|
||
|
.search-box input:focus {
|
||
|
border-color: #409eff;
|
||
|
outline: none;
|
||
|
}
|
||
|
|
||
|
.search-icon {
|
||
|
position: absolute;
|
||
|
left: 12px;
|
||
|
top: 50%;
|
||
|
transform: translateY(-50%);
|
||
|
width: 16px;
|
||
|
height: 16px;
|
||
|
color: #909399;
|
||
|
}
|
||
|
|
||
|
.filter-group {
|
||
|
display: flex;
|
||
|
gap: 12px;
|
||
|
}
|
||
|
|
||
|
.filter-select {
|
||
|
padding: 8px 12px;
|
||
|
border: 1px solid #dcdfe6;
|
||
|
border-radius: 4px;
|
||
|
font-size: 14px;
|
||
|
color: #606266;
|
||
|
background: #fff;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
|
||
|
.filter-select:focus {
|
||
|
border-color: #409eff;
|
||
|
outline: none;
|
||
|
}
|
||
|
|
||
|
.action-buttons {
|
||
|
display: flex;
|
||
|
gap: 8px;
|
||
|
}
|
||
|
|
||
|
.btn {
|
||
|
padding: 8px 16px;
|
||
|
border: none;
|
||
|
border-radius: 4px;
|
||
|
font-size: 14px;
|
||
|
cursor: pointer;
|
||
|
transition: all 0.3s;
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
gap: 4px;
|
||
|
}
|
||
|
|
||
|
.btn-primary {
|
||
|
background: #409eff;
|
||
|
color: white;
|
||
|
}
|
||
|
|
||
|
.btn-primary:hover {
|
||
|
background: #66b1ff;
|
||
|
}
|
||
|
|
||
|
.btn-default {
|
||
|
background: #f4f4f5;
|
||
|
color: #606266;
|
||
|
}
|
||
|
|
||
|
.btn-default:hover {
|
||
|
background: #e9e9eb;
|
||
|
}
|
||
|
|
||
|
.log-content {
|
||
|
flex: 1;
|
||
|
background: #fff;
|
||
|
border: 1px solid #e4e7ed;
|
||
|
border-radius: 8px;
|
||
|
overflow: hidden;
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
}
|
||
|
|
||
|
.log-header {
|
||
|
display: grid;
|
||
|
grid-template-columns: 180px 120px 120px 1fr;
|
||
|
padding: 12px 16px;
|
||
|
background: #f5f7fa;
|
||
|
border-bottom: 1px solid #e4e7ed;
|
||
|
font-weight: bold;
|
||
|
color: #606266;
|
||
|
}
|
||
|
|
||
|
.log-list {
|
||
|
flex: 1;
|
||
|
overflow-y: auto;
|
||
|
}
|
||
|
|
||
|
.log-item {
|
||
|
display: grid;
|
||
|
grid-template-columns: 180px 120px 120px 1fr;
|
||
|
padding: 12px 16px;
|
||
|
border-bottom: 1px solid #e4e7ed;
|
||
|
font-size: 14px;
|
||
|
color: #606266;
|
||
|
}
|
||
|
|
||
|
.log-item:hover {
|
||
|
background: #f5f7fa;
|
||
|
}
|
||
|
|
||
|
.log-level {
|
||
|
display: inline-block;
|
||
|
padding: 2px 8px;
|
||
|
border-radius: 4px;
|
||
|
font-size: 12px;
|
||
|
}
|
||
|
|
||
|
.level-info {
|
||
|
background: #e1f3d8;
|
||
|
color: #67c23a;
|
||
|
}
|
||
|
|
||
|
.level-warning {
|
||
|
background: #fdf6ec;
|
||
|
color: #e6a23c;
|
||
|
}
|
||
|
|
||
|
.level-error {
|
||
|
background: #fef0f0;
|
||
|
color: #f56c6c;
|
||
|
}
|
||
|
|
||
|
.level-debug {
|
||
|
background: #f4f4f5;
|
||
|
color: #909399;
|
||
|
}
|
||
|
|
||
|
.pagination {
|
||
|
display: flex;
|
||
|
justify-content: flex-end;
|
||
|
padding: 16px;
|
||
|
gap: 8px;
|
||
|
}
|
||
|
|
||
|
.page-btn {
|
||
|
padding: 6px 12px;
|
||
|
border: 1px solid #dcdfe6;
|
||
|
border-radius: 4px;
|
||
|
background: #fff;
|
||
|
color: #606266;
|
||
|
cursor: pointer;
|
||
|
transition: all 0.3s;
|
||
|
}
|
||
|
|
||
|
.page-btn:hover {
|
||
|
color: #409eff;
|
||
|
border-color: #c6e2ff;
|
||
|
}
|
||
|
|
||
|
.page-btn.active {
|
||
|
background: #409eff;
|
||
|
color: #fff;
|
||
|
border-color: #409eff;
|
||
|
}
|
||
|
|
||
|
.page-btn:disabled {
|
||
|
background: #f5f7fa;
|
||
|
color: #c0c4cc;
|
||
|
cursor: not-allowed;
|
||
|
}
|
||
|
</style>
|
||
|
<div class="log-container">
|
||
|
<div class="toolbar">
|
||
|
<div class="search-box">
|
||
|
<img src="assets/icons/png/search_b.png" alt="搜索" class="search-icon">
|
||
|
<input type="text" placeholder="搜索日志内容...">
|
||
|
</div>
|
||
|
<div class="filter-group">
|
||
|
<select class="filter-select" id="levelFilter">
|
||
|
<option value="">所有级别</option>
|
||
|
<option value="info">信息</option>
|
||
|
<option value="warning">警告</option>
|
||
|
<option value="error">错误</option>
|
||
|
<option value="debug">调试</option>
|
||
|
</select>
|
||
|
<select class="filter-select" id="timeFilter">
|
||
|
<option value="1h">最近1小时</option>
|
||
|
<option value="6h">最近6小时</option>
|
||
|
<option value="24h">最近24小时</option>
|
||
|
<option value="7d">最近7天</option>
|
||
|
<option value="30d">最近30天</option>
|
||
|
</select>
|
||
|
</div>
|
||
|
<div class="action-buttons">
|
||
|
<button class="btn btn-default" id="refreshBtn">
|
||
|
<img src="assets/icons/png/refresh.png" alt="刷新" class="icon-small">
|
||
|
刷新
|
||
|
</button>
|
||
|
<button class="btn btn-primary" id="exportBtn">
|
||
|
<img src="assets/icons/png/download.png" alt="导出" class="icon-small">
|
||
|
导出
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="log-content">
|
||
|
<div class="log-header">
|
||
|
<div>时间</div>
|
||
|
<div>级别</div>
|
||
|
<div>来源</div>
|
||
|
<div>内容</div>
|
||
|
</div>
|
||
|
<div class="log-list" id="logList">
|
||
|
<!-- 日志内容将通过JavaScript动态添加 -->
|
||
|
</div>
|
||
|
<div class="pagination">
|
||
|
<button class="page-btn" id="prevPage" disabled>上一页</button>
|
||
|
<button class="page-btn active">1</button>
|
||
|
<button class="page-btn">2</button>
|
||
|
<button class="page-btn">3</button>
|
||
|
<button class="page-btn" id="nextPage">下一页</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
`;
|
||
|
}
|
||
|
|
||
|
addEventListeners() {
|
||
|
// 搜索框事件
|
||
|
const searchInput = this.shadowRoot.querySelector('.search-box input');
|
||
|
searchInput.addEventListener('input', this.debounce(() => {
|
||
|
this.filterLogs();
|
||
|
}, 300));
|
||
|
|
||
|
// 级别筛选事件
|
||
|
const levelFilter = this.shadowRoot.querySelector('#levelFilter');
|
||
|
levelFilter.addEventListener('change', () => {
|
||
|
this.filterLogs();
|
||
|
});
|
||
|
|
||
|
// 时间筛选事件
|
||
|
const timeFilter = this.shadowRoot.querySelector('#timeFilter');
|
||
|
timeFilter.addEventListener('change', () => {
|
||
|
this.filterLogs();
|
||
|
});
|
||
|
|
||
|
// 刷新按钮事件
|
||
|
const refreshBtn = this.shadowRoot.querySelector('#refreshBtn');
|
||
|
refreshBtn.addEventListener('click', () => {
|
||
|
this.loadLogs();
|
||
|
});
|
||
|
|
||
|
// 导出按钮事件
|
||
|
const exportBtn = this.shadowRoot.querySelector('#exportBtn');
|
||
|
exportBtn.addEventListener('click', () => {
|
||
|
this.exportLogs();
|
||
|
});
|
||
|
|
||
|
// 分页按钮事件
|
||
|
const prevPage = this.shadowRoot.querySelector('#prevPage');
|
||
|
const nextPage = this.shadowRoot.querySelector('#nextPage');
|
||
|
prevPage.addEventListener('click', () => {
|
||
|
this.changePage(-1);
|
||
|
});
|
||
|
nextPage.addEventListener('click', () => {
|
||
|
this.changePage(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async loadLogs() {
|
||
|
try {
|
||
|
// 这里应该调用后端API获取日志数据
|
||
|
// 示例数据
|
||
|
const logs = [
|
||
|
{
|
||
|
timestamp: '2024-03-20 10:00:00',
|
||
|
level: 'info',
|
||
|
source: '系统',
|
||
|
content: '系统启动成功'
|
||
|
},
|
||
|
{
|
||
|
timestamp: '2024-03-20 10:01:00',
|
||
|
level: 'warning',
|
||
|
source: '数据库',
|
||
|
content: '数据库连接池接近最大连接数'
|
||
|
},
|
||
|
{
|
||
|
timestamp: '2024-03-20 10:02:00',
|
||
|
level: 'error',
|
||
|
source: 'API',
|
||
|
content: 'API请求超时'
|
||
|
}
|
||
|
];
|
||
|
|
||
|
this.renderLogs(logs);
|
||
|
} catch (error) {
|
||
|
console.error('加载日志失败:', error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
renderLogs(logs) {
|
||
|
const logList = this.shadowRoot.querySelector('#logList');
|
||
|
logList.innerHTML = logs.map(log => `
|
||
|
<div class="log-item">
|
||
|
<div>${log.timestamp}</div>
|
||
|
<div><span class="log-level level-${log.level}">${this.getLevelText(log.level)}</span></div>
|
||
|
<div>${log.source}</div>
|
||
|
<div>${log.content}</div>
|
||
|
</div>
|
||
|
`).join('');
|
||
|
}
|
||
|
|
||
|
getLevelText(level) {
|
||
|
const levelMap = {
|
||
|
'info': '信息',
|
||
|
'warning': '警告',
|
||
|
'error': '错误',
|
||
|
'debug': '调试'
|
||
|
};
|
||
|
return levelMap[level] || level;
|
||
|
}
|
||
|
|
||
|
filterLogs() {
|
||
|
const searchText = this.shadowRoot.querySelector('.search-box input').value.toLowerCase();
|
||
|
const levelFilter = this.shadowRoot.querySelector('#levelFilter').value;
|
||
|
const timeFilter = this.shadowRoot.querySelector('#timeFilter').value;
|
||
|
|
||
|
// 这里应该根据筛选条件调用后端API获取过滤后的日志
|
||
|
this.loadLogs();
|
||
|
}
|
||
|
|
||
|
exportLogs() {
|
||
|
// 实现日志导出功能
|
||
|
console.log('导出日志');
|
||
|
}
|
||
|
|
||
|
changePage(delta) {
|
||
|
// 实现分页功能
|
||
|
console.log('切换页面:', delta);
|
||
|
}
|
||
|
|
||
|
debounce(func, wait) {
|
||
|
let timeout;
|
||
|
return function executedFunction(...args) {
|
||
|
const later = () => {
|
||
|
clearTimeout(timeout);
|
||
|
func(...args);
|
||
|
};
|
||
|
clearTimeout(timeout);
|
||
|
timeout = setTimeout(later, wait);
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
customElements.define('system-log', SystemLog);
|