Merge branch 'light_v3'
This commit is contained in:
@@ -9,6 +9,7 @@ module.exports = {
|
||||
compiler: 'webpack5',
|
||||
}]
|
||||
],
|
||||
|
||||
plugins: [
|
||||
[
|
||||
"import",
|
||||
@@ -46,7 +47,12 @@ module.exports = {
|
||||
// 自动加载 css 样式文件
|
||||
// customStyleName: (name) => `@nutui/nutui-react-taro/dist/es/packages/${name.toLowerCase()}/style-jrkf/css`
|
||||
},
|
||||
|
||||
|
||||
'nutui-react',
|
||||
],
|
||||
|
||||
['transform-remove-console', { exclude: ['error', 'warn'] }],
|
||||
['@babel/plugin-transform-runtime', { corejs: false }]
|
||||
],
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
],
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.28.3",
|
||||
"@babel/runtime": "^7.21.5",
|
||||
"@nutui/nutui-react-taro": "^2.6.14",
|
||||
"@tarojs/components": "4.1.5",
|
||||
@@ -53,6 +54,7 @@
|
||||
"@tarojs/runtime": "4.1.5",
|
||||
"@tarojs/shared": "4.1.5",
|
||||
"@tarojs/taro": "4.1.5",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"dayjs": "^1.11.13",
|
||||
"qweather-icons": "^1.8.0",
|
||||
"react": "^18.0.0",
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
{
|
||||
"miniprogramRoot": "dist/",
|
||||
"projectname": "playBallTogether",
|
||||
"description": "playBallTogether",
|
||||
"appid": "wx815b533167eb7b53",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"es6": true,
|
||||
"enhance": true,
|
||||
"postcss": false,
|
||||
"preloadBackgroundData": false,
|
||||
"minified": false,
|
||||
"newFeature": true,
|
||||
"coverView": true,
|
||||
"nodeModules": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": false,
|
||||
"scopeDataCheck": false,
|
||||
"uglifyFileName": false,
|
||||
"checkInvalidKey": true,
|
||||
"checkSiteMap": true,
|
||||
"uploadWithSourceMap": true,
|
||||
"compileHotReLoad": false,
|
||||
"useMultiFrameRuntime": true,
|
||||
"useApiHook": true,
|
||||
"useApiHostProcess": false,
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"enableEngineNative": false,
|
||||
"useIsolateContext": true,
|
||||
"useCompilerModule": false,
|
||||
"userConfirmedUseCompilerModuleSwitch": false,
|
||||
"userConfirmedBundleSwitch": false,
|
||||
"packNpmManually": false,
|
||||
"packNpmRelationList": [],
|
||||
"minifyWXSS": true,
|
||||
"minifyWXML": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"simulatorType": "wechat",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"condition": {}
|
||||
}
|
||||
"miniprogramRoot": "dist/",
|
||||
"projectname": "playBallTogether",
|
||||
"description": "playBallTogether",
|
||||
"appid": "wx815b533167eb7b53",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"es6": true,
|
||||
"enhance": true,
|
||||
"postcss": true,
|
||||
"preloadBackgroundData": false,
|
||||
"minified": true,
|
||||
"newFeature": true,
|
||||
"coverView": true,
|
||||
"nodeModules": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": false,
|
||||
"scopeDataCheck": false,
|
||||
"uglifyFileName": false,
|
||||
"checkInvalidKey": true,
|
||||
"checkSiteMap": true,
|
||||
"uploadWithSourceMap": true,
|
||||
"compileHotReLoad": false,
|
||||
"useMultiFrameRuntime": true,
|
||||
"useApiHook": true,
|
||||
"useApiHostProcess": false,
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"enableEngineNative": false,
|
||||
"useIsolateContext": true,
|
||||
"useCompilerModule": false,
|
||||
"userConfirmedUseCompilerModuleSwitch": false,
|
||||
"userConfirmedBundleSwitch": false,
|
||||
"packNpmManually": false,
|
||||
"packNpmRelationList": [],
|
||||
"minifyWXSS": true,
|
||||
"minifyWXML": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"simulatorType": "wechat",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"condition": {}
|
||||
}
|
||||
@@ -3,12 +3,12 @@
|
||||
"projectname": "playBallTogether",
|
||||
"condition": {},
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"urlCheck": false,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"autoAudits": true,
|
||||
"useApiHook": true,
|
||||
"useApiHostProcess": true,
|
||||
"showShadowRootInWxmlPanel": false,
|
||||
|
||||
@@ -54,6 +54,8 @@ export default defineAppConfig({
|
||||
root: 'other_pages',
|
||||
pages: [
|
||||
"message/index",
|
||||
"comment_reply/index", // 收到的评论和回复
|
||||
"new_follow/index", // 新增关注
|
||||
"favorites/index", // 收藏页
|
||||
"ntrp-evaluate/index", // NTRP评估页
|
||||
],
|
||||
@@ -84,4 +86,5 @@ export default defineAppConfig({
|
||||
provider: "wx76a9a06e5b4e693e",
|
||||
},
|
||||
},
|
||||
"lazyCodeLoading": "requiredComponents"
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ReactNode } from "react";
|
||||
import "./nutui-theme.scss";
|
||||
import "./app.scss";
|
||||
import "qweather-icons/font/qweather-icons.css";
|
||||
import "./scss/qweather-icons/qweather-icons.css";
|
||||
import { useGlobalStore } from "./store/global";
|
||||
|
||||
interface AppProps {
|
||||
|
||||
33
src/components/EmptyState/index.scss
Normal file
33
src/components/EmptyState/index.scss
Normal file
@@ -0,0 +1,33 @@
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 124px 16px;
|
||||
min-height: 400px;
|
||||
|
||||
.empty-icon {
|
||||
width: 221px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
23
src/components/EmptyState/index.tsx
Normal file
23
src/components/EmptyState/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { View, Text, Image } from "@tarojs/components";
|
||||
import "./index.scss";
|
||||
|
||||
interface EmptyStateProps {
|
||||
text?: string;
|
||||
icon?: any; // 图片资源
|
||||
}
|
||||
|
||||
const EmptyState = ({
|
||||
text = "暂无数据",
|
||||
icon = require("@/static/message/emi.svg")
|
||||
}: EmptyStateProps) => {
|
||||
return (
|
||||
<View className="empty-state">
|
||||
<View className="empty-icon">
|
||||
<Image className="img" src={icon} />
|
||||
</View>
|
||||
<Text className="empty-text">{text}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyState;
|
||||
@@ -23,6 +23,7 @@ import FollowUserCard from './FollowUserCard/index';
|
||||
import Comments from "./Comments";
|
||||
import GeneralNavbar from "./GeneralNavbar";
|
||||
import RadarChart from './Radar'
|
||||
import EmptyState from './EmptyState';
|
||||
|
||||
export {
|
||||
ActivityTypeSwitch,
|
||||
@@ -51,4 +52,5 @@ export {
|
||||
Comments,
|
||||
GeneralNavbar,
|
||||
RadarChart,
|
||||
EmptyState,
|
||||
};
|
||||
|
||||
@@ -24,8 +24,8 @@ const envConfigs: Record<EnvType, EnvConfig> = {
|
||||
// 开发环境
|
||||
development: {
|
||||
name: '开发环境',
|
||||
apiBaseURL: 'https://sit.light120.com',
|
||||
// apiBaseURL: 'http://localhost:9098',
|
||||
apiBaseURL: 'https://sit.light120.com',
|
||||
// apiBaseURL: 'http://localhost:9098',
|
||||
timeout: 15000,
|
||||
enableLog: true,
|
||||
enableMock: true,
|
||||
|
||||
4
src/other_pages/comment_reply/index.config.ts
Normal file
4
src/other_pages/comment_reply/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: "custom",
|
||||
navigationBarTitleText: "收到的评论和回复",
|
||||
});
|
||||
228
src/other_pages/comment_reply/index.scss
Normal file
228
src/other_pages/comment_reply/index.scss
Normal file
@@ -0,0 +1,228 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
|
||||
.comment-reply-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #FFFFFF;
|
||||
|
||||
// 顶部导航栏
|
||||
.navbar {
|
||||
height: 100px;
|
||||
background: #FFFFFF;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
|
||||
.navbar-content {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 15px;
|
||||
margin-top: 44px;
|
||||
position: relative;
|
||||
|
||||
.back-button {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
.back-icon {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-left: 2.67px solid #000000;
|
||||
border-bottom: 2.67px solid #000000;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.019em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 评论列表滚动区域
|
||||
.comment-scroll {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
// 评论列表
|
||||
.comment-list {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
// 评论项
|
||||
.comment-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 16px 0;
|
||||
border-bottom: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
|
||||
.comment-left {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.user-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.user-nickname {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.action-text,
|
||||
.time-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 1.6;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #000000;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// 被回复的评论
|
||||
.original-comment {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
|
||||
.quote-line {
|
||||
width: 2px;
|
||||
height: 14px;
|
||||
background: rgba(120, 120, 128, 0.12);
|
||||
border-radius: 7px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.original-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
word-break: break-word;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 回复按钮
|
||||
.reply-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(120, 120, 128, 0.12);
|
||||
border-radius: 20px;
|
||||
width: fit-content;
|
||||
margin-top: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.reply-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.reply-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 1.6;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧球局图片
|
||||
.activity-image {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 9px;
|
||||
background: #F5F5F5;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 到底了提示
|
||||
.bottom-tip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 24px 0 12px;
|
||||
|
||||
.tip-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
244
src/other_pages/comment_reply/index.tsx
Normal file
244
src/other_pages/comment_reply/index.tsx
Normal file
@@ -0,0 +1,244 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { View, Text, ScrollView, Image } from "@tarojs/components";
|
||||
import { Avatar } from "@nutui/nutui-react-taro";
|
||||
import { withAuth, EmptyState } from "@/components";
|
||||
import commentService, { CommentActivity } from "@/services/commentService";
|
||||
import Taro from "@tarojs/taro";
|
||||
import "./index.scss";
|
||||
|
||||
// 评论/回复类型定义
|
||||
interface CommentReplyItem {
|
||||
id: number;
|
||||
user_avatar: string;
|
||||
user_nickname: string;
|
||||
action_type: "comment" | "reply"; // 评论了你的球局 / 回复了你的评论
|
||||
time: string;
|
||||
content: string;
|
||||
original_comment?: string; // 被回复的评论内容
|
||||
activity_image: string;
|
||||
activity_id: number;
|
||||
activity_title: string;
|
||||
}
|
||||
|
||||
const CommentReply = () => {
|
||||
const [commentList, setCommentList] = useState<CommentReplyItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getCommentReplyList();
|
||||
}, []);
|
||||
|
||||
// 获取评论和回复列表
|
||||
const getCommentReplyList = async () => {
|
||||
if (loading) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await commentService.getMyActivities({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
if (res.code === 0 && res.data) {
|
||||
// 映射数据
|
||||
const mappedList = res.data.rows.map((item: CommentActivity) => ({
|
||||
id: item.id,
|
||||
user_avatar: item.user?.avatar_url || "",
|
||||
user_nickname: item.user?.nickname || "匿名用户",
|
||||
action_type: item.type === "reply" ? "reply" as const : "comment" as const,
|
||||
time: item.create_time,
|
||||
content: item.content || "",
|
||||
original_comment: item.parent_comment?.content || "",
|
||||
activity_image: item.game?.image_list?.[0] || "",
|
||||
activity_id: item.game_id,
|
||||
activity_title: item.game?.title || "",
|
||||
}));
|
||||
|
||||
setCommentList(mappedList);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: "获取列表失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间显示
|
||||
const formatTime = (timeStr: string) => {
|
||||
if (!timeStr) return "";
|
||||
|
||||
const date = new Date(timeStr);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (minutes < 60) {
|
||||
return `${minutes}分钟前`;
|
||||
} else if (hours < 24) {
|
||||
return `${hours}小时前`;
|
||||
} else if (days === 1) {
|
||||
return "1天前";
|
||||
} else if (days < 7) {
|
||||
return `${days}天前`;
|
||||
} else {
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
return `${month}月${day}日`;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理回复
|
||||
const handleReply = (item: CommentReplyItem) => {
|
||||
console.log("回复:", item);
|
||||
// TODO: 跳转到回复页面或弹出回复框
|
||||
};
|
||||
|
||||
// 处理点击球局
|
||||
const handleGameClick = (gameId: number) => {
|
||||
Taro.navigateTo({
|
||||
url: `/game_pages/detail/index?id=${gameId}`,
|
||||
});
|
||||
};
|
||||
|
||||
// 处理返回
|
||||
const handleBack = () => {
|
||||
Taro.navigateBack();
|
||||
};
|
||||
|
||||
// 处理下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
const res = await commentService.getMyActivities({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
if (res.code === 0 && res.data) {
|
||||
const mappedList = res.data.rows.map((item: CommentActivity) => ({
|
||||
id: item.id,
|
||||
user_avatar: item.user?.avatar_url || "",
|
||||
user_nickname: item.user?.nickname || "匿名用户",
|
||||
action_type: item.type === "reply" ? "reply" as const : "comment" as const,
|
||||
time: item.create_time,
|
||||
content: item.content || "",
|
||||
original_comment: item.parent_comment?.content || "",
|
||||
activity_image: item.game?.image_list?.[0] || "",
|
||||
activity_id: item.game_id,
|
||||
activity_title: item.game?.title || "",
|
||||
}));
|
||||
|
||||
setCommentList(mappedList);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: "刷新失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染评论/回复项
|
||||
const renderCommentItem = (item: CommentReplyItem) => {
|
||||
const actionText = item.action_type === "comment" ? "评论了你的球局" : "回复了你的评论";
|
||||
|
||||
return (
|
||||
<View className="comment-item" key={item.id}>
|
||||
<View className="comment-left">
|
||||
<Avatar
|
||||
className="user-avatar"
|
||||
src={item.user_avatar || "https://img.yzcdn.cn/vant/cat.jpeg"}
|
||||
size="48px"
|
||||
/>
|
||||
|
||||
<View className="comment-content">
|
||||
<Text className="user-nickname">{item.user_nickname}</Text>
|
||||
|
||||
<View className="action-row">
|
||||
<Text className="action-text">{actionText}</Text>
|
||||
<Text className="time-text">{formatTime(item.time)}</Text>
|
||||
</View>
|
||||
|
||||
<Text className="comment-text">{item.content}</Text>
|
||||
|
||||
{/* 如果是回复,显示被回复的评论 */}
|
||||
{item.action_type === "reply" && item.original_comment && (
|
||||
<View className="original-comment">
|
||||
<View className="quote-line"></View>
|
||||
<Text className="original-text">{item.original_comment}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 回复按钮 */}
|
||||
<View className="reply-button" onClick={() => handleReply(item)}>
|
||||
<Image
|
||||
className="reply-icon"
|
||||
src={require('@/static/message/reply-icon.svg')}
|
||||
/>
|
||||
<Text className="reply-text">回复</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 右侧球局图片 */}
|
||||
<Image
|
||||
className="activity-image"
|
||||
src={item.activity_image}
|
||||
mode="aspectFill"
|
||||
onClick={() => handleGameClick(item.activity_id)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="comment-reply-container">
|
||||
{/* 顶部导航栏 */}
|
||||
<View className="navbar">
|
||||
<View className="navbar-content">
|
||||
<View className="back-button" onClick={handleBack}>
|
||||
<View className="back-icon"></View>
|
||||
</View>
|
||||
<Text className="navbar-title">收到的评论和回复</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 评论列表 */}
|
||||
<ScrollView
|
||||
scrollY
|
||||
className="comment-scroll"
|
||||
scrollWithAnimation
|
||||
enhanced
|
||||
showScrollbar={false}
|
||||
refresherEnabled={true}
|
||||
refresherTriggered={refreshing}
|
||||
onRefresherRefresh={handleRefresh}
|
||||
>
|
||||
{commentList.length > 0 ? (
|
||||
<View className="comment-list">
|
||||
{commentList.map(renderCommentItem)}
|
||||
|
||||
{/* 到底了提示 */}
|
||||
<View className="bottom-tip">
|
||||
<Text className="tip-text">到底了</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<EmptyState text="暂无评论和回复" />
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default withAuth(CommentReply);
|
||||
@@ -6,23 +6,22 @@
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #FFFFFF;
|
||||
|
||||
// 导航栏
|
||||
// 顶部导航栏
|
||||
.navbar {
|
||||
height: 56px;
|
||||
height: 100px;
|
||||
background: #FFFFFF;
|
||||
padding-top: 44px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.06);
|
||||
|
||||
.navbar-content {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
margin-top: 44px;
|
||||
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
@@ -39,52 +38,109 @@
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.019em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分类标签区
|
||||
.category-tabs {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 20px;
|
||||
padding: 6px 24px;
|
||||
background: #FFFFFF;
|
||||
|
||||
// 消息列表
|
||||
.message-list {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
// margin-bottom:100px;
|
||||
background-color: none !important;
|
||||
|
||||
.message-list-content {
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px 12px 112px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 15px;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
.tab-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 56px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.tab-icon {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
.tab-icon {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 消息滚动区域
|
||||
.message-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: #FFFFFF;
|
||||
|
||||
.message-cards {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
// 系统消息样式
|
||||
.system-message {
|
||||
// 系统消息卡片
|
||||
.message-card {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 20px;
|
||||
padding: 0 0 12px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
box-sizing: border-box;
|
||||
padding: 0 0 12px;
|
||||
transition: all 0.2s;
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.card-title-row {
|
||||
padding: 12px 15px 0;
|
||||
|
||||
.message-title {
|
||||
.card-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #000000;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.message-time {
|
||||
.card-time-row {
|
||||
padding: 4px 15px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
.card-time {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
@@ -93,31 +149,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
.card-content-row {
|
||||
padding: 8px 15px 0;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 2px;
|
||||
|
||||
.message-text {
|
||||
.card-content {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.message-action {
|
||||
.card-footer {
|
||||
padding: 12px 15px 0;
|
||||
|
||||
.action-divider {
|
||||
.footer-divider {
|
||||
height: 0.5px;
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
.footer-action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.action-text {
|
||||
font-family: 'PingFang SC';
|
||||
@@ -128,151 +190,94 @@
|
||||
}
|
||||
|
||||
.action-arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
.img {
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: -9px;
|
||||
width: 14px;;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用户消息样式
|
||||
.user-message {
|
||||
// 到底了提示
|
||||
.bottom-tip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 15px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
box-sizing: border-box;
|
||||
padding: 24px 0 12px;
|
||||
|
||||
.message-avatar {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
|
||||
.unread-dot {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #FF4848;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.message-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.message-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
|
||||
.message-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.29;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.unread-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #FF4848;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.tip-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态
|
||||
.empty-state {
|
||||
// 悬浮新建消息按钮
|
||||
.floating-button {
|
||||
position: fixed;
|
||||
right: 12px;
|
||||
bottom: 132px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 27% 8%, rgba(189, 255, 74, 1) 17%, rgba(149, 242, 62, 1) 54%, rgba(50, 216, 56, 1) 100%);
|
||||
border: 2px solid rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0px 4px 48px 0px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 124px 16px;
|
||||
height: 746px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 99;
|
||||
transition: all 0.2s;
|
||||
|
||||
.empty-icon {
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
|
||||
.empty-message-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%);
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #d0d0d0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #a0a0a0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
.button-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
|
||||
// 加号图标 - 竖线
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 4px;
|
||||
height: 25px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
// 加号图标 - 横线
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 25px;
|
||||
height: 4px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +1,245 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { View, Text, ScrollView, Image } from "@tarojs/components";
|
||||
import { View, Text, Image, ScrollView } from "@tarojs/components";
|
||||
import { Avatar } from "@nutui/nutui-react-taro";
|
||||
import GuideBar from "@/components/GuideBar";
|
||||
import { withAuth } from "@/components";
|
||||
import { withAuth, EmptyState } from "@/components";
|
||||
import noticeService from "@/services/noticeService";
|
||||
import Taro, { useRouter } from "@tarojs/taro";
|
||||
import { useUserInfo } from "@/store/userStore";
|
||||
import Taro from "@tarojs/taro";
|
||||
import "./index.scss";
|
||||
|
||||
// 消息类型定义
|
||||
interface MessageItem {
|
||||
id: string;
|
||||
type: "system" | "user" | "like" | "comment" | "follow";
|
||||
notification_type: string;
|
||||
title: string;
|
||||
content: string;
|
||||
time: string;
|
||||
avatar?: string;
|
||||
isRead: boolean;
|
||||
hasAction?: boolean;
|
||||
actionText?: string;
|
||||
create_time: string;
|
||||
is_read: number;
|
||||
related_user_avatar?: string;
|
||||
related_user_nickname?: string;
|
||||
activity_image?: string;
|
||||
}
|
||||
|
||||
// 消息分类类型
|
||||
type MessageCategory = "comment" | "follow";
|
||||
|
||||
const Message = () => {
|
||||
const [activeTab] = useState<"all" | "like" | "comment" | "follow">("all");
|
||||
const userInfo = useUserInfo() as any;
|
||||
const [activeTab, setActiveTab] = useState<MessageCategory | null>(null);
|
||||
const [messageList, setMessageList] = useState<MessageItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [reachedBottom, setReachedBottom] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// 获取消息列表
|
||||
const getNoticeList = async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await noticeService.getNotificationList({});
|
||||
if (res.code === 0) {
|
||||
setMessageList(res.data.list || []);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: "获取列表失败,请重试",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const getNoticeList = async () => {
|
||||
try {
|
||||
const res = await noticeService.getNotificationList({});
|
||||
if (res.code === 0) {
|
||||
setMessageList(res.data.list);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: "获取列表失败,请重试",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
getNoticeList();
|
||||
}, []);
|
||||
|
||||
// 过滤消息
|
||||
const filteredMessages = messageList.filter((message) => {
|
||||
if (activeTab === "all") return true;
|
||||
return message.type === activeTab;
|
||||
});
|
||||
// 过滤系统消息
|
||||
const filteredMessages = messageList;
|
||||
|
||||
// 渲染消息项
|
||||
const renderMessageItem = (message: MessageItem) => {
|
||||
if (message.type === "system") {
|
||||
return (
|
||||
<View className="message-item system-message" key={message.id}>
|
||||
<View className="message-header">
|
||||
<Text className="message-title">{message.title}</Text>
|
||||
<Text className="message-time">{message.time}</Text>
|
||||
</View>
|
||||
<View className="message-content">
|
||||
<Text className="message-text">{message.content}</Text>
|
||||
</View>
|
||||
{message.hasAction && (
|
||||
<View className="message-action">
|
||||
<View className="action-divider"></View>
|
||||
<View className="action-button">
|
||||
<Text className="action-text">{message.actionText}</Text>
|
||||
<Image
|
||||
className="action-arrow"
|
||||
src={require("../../static/message/icon-message-arrow.svg")}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
// 处理分类标签点击
|
||||
const handleTabClick = (tab: MessageCategory) => {
|
||||
// 点击评论标签跳转到评论和回复页面
|
||||
if (tab === "comment") {
|
||||
Taro.navigateTo({
|
||||
url: "/other_pages/comment_reply/index",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="message-item user-message" key={message.id}>
|
||||
<View className="message-avatar">
|
||||
<Avatar src={message.avatar} size="48px" />
|
||||
</View>
|
||||
<View className="message-info">
|
||||
<View className="message-header">
|
||||
<Text className="message-title">{message.title}</Text>
|
||||
<Text className="message-time">{message.time}</Text>
|
||||
</View>
|
||||
<View className="message-content">
|
||||
<Text className="message-text">{message.content}</Text>
|
||||
{!message.isRead && <View className="unread-indicator"></View>}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
// 点击关注标签跳转到新增关注页面
|
||||
if (tab === "follow") {
|
||||
Taro.navigateTo({
|
||||
url: "/other_pages/new_follow/index",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTab(activeTab === tab ? null : tab);
|
||||
};
|
||||
|
||||
// 处理查看详情
|
||||
const handleViewDetail = (messageId: string) => {
|
||||
console.log("查看详情:", messageId);
|
||||
// TODO: 根据消息类型跳转到对应详情页
|
||||
};
|
||||
|
||||
// 处理滚动到底部
|
||||
const handleScrollToLower = () => {
|
||||
if (!reachedBottom && filteredMessages.length > 0) {
|
||||
setReachedBottom(true);
|
||||
// 2秒后隐藏提示
|
||||
setTimeout(() => {
|
||||
setReachedBottom(false);
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
const res = await noticeService.getNotificationList({});
|
||||
if (res.code === 0) {
|
||||
setMessageList(res.data.list || []);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: "刷新失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间显示
|
||||
const formatTime = (timeStr: string) => {
|
||||
if (!timeStr) return "";
|
||||
const date = new Date(timeStr);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (days === 0) {
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
return `今天 ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||
} else if (days === 1) {
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
return `昨天 ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||
} else if (days < 3) {
|
||||
return `${days}天前`;
|
||||
} else {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="message-container">
|
||||
{/* 导航栏 */}
|
||||
{/* 顶部导航栏 */}
|
||||
<View className="navbar">
|
||||
<View className="navbar-content">
|
||||
<View className="navbar-left">
|
||||
<Avatar
|
||||
className="navbar-avatar"
|
||||
src="https://img.yzcdn.cn/vant/cat.jpeg"
|
||||
src={userInfo?.avatar_url }
|
||||
size="28px"
|
||||
/>
|
||||
<Text className="navbar-title">消息</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 消息列表 */}
|
||||
<ScrollView scrollY className="message-list" scrollWithAnimation enhanced showScrollbar={false} >
|
||||
{/* 分类标签 */}
|
||||
<View className="category-tabs">
|
||||
<View
|
||||
className={`tab-item ${activeTab === "comment" ? "active" : ""}`}
|
||||
onClick={() => handleTabClick("comment")}
|
||||
>
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/comment-icon.svg')}
|
||||
/>
|
||||
<Text className="tab-text">评论和回复</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`tab-item ${activeTab === "follow" ? "active" : ""}`}
|
||||
onClick={() => handleTabClick("follow")}
|
||||
>
|
||||
<Image
|
||||
className="tab-icon"
|
||||
src={require('@/static/message/follow-icon.svg')}
|
||||
/>
|
||||
<Text className="tab-text">新增关注</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 系统消息卡片列表 */}
|
||||
<ScrollView
|
||||
scrollY
|
||||
className="message-scroll"
|
||||
scrollWithAnimation
|
||||
enhanced
|
||||
showScrollbar={false}
|
||||
lowerThreshold={50}
|
||||
onScrollToLower={handleScrollToLower}
|
||||
refresherEnabled={true}
|
||||
refresherTriggered={refreshing}
|
||||
onRefresherRefresh={handleRefresh}
|
||||
>
|
||||
{filteredMessages.length > 0 ? (
|
||||
<View className="message-list-content">
|
||||
{filteredMessages.map(renderMessageItem)}
|
||||
<View className="message-cards">
|
||||
{filteredMessages.map((message) => (
|
||||
<View className="message-card" key={message.id}>
|
||||
<View className="card-title-row">
|
||||
<Text className="card-title">{message.title}</Text>
|
||||
</View>
|
||||
<View className="card-time-row">
|
||||
<Text className="card-time">{formatTime(message.create_time)}</Text>
|
||||
</View>
|
||||
<View className="card-content-row">
|
||||
<Text className="card-content">{message.content}</Text>
|
||||
</View>
|
||||
<View className="card-footer">
|
||||
<View className="footer-divider"></View>
|
||||
<View className="footer-action" onClick={() => handleViewDetail(message.id)}>
|
||||
<Text className="action-text">查看详情</Text>
|
||||
<View className="action-arrow">
|
||||
|
||||
<Image className="img" src={require('@/static/message/ar-right.svg')} ></Image>
|
||||
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
{/* 到底了提示 */}
|
||||
{filteredMessages.length > 0 && (
|
||||
<View className="bottom-tip">
|
||||
<Text className="tip-text">到底了</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<View className="empty-state">
|
||||
<View className="empty-icon">
|
||||
<View className="empty-message-icon"></View>
|
||||
</View>
|
||||
<Text className="empty-text">暂无消息</Text>
|
||||
</View>
|
||||
<EmptyState text="暂无消息" />
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{/* 悬浮新建按钮 */}
|
||||
<View className="floating-button" onClick={() => console.log("新建消息")}>
|
||||
<View className="button-icon"></View>
|
||||
</View>
|
||||
|
||||
{/* 底部导航 */}
|
||||
<GuideBar currentPage="message" />
|
||||
</View>
|
||||
|
||||
4
src/other_pages/new_follow/index.config.ts
Normal file
4
src/other_pages/new_follow/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: "custom",
|
||||
navigationBarTitleText: "新增关注",
|
||||
});
|
||||
199
src/other_pages/new_follow/index.scss
Normal file
199
src/other_pages/new_follow/index.scss
Normal file
@@ -0,0 +1,199 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
|
||||
.new-follow-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #FFFFFF;
|
||||
|
||||
// 顶部导航栏
|
||||
.navbar {
|
||||
height: 100px;
|
||||
background: #FFFFFF;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
|
||||
.navbar-content {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 15px;
|
||||
margin-top: 44px;
|
||||
position: relative;
|
||||
|
||||
.back-button {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
.back-icon {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-left: 2.67px solid #000000;
|
||||
border-bottom: 2.67px solid #000000;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.019em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关注列表滚动区域
|
||||
.follow-scroll {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 关注列表
|
||||
.follow-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 0 0 20px;
|
||||
}
|
||||
|
||||
// 关注项
|
||||
.follow-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 20px;
|
||||
|
||||
.follow-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
|
||||
.user-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
|
||||
.user-nickname {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.14;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.user-signature {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.action-text,
|
||||
.time-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 回关按钮
|
||||
.follow-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 4px 16px;
|
||||
border: 0.5px solid #000000;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.mutual {
|
||||
border-color: rgba(120, 120, 128, 0.12);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
&.mutual .button-text {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 到底了提示
|
||||
.bottom-tip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 24px 0 12px;
|
||||
|
||||
.tip-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
261
src/other_pages/new_follow/index.tsx
Normal file
261
src/other_pages/new_follow/index.tsx
Normal file
@@ -0,0 +1,261 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { View, Text, ScrollView } from "@tarojs/components";
|
||||
import { Avatar } from "@nutui/nutui-react-taro";
|
||||
import { withAuth, EmptyState } from "@/components";
|
||||
import FollowService from "@/services/followService";
|
||||
import Taro from "@tarojs/taro";
|
||||
import "./index.scss";
|
||||
|
||||
// 关注项类型定义
|
||||
interface FollowItem {
|
||||
id: number;
|
||||
user_id: number;
|
||||
user_avatar: string;
|
||||
user_nickname: string;
|
||||
user_signature?: string;
|
||||
city?: string;
|
||||
ntrp_level?: string;
|
||||
time: string;
|
||||
is_mutual: boolean; // 是否互相关注
|
||||
}
|
||||
|
||||
const NewFollow = () => {
|
||||
const [followList, setFollowList] = useState<FollowItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getFollowList();
|
||||
}, []);
|
||||
|
||||
// 获取新增关注列表
|
||||
const getFollowList = async () => {
|
||||
if (loading) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await FollowService.get_new_fans_list(1, 20);
|
||||
|
||||
if (res.list) {
|
||||
// 映射数据
|
||||
const mappedList = res.list.map((item: any) => ({
|
||||
id: item.id,
|
||||
user_id: item.id,
|
||||
user_avatar: item.avatar_url || "",
|
||||
user_nickname: item.nickname || "匿名用户",
|
||||
user_signature: item.personal_profile || "",
|
||||
city: item.city || "",
|
||||
ntrp_level: item.ntrp_level || "",
|
||||
time: item.follow_time,
|
||||
is_mutual: item.is_mutual || false,
|
||||
}));
|
||||
|
||||
setFollowList(mappedList);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: "获取列表失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间显示
|
||||
const formatTime = (timeStr: string) => {
|
||||
if (!timeStr) return "";
|
||||
|
||||
const date = new Date(timeStr);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (minutes < 1) {
|
||||
return "刚刚";
|
||||
} else if (minutes < 60) {
|
||||
return `${minutes}分钟前`;
|
||||
} else if (hours < 24) {
|
||||
return `${hours}小时前`;
|
||||
} else if (days === 1) {
|
||||
return "1天前";
|
||||
} else if (days < 7) {
|
||||
return `${days}天前`;
|
||||
} else {
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
return `${month}月${day}日`;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理回关/取消关注
|
||||
const handleFollowBack = async (item: FollowItem) => {
|
||||
try {
|
||||
if (item.is_mutual) {
|
||||
// 已经互相关注,点击取消关注
|
||||
await FollowService.unfollow_user(item.user_id);
|
||||
|
||||
Taro.showToast({
|
||||
title: "取消关注成功",
|
||||
icon: "success",
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
// 更新列表
|
||||
setFollowList(prevList =>
|
||||
prevList.map(followItem =>
|
||||
followItem.id === item.id
|
||||
? { ...followItem, is_mutual: false }
|
||||
: followItem
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// 未互关,点击回关
|
||||
await FollowService.follow_back(item.user_id);
|
||||
|
||||
Taro.showToast({
|
||||
title: "关注成功",
|
||||
icon: "success",
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
// 更新列表
|
||||
setFollowList(prevList =>
|
||||
prevList.map(followItem =>
|
||||
followItem.id === item.id
|
||||
? { ...followItem, is_mutual: true }
|
||||
: followItem
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: item.is_mutual ? "取消关注失败" : "关注失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理返回
|
||||
const handleBack = () => {
|
||||
Taro.navigateBack();
|
||||
};
|
||||
|
||||
// 处理点击用户
|
||||
const handleUserClick = (userId: number) => {
|
||||
Taro.navigateTo({
|
||||
url: `/user_pages/other/index?user_id=${userId}`,
|
||||
});
|
||||
};
|
||||
|
||||
// 处理下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
const res = await FollowService.get_new_fans_list(1, 20);
|
||||
|
||||
if (res.list) {
|
||||
const mappedList = res.list.map((item: any) => ({
|
||||
id: item.id,
|
||||
user_id: item.id,
|
||||
user_avatar: item.avatar_url || "",
|
||||
user_nickname: item.nickname || "匿名用户",
|
||||
user_signature: item.personal_profile || "",
|
||||
city: item.city || "",
|
||||
ntrp_level: item.ntrp_level || "",
|
||||
time: item.follow_time,
|
||||
is_mutual: item.is_mutual || false,
|
||||
}));
|
||||
|
||||
setFollowList(mappedList);
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: "刷新失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染关注项
|
||||
const renderFollowItem = (item: FollowItem) => {
|
||||
return (
|
||||
<View className="follow-item" key={item.id}>
|
||||
<View className="follow-left" onClick={() => handleUserClick(item.user_id)}>
|
||||
<Avatar
|
||||
className="user-avatar"
|
||||
src={item.user_avatar || "https://img.yzcdn.cn/vant/cat.jpeg"}
|
||||
size="40px"
|
||||
/>
|
||||
|
||||
<View className="user-info">
|
||||
<Text className="user-nickname">{item.user_nickname}</Text>
|
||||
|
||||
{item.user_signature ? (
|
||||
<Text className="user-signature">{item.user_signature}</Text>
|
||||
) : (
|
||||
<View className="action-row">
|
||||
<Text className="action-text">{formatTime(item.time)}关注了你</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`follow-button ${item.is_mutual ? "mutual" : ""}`}
|
||||
onClick={() => handleFollowBack(item)}
|
||||
>
|
||||
<Text className="button-text">{item.is_mutual ? "互相关注" : "回关"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="new-follow-container">
|
||||
{/* 顶部导航栏 */}
|
||||
<View className="navbar">
|
||||
<View className="navbar-content">
|
||||
<View className="back-button" onClick={handleBack}>
|
||||
<View className="back-icon"></View>
|
||||
</View>
|
||||
<Text className="navbar-title">新增关注</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 关注列表 */}
|
||||
<ScrollView
|
||||
scrollY
|
||||
className="follow-scroll"
|
||||
scrollWithAnimation
|
||||
enhanced
|
||||
showScrollbar={false}
|
||||
refresherEnabled={true}
|
||||
refresherTriggered={refreshing}
|
||||
onRefresherRefresh={handleRefresh}
|
||||
>
|
||||
{followList.length > 0 ? (
|
||||
<View className="follow-list">
|
||||
{followList.map(renderFollowItem)}
|
||||
|
||||
{/* 到底了提示 */}
|
||||
<View className="bottom-tip">
|
||||
<Text className="tip-text">到底了</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<EmptyState text="暂无新增关注" />
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default withAuth(NewFollow);
|
||||
BIN
src/scss/qweather-icons/fonts/qweather-icons.ttf
Normal file
BIN
src/scss/qweather-icons/fonts/qweather-icons.ttf
Normal file
Binary file not shown.
BIN
src/scss/qweather-icons/fonts/qweather-icons.woff2
Normal file
BIN
src/scss/qweather-icons/fonts/qweather-icons.woff2
Normal file
Binary file not shown.
92
src/scss/qweather-icons/qweather-icons.css
Normal file
92
src/scss/qweather-icons/qweather-icons.css
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "qweather-icons";
|
||||
src: url("./fonts/qweather-icons.woff2?3696017a726a77099c2617f87a3367ac") format("woff2")
|
||||
|
||||
}
|
||||
|
||||
[class^="qi-"]::before,
|
||||
[class*=" qi-"]::before {
|
||||
display: inline-block;
|
||||
font-family: "qweather-icons" !important;
|
||||
font-style: normal;
|
||||
font-weight: normal !important;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
vertical-align: -.125em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.qi-100::before { content: "\f101"; }
|
||||
.qi-101::before { content: "\f102"; }
|
||||
.qi-102::before { content: "\f103"; }
|
||||
.qi-103::before { content: "\f104"; }
|
||||
.qi-104::before { content: "\f105"; }
|
||||
.qi-150::before { content: "\f106"; }
|
||||
.qi-151::before { content: "\f107"; }
|
||||
.qi-152::before { content: "\f108"; }
|
||||
.qi-153::before { content: "\f109"; }
|
||||
.qi-300::before { content: "\f10a"; }
|
||||
.qi-301::before { content: "\f10b"; }
|
||||
.qi-302::before { content: "\f10c"; }
|
||||
.qi-303::before { content: "\f10d"; }
|
||||
.qi-304::before { content: "\f10e"; }
|
||||
.qi-305::before { content: "\f10f"; }
|
||||
.qi-306::before { content: "\f110"; }
|
||||
.qi-307::before { content: "\f111"; }
|
||||
.qi-308::before { content: "\f112"; }
|
||||
.qi-309::before { content: "\f113"; }
|
||||
.qi-310::before { content: "\f114"; }
|
||||
.qi-311::before { content: "\f115"; }
|
||||
.qi-312::before { content: "\f116"; }
|
||||
.qi-313::before { content: "\f117"; }
|
||||
.qi-314::before { content: "\f118"; }
|
||||
.qi-315::before { content: "\f119"; }
|
||||
.qi-316::before { content: "\f11a"; }
|
||||
.qi-317::before { content: "\f11b"; }
|
||||
.qi-318::before { content: "\f11c"; }
|
||||
.qi-350::before { content: "\f11d"; }
|
||||
.qi-351::before { content: "\f11e"; }
|
||||
.qi-399::before { content: "\f11f"; }
|
||||
.qi-400::before { content: "\f120"; }
|
||||
.qi-401::before { content: "\f121"; }
|
||||
.qi-402::before { content: "\f122"; }
|
||||
.qi-403::before { content: "\f123"; }
|
||||
.qi-404::before { content: "\f124"; }
|
||||
.qi-405::before { content: "\f125"; }
|
||||
.qi-406::before { content: "\f126"; }
|
||||
.qi-407::before { content: "\f127"; }
|
||||
.qi-408::before { content: "\f128"; }
|
||||
.qi-409::before { content: "\f129"; }
|
||||
.qi-410::before { content: "\f12a"; }
|
||||
.qi-456::before { content: "\f12b"; }
|
||||
.qi-457::before { content: "\f12c"; }
|
||||
.qi-499::before { content: "\f12d"; }
|
||||
.qi-500::before { content: "\f12e"; }
|
||||
.qi-501::before { content: "\f12f"; }
|
||||
.qi-502::before { content: "\f130"; }
|
||||
.qi-503::before { content: "\f131"; }
|
||||
.qi-504::before { content: "\f132"; }
|
||||
.qi-507::before { content: "\f133"; }
|
||||
.qi-508::before { content: "\f134"; }
|
||||
.qi-509::before { content: "\f135"; }
|
||||
.qi-510::before { content: "\f136"; }
|
||||
.qi-511::before { content: "\f137"; }
|
||||
.qi-512::before { content: "\f138"; }
|
||||
.qi-513::before { content: "\f139"; }
|
||||
.qi-514::before { content: "\f13a"; }
|
||||
.qi-515::before { content: "\f13b"; }
|
||||
.qi-800::before { content: "\f13c"; }
|
||||
.qi-801::before { content: "\f13d"; }
|
||||
.qi-802::before { content: "\f13e"; }
|
||||
.qi-803::before { content: "\f13f"; }
|
||||
.qi-804::before { content: "\f140"; }
|
||||
.qi-805::before { content: "\f141"; }
|
||||
.qi-806::before { content: "\f142"; }
|
||||
.qi-807::before { content: "\f143"; }
|
||||
.qi-900::before { content: "\f144"; }
|
||||
.qi-901::before { content: "\f145"; }
|
||||
.qi-999::before { content: "\f146"; }
|
||||
94
src/services/commentService.ts
Normal file
94
src/services/commentService.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import httpService from "./httpService";
|
||||
import type { ApiResponse } from "./httpService";
|
||||
|
||||
// 评论项接口定义
|
||||
export interface CommentActivity {
|
||||
id: number;
|
||||
type: "comment" | "reply";
|
||||
activity_type: string;
|
||||
game_id: number;
|
||||
parent_id: number | null;
|
||||
content: string;
|
||||
like_count: number;
|
||||
reply_count: number;
|
||||
create_time: string;
|
||||
user: {
|
||||
id: number;
|
||||
nickname: string;
|
||||
province: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
game: {
|
||||
id: number;
|
||||
title: string;
|
||||
start_time: string;
|
||||
location_name: string;
|
||||
image_list?: string[];
|
||||
};
|
||||
reply_to_user: {
|
||||
id: number;
|
||||
nickname: string;
|
||||
avatar_url: string;
|
||||
} | null;
|
||||
parent_comment: {
|
||||
id: number;
|
||||
content: string;
|
||||
user_id: number;
|
||||
user_nickname: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
// 获取评论动态请求参数
|
||||
export interface GetMyActivitiesParams {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
// 评论动态列表响应
|
||||
export interface CommentActivitiesResponse {
|
||||
rows: CommentActivity[];
|
||||
count: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
class CommentService {
|
||||
// 获取当前用户的评论和回复动态
|
||||
async getMyActivities(params: GetMyActivitiesParams = {}): Promise<ApiResponse<CommentActivitiesResponse>> {
|
||||
const { page = 1, pageSize = 10 } = params;
|
||||
return httpService.post("/comments/my_activities", { page, pageSize }, { showLoading: false });
|
||||
}
|
||||
|
||||
// 发表评论
|
||||
async createComment(game_id: number, content: string): Promise<ApiResponse<any>> {
|
||||
return httpService.post("/comments/create", { game_id, content });
|
||||
}
|
||||
|
||||
// 回复评论
|
||||
async replyComment(parent_id: number, reply_to_user_id: number, content: string): Promise<ApiResponse<any>> {
|
||||
return httpService.post("/comments/reply", { parent_id, reply_to_user_id, content });
|
||||
}
|
||||
|
||||
// 获取评论列表
|
||||
async getCommentList(game_id: number, page: number = 1, pageSize: number = 10): Promise<ApiResponse<any>> {
|
||||
return httpService.post("/comments/list", { game_id, page, pageSize });
|
||||
}
|
||||
|
||||
// 点赞/取消点赞评论
|
||||
async likeComment(comment_id: number): Promise<ApiResponse<any>> {
|
||||
return httpService.post("/comments/like", { comment_id });
|
||||
}
|
||||
|
||||
// 删除评论
|
||||
async deleteComment(comment_id: number): Promise<ApiResponse<any>> {
|
||||
return httpService.post("/comments/delete", { comment_id });
|
||||
}
|
||||
|
||||
// 获取评论的所有回复
|
||||
async getCommentReplies(comment_id: number, page: number = 1, pageSize: number = 10): Promise<ApiResponse<any>> {
|
||||
return httpService.post("/comments/replies", { comment_id, page, pageSize });
|
||||
}
|
||||
}
|
||||
|
||||
export default new CommentService();
|
||||
@@ -84,6 +84,34 @@ export class FollowService {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取新增粉丝列表(新关注的人)
|
||||
static async get_new_fans_list(
|
||||
page: number = 1,
|
||||
page_size: number = 20
|
||||
): Promise<FollowListResponse> {
|
||||
try {
|
||||
const response = await httpService.post<FollowListResponse>(
|
||||
'/user_follow/new_fans_list',
|
||||
{ page, page_size },
|
||||
{ showLoading: false }
|
||||
);
|
||||
|
||||
if (response.code === 0) {
|
||||
// 为数据添加 follow_status 标识
|
||||
const list = response.data.list.map(user => ({
|
||||
...user,
|
||||
follow_status: user.is_mutual ? 'mutual_follow' as const : 'follower' as const
|
||||
}));
|
||||
return { ...response.data, list };
|
||||
} else {
|
||||
throw new Error(response.message || '获取新增粉丝列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取新增粉丝列表失败:', error);
|
||||
return { list: [], total: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取我的关注列表
|
||||
static async get_following_list(
|
||||
page: number = 1,
|
||||
|
||||
3
src/static/message/ar-right.svg
Normal file
3
src/static/message/ar-right.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="6" height="11" viewBox="0 0 6 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.33334 1.5L5.33334 5.5L1.33334 9.5" stroke="#3C3C43" stroke-opacity="0.6" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 258 B |
11
src/static/message/comment-icon.svg
Normal file
11
src/static/message/comment-icon.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="56" height="56" rx="28" fill="#00EB47"/>
|
||||
<g clip-path="url(#clip0_4975_26341)">
|
||||
<path d="M43.7546 26.3183C43.7546 33.127 37.2195 38.7325 27.3619 38.7325C27.1843 38.7325 27.0066 38.7187 26.8288 38.705C26.6373 38.705 26.446 38.7734 26.2136 38.9376C23.8893 40.6054 19.8015 42.3965 18.1608 42.3965C17.1081 42.3965 16.8073 41.5351 17.3405 40.8516C17.86 40.1679 19.2272 38.6641 19.9929 37.4062C20.1022 37.2012 20.0339 36.9961 19.8288 36.8867C15.4401 34.5351 12.7468 30.6933 12.7468 26.3183C12.7468 19.4414 19.6374 13.8906 28.2507 13.8906C36.864 13.8906 43.7546 19.4414 43.7546 26.3183ZM32.7898 26.4414C32.7898 27.5762 33.7057 28.4922 34.8405 28.4922C35.9479 28.4922 36.8913 27.5762 36.8913 26.4414C36.8913 25.3066 35.9479 24.3906 34.8405 24.3906C33.7057 24.3906 32.7898 25.3066 32.7898 26.4414ZM26.1998 26.4414C26.1998 27.5762 27.1159 28.4922 28.2507 28.4922C29.3854 28.4922 30.3015 27.5762 30.3015 26.4414C30.3015 25.3066 29.3854 24.3906 28.2507 24.3906C27.1159 24.3906 26.1998 25.3066 26.1998 26.4414ZM19.6237 26.4414C19.6237 27.5762 20.5534 28.4922 21.6745 28.4922C22.7956 28.4922 23.7116 27.5762 23.7116 26.4414C23.7116 25.3066 22.7819 24.3906 21.6745 24.3906C20.5534 24.3906 19.6237 25.3066 19.6237 26.4414Z" fill="white" fill-opacity="0.85"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4975_26341">
|
||||
<rect width="31.418" height="28.5059" fill="white" transform="translate(12.7468 13.8906)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
38
src/static/message/emi.svg
Normal file
38
src/static/message/emi.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 21 KiB |
12
src/static/message/follow-icon.svg
Normal file
12
src/static/message/follow-icon.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="56" height="56" rx="28" fill="#3374FF"/>
|
||||
<g clip-path="url(#clip0_4975_26344)">
|
||||
<path d="M25.5664 31.8472C23.2953 33.8216 22.0164 36.3451 22.0164 38.6094C22.0164 39.2581 22.1476 39.8839 22.4609 40.4277H12.0222C10.2996 40.4277 9.61597 39.7441 9.61597 38.5C9.61597 34.7265 13.4714 30.1739 19.6374 30.1739C21.9744 30.1739 23.9794 30.8278 25.5664 31.8472ZM24.409 22.8184C24.409 25.7852 22.194 28.082 19.6511 28.082C17.0945 28.082 14.8796 25.7852 14.8796 22.8457C14.8796 19.9199 17.1081 17.7051 19.6511 17.7051C22.1804 17.7051 24.409 19.8652 24.409 22.8184Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M35.3875 27.7676C38.327 27.7676 40.8562 25.1426 40.8562 21.7246C40.8562 18.3477 38.3132 15.8457 35.3875 15.8457C32.4617 15.8457 29.9187 18.4023 29.9187 21.752C29.9187 25.1426 32.4481 27.7676 35.3875 27.7676ZM26.7878 40.4277H43.9734C46.1199 40.4277 46.8856 39.8125 46.8856 38.6094C46.8856 35.082 42.4695 30.2148 35.3738 30.2148C28.2918 30.2148 23.8757 35.082 23.8757 38.6094C23.8757 39.8125 24.6414 40.4277 26.7878 40.4277Z" fill="white" fill-opacity="0.85"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4975_26344">
|
||||
<rect width="40.5781" height="28.2871" fill="white" transform="translate(8.16669 14)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
6
src/static/message/reply-icon.svg
Normal file
6
src/static/message/reply-icon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 1.5H1V9H3.25V10.25L5.75 9H11V1.5Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.5 4.875V5.625" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 4.875V5.625" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 4.875V5.625" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 494 B |
17
yarn.lock
17
yarn.lock
@@ -720,6 +720,18 @@
|
||||
babel-plugin-polyfill-regenerator "^0.6.5"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/plugin-transform-runtime@^7.28.3":
|
||||
version "7.28.3"
|
||||
resolved "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz#f5990a1b2d2bde950ed493915e0719841c8d0eaa"
|
||||
integrity sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.27.1"
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
babel-plugin-polyfill-corejs2 "^0.4.14"
|
||||
babel-plugin-polyfill-corejs3 "^0.13.0"
|
||||
babel-plugin-polyfill-regenerator "^0.6.5"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/plugin-transform-shorthand-properties@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90"
|
||||
@@ -3203,6 +3215,11 @@ babel-plugin-transform-imports-api@1.0.0:
|
||||
dependencies:
|
||||
is-invalid-path "^1.0.2"
|
||||
|
||||
babel-plugin-transform-remove-console@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.npmmirror.com/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780"
|
||||
integrity sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==
|
||||
|
||||
babel-plugin-transform-solid-jsx@4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.npmmirror.com/babel-plugin-transform-solid-jsx/-/babel-plugin-transform-solid-jsx-4.1.5.tgz#203d07a15f32e65e09f266b6ad2080f346eb453a"
|
||||
|
||||
Reference in New Issue
Block a user