寻号功能v1.2.21最终版:

1. 发布寻号面板:去掉类型匹配度字段,标题在正文前面
2. 联系方式默认***隐藏
3. 增加匹配状态、匹配并联系藏友、留言藏友按钮
4. 留言功能所有人可见
5. 我的寻号/我的配号筛选按钮(主页面筛选)
6. 记住用户浏览的标签页
7. 后端添加matched_contact字段
This commit is contained in:
甲辰生产 2026-03-28 11:10:23 +08:00
parent 2cd90a0604
commit 4d3feeae45
3 changed files with 42 additions and 8 deletions

View File

@ -188,6 +188,9 @@ class Information(Base):
# 匹配的用户ID当用户愿意交换联系方式时 # 匹配的用户ID当用户愿意交换联系方式时
matched_user_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="SET NULL"), nullable=True) matched_user_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="SET NULL"), nullable=True)
# 匹配者的联系方式(只有发布者和匹配者可见)
matched_contact = Column(String(100), nullable=True)
# 浏览/联系次数 # 浏览/联系次数
view_count = Column(Integer, default=0) view_count = Column(Integer, default=0)
contact_count = Column(Integer, default=0) contact_count = Column(Integer, default=0)

View File

@ -60,6 +60,7 @@ class InformationResponse(BaseModel):
status: str status: str
is_matched: str = "pending" is_matched: str = "pending"
matched_user_id: Optional[str] = None matched_user_id: Optional[str] = None
matched_contact: Optional[str] = None
view_count: int view_count: int
contact_count: int contact_count: int
created_at: datetime created_at: datetime
@ -130,6 +131,7 @@ def get_information_list(
status=item.status, status=item.status,
is_matched=item.is_matched, is_matched=item.is_matched,
matched_user_id=item.matched_user_id, matched_user_id=item.matched_user_id,
matched_contact=item.matched_contact,
view_count=item.view_count, view_count=item.view_count,
contact_count=item.contact_count, contact_count=item.contact_count,
created_at=item.created_at, created_at=item.created_at,
@ -536,6 +538,7 @@ def get_my_seeks(
status=item.status, status=item.status,
is_matched=item.is_matched, is_matched=item.is_matched,
matched_user_id=item.matched_user_id, matched_user_id=item.matched_user_id,
matched_contact=item.matched_contact,
view_count=item.view_count, view_count=item.view_count,
contact_count=item.contact_count, contact_count=item.contact_count,
created_at=item.created_at, created_at=item.created_at,
@ -651,6 +654,7 @@ def get_my_information_list(
status=item.status, status=item.status,
is_matched=item.is_matched, is_matched=item.is_matched,
matched_user_id=item.matched_user_id, matched_user_id=item.matched_user_id,
matched_contact=item.matched_contact,
view_count=item.view_count, view_count=item.view_count,
contact_count=item.contact_count, contact_count=item.contact_count,
created_at=item.created_at, created_at=item.created_at,
@ -695,6 +699,7 @@ def get_my_information(
class MatchSeekRequest(BaseModel): class MatchSeekRequest(BaseModel):
info_id: str info_id: str
collection_id: Optional[str] = None collection_id: Optional[str] = None
contact: Optional[str] = None
@router.post("/seek/match-confirm") @router.post("/seek/match-confirm")

View File

@ -16,6 +16,7 @@ export default function News() {
edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: '' edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: ''
}) })
const [infoList, setInfoList] = useState([]) const [infoList, setInfoList] = useState([])
const [viewMode, setViewMode] = useState('all')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const API_BASE = localStorage.getItem('API_BASE') || '' const API_BASE = localStorage.getItem('API_BASE') || ''
@ -142,8 +143,18 @@ export default function News() {
} }
} }
// ID
const getUserId = () => {
try {
const user = JSON.parse(localStorage.getItem('user') || '{}')
return user.id || user.user_id || user.f99_90_id || null
} catch { return null }
}
const currentUserId = getUserId()
// //
const [mySeekList, setMySeekList] = useState([]) const [mySeekList, setMySeekList] = useState([])
const [myMatchedList, setMyMatchedList] = useState([])
const [editingSeek, setEditingSeek] = useState(null) const [editingSeek, setEditingSeek] = useState(null)
const fetchMySeeks = async () => { const fetchMySeeks = async () => {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
@ -153,9 +164,26 @@ export default function News() {
headers: { Authorization: `Bearer ${token}` } headers: { Authorization: `Bearer ${token}` }
}) })
const data = await res.json() const data = await res.json()
// //
const seeks = Array.isArray(data) ? data.filter(item => item.info_type === 'seek') : [] const seeks = Array.isArray(data) ? data.filter(item => item.info_type === 'seek' && item.user_id === currentUserId) : []
setMySeekList(seeks) setMySeekList(seeks)
setInfoList(seeks) //
} catch (e) { console.error(e) }
}
//
const fetchMyMatches = async () => {
const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return }
try {
const res = await fetch(`${API_BASE}/api/information/list?info_type=seek`, {
headers: { Authorization: `Bearer ${token}` }
})
const data = await res.json()
// matched_user_idID
const matched = data.filter(item => item.is_matched === 'matched' && item.matched_user_id === currentUserId)
setMyMatchedList(matched)
setInfoList(matched) //
} catch (e) { console.error(e) } } catch (e) { console.error(e) }
} }
@ -335,12 +363,10 @@ export default function News() {
{activeTab === 'seek' ? '🔍 寻配号信息' : '💰 成交行情信息'} {activeTab === 'seek' ? '🔍 寻配号信息' : '💰 成交行情信息'}
{activeTab === 'seek' && ( {activeTab === 'seek' && (
<div style={{ display: 'flex', gap: '8px', marginLeft: 'auto' }}> <div style={{ display: 'flex', gap: '8px', marginLeft: 'auto' }}>
<button onClick={() => setShowSeekPublish(!showSeekPublish)} style={{ padding: '6px 12px', background: showSeekPublish ? '#6b7280' : '#10b981', border: 'none', borderRadius: '6px', color: '#fff', fontSize: '12px', cursor: 'pointer' }}> <button onClick={() => { setViewMode('all'); fetchInfoList(); }} style={{ padding: '6px 12px', background: viewMode==='all'?'#3b82f6':'#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '12px' }}>全部</button>
'+ 发布寻号' <button onClick={() => { setViewMode('mySeeks'); fetchMySeeks(); }} style={{ padding: '6px 12px', background: viewMode==='mySeeks'?'#3b82f6':'#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '12px' }}>我的寻号</button>
</button> <button onClick={() => { setViewMode('myMatches'); fetchMyMatches(); }} style={{ padding: '6px 12px', background: viewMode==='myMatches'?'#8b5cf6':'#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '12px' }}>我的配号</button>
<button onClick={() => { setShowMySeeks(true); fetchMySeeks(); }} style={{ padding: '6px 12px', background: '#3b82f6', border: 'none', borderRadius: '6px', color: '#fff', fontSize: '12px', cursor: 'pointer' }}> <button onClick={() => setShowSeekPublish(!showSeekPublish)} style={{ padding: '6px 12px', background: showSeekPublish ? '#6b7280' : '#10b981', border: 'none', borderRadius: '6px', color: '#fff', fontSize: '12px', cursor: 'pointer' }}>发布寻号</button>
我的寻号
</button>
</div> </div>
)} )}
</h3> </h3>