update
This commit is contained in:
@@ -28,6 +28,8 @@ export const onRequestGet: PagesFunction<Env> = async (context) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//处理avatar_url
|
||||||
|
userInfo.avatar_url = `${env.NEXT_PUBLIC_CF_IMAGES_DELIVERY_URL}/${userInfo.avatar_url}/public`;
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -8,32 +8,48 @@ export const onRequestPost: PagesFunction<Env> = async (context) => {
|
|||||||
|
|
||||||
// 解析请求体
|
// 解析请求体
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { nickname } = body;
|
const { nickname, avatar_url } = body;
|
||||||
|
|
||||||
// 验证昵称
|
// 构建 SQL 更新语句和参数
|
||||||
if (!nickname || typeof nickname !== 'string' || nickname.length > 32) {
|
let sql = 'UPDATE users SET updated_at = DATETIME(\'now\')';
|
||||||
|
const params = [];
|
||||||
|
|
||||||
|
// 如果有昵称更新
|
||||||
|
if (nickname !== undefined) {
|
||||||
|
if (typeof nickname !== 'string' || nickname.length > 32) {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
message: '昵称格式不正确'
|
message: '昵称格式不正确'
|
||||||
}),
|
}),
|
||||||
{
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||||
status: 400,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
sql += ', nickname = ?';
|
||||||
|
params.push(nickname);
|
||||||
|
}
|
||||||
|
|
||||||
// 更新数据库中的昵称
|
// 如果有头像更新
|
||||||
const db = env.bgdb;
|
if (avatar_url !== undefined) {
|
||||||
const result = await db.prepare(`
|
if (typeof avatar_url !== 'string') {
|
||||||
UPDATE users
|
return new Response(
|
||||||
SET nickname = ?,
|
JSON.stringify({
|
||||||
updated_at = DATETIME('now')
|
success: false,
|
||||||
WHERE id = ?
|
message: '头像URL格式不正确'
|
||||||
`).bind(nickname, data.user.userId).run();
|
}),
|
||||||
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sql += ', avatar_url = ?';
|
||||||
|
params.push(avatar_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 WHERE 条件
|
||||||
|
sql += ' WHERE id = ?';
|
||||||
|
params.push(data.user.userId);
|
||||||
|
|
||||||
|
// 执行更新
|
||||||
|
const result = await env.bgdb.prepare(sql).bind(...params).run();
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return new Response(
|
return new Response(
|
||||||
@@ -51,12 +67,13 @@ export const onRequestPost: PagesFunction<Env> = async (context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取更新后的用户信息
|
// 获取更新后的用户信息
|
||||||
const userInfo = await db.prepare(`
|
const userInfo = await env.bgdb.prepare(`
|
||||||
SELECT id, phone, nickname, avatar_url, status
|
SELECT id, phone, nickname, avatar_url, status
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).bind(data.user.userId).first();
|
`).bind(data.user.userId).first();
|
||||||
|
//处理avatar_url
|
||||||
|
userInfo.avatar_url = `${env.NEXT_PUBLIC_CF_IMAGES_DELIVERY_URL}/${userInfo.avatar_url}/public`;
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
31
package-lock.json
generated
31
package-lock.json
generated
@@ -40,7 +40,8 @@
|
|||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-scrollbar-hide": "^2.0.0",
|
"tailwind-scrollbar-hide": "^2.0.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"wrangler": "^3.112.0"
|
"wrangler": "^3.112.0",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shadcn/ui": "^0.0.4",
|
"@shadcn/ui": "^0.0.4",
|
||||||
@@ -8158,6 +8159,34 @@
|
|||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/zustand": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=18.0.0",
|
||||||
|
"immer": ">=9.0.6",
|
||||||
|
"react": ">=18.0.0",
|
||||||
|
"use-sync-external-store": ">=1.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zwitch": {
|
"node_modules/zwitch": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",
|
||||||
|
|||||||
@@ -40,7 +40,8 @@
|
|||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-scrollbar-hide": "^2.0.0",
|
"tailwind-scrollbar-hide": "^2.0.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"wrangler": "^3.112.0"
|
"wrangler": "^3.112.0",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shadcn/ui": "^0.0.4",
|
"@shadcn/ui": "^0.0.4",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { SharePoster } from '@/pages/chat/components/SharePoster';
|
|||||||
import { MembersManagement } from '@/pages/chat/components/MembersManagement';
|
import { MembersManagement } from '@/pages/chat/components/MembersManagement';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
import { AdBanner, AdBannerMobile } from './AdSection';
|
import { AdBanner, AdBannerMobile } from './AdSection';
|
||||||
|
import { useUserStore } from '@/store/userStore';
|
||||||
// 使用本地头像数据,避免外部依赖
|
// 使用本地头像数据,避免外部依赖
|
||||||
const getAvatarData = (name: string) => {
|
const getAvatarData = (name: string) => {
|
||||||
const colors = ['#1abc9c', '#3498db', '#9b59b6', '#f1c40f', '#e67e22'];
|
const colors = ['#1abc9c', '#3498db', '#9b59b6', '#f1c40f', '#e67e22'];
|
||||||
@@ -139,6 +140,8 @@ const KaTeXStyle = () => (
|
|||||||
|
|
||||||
|
|
||||||
const ChatUI = () => {
|
const ChatUI = () => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
//获取url参数
|
//获取url参数
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const id = urlParams.get('id')? parseInt(urlParams.get('id')!) : 0;
|
const id = urlParams.get('id')? parseInt(urlParams.get('id')!) : 0;
|
||||||
@@ -202,8 +205,14 @@ const ChatUI = () => {
|
|||||||
const allNames = groupAiCharacters.map(character => character.name);
|
const allNames = groupAiCharacters.map(character => character.name);
|
||||||
allNames.push('user');
|
allNames.push('user');
|
||||||
setAllNames(allNames);
|
setAllNames(allNames);
|
||||||
|
|
||||||
|
const response1 = await request('/api/user/info');
|
||||||
|
const userInfo = await response1.json();
|
||||||
|
//设置store
|
||||||
|
userStore.setUserInfo(userInfo.data);
|
||||||
|
|
||||||
setUsers([
|
setUsers([
|
||||||
{ id: 1, name: "我" },
|
{ id: 1, name: userInfo.data.nickname, avatar: userInfo.data.avatar_url },
|
||||||
...groupAiCharacters
|
...groupAiCharacters
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -215,7 +224,7 @@ const ChatUI = () => {
|
|||||||
initData();
|
initData();
|
||||||
// 标记为已初始化
|
// 标记为已初始化
|
||||||
isInitialized.current = true;
|
isInitialized.current = true;
|
||||||
}, []); // 依赖数组保持为空
|
}, [userStore]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
@@ -235,6 +244,16 @@ const ChatUI = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 添加一个新的 useEffect 来监听 userStore.userInfo 的变化
|
||||||
|
useEffect(() => {
|
||||||
|
if (userStore.userInfo && users.length > 0) {
|
||||||
|
setUsers(prev => [
|
||||||
|
{ id: 1, name: userStore.userInfo.nickname, avatar: userStore.userInfo.avatar_url? userStore.userInfo.avatar_url : null },
|
||||||
|
...prev.slice(1) // 保留其他 AI 角色
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, [userStore.userInfo]); // 当 userInfo 变化时更新 users
|
||||||
|
|
||||||
// 4. 工具函数
|
// 4. 工具函数
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
@@ -290,7 +309,7 @@ const ChatUI = () => {
|
|||||||
// 构建历史消息数组
|
// 构建历史消息数组
|
||||||
let messageHistory = messages.map(msg => ({
|
let messageHistory = messages.map(msg => ({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: msg.sender.name == "我" ? 'user:' + msg.content : msg.sender.name + ':' + msg.content,
|
content: msg.sender.name == userStore.userInfo.nickname ? 'user:' + msg.content : msg.sender.name + ':' + msg.content,
|
||||||
name: msg.sender.name
|
name: msg.sender.name
|
||||||
}));
|
}));
|
||||||
let selectedGroupAiCharacters = groupAiCharacters;
|
let selectedGroupAiCharacters = groupAiCharacters;
|
||||||
@@ -464,34 +483,6 @@ const ChatUI = () => {
|
|||||||
//进行跳转到?id=index
|
//进行跳转到?id=index
|
||||||
window.location.href = `?id=${index}`;
|
window.location.href = `?id=${index}`;
|
||||||
return;
|
return;
|
||||||
/*
|
|
||||||
//跳转后,关闭当前页面
|
|
||||||
setSelectedGroupIndex(index);
|
|
||||||
const newGroup = groups[index];
|
|
||||||
setGroup(newGroup);
|
|
||||||
|
|
||||||
// 重新生成当前群组的 AI 角色,并按照 members 数组的顺序排序
|
|
||||||
const newGroupAiCharacters = generateAICharacters(newGroup.name)
|
|
||||||
.filter(character => newGroup.members.includes(character.id))
|
|
||||||
.sort((a, b) => {
|
|
||||||
return newGroup.members.indexOf(a.id) - newGroup.members.indexOf(b.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新用户列表
|
|
||||||
setUsers([
|
|
||||||
{ id: 1, name: "我" },
|
|
||||||
...newGroupAiCharacters
|
|
||||||
]);
|
|
||||||
setIsGroupDiscussionMode(newGroup.isGroupDiscussionMode);
|
|
||||||
|
|
||||||
// 重置消息
|
|
||||||
setMessages([]);
|
|
||||||
|
|
||||||
// 可选:关闭侧边栏(在移动设备上)
|
|
||||||
if (window.innerWidth < 768) {
|
|
||||||
setSidebarOpen(false);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -579,8 +570,8 @@ const ChatUI = () => {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<div key={message.id}
|
<div key={message.id}
|
||||||
className={`flex items-start gap-2 ${message.sender.name === "我" ? "justify-end" : ""}`}>
|
className={`flex items-start gap-2 ${message.sender.name === userStore.userInfo.nickname ? "justify-end" : ""}`}>
|
||||||
{message.sender.name !== "我" && (
|
{message.sender.name !== userStore.userInfo.nickname && (
|
||||||
<Avatar>
|
<Avatar>
|
||||||
{'avatar' in message.sender && message.sender.avatar ? (
|
{'avatar' in message.sender && message.sender.avatar ? (
|
||||||
<AvatarImage src={message.sender.avatar} className="w-10 h-10" />
|
<AvatarImage src={message.sender.avatar} className="w-10 h-10" />
|
||||||
@@ -591,16 +582,16 @@ const ChatUI = () => {
|
|||||||
)}
|
)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
)}
|
)}
|
||||||
<div className={message.sender.name === "我" ? "text-right" : ""}>
|
<div className={message.sender.name === userStore.userInfo.nickname ? "text-right" : ""}>
|
||||||
<div className="text-sm text-gray-500">{message.sender.name}</div>
|
<div className="text-sm text-gray-500">{message.sender.name}</div>
|
||||||
<div className={`mt-1 p-3 rounded-lg shadow-sm chat-message ${
|
<div className={`mt-1 p-3 rounded-lg shadow-sm chat-message ${
|
||||||
message.sender.name === "我" ? "bg-blue-500 text-white text-left" : "bg-white"
|
message.sender.name === userStore.userInfo.nickname ? "bg-blue-500 text-white text-left" : "bg-white"
|
||||||
}`}>
|
}`}>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
rehypePlugins={[rehypeKatex]}
|
rehypePlugins={[rehypeKatex]}
|
||||||
className={`prose dark:prose-invert max-w-none ${
|
className={`prose dark:prose-invert max-w-none ${
|
||||||
message.sender.name === "我" ? "text-white [&_*]:text-white" : ""
|
message.sender.name === userStore.userInfo.nickname ? "text-white [&_*]:text-white" : ""
|
||||||
}
|
}
|
||||||
[&_h2]:py-1
|
[&_h2]:py-1
|
||||||
[&_h2]:m-0
|
[&_h2]:m-0
|
||||||
@@ -638,7 +629,7 @@ const ChatUI = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{message.sender.name === "我" && (
|
{message.sender.name === userStore.userInfo.nickname && (
|
||||||
<Avatar>
|
<Avatar>
|
||||||
{'avatar' in message.sender && message.sender.avatar ? (
|
{'avatar' in message.sender && message.sender.avatar ? (
|
||||||
<AvatarImage src={message.sender.avatar} className="w-10 h-10" />
|
<AvatarImage src={message.sender.avatar} className="w-10 h-10" />
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { MessageSquareIcon, PlusCircleIcon, MenuIcon, PanelLeftCloseIcon } from
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import GitHubButton from 'react-github-btn';
|
import GitHubButton from 'react-github-btn';
|
||||||
import '@fontsource/audiowide';
|
import '@fontsource/audiowide';
|
||||||
//import { groups } from "@/config/groups";
|
|
||||||
import { AdSection } from './AdSection';
|
import { AdSection } from './AdSection';
|
||||||
import { UserSection } from './UserSection';
|
import { UserSection } from './UserSection';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Edit2Icon, LogOutIcon, CheckIcon, XIcon } from 'lucide-react';
|
import { Edit2Icon, LogOutIcon, CheckIcon, XIcon } from 'lucide-react';
|
||||||
import { request } from '@/utils/request';
|
import { request } from '@/utils/request';
|
||||||
|
import { useUserStore } from '@/store/userStore';
|
||||||
|
|
||||||
|
|
||||||
interface UserSectionProps {
|
interface UserSectionProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -15,30 +17,12 @@ interface UserInfo {
|
|||||||
|
|
||||||
export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [newNickname, setNewNickname] = useState('');
|
const [newNickname, setNewNickname] = useState('');
|
||||||
|
const [uploadingAvatar, setUploadingAvatar] = useState(false);
|
||||||
useEffect(() => {
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const fetchUserInfo = async () => {
|
const userStore = useUserStore();
|
||||||
if (!isOpen) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
const response = await request('/api/user/info');
|
|
||||||
const { data } = await response.json();
|
|
||||||
console.log('data', data);
|
|
||||||
setUserInfo(data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取用户信息失败:', error);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchUserInfo();
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
// 添加更新昵称的函数
|
// 添加更新昵称的函数
|
||||||
const updateNickname = async () => {
|
const updateNickname = async () => {
|
||||||
@@ -51,7 +35,10 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
|||||||
body: JSON.stringify({ nickname: newNickname.trim() })
|
body: JSON.stringify({ nickname: newNickname.trim() })
|
||||||
});
|
});
|
||||||
const { data } = await response.json();
|
const { data } = await response.json();
|
||||||
setUserInfo(data);
|
console.log('更新用户信息', data);
|
||||||
|
//更新用户信息
|
||||||
|
userStore.setUserInfo(data);
|
||||||
|
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新昵称失败:', error);
|
console.error('更新昵称失败:', error);
|
||||||
@@ -60,6 +47,45 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 添加上传头像的处理函数
|
||||||
|
const handleAvatarUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setUploadingAvatar(true);
|
||||||
|
|
||||||
|
// 1. 首先从后端获取上传 URL
|
||||||
|
const response = await request('/api/user/upload', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
const { uploadURL, id } = await response.json();
|
||||||
|
|
||||||
|
// 2. 上传图片到 Cloudflare Images
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file); // 使用 'file' 作为字段名
|
||||||
|
|
||||||
|
await fetch(uploadURL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新用户头像信息
|
||||||
|
const updateResponse = await request('/api/user/update', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ avatar_url: id })
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await updateResponse.json();
|
||||||
|
userStore.setUserInfo(data);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('上传头像失败:', error);
|
||||||
|
} finally {
|
||||||
|
setUploadingAvatar(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -73,13 +99,41 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
|||||||
>
|
>
|
||||||
{/* 头像区域 */}
|
{/* 头像区域 */}
|
||||||
<div className="relative group cursor-pointer">
|
<div className="relative group cursor-pointer">
|
||||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-orange-400 to-rose-400 flex items-center justify-center shadow-sm">
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
className="hidden"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleAvatarUpload}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="w-10 h-10 rounded-full bg-gradient-to-br from-orange-400 to-rose-400 flex items-center justify-center shadow-sm overflow-hidden"
|
||||||
|
onClick={() => !uploadingAvatar && fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
|
{uploadingAvatar ? (
|
||||||
|
<div className="flex items-center justify-center w-full h-full bg-black/20">
|
||||||
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : userStore.userInfo?.avatar_url ? (
|
||||||
|
<img
|
||||||
|
src={`${userStore.userInfo.avatar_url}`}
|
||||||
|
alt="avatar"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<span className="text-base font-medium text-white">
|
<span className="text-base font-medium text-white">
|
||||||
{isLoading ? '...' : userInfo?.nickname?.[0] || '我'}
|
{userStore.userInfo?.nickname?.[0] || '我'}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 头像hover效果 */}
|
{/* 头像hover效果 */}
|
||||||
<div className="absolute inset-0 rounded-full bg-black/40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute inset-0 rounded-full bg-black/40 flex items-center justify-center transition-opacity",
|
||||||
|
uploadingAvatar ? 'opacity-0' : 'opacity-0 group-hover:opacity-100'
|
||||||
|
)}
|
||||||
|
onClick={() => !uploadingAvatar && fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
<Edit2Icon className="w-4 h-4 text-white" />
|
<Edit2Icon className="w-4 h-4 text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +148,7 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
|||||||
value={newNickname}
|
value={newNickname}
|
||||||
onChange={(e) => setNewNickname(e.target.value)}
|
onChange={(e) => setNewNickname(e.target.value)}
|
||||||
className="text-sm px-2 border rounded-md w-full"
|
className="text-sm px-2 border rounded-md w-full"
|
||||||
placeholder={userInfo?.nickname || '输入新昵称'}
|
placeholder={userStore.userInfo?.nickname || '输入新昵称'}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') updateNickname();
|
if (e.key === 'Enter') updateNickname();
|
||||||
if (e.key === 'Escape') setIsEditing(false);
|
if (e.key === 'Escape') setIsEditing(false);
|
||||||
@@ -121,7 +175,7 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className="text-sm font-semibold group-hover:text-primary transition-colors">
|
<span className="text-sm font-semibold group-hover:text-primary transition-colors">
|
||||||
{isLoading ? '加载中...' : userInfo?.nickname || '游客用户'}
|
{isLoading ? '加载中...' : userStore.userInfo?.nickname || '游客用户'}
|
||||||
</span>
|
</span>
|
||||||
<Edit2Icon
|
<Edit2Icon
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -130,7 +184,7 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
|||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
setNewNickname(userInfo?.nickname || '');
|
setNewNickname(userStore.userInfo?.nickname || '');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user