v0.0.10 - 认购功能初版

This commit is contained in:
甲辰生产 2026-04-26 21:13:00 +08:00
parent d8aac9bc3e
commit ee728a5f4e
7 changed files with 981 additions and 7 deletions

View File

@ -19,6 +19,7 @@ from app.routers import information as information_router
from app.routers import yichens as yichens_router from app.routers import yichens as yichens_router
from app.routers import seek as seek_router from app.routers import seek as seek_router
from app.routers import deal as deal_router from app.routers import deal as deal_router
from app.routers import purchase as purchase_router
# 版本信息 - 从 config/VERSION 文件读取 # 版本信息 - 从 config/VERSION 文件读取
def get_version(): def get_version():
@ -88,6 +89,7 @@ app.include_router(information_router.router)
app.include_router(yichens_router.router) # 一尘看板 app.include_router(yichens_router.router) # 一尘看板
app.include_router(seek_router.router) # 寻配号 app.include_router(seek_router.router) # 寻配号
app.include_router(deal_router.router) # 成交行情 app.include_router(deal_router.router) # 成交行情
app.include_router(purchase_router.router) # 认购群
@app.get("/") @app.get("/")

View File

@ -0,0 +1,55 @@
# purchase - 认购群模型
# Version: 0.0.1 (2026-04-24)
# 认购群、成员、藏品管理
from sqlalchemy import Column, String, Text, Integer, DECIMAL, DateTime, ForeignKey
from sqlalchemy.sql import func
from app.core.database import Base
import uuid
def generate_uuid():
return str(uuid.uuid4())
class PurchaseGroup(Base):
"""认购群表"""
__tablename__ = "purchase_group"
id = Column(String(36), primary_key=True, default=generate_uuid)
name = Column(String(100), nullable=False) # 认购群名称
description = Column(Text, nullable=True) # 描述
creator_id = Column(String(36), nullable=False) # 创建者ID
creator_name = Column(String(50), nullable=True) # 创建者名字
status = Column(String(20), default="active") # active/closed
total_members = Column(Integer, default=0) # 总参与人数
total_collections = Column(Integer, default=0) # 总藏品数
total_amount = Column(DECIMAL(12,2), default=0) # 总金额
avg_price = Column(DECIMAL(10,2), default=0) # 均价
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
class PurchaseMember(Base):
"""认购成员表"""
__tablename__ = "purchase_member"
id = Column(String(36), primary_key=True, default=generate_uuid)
group_id = Column(String(36), ForeignKey("purchase_group.id"), nullable=False)
user_id = Column(String(36), nullable=False)
user_name = Column(String(50), nullable=True)
user_avatar = Column(String(255), nullable=True)
role = Column(String(20), default="member") # creator/admin/member
joined_at = Column(DateTime(timezone=True), server_default=func.now())
class PurchaseCollection(Base):
"""认购藏品表"""
__tablename__ = "purchase_collection"
id = Column(String(36), primary_key=True, default=generate_uuid)
group_id = Column(String(36), ForeignKey("purchase_group.id"), nullable=False)
user_id = Column(String(36), nullable=False)
user_name = Column(String(50), nullable=True)
collection_name = Column(String(100), nullable=True) # 藏品名称
collection_code = Column(String(50), nullable=True) # 藏品编号
number = Column(String(50), nullable=True) # 冠字号
price = Column(DECIMAL(10,2), nullable=False) # 认购价格
status = Column(String(20), default="pending") # pending/confirmed
submitted_at = Column(DateTime(timezone=True), server_default=func.now())

View File

