This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Typography, Card, Avatar, Tag, Space, Button, Divider, Input, message, Upload, Tooltip, Grid } from 'antd';
|
||||
import { UserOutlined, ClockCircleOutlined, EyeOutlined, CheckCircleFilled, LeftOutlined, UploadOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { getTopicDetail, createReply, uploadMedia } from '../api';
|
||||
import { getTopicDetail, createReply, uploadMedia, getStarUsers } from '../api';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import LoginModal from '../components/LoginModal';
|
||||
import CreateTopicModal from '../components/CreateTopicModal';
|
||||
@@ -41,6 +41,9 @@ const ForumDetail = () => {
|
||||
const [replyUploading, setReplyUploading] = useState(false);
|
||||
const [replyMediaIds, setReplyMediaIds] = useState([]);
|
||||
|
||||
// Star Users State
|
||||
const [starUsers, setStarUsers] = useState([]);
|
||||
|
||||
const fetchTopic = async () => {
|
||||
try {
|
||||
const res = await getTopicDetail(id);
|
||||
@@ -53,14 +56,31 @@ const ForumDetail = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchStarUsers = async () => {
|
||||
try {
|
||||
const res = await getStarUsers();
|
||||
setStarUsers(res.data || []);
|
||||
} catch (error) {
|
||||
console.error('Fetch star users failed', error);
|
||||
}
|
||||
};
|
||||
|
||||
const hasFetched = React.useRef(false);
|
||||
useEffect(() => {
|
||||
if (!hasFetched.current) {
|
||||
fetchTopic();
|
||||
fetchStarUsers();
|
||||
hasFetched.current = true;
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const handleReplyToUser = (nickname) => {
|
||||
const mentionText = `@${nickname} `;
|
||||
setReplyContent(prev => prev + mentionText);
|
||||
// Focus logic if needed, but simple append works
|
||||
message.info(`已添加 @${nickname}`);
|
||||
};
|
||||
|
||||
const handleSubmitReply = async () => {
|
||||
if (!user) {
|
||||
setLoginModalVisible(true);
|
||||
@@ -272,6 +292,14 @@ const ForumDetail = () => {
|
||||
<Space size={isMobile ? 'small' : 'middle'} align="center">
|
||||
<Text style={{ color: '#aaa', fontWeight: 'bold', fontSize: isMobile ? 13 : 14 }}>{reply.author_info?.nickname}</Text>
|
||||
<Text style={{ color: '#666', fontSize: 12 }}>{new Date(reply.created_at).toLocaleString()}</Text>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => handleReplyToUser(reply.author_info?.nickname)}
|
||||
style={{ padding: 0, height: 'auto' }}
|
||||
>
|
||||
回复
|
||||
</Button>
|
||||
</Space>
|
||||
<Text style={{ color: '#444', fontSize: 12 }}>#{index + 1}</Text>
|
||||
</div>
|
||||
@@ -308,6 +336,25 @@ const ForumDetail = () => {
|
||||
placeholder="友善回复,分享你的见解... (支持 Markdown)"
|
||||
style={{ marginBottom: 16, background: '#111', border: '1px solid #333', color: '#fff' }}
|
||||
/>
|
||||
|
||||
{starUsers.length > 0 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Text style={{ color: '#888', marginRight: 8 }}>@技术专家:</Text>
|
||||
<Space wrap>
|
||||
{starUsers.map(user => (
|
||||
<Tag
|
||||
key={user.id}
|
||||
color="gold"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleReplyToUser(user.nickname)}
|
||||
>
|
||||
{user.nickname}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', alignItems: isMobile ? 'stretch' : 'center', gap: isMobile ? 10 : 0 }}>
|
||||
<Upload
|
||||
beforeUpload={handleReplyUpload}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import Taro, { useRouter, useShareAppMessage, useDidShow } from '@tarojs/taro'
|
||||
import { View, Text, Image, Video, RichText, Input, ScrollView } from '@tarojs/components'
|
||||
import { AtActivityIndicator, AtIcon } from 'taro-ui'
|
||||
import { getTopicDetail, createReply, uploadMedia } from '../../../api'
|
||||
import { AtActivityIndicator, AtIcon, AtActionSheet, AtActionSheetItem } from 'taro-ui'
|
||||
import { getTopicDetail, createReply, uploadMedia, getStarUsers } from '../../../api'
|
||||
import { marked } from 'marked'
|
||||
import './detail.scss'
|
||||
|
||||
@@ -16,6 +16,10 @@ const ForumDetail = () => {
|
||||
const [sending, setSending] = useState(false)
|
||||
const [htmlContent, setHtmlContent] = useState('')
|
||||
const [userInfo, setUserInfo] = useState<any>(null)
|
||||
|
||||
// Star Users & Mention
|
||||
const [starUsers, setStarUsers] = useState<any[]>([])
|
||||
const [showStarUsers, setShowStarUsers] = useState(false)
|
||||
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
@@ -38,12 +42,24 @@ const ForumDetail = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchStarUsers = async () => {
|
||||
try {
|
||||
const res = await getStarUsers()
|
||||
// API might return array directly or { data: [] }
|
||||
const users = Array.isArray(res) ? res : (res.data || [])
|
||||
setStarUsers(users)
|
||||
} catch (error) {
|
||||
console.error('Fetch star users failed', error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const info = Taro.getStorageSync('userInfo')
|
||||
if (info) setUserInfo(info)
|
||||
|
||||
if (id) {
|
||||
fetchDetail()
|
||||
fetchStarUsers()
|
||||
}
|
||||
}, [id])
|
||||
|
||||
@@ -53,6 +69,12 @@ const ForumDetail = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const handleReplyToUser = (nickname) => {
|
||||
const mentionText = `@${nickname} `
|
||||
setReplyContent(prev => prev + mentionText)
|
||||
setShowStarUsers(false)
|
||||
}
|
||||
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: topic?.title || '技术社区',
|
||||
@@ -199,7 +221,12 @@ const ForumDetail = () => {
|
||||
<Text className='nickname'>{reply.author_info?.nickname}</Text>
|
||||
<Text style={{fontSize: 10, color: '#666', marginTop: 2}}>#{idx + 1} • {new Date(reply.created_at).toLocaleDateString()}</Text>
|
||||
</View>
|
||||
<AtIcon value='message' size='14' color='#444' />
|
||||
<View style={{display: 'flex', alignItems: 'center'}}>
|
||||
<View onClick={() => handleReplyToUser(reply.author_info?.nickname)} style={{marginRight: 10, padding: '2px 6px', background: '#f0f0f0', borderRadius: 4}}>
|
||||
<Text style={{fontSize: 10, color: '#666'}}>回复</Text>
|
||||
</View>
|
||||
<AtIcon value='message' size='14' color='#444' />
|
||||
</View>
|
||||
</View>
|
||||
<View className='reply-content'>
|
||||
{/* Simple markdown render for replies or just text if complex */}
|
||||
@@ -216,6 +243,9 @@ const ForumDetail = () => {
|
||||
<View className='action-btn' onClick={handleUpload}>
|
||||
<AtIcon value='image' size='20' color='#888' />
|
||||
</View>
|
||||
<View className='action-btn' onClick={() => setShowStarUsers(true)} style={{marginLeft: 0}}>
|
||||
<AtIcon value='at' size='20' color='#888' />
|
||||
</View>
|
||||
<View className='input-wrapper'>
|
||||
<Input
|
||||
value={replyContent}
|
||||
@@ -229,6 +259,18 @@ const ForumDetail = () => {
|
||||
{sending ? <AtActivityIndicator size={20} /> : <Text className='send-btn'>发送</Text>}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<AtActionSheet isOpened={showStarUsers} cancelText='取消' title='选择要@的技术专家' onCancel={() => setShowStarUsers(false)} onClose={() => setShowStarUsers(false)}>
|
||||
{starUsers.map(user => (
|
||||
<AtActionSheetItem key={user.id} onClick={() => handleReplyToUser(user.nickname)}>
|
||||
<View style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
|
||||
<Image src={user.avatar_url || 'https://via.placeholder.com/30'} style={{width: 24, height: 24, borderRadius: 12, marginRight: 8}} />
|
||||
<Text>{user.nickname}</Text>
|
||||
<Text style={{fontSize: 10, color: '#999', marginLeft: 4}}>({user.title || '专家'})</Text>
|
||||
</View>
|
||||
</AtActionSheetItem>
|
||||
))}
|
||||
</AtActionSheet>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user