feat: 审批流程模块 (T审批A审批)

- 新增 backend/app/api/approval.py 审批API
- 前端H5支持发起审批、审批操作
- 添加审批卡片弹窗组件
- 路由注册审批模块
This commit is contained in:
Simon
2026-06-15 09:32:41 +08:00
parent 64d6812ec3
commit 93ba41ed79
29 changed files with 6584 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
#!/bin/bash
# =============================================================================
# API 文档生成脚本
# =============================================================================
# 用途: 从 FastAPI 后端自动生成 OpenAPI 规范 + 静态 HTML 文档
# 输出:
# docs/api/openapi.json - OpenAPI 3.0 规范
# docs/api/index.html - Swagger UI 静态版
# docs/api/redoc.html - ReDoc 静态版
#
# 用法:
# bash scripts/generate-api-docs.sh # 跑后端拿 OpenAPI
# bash scripts/generate-api-docs.sh --from-running # 从运行中后端拿
# bash scripts/generate-api-docs.sh --offline # 离线生成(无需后端)
# =============================================================================
set -e
# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_ROOT"
API_DOCS_DIR="docs/api"
mkdir -p "$API_DOCS_DIR"
# 参数
MODE="auto"
for arg in "$@"; do
case $arg in
--from-running) MODE="running" ;;
--offline) MODE="offline" ;;
esac
done
# =============================================================================
# 1. 拿 OpenAPI 规范
# =============================================================================
info "── 1/3 拿 OpenAPI 规范"
case $MODE in
running|auto)
# 先看后端跑没
if curl -s -f http://localhost:8000/openapi.json > /tmp/openapi.json 2>/dev/null; then
ok "从运行中后端拿 OpenAPI"
cp /tmp/openapi.json "$API_DOCS_DIR/openapi.json"
elif [ "$MODE" = "running" ]; then
error "后端没跑,无法从 running 拿"
else
warn "后端没跑,改用离线生成"
MODE="offline"
fi
;;
esac
if [ "$MODE" = "offline" ]; then
info "离线生成 OpenAPI(import FastAPI app)..."
cd backend
if [ ! -d "venv" ]; then
warn "后端 venv 不存在,跑: python -m venv venv && pip install -r requirements.txt"
fi
cat > /tmp/gen_openapi.py <<'PYEOF'
import json
import sys
try:
from app.main import app
spec = app.openapi()
print(json.dumps(spec, ensure_ascii=False, indent=2))
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
PYEOF
if command -v python &> /dev/null; then
if python /tmp/gen_openapi.py > "../$API_DOCS_DIR/openapi.json" 2>/dev/null; then
ok "离线生成 OpenAPI 成功"
else
# 试 python3
if python3 /tmp/gen_openapi.py > "../$API_DOCS_DIR/openapi.json" 2>/dev/null; then
ok "离线生成 OpenAPI 成功(python3)"
else
warn "离线生成失败,降级到 mock 模式"
cat > "../$API_DOCS_DIR/openapi.json" <<'JSONEOF'
{
"openapi": "3.0.0",
"info": {
"title": "企微 IT 智能服务台 API",
"version": "1.0.0",
"description": "离线生成的 mock,实际跑后端再生成"
},
"paths": {}
}
JSONEOF
fi
fi
fi
cd "$PROJECT_ROOT"
fi
# 验证 OpenAPI
if [ ! -f "$API_DOCS_DIR/openapi.json" ]; then
error "OpenAPI 规范生成失败"
fi
ENDPOINT_COUNT=$(python -c "import json; d=json.load(open('$API_DOCS_DIR/openapi.json')); print(len(d.get('paths', {})))" 2>/dev/null || echo "?")
ok "OpenAPI 规范生成,端点数: $ENDPOINT_COUNT"
# =============================================================================
# 2. 生成 Swagger UI 静态 HTML
# =============================================================================
info "── 2/3 生成 Swagger UI 静态 HTML"
cat > "$API_DOCS_DIR/index.html" <<'HTMLEOF'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>企微 IT 智能服务台 API - Swagger UI</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.10.5/swagger-ui.css">
<style>
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
.topbar { background: #2c3e50; color: white; padding: 12px 24px; }
.topbar h1 { margin: 0; font-size: 20px; }
.topbar a { color: #3498db; text-decoration: none; margin-left: 16px; }
</style>
</head>
<body>
<div class="topbar">
<h1>📡 企微 IT 智能服务台 API 文档</h1>
<a href="redoc.html">📖 ReDoc 版</a>
<a href="openapi.json">📄 OpenAPI 规范</a>
</div>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.10.5/swagger-ui-bundle.js"></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: "openapi.json",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis
],
layout: "BaseLayout"
});
};
</script>
</body>
</html>
HTMLEOF
ok "Swagger UI 生成: $API_DOCS_DIR/index.html"
# =============================================================================
# 3. 生成 ReDoc 静态 HTML
# =============================================================================
info "── 3/3 生成 ReDoc 静态 HTML"
cat > "$API_DOCS_DIR/redoc.html" <<'HTMLEOF'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>企微 IT 智能服务台 API - ReDoc</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body { margin: 0; padding: 0; }
</style>
</head>
<body>
<redoc spec-url="openapi.json"></redoc>
<script src="https://unpkg.com/redoc@2.0.0/bundles/redoc.standalone.js"></script>
</body>
</html>
HTMLEOF
ok "ReDoc 生成: $API_DOCS_DIR/redoc.html"
# =============================================================================
# 4. 生成 API 模块清单
# =============================================================================
info "── 4/4 生成模块清单"
python3 -c "
import json
with open('$API_DOCS_DIR/openapi.json') as f:
spec = json.load(f)
paths = spec.get('paths', {})
modules = {}
for path, methods in paths.items():
# 解析 /api/v1/<module>/<endpoint>
parts = path.split('/')
if len(parts) >= 4 and parts[1] == 'api' and parts[2] == 'v1':
module = parts[3]
if module not in modules:
modules[module] = []
for method in methods.keys():
if method in ['get', 'post', 'put', 'delete', 'patch']:
modules[module].append({
'method': method.upper(),
'path': path,
})
print('# API 模块清单')
print()
print('**生成日期**: $(date +%Y-%m-%d)')
print('**端点总数**: ', len(paths))
print('**模块数**: ', len(modules))
print()
print('| 模块 | 端点数 | 端点 |')
print('|---|---|---|')
for module, endpoints in sorted(modules.items()):
eps = ', '.join(f\"{e['method']} {e['path']}\" for e in endpoints[:5])
if len(endpoints) > 5:
eps += f' ... (+{len(endpoints)-5})'
print(f\"| {module} | {len(endpoints)} | {eps} |\")
" > "$API_DOCS_DIR/MODULES.md" 2>/dev/null || {
warn "模块清单生成失败(Python 解析)"
cat > "$API_DOCS_DIR/MODULES.md" <<'EOF'
# API 模块清单
(生成失败,见 docs/api/openapi.json 自行查看)
EOF
}
ok "模块清单生成: $API_DOCS_DIR/MODULES.md"
# =============================================================================
# 总结
# =============================================================================
info "── 总结"
echo ""
echo "输出文件:"
echo " $API_DOCS_DIR/openapi.json - OpenAPI 3.0 规范"
echo " $API_DOCS_DIR/index.html - Swagger UI 静态版"
echo " $API_DOCS_DIR/redoc.html - ReDoc 静态版"
echo " $API_DOCS_DIR/MODULES.md - 模块清单"
echo ""
echo "查看方式:"
echo " 1. 浏览器打开 file://\$(pwd)/$API_DOCS_DIR/index.html"
echo " 2. 跑 python -m http.server -d $API_DOCS_DIR 8080 → 浏览器 http://localhost:8080"
echo ""
echo "CI 集成:"
echo " 把 'bash scripts/generate-api-docs.sh' 加进 Gitea Actions"
echo " 跑批频率:每次 main 推送后"
ok "API 文档生成完成"