diff --git a/video-gen-admin/src/api/index.ts b/video-gen-admin/src/api/index.ts index fea31823..c32acd9f 100644 --- a/video-gen-admin/src/api/index.ts +++ b/video-gen-admin/src/api/index.ts @@ -448,7 +448,7 @@ export async function deleteMenuConfig(id: string): Promise { // ── User Creation ─────────────────────────────────────── -export async function createUser(data: { username?: string; password: string; email?: string; phone?: string; credits: number; user_type: string; frontend_user_kind?: string; allowed_menus?: string[] | null; private_portrait_asset_limit?: number }): Promise { +export async function createUser(data: { username?: string; password: string; email?: string; phone?: string; credits: number; user_type: string; is_admin?: boolean; frontend_user_kind?: string; allowed_menus?: string[] | null; private_portrait_asset_limit?: number }): Promise { return api.post('/admin/users', data); } diff --git a/video-gen-admin/src/pages/AdminUsers.tsx b/video-gen-admin/src/pages/AdminUsers.tsx index 29d4e20e..4607d547 100644 --- a/video-gen-admin/src/pages/AdminUsers.tsx +++ b/video-gen-admin/src/pages/AdminUsers.tsx @@ -188,6 +188,7 @@ const AdminUsers: React.FC = () => { phone: userType === 'frontend' ? values.phone : (values.phone || undefined), credits: values.credits || 0, user_type: userType, + is_admin: userType === 'admin' ? (values.is_admin ?? false) : false, frontend_user_kind: values.frontend_user_kind || 'external', private_portrait_asset_limit: userType === 'frontend' ? Number(values.private_portrait_asset_limit ?? 5) : 0, }); @@ -866,6 +867,14 @@ const AdminUsers: React.FC = () => { )} + {createType === 'admin' && ( + + + + )} diff --git a/video-gen-api/app/api/v1/admin.py b/video-gen-api/app/api/v1/admin.py index beac3cd3..67e71976 100644 --- a/video-gen-api/app/api/v1/admin.py +++ b/video-gen-api/app/api/v1/admin.py @@ -170,7 +170,7 @@ async def create_user( email=req.email, phone=req.phone, credits=req.credits, - is_admin=(req.user_type == "admin"), + is_admin=req.is_admin if req.user_type == "admin" else False, user_type=req.user_type, frontend_user_kind=req.frontend_user_kind if req.user_type == "frontend" else FrontendUserKind.EXTERNAL.value, allowed_menus=req.allowed_menus, diff --git a/video-gen-api/app/api/v1/menu_configs.py b/video-gen-api/app/api/v1/menu_configs.py index ed56ce80..0880989a 100644 --- a/video-gen-api/app/api/v1/menu_configs.py +++ b/video-gen-api/app/api/v1/menu_configs.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from app.dependencies import get_db, get_admin_user, get_current_user +from app.dependencies import get_db, get_admin_user, get_backend_user, get_current_user from app.models.user import User from app.models.menu_config import MenuConfig from app.schemas.menu import MenuConfigCreate, MenuConfigOut @@ -61,7 +61,7 @@ async def public_list_menu_configs( @router.get("/admin/menu-configs", response_model=list[MenuConfigOut]) async def admin_list_menu_configs( - admin: User = Depends(get_admin_user), + admin: User = Depends(get_backend_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(MenuConfig).order_by(MenuConfig.sort_order)) diff --git a/video-gen-api/app/dependencies.py b/video-gen-api/app/dependencies.py index 2d5dd693..38de6d74 100644 --- a/video-gen-api/app/dependencies.py +++ b/video-gen-api/app/dependencies.py @@ -104,3 +104,14 @@ async def get_admin_user( detail="需要管理员权限", ) return current_user + + +async def get_backend_user( + current_user: User = Depends(get_current_user_allow_password_pending), +) -> User: + if current_user.user_type != "admin": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="需要后台用户权限", + ) + return current_user diff --git a/video-gen-api/app/schemas/admin.py b/video-gen-api/app/schemas/admin.py index fc4b0854..429864b9 100644 --- a/video-gen-api/app/schemas/admin.py +++ b/video-gen-api/app/schemas/admin.py @@ -70,6 +70,7 @@ class CreateUserRequest(BaseModel): phone: str | None = None credits: float = 0.0 user_type: str = Field(default="frontend", pattern="^(frontend|admin)$") + is_admin: bool = False frontend_user_kind: str = Field(default="external", pattern="^(internal|external)$") allowed_menus: list | None = None private_portrait_asset_limit: int = Field(default=5, ge=0, le=9999, description="私域人像素材总量限制,真人/虚拟、图片/视频共用,0 表示关闭") diff --git a/video-gen-api/app/schemas/generation_ai.py b/video-gen-api/app/schemas/generation_ai.py index 321a7a98..e87e177c 100644 --- a/video-gen-api/app/schemas/generation_ai.py +++ b/video-gen-api/app/schemas/generation_ai.py @@ -71,6 +71,11 @@ class GenerationAIReference(BaseModel): description="前端预览用素材地址;通常与 display_url 一致", examples=["/uploads/images/2026/07/06/demo.jpg"], ) + role: str | None = Field( + None, + description="参考素材角色。image 可选 first_frame/last_frame/reference_image;video 可选 reference_video;audio 可选 reference_audio", + examples=["first_frame"], + ) class GenerationAITaskCreate(BaseModel): diff --git a/video-gen-app/src/pages/JoinTeamPage.tsx b/video-gen-app/src/pages/JoinTeamPage.tsx index 7c4f06fe..86d67fe4 100644 --- a/video-gen-app/src/pages/JoinTeamPage.tsx +++ b/video-gen-app/src/pages/JoinTeamPage.tsx @@ -3,10 +3,10 @@ import { Button, Card, Result, Spin, Typography, Modal, message, Space, } from 'antd'; import { - CheckCircleOutlined, TeamOutlined, LoginOutlined, UserAddOutlined, + CheckCircleOutlined, TeamOutlined, LoginOutlined, UserAddOutlined, ThunderboltOutlined, } from '@ant-design/icons'; import { useNavigate, useSearchParams } from 'react-router-dom'; -import { getJoinTeamInfo, getJoinTeamInfoPublic, submitJoinRequest } from '../api'; +import { getJoinTeamInfo, getJoinTeamInfoPublic, submitJoinRequest, getSiteInfo } from '../api'; import type { JoinTeamInfo } from '../types'; import { useAuthStore } from '../store/useAuthStore'; @@ -21,9 +21,15 @@ const JoinTeamPage: React.FC = () => { const [submitting, setSubmitting] = useState(false); const [submitted, setSubmitted] = useState(false); const [confirmModalOpen, setConfirmModalOpen] = useState(false); + const [siteName, setSiteName] = useState('VideoGen.AI'); + const [siteLogo, setSiteLogo] = useState(''); useEffect(() => { checkAuth(); + getSiteInfo().then(info => { + setSiteName(info.siteName); + setSiteLogo(info.siteLogo); + }).catch(() => {}); }, [checkAuth]); useEffect(() => { @@ -146,22 +152,39 @@ const JoinTeamPage: React.FC = () => { return ( <> -
- - - 加入团队 - - 您被邀请加入团队 - - - 「{info.teamName}」 - - - 加入后团队管理人可以为您分配积分、查看您的积分使用情况。 - +
+
+ {/* 网站名称 */} +
+ {siteLogo ? ( + logo + ) : ( +
+ +
+ )} + {siteName} +
+ + + + 加入团队 + + 您被邀请加入团队 + + + 「{info.teamName}」 + + + 加入后团队管理人可以为您分配积分、查看您的积分使用情况。 + {user ? (
{ title: '操作', key: 'action', width: 160, render: (_: any, r: TeamJoinRequest) => ( - - +