@ -0,0 +1,276 @@
# purchase - 认购群API路由
# Version: 0.0.1 (2026-04-24)
# 认购群、成员、藏品管理
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
from app.core.database import get_db
from app.core.auth import get_current_user
from app.models.models import User
from app.models.purchase import PurchaseGroup, PurchaseMember, PurchaseCollection
router = APIRouter(prefix="/api/purchase", tags=["认购"])
# ============ Schema ============
class PurchaseGroupCreate(BaseModel):
name: str
description: Optional[str] = None
class PurchaseGroupResponse(BaseModel):
id: str
name: str
description: Optional[str]
creator_id: str
creator_name: Optional[str]
status: str
total_members: int
total_collections: int
total_amount: float
avg_price: float
created_at: Optional[datetime]
class PurchaseCollectionCreate(BaseModel):
collection_name: str
collection_code: Optional[str] = None
number: Optional[str] = None
price: float
# ============ 认购群API ============
@router.get("/groups", response_model=List[PurchaseGroupResponse])
def get_groups(
status: str = Query("active", description="过滤状态"),
db: Session = Depends(get_db)
):
"""获取所有认购群列表"""
query = db.query(PurchaseGroup)
if status:
query = query.filter(PurchaseGroup.status == status)
groups = query.order_by(PurchaseGroup.created_at.desc()).all()
return groups
@router.get("/my-groups", response_model=List[PurchaseGroupResponse])
def get_my_groups(
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取我参与的认购群"""
member_groups = db.query(PurchaseMember).filter(
PurchaseMember.user_id == current_user.f99_90_id
).all()
group_ids = [m.group_id for m in member_groups]
groups = db.query(PurchaseGroup).filter(
PurchaseGroup.id.in_(group_ids)
).all()
return groups
@router.post("/groups", response_model=PurchaseGroupResponse)
def create_group(
data: PurchaseGroupCreate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""创建认购群(创建者即为群主)"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
group = PurchaseGroup(
name=data.name,
description=data.description,
creator_id=current_user.f99_90_id,
creator_name=current_user.f01_01_name,
status="active"
)
db.add(group)
db.commit()
# 创建者自动加入并成为群主
member = PurchaseMember(
group_id=group.id,
user_id=current_user.f99_90_id,
user_name=current_user.f01_01_name,
user_avatar=current_user.avatar,
role="creator"
)
db.add(member)
db.commit()
db.refresh(group)
return group
@router.get("/groups/{group_id}", response_model=PurchaseGroupResponse)
def get_group(
group_id: str,
db: Session = Depends(get_db)
):
"""获取认购群详情"""
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
return group
@router.post("/groups/{group_id}/join")
def join_group(
group_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""加入认购群"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
# 检查群是否存在
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
if group.status != "active":
raise HTTPException(status_code=400, detail="该认购群已结束")
# 检查是否已加入
existing = db.query(PurchaseMember).filter(
PurchaseMember.group_id == group_id,
PurchaseMember.user_id == current_user.f99_90_id
).first()
if existing:
raise HTTPException(status_code=400, detail="您已加入该认购群")
# 加入
member = PurchaseMember(
group_id=group_id,
user_id=current_user.f99_90_id,
user_name=current_user.f01_01_name,
user_avatar=current_user.avatar,
role="member"
)
db.add(member)
# 更新群成员数
group.total_members += 1
db.commit()
return {"message": "加入成功"}
@router.get("/groups/{group_id}/members")
def get_group_members(
group_id: str,
db: Session = Depends(get_db)
):
"""获取认购群成员列表"""
members = db.query(PurchaseMember).filter(
PurchaseMember.group_id == group_id
).order_by(PurchaseMember.joined_at.asc()).all()
return [
{
"id": m.id,
"user_id": m.user_id,
"user_name": m.user_name,
"user_avatar": m.user_avatar,
"role": m.role,
"joined_at": m.joined_at
}
for m in members
]
@router.get("/groups/{group_id}/collections")
def get_group_collections(
group_id: str,
db: Session = Depends(get_db)
):
"""获取认购群藏品列表"""
collections = db.query(PurchaseCollection).filter(
PurchaseCollection.group_id == group_id
).order_by(PurchaseCollection.submitted_at.desc()).all()
return [
{
"id": c.id,
"user_id": c.user_id,
"user_name": c.user_name,
"collection_name": c.collection_name,
"collection_code": c.collection_code,
"number": c.number,
"price": float(c.price),
"status": c.status,
"submitted_at": c.submitted_at
}
for c in collections
]
@router.post("/groups/{group_id}/collections")
def add_collection(
group_id: str,
data: PurchaseCollectionCreate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""提交认购藏品"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
# 检查群是否存在
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
# 检查是否已加入
member = db.query(PurchaseMember).filter(
PurchaseMember.group_id == group_id,
PurchaseMember.user_id == current_user.f99_90_id
).first()
if not member:
raise HTTPException(status_code=400, detail="请先加入该认购群")
# 添加藏品
collection = PurchaseCollection(
group_id=group_id,
user_id=current_user.f99_90_id,
user_name=current_user.f01_01_name,
collection_name=data.collection_name,
collection_code=data.collection_code,
number=data.number,
price=data.price
)
db.add(collection)
# 更新群统计
group.total_collections += 1
group.total_amount += float(data.price)
if group.total_collections > 0:
group.avg_price = group.total_amount / group.total_collections
db.commit()
db.refresh(collection)
return collection
@router.post("/groups/{group_id}/close")
def close_group(
group_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""结束认购群(仅群主可操作)"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
# 检查是否为群主
if group.creator_id != current_user.f99_90_id:
raise HTTPException(status_code=403, detail="只有群主可以结束认购群")
group.status = "closed"
db.commit()
return {"message": "认购群已结束"}

View File

@ -1,18 +1,18 @@
{ {
"version": "0.0.8", "version": "0.0.9",
"updated": "2026-04-24", "updated": "2026-04-24",
"modules": { "modules": {
"frontend": { "frontend": {
"version": "0.0.8", "version": "0.0.9",
"pages": { "pages": {
"Add": "0.0.1", "Add": "0.0.1",
"Admin": "0.0.1", "Admin": "0.0.1",
"Detail": "0.0.1", "Detail": "0.0.1",
"Edit": "0.0.1", "Edit": "0.0.1",
"Home": "0.0.1", "Home": "0.0.4",
"List": "0.0.1", "List": "0.0.1",
"Login": "0.0.1", "Login": "0.0.1",
"News": "0.0.3", "News": "0.0.8",
"News_YichensBoard": "0.0.1", "News_YichensBoard": "0.0.1",
"Settings": "0.0.1", "Settings": "0.0.1",
"Stats": "0.0.1", "Stats": "0.0.1",
@ -23,7 +23,7 @@
} }
}, },
"backend": { "backend": {
"version": "0.0.8", "version": "0.0.9",
"routers": { "routers": {
"auth": "0.0.1", "auth": "0.0.1",
"collections": "0.0.1", "collections": "0.0.1",
@ -32,7 +32,7 @@
"news": "0.0.1", "news": "0.0.1",
"ocr": "0.0.1", "ocr": "0.0.1",
"operations": "0.0.1", "operations": "0.0.1",
"seek": "0.0.4", "seek": "0.0.9",
"users": "0.0.1", "users": "0.0.1",
"yichens": "0.0.1" "yichens": "0.0.1"
}, },
@ -44,5 +44,12 @@
"utils": "0.0.1" "utils": "0.0.1"
} }
} }
},
"changelog": {
"0.0.9": [
"Home.jsx: 修复寻配号数据统计API",
"News.jsx: 寻配号功能稳定版(自有匹配+网络匹配缓存)",
"seek.py: 路由优化,留言功能迁移,缓存机制"
]
} }
} }

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<title>甲辰收藏 v=0.0.5</title> <title>甲辰收藏 v=0.0.9</title>
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="/static/icons/favicon.png" /> <link rel="icon" type="image/png" href="/static/icons/favicon.png" />

View File

@ -5,6 +5,7 @@ import List from './pages/List'
import Add from './pages/Add' import Add from './pages/Add'
import Stats from './pages/Stats' import Stats from './pages/Stats'
import News from './pages/News' import News from './pages/News'
import Purchase from './pages/Purchase'
import Login from './pages/Login' import Login from './pages/Login'
import Detail from './pages/Detail' import Detail from './pages/Detail'
import Edit from './pages/Edit' import Edit from './pages/Edit'
@ -30,6 +31,7 @@ export default function App() {
const basePath = path.split('?')[0] const basePath = path.split('?')[0]
if (basePath === '/') return <Home /> if (basePath === '/') return <Home />
if (basePath === '/news') return <News /> if (basePath === '/news') return <News />
if (basePath === '/purchase') return <Purchase />
if (basePath === '/stats') return <Stats /> if (basePath === '/stats') return <Stats />
if (basePath === '/list') return <List /> if (basePath === '/list') return <List />
if (basePath === '/add') return <Add /> if (basePath === '/add') return <Add />

View File

@ -0,0 +1,632 @@
/**
* Purchase - 我的认购/团购页面
* Version: 0.0.1 (2026-04-24)
* 认购群管理加入查看详情
*/
import React, { useState, useEffect } from 'react'
import YichensBoard from './YichensBoard'
const API_BASE = ''
function Purchase() {
const [user, setUser] = useState(null)
const [myGroups, setMyGroups] = useState([])
const [allGroups, setAllGroups] = useState([])
const [showCreate, setShowCreate] = useState(false)
const [selectedGroup, setSelectedGroup] = useState(null)
const [groupMembers, setGroupMembers] = useState([])
const [groupCollections, setGroupCollections] = useState([])
const [newGroup, setNewGroup] = useState({ name: '', description: '' })
const [newCollection, setNewCollection] = useState({ collection_name: '', collection_code: '', number: '', price: '' })
const [loading, setLoading] = useState(true)
const [activeTab, setActiveTab] = useState('my') // my/all
useEffect(() => {
const userData = localStorage.getItem('user')
if (userData) {
try {
setUser(JSON.parse(userData))
} catch (e) {
console.error('Parse user error:', e)
}
}
fetchGroups()
}, [])
const fetchGroups = async () => {
setLoading(true)
const token = localStorage.getItem('token')
try {
//
const myRes = await fetch(`${API_BASE}/api/purchase/my-groups`, {
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
})
const myData = await myRes.json()
setMyGroups(myData || [])
//
const allRes = await fetch(`${API_BASE}/api/purchase/groups`)
const allData = await allRes.json()
setAllGroups(allData || [])
} catch (e) {
console.error('获取认购群失败:', e)
}
setLoading(false)
}
const createGroup = async () => {
const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return }
if (!newGroup.name.trim()) { alert('请输入认购群名称'); return }
try {
const res = await fetch(`${API_BASE}/api/purchase/groups`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(newGroup)
})
const data = await res.json()
if (res.ok) {
alert('创建成功!')
setShowCreate(false)
setNewGroup({ name: '', description: '' })
fetchGroups()
} else {
alert(data.detail || '创建失败')
}
} catch (e) {
console.error('创建失败:', e)
alert('创建失败')
}
}
const joinGroup = async (groupId) => {
const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return }
try {
const res = await fetch(`${API_BASE}/api/purchase/groups/${groupId}/join`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
})
const data = await res.json()
if (res.ok) {
alert('加入成功!')
fetchGroups()
} else {
alert(data.detail || '加入失败')
}
} catch (e) {
console.error('加入失败:', e)
alert('加入失败')
}
}
const viewGroupDetail = async (groupId) => {
const token = localStorage.getItem('token')
try {
//
const membersRes = await fetch(`${API_BASE}/api/purchase/groups/${groupId}/members`, {
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
})
const membersData = await membersRes.json()
setGroupMembers(membersData || [])
//
const collectionsRes = await fetch(`${API_BASE}/api/purchase/groups/${groupId}/collections`, {
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
})
const collectionsData = await collectionsRes.json()
setGroupCollections(collectionsData || [])
//
const group = allGroups.find(g => g.id === groupId) || myGroups.find(g => g.id === groupId)
setSelectedGroup(group)
} catch (e) {
console.error('获取详情失败:', e)
}
}
const submitCollection = async () => {
const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return }
if (!selectedGroup) return
if (!newCollection.collection_name || !newCollection.price) {
alert('请填写藏品名称和价格'); return
}
try {
const res = await fetch(`${API_BASE}/api/purchase/groups/${selectedGroup.id}/collections`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
collection_name: newCollection.collection_name,
collection_code: newCollection.collection_code,
number: newCollection.number,
price: parseFloat(newCollection.price)
})
})
const data = await res.json()
if (res.ok) {
alert('提交成功!')
setNewCollection({ collection_name: '', collection_code: '', number: '', price: '' })
viewGroupDetail(selectedGroup.id)
fetchGroups()
} else {
alert(data.detail || '提交失败')
}
} catch (e) {
console.error('提交失败:', e)
alert('提交失败')
}
}
const closeGroup = async () => {
const token = localStorage.getItem('token')
if (!token || !selectedGroup) return
if (!confirm('确定要结束该认购群吗?')) return
try {
const res = await fetch(`${API_BASE}/api/purchase/groups/${selectedGroup.id}/close`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
})
const data = await res.json()
if (res.ok) {
alert('已结束认购群')
setSelectedGroup(null)
fetchGroups()
} else {
alert(data.detail || '操作失败')
}
} catch (e) {
console.error('操作失败:', e)
}
}
const formatMoney = (val) => {
if (!val || val === 0) return '¥0'
return '¥' + parseFloat(val).toLocaleString()
}
//
const renderGroupCard = (group, showJoin = false) => (
<div
key={group.id}
onClick={() => viewGroupDetail(group.id)}
style={{
background: 'linear-gradient(135deg, rgba(59,130,246,0.15) 0%, rgba(59,130,246,0.05) 100%)',
borderRadius: '12px',
padding: '16px',
border: '1px solid rgba(59,130,246,0.2)',
cursor: 'pointer',
marginBottom: '12px'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={{ color: '#fff', fontSize: '16px', fontWeight: 'bold' }}>{group.name}</div>
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px', marginTop: '4px' }}>
群主: {group.creator_name} · {group.total_members}人参与
</div>
</div>
{showJoin && (
<button
onClick={(e) => { e.stopPropagation(); joinGroup(group.id) }}
style={{
background: '#3b82f6',
color: '#fff',
border: 'none',
borderRadius: '6px',
padding: '8px 16px',
fontSize: '13px',
cursor: 'pointer'
}}
>
加入
</button>
)}
</div>
<div style={{ display: 'flex', gap: '16px', marginTop: '12px', color: 'rgba(255,255,255,0.7)', fontSize: '13px' }}>
<span>藏品: {group.total_collections}</span>
<span>总价: {formatMoney(group.total_amount)}</span>
<span>均价: {formatMoney(group.avg_price)}</span>
</div>
</div>
)
//
if (selectedGroup) {
return (
<div style={{
minHeight: '100vh',
background: '#0f172a',
color: '#fff',
padding: '16px'
}}>
{/* 头部 */}
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}>
<button
onClick={() => setSelectedGroup(null)}
style={{
background: 'transparent',
border: 'none',
color: '#fff',
fontSize: '24px',
cursor: 'pointer',
marginRight: '12px'
}}
>
</button>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '18px', fontWeight: 'bold' }}>{selectedGroup.name}</div>
<div style={{ fontSize: '12px', color: 'rgba(255,255,255,0.5)' }}>
群主: {selectedGroup.creator_name} · {selectedGroup.status === 'active' ? '进行中' : '已结束'}
</div>
</div>
{selectedGroup.creator_id === user?.f99_90_id && selectedGroup.status === 'active' && (
<button
onClick={closeGroup}
style={{
background: '#ef4444',
color: '#fff',
border: 'none',
borderRadius: '6px',
padding: '8px 12px',
fontSize: '13px',
cursor: 'pointer'
}}
>
结束
</button>
)}
</div>
{/* 统计卡片 */}
<div style={{
background: 'linear-gradient(135deg, rgba(245,158,11,0.15) 0%, rgba(245,158,11,0.05) 100%)',
borderRadius: '12px',
padding: '16px',
border: '1px solid rgba(245,158,11,0.2)',
marginBottom: '16px'
}}>
<div style={{ color: 'rgba(255,255,255,0.7)', fontSize: '13px', marginBottom: '8px' }}>统计</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px', textAlign: 'center' }}>
<div>
<div style={{ color: '#fbbf24', fontSize: '20px', fontWeight: 'bold' }}>{selectedGroup.total_members}</div>
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '11px' }}>参与人数</div>
</div>
<div>
<div style={{ color: '#34d399', fontSize: '20px', fontWeight: 'bold' }}>{selectedGroup.total_collections}</div>
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '11px' }}>认购数量</div>
</div>
<div>
<div style={{ color: '#a78bfa', fontSize: '20px', fontWeight: 'bold' }}>{formatMoney(selectedGroup.avg_price)}</div>
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '11px' }}>藏品均价</div>
</div>
</div>
<div style={{ marginTop: '12px', paddingTop: '12px', borderTop: '1px solid rgba(255,255,255,0.1)', textAlign: 'center' }}>
<span style={{ color: 'rgba(255,255,255,0.7)', fontSize: '13px' }}>总价: </span>
<span style={{ color: '#fff', fontSize: '16px', fontWeight: 'bold' }}>{formatMoney(selectedGroup.total_amount)}</span>
</div>
</div>
{/* 成员列表 */}
<div style={{ marginBottom: '16px' }}>
<div style={{ color: 'rgba(255,255,255,0.7)', fontSize: '14px', marginBottom: '12px' }}>参与成员</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{groupMembers.map(m => (
<div key={m.id} style={{
background: 'rgba(255,255,255,0.1)',
borderRadius: '20px',
padding: '6px 12px',
display: 'flex',
alignItems: 'center',
gap: '6px'
}}>
<div style={{ width: '20px', height: '20px', borderRadius: '50%', background: '#3b82f6', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '10px' }}>
{m.user_name?.charAt(0) || '?'}
</div>
<span style={{ fontSize: '12px', color: '#fff' }}>{m.user_name}</span>
{m.role === 'creator' && <span style={{ fontSize: '10px', color: '#fbbf24' }}>👑</span>}
</div>
))}
{groupMembers.length === 0 && <div style={{ color: 'rgba(255,255,255,0.3)', fontSize: '13px' }}>暂无成员</div>}
</div>
</div>
{/* 认购藏品列表 */}
<div>
<div style={{ color: 'rgba(255,255,255,0.7)', fontSize: '14px', marginBottom: '12px' }}>认购藏品</div>
{groupCollections.map(c => (
<div key={c.id} style={{
background: 'rgba(255,255,255,0.05)',
borderRadius: '8px',
padding: '12px',
marginBottom: '8px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<div>
<div style={{ color: '#fff', fontSize: '14px' }}>{c.collection_name}</div>
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '12px' }}>
{c.number} · {c.user_name}
</div>
</div>
<div style={{ color: '#34d399', fontSize: '16px', fontWeight: 'bold' }}>
{formatMoney(c.price)}
</div>
</div>
))}
{groupCollections.length === 0 && <div style={{ color: 'rgba(255,255,255,0.3)', fontSize: '13px', textAlign: 'center', padding: '20px' }}>暂无认购藏品</div>}
</div>
{/* 提交认购藏品表单 */}
{selectedGroup.status === 'active' && (
<div style={{
position: 'fixed',
bottom: '20px',
left: '16px',
right: '16px',
background: '#1e293b',
borderRadius: '12px',
padding: '16px',
border: '1px solid rgba(255,255,255,0.1)'
}}>
<div style={{ color: '#fff', fontSize: '14px', marginBottom: '12px' }}>提交认购藏品</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '12px' }}>
<input
value={newCollection.collection_name}
onChange={(e) => setNewCollection({...newCollection, collection_name: e.target.value})}
placeholder="藏品名称"
style={{
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '6px',
padding: '10px',
color: '#fff',
fontSize: '13px'
}}
/>
<input
value={newCollection.number}
onChange={(e) => setNewCollection({...newCollection, number: e.target.value})}
placeholder="冠字号"
style={{
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '6px',
padding: '10px',
color: '#fff',
fontSize: '13px'
}}
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '12px' }}>
<input
value={newCollection.collection_code}
onChange={(e) => setNewCollection({...newCollection, collection_code: e.target.value})}
placeholder="编号(可选)"
style={{
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '6px',
padding: '10px',
color: '#fff',
fontSize: '13px'
}}
/>
<input
value={newCollection.price}
onChange={(e) => setNewCollection({...newCollection, price: e.target.value})}
placeholder="价格"
type="number"
style={{
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '6px',
padding: '10px',
color: '#fff',
fontSize: '13px'
}}
/>
</div>
<button
onClick={submitCollection}
style={{
width: '100%',
background: '#3b82f6',
color: '#fff',
border: 'none',
borderRadius: '8px',
padding: '12px',
fontSize: '14px',
fontWeight: 'bold',
cursor: 'pointer'
}}
>
提交认购
</button>
</div>
)}
</div>
)
}
return (
<div style={{
minHeight: '100vh',
background: '#0f172a',
color: '#fff',
padding: '16px'
}}>
{/* 头部 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
<div style={{ fontSize: '20px', fontWeight: 'bold' }}>我的认购/团购</div>
<button
onClick={() => setShowCreate(true)}
style={{
background: '#3b82f6',
color: '#fff',
border: 'none',
borderRadius: '8px',
padding: '10px 16px',
fontSize: '14px',
cursor: 'pointer'
}}
>
+ 创建认购群
</button>
</div>
{/* Tab切换 */}
<div style={{ display: 'flex', gap: '8px', marginBottom: '20px' }}>
<button
onClick={() => setActiveTab('my')}
style={{
flex: 1,
background: activeTab === 'my' ? '#3b82f6' : 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius: '8px',
padding: '12px',
fontSize: '14px',
cursor: 'pointer'
}}
>
我参与的 ({myGroups.length})
</button>
<button
onClick={() => setActiveTab('all')}
style={{
flex: 1,
background: activeTab === 'all' ? '#3b82f6' : 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius: '8px',
padding: '12px',
fontSize: '14px',
cursor: 'pointer'
}}
>
所有认购群 ({allGroups.length})
</button>
</div>
{/* 认购群列表 */}
{loading ? (
<div style={{ textAlign: 'center', padding: '40px', color: 'rgba(255,255,255,0.5)' }}>加载中...</div>
) : (
<div>
{(activeTab === 'my' ? myGroups : allGroups).map(group =>
renderGroupCard(group, activeTab === 'all')
)}
{(activeTab === 'my' ? myGroups : allGroups).length === 0 && (
<div style={{ textAlign: 'center', padding: '40px', color: 'rgba(255,255,255,0.3)' }}>
{activeTab === 'my' ? '您还没有参与任何认购群' : '暂无认购群'}
</div>
)}
</div>
)}
{/* 创建认购群弹窗 */}
{showCreate && (
<div style={{
position: 'fixed',
top: 0, left: 0, right: 0, bottom: 0,
background: 'rgba(0,0,0,0.8)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 100
}}>
<div style={{
background: '#1e293b',
borderRadius: '12px',
padding: '20px',
width: '90%',
maxWidth: '400px'
}}>
<div style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>创建认购群</div>
<input
value={newGroup.name}
onChange={(e) => setNewGroup({...newGroup, name: e.target.value})}
placeholder="认购群名称"
style={{
width: '100%',
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '8px',
padding: '12px',
color: '#fff',
fontSize: '14px',
marginBottom: '12px'
}}
/>
<textarea
value={newGroup.description}
onChange={(e) => setNewGroup({...newGroup, description: e.target.value})}
placeholder="描述(可选)"
rows={3}
style={{
width: '100%',
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '8px',
padding: '12px',
color: '#fff',
fontSize: '14px',
marginBottom: '16px',
resize: 'none'
}}
/>
<div style={{ display: 'flex', gap: '8px' }}>
<button
onClick={() => setShowCreate(false)}
style={{
flex: 1,
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius: '8px',
padding: '12px',
fontSize: '14px',
cursor: 'pointer'
}}
>
取消
</button>
<button
onClick={createGroup}
style={{
flex: 1,
background: '#3b82f6',
color: '#fff',
border: 'none',
borderRadius: '8px',
padding: '12px',
fontSize: '14px',
cursor: 'pointer'
}}
>
创建
</button>
</div>
</div>
</div>
)}
</div>
)
}
export default Purchase