支持用户换头像
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
2. 登录 [Cloudflare Dashboard](https://dash.cloudflare.com/)
|
||||
- 进入Workers > Workers & Pages 页面
|
||||
- 点击 "创建" 按钮
|
||||
- 选择 "Pages">"Connect to Git"
|
||||
- 选择 "Pages">"Connect to Git (导入现有 Git 存储库)"
|
||||
|
||||
3. 配置部署选项
|
||||
- 选择你 fork 的仓库
|
||||
@@ -141,7 +141,7 @@ APIKEY|对应角色|服务商|申请地址|
|
||||
## 贡献指南
|
||||
|
||||
欢迎提交 Pull Request 或提出 Issue。
|
||||
加官方微信:`botgroup` 拉你进微信群
|
||||
加官方微信号:`botgroup` 拉你进微信群。
|
||||
当然也可以加共建QQ群交流:922322461(群号)
|
||||
|
||||
## 跪谢赞助商ORZ
|
||||
|
||||
@@ -20,5 +20,5 @@ export const onRequestPost: PagesFunction<Env> = async (context) =>{
|
||||
const data1 = await response.json();
|
||||
console.log('上传头像', data1);
|
||||
//下发image前缀
|
||||
return Response.json(data1.result);
|
||||
return Response.json({data :data1.result});
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
<link rel="icon" type="image/x-icon" href="/chat_bubble.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<title>AI机器人群聊</title>
|
||||
<!-- 运行时配置文件 - 在应用加载前加载配置 -->
|
||||
<script src="/config.js"></script>
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
|
||||
6
public/config.js
Normal file
6
public/config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// 运行时配置文件
|
||||
// 部署时可以直接修改这个文件来改变配置,无需重新编译
|
||||
window.APP_CONFIG = {
|
||||
// 权限验证开关:'1' 开启,'0' 关闭
|
||||
AUTH_ACCESS: '1',
|
||||
};
|
||||
16
src/App.tsx
16
src/App.tsx
@@ -1,10 +1,24 @@
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { Toaster } from 'sonner';
|
||||
import { router } from './routes';
|
||||
|
||||
function App() {
|
||||
console.log("App rendering"); // 添加日志
|
||||
return (
|
||||
<RouterProvider router={router} />
|
||||
<>
|
||||
<RouterProvider router={router} />
|
||||
<Toaster
|
||||
position="top-center"
|
||||
richColors
|
||||
toastOptions={{
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
},
|
||||
}}
|
||||
theme="light"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@ import { Navigate, useLocation } from 'react-router-dom';
|
||||
|
||||
export default function AuthGuard({ children }) {
|
||||
//判断环境变量中的AUTH_ACCESS是否为1开启权限校验
|
||||
const authAccess = import.meta.env.AUTH_ACCESS;
|
||||
//const authAccess = import.meta.env.AUTH_ACCESS;
|
||||
const authAccess = window.APP_CONFIG?.AUTH_ACCESS ||
|
||||
import.meta.env.AUTH_ACCESS ||
|
||||
'0';
|
||||
console.log(authAccess, 'authAccess');
|
||||
if (authAccess === '1') {
|
||||
const location = useLocation();
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
150
src/index.css
150
src/index.css
@@ -2,143 +2,63 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* ... */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Audiowide:wght@400&display=swap');
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
|
||||
--background: 0 0% 100%;
|
||||
|
||||
--foreground: 0 0% 3.9%;
|
||||
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--primary: 0 0% 9%;
|
||||
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 9% 11%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
|
||||
--accent: 0 0% 96.1%;
|
||||
|
||||
--accent-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
|
||||
--input: 0 0% 89.8%;
|
||||
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
|
||||
--chart-2: 173 58% 39%;
|
||||
|
||||
--chart-3: 197 37% 24%;
|
||||
|
||||
--chart-4: 43 74% 66%;
|
||||
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
--radius: 0.5rem
|
||||
;
|
||||
|
||||
--sidebar-background: 0 0% 98%;
|
||||
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
|
||||
--sidebar-border: 220 13% 91%;
|
||||
|
||||
--sidebar-ring: 217.2 91.2% 59.8%}
|
||||
.dark {
|
||||
|
||||
--background: 0 0% 3.9%;
|
||||
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--card: 0 0% 3.9%;
|
||||
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 0 0% 3.9%;
|
||||
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
|
||||
--input: 0 0% 14.9%;
|
||||
|
||||
--ring: 0 0% 83.1%;
|
||||
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
|
||||
--chart-2: 160 60% 45%;
|
||||
|
||||
--chart-3: 30 80% 55%;
|
||||
|
||||
--chart-4: 280 65% 60%;
|
||||
|
||||
--chart-5: 340 75% 55%
|
||||
;
|
||||
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
|
||||
--sidebar-ring: 217.2 91.2% 59.8%}
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -150,6 +70,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[data-sonner-toaster] [data-type="error"] {
|
||||
background-color: #fef2f2 !important;
|
||||
border-color: #fecaca !important;
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
|
||||
[data-sonner-toaster] [data-icon] {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
display: inline-block;
|
||||
animation: blink 1s step-end infinite;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Edit2Icon, LogOutIcon, CheckIcon, XIcon } from 'lucide-react';
|
||||
import { request } from '@/utils/request';
|
||||
import { useUserStore } from '@/store/userStore';
|
||||
import { getAvatarData } from '@/utils/avatar';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface UserSectionProps {
|
||||
isOpen: boolean;
|
||||
@@ -16,6 +16,7 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [newNickname, setNewNickname] = useState('');
|
||||
|
||||
const [uploadingAvatar, setUploadingAvatar] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const userStore = useUserStore();
|
||||
@@ -31,13 +32,12 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
||||
body: JSON.stringify({ nickname: newNickname.trim() })
|
||||
});
|
||||
const { data } = await response.json();
|
||||
console.log('更新用户信息', data);
|
||||
//更新用户信息
|
||||
userStore.setUserInfo(data);
|
||||
|
||||
toast.success('更新昵称成功');
|
||||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error('更新昵称失败:', error);
|
||||
toast.error(error instanceof Error ? error.message : '更新昵称失败');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -55,7 +55,10 @@ export const UserSection: React.FC<UserSectionProps> = ({ isOpen }) => {
|
||||
const response = await request('/api/user/upload', {
|
||||
method: 'POST'
|
||||
});
|
||||
const { uploadURL, id } = await response.json();
|
||||
const { data: uploadData } = await response.json();
|
||||
const uploadURL = uploadData.uploadURL;
|
||||
const id = uploadData.id;
|
||||
|
||||
|
||||
// 2. 上传图片到 Cloudflare Images
|
||||
const formData = new FormData();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { toast } from 'sonner';
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
interface PhoneLoginProps {
|
||||
onLogin: (phone: string, code: string) => void;
|
||||
}
|
||||
@@ -15,13 +17,13 @@ const PhoneLogin: React.FC<PhoneLoginProps> = ({ handleLoginSuccess }) => {
|
||||
// 发送验证码
|
||||
const handleSendCode = async () => {
|
||||
if (!phone || !/^1[3-9]\d{9}$/.test(phone)) {
|
||||
alert('请输入正确的手机号');
|
||||
toast.error('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
//setIsLoading(true);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch(`/api/sendcode`, {
|
||||
const response = await request(`/api/sendcode`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -33,6 +35,8 @@ const PhoneLogin: React.FC<PhoneLoginProps> = ({ handleLoginSuccess }) => {
|
||||
throw new Error('发送验证码失败');
|
||||
}
|
||||
|
||||
toast.success('验证码已发送');
|
||||
|
||||
// 开始倒计时
|
||||
setCountdown(60);
|
||||
const timer = setInterval(() => {
|
||||
@@ -47,7 +51,7 @@ const PhoneLogin: React.FC<PhoneLoginProps> = ({ handleLoginSuccess }) => {
|
||||
|
||||
} catch (error) {
|
||||
console.error('发送验证码失败:', error);
|
||||
alert('发送验证码失败,请重试');
|
||||
toast.error('发送验证码失败,请重试');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -58,7 +62,7 @@ const PhoneLogin: React.FC<PhoneLoginProps> = ({ handleLoginSuccess }) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!phone || !code) {
|
||||
alert('请输入手机号和验证码');
|
||||
toast.error('请输入手机号和验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,25 +70,17 @@ const PhoneLogin: React.FC<PhoneLoginProps> = ({ handleLoginSuccess }) => {
|
||||
try {
|
||||
const response = await request(`/api/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ phone, code }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || '登录失败');
|
||||
}
|
||||
|
||||
//执行登录成功回调
|
||||
handleLoginSuccess(data.data.token);
|
||||
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
alert(error instanceof Error ? error.message : '登录失败,请重试');
|
||||
toast.error(error instanceof Error ? error.message : '登录失败,请重试');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -98,7 +94,6 @@ const PhoneLogin: React.FC<PhoneLoginProps> = ({ handleLoginSuccess }) => {
|
||||
<span style={{fontFamily: 'Audiowide, system-ui', color: '#ff6600'}} className="text-3xl ml-2">botgroup.chat</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="text-gray-500 mb-4 text-center">
|
||||
仅支持中国大陆手机号登录
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// 优先使用运行时配置,降级到构建时配置
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
|
||||
export async function request(url: string, options: RequestInit = {}) {
|
||||
const token = localStorage.getItem('token');
|
||||
@@ -26,7 +27,9 @@ export async function request(url: string, options: RequestInit = {}) {
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Request failed');
|
||||
// 解析错误信息用于抛出异常
|
||||
const data = await response.json();
|
||||
throw new Error(data.message || 'Request failed');
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
Reference in New Issue
Block a user