1、前台/team-management加入申请的通过失败按钮增加一下图标,区分一下
2、前台/join-team页面上边要增加网站名称,要不然用户看不到是什么网站 3、后台/users的后台用户我新增一个,然后设置菜单权限,实际登陆没有生效
This commit is contained in:
@@ -448,7 +448,7 @@ export async function deleteMenuConfig(id: string): Promise<void> {
|
|||||||
|
|
||||||
// ── User Creation ───────────────────────────────────────
|
// ── 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<any> {
|
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<any> {
|
||||||
return api.post('/admin/users', data);
|
return api.post('/admin/users', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ const AdminUsers: React.FC = () => {
|
|||||||
phone: userType === 'frontend' ? values.phone : (values.phone || undefined),
|
phone: userType === 'frontend' ? values.phone : (values.phone || undefined),
|
||||||
credits: values.credits || 0,
|
credits: values.credits || 0,
|
||||||
user_type: userType,
|
user_type: userType,
|
||||||
|
is_admin: userType === 'admin' ? (values.is_admin ?? false) : false,
|
||||||
frontend_user_kind: values.frontend_user_kind || 'external',
|
frontend_user_kind: values.frontend_user_kind || 'external',
|
||||||
private_portrait_asset_limit: userType === 'frontend' ? Number(values.private_portrait_asset_limit ?? 5) : 0,
|
private_portrait_asset_limit: userType === 'frontend' ? Number(values.private_portrait_asset_limit ?? 5) : 0,
|
||||||
});
|
});
|
||||||
@@ -866,6 +867,14 @@ const AdminUsers: React.FC = () => {
|
|||||||
<InputNumber min={0} max={9999} precision={0} style={{ width: '100%' }} size="large" />
|
<InputNumber min={0} max={9999} precision={0} style={{ width: '100%' }} size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
{createType === 'admin' && (
|
||||||
|
<Form.Item name="is_admin" label="超级管理员" valuePropName="checked" initialValue={false}>
|
||||||
|
<Switch
|
||||||
|
checkedChildren="是"
|
||||||
|
unCheckedChildren="否"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
<Form.Item name="email" label="邮箱">
|
<Form.Item name="email" label="邮箱">
|
||||||
<Input placeholder="选填" size="large" />
|
<Input placeholder="选填" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ async def create_user(
|
|||||||
email=req.email,
|
email=req.email,
|
||||||
phone=req.phone,
|
phone=req.phone,
|
||||||
credits=req.credits,
|
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,
|
user_type=req.user_type,
|
||||||
frontend_user_kind=req.frontend_user_kind if req.user_type == "frontend" else FrontendUserKind.EXTERNAL.value,
|
frontend_user_kind=req.frontend_user_kind if req.user_type == "frontend" else FrontendUserKind.EXTERNAL.value,
|
||||||
allowed_menus=req.allowed_menus,
|
allowed_menus=req.allowed_menus,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
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.user import User
|
||||||
from app.models.menu_config import MenuConfig
|
from app.models.menu_config import MenuConfig
|
||||||
from app.schemas.menu import MenuConfigCreate, MenuConfigOut
|
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])
|
@router.get("/admin/menu-configs", response_model=list[MenuConfigOut])
|
||||||
async def admin_list_menu_configs(
|
async def admin_list_menu_configs(
|
||||||
admin: User = Depends(get_admin_user),
|
admin: User = Depends(get_backend_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
result = await db.execute(select(MenuConfig).order_by(MenuConfig.sort_order))
|
result = await db.execute(select(MenuConfig).order_by(MenuConfig.sort_order))
|
||||||
|
|||||||
@@ -104,3 +104,14 @@ async def get_admin_user(
|
|||||||
detail="需要管理员权限",
|
detail="需要管理员权限",
|
||||||
)
|
)
|
||||||
return current_user
|
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
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class CreateUserRequest(BaseModel):
|
|||||||
phone: str | None = None
|
phone: str | None = None
|
||||||
credits: float = 0.0
|
credits: float = 0.0
|
||||||
user_type: str = Field(default="frontend", pattern="^(frontend|admin)$")
|
user_type: str = Field(default="frontend", pattern="^(frontend|admin)$")
|
||||||
|
is_admin: bool = False
|
||||||
frontend_user_kind: str = Field(default="external", pattern="^(internal|external)$")
|
frontend_user_kind: str = Field(default="external", pattern="^(internal|external)$")
|
||||||
allowed_menus: list | None = None
|
allowed_menus: list | None = None
|
||||||
private_portrait_asset_limit: int = Field(default=5, ge=0, le=9999, description="私域人像素材总量限制,真人/虚拟、图片/视频共用,0 表示关闭")
|
private_portrait_asset_limit: int = Field(default=5, ge=0, le=9999, description="私域人像素材总量限制,真人/虚拟、图片/视频共用,0 表示关闭")
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ class GenerationAIReference(BaseModel):
|
|||||||
description="前端预览用素材地址;通常与 display_url 一致",
|
description="前端预览用素材地址;通常与 display_url 一致",
|
||||||
examples=["/uploads/images/2026/07/06/demo.jpg"],
|
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):
|
class GenerationAITaskCreate(BaseModel):
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import {
|
|||||||
Button, Card, Result, Spin, Typography, Modal, message, Space,
|
Button, Card, Result, Spin, Typography, Modal, message, Space,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
CheckCircleOutlined, TeamOutlined, LoginOutlined, UserAddOutlined,
|
CheckCircleOutlined, TeamOutlined, LoginOutlined, UserAddOutlined, ThunderboltOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
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 type { JoinTeamInfo } from '../types';
|
||||||
import { useAuthStore } from '../store/useAuthStore';
|
import { useAuthStore } from '../store/useAuthStore';
|
||||||
|
|
||||||
@@ -21,9 +21,15 @@ const JoinTeamPage: React.FC = () => {
|
|||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [submitted, setSubmitted] = useState(false);
|
const [submitted, setSubmitted] = useState(false);
|
||||||
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
|
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
|
||||||
|
const [siteName, setSiteName] = useState('VideoGen.AI');
|
||||||
|
const [siteLogo, setSiteLogo] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkAuth();
|
checkAuth();
|
||||||
|
getSiteInfo().then(info => {
|
||||||
|
setSiteName(info.siteName);
|
||||||
|
setSiteLogo(info.siteLogo);
|
||||||
|
}).catch(() => {});
|
||||||
}, [checkAuth]);
|
}, [checkAuth]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -146,22 +152,39 @@ const JoinTeamPage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', padding: 40 }}>
|
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px 20px' }}>
|
||||||
<Card
|
<div style={{ width: '100%', maxWidth: 480 }}>
|
||||||
variant="outlined"
|
{/* 网站名称 */}
|
||||||
style={{ borderRadius: 16, maxWidth: 480, width: '100%', textAlign: 'center' }}
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 12, marginBottom: 32 }}>
|
||||||
>
|
{siteLogo ? (
|
||||||
<TeamOutlined style={{ fontSize: 48, color: '#6366f1', marginBottom: 16 }} />
|
<img src={siteLogo} alt="logo" style={{ width: 40, height: 40, borderRadius: 10 }} />
|
||||||
<Typography.Title level={3}>加入团队</Typography.Title>
|
) : (
|
||||||
<Typography.Text style={{ fontSize: 16, color: '#475569', display: 'block', marginBottom: 8 }}>
|
<div style={{
|
||||||
您被邀请加入团队
|
width: 40, height: 40, borderRadius: 10,
|
||||||
</Typography.Text>
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
<Typography.Title level={4} style={{ color: '#6366f1', margin: '16px 0 24px' }}>
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
「{info.teamName}」
|
}}>
|
||||||
</Typography.Title>
|
<ThunderboltOutlined style={{ fontSize: 20, color: '#fff' }} />
|
||||||
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 24 }}>
|
</div>
|
||||||
加入后团队管理人可以为您分配积分、查看您的积分使用情况。
|
)}
|
||||||
</Typography.Text>
|
<span style={{ fontSize: 22, fontWeight: 700, color: '#1e293b', letterSpacing: 0.5 }}>{siteName}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
variant="outlined"
|
||||||
|
style={{ borderRadius: 16, width: '100%', textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
<TeamOutlined style={{ fontSize: 48, color: '#6366f1', marginBottom: 16 }} />
|
||||||
|
<Typography.Title level={3}>加入团队</Typography.Title>
|
||||||
|
<Typography.Text style={{ fontSize: 16, color: '#475569', display: 'block', marginBottom: 8 }}>
|
||||||
|
您被邀请加入团队
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Title level={4} style={{ color: '#6366f1', margin: '16px 0 24px' }}>
|
||||||
|
「{info.teamName}」
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 24 }}>
|
||||||
|
加入后团队管理人可以为您分配积分、查看您的积分使用情况。
|
||||||
|
</Typography.Text>
|
||||||
|
|
||||||
{user ? (
|
{user ? (
|
||||||
<Button
|
<Button
|
||||||
@@ -197,7 +220,8 @@ const JoinTeamPage: React.FC = () => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
import { DatePicker } from 'antd';
|
import { DatePicker } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import {
|
import {
|
||||||
CopyOutlined, DownloadOutlined, PlusOutlined, ReloadOutlined, UserOutlined, HistoryOutlined, WalletOutlined, BellOutlined, ClockCircleOutlined,
|
CopyOutlined, DownloadOutlined, PlusOutlined, ReloadOutlined, UserOutlined, HistoryOutlined, WalletOutlined, BellOutlined, ClockCircleOutlined, CheckOutlined, CloseOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
@@ -405,8 +405,8 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
title: '操作', key: 'action', width: 160,
|
title: '操作', key: 'action', width: 160,
|
||||||
render: (_: any, r: TeamJoinRequest) => (
|
render: (_: any, r: TeamJoinRequest) => (
|
||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<Button size="small" type="link" style={{ color: '#16a34a', padding: 0 }} onClick={() => handleRequest(r.id, 'approve')}>通过</Button>
|
<Button size="small" type="link" icon={<CheckOutlined />} style={{ color: '#16a34a', padding: 0 }} onClick={() => handleRequest(r.id, 'approve')}>通过</Button>
|
||||||
<Button size="small" type="link" danger style={{ padding: 0 }} onClick={() => {
|
<Button size="small" type="link" icon={<CloseOutlined />} danger style={{ padding: 0 }} onClick={() => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '拒绝申请',
|
title: '拒绝申请',
|
||||||
content: (
|
content: (
|
||||||
|
|||||||
Reference in New Issue
Block a user