Files
wecom_it_smart_desk/scripts/dashboard.py
T
Simon 93ba41ed79 feat: 审批流程模块 (T审批A审批)
- 新增 backend/app/api/approval.py 审批API
- 前端H5支持发起审批、审批操作
- 添加审批卡片弹窗组件
- 路由注册审批模块
2026-06-15 09:32:41 +08:00

257 lines
12 KiB
Python

# =============================================================================
# 🎁 惊喜 1: 项目健康度仪表盘
# =============================================================================
# 用途: 一键生成项目健康度总览 HTML(明早桌面打开即用)
# 跑法: python scripts/dashboard.py
# 产物: docs/dashboard.html
# =============================================================================
import os
import json
import subprocess
from datetime import datetime
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
OUTPUT = PROJECT_ROOT / "docs" / "dashboard.html"
def count_lines(glob_pattern: str) -> int:
"""统计符合 glob 的代码总行数"""
import glob
total = 0
for f in glob.glob(glob_pattern, recursive=True):
if os.path.isfile(f):
try:
with open(f, "r", encoding="utf-8", errors="ignore") as fp:
total += sum(1 for _ in fp)
except Exception:
pass
return total
def count_files(glob_pattern: str) -> int:
import glob
return sum(1 for f in glob.glob(glob_pattern, recursive=True) if os.path.isfile(f))
def git_info() -> dict:
"""拿 git 仓库信息"""
try:
result = {
"branch": subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=PROJECT_ROOT, capture_output=True, text=True
).stdout.strip(),
"last_commit": subprocess.run(
["git", "log", "-1", "--format=%h %s"],
cwd=PROJECT_ROOT, capture_output=True, text=True
).stdout.strip(),
"commit_count": subprocess.run(
["git", "rev-list", "--count", "HEAD"],
cwd=PROJECT_ROOT, capture_output=True, text=True
).stdout.strip(),
}
return result
except Exception as e:
return {"error": str(e)}
def main():
# 1. 代码统计
stats = {
"backend_python_files": count_files("backend/app/**/*.py"),
"backend_python_lines": count_lines("backend/app/**/*.py"),
"frontend_admin_files": count_files("frontend-admin/src/**/*.{vue,ts,js}"),
"frontend_agent_files": count_files("frontend-agent/src/**/*.{vue,ts,js}"),
"frontend_h5_files": count_files("frontend-h5/src/**/*.{vue,ts,js}"),
"frontend_portal_files": count_files("frontend-portal/src/**/*.{vue,ts,js}"),
"docs_files": count_files("docs/**/*.md"),
"scripts_files": count_files("scripts/**/*.sh"),
"tests_files": count_files("backend/tests/**/*.py"),
}
# 2. 文档统计
docs_path = PROJECT_ROOT / "docs"
doc_categories = {
"评审报告": len(list((docs_path / "评审报告").glob("*.md"))) if (docs_path / "评审报告").exists() else 0,
"审计报告": len(list((docs_path / "审计报告").glob("*.md"))) if (docs_path / "审计报告").exists() else 0,
"ADRs": len(list((docs_path / "ADRs").glob("*.md"))) if (docs_path / "ADRs").exists() else 0,
"SOPs": len(list((docs_path / "SOPs").glob("*.md"))) if (docs_path / "SOPs").exists() else 0,
"路线图": len(list((docs_path / "路线图").glob("*.md"))) if (docs_path / "路线图").exists() else 0,
}
# 3. 风险统计(解析风险跟踪表)
risk_file = docs_path / "风险跟踪表.md"
risk_stats = {"P0_remaining": 0, "P1": 0, "P2": 0, "P3": 0, "M": 0, "L": 0}
if risk_file.exists():
text = risk_file.read_text(encoding="utf-8")
for level in ["P0", "P1", "P2", "P3", "M", "L"]:
risk_stats[f"{level}_total"] = text.count(f"### {level}-")
risk_stats[f"{level}_remaining"] = text.count(f"### {level}-") - text.count("")
# 4. Git 信息
g = git_info()
# 5. 模板渲染
html = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>企微 IT 智能服务台 - 健康度仪表盘</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}}
.container {{ max-width: 1400px; margin: 0 auto; }}
h1 {{ color: white; margin-bottom: 20px; text-align: center; font-size: 2.2em; }}
.timestamp {{ color: rgba(255,255,255,0.8); text-align: center; margin-bottom: 30px; }}
.grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }}
.card {{
background: white; border-radius: 12px; padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
transition: transform 0.2s;
}}
.card:hover {{ transform: translateY(-2px); }}
.card h2 {{ font-size: 1.1em; color: #555; margin-bottom: 12px; }}
.big-number {{ font-size: 2.4em; font-weight: bold; color: #667eea; }}
.label {{ color: #888; font-size: 0.9em; }}
.stat-row {{
display: flex; justify-content: space-between;
padding: 6px 0; border-bottom: 1px solid #f0f0f0;
}}
.stat-row:last-child {{ border: none; }}
.badge {{
display: inline-block; padding: 4px 10px;
border-radius: 20px; font-size: 0.85em; margin: 2px;
}}
.badge.green {{ background: #d4edda; color: #155724; }}
.badge.yellow {{ background: #fff3cd; color: #856404; }}
.badge.red {{ background: #f8d7da; color: #721c24; }}
.badge.blue {{ background: #d1ecf1; color: #0c5460; }}
.git-info {{
background: #282c34; color: #abb2bf;
padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace;
font-size: 0.9em; line-height: 1.6;
}}
.git-info .hash {{ color: #61afef; }}
</style>
</head>
<body>
<div class="container">
<h1>🚀 企微 IT 智能服务台 - 健康度仪表盘</h1>
<div class="timestamp">生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</div>
<div class="grid">
<!-- 概览 -->
<div class="card">
<h2>📊 代码规模</h2>
<div class="big-number">{stats['backend_python_lines']:,}</div>
<div class="label">后端 Python 代码行</div>
<div style="margin-top: 12px;">
<div class="stat-row"><span>后端 Python 文件</span><strong>{stats['backend_python_files']}</strong></div>
<div class="stat-row"><span>Admin 前端</span><strong>{stats['frontend_admin_files']} 文件</strong></div>
<div class="stat-row"><span>Agent 前端</span><strong>{stats['frontend_agent_files']} 文件</strong></div>
<div class="stat-row"><span>H5 前端</span><strong>{stats['frontend_h5_files']} 文件</strong></div>
<div class="stat-row"><span>Portal 前端</span><strong>{stats['frontend_portal_files']} 文件</strong></div>
</div>
</div>
<!-- 文档统计 -->
<div class="card">
<h2>📚 文档</h2>
<div class="big-number">{stats['docs_files']}</div>
<div class="label">文档总数</div>
<div style="margin-top: 12px;">
{''.join(f'<div class="stat-row"><span>{k}</span><strong>{v}</strong></div>' for k, v in doc_categories.items())}
</div>
</div>
<!-- 风险状态 -->
<div class="card">
<h2>🛡️ 风险状态</h2>
<div class="big-number" style="color: #dc3545;">{risk_stats.get('P0_remaining', 0)}</div>
<div class="label">P0 遗留(需立即修)</div>
<div style="margin-top: 12px;">
<div class="stat-row"><span>P1 中危</span><span class="badge yellow">{risk_stats.get('P1_remaining', 0)} 待修</span></div>
<div class="stat-row"><span>P2 低危</span><span class="badge yellow">{risk_stats.get('P2_remaining', 0)} 待修</span></div>
<div class="stat-row"><span>M 中</span><span class="badge blue">{risk_stats.get('M_remaining', 0)} 待修</span></div>
<div class="stat-row"><span>L 低</span><span class="badge blue">{risk_stats.get('L_remaining', 0)} 待修</span></div>
</div>
</div>
<!-- 脚本与测试 -->
<div class="card">
<h2>🛠️ 工具链</h2>
<div class="big-number">{stats['scripts_files']}</div>
<div class="label">自动化脚本</div>
<div style="margin-top: 12px;">
<div class="stat-row"><span>后端测试</span><strong>{stats['tests_files']} 文件</strong></div>
<div class="stat-row"><span>安全审计</span><span class="badge green">✅ 已配</span></div>
<div class="stat-row"><span>API 文档</span><span class="badge green">✅ 已配</span></div>
<div class="stat-row"><span>备份脚本</span><span class="badge green">✅ 已配</span></div>
<div class="stat-row"><span>Pre-commit</span><span class="badge green">✅ 已配</span></div>
</div>
</div>
<!-- Git 状态 -->
<div class="card" style="grid-column: span 2;">
<h2>📦 Git 状态</h2>
<div class="git-info">
<div>分支: <span class="hash">{g.get('branch', '?')}</span></div>
<div>提交数: <span class="hash">{g.get('commit_count', '?')}</span></div>
<div>最近提交: <span class="hash">{g.get('last_commit', '?')}</span></div>
</div>
</div>
<!-- 模块完成度 -->
<div class="card" style="grid-column: span 3;">
<h2>✅ 阶段完成度</h2>
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-top: 12px;">
<div style="text-align: center;">
<div class="big-number" style="font-size: 1.8em; color: #28a745;">66%</div>
<div class="label">阶段 1</div>
</div>
<div style="text-align: center;">
<div class="big-number" style="font-size: 1.8em; color: #ffc107;">0%</div>
<div class="label">阶段 2(转人工)</div>
</div>
<div style="text-align: center;">
<div class="big-number" style="font-size: 1.8em; color: #6c757d;">0%</div>
<div class="label">阶段 3(H5+WS)</div>
</div>
<div style="text-align: center;">
<div class="big-number" style="font-size: 1.8em; color: #6c757d;">规划中</div>
<div class="label">阶段 4(AI Wingman)</div>
</div>
<div style="text-align: center;">
<div class="big-number" style="font-size: 1.8em; color: #6c757d;">规划中</div>
<div class="label">阶段 5(自动化)</div>
</div>
</div>
</div>
</div>
<div style="text-align: center; color: rgba(255,255,255,0.7); margin-top: 40px; font-size: 0.9em;">
企微 IT 智能服务台 · 健康度仪表盘 v1.0
</div>
</div>
</body>
</html>
"""
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_text(html, encoding="utf-8")
print(f"✅ 仪表盘已生成: {OUTPUT}")
print(f" 打开方式: 直接在浏览器打开 file:///{OUTPUT}")
if __name__ == "__main__":
main()