From ee728a5f4e472802fed98433d71f4b3695682418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Sun, 26 Apr 2026 21:13:00 +0800 Subject: [PATCH] =?UTF-8?q?v0.0.10=20-=20=E8=AE=A4=E8=B4=AD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/main.py | 2 + backend/app/models/purchase.py | 55 +++ backend/app/routers/purchase.py | 276 ++++++++++++++ config/VERSION.json | 19 +- frontend/index.html | 2 +- frontend/src/App.jsx | 2 + frontend/src/pages/Purchase.jsx | 632 ++++++++++++++++++++++++++++++++ 7 files changed, 981 insertions(+), 7 deletions(-) create mode 100644 backend/app/models/purchase.py create mode 100644 backend/app/routers/purchase.py create mode 100644 frontend/src/pages/Purchase.jsx diff --git a/backend/app/main.py b/backend/app/main.py index 0587121..6738fd6 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -19,6 +19,7 @@ from app.routers import information as information_router from app.routers import yichens as yichens_router from app.routers import seek as seek_router from app.routers import deal as deal_router +from app.routers import purchase as purchase_router # 版本信息 - 从 config/VERSION 文件读取 def get_version(): @@ -88,6 +89,7 @@ app.include_router(information_router.router) app.include_router(yichens_router.router) # 一尘看板 app.include_router(seek_router.router) # 寻配号 app.include_router(deal_router.router) # 成交行情 +app.include_router(purchase_router.router) # 认购群 @app.get("/") diff --git a/backend/app/models/purchase.py b/backend/app/models/purchase.py new file mode 100644 index 0000000..ef9d392 --- /dev/null +++ b/backend/app/models/purchase.py @@ -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()) \ No newline at end of file diff --git a/backend/app/routers/purchase.py b/backend/app/routers/purchase.py new file mode 100644 index 0000000..1663f27 --- /dev/null +++ b/backend/app/routers/purchase.py @@ -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": "认购群已结束"} \ No newline at end of file diff --git a/config/VERSION.json b/config/VERSION.json index d27821e..a263464 100644 --- a/config/VERSION.json +++ b/config/VERSION.json @@ -1,18 +1,18 @@ { - "version": "0.0.8", + "version": "0.0.9", "updated": "2026-04-24", "modules": { "frontend": { - "version": "0.0.8", + "version": "0.0.9", "pages": { "Add": "0.0.1", "Admin": "0.0.1", "Detail": "0.0.1", "Edit": "0.0.1", - "Home": "0.0.1", + "Home": "0.0.4", "List": "0.0.1", "Login": "0.0.1", - "News": "0.0.3", + "News": "0.0.8", "News_YichensBoard": "0.0.1", "Settings": "0.0.1", "Stats": "0.0.1", @@ -23,7 +23,7 @@ } }, "backend": { - "version": "0.0.8", + "version": "0.0.9", "routers": { "auth": "0.0.1", "collections": "0.0.1", @@ -32,7 +32,7 @@ "news": "0.0.1", "ocr": "0.0.1", "operations": "0.0.1", - "seek": "0.0.4", + "seek": "0.0.9", "users": "0.0.1", "yichens": "0.0.1" }, @@ -44,5 +44,12 @@ "utils": "0.0.1" } } + }, + "changelog": { + "0.0.9": [ + "Home.jsx: 修复寻配号数据统计API", + "News.jsx: 寻配号功能稳定版(自有匹配+网络匹配缓存)", + "seek.py: 路由优化,留言功能迁移,缓存机制" + ] } } \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 0bc01c7..2ba9e20 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v=0.0.5 + 甲辰收藏 v=0.0.9 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 34e17ce..f0edd39 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5,6 +5,7 @@ import List from './pages/List' import Add from './pages/Add' import Stats from './pages/Stats' import News from './pages/News' +import Purchase from './pages/Purchase' import Login from './pages/Login' import Detail from './pages/Detail' import Edit from './pages/Edit' @@ -30,6 +31,7 @@ export default function App() { const basePath = path.split('?')[0] if (basePath === '/') return if (basePath === '/news') return + if (basePath === '/purchase') return if (basePath === '/stats') return if (basePath === '/list') return if (basePath === '/add') return diff --git a/frontend/src/pages/Purchase.jsx b/frontend/src/pages/Purchase.jsx new file mode 100644 index 0000000..9452c08 --- /dev/null +++ b/frontend/src/pages/Purchase.jsx @@ -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) => ( +
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' + }} + > +
+
+
{group.name}
+
+ 群主: {group.creator_name} · {group.total_members}人参与 +
+
+ {showJoin && ( + + )} +
+
+ 藏品: {group.total_collections}件 + 总价: {formatMoney(group.total_amount)} + 均价: {formatMoney(group.avg_price)} +
+
+ ) + + // 群详情弹窗 + if (selectedGroup) { + return ( +
+ {/* 头部 */} +
+ +
+
{selectedGroup.name}
+
+ 群主: {selectedGroup.creator_name} · {selectedGroup.status === 'active' ? '进行中' : '已结束'} +
+
+ {selectedGroup.creator_id === user?.f99_90_id && selectedGroup.status === 'active' && ( + + )} +
+ + {/* 统计卡片 */} +
+
统计
+
+
+
{selectedGroup.total_members}
+
参与人数
+
+
+
{selectedGroup.total_collections}
+
认购数量
+
+
+
{formatMoney(selectedGroup.avg_price)}
+
藏品均价
+
+
+
+ 总价: + {formatMoney(selectedGroup.total_amount)} +
+
+ + {/* 成员列表 */} +
+
参与成员
+
+ {groupMembers.map(m => ( +
+
+ {m.user_name?.charAt(0) || '?'} +
+ {m.user_name} + {m.role === 'creator' && 👑} +
+ ))} + {groupMembers.length === 0 &&
暂无成员
} +
+
+ + {/* 认购藏品列表 */} +
+
认购藏品
+ {groupCollections.map(c => ( +
+
+
{c.collection_name}
+
+ {c.number} · {c.user_name} +
+
+
+ {formatMoney(c.price)} +
+
+ ))} + {groupCollections.length === 0 &&
暂无认购藏品
} +
+ + {/* 提交认购藏品表单 */} + {selectedGroup.status === 'active' && ( +
+
提交认购藏品
+
+ 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' + }} + /> + 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' + }} + /> +
+
+ 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' + }} + /> + 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' + }} + /> +
+ +
+ )} +
+ ) + } + + return ( +
+ {/* 头部 */} +
+
我的认购/团购
+ +
+ + {/* Tab切换 */} +
+ + +
+ + {/* 认购群列表 */} + {loading ? ( +
加载中...
+ ) : ( +
+ {(activeTab === 'my' ? myGroups : allGroups).map(group => + renderGroupCard(group, activeTab === 'all') + )} + {(activeTab === 'my' ? myGroups : allGroups).length === 0 && ( +
+ {activeTab === 'my' ? '您还没有参与任何认购群' : '暂无认购群'} +
+ )} +
+ )} + + {/* 创建认购群弹窗 */} + {showCreate && ( +
+
+
创建认购群
+ 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' + }} + /> +