diff --git a/backend/test_new_login.py b/backend/test_new_login.py new file mode 100644 index 00000000..69f1726a --- /dev/null +++ b/backend/test_new_login.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +""" +测试新登录逻辑(单枪匹马/组队团战/独当一面) +""" + +import requests + +API_BASE = 'http://localhost:8000/api/v1' + +def test_new_login(): + print("="*60) + print("🎯 测试新登录逻辑") + print("="*60) + + # 1. 单枪匹马 + print("\n🥷 测试 1: 单枪匹马(仅人类)") + res = requests.post(f'{API_BASE}/auth/login/', json={ + 'username': 'test', + 'password': 'test123', + 'mode': 'solo' + }) + if res.status_code == 200: + data = res.json() + print(f"✅ 登录成功") + print(f" 模式:{data['mode_name']}") + print(f" 会话数:{len(data['sessions'])}") + for s in data['sessions']: + print(f" - {s['session_type']}: {s['nickname']} ({s['emoji']})") + else: + print(f"❌ 登录失败:{res.json()}") + + # 2. 组队团战 + print("\n🛡️ 测试 2: 组队团战(人类 +2 龙虾)") + res = requests.post(f'{API_BASE}/auth/login/', json={ + 'username': 'test', + 'password': 'test123', + 'mode': 'team', + 'agent_ids': ['flying_hero', 'lobster_monitor'] + }) + if res.status_code == 200: + data = res.json() + print(f"✅ 登录成功") + print(f" 模式:{data['mode_name']}") + print(f" 会话数:{len(data['sessions'])}") + for s in data['sessions']: + emoji = s.get('emoji', '🤖') + print(f" - {s['session_type']}: {s['nickname']} ({emoji})") + else: + print(f"❌ 登录失败:{res.json()}") + + # 3. 独当一面 + print("\n⚔️ 测试 3: 独当一面(仅龙虾)") + res = requests.post(f'{API_BASE}/auth/login/', json={ + 'username': 'test', + 'password': 'test123', + 'mode': 'agent_only', + 'agent_ids': ['flying_hero'] + }) + if res.status_code == 200: + data = res.json() + print(f"✅ 登录成功") + print(f" 模式:{data['mode_name']}") + print(f" 会话数:{len(data['sessions'])}") + for s in data['sessions']: + emoji = s.get('emoji', '🤖') + print(f" - {s['session_type']}: {s['nickname']} ({emoji})") + else: + print(f"❌ 登录失败:{res.json()}") + + # 4. 错误测试 - 组队但没选龙虾 + print("\n❌ 测试 4: 组队但没选龙虾(应该失败)") + res = requests.post(f'{API_BASE}/auth/login/', json={ + 'username': 'test', + 'password': 'test123', + 'mode': 'team', + 'agent_ids': [] + }) + if res.status_code == 400: + print(f"✅ 正确失败:{res.json()['error']}") + else: + print(f"❌ 应该失败但成功了:{res.json()}") + + print("\n" + "="*60) + print("✅ 新登录逻辑测试完成!") + print("="*60) + print("\n📊 三种模式:") + print("1. 🥷 单枪匹马 - 人类单独出战") + print("2. 🛡️ 组队团战 - 人类 +N 龙虾") + print("3. ⚔️ 独当一面 - 龙虾单独出征") + +if __name__ == '__main__': + test_new_login() diff --git a/backend/users/views.py b/backend/users/views.py index 7b7a552c..68661a9a 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -8,11 +8,17 @@ User = get_user_model() class LoginSerializer(serializers.Serializer): username = serializers.CharField() password = serializers.CharField() - login_mode = serializers.ChoiceField( - choices=['human_only', 'agent_only', 'both'], - default='human_only' + mode = serializers.ChoiceField( + choices=['solo', 'team', 'agent_only'], + default='solo', + help_text='solo=单枪匹马,team=组队团战,agent_only=独当一面' + ) + agent_ids = serializers.ListField( + child=serializers.CharField(), + required=False, + default=list, + help_text='选择的龙虾 ID 列表(team 或 agent_only 模式)' ) - selected_agent_id = serializers.CharField(required=False, allow_blank=True) class LoginView(views.APIView): @@ -23,8 +29,8 @@ class LoginView(views.APIView): username = serializer.validated_data['username'] password = serializer.validated_data['password'] - login_mode = serializer.validated_data.get('login_mode', 'human_only') - selected_agent_id = serializer.validated_data.get('selected_agent_id') + mode = serializer.validated_data.get('mode', 'solo') + agent_ids = serializer.validated_data.get('agent_ids', []) user = authenticate(username=username, password=password) if not user: @@ -40,8 +46,8 @@ class LoginView(views.APIView): # 构建会话信息 sessions = [] - if login_mode in ['human_only', 'both']: - # 人类身份 + if mode in ['solo', 'team']: + # 人类身份(单枪匹马 或 组队团战) sessions.append({ 'session_type': 'human', 'nickname': user.username, @@ -49,10 +55,17 @@ class LoginView(views.APIView): 'user_id': user.id }) - if login_mode in ['agent_only', 'both']: - # 龙虾身份 - if selected_agent_id: - agent = user.get_linked_agent(selected_agent_id) + if mode in ['team', 'agent_only']: + # 龙虾身份(组队团战 或 独当一面) + if not agent_ids: + return Response( + {'error': '组队或独当一面模式需要选择至少一只龙虾'}, + status=status.HTTP_400_BAD_REQUEST + ) + + # 添加所有选择的龙虾 + for agent_id in agent_ids: + agent = user.get_linked_agent(agent_id) if agent: sessions.append({ 'session_type': 'agent', @@ -64,10 +77,17 @@ class LoginView(views.APIView): }) else: return Response( - {'error': f'未找到绑定的龙虾:{selected_agent_id}'}, + {'error': f'未找到绑定的龙虾:{agent_id}'}, status=status.HTTP_400_BAD_REQUEST ) + # 模式名称映射 + mode_names = { + 'solo': '单枪匹马', + 'team': '组队团战', + 'agent_only': '独当一面' + } + return Response({ 'token': token, 'user': { @@ -77,7 +97,8 @@ class LoginView(views.APIView): 'linked_agents': user.linked_agents }, 'sessions': sessions, - 'login_mode': login_mode + 'mode': mode, + 'mode_name': mode_names.get(mode, mode) }) diff --git a/frontend/src/App.js b/frontend/src/App.js index 4c5e678a..bfb33e37 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -14,9 +14,9 @@ axios.interceptors.request.use(config => { function LoginPage() { const [username, setUsername] = useState('test'); const [password, setPassword] = useState('test123'); - const [loginMode, setLoginMode] = useState('human_only'); + const [mode, setMode] = useState('solo'); const [agents, setAgents] = useState([]); - const [selectedAgent, setSelectedAgent] = useState(''); + const [selectedAgents, setSelectedAgents] = useState([]); const navigate = useNavigate(); // 扫描本机龙虾 @@ -28,9 +28,6 @@ function LoginPage() { try { const res = await axios.get(`${API_BASE}/user/scan-local-agents/`); setAgents(res.data.agents || []); - if (res.data.agents?.length > 0) { - setSelectedAgent(res.data.agents[0].agent_id); - } } catch (error) { console.error('扫描龙虾失败:', error); } @@ -42,24 +39,32 @@ function LoginPage() { const payload = { username, password, - login_mode: loginMode + mode }; - if (loginMode !== 'human_only' && selectedAgent) { - payload.selected_agent_id = selectedAgent; + if (mode !== 'solo' && selectedAgents.length > 0) { + payload.agent_ids = selectedAgents; } const res = await axios.post(`${API_BASE}/auth/login/`, payload); localStorage.setItem('token', res.data.token); localStorage.setItem('user', JSON.stringify(res.data.user)); localStorage.setItem('sessions', JSON.stringify(res.data.sessions)); - localStorage.setItem('login_mode', res.data.login_mode); + localStorage.setItem('mode', res.data.mode); navigate('/meetings'); } catch (error) { alert('登录失败:' + (error.response?.data?.detail || error.response?.data?.error || error.message)); } }; + const toggleAgent = (agentId) => { + setSelectedAgents(prev => + prev.includes(agentId) + ? prev.filter(id => id !== agentId) + : [...prev, agentId] + ); + }; + return (
@@ -68,64 +73,71 @@ function LoginPage() { setUsername(e.target.value)} style={styles.input} required /> setPassword(e.target.value)} style={styles.input} required /> - {/* 身份模式选择 */} + {/* 出战模式选择 */}
- -
- {/* 龙虾选择 */} - {loginMode !== 'human_only' && ( -
- - + {/* 龙虾选择(组队或独当一面) */} + {mode !== 'solo' && ( +
+ + {agents.length === 0 ? ( +

未找到可用龙虾

+ ) : ( + agents.map(a => ( + + )) + )} + {selectedAgents.length > 0 && ( +

+ 已选 {selectedAgents.length} 只龙虾队友 🦸 +

+ )}
)} - +