Files
Task_Report/query_task_data.php
2025-04-18 01:20:11 +08:00

944 lines
31 KiB
PHP
Raw 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.

<?php
// 设置错误报告
ini_set('display_errors', 1);
error_reporting(E_ALL);
// 数据库连接信息
$db_host = '192.168.2.4';
$db_port = 3307;
$db_name = 'task_reporter';
$db_user = 'task_reporter';
$db_pass = 'Pass12349ers!';
// 创建数据库连接
try {
$pdo = new PDO("mysql:host=$db_host;port=$db_port;dbname=$db_name", $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("SET NAMES utf8");
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}
// 获取所有工具名称和任务名称(用于筛选)
$tools_stmt = $pdo->query("SELECT DISTINCT tool_name FROM task ORDER BY tool_name");
$tools = $tools_stmt->fetchAll(PDO::FETCH_COLUMN);
$tasks_stmt = $pdo->query("SELECT DISTINCT task_name FROM task ORDER BY task_name");
$task_names = $tasks_stmt->fetchAll(PDO::FETCH_COLUMN);
?>
<!DOCTYPE html>
<html>
<head>
<title>Task Report Data</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
h1 {
color: #ff8c00;
text-align: center;
margin-bottom: 30px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.filters {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
}
.filter-group {
display: flex;
flex-direction: column;
}
.filter-group label {
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.filter-group select,
.filter-group input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.buttons {
display: flex;
justify-content: space-between;
margin: 20px 0;
}
button {
padding: 10px 20px;
background-color: #ff8c00;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
button:hover {
background-color: #e67e00;
}
.export-btn {
background-color: #4CAF50;
}
.export-btn:hover {
background-color: #45a049;
}
.back-btn {
background-color: #555;
}
.back-btn:hover {
background-color: #444;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f2f2f2;
font-weight: bold;
cursor: pointer;
}
th:hover {
background-color: #e6e6e6;
}
tr:hover {
background-color: #f9f9f9;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 20px;
}
.pagination button {
margin: 0 5px;
padding: 8px 12px;
background-color: #f2f2f2;
color: #333;
border: 1px solid #ddd;
}
.pagination button.active {
background-color: #ff8c00;
color: white;
border-color: #ff8c00;
}
.pagination button:hover:not(.active) {
background-color: #ddd;
}
.stats-container {
margin-top: 30px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 5px;
flex: 1;
}
.stats-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.stats-header h2 {
margin: 0;
color: #555;
}
/* 标签页样式 */
.tabs {
display: flex;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
background-color: #f2f2f2;
border: 1px solid #ddd;
border-bottom: none;
margin-right: 5px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.tab.active {
background-color: #fff;
border-bottom: 1px solid #fff;
margin-bottom: -1px;
font-weight: bold;
color: #ff8c00;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.chart-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.chart {
flex: 1;
min-width: 300px;
height: 300px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 15px;
position: relative;
}
.tool-stats {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
margin-top: 20px;
}
.tool-card {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 15px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.tool-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.tool-card h3 {
margin-top: 0;
color: #ff8c00;
}
.tool-card p {
margin: 5px 0;
color: #666;
}
.tool-card .count {
font-weight: bold;
color: #333;
}
.tool-card .time-saved {
color: #4CAF50;
font-weight: bold;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: #fff;
margin: 10% auto;
padding: 20px;
width: 80%;
max-width: 800px;
border-radius: 5px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #555;
}
#loading {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
z-index: 9999;
justify-content: center;
align-items: center;
}
.spinner {
border: 5px solid #f3f3f3;
border-top: 5px solid #ff8c00;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div id="loading">
<div class="spinner"></div>
</div>
<div class="container">
<h1>Task Report Data</h1>
<div class="filters">
<div class="filter-group">
<label for="start-date">开始日期:</label>
<input type="date" id="start-date">
</div>
<div class="filter-group">
<label for="end-date">结束日期:</label>
<input type="date" id="end-date">
</div>
<div class="filter-group">
<label for="tool-name">工具名称:</label>
<select id="tool-name">
<option value="">全部</option>
<?php foreach ($tools as $tool): ?>
<option value="<?php echo htmlspecialchars($tool); ?>"><?php echo htmlspecialchars($tool); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="filter-group">
<label for="task-name">任务名称:</label>
<select id="task-name">
<option value="">全部</option>
<?php foreach ($task_names as $task_name): ?>
<option value="<?php echo htmlspecialchars($task_name); ?>"><?php echo htmlspecialchars($task_name); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="filter-group">
<label for="show-debug">显示调试工具:</label>
<select id="show-debug">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="filter-group">
<label for="sort-by">排序字段:</label>
<select id="sort-by">
<option value="timestamp">时间</option>
<option value="time_saved">节省时间</option>
<option value="time_cost">花费时间</option>
</select>
</div>
<div class="filter-group">
<label for="sort-direction">排序方向:</label>
<select id="sort-direction">
<option value="desc">降序</option>
<option value="asc">升序</option>
</select>
</div>
</div>
<div class="buttons">
<button id="filter-btn">应用筛选</button>
<button id="export-btn" class="export-btn">导出CSV</button>
<button class="report-btn" onclick="window.location.href='generate_report.php'">查看报告</button>
<button class="back-btn" onclick="window.location.href='index.php'">返回首页</button>
</div>
<div class="stats-container">
<div class="stats-header">
<h2>数据与统计</h2>
</div>
<div class="tabs">
<div class="tab active" data-tab="data">请求数据</div>
<div class="tab" data-tab="trends">趋势分析</div>
<div class="tab" data-tab="tools">工具统计</div>
</div>
<div id="data-tab" class="tab-content active">
<table id="task-table">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>工具名称</th>
<th>任务名称</th>
<th>节省时间</th>
<th>花费时间</th>
<th>时间戳</th>
</tr>
</thead>
<tbody id="task-data">
<!-- 任务数据将通过JavaScript动态加载 -->
</tbody>
</table>
<div class="pagination" id="pagination">
<!-- 分页按钮将通过JavaScript动态生成 -->
</div>
</div>
<div id="trends-tab" class="tab-content">
<div class="chart-container">
<div class="chart" id="requests-chart">
<canvas id="requests-canvas"></canvas>
</div>
<div class="chart" id="time-saved-chart">
<canvas id="time-saved-canvas"></canvas>
</div>
</div>
</div>
<div id="tools-tab" class="tab-content">
<div class="tool-stats" id="tool-stats">
<!-- 工具统计数据将通过JavaScript动态加载 -->
</div>
</div>
</div>
</div>
<div id="tool-modal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2 id="modal-title">工具详情</h2>
<div id="modal-content">
<!-- 工具详情将通过JavaScript动态加载 -->
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 全局变量
let currentPage = 1;
let totalPages = 1;
let perPage = 50;
let requestsChart = null;
let timeSavedChart = null;
// DOM元素
const startDateInput = document.getElementById('start-date');
const endDateInput = document.getElementById('end-date');
const toolNameSelect = document.getElementById('tool-name');
const taskNameSelect = document.getElementById('task-name');
const showDebugSelect = document.getElementById('show-debug');
const sortBySelect = document.getElementById('sort-by');
const sortDirectionSelect = document.getElementById('sort-direction');
const filterBtn = document.getElementById('filter-btn');
const exportBtn = document.getElementById('export-btn');
const taskDataContainer = document.getElementById('task-data');
const paginationContainer = document.getElementById('pagination');
const toolStatsContainer = document.getElementById('tool-stats');
const modal = document.getElementById('tool-modal');
const modalTitle = document.getElementById('modal-title');
const modalContent = document.getElementById('modal-content');
const closeModal = document.getElementsByClassName('close')[0];
const loading = document.getElementById('loading');
// 设置默认日期范围最近30天
const today = new Date();
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(today.getDate() - 30);
startDateInput.value = formatDate(thirtyDaysAgo);
endDateInput.value = formatDate(today);
// 初始化页面
document.addEventListener('DOMContentLoaded', function() {
loadTaskData();
loadToolStatistics();
loadTrendData();
// 事件监听器
filterBtn.addEventListener('click', function() {
currentPage = 1;
loadTaskData();
loadToolStatistics();
loadTrendData();
});
exportBtn.addEventListener('click', exportCsv);
closeModal.addEventListener('click', function() {
modal.style.display = 'none';
});
window.addEventListener('click', function(event) {
if (event.target === modal) {
modal.style.display = 'none';
}
});
// 标签页切换
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
// 移除所有标签页的active类
document.querySelectorAll('.tab').forEach(t => {
t.classList.remove('active');
});
// 移除所有内容区的active类
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// 添加当前标签页的active类
this.classList.add('active');
// 显示对应的内容区
const tabId = this.getAttribute('data-tab');
document.getElementById(tabId + '-tab').classList.add('active');
// 如果切换到趋势分析标签页,重新渲染图表
if (tabId === 'trends') {
setTimeout(() => {
if (requestsChart) requestsChart.resize();
if (timeSavedChart) timeSavedChart.resize();
}, 10);
}
});
});
});
// 加载任务数据
function loadTaskData() {
showLoading();
const params = new URLSearchParams({
action: 'query_task_data',
start_date: startDateInput.value,
end_date: endDateInput.value,
tool_name: toolNameSelect.value,
task_name: taskNameSelect.value,
show_debug: showDebugSelect.value,
sort_by: sortBySelect.value,
sort_direction: sortDirectionSelect.value,
page: currentPage,
per_page: perPage
});
fetch(`index.php?${params.toString()}`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
return;
}
renderTaskData(data.tasks);
renderPagination(data.page, data.total_pages);
totalPages = data.total_pages;
hideLoading();
})
.catch(error => {
console.error('Error:', error);
alert('加载数据时出错');
hideLoading();
});
}
// 渲染任务数据
function renderTaskData(tasks) {
taskDataContainer.innerHTML = '';
if (tasks.length === 0) {
const row = document.createElement('tr');
row.innerHTML = '<td colspan="7" style="text-align: center;">没有找到匹配的数据</td>';
taskDataContainer.appendChild(row);
return;
}
tasks.forEach(task => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${task.id}</td>
<td>${task.username}</td>
<td>${task.tool_name}</td>
<td>${task.task_name}</td>
<td>${task.time_saved}</td>
<td>${task.time_cost}</td>
<td>${task.timestamp}</td>
`;
taskDataContainer.appendChild(row);
});
}
// 渲染分页
function renderPagination(currentPage, totalPages) {
paginationContainer.innerHTML = '';
if (totalPages <= 1) {
return;
}
// 添加"上一页"按钮
if (currentPage > 1) {
const prevBtn = document.createElement('button');
prevBtn.textContent = '上一页';
prevBtn.addEventListener('click', () => {
goToPage(currentPage - 1);
});
paginationContainer.appendChild(prevBtn);
}
// 添加页码按钮
let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, startPage + 4);
if (endPage - startPage < 4) {
startPage = Math.max(1, endPage - 4);
}
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.textContent = i;
pageBtn.className = i === currentPage ? 'active' : '';
pageBtn.addEventListener('click', () => {
goToPage(i);
});
paginationContainer.appendChild(pageBtn);
}
// 添加"下一页"按钮
if (currentPage < totalPages) {
const nextBtn = document.createElement('button');
nextBtn.textContent = '下一页';
nextBtn.addEventListener('click', () => {
goToPage(currentPage + 1);
});
paginationContainer.appendChild(nextBtn);
}
}
// 跳转到指定页
function goToPage(page) {
currentPage = page;
loadTaskData();
window.scrollTo(0, 0);
}
// 加载工具统计数据
function loadToolStatistics() {
const params = new URLSearchParams({
action: 'get_tool_statistics',
start_date: startDateInput.value,
end_date: endDateInput.value
});
fetch(`index.php?${params.toString()}`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
return;
}
renderToolStatistics(data.tools, data.total_requests, data.total_time_saved);
})
.catch(error => {
console.error('Error:', error);
});
}
// 渲染工具统计数据
function renderToolStatistics(tools, totalRequests, totalTimeSaved) {
toolStatsContainer.innerHTML = '';
if (tools.length === 0) {
toolStatsContainer.innerHTML = '<p>没有找到匹配的数据</p>';
return;
}
tools.forEach(tool => {
const card = document.createElement('div');
card.className = 'tool-card';
card.innerHTML = `
<h3>${tool.tool_name}</h3>
<p>请求次数: <span class="count">${tool.request_count}</span></p>
<p>节省时间: <span class="time-saved">${parseFloat(tool.time_saved).toFixed(2)} 小时</span></p>
`;
card.addEventListener('click', () => {
showToolDetails(tool.tool_name);
});
toolStatsContainer.appendChild(card);
});
}
// 显示工具详情
function showToolDetails(toolName) {
showLoading();
const params = new URLSearchParams({
action: 'get_tool_tasks',
tool_name: toolName
});
fetch(`index.php?${params.toString()}`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
return;
}
modalTitle.textContent = `${toolName} 详情`;
if (data.tasks.length === 0) {
modalContent.innerHTML = '<p>没有找到匹配的数据</p>';
} else {
let html = '<table style="width:100%"><thead><tr><th>任务名称</th><th>请求次数</th><th>节省时间</th></tr></thead><tbody>';
data.tasks.forEach(task => {
html += `
<tr>
<td>${task.task_name}</td>
<td>${task.total_requests}</td>
<td>${parseFloat(task.time_saved).toFixed(2)} 小时</td>
</tr>
`;
});
html += '</tbody></table>';
modalContent.innerHTML = html;
}
modal.style.display = 'block';
hideLoading();
})
.catch(error => {
console.error('Error:', error);
hideLoading();
});
}
// 加载趋势数据
function loadTrendData() {
const params = new URLSearchParams({
action: 'get_trend_data',
start_date: startDateInput.value,
end_date: endDateInput.value
});
fetch(`index.php?${params.toString()}`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
return;
}
renderTrendCharts(data.dates, data.requests, data.timeSaved);
})
.catch(error => {
console.error('Error:', error);
});
}
// 渲染趋势图表
function renderTrendCharts(dates, requests, timeSaved) {
// 请求次数趋势图
const requestsCtx = document.getElementById('requests-canvas').getContext('2d');
if (requestsChart) {
requestsChart.destroy();
}
requestsChart = new Chart(requestsCtx, {
type: 'line',
data: {
labels: dates,
datasets: [{
label: '每日请求次数',
data: requests,
backgroundColor: 'rgba(255, 140, 0, 0.2)',
borderColor: 'rgba(255, 140, 0, 1)',
borderWidth: 2,
pointBackgroundColor: 'rgba(255, 140, 0, 1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '每日请求次数趋势',
font: {
size: 16
}
},
legend: {
display: true,
position: 'top'
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
// 节省时间趋势图
const timeSavedCtx = document.getElementById('time-saved-canvas').getContext('2d');
if (timeSavedChart) {
timeSavedChart.destroy();
}
timeSavedChart = new Chart(timeSavedCtx, {
type: 'line',
data: {
labels: dates,
datasets: [{
label: '每日节省时间(小时)',
data: timeSaved,
backgroundColor: 'rgba(76, 175, 80, 0.2)',
borderColor: 'rgba(76, 175, 80, 1)',
borderWidth: 2,
pointBackgroundColor: 'rgba(76, 175, 80, 1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '每日节省时间趋势',
font: {
size: 16
}
},
legend: {
display: true,
position: 'top'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// 导出CSV
function exportCsv() {
const params = new URLSearchParams({
action: 'export_csv',
start_date: startDateInput.value,
end_date: endDateInput.value,
tool_name: toolNameSelect.value,
task_name: taskNameSelect.value,
show_debug: showDebugSelect.value
});
window.location.href = `index.php?${params.toString()}`;
}
// 格式化日期为YYYY-MM-DD
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 显示加载中
function showLoading() {
loading.style.display = 'flex';
}
// 隐藏加载中
function hideLoading() {
loading.style.display = 'none';
}
</script>
</body>
</html>