PCState 是一个运行在 Windows 系统托盘中的小工具,记录用户每天使用电脑的时间分布和各应用的运行时长。
- 空闲检测:通过 Windows API 检测用户是否有键鼠操作
- 活动记录:每分钟记录活跃状态和预处理后的程序名
- 数据可视化:Flask 后端 + React 前端,API 驱动的图表展示
- 在线配置:Web 页面可直接修改黑名单、显示名映射、网站关键词等
- 托盘运行:最小化到系统托盘,开机自启
pcstate/
├── main.py # 程序入口
├── build.py # 打包脚本
├── version.py # 版本号 (如 3.0.1.0)
├── requirements.txt # Python 依赖
│
├── src/ # Python 后端模块
│ ├── main.py # 托盘程序 + 主循环 + Flask 生命周期管理
│ ├── detector.py # 空闲检测 + 活动窗口信息 (Windows API)
│ ├── preprocessor.py # 数据预处理(显示名映射、网站嗅探、IDE/Office识别)
│ ├── sqlite.py # SQLite 存储层 + 配置管理
│ ├── config.py # 配置管理封装
│ ├── chart_data.py # 图表数据聚合(按日期区间+图表类型构建数据)
│ ├── api_server.py # Flask API 服务(图表API + 配置API + 静态托管)
│ ├── exporter_csv.py # CSV 导出
│ ├── startup_manager.py # 开机启动管理
│ ├── notifier.py # Windows Toast 通知
│ └── utils.py # 路径工具函数
│
├── frontend/ # React 前端源码
│ └── src/
│ ├── main.tsx # 前端入口 (dayjs locale + antd ConfigProvider)
│ ├── components/
│ │ ├── App.tsx # 主界面
│ │ ├── ConfigPanel.tsx # 配置面板
│ │ ├── HeatmapChart.tsx
│ │ ├── AppPieChart.tsx
│ │ ├── AppBarChart.tsx
│ │ ├── MultiDayHeatmapChart.tsx
│ │ ├── MultiDayPieChart.tsx
│ │ └── MultiDayBarChart.tsx
│ ├── dataProcessor.ts # 日期工具函数
│ └── index.css
│ ├── vite.config.ts # Vite 配置 (singlefile 插件)
│ └── package.json
│
├── viewer/ # 前端构建产物 (单文件 index.html)
├── public/ # 静态资源 (图标 .ico)
└── pcstate.db # SQLite 数据库 (运行时生成)
┌─────────────────────────────────────────────────────────────────┐
│ check_and_report() [60s循环] │
│ detector → preprocessor → sqlite.write() │
│ │
│ Flask 线程 (懒启动, 10分钟空闲超时自动关闭) │
│ GET / → viewer/index.html │
│ GET /api/chart → chart_data 聚合 → JSON │
│ GET /api/config → DB 配置 → JSON │
│ PUT /api/config/* → 更新 DB + 重载 Preprocessor │
│ GET /api/version → 版本号 │
│ │
│ React 前端 │
│ fetch API → 渲染图表 / 配置管理 │
└─────────────────────────────────────────────────────────────────┘
程序启动后:
- 从 DB 加载预处理配置,初始化
Preprocessor全局实例 - 创建系统托盘图标
- 启动
check_and_report()采集线程(daemon) - 进入
PumpMessages()等待托盘事件
首次点击"查看报表"时懒启动 Flask 服务线程(daemon,10分钟无请求自动关闭)。
采集循环:
def check_and_report():
while running:
idle_time = detector.get_idle_duration()
is_active = idle_time < 60
window_title, process_name = detector.get_active_window_info()
check_time = datetime.now() - timedelta(minutes=1)
# 预处理:显示名映射 + IDE/Office/浏览器识别
base_name = process_name[:-4] if ... else process_name
if base_name not in _blacklist:
prog_name = get_global().process(process_name, window_title)
backend.write(check_time.hour, check_time.minute, is_active, prog_name, check_time.date())
time.sleep(60)调用 Windows API:
GetLastInputInfo— 获取最后输入时间GetTickCount64— 当前系统运行时间GetForegroundWindow+GetWindowThreadProcessId+GetModuleFileNameEx— 前台窗口信息
Preprocessor 类,在采集-入库之间处理进程名+窗口标题:
| 步骤 | 说明 | 示例 |
|---|---|---|
| 去 .exe | 规范化进程名 | WINWORD.EXE → WINWORD |
| 显示名映射 | 查 DB 配置表 | WINWORD → Word |
| IDE 增强 | 解析 VSCode 标题中的项目名 | Code.exe + "App.tsx - pcstate - VSCode" → VSCode - pcstate |
| Office 增强 | 解析标题中的文件名 | WINWORD.EXE + "报告.docx - Word" → Word - 报告.docx |
| 浏览器嗅探 | 窗口标题关键字匹配网站名 | chrome.exe + "知乎 - Chrome" → Chrome浏览器 - 知乎 |
配置(display_names, site_names, blacklist)存储在 DB config 表,可通过 Web UI 修改后即时生效(reload_global())。
表结构:
CREATE TABLE activity (
time INTEGER PRIMARY KEY, -- 分钟级 Unix 时间戳
is_active INTEGER NOT NULL, -- 0/1
prog_name TEXT, -- 预处理后的程序名
win_title TEXT -- 保留字段(已废弃)
);
CREATE TABLE config (
key TEXT PRIMARY KEY, -- 配置键
value TEXT -- 配置值 (JSON 或 简单值)
);核心接口:
write(hour, minute, is_active, prog_name, date)— 写入记录get_slots(date)— 288 槽位活跃数据get_hourly_app_durations(date)— 每小时应用数据get_config(key)/set_config(key, value)— 键值配置get_config_json(key)— JSON 配置反序列化load_preprocessor_config()— 加载预处理全套配置
Config 表存储的键:
| 键 | 类型 | 说明 |
|---|---|---|
day_start_hour |
int | 一天起始小时 (0/4) |
timezone |
int | 时区偏移 |
display_names |
JSON | [{prog_name, display_name}] |
site_names |
JSON | ["知乎","GitHub",...] |
blacklist |
JSON | ["LockApp","LogonUI"] |
根据图表类型和日期区间,从 DB 查询并按需聚合:
| 函数 | 单日输出 | 多日输出 |
|---|---|---|
build_heatmap |
{slots: [288]} |
{hourlyActivity: N×24, days: [...]} |
build_pie |
{appTotals: {...}} |
{appTotals: {...}} |
build_bar |
{hourlyAppData: [24], ...} |
{hourlyAppData: N×24, ...} |
所有函数自动处理 dayStartHour 跨日拼接和 <5% 小应用合并。
懒启动模式:首次调用 open_viewer() 时才创建 daemon 线程启动 Flask。10 分钟无请求自动调用 server.shutdown() 关闭。
端点:
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/ |
托管前端页面 |
GET |
/api/chart?start=&end=&type= |
图表数据 |
GET |
/api/config |
获取全部配置 |
PUT |
/api/config/displayNames |
更新显示名映射 |
PUT |
/api/config/siteNames |
更新网站关键词 |
PUT |
/api/config/blacklist |
更新黑名单 |
PUT |
/api/config/dayStartHour |
更新一天起始时间 |
GET |
/api/version |
获取版本号 |
通过 Windows 启动文件夹管理快捷方式,自动检测并修复指向旧版本的快捷方式。
- React 18 + TypeScript
- Ant Design 5 (DatePicker, Segmented, Collapse, Result, Tooltip, notification)
- ECharts 6 (热力图 custom series、饼图、柱状图)
- Vite 5 + vite-plugin-singlefile (单文件 HTML 输出)
- dayjs (日期处理,周一为周首日)
Flask localhost:21520
│
│ fetch /api/chart?start=&end=&type=
▼
App.tsx (useState: dateRange + chartType)
│
├── chartData → 单日组件 (HeatmapChart/AppPieChart/AppBarChart)
└── chartData → 多日组件 (MultiDay*)
App.tsx
├── 标题栏 + 配置模式按钮
├── 选择区域卡片(日期区间 + 图表类型,仅在报表模式)
│ ├── RangePicker + InfoTip
│ └── Segmented + InfoTip
├── 配置面板卡片(仅在配置模式)
│ └── ConfigPanel
│ ├── Collapse
│ │ ├── 一天起始时间 (Radio.Button, 即时生效)
│ │ ├── 进程黑名单 (TextArea + 保存)
│ │ ├── 显示名映射 (Input 对 + 增删 + 保存)
│ │ └── 网站名关键词 (TextArea + 保存)
│ └── 返回报表按钮
├── 图表区域卡片
│ ├── 图标题
│ ├── 图表主体 (单日/多日组件)
│ └── 图说明 (关键数据 + 说明文字)
└── 页脚 (版本号 + 项目主页)
配置模式下通过 Collapse 折叠面板管理四项设置:
- 一天起始时间:Radio 选择,修改即时调 PUT API 保存
- 黑名单 / 显示名 / 网站名:本地缓存编辑,点击"保存设置"调 PUT API
- 保存成功后 antd
notification.success反馈
# 完整构建
python build.py --release
# 跳过前端构建 (仅重打包 Python)
python build.py --skip-frontend- 版本同步:
build.py读取version.py,同步到package.json - 前端构建:
npm run build→viewer/index.html(单文件) - Python 打包:
PyInstaller打包为单文件 exe - 生成发布: 复制到
release/pcstate-{版本}/
--name=PCStateMonitor
--onefile
--windowed
--add-data=viewer;viewer
--add-data=src;src
--add-data=public;public程序目录/
├── pcstate.db # SQLite 数据库 (活动记录 + 配置)
└── PCStateMonitor.exe # 主程序 (单文件)
不再生成 temp/ 目录。前端页面由 Flask 直接托管。
- 数据按需拉取,无需每次打开页面都重新生成全量数据
- 后端可做聚合运算,前端只负责渲染
- API 端点可复用(配置管理、版本号等)
- 懒启动 + 超时自毁,不占用资源
- 存储层不应包含业务逻辑
- 预处理后入库,查询时无需重复解析
- 配置变更后
reload_global()即时生效
- 灵活扩展,无需修改表结构
- 一条 SQL 读写完整配置
- 前端通过 REST API 直接操作
pip install -r requirements.txt
cd frontend && npm install && npm run build && cd ..
python main.py- 在
chart_data.py中添加build_xxx()函数 - 在
api_server.py的builders字典中注册 - 在前端创建图表组件并在
App.tsx中渲染
- 在
preprocessor.py的默认数据中添加 - 在
sqlite.py的_init_db()中INSERT OR IGNORE - 在
api_server.py的api_set_config中处理对应 key - 在
ConfigPanel.tsx中添加 UI