Skip to content

Commit cab413f

Browse files
authored
add user-related interactions on the frontend (#250)
* fix: fix the home page * fix: modify the logic of reading configuration items to reduce the number of calls to redis when redis does not exist * feature: add user-related interactions on the frontend
1 parent 6e85f53 commit cab413f

File tree

12 files changed

+463
-38
lines changed

12 files changed

+463
-38
lines changed

backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/UserContextFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class UserContextFilter implements GlobalFilter {
3535

3636
private final UserService userService;
3737

38-
@Value("${datamate.jwt.enable:false}")
38+
@Value("${datamate.jwt.enable:true}")
3939
private Boolean jwtEnable;
4040

4141
@Override

backend/shared/domain-common/src/main/java/com/datamate/common/setting/application/SysParamApplicationService.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,17 @@ public void deleteParamById(String paramKey) {
5959
}
6060

6161
public String getParamByKey(String paramId) {
62-
String value = redisClient.getParam(paramId);
62+
boolean redisEnable = false;
63+
String value = null;
64+
try {
65+
value = redisClient.getParamWithThrow(paramId);
66+
redisEnable = true;
67+
} catch (Exception e) {
68+
log.warn(e.getMessage());
69+
}
6370
if (value == null) {
6471
SysParam sysParam = sysParamRepository.getById(paramId);
65-
if (sysParam != null) {
72+
if (sysParam != null && redisEnable) {
6673
value = sysParam.getParamValue();
6774
}
6875
}

backend/shared/domain-common/src/main/java/com/datamate/common/setting/infrastructure/client/RedisClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@ public String getParam(String key) {
2323
public void delParam(String key) {
2424
FunctionUtil.doWithoutThrow(redisTemplate::delete, key);
2525
}
26+
27+
public String getParamWithThrow(String key) {
28+
return redisTemplate.opsForValue().get(key);
29+
}
2630
}

frontend/src/hooks/useFetchData.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,11 @@ export default function useFetchData<T>(
154154
poll();
155155
}
156156
} catch (error) {
157-
console.error(error);
158-
message.error("数据获取失败,请稍后重试");
157+
if (error.status === 401) {
158+
message.warn("请登录");
159+
} else {
160+
message.error("数据获取失败,请稍后重试");
161+
}
159162
} finally {
160163
Loading.hide();
161164
setLoading(false);

frontend/src/pages/Home/Home.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
MessageSquare,
1010
GitBranch,
1111
} from "lucide-react";
12-
import { features, menuItems } from "../Layout/menu";
12+
import { features, menuItems } from "../Layout/Menu.tsx";
1313
import { useState } from 'react';
1414
import { useNavigate } from "react-router";
1515
import { Card } from "antd";
@@ -74,8 +74,8 @@ export default function WelcomePage() {
7474
AI数据集准备工具
7575
</div>
7676
<h1 className="text-4xl md:text-6xl font-bold text-gray-900 mb-6">
77-
构建高质量
78-
<span className="text-blue-600"> AI数据集</span>
77+
DataMate
78+
<span className="text-blue-600"> 构建高质量 AI数据集</span>
7979
</h1>
8080
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-8">
8181
从数据管理到知识生成,一站式解决企业AI数据处理的场景问题。
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { User, Globe, LogIn, UserPlus, Sparkles } from "lucide-react"
2+
import { memo, useState, useEffect } from "react";
3+
import { NavLink } from "react-router";
4+
import { Button, Dropdown, message } from "antd"
5+
import type { MenuProps } from 'antd'
6+
import { LoginDialog } from "./LoginDialog"
7+
import { SignupDialog } from "./SignupDialog"
8+
import { post} from "@/utils/request.ts";
9+
10+
function loginUsingPost(data: any) {
11+
return post("/api/user/login", data);
12+
}
13+
14+
function signupUsingPost(data: any) {
15+
return post("/api/user/signup", data);
16+
}
17+
18+
export function Header() {
19+
const [loginOpen, setLoginOpen] = useState(false)
20+
const [signupOpen, setSignupOpen] = useState(false)
21+
const [loading, setLoading] = useState(false);
22+
23+
const handleLogin = async (values: { username: string; password: string }) => {
24+
try {
25+
setLoading(true);
26+
const response = await loginUsingPost(values);
27+
// Store the token in localStorage
28+
localStorage.setItem('session', JSON.stringify(response.data));
29+
message.success('登录成功');
30+
setLoginOpen(false);
31+
// Optionally refresh the page or update the UI
32+
window.location.reload();
33+
} catch (error) {
34+
console.error('Login error:', error);
35+
message.error('登录失败,请稍后重试');
36+
} finally {
37+
setLoading(false);
38+
}
39+
};
40+
41+
const handleSignup = async (values: {
42+
username: string;
43+
email: string;
44+
password: string;
45+
confirmPassword: string
46+
}) => {
47+
if (values.password !== values.confirmPassword) {
48+
message.error('两次输入的密码不一致');
49+
return;
50+
}
51+
52+
try {
53+
setLoading(true);
54+
const { username, email, password } = values;
55+
const response = await signupUsingPost({ username, email, password });
56+
57+
message.success('注册成功,已自动登录');
58+
localStorage.setItem('session', JSON.stringify(response.data));
59+
setSignupOpen(false);
60+
} catch (error) {
61+
console.error('Registration error:', error);
62+
message.error('注册失败,请稍后重试');
63+
} finally {
64+
setLoading(false);
65+
}
66+
};
67+
68+
const handleLogout = () => {
69+
localStorage.removeItem('session');
70+
message.success('已退出登录');
71+
// Refresh the page after logout
72+
window.location.reload();
73+
};
74+
75+
const openLoginDialog = () => {
76+
setLoginOpen(true);
77+
};
78+
79+
const openSignupDialog = () => {
80+
setSignupOpen(true);
81+
};
82+
83+
useEffect(() => {
84+
window.addEventListener('show-login', openLoginDialog);
85+
86+
return () => {
87+
window.removeEventListener('show-login', openLoginDialog);
88+
};
89+
}, []);
90+
91+
const languageMenuItems: MenuProps['items'] = [
92+
{
93+
key: 'zh',
94+
label: '简体中文',
95+
},
96+
// {
97+
// key: 'en',
98+
// label: 'English',
99+
// }
100+
]
101+
102+
const userDropdownItems: MenuProps['items'] = localStorage.getItem("session")
103+
? [
104+
{
105+
key: 'profile',
106+
label: JSON.parse(localStorage.getItem("session") as string).email,
107+
},
108+
{
109+
type: 'divider',
110+
},
111+
{
112+
key: 'logout',
113+
label: '退出登录',
114+
icon: <LogIn className="h-4 w-4" />,
115+
onClick: handleLogout,
116+
},
117+
]
118+
: [
119+
{
120+
key: 'login',
121+
label: '登录',
122+
icon: <LogIn className="h-4 w-4" />,
123+
onClick: () => setLoginOpen(true),
124+
},
125+
{
126+
type: 'divider',
127+
},
128+
{
129+
key: 'register',
130+
label: '注册',
131+
icon: <UserPlus className="h-4 w-4" />,
132+
onClick: () => setSignupOpen(true),
133+
},
134+
];
135+
136+
return (
137+
<>
138+
<header className="sticky top-0 z-50 w-full border-b border-gray-200 bg-white">
139+
<div className="flex h-14 items-center justify-between px-6">
140+
<div className="flex items-center gap-2">
141+
<div className="flex items-center gap-2">
142+
<NavLink to="/" className="flex items-center gap-2 cursor-pointer">
143+
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center">
144+
<Sparkles className="w-5 h-5 text-white" />
145+
</div>
146+
<span className="text-lg font-bold text-gray-900">DataMate</span>
147+
</NavLink>
148+
</div>
149+
</div>
150+
151+
<div className="flex items-center gap-4">
152+
<Dropdown
153+
menu={{ items: languageMenuItems }}
154+
placement="bottomRight"
155+
>
156+
<Button type="text" className="flex items-center gap-2">
157+
<Globe className="h-4 w-4" />
158+
<span>简体中文</span>
159+
</Button>
160+
</Dropdown>
161+
162+
<Dropdown
163+
menu={{ items: userDropdownItems }}
164+
placement="bottomRight"
165+
overlayClassName="w-40"
166+
>
167+
<Button type="text" icon={<User className="h-4 w-4" />}>
168+
{localStorage.getItem("session") ? (
169+
<span className="ml-1">{JSON.parse(localStorage.getItem("session") as string).username}</span>
170+
) : null}
171+
</Button>
172+
</Dropdown>
173+
</div>
174+
</div>
175+
</header>
176+
177+
<LoginDialog
178+
open={loginOpen}
179+
onOpenChange={setLoginOpen}
180+
onLogin={handleLogin}
181+
loading={loading}
182+
onSignupClick={openSignupDialog}
183+
/>
184+
<SignupDialog
185+
open={signupOpen}
186+
onOpenChange={setSignupOpen}
187+
onSignup={handleSignup}
188+
loading={loading}
189+
onLoginClick={openLoginDialog}
190+
/>
191+
</>
192+
)
193+
}
194+
195+
export default memo(Header);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Button, Form, Input, Modal } from "antd"
2+
import { LogIn } from "lucide-react"
3+
4+
interface LoginDialogProps {
5+
open: boolean
6+
onOpenChange: (open: boolean) => void
7+
onLogin: (values: { username: string; password: string }) => Promise<void>;
8+
loading: boolean
9+
onSignupClick?: () => void
10+
}
11+
12+
export function LoginDialog({ open, onOpenChange, onLogin, loading, onSignupClick }: LoginDialogProps) {
13+
const [form] = Form.useForm()
14+
15+
const handleSubmit = async (values: { username: string; password: string }) => {
16+
await onLogin(values);
17+
form.resetFields();
18+
};
19+
20+
return (
21+
<Modal
22+
title={
23+
<div className="flex items-center gap-2">
24+
<LogIn className="h-5 w-5" />
25+
<span>登录</span>
26+
</div>
27+
}
28+
open={open}
29+
onCancel={() => onOpenChange(false)}
30+
footer={null}
31+
width={400}
32+
maskClosable={false}
33+
>
34+
<div className="text-gray-500 mb-6">请输入您的用户名和密码登录系统</div>
35+
36+
<Form
37+
form={form}
38+
layout="vertical"
39+
onFinish={handleSubmit}
40+
autoComplete="off"
41+
>
42+
<Form.Item
43+
name="username"
44+
label="用户名"
45+
rules={[{ required: true, message: '请输入用户名' }]}
46+
>
47+
<Input placeholder="请输入用户名" />
48+
</Form.Item>
49+
50+
<Form.Item
51+
name="password"
52+
label="密码"
53+
rules={[{ required: true, message: '请输入密码' }]}
54+
>
55+
<Input.Password placeholder="请输入密码" />
56+
</Form.Item>
57+
58+
<Form.Item>
59+
<Button
60+
type="primary"
61+
htmlType="submit"
62+
loading={loading}
63+
block
64+
>
65+
登录
66+
</Button>
67+
<div className="mt-4 text-center text-sm">
68+
还没有账号?
69+
<a
70+
className="text-blue-500 hover:text-blue-600 ml-1 cursor-pointer"
71+
onClick={(e) => {
72+
e.preventDefault();
73+
onOpenChange(false);
74+
onSignupClick && onSignupClick();
75+
}}
76+
>
77+
立即注册
78+
</a>
79+
</div>
80+
</Form.Item>
81+
</Form>
82+
</Modal>
83+
)
84+
}

frontend/src/pages/Layout/MainLayout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import React, { memo } from "react";
1+
import { memo } from "react";
22
import { Outlet } from "react-router";
33
import Sidebar from "./Sidebar";
4+
import Header from "./Header.tsx";
45

56
const MainLayout = () => {
67
return (
78
<div className="w-full h-screen flex flex-col bg-gray-50 min-w-6xl">
9+
<Header />
810
<div className="w-full h-full flex">
911
{/* Sidebar */}
1012
<Sidebar />

0 commit comments

Comments
 (0)