feat: 修复 H5 底部导航覆盖 + 更新项目进度文档
## H5 底部导航修复 (Bug #10) - 精简 App.vue,移除重复 tabbar,仅保留全局样式 - uni-page 设置 height: calc(100% - 50px) + overflow-y: auto - 内容区域精确停在底部导航上方,独立滚动不再叠加 - 恢复 custom-tab-bar 组件 ## 项目进度文档 - PROGRESS.md 更新至 10 个 Bug 修复 - 新增 H5 底部导航修复记录 - 新增历史变更条目
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
from typing import Dict, Any, Optional, List
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_, or_
|
||||
from app.models.team import Team, TeamMember
|
||||
from app.models.user import User
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TeamService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def create_team(self, owner_id: str, name: str, description: str = None) -> Dict[str, Any]:
|
||||
existing = await self.db.execute(
|
||||
select(Team).where(and_(Team.owner_id == owner_id, Team.is_active == True))
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
raise ValueError("You already own an active team")
|
||||
|
||||
user_result = await self.db.execute(select(User).where(User.id == owner_id))
|
||||
user = user_result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise ValueError("User not found")
|
||||
|
||||
max_members = 20 if user.tier == "enterprise" else (10 if user.tier == "pro" else 5)
|
||||
|
||||
team = Team(
|
||||
name=name,
|
||||
owner_id=owner_id,
|
||||
description=description,
|
||||
max_members=max_members,
|
||||
tier=user.tier,
|
||||
)
|
||||
self.db.add(team)
|
||||
await self.db.flush()
|
||||
|
||||
member = TeamMember(
|
||||
team_id=team.id,
|
||||
user_id=owner_id,
|
||||
role="owner",
|
||||
status="active",
|
||||
)
|
||||
self.db.add(member)
|
||||
await self.db.flush()
|
||||
|
||||
return await self._to_dict(team, include_members=True)
|
||||
|
||||
async def get_team(self, team_id: str, user_id: str) -> Optional[Dict[str, Any]]:
|
||||
result = await self.db.execute(
|
||||
select(Team).where(Team.id == team_id)
|
||||
)
|
||||
team = result.scalar_one_or_none()
|
||||
if not team:
|
||||
return None
|
||||
|
||||
is_member = await self.db.execute(
|
||||
select(TeamMember).where(
|
||||
and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id)
|
||||
)
|
||||
)
|
||||
if not is_member.scalar_one_or_none():
|
||||
return None
|
||||
|
||||
return await self._to_dict(team, include_members=True)
|
||||
|
||||
async def list_user_teams(self, user_id: str) -> List[Dict[str, Any]]:
|
||||
member_result = await self.db.execute(
|
||||
select(TeamMember.team_id).where(TeamMember.user_id == user_id)
|
||||
)
|
||||
team_ids = [r[0] for r in member_result.all()]
|
||||
|
||||
if not team_ids:
|
||||
return []
|
||||
|
||||
result = await self.db.execute(
|
||||
select(Team).where(Team.id.in_(team_ids), Team.is_active == True)
|
||||
)
|
||||
teams = result.scalars().all()
|
||||
|
||||
return [await self._to_dict(t) for t in teams]
|
||||
|
||||
async def invite_member(self, team_id: str, owner_id: str, user_id: str) -> Dict[str, Any]:
|
||||
team_result = await self.db.execute(
|
||||
select(Team).where(and_(Team.id == team_id, Team.owner_id == owner_id))
|
||||
)
|
||||
team = team_result.scalar_one_or_none()
|
||||
if not team:
|
||||
raise ValueError("Team not found or not authorized")
|
||||
|
||||
member_count = await self.db.execute(
|
||||
select(func.count(TeamMember.id)).where(
|
||||
and_(TeamMember.team_id == team_id, TeamMember.status == "active")
|
||||
)
|
||||
)
|
||||
if (member_count.scalar() or 0) >= team.max_members:
|
||||
raise ValueError("Team member limit reached")
|
||||
|
||||
existing = await self.db.execute(
|
||||
select(TeamMember).where(
|
||||
and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id)
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
raise ValueError("User is already a member")
|
||||
|
||||
member = TeamMember(
|
||||
team_id=team_id,
|
||||
user_id=user_id,
|
||||
role="member",
|
||||
invited_by=owner_id,
|
||||
status="active",
|
||||
)
|
||||
self.db.add(member)
|
||||
await self.db.flush()
|
||||
|
||||
return {"user_id": user_id, "role": "member", "status": "active"}
|
||||
|
||||
async def remove_member(self, team_id: str, owner_id: str, user_id: str) -> bool:
|
||||
team_result = await self.db.execute(
|
||||
select(Team).where(and_(Team.id == team_id, Team.owner_id == owner_id))
|
||||
)
|
||||
team = team_result.scalar_one_or_none()
|
||||
if not team:
|
||||
return False
|
||||
|
||||
result = await self.db.execute(
|
||||
select(TeamMember).where(
|
||||
and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id)
|
||||
)
|
||||
)
|
||||
member = result.scalar_one_or_none()
|
||||
if not member or member.role == "owner":
|
||||
return False
|
||||
|
||||
await self.db.delete(member)
|
||||
return True
|
||||
|
||||
async def leave_team(self, team_id: str, user_id: str) -> bool:
|
||||
result = await self.db.execute(
|
||||
select(TeamMember).where(
|
||||
and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id)
|
||||
)
|
||||
)
|
||||
member = result.scalar_one_or_none()
|
||||
if not member or member.role == "owner":
|
||||
return False
|
||||
|
||||
await self.db.delete(member)
|
||||
return True
|
||||
|
||||
async def update_role(self, team_id: str, owner_id: str, user_id: str, role: str) -> bool:
|
||||
team_result = await self.db.execute(
|
||||
select(Team).where(and_(Team.id == team_id, Team.owner_id == owner_id))
|
||||
)
|
||||
if not team_result.scalar_one_or_none():
|
||||
return False
|
||||
|
||||
result = await self.db.execute(
|
||||
select(TeamMember).where(
|
||||
and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id)
|
||||
)
|
||||
)
|
||||
member = result.scalar_one_or_none()
|
||||
if not member or member.role == "owner":
|
||||
return False
|
||||
|
||||
member.role = role
|
||||
await self.db.flush()
|
||||
return True
|
||||
|
||||
async def _to_dict(self, team: Team, include_members: bool = False) -> Dict[str, Any]:
|
||||
result = {
|
||||
"id": str(team.id),
|
||||
"name": team.name,
|
||||
"owner_id": str(team.owner_id),
|
||||
"description": team.description,
|
||||
"tier": team.tier,
|
||||
"is_active": team.is_active,
|
||||
"created_at": team.created_at.isoformat() if team.created_at else None,
|
||||
}
|
||||
|
||||
if include_members:
|
||||
members_result = await self.db.execute(
|
||||
select(TeamMember).where(TeamMember.team_id == team.id)
|
||||
)
|
||||
members = members_result.scalars().all()
|
||||
result["members"] = [
|
||||
{
|
||||
"user_id": str(m.user_id),
|
||||
"role": m.role,
|
||||
"status": m.status,
|
||||
"joined_at": m.joined_at.isoformat() if m.joined_at else None,
|
||||
}
|
||||
for m in members
|
||||
]
|
||||
result["member_count"] = len([m for m in members if m.status == "active"])
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user