宠物部分
Change-Id: I57d26086523c2588778f55873c36c58a2597d6fc
diff --git a/git-filter-repo.exe b/git-filter-repo.exe
new file mode 100644
index 0000000..8537307
--- /dev/null
+++ b/git-filter-repo.exe
@@ -0,0 +1 @@
+Not Found
\ No newline at end of file
diff --git a/src/api/homepage.ts b/src/api/homepage.ts
index 917eaa3..5463039 100644
--- a/src/api/homepage.ts
+++ b/src/api/homepage.ts
@@ -1,5 +1,2 @@
export const getUserMessage="/post/getByUserId"
-export const getUserDetail="/user/detail"
-
-
-// export const getUserMessage = (userId: number) => `/user/message?userId=${userId}`;
\ No newline at end of file
+export const getUserDetail="/user"
\ No newline at end of file
diff --git a/src/api/search.ts b/src/api/search.ts
new file mode 100644
index 0000000..e6a7323
--- /dev/null
+++ b/src/api/search.ts
@@ -0,0 +1 @@
+export const getSearch= '/post/search';
\ No newline at end of file
diff --git a/src/components/pet/index.tsx b/src/components/pet/index.tsx
index c859b68..51871bc 100644
--- a/src/components/pet/index.tsx
+++ b/src/components/pet/index.tsx
@@ -1,91 +1,109 @@
-import React, { useState, useEffect } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
+import styles from './pet.module.css'; // 确保这个路径对
-const petList = [
- {
- id: 'cat01',
- name: '喵喵',
- image: '/pets/cat.png',
- mood: '开心',
- hunger: 50,
- },
- {
- id: 'dog01',
- name: '汪汪',
- image: '/pets/dog.png',
- mood: '饥饿',
- hunger: 70,
- }
-];
+const PetGame: React.FC = () => {
+ const petRef = useRef<HTMLDivElement>(null);
+ const [mood, setMood] = useState(50);
+ const [hunger, setHunger] = useState(50);
-const PetGame = () => {
- const [selectedPetIndex, setSelectedPetIndex] = useState(0);
- const [petStates, setPetStates] = useState(petList);
-
- // 定时每 5 秒减少一点饥饿值
+ // 从 localStorage 恢复状态
useEffect(() => {
- const timer = setInterval(() => {
- setPetStates(prev =>
- prev.map((pet, index) =>
- index === selectedPetIndex
- ? {
- ...pet,
- hunger: Math.min(100, pet.hunger + 2),
- mood: pet.hunger > 80 ? '生气' : pet.mood
- }
- : pet
- )
- );
- }, 5000);
- return () => clearInterval(timer);
- }, [selectedPetIndex]);
+ const saved = localStorage.getItem('petState');
+ if (saved) {
+ const { mood: savedMood, hunger: savedHunger } = JSON.parse(saved);
+ setMood(savedMood);
+ setHunger(savedHunger);
+ }
+ }, []);
+ // 每分钟更新状态并保存
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setMood(prev => {
+ const newMood = Math.min(prev + 1, 100);
+ savePetState(newMood, hunger);
+ return newMood;
+ });
+ setHunger(prev => {
+ const newHunger = Math.max(prev - 1, 0);
+ savePetState(mood, newHunger);
+ return newHunger;
+ });
+ }, 60000); // 每分钟一次
+
+ return () => clearInterval(interval);
+ }, [mood, hunger]);
+
+ // 保存状态到 localStorage
+ const savePetState = (newMood: number, newHunger: number) => {
+ localStorage.setItem('petState', JSON.stringify({ mood: newMood, hunger: newHunger }));
+ };
+
+ // 拖动功能(保留)
+ useEffect(() => {
+ const pet = petRef.current;
+ if (!pet) return;
+
+ let offsetX = 0;
+ let offsetY = 0;
+ let isDragging = false;
+
+ const handleMouseDown = (e: MouseEvent) => {
+ isDragging = true;
+ const rect = pet.getBoundingClientRect();
+ offsetX = e.clientX - rect.left;
+ offsetY = e.clientY - rect.top;
+ };
+
+ const handleMouseMove = (e: MouseEvent) => {
+ if (isDragging && pet) {
+ pet.style.left = `${e.clientX - offsetX}px`;
+ pet.style.top = `${e.clientY - offsetY}px`;
+ }
+ };
+
+ const handleMouseUp = () => {
+ isDragging = false;
+ };
+
+ pet.addEventListener('mousedown', handleMouseDown);
+ document.addEventListener('mousemove', handleMouseMove);
+ document.addEventListener('mouseup', handleMouseUp);
+
+ return () => {
+ pet.removeEventListener('mousedown', handleMouseDown);
+ document.removeEventListener('mousemove', handleMouseMove);
+ document.removeEventListener('mouseup', handleMouseUp);
+ };
+ }, []);
+
+ // 喂食按钮逻辑
const handleFeed = () => {
- setPetStates(prev =>
- prev.map((pet, index) =>
- index === selectedPetIndex
- ? {
- ...pet,
- hunger: Math.max(0, pet.hunger - 20),
- mood: '开心'
- }
- : pet
- )
- );
+ const newHunger = Math.min(hunger + 10, 100);
+ setHunger(newHunger);
+ savePetState(mood, newHunger);
};
- const handlePet = () => {
- setPetStates(prev =>
- prev.map((pet, index) =>
- index === selectedPetIndex
- ? { ...pet, mood: '超级开心' }
- : pet
- )
- );
+ // 互动按钮逻辑
+ const handleInteract = () => {
+ const newMood = Math.min(mood + 10, 100);
+ setMood(newMood);
+ savePetState(newMood, hunger);
};
- const currentPet = petStates[selectedPetIndex];
-
return (
- <div style={{ textAlign: 'center', padding: 20 }}>
- <h2>当前宠物:{currentPet.name}</h2>
- <img
- src={currentPet.image}
- alt={currentPet.name}
- style={{ width: 200, height: 200, cursor: 'pointer' }}
- onClick={handlePet}
- />
- <p>情绪:{currentPet.mood}</p>
- <p>饥饿值:{currentPet.hunger}/100</p>
+ <div className={styles.cardContainer}>
+ <div className={styles.card}>
+ <p className={styles.text}>情绪:{mood}</p>
+ <p className={styles.text}>饥饿值:{hunger}/100</p>
- <button onClick={handleFeed}>🍖 喂食</button>
+ <button className={styles.feedButton} onClick={handleFeed}>
+ 🍖 喂食
+ </button>
- <div style={{ marginTop: 20 }}>
- <h4>切换宠物:</h4>
- {petStates.map((pet, index) => (
- <button key={pet.id} onClick={() => setSelectedPetIndex(index)} style={{ marginRight: 8 }}>
- {pet.name}
- </button>
- ))}
+ <button className={styles.feedButton} onClick={handleInteract}>
+ 🐾 摸摸头 / 玩游戏
+ </button>
</div>
</div>
);
diff --git a/src/components/pet/pet.module.css b/src/components/pet/pet.module.css
new file mode 100644
index 0000000..833729a
--- /dev/null
+++ b/src/components/pet/pet.module.css
@@ -0,0 +1,71 @@
+/* 外部容器:左右布局 */
+.wrapper {
+ background:none;
+ border:none;
+ box-shadow: none;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ padding: 40px;
+ gap: 60px;
+}
+
+/* 宠物区域 */
+.petSection {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.petImage {
+ width: 200px;
+ height: 200px;
+ object-fit: cover;
+ cursor: pointer;
+ margin-bottom: 12px;
+}
+
+/* 卡片区域:紧跟宠物图下方,模拟右下角 */
+.cardContainer {
+ align-self: flex-end; /* 让它贴底部对齐 */
+ width: 280px;
+}
+
+.card {
+
+ border-radius: 16px;
+
+ padding: 20px;
+}
+
+.text {
+ font-size: 16px;
+ margin: 12px 0;
+ color: #333;
+}
+
+.feedButton {
+ width: 100%;
+ padding: 12px;
+ font-size: 16px;
+ background-color: #ff9800;
+ color: white;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ margin: 16px 0;
+ transition: background-color 0.2s;
+}
+
+.feedButton:hover {
+ background-color: #fb8c00;
+}
+
+.switchSection {
+ margin-top: 20px;
+}
+
+.switchTitle {
+ font-weight: 600;
+ font-size: 14px;
+}
diff --git a/src/components/selfStatus/selfStatus.tsx b/src/components/selfStatus/selfStatus.tsx
index 0fd0636..24028ad 100644
--- a/src/components/selfStatus/selfStatus.tsx
+++ b/src/components/selfStatus/selfStatus.tsx
@@ -23,14 +23,19 @@
const downloadPoints = useAppSelector(state => state.user.downloadPoints);
const avatar = useAppSelector(state => state.user.avatar) || 'https://pic.baike.soso.com/ugc/baikepic2/6664/20220301143956-1127285627_png_800_800_370852.jpg/0';
const dispatch = useAppDispatch();
- const { data, refresh } = useApi(() => request.get(getUserInfo), false);
- const dataRef = useRef(data);
- useEffect(() => {
- refresh();
+ const { refresh } = useApi(() => request.get(getUserInfo), false);
+ const fenchData = async () => {
+ const data = await refresh();
+ console.log(data)
+
if (data) {
- dispatch({ type: "user/getUserInfo", payload: data });
+ dispatch({ type: "user/getUserInfo", payload: data.userInfo });
}
- }, [dataRef, dispatch]);
+ }
+ useEffect(() => {
+ fenchData();
+
+ }, [ dispatch]);
function handleAvatarClick(){
nav('/homepage')
diff --git a/src/store/userReducer.ts b/src/store/userReducer.ts
index ab065b7..4334475 100644
--- a/src/store/userReducer.ts
+++ b/src/store/userReducer.ts
@@ -1,5 +1,6 @@
import { createSlice } from '@reduxjs/toolkit';
import { isTokenExpired } from '@/utils/jwt';
+import { getUserInfo } from '@/api/user';
interface UserState {
userId: string;
@@ -34,6 +35,7 @@
state.isLogin = true;
},
getUserInfo: (state, action) => {
+ console.log(action);
state.userId = action.payload.userId;
state.userName = action.payload.userName;
state.role = action.payload.role;
diff --git a/src/views/forum/index.tsx b/src/views/forum/index.tsx
index 13c369f..d2e7c5a 100644
--- a/src/views/forum/index.tsx
+++ b/src/views/forum/index.tsx
@@ -12,7 +12,12 @@
import ad2 from '&/assets/ad2.png'
import { useEffect } from "react";
+
+
export default function Forum() {
+
+
+
useEffect(() => {
// 禁止滚动
document.body.style.overflow = 'hidden';
diff --git a/src/views/homepage/homepage.module.css b/src/views/homepage/homepage.module.css
index fdcc060..cd57580 100644
--- a/src/views/homepage/homepage.module.css
+++ b/src/views/homepage/homepage.module.css
@@ -1,25 +1,9 @@
-/* 主题色变量 */
-/* :root {
- --primary-color: #3498db;
- --primary-hover: #2980b9;
- --secondary-color: #f1c40f;
- --dark-color: #2c3e50;
- --light-color: #ecf0f1;
- --text-color: #333;
- --text-secondary: #7f8c8d;
- --border-color: #ddd;
- } */
-
- /* --bg-color: #2b2b2b;
- --text-color: #f1f1f1;
- --card-bg: #1e1e1e;
- --border-color: #444444; */
-
.container {
min-height: 100vh;
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ overflow: hidden;
}
/* 顶部导航栏 */
@@ -157,8 +141,14 @@
/* 作品区 */
.worksSection {
margin-top: 30px;
+ max-height: 450px;
+ overflow-y: auto;
}
-
+
+ .worksSection::-webkit-scrollbar {
+ width: 6px;
+ }
+
.sectionTitle {
margin: 0 0 20px 0;
color: var(--dark-color);
diff --git a/src/views/homepage/homepage.tsx b/src/views/homepage/homepage.tsx
index ec74187..dab0c0f 100644
--- a/src/views/homepage/homepage.tsx
+++ b/src/views/homepage/homepage.tsx
@@ -1,10 +1,10 @@
import React, { useCallback, useEffect,useState,useRef } from 'react';
import styles from './homepage.module.css';
import { useApi } from '@/hooks/request';
-import { useSelector } from 'react-redux';
+import { useAppSelector } from '@/hooks/store';
import { RootState } from '@/store';
import { useNavigate } from 'react-router';
-import logo from '&/assets/logo.png';
+import Logo from '&/assets/logo.png';
import { getUserMessage } from '@/api/homepage'
import { getUserDetail } from '@/api/homepage'
import request from '@/utils/request'
@@ -12,6 +12,14 @@
import { postUserLogin } from '@/api/auth';
import { debounce } from 'lodash';
import { Q } from 'react-router/dist/development/fog-of-war-CGNKxM4z';
+import head from '&/assets/head.jpg'
+import lv_one from '&/assets/pet/lv_one.png'
+import lv_two from '&/assets/pet/lv_two.png'
+import lv_three from '&/assets/pet/lv_three.png'
+import lv_four from '&/assets/pet/lv_four.png'
+import lv_five from '&/assets/pet/lv_five.png'
+import PetGame from '@/components/pet'
+
interface WorkItem {
postId: number,
@@ -46,25 +54,47 @@
const [works, setWorks] = useState<WorkItem[]>([]);
const worksRef = useRef<WorkItem[]>([]);
-
const navigate = useNavigate();
- const userInfo = useSelector((state: RootState) => state.user);
+ const userInfo = useAppSelector((state: RootState) => state.user);
const userId = userInfo.userId; // 从Redux获取当前用户ID
+ const levelImages: { [key: string]: string } = {
+ 'lv1': lv_one,
+ 'lv2': lv_two,
+ 'lv3': lv_three,
+ 'lv4': lv_four,
+ 'lv5': lv_five
+ };
+
const { data:userdata, loading:userloading, error:usererror, refresh: getUserDetailRefresh } = useApi(
- () => request.get(getUserDetail, {params: {userId}}).then(res => res.data.data),
+ () => request.get(getUserDetail, {params: {userId}}),
+ false
);
- const { data:workdata, loading:workloading, error:workerror, refresh: getUserMessageRefresh } = useApi(
- () => request.get(getUserMessage, { params: { userId } }).then(res => res.data.data),
+ const { data: workdata, loading: workloading, error: workerror, refresh: getUserMessageRefresh } = useApi(
+ () => request.get(`${getUserMessage}/${userId}`), // 注意这里拼接了 userId
false
);
const getUserDetails = debounce(async () => {
try{
- const res = await getUserDetailRefresh({userId});
- console.log("res", res);
- setUserStats(res);
+ // const res = await getUserDetailRefresh({userId});
+ const res = await getUserDetailRefresh();
+ const data_1 = res?.userInfo;
+ const data_2 = res?.statistics;
+ console.log('data', data_1)
+ const formatted: UserStats = {
+ username: data_1.userName,
+ uploadAmount: data_1.uploadAmount,
+ level: data_1.userLevel,
+ likes: data_2.likes,
+ following: data_2.followingCount,
+ followers: data_2.followersCount,
+ mutualFollows: data_2.mutualFollows
+ };
+
+ setUserStats(formatted);
+ console.log('formatted:',formatted)
}catch(error){
console.error('获取用户信息错误', error);
}
@@ -72,15 +102,21 @@
const getUserPost = debounce(async () => {
- try{
- const res = await getUserMessageRefresh({userId});
+ try {
+ const url = `${getUserMessage}/${userId}`; // ✅ 正确的 URL 拼接方式
+ console.log("请求发送:", url);
+
+ // 不需要传 userId 参数了,因为 URL 已包含它
+ const res = await getUserMessageRefresh();
console.log("res", res);
+
worksRef.current = res;
setWorks(res);
- }catch(error) {
+ } catch (error) {
console.error('获取帖子列表错误', error);
}
- },1000) as () => void;
+ }, 1000) as () => void;
+
useEffect(() => {
if (!userId) {
@@ -105,13 +141,13 @@
<div className={styles.userProfile}>
<div className={styles.userHeader}>
<img
- src={userInfo.avatar || '/default-avatar.png'}
+ src={head}
alt="用户头像"
className={styles.userAvatar}
/>
<div className={styles.userInfo}>
- <h2 className={styles.username}>阳菜,放睛!</h2>
- <div className={styles.inviteCode}>邀请码:1314520</div>
+ <h2 className={styles.username}>{userStats?.username}</h2>
+ <div className={styles.inviteCode}>邀请码:123456</div>
<button className={styles.editButton}>编辑主页</button>
</div>
</div>
@@ -129,10 +165,10 @@
<div className={styles.statNumber}>{userStats?.followers ?? '--'}</div>
<div className={styles.statLabel}>粉丝</div>
</div>
- <div className={styles.statItem}>
+ {/* <div className={styles.statItem}>
<div className={styles.statNumber}>{userStats?.mutualFollows ?? '--'}</div>
<div className={styles.statLabel}>互关</div>
- </div>
+ </div> */}
</div>
<div className={styles.userData}>
@@ -168,14 +204,15 @@
<div className={styles.petSection}>
<h3 className={styles.sectionTitle}>宠物图</h3>
<div className={styles.petContainer}>
- <img
- src="/assets/pet-blue-star.png"
- alt="蓝色星星宠物"
- className={styles.petImage}
- />
+ <img
+ src={levelImages[userStats?.level || 'lv5']} // 默认用 '1' 级
+ alt={`等级${userStats?.level}宠物`}
+ className={styles.petImage}
+ />
</div>
+ <PetGame/>
</div>
-
+
</div>
</div>
</div>
diff --git a/src/views/login/login.tsx b/src/views/login/login.tsx
index fef6e32..70b5573 100644
--- a/src/views/login/login.tsx
+++ b/src/views/login/login.tsx
@@ -48,7 +48,16 @@
if (userInfo == null || (userInfo as any).error) {
throw new Error('获取用户信息失败');
}
- dispatch({ type: "user/getUserInfo", payload: userInfo });
+
+ dispatch({ type: "user/getUserInfo", payload: userInfo.userInfo });
+
+ //设置宠物初始化参数
+ localStorage.setItem('petState', JSON.stringify({
+ mood: 30,
+ hunger: 50,
+ timestamp: Date.now()
+ }));
+
nav('/');
} catch (error) {
showErrorMessage('登录失败,请检查您的用户名和密码');
diff --git a/src/views/postDetail/postDetail.tsx b/src/views/postDetail/postDetail.tsx
index d6029a8..202907b 100644
--- a/src/views/postDetail/postDetail.tsx
+++ b/src/views/postDetail/postDetail.tsx
@@ -65,6 +65,8 @@
const commentsRes = await getPostCommentsRefresh();
setComments(commentsRes as CommentResponse[]);
setLoading(false);
+ console.log("postRes:", postRes);
+ console.log("commentsRes:", commentsRes);
};
fetchData();
}, [postId]);
diff --git a/src/views/search/search.module.css b/src/views/search/search.module.css
index 2e1f40a..4475d20 100644
--- a/src/views/search/search.module.css
+++ b/src/views/search/search.module.css
@@ -130,3 +130,28 @@
display: flex;
justify-content: flex-end;
}
+
+
+.secondaryHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ padding: 16px;
+ border-bottom: 1px solid var(--primary-color);
+ background-color: var(--light-color);
+}
+
+.results {
+ margin-top: 20px;
+ padding: 20px;
+}
+
+.resultList {
+ list-style: none;
+ padding: 0;
+}
+
+.resultItem {
+ border-bottom: 1px solid var(--primary-color);
+ padding: 12px 0;
+}
diff --git a/src/views/search/search.tsx b/src/views/search/search.tsx
index 6dba69c..bf252c5 100644
--- a/src/views/search/search.tsx
+++ b/src/views/search/search.tsx
@@ -2,6 +2,7 @@
import React, { useState, useEffect } from 'react';
import styles from './search.module.css';
import { useLocation } from "react-router-dom";
+import { getSearch } from '@/api/search'
interface PostItem {
postId: number;
@@ -32,19 +33,49 @@
11: "table",
},
video: {
- 20: "视频标签1",
- 21: "视频标签2",
- 22: "视频标签3",
+ 20: "chinese",
+ 21: "America",
+ 22: "Japan",
+ 23: "Koera",
+ 24: "Europe",
+ 25: "other",
+ 26: "Short",
+ 27: "plot",
+ 28: "comedy",
+ 29: "love",
+ 30: "action",
+ 31: "terror",
+ 32: "science fiction",
+ 33: "commit a crime",
+ 34: "Thriller",
},
music: {
- 40: "音乐标签1",
- 41: "音乐标签2",
- 42: "音乐标签3",
+ 40: "chinese",
+ 41: "America",
+ 42: "Japan",
+ 43: "Korea",
+ 44: "Europe",
+ 45: "other",
+ 46: "rap",
+ 47: "Electric sound",
+ 48: "Guofeng",
+ 49: "motion",
+ 50: "ballad",
+ 51: "Rock and roll",
+ 52: "classical",
},
software: {
- 60: "软件标签1",
- 61: "软件标签2",
- 62: "软件标签3",
+ 60: "android",
+ 61: "mac",
+ 62: "pc",
+ 63: "ios",
+ 64: "other",
+ 65: "life",
+ 66: "shopping",
+ 67: "video",
+ 68: "music",
+ 69: "read",
+ 70: "system",
},
};
@@ -64,13 +95,34 @@
};
useEffect(() => {
- fetch('/api/posts')
- .then((res) => res.json())
- .then((data) => {
+ if (!keyword) return;
+
+ const requestBody = {
+ keyword,
+ tags: [] // 可以为空
+ };
+
+ console.log("请求发送:", getSearch, requestBody);
+
+
+ fetch(getSearch, {
+ method: 'Get',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(requestBody)
+ })
+ .then(res => res.json())
+ .then((res) => {
+ const data: PostItem[] = res.data;
setPosts(data);
setFilteredPosts(data);
+ })
+ .catch((error) => {
+ console.error('搜索请求失败:', error);
});
- }, []);
+ }, [keyword]);
+
const applyFilters = () => {
let filtered = posts;
@@ -94,6 +146,7 @@
};
return (
+ <div>
<div className={styles.secondaryHeader}>
<div className={styles.leftSection}>
<select
@@ -154,6 +207,26 @@
</div>
</div>
+ <div className={styles.results}>
+ {filteredPosts.length === 0 ? (
+ <p>暂无搜索结果</p>
+ ) : (
+ <ul className={styles.resultList}>
+ {filteredPosts.map((post) => (
+ <li key={post.postId} className={styles.resultItem}>
+ <h3>{post.postTitle || '无标题'}</h3>
+ <p>{post.postContent || '无内容'}</p>
+ <p>类型:{post.postType || '未知'} | 热度评分:{post.hotScore}</p>
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
+
+ </div>
+
+
+
);
};
diff --git a/src/views/upload/upload.tsx b/src/views/upload/upload.tsx
index 1bf096a..19e1197 100644
--- a/src/views/upload/upload.tsx
+++ b/src/views/upload/upload.tsx
@@ -71,7 +71,7 @@
try {
const res = await instance.post(Upload, payload);
- console.log('mock返回内容:', res.code);
+ console.log('后端返回内容:', res.code);
// 判断返回内容是否成功(根据你 mock 接口返回的 code 字段)
if (res.code !== 0) throw new Error('发布失败');