Commit 420949cd authored by 越 贾's avatar 越 贾
Browse files

2025-08-29:ver3:设定固定位置避免消息过多时挤占聊天框,美观化前端样式(强AI辅助),检查删除了冗余API Routes

parent 4eab09ff
No preview for this file type
// src/app/api/room/message/getUpdate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const roomId = searchParams.get('roomId');
const sinceMessageId = searchParams.get('sinceMessageId');
if (!roomId || !sinceMessageId) {
return NextResponse.json({ code: 1, message: '缺少参数' }, { status: 400 });
}
const list = await prisma.message.findMany({
where: { roomId: Number(roomId), id: { gt: Number(sinceMessageId) } },
orderBy: { createdAt: 'asc' },
});
const messages = list.map((m) => ({
messageId: m.id,
roomId: m.roomId,
sender: m.sender,
content: m.content,
time: m.createdAt.getTime(),
}));
return NextResponse.json({ code: 0, data: { messages } });
} catch (e) {
console.error('增量拉取失败:', e);
return NextResponse.json({ code: 1, message: '服务器错误' }, { status: 500 });
}
}
\ No newline at end of file
// src/app/api/room/message/list/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const roomId = searchParams.get('roomId');
if (!roomId) {
return NextResponse.json({
code: 1,
message: '缺少 roomId 参数'
}, { status: 400 });
}
const messages = await prisma.message.findMany({
where: { roomId: Number(roomId) },
orderBy: { createdAt: 'asc' },
});
const formattedMessages = messages.map(msg => ({
messageId: msg.id,
roomId: msg.roomId,
sender: msg.sender,
content: msg.content,
time: msg.createdAt.getTime(),
}));
return NextResponse.json({
code: 0,
data: { messages: formattedMessages }
});
} catch (error) {
console.error('获取消息列表失败:', error);
return NextResponse.json({
code: 1,
message: '获取消息失败'
}, { status: 500 });
}
}
\ No newline at end of file
// src/app/api/room/add/route.ts
// chatroom/src/app/api/room/add/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
......
// src/app/api/room/delete/route.ts
// chatroom/src/app/api/room/delete/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
......
// src/app/api/room/list/route.ts
// chatroom/src/app/api/room/list/route.ts
import { NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
......
// src/app/api/room/message/getUpdate/route.ts
// chatroom/src/app/api/room/message/getUpdate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
......
/* src/app/chat/page.module.css */
/* 页面整体包装器 */
.pageWrapper {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
/* 顶部状态栏 */
.topBar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 100;
}
.appTitle {
font-size: 1.2rem;
font-weight: bold;
color: white;
letter-spacing: 1px;
}
/* 认证状态样式 */
.authStatus {
display: flex;
align-items: center;
gap: 1rem;
}
.loggedIn, .notLoggedIn {
display: flex;
align-items: center;
gap: 0.75rem;
color: white;
}
.userInfo, .guestInfo {
font-size: 0.9rem;
padding: 0.25rem 0.75rem;
background: rgba(255, 255, 255, 0.2);
border-radius: 15px;
font-weight: 500;
}
.logoutBtn {
padding: 0.4rem 1rem;
background: rgba(255, 255, 255, 0.3);
color: white;
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 20px;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.3s ease;
}
.logoutBtn:hover {
background: rgba(255, 255, 255, 0.4);
transform: translateY(-1px);
}
.loginLink {
padding: 0.4rem 1rem;
background: white;
color: #667eea;
text-decoration: none;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.3s ease;
}
.loginLink:hover {
background: #f0f0f0;
transform: translateY(-1px);
}
/* 聊天容器调整 */
.chatContainer {
display: flex;
flex: 1;
max-width: 1200px;
width: 100%;
margin: 0 auto;
background-color: #f5f5f5;
overflow: hidden;
}
/* sidebar和chatArea样式不变 */
.chatContainer {
display: flex;
height: 100vh;
......@@ -93,15 +186,19 @@
display: flex;
flex-direction: column;
background-color: white;
position: relative; /* 添加相对定位 */
overflow: hidden; /* 防止内容溢出 */
}
.chatContent {
flex: 1;
display: flex;
flex-direction: column;
height: 100vh;
height: 100%;
position: relative; /* 添加相对定位 */
}
.chatHeader {
padding: 1rem;
border-bottom: 1px solid #e1e1e1;
......@@ -119,6 +216,8 @@
overflow-y: auto;
padding: 1rem;
background-color: #f8f9fa;
/* 为输入框预留空间,防止消息被遮挡 */
padding-bottom: 80px;
}
.messageItem {
......@@ -170,13 +269,19 @@
}
.messageInput {
position: absolute; /* 改为绝对定位 */
bottom: 0;
left: 0;
right: 0;
display: flex;
padding: 1rem;
border-top: 1px solid #e1e1e1;
background-color: white;
border-top: 1px solid #e1e1e1;
gap: 0.5rem;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05); /* 添加阴影 */
}
.textInput {
flex: 1;
padding: 0.75rem;
......
......@@ -367,7 +367,7 @@ export default function ChatPage() {
const res = await addRoomTrigger({ user: nickname, roomName: newRoomName.trim() });
refreshRooms();
if (res?.roomId) {
handleSelectRoom(res.roomId); // 🆕 使用新的设置函数
handleSelectRoom(res.roomId); //使用新的设置函数
}
setShowAddModal(false);
setNewRoomName('');
......@@ -397,7 +397,7 @@ export default function ChatPage() {
await deleteRoomTrigger({ roomId });
if (selectedRoomId === roomId) {
handleSelectRoom(null); // 🆕 使用新的设置函数
handleSelectRoom(null); // 使用新的设置函数
}
refreshRooms();
......@@ -451,14 +451,17 @@ export default function ChatPage() {
const messageError = initialError;
return (
<main className={styles.chatContainer}>
{/* 身份验证状态显示 */}
<div className={styles.pageWrapper}>
{/* 登录状态移到顶部横条 */}
<div className={styles.topBar}>
<div className={styles.appTitle}>JYCHATROOM</div>
<AuthStatus
isLoggedIn={isLoggedIn}
username={authUsername}
onLogout={logout}
/>
</div>
<main className={styles.chatContainer}>
{/* 左侧房间列表 - 修改点击事件 */}
<div className={styles.sidebar}>
<div className={styles.sidebarHeader}>
......@@ -479,7 +482,7 @@ export default function ChatPage() {
<RoomEntry
key={r.roomId}
room={r}
onClick={() => handleSelectRoom(r.roomId)} // 🆕 使用新的设置函数
onClick={() => handleSelectRoom(r.roomId)} // 使用新的设置函数
isActive={selectedRoomId === r.roomId}
onDelete={() => handleDeleteRoom(r.roomId)}
canDelete={isLoggedIn}
......@@ -565,5 +568,7 @@ export default function ChatPage() {
</div>
)}
</main>
</div>
);
}
\ No newline at end of file
/* chatroom/src/app/greet/greet.css */
.div{
color: #c3aae2;
text-align: center;
padding: 20px;
}
.title{
font-size: 2rem;
color: #25044e;
margin-bottom: 20px;
}
\ No newline at end of file
/* chatroom/src/app/greet/greet.module.css */
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-align: center;
padding: 20px;
}
.title {
font-size: 3rem;
color: white;
margin-bottom: 1rem;
animation: fadeIn 0.5s ease-in;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.subtitle {
font-size: 1.2rem;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 2rem;
animation: fadeIn 0.5s ease-in 0.2s both;
}
.loader {
width: 50px;
height: 50px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top: 3px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
\ No newline at end of file
......@@ -3,9 +3,9 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import './greet.css';
import styles from './greet.module.css'; // 改为 module.css
export default function ChatPage() {
export default function GreetPage() {
const [nickname, setNickname] = useState('');
const router = useRouter();
......@@ -13,22 +13,24 @@ export default function ChatPage() {
const savedNickname = localStorage.getItem('nickname');
if (!savedNickname) {
alert('请先设置昵称');
router.push('/nickname');
router.push('/setname');
} else {
setNickname(savedNickname);
}
}, []);
}, [router]);
useEffect(() => {
setTimeout(() => {
// 5秒后跳转到目标页面
const timer = setTimeout(() => {
router.push('/chat')
}, 3000); // 5000毫秒(5秒)后执行
}, []); // 空依赖数组表示这个effect只会在组件挂载时执行一次
}, 3000);
return () => clearTimeout(timer); // 清理定时器
}, [router]);
return (
<div>
<h1 className='{styles.title}'>欢迎,{nickname}</h1>
<p>这个页面将在3秒后自动跳转聊天页面</p>
<div className={styles.container}>
<h1 className={styles.title}>欢迎,{nickname}</h1>
<p className={styles.subtitle}>即将进入聊天室...</p>
<div className={styles.loader}></div>
</div>
);
}
\ No newline at end of file
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh; /* 占满整个视口高度 */
padding: 2rem;
background-color: #7c66c7;
}
.card {
background: white;
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
.title {
text-align: center;
color: #333;
margin-bottom: 2rem;
font-size: 2rem;
}
.input {
width: 95%;
padding: 0.75rem;
margin-bottom: 1rem;
border: 2px solid #e1e1e1;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s;
}
.input:focus {
outline: none;
border-color: #667eea;
}
.button {
width: 102%;
padding: 0.75rem;
background: #f3f0ff;
color: rgb(36, 16, 132);
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s;
}
.button:hover {
transform: translateY(-2px);
}
.footer {
text-align: center;
margin-top: 1.5rem;
color: #666;
}
.link {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.link:hover {
text-decoration: underline;
}
\ No newline at end of file
......@@ -2,6 +2,7 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import styles from './page.module.css';
export default function LoginPage() {
const [username, setU] = useState('');
......@@ -19,16 +20,33 @@ export default function LoginPage() {
localStorage.setItem('token', json.data.accessToken);
localStorage.setItem('nickname', username);
router.push('/setname'); // 或直接 /chat
router.push('/setname');
};
return (
<main style={{ padding: '2rem', textAlign: 'center' }}>
<h1>登录</h1>
<input placeholder="用户名" value={username} onChange={e => setU(e.target.value)} /><br/>
<input placeholder="密码" type="password" value={password} onChange={e => setP(e.target.value)} /><br/>
<button onClick={login}>登录</button>
<p>没账号?<a href="/signup">去注册</a></p>
</main>
<div className={styles.container}>
<div className={styles.card}>
<h1 className={styles.title}>登录</h1>
<input
className={styles.input}
placeholder="用户名"
value={username}
onChange={e => setU(e.target.value)}
/>
<input
className={styles.input}
placeholder="密码"
type="password"
value={password}
onChange={e => setP(e.target.value)}
/>
<button className={styles.button} onClick={login}>
GET IN
</button>
<p className={styles.footer}>
没有账号?<a href="/signup" className={styles.link}>去注册</a>
</p>
</div>
</div>
);
}
\ No newline at end of file
/*chatroom/src/app/setname/page.module.css*/
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
background-color: #7c66c7;
}
.card {
background: white;
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
text-align: center;
}
.title {
color: #333;
margin-bottom: 1rem;
font-size: 2rem;
}
.subtitle {
color: #666;
margin-bottom: 2rem;
font-size: 1rem;
}
.input {
width: 95%;
padding: 0.75rem;
margin-bottom: 1.5rem;
border: 2px solid #e1e1e1;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s;
text-align: center;
}
.input:focus {
outline: none;
border-color: #667eea;
}
.button {
width: 102%;
padding: 0.75rem;
background: #f3f0ff;
color: rgb(36, 16, 132);
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s;
}
.button:hover {
transform: translateY(-2px);
background: #e6e0ff;
}
.button:active {
transform: translateY(0);
}
.button:disabled {
background: #e0e0e0;
color: #999;
cursor: not-allowed;
transform: none;
}
/* 可爱的装饰元素 */
.emoji {
font-size: 3rem;
margin-bottom: 1rem;
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
\ No newline at end of file
// chatroom/src/app/setname/page.tsx
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import styles from './page.module.css';
export default function NicknamePage() {
export default function SetnamePage() {
const [nickname, setNickname] = useState('');
const [currentNickname, setCurrentNickname] = useState('');
const router = useRouter();
useEffect(() => {
// 检查是否已有昵称
const saved = localStorage.getItem('nickname');
if (saved) {
setCurrentNickname(saved);
setNickname(saved);
}
}, []);
const handleEnterChat = () => {
if (!nickname.trim()) {
alert('请输入昵称');
return;
}
// 将昵称保存在 localStorage 中(暂时作为模拟)
// 将昵称保存在 localStorage 中
localStorage.setItem('nickname', nickname);
// 跳转到聊天室页面
// 跳转到欢迎页面
router.push('/greet');
};
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleEnterChat();
}
};
return (
<main style={{ padding: '2rem', textAlign: 'center' }}>
<h1>设置你的昵称</h1>
<div className={styles.container}>
<div className={styles.card}>
<div className={styles.emoji}>👋</div>
<h1 className={styles.title}>设置昵称</h1>
<p className={styles.subtitle}>
{currentNickname
? `当前昵称:${currentNickname}`
: '请输入一个昵称来开始聊天'}
</p>
<input
className={styles.input}
type="text"
placeholder="请输入昵称"
placeholder="请输入你的昵称"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
style={{
padding: '0.5rem',
fontSize: '1rem',
width: '200px',
margin: '1rem 0',
}}
onKeyPress={handleKeyPress}
maxLength={20}
/>
<br />
<button
className={styles.button}
onClick={handleEnterChat}
style={{
padding: '0.5rem 1rem',
fontSize: '1rem',
cursor: 'pointer',
}}
disabled={!nickname.trim()}
>
进入聊天室
</button>
</main>
</div>
</div>
);
}
\ No newline at end of file
/* chatroom/src/app/signup/page.module.css */
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
background-color: #7c66c7;
}
.card {
background: white;
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
.title {
text-align: center;
color: #333;
margin-bottom: 2rem;
font-size: 2rem;
}
.input {
width: 95%;
padding: 0.75rem;
margin-bottom: 1rem;
border: 2px solid #e1e1e1;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s;
}
.input:focus {
outline: none;
border-color: #667eea;
}
.button {
width: 102%;
padding: 0.75rem;
background: #f3f0ff;
color: rgb(36, 16, 132);
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s;
}
.button:hover {
transform: translateY(-2px);
background: #e6e0ff;
}
.button:active {
transform: translateY(0);
}
.footer {
text-align: center;
margin-top: 1.5rem;
color: #666;
}
.link {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.link:hover {
text-decoration: underline;
}
/* 错误提示样式 */
.error {
color: #d32f2f;
font-size: 0.875rem;
margin-top: -0.5rem;
margin-bottom: 0.5rem;
}
\ No newline at end of file
......@@ -3,37 +3,82 @@
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import styles from './page.module.css';
export default function SignupPage() {
const [username, setU] = useState('');
const [password, setP] = useState('');
const [confirmPwd, setC] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const signup = async () => {
if (!username.trim() || !password.trim()) return alert('用户名和密码不能为空');
if (password !== confirmPwd) return alert('两次密码不一致');
setError('');
if (!username.trim() || !password.trim()) {
setError('用户名和密码不能为空');
return;
}
if (password !== confirmPwd) {
setError('两次密码不一致');
return;
}
const res = await fetch('/api/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const json = await res.json();
if (json.code !== 0) return alert(json.message);
if (json.code !== 0) {
setError(json.message);
return;
}
alert('注册成功!请登录');
router.push('/login');
};
return (
<main style={{ padding: '2rem', textAlign: 'center' }}>
<h1>注册账号</h1>
<input placeholder="用户名" value={username} onChange={e => setU(e.target.value)} /><br />
<input placeholder="密码" type="password" value={password} onChange={e => setP(e.target.value)} /><br />
<input placeholder="确认密码" type="password" value={confirmPwd} onChange={e => setC(e.target.value)} /><br />
<button onClick={signup}>注册</button>
<p>已有账号?<a href="/login">去登录</a></p>
</main>
<div className={styles.container}>
<div className={styles.card}>
<h1 className={styles.title}>注册账号</h1>
<input
className={styles.input}
placeholder="用户名"
value={username}
onChange={e => setU(e.target.value)}
/>
<input
className={styles.input}
placeholder="密码"
type="password"
value={password}
onChange={e => setP(e.target.value)}
/>
<input
className={styles.input}
placeholder="确认密码"
type="password"
value={confirmPwd}
onChange={e => setC(e.target.value)}
/>
{error && <div className={styles.error}>{error}</div>}
<button className={styles.button} onClick={signup}>
注册
</button>
<p className={styles.footer}>
已有账号?<a href="/login" className={styles.link}>去登录</a>
</p>
</div>
</div>
);
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment