diff --git a/src/features/login/authSlice.tsx b/src/features/login/authSlice.tsx index f427a26..24cdb72 100644 --- a/src/features/login/authSlice.tsx +++ b/src/features/login/authSlice.tsx @@ -15,6 +15,12 @@ export interface User { is_superuser: boolean; last_login: string | null; date_joined: string; + gender: number; + avatar: string; + bio: string; + location: string; + phone: string; + is_set_password: boolean; } // 登录响应类型 @@ -45,7 +51,7 @@ export interface AuthState { isLogin: boolean; error: string | null; loading: boolean; - expiresAtTimestamp: number | null; + expires_at_timestamp: number | null; } export interface ErrorResponse { @@ -71,7 +77,7 @@ const initialState: AuthState = { isLogin: !!localStorage.getItem("access_token"), error: null, loading: false, - expiresIn: localStorage.getItem("expires_at_timestamp") + expires_at_timestamp: localStorage.getItem("expires_at_timestamp") ? parseInt(localStorage.getItem("expires_at_timestamp")!, 10) : null, }; @@ -195,7 +201,7 @@ const authSlice = createSlice({ state.tokenType = null; state.isLogin = false; state.error = null; - state.expiresAtTimestamp = null; + state.expires_at_timestamp = null; // 删除 localStorage 中的 token localStorage.removeItem("access_token"); @@ -219,7 +225,7 @@ const authSlice = createSlice({ state.accessToken = action.payload.access; state.refreshToken = action.payload.refresh; state.tokenType = action.payload.token_type; - state.expiresAtTimestamp = action.payload.expires_at_timestamp; + state.expires_at_timestamp = action.payload.expires_at_timestamp; state.isLogin = true; }) .addCase(emailCodeLogin.rejected, (state, action) => { @@ -275,5 +281,5 @@ export const selectTokenType = (state: { auth: AuthState }) => state.auth.tokenType; export const selectIsLogin = (state: { auth: AuthState }) => state.auth.isLogin; export const selectExpiresAtTimestamp = (state: { auth: AuthState }) => - state.auth.expiresAtTimestamp; + state.auth.expires_at_timestamp; export const selectLoading = (state: { auth: AuthState }) => state.auth.loading; diff --git a/src/pages/Profile/Profile.tsx b/src/pages/Profile/Profile.tsx index fc5783a..6aa9936 100644 --- a/src/pages/Profile/Profile.tsx +++ b/src/pages/Profile/Profile.tsx @@ -20,11 +20,21 @@ import { } from "@ant-design/icons"; import type { MenuProps } from "antd"; import { Button, Menu, Flex, Typography, Steps, Input } from "antd"; -import { Select, Space, Result } from "antd"; +import { Select, Space, Result, Radio } from "antd"; + +import type { CascaderProps } from "antd"; +import { Cascader } from "antd"; +import type { HTMLAriaDataAttributes } from "antd/es/_util/aria-data-attrs"; import { useSelector } from "react-redux"; -import { Tooltip } from "antd"; +import { Divider } from "antd"; + +import { Row, Col, Tag } from "antd"; + +import dayjs from "dayjs"; + +const { TextArea } = Input; import { selectUser, @@ -39,10 +49,128 @@ interface MenuInfo { children?: MenuInfo[]; } -const { Text } = Typography; +const { Text, Title } = Typography; const content = "This is a content."; +import { Timeline, List, Avatar, Card } from "antd"; +import { + ClockCircleOutlined, + FileTextOutlined, + VideoCameraOutlined, + PictureOutlined, + UserOutlined, + EyeOutlined, + LikeOutlined, + CommentOutlined, + ShareAltOutlined, +} from "@ant-design/icons"; + +const { Option } = Select; + +// 模拟时间轴数据 +const timelineItems = [ + { + time: "今天", + count: 8, + color: "green", + }, + { + time: "昨天", + count: 12, + color: "blue", + }, + { + time: "本周", + count: 45, + color: "purple", + }, + { + time: "本月", + count: 128, + color: "orange", + }, + { + time: "更早", + count: 256, + color: "gray", + }, +]; + +// 模拟浏览历史数据 +const historyData = [ + { + id: 1, + title: "React Hooks 完全指南", + type: "article", + author: "张三", + avatar: "https://example.com/avatar1.jpg", + time: "2小时前", + duration: "15分钟阅读", + tags: ["React", "前端", "教程"], + views: 1234, + likes: 56, + comments: 23, + thumbnail: "https://example.com/thumb1.jpg", + }, + { + id: 2, + title: "Ant Design 5.0 新特性解析", + type: "video", + author: "李四", + avatar: "https://example.com/avatar2.jpg", + time: "5小时前", + duration: "25:30", + tags: ["Ant Design", "UI框架"], + views: 2345, + likes: 89, + comments: 45, + thumbnail: "https://example.com/thumb2.jpg", + }, + { + id: 3, + title: "2024年Web开发趋势预测", + type: "article", + author: "王五", + avatar: "https://example.com/avatar3.jpg", + time: "1天前", + duration: "10分钟阅读", + tags: ["趋势", "前端"], + views: 3456, + likes: 123, + comments: 67, + thumbnail: "https://example.com/thumb3.jpg", + }, + { + id: 4, + title: "TypeScript 高级类型实战", + type: "article", + author: "赵六", + avatar: "https://example.com/avatar4.jpg", + time: "2天前", + duration: "20分钟阅读", + tags: ["TypeScript", "进阶"], + views: 4567, + likes: 156, + comments: 89, + thumbnail: "https://example.com/thumb4.jpg", + }, + { + id: 5, + title: "Node.js 性能优化指南", + type: "article", + author: "钱七", + avatar: "https://example.com/avatar5.jpg", + time: "3天前", + duration: "18分钟阅读", + tags: ["Node.js", "后端", "性能"], + views: 5678, + likes: 189, + comments: 102, + thumbnail: "https://example.com/thumb5.jpg", + }, +]; + const Home: React.FC = () => { const user = useSelector(selectUser); const accessToken = useSelector(selectAccessToken); @@ -55,8 +183,101 @@ const Home: React.FC = () => { const [collapsed, setCollapsed] = useState(false); const [MenuKey, setMenuKey] = useState("user_info"); const [change_password_step, setChange_password_step] = useState(0); + + // 是否在编辑用户信息 + const [isEditingNickname, setIsEditingNickname] = useState(false); + const [nickname, setNickname] = useState(user?.username); + + const [isEditingGender, setIsEditingGender] = useState(false); + const [gender, setGender] = useState(user?.gender); + + const [isEditingBio, setIsEditingBio] = useState(false); + const [bio, setBio] = useState(user?.bio); + + const [isEditingLocation, setIsEditingLocation] = useState(false); + const [location, setLocation] = useState(user?.location); + + const [isEditingAvatar, setIsEditingAvatar] = useState(false); + const [avatar, setAvatar] = useState(user?.avatar); + const navigation = useNavigate(); - const [stepItems, setStepItems] = useState([ + + const hidePhoneNumber = (phone) => { + if (!phone) return ""; + return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2"); + }; + + const settingsData = [ + { + title: "密 码", + status: "存在风险,请设置密码", + action: "设置密码", + }, + { + title: "手 机", + status: "182****4099", + action: "修改手机", + }, + { + title: "邮 箱", + status: "存在风险,请绑定邮箱", + action: "绑定邮箱", + }, + { + title: "三方账号", + status: ( + + QQ + 微信 + 微信 + + ), + action: "绑定/解绑", + }, + { + title: "登录记录", + status: "", + action: "查看记录", + }, + { + title: "账号注销", + status: "", + action: "立即注销", + }, + ]; + + const [selectedTime, setSelectedTime] = useState("今天"); + const [filterType, setFilterType] = useState("all"); + + // 根据选择的时间筛选数据 + const filteredData = historyData + .filter((item) => { + if (selectedTime === "今天") return item.time.includes("小时前"); + if (selectedTime === "昨天") return item.time === "1天前"; + if (selectedTime === "本周") + return item.time.includes("天前") && parseInt(item.time) <= 7; + if (selectedTime === "本月") return true; + return true; + }) + .filter((item) => { + if (filterType === "all") return true; + return item.type === filterType; + }); + + const getTypeIcon = (type) => { + switch (type) { + case "article": + return ; + case "video": + return ; + case "image": + return ; + default: + return ; + } + }; + + const [stepItems] = useState([ { title: "发送邮件或者手机号验证码", content: "验证完成或者未完成或者正在验证码", @@ -84,6 +305,63 @@ const Home: React.FC = () => { setCollapsed(!collapsed); }; + const onChange: CascaderProps