- 项目详情页三阶段里程碑管理(前期/制作/后期) - 制作卡片改用180px ECharts圆环进度图+右侧数据列表 - 修复损耗率双重计算bug(测试秒数不再重复计入超产) - 新增飞书推送服务、豆包AI风险分析、APScheduler定时报告 - 项目列表页增强(筛选/排序/批量操作/废弃功能) - 成员详情页产出时间轴+效率对比 - 成本页固定开支管理 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
152 lines
5.2 KiB
Python
152 lines
5.2 KiB
Python
"""AirLabs Project —— 主入口"""
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
from database import engine, Base
|
|
from models import (
|
|
User, Role, PhaseGroup, BUILTIN_ROLES, COST_PERM_MIGRATION,
|
|
Project, ProjectMilestone, DEFAULT_MILESTONES
|
|
)
|
|
from auth import hash_password
|
|
import os
|
|
import logging
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 创建所有表
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
app = FastAPI(title="AirLabs Project", version="1.0.0")
|
|
|
|
# CORS
|
|
from config import CORS_ORIGINS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# 注册路由
|
|
from routers.auth import router as auth_router
|
|
from routers.users import router as users_router
|
|
from routers.projects import router as projects_router
|
|
from routers.submissions import router as submissions_router
|
|
from routers.costs import router as costs_router
|
|
from routers.dashboard import router as dashboard_router
|
|
from routers.roles import router as roles_router
|
|
from routers.reports import router as reports_router
|
|
|
|
app.include_router(auth_router)
|
|
app.include_router(users_router)
|
|
app.include_router(projects_router)
|
|
app.include_router(submissions_router)
|
|
app.include_router(costs_router)
|
|
app.include_router(dashboard_router)
|
|
app.include_router(roles_router)
|
|
app.include_router(reports_router)
|
|
|
|
# 前端静态文件
|
|
frontend_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "frontend", "dist")
|
|
if os.path.exists(frontend_dir):
|
|
app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets")
|
|
|
|
@app.get("/{full_path:path}")
|
|
async def serve_frontend(full_path: str):
|
|
file_path = os.path.join(frontend_dir, full_path)
|
|
if os.path.isfile(file_path):
|
|
return FileResponse(file_path)
|
|
return FileResponse(os.path.join(frontend_dir, "index.html"))
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def start_scheduler():
|
|
"""启动定时任务调度器"""
|
|
from services.scheduler_service import setup_scheduler
|
|
setup_scheduler()
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def stop_scheduler():
|
|
"""关闭定时任务调度器"""
|
|
from services.scheduler_service import scheduler
|
|
scheduler.shutdown(wait=False)
|
|
logger.info("[定时任务] 已关闭")
|
|
|
|
|
|
@app.on_event("startup")
|
|
def init_roles_and_admin():
|
|
"""首次启动时创建内置角色和默认管理员"""
|
|
from database import SessionLocal
|
|
db = SessionLocal()
|
|
try:
|
|
# 初始化内置角色
|
|
for role_name, role_def in BUILTIN_ROLES.items():
|
|
existing = db.query(Role).filter(Role.name == role_name).first()
|
|
if not existing:
|
|
role = Role(
|
|
name=role_name,
|
|
description=role_def["description"],
|
|
permissions=role_def["permissions"],
|
|
is_system=1,
|
|
)
|
|
db.add(role)
|
|
print(f"[OK] created role: {role_name}")
|
|
db.commit()
|
|
|
|
# 迁移旧成本权限 → 细分权限
|
|
old_cost_perms = set(COST_PERM_MIGRATION.keys())
|
|
for role in db.query(Role).all():
|
|
perms = list(role.permissions or [])
|
|
changed = False
|
|
for old_perm, new_perms in COST_PERM_MIGRATION.items():
|
|
if old_perm in perms:
|
|
perms.remove(old_perm)
|
|
for np in new_perms:
|
|
if np not in perms:
|
|
perms.append(np)
|
|
changed = True
|
|
if changed:
|
|
role.permissions = perms
|
|
print(f"[MIGRATE] upgraded cost permissions for role: {role.name}")
|
|
db.commit()
|
|
|
|
# 为已有项目补充默认里程碑
|
|
for proj in db.query(Project).all():
|
|
has_ms = db.query(ProjectMilestone).filter(
|
|
ProjectMilestone.project_id == proj.id
|
|
).first()
|
|
if not has_ms:
|
|
for ms in DEFAULT_MILESTONES:
|
|
db.add(ProjectMilestone(
|
|
project_id=proj.id,
|
|
name=ms["name"],
|
|
phase=PhaseGroup(ms["phase"]),
|
|
sort_order=ms.get("sort_order", 0),
|
|
))
|
|
print(f"[MIGRATE] added default milestones for project: {proj.name}")
|
|
db.commit()
|
|
|
|
# 创建默认管理员(关联超级管理员角色)
|
|
admin_role = db.query(Role).filter(Role.name == "超级管理员").first()
|
|
if admin_role and not db.query(User).filter(User.username == "admin").first():
|
|
owner = User(
|
|
username="admin",
|
|
password_hash=hash_password("admin123"),
|
|
name="管理员",
|
|
phase_group=PhaseGroup.PRODUCTION,
|
|
role_id=admin_role.id,
|
|
monthly_salary=0,
|
|
)
|
|
db.add(owner)
|
|
db.commit()
|
|
print("[OK] default admin created: admin / admin123")
|
|
finally:
|
|
db.close()
|