Update
This commit is contained in:
162
.gitignore
vendored
Normal file
162
.gitignore
vendored
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
140
README.md
Normal file
140
README.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# CGNICO 任务报告系统
|
||||||
|
|
||||||
|
这是一个用于跟踪和报告工具使用情况的系统,专为群晖DSM的Webstation环境优化。
|
||||||
|
|
||||||
|
## 系统功能
|
||||||
|
|
||||||
|
- 记录工具使用数据(用户名、工具名称、任务名称、节省时间、花费时间)
|
||||||
|
- 提供数据可视化界面,展示工具使用统计和趋势
|
||||||
|
- 支持数据筛选、排序和导出
|
||||||
|
- 提供API接口,方便其他应用程序提交数据
|
||||||
|
- 支持定期生成报告
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- PHP 7.4+
|
||||||
|
- MariaDB 10
|
||||||
|
- HTML/CSS/JavaScript
|
||||||
|
- Chart.js (用于数据可视化)
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
- `index.php` - 主页和API处理
|
||||||
|
- `query_task_data.php` - 数据查询和可视化界面
|
||||||
|
- `api.php` - 独立的API接口
|
||||||
|
- `client_example.php` - API使用示例
|
||||||
|
- `insert_test_data.php` - 插入测试数据的工具
|
||||||
|
- `check_data.php` - 检查数据库记录的工具
|
||||||
|
- `generate_report.php` - 定期报告生成工具
|
||||||
|
- `static/` - 静态资源文件夹
|
||||||
|
|
||||||
|
## 部署指南
|
||||||
|
|
||||||
|
### 系统要求
|
||||||
|
|
||||||
|
- 群晖DSM系统
|
||||||
|
- Webstation已安装(使用Apache HTTP Server 2.4)
|
||||||
|
- PHP 7.4+
|
||||||
|
- MariaDB 10
|
||||||
|
|
||||||
|
### 部署步骤
|
||||||
|
|
||||||
|
1. 将所有文件复制到群晖DSM的Web目录:`/volume1/web/task-reporter`
|
||||||
|
|
||||||
|
2. 设置正确的文件权限:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod -R 755 /volume1/web/task-reporter
|
||||||
|
chown -R http:http /volume1/web/task-reporter
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 在MariaDB中创建数据库和用户:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE task_reporter;
|
||||||
|
CREATE USER 'task_reporter'@'%' IDENTIFIED BY 'Pass12349ers!';
|
||||||
|
GRANT ALL PRIVILEGES ON task_reporter.* TO 'task_reporter'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 在Webstation中配置网站:
|
||||||
|
- 虚拟主机名称:toolstrack.cgnico.com
|
||||||
|
- 端口:9000
|
||||||
|
- 文档根目录:/volume1/web/task-reporter
|
||||||
|
- 启用PHP
|
||||||
|
- Web服务器:Apache HTTP Server 2.4
|
||||||
|
|
||||||
|
### 访问系统
|
||||||
|
|
||||||
|
- 本地访问:[http://192.168.2.4:9000](http://192.168.2.4:9000)
|
||||||
|
- 域名访问:[https://toolstrack.cgnico.com](https://toolstrack.cgnico.com)
|
||||||
|
|
||||||
|
## API使用
|
||||||
|
|
||||||
|
系统提供了简单的API接口,用于提交任务数据:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
POST [https://toolstrack.cgnico.com/api.php](https://toolstrack.cgnico.com/api.php)
|
||||||
|
```
|
||||||
|
|
||||||
|
请求体示例(JSON格式):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "用户名",
|
||||||
|
"tool_name": "工具名称",
|
||||||
|
"task_name": "任务名称",
|
||||||
|
"time_saved": "1.5",
|
||||||
|
"time_cost": "0.2",
|
||||||
|
"timestamp": "2025-04-13 12:30:00" // 可选
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
成功响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "2025-04-13 12:30:00 工具名称-任务名称 reported successfully!",
|
||||||
|
"id": 123
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
详细的API使用示例请参考 `client_example.php` 文件。
|
||||||
|
|
||||||
|
## 定期报告功能
|
||||||
|
|
||||||
|
系统支持定期生成工具使用报告:
|
||||||
|
|
||||||
|
### 功能特点
|
||||||
|
|
||||||
|
- 自动生成全面的工具使用报告
|
||||||
|
- 支持多种时间范围(周报、月报、季报、年报)
|
||||||
|
- 包含总体统计、工具排名、用户排名和每日趋势
|
||||||
|
- 自动保存报告历史记录
|
||||||
|
- 支持在线查看和下载报告
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
1. **手动生成报告**:
|
||||||
|
访问 [https://toolstrack.cgnico.com/generate_report.php](https://toolstrack.cgnico.com/generate_report.php) 页面,选择报告时间范围,点击"生成报告"按钮。
|
||||||
|
|
||||||
|
2. **设置定期执行**:
|
||||||
|
在群晖DSM的控制面板中,找到"任务计划",添加新的计划任务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 每周日凌晨生成周报
|
||||||
|
0 0 * * 7 php /volume1/web/task-reporter/generate_report.php
|
||||||
|
|
||||||
|
# 或每月1日生成月报
|
||||||
|
0 0 1 * * php /volume1/web/task-reporter/generate_report.php
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **查看历史报告**:
|
||||||
|
在生成报告页面下方可以看到历史报告列表,点击"查看"或"下载"按钮访问历史报告。
|
||||||
|
|
||||||
|
所有生成的报告都会保存在 `reports` 目录下,以日期命名,方便随时查看历史报告。也可以在主页上通过"查看报告"按钮直接访问最新报告。
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题,请联系:[jeffreytsai1004@gmail.com](mailto:jeffreytsai1004@gmail.com)
|
231
TaskReporter.py
Normal file
231
TaskReporter.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
TaskReporter - Task Reporting Tool
|
||||||
|
|
||||||
|
This script is designed to record and track user task execution. It sends task information
|
||||||
|
(including username, tool name, task name, time saved, time cost) to a specified server for statistics.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
#---------------------------------- usage 1: decorator ----------------------------------
|
||||||
|
from TaskReporter import task_reporter_decorator
|
||||||
|
|
||||||
|
debug_mode = True
|
||||||
|
|
||||||
|
@task_reporter_decorator(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5)
|
||||||
|
def your_function():
|
||||||
|
# your code
|
||||||
|
|
||||||
|
#---------------------------------- usage 2: function ----------------------------------
|
||||||
|
from TaskReporter import task_reporter
|
||||||
|
|
||||||
|
debug_mode = True
|
||||||
|
|
||||||
|
def your_function():
|
||||||
|
start_time = time.time()
|
||||||
|
# your function code
|
||||||
|
task_reporter(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5, time_cost=round(time.time()-start_time, 3))
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys
|
||||||
|
from functools import wraps
|
||||||
|
import time, datetime, json
|
||||||
|
|
||||||
|
# add lib path to import requests
|
||||||
|
py_version = sys.version_info.major
|
||||||
|
minor_version = sys.version_info.minor
|
||||||
|
version = "py{}{}".format(py_version, minor_version) if py_version in [2,3] and minor_version<=7 else "py38+"
|
||||||
|
lib_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "reporter_lib", version)
|
||||||
|
if lib_path not in sys.path:
|
||||||
|
sys.path.append(lib_path)
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.packages.urllib3.util.retry import Retry
|
||||||
|
|
||||||
|
|
||||||
|
class TaskReporter:
|
||||||
|
def __init__(self):
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.mount('http://', HTTPAdapter(max_retries=3))
|
||||||
|
self.session.mount('https://', HTTPAdapter(max_retries=3))
|
||||||
|
self.server_status = {'available': True, 'last_check': 0}
|
||||||
|
self.SERVER_URL = 'https://toolstrack.cgnico.com'
|
||||||
|
self.cache_file = os.path.join(os.environ.get('APPDATA'), 'task_cache.json')
|
||||||
|
|
||||||
|
def report_task(self, data, is_cached_task=False):
|
||||||
|
# first check and send cached data
|
||||||
|
if not is_cached_task:
|
||||||
|
self._try_send_cached_tasks()
|
||||||
|
|
||||||
|
task_has_cached = False
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if not self.server_status['available'] and not is_cached_task:
|
||||||
|
if current_time - self.server_status['last_check'] < 10:
|
||||||
|
self._cache_task(data)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.session.post(
|
||||||
|
'{}/api.php'.format(self.SERVER_URL),
|
||||||
|
json=data,
|
||||||
|
timeout=1
|
||||||
|
)
|
||||||
|
self.server_status['available'] = True
|
||||||
|
print(response.json())
|
||||||
|
return response.json()
|
||||||
|
except (requests.exceptions.RequestException, TimeoutError) as e:
|
||||||
|
self.server_status['available'] = False
|
||||||
|
self.server_status['last_check'] = current_time
|
||||||
|
if not task_has_cached and not is_cached_task: self._cache_task(data)
|
||||||
|
task_has_cached = True
|
||||||
|
print("Failed to connect to server, task has been cached\n{}".format(str(e)))
|
||||||
|
# print("Failed to connect to server: {}".format(str(e)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _cache_task(self, data):
|
||||||
|
try:
|
||||||
|
data['timestamp'] = (datetime.datetime.utcnow() + datetime.timedelta(hours=8)).isoformat()
|
||||||
|
|
||||||
|
cache = self._read_cache()
|
||||||
|
cache.append(data)
|
||||||
|
|
||||||
|
with open(self.cache_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(cache, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# print("Task cached: {}".format(self.cache_file))
|
||||||
|
except Exception as e:
|
||||||
|
print("Cache task failed:{}".format(str(e)))
|
||||||
|
|
||||||
|
def _read_cache(self):
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.cache_file):
|
||||||
|
with open(self.cache_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
print("Read cache failed: {}".format(str(e)))
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _try_send_cached_tasks(self):
|
||||||
|
if not os.path.exists(self.cache_file):
|
||||||
|
return
|
||||||
|
|
||||||
|
cache = self._read_cache()
|
||||||
|
if not cache:
|
||||||
|
return
|
||||||
|
|
||||||
|
# print("Find {} cached tasks, try to send again...".format(len(cache)))
|
||||||
|
if not self.server_status['available']:
|
||||||
|
return
|
||||||
|
|
||||||
|
successful_indices = []
|
||||||
|
for i, task in enumerate(cache):
|
||||||
|
try:
|
||||||
|
response = self.report_task(task, is_cached_task=True)
|
||||||
|
if response != False:
|
||||||
|
successful_indices.append(i)
|
||||||
|
print("Cached task sent successfully: {}".format(task.get('task_name')))
|
||||||
|
else:
|
||||||
|
# print("Cached task sent failed")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print("Send cached task failed: {}".format(str(e)))
|
||||||
|
break
|
||||||
|
|
||||||
|
if successful_indices:
|
||||||
|
new_cache = [task for i, task in enumerate(cache) if i not in successful_indices]
|
||||||
|
if new_cache:
|
||||||
|
with open(self.cache_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(new_cache, f, ensure_ascii=False, indent=2)
|
||||||
|
print("{} tasks left to send".format(len(new_cache)))
|
||||||
|
else:
|
||||||
|
os.remove(self.cache_file)
|
||||||
|
print("All cached tasks sent")
|
||||||
|
|
||||||
|
reporter = TaskReporter()
|
||||||
|
|
||||||
|
def task_reporter_decorator(tool_name, task_name, debug=False, time_saved=0):
|
||||||
|
"""
|
||||||
|
task reporter decorator
|
||||||
|
:param tool_name: name of the tool, eg "MetaBox"
|
||||||
|
:param task_name: name of the task, eg "CheckUV"
|
||||||
|
:param debug: set to True when develop, and set it to False when release the tool to artists
|
||||||
|
:param time_saved: time saved for this task, usually should communicate with artists to get a reasonable value
|
||||||
|
"""
|
||||||
|
# reporter = TaskReporter()
|
||||||
|
def run_and_report(func):
|
||||||
|
@wraps(func)
|
||||||
|
def execute_task(*args, **kwargs):
|
||||||
|
# record start time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# execute original function
|
||||||
|
try:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
# report task - 无论函数执行成功与否都发送报告
|
||||||
|
success = result != False
|
||||||
|
data= {
|
||||||
|
'username': os.environ.get('USERNAME'),
|
||||||
|
'tool_name': tool_name + "(Debug)" if debug else tool_name,
|
||||||
|
'task_name': task_name,
|
||||||
|
'time_saved': str(time_saved if success else 0), # 如果失败,节省时间为0
|
||||||
|
'time_cost': str(round(time.time() - start_time, 3)), # 确保是字符串格式
|
||||||
|
'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 使用文档中指定的时间格式
|
||||||
|
'status': "success" if success else "failed" # 添加状态字段
|
||||||
|
}
|
||||||
|
reporter.report_task(data)
|
||||||
|
|
||||||
|
return execute_task
|
||||||
|
return run_and_report
|
||||||
|
|
||||||
|
def task_reporter(tool_name, task_name, debug=False, time_saved=0, time_cost=0):
|
||||||
|
'''
|
||||||
|
task reporter function
|
||||||
|
:param tool_name: name of the tool, eg "MetaBox"
|
||||||
|
:param task_name: name of the task, eg "CheckUV"
|
||||||
|
:param debug: set to True when develop, and set it to False when release the tool to artists
|
||||||
|
:param time_saved: time saved for this task, usually should communicate with artists to get a reasonable value
|
||||||
|
:param time_cost: time cost for this task, you can get it from
|
||||||
|
time_cost = round(time.time() - start_time, 3)
|
||||||
|
'''
|
||||||
|
# reporter = TaskReporter()
|
||||||
|
data = {
|
||||||
|
'username': os.environ.get('USERNAME'),
|
||||||
|
'tool_name': tool_name + "(Debug)" if debug else tool_name,
|
||||||
|
'task_name': task_name,
|
||||||
|
'time_saved': str(time_saved), # 确保是字符串格式
|
||||||
|
'time_cost': str(round(time_cost, 3)), # 确保是字符串格式
|
||||||
|
'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 使用文档中指定的时间格式
|
||||||
|
}
|
||||||
|
reporter.report_task(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#---------------------------------- usage 1: decorator ----------------------------------
|
||||||
|
debug_mode = True
|
||||||
|
@task_reporter_decorator(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5)
|
||||||
|
def check_uv_dec():
|
||||||
|
print("Check UV, and report by decorator")
|
||||||
|
|
||||||
|
#---------------------------------- usage 2: function ----------------------------------
|
||||||
|
def check_uv_func():
|
||||||
|
debug_mode = True
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# function code
|
||||||
|
print("Check UV, and report by function")
|
||||||
|
|
||||||
|
task_reporter(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5, time_cost=round(time.time()-start_time, 3))
|
||||||
|
|
||||||
|
check_uv_dec()
|
||||||
|
check_uv_func()
|
||||||
|
|
140
api.php
Normal file
140
api.php
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
// 设置错误报告
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// 允许跨域请求
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type");
|
||||||
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
|
||||||
|
// 处理OPTIONS请求(预检请求)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库连接信息
|
||||||
|
$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) {
|
||||||
|
echo json_encode(["error" => "数据库连接失败: " . $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求到日志文件
|
||||||
|
function log_request($message) {
|
||||||
|
$log_file = __DIR__ . '/api_log.txt';
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$log_message = "[$timestamp] $message\n";
|
||||||
|
file_put_contents($log_file, $log_message, FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理添加任务的POST请求
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
try {
|
||||||
|
log_request("收到POST请求");
|
||||||
|
$raw_data = file_get_contents('php://input');
|
||||||
|
log_request("原始数据: $raw_data");
|
||||||
|
|
||||||
|
$data = json_decode($raw_data, true);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new Exception("无效的JSON数据: " . json_last_error_msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
log_request("解析后的数据: " . print_r($data, true));
|
||||||
|
|
||||||
|
// 验证必要字段
|
||||||
|
$required_fields = ['username', 'tool_name', 'task_name', 'time_saved', 'time_cost'];
|
||||||
|
foreach ($required_fields as $field) {
|
||||||
|
if (!isset($data[$field]) || (is_string($data[$field]) && trim($data[$field]) === '')) {
|
||||||
|
throw new Exception("缺少必要字段: $field");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有提供时间戳,使用当前时间
|
||||||
|
if (!isset($data['timestamp']) || empty($data['timestamp'])) {
|
||||||
|
$data['timestamp'] = date('Y-m-d H:i:s');
|
||||||
|
log_request("使用当前时间作为时间戳: {$data['timestamp']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保时间戳格式正确
|
||||||
|
if (strpos($data['timestamp'], 'T') !== false) {
|
||||||
|
$parts = explode(".", $data['timestamp']);
|
||||||
|
$data['timestamp'] = str_replace("T", " ", $parts[0]);
|
||||||
|
log_request("转换ISO时间戳格式: {$data['timestamp']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备SQL语句
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO task (username, tool_name, task_name, time_saved, time_cost, timestamp)
|
||||||
|
VALUES (:username, :tool_name, :task_name, :time_saved, :time_cost, :timestamp)");
|
||||||
|
|
||||||
|
// 绑定参数
|
||||||
|
$stmt->bindParam(':username', $data['username']);
|
||||||
|
$stmt->bindParam(':tool_name', $data['tool_name']);
|
||||||
|
$stmt->bindParam(':task_name', $data['task_name']);
|
||||||
|
$stmt->bindParam(':time_saved', $data['time_saved']);
|
||||||
|
$stmt->bindParam(':time_cost', $data['time_cost']);
|
||||||
|
$stmt->bindParam(':timestamp', $data['timestamp']);
|
||||||
|
|
||||||
|
log_request("准备执行SQL插入");
|
||||||
|
|
||||||
|
// 执行SQL
|
||||||
|
$result = $stmt->execute();
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$last_id = $pdo->lastInsertId();
|
||||||
|
log_request("数据成功插入,ID: $last_id");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"message" => "{$data['timestamp']} {$data['tool_name']}-{$data['task_name']} reported successfully!",
|
||||||
|
"id" => $last_id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
log_request("数据插入失败");
|
||||||
|
throw new Exception("数据插入失败");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
log_request("错误: " . $e->getMessage());
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"error" => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理简单的GET请求,用于测试API是否正常工作
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
echo json_encode([
|
||||||
|
"status" => "API正常工作",
|
||||||
|
"message" => "使用POST请求提交任务数据",
|
||||||
|
"example" => [
|
||||||
|
"username" => "用户名",
|
||||||
|
"tool_name" => "工具名称",
|
||||||
|
"task_name" => "任务名称",
|
||||||
|
"time_saved" => "节省时间(小时)",
|
||||||
|
"time_cost" => "花费时间(小时)",
|
||||||
|
"timestamp" => "可选,格式:YYYY-MM-DD HH:MM:SS"
|
||||||
|
],
|
||||||
|
"contact" => "jeffreytsai1004@gmail.com"
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是GET或POST请求,返回错误
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(["error" => "不支持的请求方法"]);
|
67
check_data.php
Normal file
67
check_data.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?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");
|
||||||
|
echo "数据库连接成功!<br>";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("数据库连接失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询最近的记录
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("SELECT * FROM task ORDER BY id DESC LIMIT 20");
|
||||||
|
$records = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo "<h2>最近20条记录</h2>";
|
||||||
|
|
||||||
|
if (count($records) > 0) {
|
||||||
|
echo "<table border='1' cellpadding='5' cellspacing='0'>";
|
||||||
|
echo "<tr><th>ID</th><th>用户名</th><th>工具名称</th><th>任务名称</th><th>节省时间</th><th>花费时间</th><th>时间戳</th></tr>";
|
||||||
|
|
||||||
|
foreach ($records as $record) {
|
||||||
|
echo "<tr>";
|
||||||
|
echo "<td>{$record['id']}</td>";
|
||||||
|
echo "<td>{$record['username']}</td>";
|
||||||
|
echo "<td>{$record['tool_name']}</td>";
|
||||||
|
echo "<td>{$record['task_name']}</td>";
|
||||||
|
echo "<td>{$record['time_saved']}</td>";
|
||||||
|
echo "<td>{$record['time_cost']}</td>";
|
||||||
|
echo "<td>{$record['timestamp']}</td>";
|
||||||
|
echo "</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "</table>";
|
||||||
|
} else {
|
||||||
|
echo "没有找到记录";
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "查询失败: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查API请求日志
|
||||||
|
echo "<h2>API请求日志检查</h2>";
|
||||||
|
|
||||||
|
// 检查api.php是否正确处理请求
|
||||||
|
echo "<p>请确认以下几点:</p>";
|
||||||
|
echo "<ol>";
|
||||||
|
echo "<li>api.php文件是否正确接收和处理请求</li>";
|
||||||
|
echo "<li>数据库连接信息是否正确</li>";
|
||||||
|
echo "<li>TaskReporter.py是否正确发送数据到api.php</li>";
|
||||||
|
echo "</ol>";
|
||||||
|
|
||||||
|
// 显示刷新页面的链接
|
||||||
|
echo "<p><a href='query_task_data.php'>刷新数据查询页面</a></p>";
|
||||||
|
?>
|
93
clean_old_reports.php
Normal file
93
clean_old_reports.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 清理旧报告文件脚本
|
||||||
|
*
|
||||||
|
* 此脚本用于自动清理过期的报告文件,保持报告目录整洁
|
||||||
|
* 可以通过群晖的计划任务定期执行
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 设置错误报告
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// 配置信息
|
||||||
|
$config = [
|
||||||
|
// 保留最近的报告数量
|
||||||
|
'keep_recent' => 30,
|
||||||
|
|
||||||
|
// 报告目录
|
||||||
|
'reports_dir' => __DIR__ . '/reports/',
|
||||||
|
|
||||||
|
// 日志文件
|
||||||
|
'log_file' => __DIR__ . '/logs/clean_reports.log',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 记录日志
|
||||||
|
function log_message($message) {
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
// 确保日志目录存在
|
||||||
|
$log_dir = dirname($config['log_file']);
|
||||||
|
if (!is_dir($log_dir)) {
|
||||||
|
mkdir($log_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$date = date('Y-m-d H:i:s');
|
||||||
|
$log_entry = "[$date] $message\n";
|
||||||
|
|
||||||
|
// 写入日志文件
|
||||||
|
file_put_contents($config['log_file'], $log_entry, FILE_APPEND);
|
||||||
|
|
||||||
|
// 如果是CLI模式,也输出到控制台
|
||||||
|
if (php_sapi_name() === 'cli') {
|
||||||
|
echo $log_entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主程序
|
||||||
|
try {
|
||||||
|
log_message('===== 开始执行报告清理脚本 =====');
|
||||||
|
|
||||||
|
// 检查报告目录是否存在
|
||||||
|
if (!is_dir($config['reports_dir'])) {
|
||||||
|
throw new Exception("报告目录不存在: {$config['reports_dir']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有报告文件
|
||||||
|
$reports = glob($config['reports_dir'] . 'report_*.html');
|
||||||
|
|
||||||
|
if (empty($reports)) {
|
||||||
|
log_message('没有找到需要清理的报告文件');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message('找到 ' . count($reports) . ' 个报告文件');
|
||||||
|
|
||||||
|
// 按修改时间排序(最新的在前)
|
||||||
|
usort($reports, function($a, $b) {
|
||||||
|
return filemtime($b) - filemtime($a);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保留最近的N个报告
|
||||||
|
$reports_to_keep = array_slice($reports, 0, $config['keep_recent']);
|
||||||
|
$reports_to_delete = array_slice($reports, $config['keep_recent']);
|
||||||
|
|
||||||
|
log_message('将保留最近的 ' . count($reports_to_keep) . ' 个报告');
|
||||||
|
log_message('将删除 ' . count($reports_to_delete) . ' 个旧报告');
|
||||||
|
|
||||||
|
// 删除旧报告
|
||||||
|
foreach ($reports_to_delete as $report) {
|
||||||
|
$filename = basename($report);
|
||||||
|
if (unlink($report)) {
|
||||||
|
log_message("已删除: $filename");
|
||||||
|
} else {
|
||||||
|
log_message("删除失败: $filename");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message('===== 报告清理完成 =====');
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
log_message('错误: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
79
client_example.php
Normal file
79
client_example.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 任务报告系统客户端示例
|
||||||
|
*
|
||||||
|
* 此示例演示如何使用PHP向任务报告系统API提交数据
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 设置API地址
|
||||||
|
$api_url = 'https://toolstrack.cgnico.com/api.php';
|
||||||
|
// 本地测试可以使用:
|
||||||
|
// $api_url = 'http://192.168.2.4:9000/api.php';
|
||||||
|
|
||||||
|
// 准备要提交的数据
|
||||||
|
$data = [
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'MetaBox',
|
||||||
|
'task_name' => 'ExampleTask',
|
||||||
|
'time_saved' => '1.5', // 节省时间(小时)
|
||||||
|
'time_cost' => '0.2', // 花费时间(小时)
|
||||||
|
// 'timestamp' => date('Y-m-d H:i:s') // 可选,如果不提供将使用服务器时间
|
||||||
|
];
|
||||||
|
|
||||||
|
// 将数据转换为JSON
|
||||||
|
$json_data = json_encode($data);
|
||||||
|
|
||||||
|
// 设置cURL选项
|
||||||
|
$ch = curl_init($api_url);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Content-Length: ' . strlen($json_data)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 执行请求
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// 输出结果
|
||||||
|
echo "HTTP响应代码: $http_code\n";
|
||||||
|
echo "响应内容:\n";
|
||||||
|
echo $response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Python客户端示例
|
||||||
|
*
|
||||||
|
* 以下是使用Python向API提交数据的示例代码
|
||||||
|
*
|
||||||
|
* ```python
|
||||||
|
* import requests
|
||||||
|
* import json
|
||||||
|
* from datetime import datetime
|
||||||
|
*
|
||||||
|
* # 设置API地址
|
||||||
|
* api_url = 'https://toolstrack.cgnico.com/api.php'
|
||||||
|
* # 本地测试可以使用:
|
||||||
|
* # api_url = 'http://192.168.2.4:9000/api.php'
|
||||||
|
*
|
||||||
|
* # 准备要提交的数据
|
||||||
|
* data = {
|
||||||
|
* 'username': 'JeffreyTsai',
|
||||||
|
* 'tool_name': 'MetaBox',
|
||||||
|
* 'task_name': 'ExampleTask',
|
||||||
|
* 'time_saved': '1.5', # 节省时间(小时)
|
||||||
|
* 'time_cost': '0.2', # 花费时间(小时)
|
||||||
|
* # 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 可选
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* # 发送POST请求
|
||||||
|
* response = requests.post(api_url, json=data)
|
||||||
|
*
|
||||||
|
* # 输出结果
|
||||||
|
* print(f"HTTP响应代码: {response.status_code}")
|
||||||
|
* print("响应内容:")
|
||||||
|
* print(response.json())
|
||||||
|
* ```
|
||||||
|
*/
|
9
data.csv
Normal file
9
data.csv
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
index,username,tool_name,task_name,timestamp
|
||||||
|
1,JeffreyTsai,MetaBox,StudioLibrary,2025-01-20 17:53:54
|
||||||
|
2,JeffreyTsai,MetaBox,UVTools,2025-01-20 17:53:54
|
||||||
|
3,JeffreyTsai,MetaBox,UVTools,2025-01-20 17:53:54
|
||||||
|
4,JeffreyTsai,MetaBox,ahoge,2025-01-21 10:23:37
|
||||||
|
5,JeffreyTsai,MetaBox,gscurvetool,2025-01-21 10:23:37
|
||||||
|
6,JeffreyTsai,MetaBox,DebugTool,2025-01-21 10:29:27
|
||||||
|
7,JeffreyTsai,MetaBox,CheckNormal,2025-01-21 10:42:27
|
||||||
|
8,JeffreyTsai,MetaBox,CheckNormal,2025-01-21 10:42:27
|
|
642
generate_report.php
Normal file
642
generate_report.php
Normal file
@@ -0,0 +1,642 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 定期报告生成脚本
|
||||||
|
*
|
||||||
|
* 此脚本生成任务报告并保存到服务器
|
||||||
|
* 可以通过群晖的计划任务定期执行
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 设置错误报告
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// 启用网页输出模式
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
|
||||||
|
// 配置信息
|
||||||
|
$config = [
|
||||||
|
// 数据库配置
|
||||||
|
'db_host' => '192.168.2.4',
|
||||||
|
'db_port' => 3307,
|
||||||
|
'db_name' => 'task_reporter',
|
||||||
|
'db_user' => 'task_reporter',
|
||||||
|
'db_pass' => 'Pass12349ers!',
|
||||||
|
|
||||||
|
// 报告生成选项
|
||||||
|
'author' => 'CGNICO Task Reporter', // 报告作者
|
||||||
|
|
||||||
|
// 报告配置
|
||||||
|
'report_days' => 7, // 报告包含的天数(例如:7表示过去一周)
|
||||||
|
'report_title' => 'CGNICO工具使用报告', // 报告标题
|
||||||
|
];
|
||||||
|
|
||||||
|
// 根据报告天数设置标题
|
||||||
|
if (isset($_GET['days'])) {
|
||||||
|
$config['report_days'] = intval($_GET['days']);
|
||||||
|
|
||||||
|
// 根据天数调整报告标题
|
||||||
|
if ($config['report_days'] == 7) {
|
||||||
|
$config['report_title'] = 'CGNICO工具使用周报';
|
||||||
|
} elseif ($config['report_days'] == 30) {
|
||||||
|
$config['report_title'] = 'CGNICO工具使用月报';
|
||||||
|
} elseif ($config['report_days'] == 90) {
|
||||||
|
$config['report_title'] = 'CGNICO工具使用季报';
|
||||||
|
} elseif ($config['report_days'] == 365) {
|
||||||
|
$config['report_title'] = 'CGNICO工具使用年报';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建数据库连接
|
||||||
|
try {
|
||||||
|
$pdo = new PDO(
|
||||||
|
"mysql:host={$config['db_host']};port={$config['db_port']};dbname={$config['db_name']}",
|
||||||
|
$config['db_user'],
|
||||||
|
$config['db_pass']
|
||||||
|
);
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$pdo->exec("SET NAMES utf8");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("数据库连接失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成报告
|
||||||
|
function generate_report($pdo, $days) {
|
||||||
|
$report = [];
|
||||||
|
|
||||||
|
// 获取日期范围
|
||||||
|
$end_date = date('Y-m-d');
|
||||||
|
$start_date = date('Y-m-d', strtotime("-{$days} days"));
|
||||||
|
|
||||||
|
// 1. 总体统计
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_tasks,
|
||||||
|
SUM(time_saved) as total_time_saved,
|
||||||
|
COUNT(DISTINCT username) as total_users,
|
||||||
|
COUNT(DISTINCT tool_name) as total_tools
|
||||||
|
FROM task
|
||||||
|
WHERE timestamp BETWEEN :start_date AND :end_date
|
||||||
|
AND tool_name NOT LIKE '%(Debug)%'
|
||||||
|
");
|
||||||
|
$stmt->bindParam(':start_date', $start_date);
|
||||||
|
$stmt->bindParam(':end_date', $end_date);
|
||||||
|
$stmt->execute();
|
||||||
|
$report['summary'] = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 2. 工具使用统计
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
tool_name,
|
||||||
|
COUNT(*) as usage_count,
|
||||||
|
SUM(time_saved) as time_saved
|
||||||
|
FROM task
|
||||||
|
WHERE timestamp BETWEEN :start_date AND :end_date
|
||||||
|
AND tool_name NOT LIKE '%(Debug)%'
|
||||||
|
GROUP BY tool_name
|
||||||
|
ORDER BY usage_count DESC
|
||||||
|
LIMIT 10
|
||||||
|
");
|
||||||
|
$stmt->bindParam(':start_date', $start_date);
|
||||||
|
$stmt->bindParam(':end_date', $end_date);
|
||||||
|
$stmt->execute();
|
||||||
|
$report['tools'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 3. 用户使用统计
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
username,
|
||||||
|
COUNT(*) as usage_count,
|
||||||
|
SUM(time_saved) as time_saved
|
||||||
|
FROM task
|
||||||
|
WHERE timestamp BETWEEN :start_date AND :end_date
|
||||||
|
AND tool_name NOT LIKE '%(Debug)%'
|
||||||
|
GROUP BY username
|
||||||
|
ORDER BY usage_count DESC
|
||||||
|
LIMIT 10
|
||||||
|
");
|
||||||
|
$stmt->bindParam(':start_date', $start_date);
|
||||||
|
$stmt->bindParam(':end_date', $end_date);
|
||||||
|
$stmt->execute();
|
||||||
|
$report['users'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 4. 每日使用趋势
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
DATE(timestamp) as date,
|
||||||
|
COUNT(*) as usage_count,
|
||||||
|
SUM(time_saved) as time_saved
|
||||||
|
FROM task
|
||||||
|
WHERE timestamp BETWEEN :start_date AND :end_date
|
||||||
|
AND tool_name NOT LIKE '%(Debug)%'
|
||||||
|
GROUP BY DATE(timestamp)
|
||||||
|
ORDER BY date
|
||||||
|
");
|
||||||
|
$stmt->bindParam(':start_date', $start_date);
|
||||||
|
$stmt->bindParam(':end_date', $end_date);
|
||||||
|
$stmt->execute();
|
||||||
|
$report['daily'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'report' => $report,
|
||||||
|
'start_date' => $start_date,
|
||||||
|
'end_date' => $end_date
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成HTML报告
|
||||||
|
function generate_html_report($report_data) {
|
||||||
|
$report = $report_data['report'];
|
||||||
|
$start_date = $report_data['start_date'];
|
||||||
|
$end_date = $report_data['end_date'];
|
||||||
|
|
||||||
|
$html = '
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>CGNICO工具使用报告</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
.summary {
|
||||||
|
background-color: #f0f7ff;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
color: #0066cc;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>CGNICO工具使用报告</h1>
|
||||||
|
<p>报告时间范围: ' . $start_date . ' 至 ' . $end_date . '</p>
|
||||||
|
|
||||||
|
<div class="summary">
|
||||||
|
<h2>总体统计</h2>
|
||||||
|
<p>在过去的 ' . round((strtotime($end_date) - strtotime($start_date)) / 86400) . ' 天中:</p>
|
||||||
|
<ul>
|
||||||
|
<li>总任务数: <span class="highlight">' . number_format($report['summary']['total_tasks']) . '</span></li>
|
||||||
|
<li>总节省时间: <span class="highlight">' . number_format($report['summary']['total_time_saved'], 2) . ' 小时</span></li>
|
||||||
|
<li>活跃用户数: <span class="highlight">' . $report['summary']['total_users'] . '</span></li>
|
||||||
|
<li>使用的工具数: <span class="highlight">' . $report['summary']['total_tools'] . '</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>工具使用排名</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>排名</th>
|
||||||
|
<th>工具名称</th>
|
||||||
|
<th>使用次数</th>
|
||||||
|
<th>节省时间 (小时)</th>
|
||||||
|
</tr>';
|
||||||
|
|
||||||
|
$rank = 1;
|
||||||
|
foreach ($report['tools'] as $tool) {
|
||||||
|
$html .= '
|
||||||
|
<tr>
|
||||||
|
<td>' . $rank++ . '</td>
|
||||||
|
<td>' . htmlspecialchars($tool['tool_name']) . '</td>
|
||||||
|
<td>' . number_format($tool['usage_count']) . '</td>
|
||||||
|
<td>' . number_format($tool['time_saved'], 2) . '</td>
|
||||||
|
</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>用户使用排名</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>排名</th>
|
||||||
|
<th>用户名</th>
|
||||||
|
<th>使用次数</th>
|
||||||
|
<th>节省时间 (小时)</th>
|
||||||
|
</tr>';
|
||||||
|
|
||||||
|
$rank = 1;
|
||||||
|
foreach ($report['users'] as $user) {
|
||||||
|
$html .= '
|
||||||
|
<tr>
|
||||||
|
<td>' . $rank++ . '</td>
|
||||||
|
<td>' . htmlspecialchars($user['username']) . '</td>
|
||||||
|
<td>' . number_format($user['usage_count']) . '</td>
|
||||||
|
<td>' . number_format($user['time_saved'], 2) . '</td>
|
||||||
|
</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>每日使用趋势</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>日期</th>
|
||||||
|
<th>使用次数</th>
|
||||||
|
<th>节省时间 (小时)</th>
|
||||||
|
</tr>';
|
||||||
|
|
||||||
|
foreach ($report['daily'] as $day) {
|
||||||
|
$html .= '
|
||||||
|
<tr>
|
||||||
|
<td>' . $day['date'] . '</td>
|
||||||
|
<td>' . number_format($day['usage_count']) . '</td>
|
||||||
|
<td>' . number_format($day['time_saved'], 2) . '</td>
|
||||||
|
</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="margin-top: 30px; color: #666; font-size: 12px; text-align: center;">
|
||||||
|
<p>此报告由CGNICO Task Reporter自动生成于 ' . date('Y-m-d H:i:s') . '</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录日志
|
||||||
|
function log_message($message) {
|
||||||
|
$log_dir = __DIR__ . '/logs';
|
||||||
|
if (!is_dir($log_dir)) {
|
||||||
|
mkdir($log_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$log_file = $log_dir . '/report_' . date('Y-m-d') . '.log';
|
||||||
|
$time = date('Y-m-d H:i:s');
|
||||||
|
file_put_contents($log_file, "[$time] $message\n", FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主程序
|
||||||
|
try {
|
||||||
|
// 创建报告目录(如果不存在)
|
||||||
|
$report_dir = __DIR__ . '/reports/';
|
||||||
|
if (!is_dir($report_dir)) {
|
||||||
|
mkdir($report_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要生成新报告
|
||||||
|
$generate_new_report = false;
|
||||||
|
$report_file = $report_dir . 'report_' . date('Y-m-d') . '.html';
|
||||||
|
|
||||||
|
if (isset($_GET['generate']) && $_GET['generate'] == '1') {
|
||||||
|
$generate_new_report = true;
|
||||||
|
} elseif (!file_exists($report_file)) {
|
||||||
|
// 如果今天的报告不存在,自动生成
|
||||||
|
$generate_new_report = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($generate_new_report) {
|
||||||
|
log_message('===== 开始执行报告生成脚本 =====');
|
||||||
|
|
||||||
|
// 生成报告数据
|
||||||
|
log_message('开始生成报告数据...');
|
||||||
|
$days = $config['report_days'];
|
||||||
|
$report_data = generate_report($pdo, $days);
|
||||||
|
log_message('报告数据生成完成');
|
||||||
|
|
||||||
|
// 生成HTML报告
|
||||||
|
log_message('生成HTML报告...');
|
||||||
|
$html_report = generate_html_report($report_data);
|
||||||
|
log_message('HTML报告生成完成');
|
||||||
|
|
||||||
|
// 保存报告到文件
|
||||||
|
file_put_contents($report_file, $html_report);
|
||||||
|
log_message('报告已保存到: ' . $report_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始HTML输出
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>CGNICO工具使用报告生成器</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #ff8c00;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #555;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
background-color: #dff0d8;
|
||||||
|
border: 1px solid #d6e9c6;
|
||||||
|
color: #3c763d;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #337ab7;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #286090;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background-color: #3d8b40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #e3e3e3;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
color: #777;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>CGNICO工具使用报告生成器</h1>
|
||||||
|
|
||||||
|
<?php if ($generate_new_report): ?>
|
||||||
|
<div class="success-message">
|
||||||
|
<p><strong>报告已生成成功!</strong></p>
|
||||||
|
<p>报告时间范围: 过去<?php echo $config['report_days']; ?>天 (<?php echo date('Y-m-d', strtotime("-{$config['report_days']} days")); ?> 至 <?php echo date('Y-m-d'); ?>)</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<a href="reports/report_<?php echo date('Y-m-d'); ?>.html" target="_blank" class="btn btn-primary">查看最新报告</a>
|
||||||
|
<a href="reports/report_<?php echo date('Y-m-d'); ?>.html" download class="btn btn-success">下载最新报告</a>
|
||||||
|
<a href="index.php" class="btn btn-secondary">返回首页</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<h3>生成新报告</h3>
|
||||||
|
<form method="get" action="">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="days">报告时间范围:</label>
|
||||||
|
<select name="days" id="days">
|
||||||
|
<option value="7" <?php echo ($config['report_days'] == 7) ? 'selected' : ''; ?>>过去7天 (一周)</option>
|
||||||
|
<option value="30" <?php echo ($config['report_days'] == 30) ? 'selected' : ''; ?>>过去30天 (一个月)</option>
|
||||||
|
<option value="90" <?php echo ($config['report_days'] == 90) ? 'selected' : ''; ?>>过去90天 (一季度)</option>
|
||||||
|
<option value="365" <?php echo ($config['report_days'] == 365) ? 'selected' : ''; ?>>过去365天 (一年)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="generate" value="1">
|
||||||
|
<button type="submit" class="btn btn-primary">生成报告</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// 历史报告列表
|
||||||
|
$reports_dir = __DIR__ . '/reports/';
|
||||||
|
if (is_dir($reports_dir)) {
|
||||||
|
$reports = glob($reports_dir . 'report_*.html');
|
||||||
|
if (!empty($reports)) {
|
||||||
|
echo "<div class=\"panel\">\n";
|
||||||
|
echo "<h3>历史报告</h3>\n";
|
||||||
|
echo "<table>\n";
|
||||||
|
echo "<tr>\n";
|
||||||
|
echo "<th>报告日期</th>\n";
|
||||||
|
echo "<th>报告类型</th>\n";
|
||||||
|
echo "<th>操作</th>\n";
|
||||||
|
echo "</tr>\n";
|
||||||
|
|
||||||
|
rsort($reports); // 按日期降序排序
|
||||||
|
|
||||||
|
foreach (array_slice($reports, 0, 10) as $report) { // 只显示最近10个
|
||||||
|
$filename = basename($report);
|
||||||
|
$date = substr($filename, 7, 10); // 从 report_YYYY-MM-DD.html 提取日期
|
||||||
|
|
||||||
|
// 尝试确定报告类型
|
||||||
|
$report_type = "常规报告";
|
||||||
|
$file_content = file_get_contents($report);
|
||||||
|
if (strpos($file_content, "工具使用周报") !== false) {
|
||||||
|
$report_type = "周报";
|
||||||
|
} elseif (strpos($file_content, "工具使用月报") !== false) {
|
||||||
|
$report_type = "月报";
|
||||||
|
} elseif (strpos($file_content, "工具使用季报") !== false) {
|
||||||
|
$report_type = "季报";
|
||||||
|
} elseif (strpos($file_content, "工具使用年报") !== false) {
|
||||||
|
$report_type = "年报";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "<tr>\n";
|
||||||
|
echo "<td>{$date}</td>\n";
|
||||||
|
echo "<td>{$report_type}</td>\n";
|
||||||
|
echo "<td class=\"table-actions\">\n";
|
||||||
|
echo "<a href='reports/{$filename}' target='_blank' class='btn btn-primary' style='padding: 5px 10px; margin-right: 5px;'>查看</a>\n";
|
||||||
|
echo "<a href='reports/{$filename}' download class='btn btn-success' style='padding: 5px 10px;'>下载</a>\n";
|
||||||
|
echo "</td>\n";
|
||||||
|
echo "</tr>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "</table>\n";
|
||||||
|
echo "</div>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>CGNICO Task Report System © <?php echo date('Y'); ?> | 联系方式: <a href="mailto:jeffreytsai1004@gmail.com">jeffreytsai1004@gmail.com</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
} catch (Exception $e) {
|
||||||
|
log_message('错误: ' . $e->getMessage());
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>错误 - CGNICO工具使用报告生成器</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #d9534f;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
background-color: #f2dede;
|
||||||
|
border: 1px solid #ebccd1;
|
||||||
|
color: #a94442;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>发生错误</h1>
|
||||||
|
<div class="error-message">
|
||||||
|
<p><strong>错误:</strong> <?php echo $e->getMessage(); ?></p>
|
||||||
|
</div>
|
||||||
|
<a href="index.php" class="btn">返回首页</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
585
index.php
Normal file
585
index.php
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
<?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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建任务表(如果不存在)
|
||||||
|
try {
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS task (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(100) NOT NULL,
|
||||||
|
tool_name VARCHAR(100) NOT NULL,
|
||||||
|
task_name VARCHAR(100) NOT NULL,
|
||||||
|
time_saved VARCHAR(100) NOT NULL,
|
||||||
|
time_cost VARCHAR(100) NOT NULL,
|
||||||
|
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)";
|
||||||
|
$pdo->exec($sql);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("创建表失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理添加任务的POST请求
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'add_task') {
|
||||||
|
try {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new Exception("无效的JSON数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO task (username, tool_name, task_name, time_saved, time_cost, timestamp)
|
||||||
|
VALUES (:username, :tool_name, :task_name, :time_saved, :time_cost, :timestamp)");
|
||||||
|
|
||||||
|
$stmt->bindParam(':username', $data['username']);
|
||||||
|
$stmt->bindParam(':tool_name', $data['tool_name']);
|
||||||
|
$stmt->bindParam(':task_name', $data['task_name']);
|
||||||
|
$stmt->bindParam(':time_saved', $data['time_saved']);
|
||||||
|
$stmt->bindParam(':time_cost', $data['time_cost']);
|
||||||
|
$stmt->bindParam(':timestamp', $data['timestamp']);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$time = explode(".", $data['timestamp'])[0];
|
||||||
|
$time = str_replace("T", " ", $time);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
"message" => "$time {$data['tool_name']}-{$data['task_name']} reported successfully!"
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(["error" => $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理查询任务数据的GET请求
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'query_task_data') {
|
||||||
|
try {
|
||||||
|
// 获取筛选参数
|
||||||
|
$start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null;
|
||||||
|
$end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null;
|
||||||
|
$task_name = isset($_GET['task_name']) ? $_GET['task_name'] : null;
|
||||||
|
$tool_name = isset($_GET['tool_name']) ? $_GET['tool_name'] : null;
|
||||||
|
$time_saved = isset($_GET['time_saved']) ? $_GET['time_saved'] : null;
|
||||||
|
$time_cost = isset($_GET['time_cost']) ? $_GET['time_cost'] : null;
|
||||||
|
$show_debug = isset($_GET['show_debug']) && $_GET['show_debug'] === 'true';
|
||||||
|
|
||||||
|
// 获取排序参数
|
||||||
|
$sort_by = isset($_GET['sort_by']) ? $_GET['sort_by'] : 'timestamp';
|
||||||
|
$sort_direction = isset($_GET['sort_direction']) ? $_GET['sort_direction'] : 'desc';
|
||||||
|
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
$per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 50;
|
||||||
|
|
||||||
|
// 构建查询
|
||||||
|
$query = "SELECT * FROM task WHERE 1=1";
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
// 应用日期筛选
|
||||||
|
if ($start_date) {
|
||||||
|
$query .= " AND timestamp >= :start_date";
|
||||||
|
$params[':start_date'] = $start_date . " 00:00:00";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($end_date) {
|
||||||
|
$query .= " AND timestamp <= :end_date";
|
||||||
|
$params[':end_date'] = $end_date . " 23:59:59";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用任务名称筛选
|
||||||
|
if ($task_name) {
|
||||||
|
$query .= " AND task_name LIKE :task_name";
|
||||||
|
$params[':task_name'] = "%$task_name%";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用工具名称筛选
|
||||||
|
if ($tool_name) {
|
||||||
|
$query .= " AND tool_name LIKE :tool_name";
|
||||||
|
$params[':tool_name'] = "%$tool_name%";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排除调试工具(如果不显示调试)
|
||||||
|
if (!$show_debug) {
|
||||||
|
$query .= " AND tool_name NOT LIKE '%(Debug)%'";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用排序
|
||||||
|
$valid_sort_fields = ['timestamp', 'time_saved', 'time_cost'];
|
||||||
|
if (!in_array($sort_by, $valid_sort_fields)) {
|
||||||
|
$sort_by = 'timestamp';
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= " ORDER BY $sort_by " . ($sort_direction === 'asc' ? 'ASC' : 'DESC');
|
||||||
|
|
||||||
|
// 应用分页
|
||||||
|
$offset = ($page - 1) * $per_page;
|
||||||
|
$query .= " LIMIT :offset, :limit";
|
||||||
|
$params[':offset'] = $offset;
|
||||||
|
$params[':limit'] = $per_page;
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
$stmt = $pdo->prepare($query);
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
if ($key === ':offset' || $key === ':limit') {
|
||||||
|
$stmt->bindValue($key, $value, PDO::PARAM_INT);
|
||||||
|
} else {
|
||||||
|
$stmt->bindValue($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 获取总记录数
|
||||||
|
$count_query = "SELECT COUNT(*) FROM task WHERE 1=1";
|
||||||
|
$count_params = [];
|
||||||
|
|
||||||
|
if ($start_date) {
|
||||||
|
$count_query .= " AND timestamp >= :start_date";
|
||||||
|
$count_params[':start_date'] = $start_date . " 00:00:00";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($end_date) {
|
||||||
|
$count_query .= " AND timestamp <= :end_date";
|
||||||
|
$count_params[':end_date'] = $end_date . " 23:59:59";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($task_name) {
|
||||||
|
$count_query .= " AND task_name LIKE :task_name";
|
||||||
|
$count_params[':task_name'] = "%$task_name%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tool_name) {
|
||||||
|
$count_query .= " AND tool_name LIKE :tool_name";
|
||||||
|
$count_params[':tool_name'] = "%$tool_name%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$show_debug) {
|
||||||
|
$count_query .= " AND tool_name NOT LIKE '%(Debug)%'";
|
||||||
|
}
|
||||||
|
|
||||||
|
$count_stmt = $pdo->prepare($count_query);
|
||||||
|
foreach ($count_params as $key => $value) {
|
||||||
|
$count_stmt->bindValue($key, $value);
|
||||||
|
}
|
||||||
|
$count_stmt->execute();
|
||||||
|
$total_count = $count_stmt->fetchColumn();
|
||||||
|
|
||||||
|
// 获取所有工具名称和任务名称(用于筛选)
|
||||||
|
$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);
|
||||||
|
|
||||||
|
// 返回数据
|
||||||
|
$result = [
|
||||||
|
'tasks' => $tasks,
|
||||||
|
'total' => $total_count,
|
||||||
|
'page' => $page,
|
||||||
|
'per_page' => $per_page,
|
||||||
|
'total_pages' => ceil($total_count / $per_page),
|
||||||
|
'tools' => $tools,
|
||||||
|
'task_names' => $task_names
|
||||||
|
];
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($result);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理获取工具统计数据的GET请求
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'get_tool_statistics') {
|
||||||
|
try {
|
||||||
|
// 获取日期范围
|
||||||
|
$start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null;
|
||||||
|
$end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null;
|
||||||
|
|
||||||
|
// 构建查询
|
||||||
|
$query = "SELECT tool_name, COUNT(*) as request_count, SUM(time_saved) as time_saved
|
||||||
|
FROM task
|
||||||
|
WHERE tool_name NOT LIKE '%(Debug)%'";
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($start_date) {
|
||||||
|
$query .= " AND timestamp >= :start_date";
|
||||||
|
$params[':start_date'] = $start_date . " 00:00:00";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($end_date) {
|
||||||
|
$query .= " AND timestamp <= :end_date";
|
||||||
|
$params[':end_date'] = $end_date . " 23:59:59";
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= " GROUP BY tool_name ORDER BY request_count DESC";
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
$stmt = $pdo->prepare($query);
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
$stmt->bindValue($key, $value);
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$tools_stats = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 计算总请求数和总节省时间
|
||||||
|
$total_requests = 0;
|
||||||
|
$total_time_saved = 0;
|
||||||
|
|
||||||
|
foreach ($tools_stats as $stat) {
|
||||||
|
$total_requests += $stat['request_count'];
|
||||||
|
$total_time_saved += $stat['time_saved'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回数据
|
||||||
|
$result = [
|
||||||
|
'tools' => $tools_stats,
|
||||||
|
'total_requests' => $total_requests,
|
||||||
|
'total_time_saved' => $total_time_saved
|
||||||
|
];
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($result);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理导出CSV的GET请求
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'export_csv') {
|
||||||
|
try {
|
||||||
|
// 获取筛选参数
|
||||||
|
$start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null;
|
||||||
|
$end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null;
|
||||||
|
$task_name = isset($_GET['task_name']) ? $_GET['task_name'] : null;
|
||||||
|
$tool_name = isset($_GET['tool_name']) ? $_GET['tool_name'] : null;
|
||||||
|
$show_debug = isset($_GET['show_debug']) && $_GET['show_debug'] === 'true';
|
||||||
|
|
||||||
|
// 构建查询
|
||||||
|
$query = "SELECT id, username, tool_name, task_name, time_saved, time_cost, timestamp
|
||||||
|
FROM task WHERE 1=1";
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($start_date) {
|
||||||
|
$query .= " AND timestamp >= :start_date";
|
||||||
|
$params[':start_date'] = $start_date . " 00:00:00";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($end_date) {
|
||||||
|
$query .= " AND timestamp <= :end_date";
|
||||||
|
$params[':end_date'] = $end_date . " 23:59:59";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($task_name) {
|
||||||
|
$query .= " AND task_name LIKE :task_name";
|
||||||
|
$params[':task_name'] = "%$task_name%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tool_name) {
|
||||||
|
$query .= " AND tool_name LIKE :tool_name";
|
||||||
|
$params[':tool_name'] = "%$tool_name%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$show_debug) {
|
||||||
|
$query .= " AND tool_name NOT LIKE '%(Debug)%'";
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= " ORDER BY timestamp DESC";
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
$stmt = $pdo->prepare($query);
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
$stmt->bindValue($key, $value);
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 设置CSV文件头
|
||||||
|
header('Content-Type: text/csv; charset=utf-8');
|
||||||
|
header('Content-Disposition: attachment; filename=task_data.csv');
|
||||||
|
|
||||||
|
// 创建CSV输出流
|
||||||
|
$output = fopen('php://output', 'w');
|
||||||
|
|
||||||
|
// 添加CSV头行
|
||||||
|
fputcsv($output, ['ID', 'Username', 'Tool Name', 'Task Name', 'Time Saved', 'Time Cost', 'Timestamp']);
|
||||||
|
|
||||||
|
// 添加数据行
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
fputcsv($output, [
|
||||||
|
$task['id'],
|
||||||
|
$task['username'],
|
||||||
|
$task['tool_name'],
|
||||||
|
$task['task_name'],
|
||||||
|
$task['time_saved'],
|
||||||
|
$task['time_cost'],
|
||||||
|
$task['timestamp']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($output);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理获取特定工具任务的GET请求
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'get_tool_tasks') {
|
||||||
|
try {
|
||||||
|
$tool_name = isset($_GET['tool_name']) ? $_GET['tool_name'] : null;
|
||||||
|
|
||||||
|
if (!$tool_name) {
|
||||||
|
throw new Exception("Tool name is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询该工具的所有任务
|
||||||
|
$stmt = $pdo->prepare("SELECT task_name, COUNT(*) as total_requests, SUM(time_saved) as time_saved
|
||||||
|
FROM task
|
||||||
|
WHERE tool_name = :tool_name AND tool_name NOT LIKE '%(Debug)%'
|
||||||
|
GROUP BY task_name
|
||||||
|
ORDER BY total_requests DESC");
|
||||||
|
$stmt->bindParam(':tool_name', $tool_name);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['tasks' => $tasks]);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理获取趋势数据的GET请求
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'get_trend_data') {
|
||||||
|
try {
|
||||||
|
// 获取查询参数中的日期范围
|
||||||
|
$start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null;
|
||||||
|
$end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null;
|
||||||
|
|
||||||
|
// 如果没有指定日期范围,默认显示最近30天
|
||||||
|
if (!$start_date || !$end_date) {
|
||||||
|
$end_date = date('Y-m-d');
|
||||||
|
$start_date = date('Y-m-d', strtotime('-30 days'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将字符串日期转换为日期对象
|
||||||
|
$start_datetime = date_create($start_date);
|
||||||
|
$end_datetime = date_create($end_date);
|
||||||
|
|
||||||
|
// 设置结束日期为当天的最后一秒
|
||||||
|
$end_datetime->setTime(23, 59, 59);
|
||||||
|
|
||||||
|
// 使用PDO查询
|
||||||
|
$stmt = $pdo->prepare("SELECT DATE(timestamp) as date, COUNT(*) as total_requests, SUM(time_saved) as time_saved
|
||||||
|
FROM task
|
||||||
|
WHERE timestamp BETWEEN :start_date AND :end_date
|
||||||
|
GROUP BY DATE(timestamp)
|
||||||
|
ORDER BY date");
|
||||||
|
$stmt->bindValue(':start_date', $start_datetime->format('Y-m-d 00:00:00'));
|
||||||
|
$stmt->bindValue(':end_date', $end_datetime->format('Y-m-d H:i:s'));
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 填充所有日期(包括没有数据的日期)
|
||||||
|
$dates = [];
|
||||||
|
$requests = [];
|
||||||
|
$time_saved = [];
|
||||||
|
|
||||||
|
// 创建日期范围内的所有日期
|
||||||
|
$date_range = [];
|
||||||
|
$current = clone $start_datetime;
|
||||||
|
while ($current <= $end_datetime) {
|
||||||
|
$date_range[$current->format('Y-m-d')] = [
|
||||||
|
'total_requests' => 0,
|
||||||
|
'time_saved' => 0
|
||||||
|
];
|
||||||
|
$current->modify('+1 day');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充实际数据
|
||||||
|
foreach ($results as $row) {
|
||||||
|
$date_range[$row['date']] = [
|
||||||
|
'total_requests' => (int)$row['total_requests'],
|
||||||
|
'time_saved' => (float)$row['time_saved']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为数组格式
|
||||||
|
foreach ($date_range as $date => $stats) {
|
||||||
|
$dates[] = $date;
|
||||||
|
$requests[] = $stats['total_requests'];
|
||||||
|
$time_saved[] = $stats['time_saved'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$response_data = [
|
||||||
|
'dates' => $dates,
|
||||||
|
'requests' => $requests,
|
||||||
|
'timeSaved' => $time_saved
|
||||||
|
];
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($response_data);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有特定操作,显示主页
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Task Report System</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-image: url('static/background.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: rgb(255, 166, 0);
|
||||||
|
font-size: 4em;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-shadow: 2px 2px 4px rgba(114, 77, 10, 0.5);
|
||||||
|
animation: fadeIn 1.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid white;
|
||||||
|
color: white;
|
||||||
|
padding: 15px 40px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
animation: slideUp 1s ease-out 0.5s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 淡入动画 */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-btn {
|
||||||
|
padding: 12px 30px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h1>CGNICO Task Report System</h1>
|
||||||
|
<button class="view-btn" onclick="window.location.href='query_task_data.php'" style="margin-right: 15px;">
|
||||||
|
查看任务数据
|
||||||
|
</button>
|
||||||
|
<button class="view-btn" onclick="window.location.href='generate_report.php'" style="margin-left: 15px;">
|
||||||
|
查看报告
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
236
insert_test_data.php
Normal file
236
insert_test_data.php
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
<?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");
|
||||||
|
echo "数据库连接成功!<br>";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("数据库连接失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保表存在
|
||||||
|
try {
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS task (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(100) NOT NULL,
|
||||||
|
tool_name VARCHAR(100) NOT NULL,
|
||||||
|
task_name VARCHAR(100) NOT NULL,
|
||||||
|
time_saved VARCHAR(100) NOT NULL,
|
||||||
|
time_cost VARCHAR(100) NOT NULL,
|
||||||
|
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)";
|
||||||
|
$pdo->exec($sql);
|
||||||
|
echo "表结构检查完成!<br>";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("创建表失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空现有数据(可选)
|
||||||
|
try {
|
||||||
|
$pdo->exec("TRUNCATE TABLE task");
|
||||||
|
echo "表已清空,准备插入测试数据...<br>";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "清空表失败: " . $e->getMessage() . "<br>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备测试数据
|
||||||
|
$test_data = [
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'MetaBox',
|
||||||
|
'task_name' => 'CheckUV',
|
||||||
|
'time_saved' => '5.2',
|
||||||
|
'time_cost' => '0.3',
|
||||||
|
'timestamp' => '2025-04-01 09:30:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'MetaBox',
|
||||||
|
'task_name' => 'FixNormals',
|
||||||
|
'time_saved' => '3.5',
|
||||||
|
'time_cost' => '0.2',
|
||||||
|
'timestamp' => '2025-04-01 10:15:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'RigTools',
|
||||||
|
'task_name' => 'AutoRig',
|
||||||
|
'time_saved' => '12.0',
|
||||||
|
'time_cost' => '1.5',
|
||||||
|
'timestamp' => '2025-04-02 14:20:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'LiuYang',
|
||||||
|
'tool_name' => 'TextureManager',
|
||||||
|
'task_name' => 'BatchExport',
|
||||||
|
'time_saved' => '8.5',
|
||||||
|
'time_cost' => '0.8',
|
||||||
|
'timestamp' => '2025-04-03 11:45:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'LiuYang',
|
||||||
|
'tool_name' => 'TextureManager',
|
||||||
|
'task_name' => 'ResizeTextures',
|
||||||
|
'time_saved' => '4.2',
|
||||||
|
'time_cost' => '0.5',
|
||||||
|
'timestamp' => '2025-04-03 15:30:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'ZhangWei',
|
||||||
|
'tool_name' => 'AnimTools',
|
||||||
|
'task_name' => 'CycleCheck',
|
||||||
|
'time_saved' => '6.8',
|
||||||
|
'time_cost' => '0.4',
|
||||||
|
'timestamp' => '2025-04-04 09:15:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'ZhangWei',
|
||||||
|
'tool_name' => 'AnimTools',
|
||||||
|
'task_name' => 'KeyCleaner',
|
||||||
|
'time_saved' => '5.5',
|
||||||
|
'time_cost' => '0.3',
|
||||||
|
'timestamp' => '2025-04-04 14:45:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'WangFang',
|
||||||
|
'tool_name' => 'LightingSetup',
|
||||||
|
'task_name' => 'HDRIManager',
|
||||||
|
'time_saved' => '7.2',
|
||||||
|
'time_cost' => '0.6',
|
||||||
|
'timestamp' => '2025-04-05 10:30:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'WangFang',
|
||||||
|
'tool_name' => 'LightingSetup',
|
||||||
|
'task_name' => 'ShadowFix',
|
||||||
|
'time_saved' => '3.8',
|
||||||
|
'time_cost' => '0.4',
|
||||||
|
'timestamp' => '2025-04-05 16:20:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'MetaBox',
|
||||||
|
'task_name' => 'CheckUV',
|
||||||
|
'time_saved' => '5.0',
|
||||||
|
'time_cost' => '0.3',
|
||||||
|
'timestamp' => '2025-04-06 11:10:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'MetaBox(Debug)',
|
||||||
|
'task_name' => 'TestFeature',
|
||||||
|
'time_saved' => '0.5',
|
||||||
|
'time_cost' => '1.2',
|
||||||
|
'timestamp' => '2025-04-07 09:45:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'LiuYang',
|
||||||
|
'tool_name' => 'TextureManager',
|
||||||
|
'task_name' => 'BatchExport',
|
||||||
|
'time_saved' => '8.0',
|
||||||
|
'time_cost' => '0.7',
|
||||||
|
'timestamp' => '2025-04-08 13:30:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'ZhangWei',
|
||||||
|
'tool_name' => 'AnimTools',
|
||||||
|
'task_name' => 'CycleCheck',
|
||||||
|
'time_saved' => '6.5',
|
||||||
|
'time_cost' => '0.4',
|
||||||
|
'timestamp' => '2025-04-09 10:20:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'WangFang',
|
||||||
|
'tool_name' => 'LightingSetup',
|
||||||
|
'task_name' => 'HDRIManager',
|
||||||
|
'time_saved' => '7.0',
|
||||||
|
'time_cost' => '0.6',
|
||||||
|
'timestamp' => '2025-04-10 15:45:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'RigTools',
|
||||||
|
'task_name' => 'AutoRig',
|
||||||
|
'time_saved' => '11.5',
|
||||||
|
'time_cost' => '1.4',
|
||||||
|
'timestamp' => '2025-04-11 09:30:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'LiuYang',
|
||||||
|
'tool_name' => 'TextureManager',
|
||||||
|
'task_name' => 'ResizeTextures',
|
||||||
|
'time_saved' => '4.0',
|
||||||
|
'time_cost' => '0.5',
|
||||||
|
'timestamp' => '2025-04-12 14:15:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'ZhangWei',
|
||||||
|
'tool_name' => 'AnimTools',
|
||||||
|
'task_name' => 'KeyCleaner',
|
||||||
|
'time_saved' => '5.2',
|
||||||
|
'time_cost' => '0.3',
|
||||||
|
'timestamp' => '2025-04-12 16:40:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'WangFang',
|
||||||
|
'tool_name' => 'LightingSetup',
|
||||||
|
'task_name' => 'ShadowFix',
|
||||||
|
'time_saved' => '3.5',
|
||||||
|
'time_cost' => '0.4',
|
||||||
|
'timestamp' => '2025-04-13 10:15:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'MetaBox',
|
||||||
|
'task_name' => 'FixNormals',
|
||||||
|
'time_saved' => '3.2',
|
||||||
|
'time_cost' => '0.2',
|
||||||
|
'timestamp' => '2025-04-13 11:30:00'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username' => 'JeffreyTsai',
|
||||||
|
'tool_name' => 'MetaBox',
|
||||||
|
'task_name' => 'CheckUV',
|
||||||
|
'time_saved' => '5.1',
|
||||||
|
'time_cost' => '0.3',
|
||||||
|
'timestamp' => '2025-04-13 13:45:00'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// 插入测试数据
|
||||||
|
$success_count = 0;
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO task (username, tool_name, task_name, time_saved, time_cost, timestamp)
|
||||||
|
VALUES (:username, :tool_name, :task_name, :time_saved, :time_cost, :timestamp)");
|
||||||
|
|
||||||
|
foreach ($test_data as $data) {
|
||||||
|
try {
|
||||||
|
$stmt->bindParam(':username', $data['username']);
|
||||||
|
$stmt->bindParam(':tool_name', $data['tool_name']);
|
||||||
|
$stmt->bindParam(':task_name', $data['task_name']);
|
||||||
|
$stmt->bindParam(':time_saved', $data['time_saved']);
|
||||||
|
$stmt->bindParam(':time_cost', $data['time_cost']);
|
||||||
|
$stmt->bindParam(':timestamp', $data['timestamp']);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$success_count++;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "插入数据失败: " . $e->getMessage() . "<br>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "成功插入 {$success_count} 条测试数据!<br>";
|
||||||
|
echo "<br>所有操作已完成!<br>";
|
||||||
|
echo "<a href='index.php'>返回首页</a> | <a href='query_task_data.php'>查看数据</a>";
|
||||||
|
?>
|
943
query_task_data.php
Normal file
943
query_task_data.php
Normal file
@@ -0,0 +1,943 @@
|
|||||||
|
<?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">×</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>
|
BIN
static/background.jpg
Normal file
BIN
static/background.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 713 KiB |
1387
templates/data_table.html
Normal file
1387
templates/data_table.html
Normal file
File diff suppressed because it is too large
Load Diff
110
templates/index.html
Normal file
110
templates/index.html
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Task Report System</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-image: url('static/background.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: rgb(255, 166, 0);
|
||||||
|
font-size: 4em;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-shadow: 2px 2px 4px rgba(114, 77, 10, 0.5);
|
||||||
|
animation: fadeIn 1.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid white;
|
||||||
|
color: white;
|
||||||
|
padding: 15px 40px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
animation: slideUp 1s ease-out 0.5s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 淡入动画 */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-btn {
|
||||||
|
padding: 12px 30px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h1>CGNICO Task Report System</h1>
|
||||||
|
<button class="view-btn" onclick="window.location.href='/query_task_data'">
|
||||||
|
View Task Data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in New Issue
Block a user