7b62c2f8b4
## H5 底部导航修复 (Bug #10) - 精简 App.vue,移除重复 tabbar,仅保留全局样式 - uni-page 设置 height: calc(100% - 50px) + overflow-y: auto - 内容区域精确停在底部导航上方,独立滚动不再叠加 - 恢复 custom-tab-bar 组件 ## 项目进度文档 - PROGRESS.md 更新至 10 个 Bug 修复 - 新增 H5 底部导航修复记录 - 新增历史变更条目
130 lines
4.4 KiB
Python
130 lines
4.4 KiB
Python
from typing import Dict, Any, List
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func, and_
|
|
from app.models.user import User
|
|
from app.models.team import Team, TeamMember
|
|
from app.models.analytics import UsageLog
|
|
from app.models.customer import Customer
|
|
from app.models.quotation import Quotation
|
|
from datetime import datetime, timedelta
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AdminService:
|
|
def __init__(self, db: AsyncSession):
|
|
self.db = db
|
|
|
|
async def get_dashboard(self) -> Dict[str, Any]:
|
|
now = datetime.utcnow()
|
|
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
user_count = await self.db.execute(select(func.count(User.id)))
|
|
team_count = await self.db.execute(select(func.count(Team.id)))
|
|
customer_count = await self.db.execute(select(func.count(Customer.id)))
|
|
quotation_count = await self.db.execute(select(func.count(Quotation.id)))
|
|
|
|
today_logs = await self.db.execute(
|
|
select(func.count(UsageLog.id)).where(UsageLog.created_at >= today_start)
|
|
)
|
|
total_logs = await self.db.execute(select(func.count(UsageLog.id)))
|
|
|
|
recent_users_result = await self.db.execute(
|
|
select(User).order_by(User.created_at.desc()).limit(5)
|
|
)
|
|
recent_users = recent_users_result.scalars().all()
|
|
|
|
return {
|
|
"users": {
|
|
"total": user_count.scalar() or 0,
|
|
},
|
|
"teams": {
|
|
"total": team_count.scalar() or 0,
|
|
},
|
|
"customers": {
|
|
"total": customer_count.scalar() or 0,
|
|
},
|
|
"quotations": {
|
|
"total": quotation_count.scalar() or 0,
|
|
},
|
|
"usage": {
|
|
"today": today_logs.scalar() or 0,
|
|
"total": total_logs.scalar() or 0,
|
|
},
|
|
"recent_users": [
|
|
{
|
|
"id": str(u.id),
|
|
"username": u.username,
|
|
"tier": u.tier,
|
|
"is_active": u.is_active,
|
|
"created_at": u.created_at.isoformat() if u.created_at else None,
|
|
}
|
|
for u in recent_users
|
|
],
|
|
}
|
|
|
|
async def list_users(self, page: int = 1, size: int = 20) -> Dict[str, Any]:
|
|
query = select(User).order_by(User.created_at.desc()).offset((page - 1) * size).limit(size)
|
|
count_query = select(func.count(User.id))
|
|
|
|
total = await self.db.execute(count_query)
|
|
result = await self.db.execute(query)
|
|
users = result.scalars().all()
|
|
|
|
return {
|
|
"items": [
|
|
{
|
|
"id": str(u.id),
|
|
"username": u.username,
|
|
"phone": u.phone,
|
|
"tier": u.tier,
|
|
"is_active": u.is_active,
|
|
"created_at": u.created_at.isoformat() if u.created_at else None,
|
|
}
|
|
for u in users
|
|
],
|
|
"total": total.scalar(),
|
|
"page": page,
|
|
"size": size,
|
|
}
|
|
|
|
async def update_user_tier(self, user_id: str, tier: str) -> bool:
|
|
result = await self.db.execute(select(User).where(User.id == user_id))
|
|
user = result.scalar_one_or_none()
|
|
if not user:
|
|
return False
|
|
user.tier = tier
|
|
await self.db.flush()
|
|
return True
|
|
|
|
async def toggle_user_active(self, user_id: str) -> bool:
|
|
result = await self.db.execute(select(User).where(User.id == user_id))
|
|
user = result.scalar_one_or_none()
|
|
if not user:
|
|
return False
|
|
user.is_active = not user.is_active
|
|
await self.db.flush()
|
|
return True
|
|
|
|
async def get_system_health(self) -> Dict[str, Any]:
|
|
return {
|
|
"status": "healthy",
|
|
"version": "1.0.0",
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
}
|
|
|
|
async def log_usage(self, user_id: str, action: str, detail: Dict = None, ip: str = None, ua: str = None):
|
|
try:
|
|
log = UsageLog(
|
|
user_id=user_id,
|
|
action=action,
|
|
detail=detail or {},
|
|
ip_address=ip,
|
|
user_agent=ua,
|
|
)
|
|
self.db.add(log)
|
|
await self.db.flush()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to log usage: {e}")
|