Merge branch 'main' of https://gitee.com/wg123/video-gen
This commit is contained in:
@@ -188,7 +188,7 @@ async def get_join_info(
|
|||||||
"""验证邀请码并返回团队信息(用于加入页面展示)。"""
|
"""验证邀请码并返回团队信息(用于加入页面展示)。"""
|
||||||
invitation = await team_invitation_service.get_invitation_by_code(db, code)
|
invitation = await team_invitation_service.get_invitation_by_code(db, code)
|
||||||
if not invitation:
|
if not invitation:
|
||||||
return JoinTeamInfoOut(team_name="", team_id="", valid=False)
|
return JoinTeamInfoOut(team_name="", team_id="", valid=False, already_in_team=False, has_pending_request=False)
|
||||||
|
|
||||||
team = await db.execute(
|
team = await db.execute(
|
||||||
select(Team.name).where(Team.id == invitation.team_id, Team.deleted_at.is_(None)).limit(1)
|
select(Team.name).where(Team.id == invitation.team_id, Team.deleted_at.is_(None)).limit(1)
|
||||||
@@ -227,7 +227,7 @@ async def get_join_info_public(
|
|||||||
"""公开接口:验证邀请码并返回团队信息(无需登录)。"""
|
"""公开接口:验证邀请码并返回团队信息(无需登录)。"""
|
||||||
invitation = await team_invitation_service.get_invitation_by_code(db, code)
|
invitation = await team_invitation_service.get_invitation_by_code(db, code)
|
||||||
if not invitation:
|
if not invitation:
|
||||||
return {"team_name": "", "team_id": "", "valid": False}
|
return {"team_name": "", "team_id": "", "valid": False, "already_in_team": False, "has_pending_request": False}
|
||||||
|
|
||||||
team = await db.execute(
|
team = await db.execute(
|
||||||
select(Team.name).where(Team.id == invitation.team_id, Team.deleted_at.is_(None)).limit(1)
|
select(Team.name).where(Team.id == invitation.team_id, Team.deleted_at.is_(None)).limit(1)
|
||||||
@@ -238,6 +238,8 @@ async def get_join_info_public(
|
|||||||
"team_name": team_name,
|
"team_name": team_name,
|
||||||
"team_id": invitation.team_id,
|
"team_id": invitation.team_id,
|
||||||
"valid": True,
|
"valid": True,
|
||||||
|
"already_in_team": False,
|
||||||
|
"has_pending_request": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ async def create_join_request(
|
|||||||
|
|
||||||
# 验证用户存在
|
# 验证用户存在
|
||||||
user_result = await db.execute(
|
user_result = await db.execute(
|
||||||
select(User).where(User.id == user_id, User.deleted_at.is_(None)).limit(1)
|
select(User).where(User.id == user_id, User.is_active == True).limit(1)
|
||||||
)
|
)
|
||||||
user = user_result.scalar_one_or_none()
|
user = user_result.scalar_one_or_none()
|
||||||
if not user:
|
if not user:
|
||||||
|
|||||||
+10
-10
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-8kqorb_r.js"></script>
|
<script type="module" crossorigin src="/assets/index-DUQKfJjB.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D9_3MPsN.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D9_3MPsN.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ async function tryDecrypt(data: string): Promise<string | null> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||||
const { method = 'GET', body, auth = true, encryptBody = USE_ENCRYPTION, signal } = options;
|
const { method = 'GET', body, auth = true, encryptBody = USE_ENCRYPTION, signal, skipAuthRedirect } = options;
|
||||||
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
CheckCircleOutlined, TeamOutlined, LoginOutlined, UserAddOutlined,
|
CheckCircleOutlined, TeamOutlined, LoginOutlined, UserAddOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { getJoinTeamInfo, submitJoinRequest } from '../api';
|
import { getJoinTeamInfo, getJoinTeamInfoPublic, submitJoinRequest } from '../api';
|
||||||
import type { JoinTeamInfo } from '../types';
|
import type { JoinTeamInfo } from '../types';
|
||||||
import { useAuthStore } from '../store/useAuthStore';
|
import { useAuthStore } from '../store/useAuthStore';
|
||||||
|
|
||||||
@@ -34,7 +34,8 @@ const JoinTeamPage: React.FC = () => {
|
|||||||
if (authLoading) return;
|
if (authLoading) return;
|
||||||
|
|
||||||
setInfoLoading(true);
|
setInfoLoading(true);
|
||||||
getJoinTeamInfo(code)
|
const fetchInfo = user ? getJoinTeamInfo(code) : getJoinTeamInfoPublic(code);
|
||||||
|
fetchInfo
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setInfo(data);
|
setInfo(data);
|
||||||
if (data?.valid && user && !data.alreadyInTeam && !data.hasPendingRequest) {
|
if (data?.valid && user && !data.alreadyInTeam && !data.hasPendingRequest) {
|
||||||
@@ -122,7 +123,6 @@ const JoinTeamPage: React.FC = () => {
|
|||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" onClick={() => navigate('/projects')}>返回首页</Button>
|
<Button type="primary" onClick={() => navigate('/projects')}>返回首页</Button>
|
||||||
<Button onClick={() => navigate('/team-management')}>团队管理</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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,
|
CopyOutlined, DownloadOutlined, PlusOutlined, ReloadOutlined, UserOutlined, HistoryOutlined, WalletOutlined, BellOutlined, ClockCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
@@ -134,8 +134,7 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const values = await invForm.validateFields();
|
const values = await invForm.validateFields();
|
||||||
setInvSaving(true);
|
setInvSaving(true);
|
||||||
const expiresAt = values.expiresAt ? new Date(values.expiresAt).toISOString() : null;
|
await createTeamInvitation(values.maxUses || null, null);
|
||||||
await createTeamInvitation(values.maxUses || null, expiresAt);
|
|
||||||
message.success('邀请码已生成');
|
message.success('邀请码已生成');
|
||||||
setInvModal(false);
|
setInvModal(false);
|
||||||
invForm.resetFields();
|
invForm.resetFields();
|
||||||
@@ -698,9 +697,19 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
<Form.Item name="maxUses" label="最大使用次数">
|
<Form.Item name="maxUses" label="最大使用次数">
|
||||||
<InputNumber style={{ width: '100%' }} min={1} placeholder="留空表示不限" size="large" />
|
<InputNumber style={{ width: '100%' }} min={1} placeholder="留空表示不限" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="expiresAt" label="过期时间">
|
<div style={{
|
||||||
<Input type="datetime-local" style={{ width: '100%' }} placeholder="默认24小时后过期" size="large" />
|
padding: '12px 16px',
|
||||||
</Form.Item>
|
background: '#eef2ff',
|
||||||
|
borderRadius: 8,
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#4f46e5',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
}}>
|
||||||
|
<ClockCircleOutlined />
|
||||||
|
<span>邀请码自生成起 <strong>24 小时</strong> 内有效,过期自动失效。</span>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user