Files
Task_Report/templates/data_table.html
2025-04-18 01:20:11 +08:00

1387 lines
48 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<title>CGNCIO Task Report System</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1300px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
position: relative;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
}
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background-color: white;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #8f8f8f;
color: white;
font-weight: 500;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f5f5f5;
}
.timestamp {
color: #666;
font-size: 0.9em;
}
.status-header {
text-align: center;
padding: 20px;
background-color: #fff2d7;
border-radius: 5px;
margin-bottom: 20px;
}
.total-tasks {
color: #c48613;
font-weight: bold;
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
th, td {
padding: 8px 10px;
}
}
/* 添加导出按钮样式 */
.export-btn {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin: 10px 0;
transition: background-color 0.3s;
}
.export-btn:hover {
background-color: #45a049;
}
.button-container {
text-align: right;
margin-bottom: 0px;
}
.filter-container {
margin: 20px 0;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
justify-content: space-between;
}
.filter-group {
display: flex;
align-items: center;
gap: 10px;
}
.filter-btn {
background-color: #4CAF50;
color: white;
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.filter-btn:hover {
background-color: #45a049;
}
.filter-btn.clear {
background-color: #6c757d;
}
input[type="date"], input[type="text"] {
width: 120px;
padding: 6px;
border: 1px solid #ddd;
border-radius: 4px;
}
/* 添加返回按钮样式 */
.back-btn {
position: absolute;
top: 20px;
left: 20px;
background: none; /* 无背景 */
border: none;
font-size: 16px;
color: #666;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
padding: 10px;
transition: color 0.3s;
}
.back-btn:hover {
color: #333;
}
/* 排序按钮样式 */
.sort-btn {
cursor: pointer;
padding: 0 0px;
color: #fff;
user-select: none;
}
.sort-btn:hover {
color: #ddd;
}
/* 排序图标(箭头)样式 */
.sort-icon {
position: relative;
display: inline-block;
width: 8px;
height: 10px;
margin-left: 5px;
vertical-align: middle;
}
/* 向上箭头 */
.sort-icon:before {
content: '';
position: absolute;
top: 0;
left: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}
/* 向下箭头 */
.sort-icon:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
/* 升序时只显示向上箭头 */
.sort-asc .sort-icon:after {
display: none;
}
/* 降序时只显示向下箭头 */
.sort-desc .sort-icon:before {
display: none;
}
/* Tab 容器样式 */
.tab-container {
margin: 20px 0;
}
.tabs {
display: flex;
gap: 15px; /* 增加 tab 之间的间距 */
border-bottom: 2px solid #ff9900;
padding-bottom: 0; /* 移除底部内边距 */
margin-bottom: 20px;
position: relative; /* 用于定位伪元素 */
}
/* Tab 按钮样式 */
.tab {
padding: 12px 25px; /* 增加内边距使 tab 更大 */
cursor: pointer;
border: 2px solid #ff9900;
border-bottom: none; /* 移除底部边框 */
border-radius: 8px 8px 0 0; /* 增加圆角 */
background-color: #fff2d7; /* 浅色背景 */
color: #ff9900;
font-weight: 500;
font-size: 15px; /* 增加字体大小 */
position: relative;
bottom: -2px; /* 对齐底部边框 */
}
/* 鼠标悬停效果 */
.tab:hover {
background-color: #ffead1; /* 更浅的背景色 */
transform: translateY(-2px); /* 轻微上浮效果 */
}
/* 激活状态样式 */
.tab.active {
background-color: #ff9900;
color: white;
border-bottom: 2px solid #ff9900;
bottom: -2px;
transform: translateY(0); /* 保持位置 */
box-shadow: 0 -2px 5px rgba(0,0,0,0.1); /* 添加阴影 */
}
/* Tab 内容区域样式 */
.tab-content {
background-color: white;
border-radius: 0 0 8px 8px;
padding: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
/* 底部状态栏样式 */
.bottom-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding: 15px;
background-color: #fff2d7;
border-radius: 5px;
}
.status-info {
display: flex;
align-items: center;
}
.total-tasks {
margin-right: 20px;
color: #c48613;
font-weight: bold;
}
/* 工具统计页面样式 */
.tools-container {
display: flex;
gap: 20px;
margin-top: 20px;
}
.tool-list, .task-details {
flex: 1;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.tool-row {
cursor: pointer;
transition: background-color 0.3s;
}
.tool-row:hover {
background-color: #fff2d7;
}
.tool-row.selected {
background-color: #ffe4b3;
}
h3 {
color: #333;
margin-bottom: 5px;
padding-bottom: 5px;
}
/* 筛选器样式 */
.filter-group label {
font-weight: 500;
color: #666;
}
.filter-group input[type="date"],
.filter-group select {
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.button-group {
margin-left: auto;
display: flex;
gap: 10px;
}
.filter-btn:not(.clear) {
background-color: #00aa0e;
color: white;
}
.filter-btn:not(.clear):hover {
background-color: #01a701;
}
.filter-btn.clear {
background-color: #6c757d;
color: white;
}
.filter-btn.clear:hover {
background-color: #5a6268;
}
/* 统一容器样式 */
.statistics-container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin-top: 20px;
position: relative;
}
.statistics-content {
display: flex;
gap: 60px;
}
/* 统计区域样式 */
.statistics-section {
flex: 1;
}
/* 工具名称高亮样式 */
.tool-highlight {
background-color: #fff2d7; /* 淡橙色背景 */
padding: 2px 8px; /* 添加内边距使背景更明显 */
border-radius: 4px; /* 圆角 */
}
/* 标题样式 */
h3 {
color: #333;
margin-bottom: 20px;
font-size: 1.2em;
font-weight: 500;
}
/* 表格样式 */
#toolsTable, #taskDetailsTable {
width: 100%;
border-collapse: collapse;
}
#toolsTable th, #taskDetailsTable th,
#toolsTable td, #taskDetailsTable td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
#toolsTable th, #taskDetailsTable th {
background-color: #8f8f8f;
color: white;
font-weight: 500;
}
/* 确保响应式布局 */
@media (max-width: 1200px) {
.statistics-content {
flex-direction: column;
}
.statistics-section {
width: 100%;
}
}
/* 选项框容器样式 */
.view-select-container {
position: absolute;
top: 10px;
right: 20px;
transform: none;
}
/* 选项框样式 */
#viewSelect {
padding: 6px 12px;
font-size: 12px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: white;
cursor: pointer;
outline: none;
min-width: 100px;
text-align: left;
color: #666;
}
#viewSelect:hover {
border-color: #ff9900;
}
#viewSelect:focus {
border-color: #ff9900;
box-shadow: 0 0 0 2px rgba(255, 153, 0, 0.1);
}
#chartView {
height: 500px;
width: 100%;
display: none; /* 初始隐藏 */
}
#chartView.active {
display: block;
}
#trendView {
height: 500px;
width: 100%;
}
#trendView.active {
display: block;
}
/* 复选框样式 */
.checkbox-label {
display: flex;
align-items: center;
cursor: pointer;
color: #666;
font-size: 15px;
}
.checkbox-label input[type="checkbox"] {
cursor: pointer;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
gap: 10px;
}
.page-link {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
color: #333;
text-decoration: none;
}
.page-link:hover {
background-color: #f5f5f5;
}
.current-page {
padding: 8px 12px;
background-color: #ff9900;
color: white;
border-radius: 4px;
}
.ellipsis {
color: #666;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
</head>
<body>
<div class="container">
<!-- 返回按钮 -->
<button class="back-btn" onclick="window.location.href='/'">
← Back
</button>
<h1>Task Report System</h1>
<!-- Tab 导航 -->
<div class="tab-container">
<div class="tabs">
<div class="tab {% if active_tab == 'data' %}active{% endif %}"
onclick="switchTab('data')">Data Details</div>
<div class="tab {% if active_tab == 'tools' %}active{% endif %}"
onclick="switchTab('tools')">Tool Statistics</div>
</div>
</div>
<!-- Tab 内容 -->
<div id="dataTab" class="tab-content"
style="display: {% if active_tab == 'data' %}block{% else %}none{% endif %}">
<!-- 筛选器 -->
<div class="filter-container">
<!-- 日期范围筛选 -->
<div class="filter-group">
<label>Date Range: </label>
<input type="date" id="start-date" name="start-date"
value="{{ filters.start_date or '' }}">
<span>to</span>
<input type="date" id="end-date" name="end-date"
value="{{ filters.end_date or '' }}">
</div>
<!-- Tool Name 筛选 -->
<div class="filter-group">
<label>Tool Name</label>
<input type="text" id="tool-name-filter"
value="{{ filters.tool_name or '' }}"
placeholder="Input Tool Name">
</div>
<!-- Task Name 筛选 -->
<div class="filter-group">
<label>Task Name</label>
<input type="text" id="task-name-filter"
value="{{ filters.task_name or '' }}"
placeholder="Input Task Name">
</div>
<!-- 添加 Debug 数据显示控制 -->
<div class="filter-group">
<label class="checkbox-label">
<input type="checkbox" id="show-debug"
{% if filters.show_debug %}checked{% endif %}
onchange="applyFilters()">
Show Debug Data
</label>
</div>
<!-- 筛选按钮 -->
<div class="button-group">
<button class="filter-btn" onclick="applyFilters()">Apply</button>
<button class="filter-btn clear" onclick="clearFilters()">Clear</button>
</div>
</div>
<!-- 表格 -->
<div class="table-container">
<table>
<thead>
<tr>
<th class="sortable" onclick="sortTable('timestamp')">
Timestamp
<span class="sort-btn" id="timestamp_sort">
<span class="sort-icon"></span>
</span>
</th>
<th>Username</th>
<th>Tool Name</th>
<th>Task Name</th>
<th class="sortable" onclick="sortTable('time_saved')">
Time Saved (s)
<span class="sort-btn" id="time_saved_sort">
<span class="sort-icon"></span>
</span>
</th>
<th class="sortable" onclick="sortTable('time_cost')">
Time Cost (s)
<span class="sort-btn" id="time_cost_sort">
<span class="sort-icon"></span>
</span>
</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr>
<td class="timestamp">{{ task[4] }}</td>
<td>{{ task[1] }}</td>
<td>{{ task[2] }}</td>
<td>{{ task[3] }}</td>
<td>{{ task[5] }}</td>
<td>{{ task[6] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="pagination">
<!-- 上一页 -->
{% if pagination.has_prev %}
<a href="{{ url_for('query_task_data',
page=pagination.prev_num,
per_page=per_page,
tool_name=filters.tool_name,
task_name=filters.task_name,
start_date=filters.start_date,
end_date=filters.end_date,
sort_by=filters.sort_by,
sort_direction=filters.sort_direction,
show_debug=filters.show_debug,
tab=active_tab) }}"
class="page-link">上一页</a>
{% endif %}
<!-- 页码 -->
{% for page_num in pagination.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %}
{% if page_num %}
{% if page_num == pagination.page %}
<span class="current-page">{{ page_num }}</span>
{% else %}
<a href="{{ url_for('query_task_data',
page=page_num,
per_page=per_page,
tool_name=filters.tool_name,
task_name=filters.task_name,
start_date=filters.start_date,
end_date=filters.end_date,
sort_by=filters.sort_by,
sort_direction=filters.sort_direction,
show_debug=filters.show_debug,
tab=active_tab) }}"
class="page-link">{{ page_num }}</a>
{% endif %}
{% else %}
<span class="ellipsis">...</span>
{% endif %}
{% endfor %}
<!-- 下一页 -->
{% if pagination.has_next %}
<a href="{{ url_for('query_task_data',
page=pagination.next_num,
per_page=per_page,
tool_name=filters.tool_name,
task_name=filters.task_name,
start_date=filters.start_date,
end_date=filters.end_date,
sort_by=filters.sort_by,
sort_direction=filters.sort_direction,
show_debug=filters.show_debug,
tab=active_tab) }}"
class="page-link">下一页</a>
{% endif %}
</div>
<!-- 底部状态栏 -->
<div class="bottom-bar">
<div class="status-info">
<span class="total-tasks">Total Tasks: {{ tasks|length }}</span>
</div>
<div class="button-container">
<button class="export-btn" onclick="window.location.href='/export-csv'">
Export to CSV
</button>
</div>
</div>
</div>
<div id="toolsTab" class="tab-content"
style="display: {% if active_tab == 'tools' %}block{% else %}none{% endif %}">
<!-- 添加筛选器 -->
<div class="filter-container">
<!-- 日期范围筛选 -->
<div class="filter-group">
<label>Month:</label>
<select id="month-select" onchange="handleMonthSelect()">
<option value="">Select Month</option>
{% for month in range(1, 13) %}
<option value="{{ month }}"
{% if filters.month == month|string %}selected{% endif %}>
{{ month }}月
</option>
{% endfor %}
</select>
<select id="year-select" onchange="handleMonthSelect()">
{% for year in range(2023, 2026) %}
<option value="{{ year }}"
{% if filters.year == year|string %}selected{% endif %}
{% if not filters.year and year == 2025 %}selected{% endif %}>
{{ year }}
</option>
{% endfor %}
</select>
<label style="margin-left: 30px;">Date Range:</label>
<input type="date" id="tools-start-date" name="tools-start-date"
value="{{ filters.tools_start_date or '' }}">
<span>to</span>
<input type="date" id="tools-end-date" name="tools-end-date"
value="{{ filters.tools_end_date or '' }}">
</div>
<!-- 筛选按钮 -->
<div class="filter-buttons">
<button class="filter-btn" onclick="applyToolsFilter()">Apply</button>
<button class="filter-btn clear" onclick="clearToolsFilter()">Clear</button>
</div>
</div>
<!-- 统一的容器 -->
<div class="statistics-container">
<!-- 添加选项框 -->
<div class="view-select-container">
<select id="viewSelect" onchange="switchView(this.value)">
<option value="details" selected>Statistic Details</option>
<option value="chart">Statistic Chart</option>
<option value="trend">Trend Chart</option>
</select>
</div>
<!-- 表格视图 -->
<div id="tableView" class="statistics-content">
<!-- 左侧工具列表 -->
<div class="statistics-section">
<h3>Tool Statistics</h3>
<table id="toolsTable">
<thead>
<tr>
<th>Index</th>
<th>Tool Name</th>
<th class="sortable" onclick="sortToolTable('total_requests')">
Total Request
<span class="sort-btn" id="total_requests_sort">
<span class="sort-icon"></span>
</span>
</th>
<th>Time Saved (s)</th>
</tr>
</thead>
<tbody>
{% for tool in tool_stats %}
<tr onclick="showTaskDetails('{{ tool.tool_name }}')"
class="tool-row {% if loop.first %}selected{% endif %}">
<td>{{ loop.index }}</td>
<td>{{ tool.tool_name }}</td>
<td>{{ tool.total_requests }}</td>
<td>{{ tool.time_saved }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- 右侧任务详情 -->
<div class="statistics-section">
<h3 id="selectedToolTitle">Task Details</h3>
<table id="taskDetailsTable">
<thead>
<tr>
<th>Index</th>
<th>Task Name</th>
<th>Total Request</th>
<th>Time Saved (s)</th>
</tr>
</thead>
<tbody id="taskDetailsBody">
<!-- 动态填充内容 -->
</tbody>
</table>
</div>
</div>
<!-- 图表视图 -->
<div id="chartView" class="statistics-content" style="display: none;">
<canvas id="toolStatsChart"></canvas>
</div>
<!-- 添加趋势图容器 -->
<div id="trendView" class="statistics-content" style="display: none;">
<canvas id="trendChart"></canvas>
</div>
</div>
</div>
</div>
<script>
function applyFilters() {
const startDate = document.getElementById('start-date').value;
const endDate = document.getElementById('end-date').value;
const taskName = document.getElementById('task-name-filter').value;
const toolName = document.getElementById('tool-name-filter').value;
const showDebug = document.getElementById('show-debug').checked;
// 构建查询参数
const params = new URLSearchParams();
if (startDate) params.append('start_date', startDate);
if (endDate) params.append('end_date', endDate);
if (taskName) params.append('task_name', taskName);
if (toolName) params.append('tool_name', toolName);
params.append('show_debug', showDebug);
// 发送请求
window.location.href = `/query_task_data?${params.toString()}`;
}
function clearFilters() {
document.getElementById('start-date').value = '';
document.getElementById('end-date').value = '';
document.getElementById('task-name-filter').value = '';
document.getElementById('tool-name-filter').value = '';
window.location.href = '/query_task_data';
}
let currentSort = {
column: null,
direction: null
};
function sortTable(column) {
// 更新排序状态
if (currentSort.column === column) {
if (currentSort.direction === 'asc') {
currentSort.direction = 'desc';
} else {
currentSort.direction = 'asc';
}
} else {
currentSort.column = column;
currentSort.direction = 'asc';
}
// 构建查询参数
const params = new URLSearchParams(window.location.search);
params.set('sort_by', column);
params.set('sort_direction', currentSort.direction);
// 保持现有的筛选条件
const startDate = document.getElementById('start-date').value;
const endDate = document.getElementById('end-date').value;
const taskName = document.getElementById('task-name-filter').value;
const toolName = document.getElementById('tool-name-filter').value;
if (startDate) params.set('start_date', startDate);
if (endDate) params.set('end_date', endDate);
if (taskName) params.set('task_name', taskName);
if (toolName) params.set('tool_name', toolName);
// 更新 URL 并刷新页面
window.location.href = `/query_task_data?${params.toString()}`;
}
// 页面加载时初始化
window.onload = function() {
const urlParams = new URLSearchParams(window.location.search);
// const sortBy = urlParams.get('sort_by');
// const sortDirection = urlParams.get('sort_direction');
currentSort.column = urlParams.get('sort_by') || null;
currentSort.direction = urlParams.get('sort_direction') || null;
// 初始化 tab
const activeTab = urlParams.get('tab') || 'data';
// 如果是工具统计页面,自动加载第一个工具的任务详情
if (activeTab === 'tools') {
const firstTool = document.querySelector('.tool-row');
if (firstTool) {
const toolName = firstTool.querySelector('td:nth-child(2)').textContent;
firstTool.classList.add('selected');
// 立即获取并显示第一个工具的任务详情
fetch(`/get_tool_tasks?tool_name=${encodeURIComponent(toolName)}`)
.then(response => response.json())
.then(data => {
const tbody = document.getElementById('taskDetailsBody');
document.getElementById('selectedToolTitle').innerHTML =
`Task Details of <span class="tool-highlight">${toolName}</span>`;
tbody.innerHTML = data.tasks.map((task, index) => `
<tr>
<td>${index + 1}</td>
<td>${task.task_name}</td>
<td>${task.total_requests}</td>
<td>${task.time_saved}</td>
</tr>
`).join('');
})
.catch(error => console.error('Error:', error));
}
}
// 更新排序图标
if (currentSort.column) {
const sortBtn = document.getElementById(`${currentSort.column}_sort`);
if (sortBtn) {
sortBtn.parentElement.classList.add(`sort-${currentSort.direction}`);
}
}
}
// 添加 Tab 切换功能
function switchTab(tabName) {
// 隐藏所有 tab 内容
document.querySelectorAll('.tab-content').forEach(tab => {
tab.style.display = 'none';
});
// 移除所有 tab 的 active 类
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// 显示选中的 tab 内容
document.getElementById(tabName + 'Tab').style.display = 'block';
// 添加 active 类到选中的 tab
event.target.classList.add('active');
// 如果切换到工具统计 tab立即加载数据
if (tabName === 'tools') {
// 构建查询参数
const params = new URLSearchParams();
// const params = new URLSearchParams(window.location.search);
params.set('tab', 'tools');
// 使用 fetch 获取数据而不是刷新页面
fetch(`/query_task_data?${params.toString()}`)
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const toolsContent = doc.getElementById('toolsTab').innerHTML;
document.getElementById('toolsTab').innerHTML = toolsContent;
// 自动加载第一个工具的详情
const firstTool = document.querySelector('.tool-row');
if (firstTool) {
const toolName = firstTool.querySelector('td:nth-child(2)').textContent;
firstTool.classList.add('selected');
// 立即获取并显示第一个工具的任务详情
fetch(`/get_tool_tasks?tool_name=${encodeURIComponent(toolName)}`)
.then(response => response.json())
.then(data => {
const tbody = document.getElementById('taskDetailsBody');
document.getElementById('selectedToolTitle').innerHTML =
`Task Details of <span class="tool-highlight">${toolName}</span>`;
tbody.innerHTML = data.tasks.map((task, index) => `
<tr>
<td>${index + 1}</td>
<td>${task.task_name}</td>
<td>${task.total_requests}</td>
<td>${task.time_saved}</td>
</tr>
`).join('');
})
.catch(error => console.error('Error:', error));
}
})
.catch(error => console.error('Error:', error));
}
// 更新 URL 参数
const params = new URLSearchParams(window.location.search);
params.set('tab', tabName);
window.history.replaceState({}, '', `?${params.toString()}`);
}
// 工具统计相关的 JavaScript
let selectedToolName = null;
function showTaskDetails(toolName) {
// 更新选中状态
document.querySelectorAll('.tool-row').forEach(row => {
row.classList.remove('selected');
});
event.target.closest('tr').classList.add('selected');
// 更新标题
document.getElementById('selectedToolTitle').innerHTML =
`Task Details of <span class="tool-highlight">${toolName}</span>`;
// 获取任务详情数据
fetch(`/get_tool_tasks?tool_name=${encodeURIComponent(toolName)}`)
.then(response => response.json())
.then(data => {
const tbody = document.getElementById('taskDetailsBody');
tbody.innerHTML = data.tasks.map((task, index) => `
<tr>
<td>${index + 1}</td>
<td>${task.task_name}</td>
<td>${task.total_requests}</td>
<td>${task.time_saved}</td>
</tr>
`).join('');
})
.catch(error => console.error('Error:', error));
}
// 处理月份选择
function handleMonthSelect() {
const monthSelect = document.getElementById('month-select');
const yearSelect = document.getElementById('year-select');
if (monthSelect.value) {
const year = yearSelect.value;
const month = monthSelect.value.padStart(2, '0');
const lastDay = new Date(year, month, 0).getDate();
document.getElementById('tools-start-date').value = `${year}-${month}-01`;
document.getElementById('tools-end-date').value = `${year}-${month}-${lastDay}`;
}
}
// 应用筛选
function applyToolsFilter() {
const startDate = document.getElementById('tools-start-date').value;
const endDate = document.getElementById('tools-end-date').value;
const month = document.getElementById('month-select').value;
const year = document.getElementById('year-select').value;
const params = new URLSearchParams(window.location.search);
if (startDate) params.set('tools_start_date', startDate);
if (endDate) params.set('tools_end_date', endDate);
if (month) params.set('month', month);
if (year) params.set('year', year);
params.set('tab', 'tools'); // 保持在工具统计标签页
window.location.href = `/query_task_data?${params.toString()}`;
}
// 清除筛选
function clearToolsFilter() {
// 清空所有筛选条件
document.getElementById('tools-start-date').value = '';
document.getElementById('tools-end-date').value = '';
document.getElementById('month-select').value = '';
document.getElementById('year-select').value = '';
// 清空 URL 参数,只保留 tab 参数
const params = new URLSearchParams();
params.set('tab', 'tools'); // 保持在工具统计标签页
// 跳转到清空后的 URL
window.location.href = `/query_task_data?${params.toString()}`;
}
// 视图切换函数
function switchView(viewType) {
const tableView = document.getElementById('tableView');
const chartView = document.getElementById('chartView');
const trendView = document.getElementById('trendView');
// 隐藏所有视图
tableView.style.display = 'none';
chartView.style.display = 'none';
trendView.style.display = 'none';
// 显示选中的视图
if (viewType === 'trend') {
trendView.style.display = 'block';
// 使用 setTimeout 确保 DOM 更新后再更新图表
setTimeout(updateTrendChart, 0);
} else if (viewType === 'chart') {
chartView.style.display = 'block';
setTimeout(updateChart, 0);
} else {
tableView.style.display = 'flex';
}
}
function updateChart() {
const toolRows = document.querySelectorAll('#toolsTable tbody tr');
const labels = [];
const requestData = [];
const timeData = [];
toolRows.forEach(row => {
labels.push(row.cells[1].textContent);
requestData.push(parseInt(row.cells[2].textContent));
timeData.push(parseInt(row.cells[3].textContent));
});
const ctx = document.getElementById('toolStatsChart');
if (window.toolChart) {
window.toolChart.destroy();
}
window.toolChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'Total Requests',
data: requestData,
backgroundColor: 'rgba(255, 153, 0, 0.7)',
borderColor: 'rgba(255, 153, 0, 1)',
borderWidth: 1,
yAxisID: 'y-requests'
},
{
label: 'Time Saved (s)',
data: timeData,
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
yAxisID: 'y-time'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
'y-requests': {
type: 'linear',
position: 'left',
title: {
display: true,
text: 'Total Requests',
color: 'rgba(255, 153, 0, 1)'
},
grid: {
drawOnChartArea: false
},
ticks: {
color: 'rgba(255, 153, 0, 1)',
stepSize: 1,
precision: 0
}
},
'y-time': {
type: 'linear',
position: 'right',
title: {
display: true,
text: 'Time Saved (s)',
color: 'rgba(54, 162, 235, 1)'
},
ticks: {
color: 'rgba(54, 162, 235, 1)'
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
title: {
display: true,
text: 'Tool Statistics Overview',
font: {
size: 16,
weight: 'bold'
},
padding: 20
},
legend: {
position: 'top',
labels: {
padding: 20,
font: {
size: 12
}
}
}
},
barPercentage: 0.7,
categoryPercentage: 0.4
}
});
}
let trendChart = null; // 声明全局变量
function formatDate(date) {
return date.toISOString().split('T')[0];
}
function getCurrentDate() {
const today = new Date();
return formatDate(today);
}
function getDateBefore(days) {
const date = new Date();
date.setDate(date.getDate() - days);
return formatDate(date);
}
function updateTrendChart() {
console.log('Updating trend chart...');
// 获取当前的日期范围
const startDate = document.querySelector('input[name="tools-start-date"]').value;
const endDate = document.querySelector('input[name="tools-end-date"]').value;
console.log('Date range:', startDate, endDate);
// 创建 URLSearchParams 实例
const params = new URLSearchParams();
// 如果有日期范围,则使用指定的日期范围
if (startDate && endDate) {
params.append('start_date', startDate);
params.append('end_date', endDate);
}
else{
params.append('start_date', getDateBefore(30));
params.append('end_date', getCurrentDate());
}
console.log('Final request params:', params.toString());
// 获取趋势数据
fetch(`/get_trend_data?${params.toString()}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Trend data:', data);
const ctx = document.getElementById('trendChart');
if (!ctx) {
console.error('Trend chart canvas not found');
return;
}
// 销毁旧图表
if (trendChart instanceof Chart) {
trendChart.destroy();
}
// 创建新图表
trendChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.dates,
datasets: [
{
label: 'Total Requests',
data: data.requests,
borderColor: 'rgba(255, 153, 0, 1)',
backgroundColor: 'rgba(255, 153, 0, 0.1)',
yAxisID: 'y-requests',
tension: 0.4,
fill: true
},
{
label: 'Time Saved (s)',
data: data.timeSaved,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
yAxisID: 'y-time',
tension: 0.4,
fill: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
'y-requests': {
type: 'linear',
position: 'left',
title: {
display: true,
text: 'Total Requests',
color: 'rgba(255, 153, 0, 1)'
},
ticks: {
stepSize: 1,
precision: 0,
color: 'rgba(255, 153, 0, 1)'
},
grid: {
drawOnChartArea: false
}
},
'y-time': {
type: 'linear',
position: 'right',
title: {
display: true,
text: 'Time Saved (s)',
color: 'rgba(54, 162, 235, 1)'
},
ticks: {
color: 'rgba(54, 162, 235, 1)'
}
},
x: {
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MM/dd'
}
},
title: {
display: true,
text: 'Date'
}
}
},
plugins: {
title: {
display: true,
text: 'Usage Trend Overview',
font: {
size: 16,
weight: 'bold'
},
padding: 20
},
legend: {
position: 'top'
}
}
}
});
})
.catch(error => {
console.error('Error fetching trend data:', error);
alert('Failed to load trend data. Please try again.');
});
}
</script>
</body>
</html>