Compare commits
2 Commits
645819844e
...
98b3917acc
| Author | SHA1 | Date |
|---|---|---|
|
|
98b3917acc | |
|
|
02711d73c9 |
|
|
@ -1 +1 @@
|
|||
1.2.98
|
||||
VERSION=v0.2.1
|
||||
|
|
|
|||
|
|
@ -91,21 +91,7 @@ def register(user_data: UserCreate, db: Session = Depends(get_db)):
|
|||
# 更新:
|
||||
|
||||
# 更新:
|
||||
# 检查手机号是否已存在
|
||||
# 更新:
|
||||
if user_data.phone:
|
||||
# 更新:
|
||||
existing_phone = db.query(User).filter(User.phone == user_data.phone).first()
|
||||
# 更新:
|
||||
if existing_phone:
|
||||
# 更新:
|
||||
raise HTTPException(
|
||||
# 更新:
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
# 更新:
|
||||
detail="E00040:该手机号已被注册,请更换手机号"
|
||||
# 更新:
|
||||
)
|
||||
# 检查手机号是否已存在(移除,2026-06-13)
|
||||
# 更新:
|
||||
if user_data.email:
|
||||
# 更新:
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ def get_information_list(
|
|||
user_id: Optional[str] = Query(None, description="用户ID,用于获取该用户的行情"),
|
||||
deal_date: Optional[str] = Query(None, description="成交日期过滤,格式YYYY-MM-DD"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
page_size: int = Query(20, ge=1, le=1000),
|
||||
current_user: Optional[User] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
response: Response = None
|
||||
|
|
@ -880,7 +880,7 @@ def get_deal_stats(
|
|||
@router.get("/my/list", response_model=List[InformationResponse])
|
||||
def get_my_information_list(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
page_size: int = Query(20, ge=1, le=1000),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class UserBase(BaseModel):
|
|||
class UserCreate(UserBase):
|
||||
password: str = Field(..., min_length=6)
|
||||
invite_code: Optional[str] = Field(None, alias="inviteCode") # 填写的邀请码(选填)
|
||||
# 移除手机号和短信验证(2026-06-13)
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,41 @@
|
|||
# 版本更新记录
|
||||
|
||||
## v0.2.7 (2026-07-19) - 龙钞宣传功能
|
||||
|
||||
### 前端更新
|
||||
- **Wiki.jsx (新增)**
|
||||
- 龙钞百科页面,包含:什么是龙钞、号码分类、术语解释、评级知识、保养指南、交易指南
|
||||
- 新手入门必备指南
|
||||
|
||||
- **SharePoster.jsx (新增)**
|
||||
- 藏品分享海报组件
|
||||
- 一键生成精美海报,朋友圈分享
|
||||
|
||||
- **List.jsx**
|
||||
- 新增分享按钮,每个藏品卡片右上角显示"📤 分享"按钮
|
||||
- 点击生成精美海报,保存到相册
|
||||
|
||||
- **Home.jsx**
|
||||
- 快捷操作新增"📚 龙钞百科"入口
|
||||
|
||||
### 依赖更新
|
||||
- 新增 html2canvas 库,用于生成海报图片
|
||||
|
||||
---
|
||||
|
||||
## v0.2.5 (2026-05-13)
|
||||
|
||||
### 前端更新
|
||||
- **News.jsx**
|
||||
- 移除成交行情价格统计表格,只显示筛选后的结果
|
||||
- 成交行情获取数量改为500条
|
||||
|
||||
- **YichensBoard.jsx**
|
||||
- 增加复制链接功能,点击可复制原帖链接
|
||||
|
||||
### 环境配置
|
||||
- C环境Nginx配置为代理到B环境后端
|
||||
|
||||
## v0.2.3 (2026-05-11)
|
||||
|
||||
### 前端更新
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
VERSION=v0.1.8
|
||||
VERSION=0.2.5
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
{
|
||||
"version": "0.2.4",
|
||||
"updated": "2026-05-11",
|
||||
"version": "0.2.7",
|
||||
"updated": "2026-07-19",
|
||||
"modules": {
|
||||
"frontend": {
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.3",
|
||||
"pages": {
|
||||
"Login": "0.0.2",
|
||||
"Add": "0.0.2",
|
||||
"News": "0.2.0"
|
||||
"News": "0.1.2",
|
||||
"YichensBoard": "0.0.5",
|
||||
"Wiki": "1.0.0",
|
||||
"SharePoster": "1.0.0",
|
||||
"List": "0.1.7"
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"routers": {
|
||||
"auth": "0.0.2",
|
||||
"seek": "0.2.0",
|
||||
"purchase": "0.3.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<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">
|
||||
<title>甲辰收藏 v=0.0.9</title>
|
||||
<title>甲辰收藏 v0.2.5</title>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="/static/icons/favicon.png" />
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"version": "1.2.82",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"html2canvas": "^1.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.1.0"
|
||||
|
|
@ -1239,6 +1240,15 @@
|
|||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.8",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz",
|
||||
|
|
@ -1352,6 +1362,15 @@
|
|||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
|
|
@ -1673,6 +1692,19 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
|
@ -1998,6 +2030,15 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
|
|
@ -2046,6 +2087,15 @@
|
|||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.2.tgz",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"html2canvas": "^1.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.1.0"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Login from './pages/Login'
|
|||
import Detail from './pages/Detail'
|
||||
import Edit from './pages/Edit'
|
||||
import Admin from './pages/Admin'
|
||||
import Wiki from './pages/Wiki'
|
||||
|
||||
export default function App() {
|
||||
const [path, setPath] = useState(window.location.hash.slice(1) || '/')
|
||||
|
|
@ -38,6 +39,7 @@ export default function App() {
|
|||
if (basePath === '/login') return <Login />
|
||||
if (basePath === '/settings') return <Settings />
|
||||
if (basePath === '/admin') return <Admin />
|
||||
if (basePath === '/wiki') return <Wiki />
|
||||
if (basePath.startsWith('/edit')) return <Edit />
|
||||
if (basePath.startsWith('/detail')) return <Detail />
|
||||
return <Home />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,404 @@
|
|||
/**
|
||||
* SharePoster - 藏品分享海报组件
|
||||
* 生成精美的藏品分享图片,用于朋友圈分享
|
||||
* Version: 1.0.0
|
||||
*/
|
||||
|
||||
import React, { useState, useRef } from 'react'
|
||||
import html2canvas from 'html2canvas'
|
||||
|
||||
export default function SharePoster({ collection, onClose }) {
|
||||
const posterRef = useRef(null)
|
||||
const [generating, setGenerating] = useState(false)
|
||||
const [generated, setGenerated] = useState(false)
|
||||
|
||||
// 获取号码分类颜色
|
||||
const getNumberCategoryColor = (cat) => {
|
||||
const colors = {
|
||||
'圆圆号': '#ef4444',
|
||||
'倒置号': '#f97316',
|
||||
'金马王': '#eab308',
|
||||
'金马号': '#84cc16',
|
||||
'金山王': '#22c55e',
|
||||
'天马王': '#14b8a6',
|
||||
'金山号': '#06b6d4',
|
||||
'天马号': '#0ea5e9',
|
||||
'朦胧王': '#3b82f6',
|
||||
'朦胧号': '#6366f1',
|
||||
'如意号': '#8b5cf6',
|
||||
'钻石号': '#a855f7',
|
||||
'永恒号': '#d946ef',
|
||||
'带7号': '#ec4899',
|
||||
'带4号': '#f43f5e',
|
||||
}
|
||||
return colors[cat] || '#94a3b8'
|
||||
}
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'in_collection': '#22c55e',
|
||||
'selling': '#f59e0b',
|
||||
'sold': '#ef4444',
|
||||
'grading': '#06b6d4',
|
||||
'repairing': '#8b5cf6',
|
||||
'transit': '#6366f1',
|
||||
'seeking': '#f97316',
|
||||
'other': '#94a3b8'
|
||||
}
|
||||
return colors[status] || '#94a3b8'
|
||||
}
|
||||
|
||||
// 获取状态文字
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
'in_collection': '收藏中',
|
||||
'selling': '出售中',
|
||||
'sold': '已售',
|
||||
'grading': '送评中',
|
||||
'repairing': '修复中',
|
||||
'transit': '在途中',
|
||||
'seeking': '寻号中',
|
||||
'other': '其他'
|
||||
}
|
||||
return texts[status] || status
|
||||
}
|
||||
|
||||
// 格式化冠字号
|
||||
const formatPrefixSerial = (ps) => {
|
||||
if (!ps) return '-'
|
||||
const match = ps.match(/^([A-Z]+)(\d+)$/i)
|
||||
if (match) {
|
||||
return match[1].toUpperCase() + ' ' + match[2].match(/.{1,4}/g).join(' ')
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
// 生成海报图片
|
||||
const generatePoster = async () => {
|
||||
if (!posterRef.current) return
|
||||
|
||||
setGenerating(true)
|
||||
try {
|
||||
const canvas = await html2canvas(posterRef.current, {
|
||||
scale: 2,
|
||||
backgroundColor: '#0f172a',
|
||||
useCORS: true,
|
||||
allowTaint: true
|
||||
})
|
||||
|
||||
// 转换为图片并下载
|
||||
const link = document.createElement('a')
|
||||
link.download = `龙钞-${collection.code || collection.prefixSerial || '藏品'}.png`
|
||||
link.href = canvas.toDataURL('image/png')
|
||||
link.click()
|
||||
|
||||
setGenerated(true)
|
||||
} catch (e) {
|
||||
console.error('生成海报失败:', e)
|
||||
alert('生成海报失败,请重试')
|
||||
}
|
||||
setGenerating(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'rgba(0,0,0,0.9)',
|
||||
zIndex: 1000,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '20px'
|
||||
}}>
|
||||
{/* 标题栏 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
maxWidth: '400px',
|
||||
marginBottom: '20px'
|
||||
}}>
|
||||
<h3 style={{ color: '#fff', margin: 0, fontSize: '18px' }}>📤 生成分享海报</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
border: 'none',
|
||||
borderRadius: '50%',
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
color: '#fff',
|
||||
fontSize: '18px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>✕</button>
|
||||
</div>
|
||||
|
||||
{/* 海报预览区域 */}
|
||||
<div ref={posterRef} style={{
|
||||
width: '320px',
|
||||
minHeight: '480px',
|
||||
background: 'linear-gradient(135deg, #1e293b 0%, #0f172a 100%)',
|
||||
borderRadius: '16px',
|
||||
padding: '20px',
|
||||
boxSizing: 'border-box',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* 背景装饰 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '-50%',
|
||||
right: '-30%',
|
||||
width: '200px',
|
||||
height: '200px',
|
||||
background: 'radial-gradient(circle, rgba(251,191,36,0.15) 0%, transparent 70%)',
|
||||
borderRadius: '50%'
|
||||
}} />
|
||||
|
||||
{/* 头部 */}
|
||||
<div style={{ textAlign: 'center', marginBottom: '20px', position: 'relative' }}>
|
||||
<div style={{
|
||||
fontSize: '28px',
|
||||
fontWeight: 'bold',
|
||||
background: 'linear-gradient(135deg, #fbbf24, #f59e0b)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
marginBottom: '4px'
|
||||
}}>
|
||||
🐉 甲辰龙钞
|
||||
</div>
|
||||
<div style={{ color: '#94a3b8', fontSize: '11px' }}>
|
||||
一甲辰藏品管理系统
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 分割线 */}
|
||||
<div style={{
|
||||
height: '1px',
|
||||
background: 'linear-gradient(90deg, transparent, rgba(251,191,36,0.3), transparent)',
|
||||
marginBottom: '20px'
|
||||
}} />
|
||||
|
||||
{/* 藏品信息 */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
{/* 冠字号 - 大字体 */}
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
marginBottom: '16px',
|
||||
padding: '12px',
|
||||
background: 'rgba(251,191,36,0.1)',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid rgba(251,191,36,0.2)'
|
||||
}}>
|
||||
<div style={{ color: '#94a3b8', fontSize: '10px', marginBottom: '4px' }}>冠字号</div>
|
||||
<div style={{
|
||||
color: '#fbbf24',
|
||||
fontSize: '24px',
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: '2px'
|
||||
}}>
|
||||
{formatPrefixSerial(collection.prefixSerial)}
|
||||
</div>
|
||||
{collection.code && (
|
||||
<div style={{ color: '#64748b', fontSize: '12px', marginTop: '4px' }}>
|
||||
编号: {collection.code}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 基本信息网格 */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '16px' }}>
|
||||
<div style={{ background: 'rgba(255,255,255,0.03)', padding: '10px', borderRadius: '8px' }}>
|
||||
<div style={{ color: '#64748b', fontSize: '10px' }}>版别</div>
|
||||
<div style={{ color: '#fff', fontSize: '13px', fontWeight: 'bold' }}>{collection.version || '-'}</div>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(255,255,255,0.03)', padding: '10px', borderRadius: '8px' }}>
|
||||
<div style={{ color: '#64748b', fontSize: '10px' }}>包装</div>
|
||||
<div style={{ color: '#fff', fontSize: '13px', fontWeight: 'bold' }}>{collection.packaging || '-'}</div>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(255,255,255,0.03)', padding: '10px', borderRadius: '8px' }}>
|
||||
<div style={{ color: '#64748b', fontSize: '10px' }}>珍惜度</div>
|
||||
<div style={{ color: '#a78bfa', fontSize: '13px', fontWeight: 'bold' }}>{collection.rarity || '-'}</div>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(255,255,255,0.03)', padding: '10px', borderRadius: '8px' }}>
|
||||
<div style={{ color: '#64748b', fontSize: '10px' }}>状态</div>
|
||||
<div style={{ color: getStatusColor(collection.status), fontSize: '13px', fontWeight: 'bold' }}>
|
||||
{getStatusText(collection.status)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 号码分类标签 */}
|
||||
{collection.numberCategory && (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
marginBottom: '16px',
|
||||
padding: '8px',
|
||||
background: getNumberCategoryColor(collection.numberCategory) + '20',
|
||||
borderRadius: '8px',
|
||||
border: `1px solid ${getNumberCategoryColor(collection.numberCategory)}40`
|
||||
}}>
|
||||
<span style={{
|
||||
color: getNumberCategoryColor(collection.numberCategory),
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
⭐ {collection.numberCategory}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 评级信息 */}
|
||||
{collection.isGraded && (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
marginBottom: '16px',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
{collection.gradingCompany && (
|
||||
<span style={{
|
||||
background: 'rgba(6,182,212,0.15)',
|
||||
color: '#06b6d4',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '11px'
|
||||
}}>
|
||||
{collection.gradingCompany}
|
||||
</span>
|
||||
)}
|
||||
{collection.gradingScore && (
|
||||
<span style={{
|
||||
background: 'rgba(249,115,22,0.2)',
|
||||
color: '#f97316',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{collection.gradingScore}分
|
||||
</span>
|
||||
)}
|
||||
{collection.threeStar && (
|
||||
<span style={{
|
||||
background: 'rgba(6,182,212,0.15)',
|
||||
color: '#06b6d4',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
fontSize: '11px'
|
||||
}}>
|
||||
★★★
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 价格信息 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
padding: '12px',
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
borderRadius: '8px'
|
||||
}}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ color: '#64748b', fontSize: '10px' }}>成本</div>
|
||||
<div style={{ color: '#e2e8f0', fontSize: '14px' }}>
|
||||
{collection.costPrice ? `¥${collection.costPrice}` : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ color: '#64748b', fontSize: '10px' }}>目标价</div>
|
||||
<div style={{ color: '#fbbf24', fontSize: '14px' }}>
|
||||
{collection.targetPrice ? `¥${collection.targetPrice}` : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ color: '#64748b', fontSize: '10px' }}>售价</div>
|
||||
<div style={{ color: '#4ade80', fontSize: '14px' }}>
|
||||
{collection.goalPrice ? `¥${collection.goalPrice}` : '-'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '15px',
|
||||
left: '20px',
|
||||
right: '20px',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<div style={{
|
||||
color: '#64748b',
|
||||
fontSize: '10px',
|
||||
marginBottom: '4px'
|
||||
}}>
|
||||
长按识别二维码了解更多
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<span style={{ fontSize: '20px' }}>🐉</span>
|
||||
<span style={{ color: '#94a3b8', fontSize: '11px' }}>jiachenlong.com</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
marginTop: '20px',
|
||||
width: '100%',
|
||||
maxWidth: '400px'
|
||||
}}>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '14px',
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
border: 'none',
|
||||
borderRadius: '12px',
|
||||
color: '#fff',
|
||||
fontSize: '15px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={generatePoster}
|
||||
disabled={generating}
|
||||
style={{
|
||||
flex: 2,
|
||||
padding: '14px',
|
||||
background: generating ? '#94a3b8' : 'linear-gradient(135deg, #fbbf24, #f59e0b)',
|
||||
border: 'none',
|
||||
borderRadius: '12px',
|
||||
color: '#1e293b',
|
||||
fontSize: '15px',
|
||||
fontWeight: 'bold',
|
||||
cursor: generating ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
>
|
||||
{generating ? '生成中...' : generated ? '已保存 ✓' : '保存到相册'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -241,6 +241,21 @@ export default function Home() {
|
|||
<div style={{ color: '#3b82f6', fontSize: '18px', fontWeight: '600', whiteSpace: 'nowrap' }}>我的认购</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '10px', marginTop: '2px' }}>认购群入口</div>
|
||||
</div>
|
||||
<div onClick={() => window.location.hash = '#/wiki'} style={{
|
||||
background: 'linear-gradient(135deg, rgba(251,191,36,0.15) 0%, rgba(251,191,36,0.05) 100%)',
|
||||
borderRadius: '12px',
|
||||
padding: '12px 16px',
|
||||
border: '1px solid rgba(251,191,36,0.2)',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<div style={{ color: '#fbbf24', fontSize: '18px', fontWeight: '600', whiteSpace: 'nowrap' }}>📚 龙钞百科</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '10px', marginTop: '2px' }}>新手入门指南</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
/**
|
||||
* List - 藏品列表页面
|
||||
* Version: 0.1.6
|
||||
* 更新:新增成交行情页面筛选功能(版别、包装类型、号码分类)
|
||||
* Version: 0.1.7
|
||||
* 更新:新增分享海报功能
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { APP_VERSION } from '../config/version'
|
||||
import SharePoster from '../components/SharePoster'
|
||||
const API_BASE = localStorage.getItem('API_BASE') || ''
|
||||
|
||||
export default function List() {
|
||||
|
|
@ -34,6 +35,7 @@ export default function List() {
|
|||
const [dealVersionFilter, setDealVersionFilter] = useState('') // 版别筛选
|
||||
const [dealPackagingFilter, setDealPackagingFilter] = useState('') // 包装类型筛选
|
||||
const [dealNumberCatFilter, setDealNumberCatFilter] = useState('') // 号码分类筛选
|
||||
const [shareCollection, setShareCollection] = useState(null) // 分享弹窗
|
||||
|
||||
useEffect(() => {
|
||||
// 检查是否管理员
|
||||
|
|
@ -497,7 +499,26 @@ export default function List() {
|
|||
}
|
||||
|
||||
const ListItem = ({ item }) => (
|
||||
<div onClick={() => goDetail(item.id)} style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.05)', borderRadius: '12px', padding: '10px', marginBottom: '8px', cursor: 'pointer' }}>
|
||||
<div onClick={() => goDetail(item.id)} style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.05)', borderRadius: '12px', padding: '10px', marginBottom: '8px', cursor: 'pointer', position: 'relative' }}>
|
||||
{/* 分享按钮 */}
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setShareCollection(item) }}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
right: '8px',
|
||||
background: 'rgba(251, 191, 36, 0.15)',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '4px 8px',
|
||||
fontSize: '12px',
|
||||
cursor: 'pointer',
|
||||
color: '#fbbf24',
|
||||
zIndex: 10
|
||||
}}
|
||||
>
|
||||
📤 分享
|
||||
</button>
|
||||
{/* 第1行:编号 + 冠字号 + 包装(蓝色) | 状态 + 持仓类型 + 珍惜度(紫色) */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '4px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' }}>
|
||||
|
|
@ -1116,6 +1137,14 @@ function DealListItem({ deal, onRefresh }) {
|
|||
|
||||
{/* 版本号显示 */}
|
||||
<div style={{ position: 'fixed', bottom: '70px', right: '12px', color: 'rgba(255,255,255,0.2)', fontSize: '10px' }}>v0.0.1</div>
|
||||
|
||||
{/* 分享海报弹窗 */}
|
||||
{shareCollection && (
|
||||
<SharePoster
|
||||
collection={shareCollection}
|
||||
onClose={() => setShareCollection(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Login - 登录页面
|
||||
* Version: 0.0.1
|
||||
* 更新:
|
||||
* Version: 0.0.2 (2026-06-13)
|
||||
* 更新:移除注册时的手机号和短信验证功能
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
|
@ -23,79 +23,22 @@ export default function Login() {
|
|||
password: '',
|
||||
confirmPassword: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
verifyCode: '',
|
||||
inviteCode: ''
|
||||
})
|
||||
const [registerLoading, setRegisterLoading] = useState(false)
|
||||
const [registerError, setRegisterError] = useState('')
|
||||
|
||||
// 短信获取相关
|
||||
const [sendingCode, setSendingCode] = useState(false)
|
||||
const [codeCountdown, setCodeCountdown] = useState(0)
|
||||
const [codeSent, setCodeSent] = useState(false)
|
||||
|
||||
// 生成获取
|
||||
useEffect(() => {
|
||||
generateCaptcha()
|
||||
}, [])
|
||||
|
||||
// 获取倒计时
|
||||
useEffect(() => {
|
||||
if (codeCountdown > 0) {
|
||||
const timer = setTimeout(() => setCodeCountdown(codeCountdown - 1), 1000)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [codeCountdown])
|
||||
|
||||
const generateCaptcha = () => {
|
||||
const num1 = Math.floor(Math.random() * 10)
|
||||
const num2 = Math.floor(Math.random() * 10)
|
||||
setCaptcha({ num1, num2, answer: '' })
|
||||
}
|
||||
|
||||
// 发送短信获取
|
||||
const handleSendCode = async () => {
|
||||
if (!registerData.phone) {
|
||||
setRegisterError('请先输入手机号')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(registerData.phone)) {
|
||||
setRegisterError('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
setSendingCode(true)
|
||||
setRegisterError('')
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/send-verification-code', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ phone: registerData.phone })
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (data.success) {
|
||||
setCodeSent(true)
|
||||
setCodeCountdown(60)
|
||||
setRegisterError('')
|
||||
} else {
|
||||
setRegisterError(data.message || '发送失败')
|
||||
}
|
||||
} catch (err) {
|
||||
setRegisterError('发送失败,请稍后重试')
|
||||
} finally {
|
||||
setSendingCode(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理注册
|
||||
const handleRegister = async () => {
|
||||
// 验证输入
|
||||
|
|
@ -124,33 +67,13 @@ export default function Login() {
|
|||
return
|
||||
}
|
||||
|
||||
// 验证手机号
|
||||
if (!registerData.phone) {
|
||||
setRegisterError('手机号为必填项')
|
||||
return
|
||||
}
|
||||
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(registerData.phone)) {
|
||||
setRegisterError('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证短信获取
|
||||
if (!registerData.verifyCode) {
|
||||
setRegisterError('请输入短信获取')
|
||||
return
|
||||
}
|
||||
|
||||
setRegisterLoading(true)
|
||||
setRegisterError('')
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
username: registerData.username,
|
||||
password: registerData.password,
|
||||
phone: registerData.phone,
|
||||
verifyCode: registerData.verifyCode
|
||||
password: registerData.password
|
||||
}
|
||||
if (registerData.email) {
|
||||
payload.email = registerData.email
|
||||
|
|
@ -175,9 +98,7 @@ export default function Login() {
|
|||
|
||||
alert('注册成功!请登录')
|
||||
setShowRegister(false)
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '', inviteCode: '' })
|
||||
setCodeSent(false)
|
||||
setCodeCountdown(0)
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', inviteCode: '' })
|
||||
generateCaptcha()
|
||||
} catch (err) {
|
||||
console.error('注册错误:', err)
|
||||
|
|
@ -491,68 +412,6 @@ export default function Login() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* 手机号 */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<input
|
||||
type="tel"
|
||||
value={registerData.phone}
|
||||
onChange={(e) => setRegisterData(prev => ({ ...prev, phone: e.target.value }))}
|
||||
placeholder="手机号(11位)"
|
||||
maxLength={11}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
color: '#fff',
|
||||
fontSize: '16px',
|
||||
outline: 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 短信获取 */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
<input
|
||||
type="text"
|
||||
value={registerData.verifyCode}
|
||||
onChange={(e) => setRegisterData(prev => ({ ...prev, verifyCode: e.target.value }))}
|
||||
placeholder="短信验证码"
|
||||
maxLength={6}
|
||||
style={{
|
||||
flex: 1, minWidth: "90px", width: "auto",
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
color: '#fff',
|
||||
fontSize: '16px',
|
||||
outline: 'none'
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendCode}
|
||||
disabled={sendingCode || codeCountdown > 0}
|
||||
style={{
|
||||
minWidth: "90px", width: "auto",
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
background: codeCountdown > 0 ? 'rgba(148, 163, 184, 0.3)' : 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor: codeCountdown > 0 ? 'not-allowed' : 'pointer',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
{codeCountdown > 0 ? `${codeCountdown}秒` : sendingCode ? '发送中...' : '获取验证码'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 邮箱(可选) */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<input
|
||||
|
|
@ -656,10 +515,8 @@ export default function Login() {
|
|||
<button
|
||||
onClick={() => {
|
||||
setShowRegister(false)
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '', inviteCode: '' })
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', inviteCode: '' })
|
||||
setRegisterError('')
|
||||
setCodeSent(false)
|
||||
setCodeCountdown(0)
|
||||
}}
|
||||
style={{
|
||||
flex: 1, minWidth: "90px", width: "auto",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* News - 资讯列表页面
|
||||
* Version: 0.1.0 (2026-05-07)
|
||||
* 更新:成交行情列表和详情弹窗列表都按成交日期(deal_date)倒序排序
|
||||
* Version: 0.1.2 (2026-05-13)
|
||||
* 更新:移除成交行情价格统计表格,增加获取数量到500
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
|
@ -97,7 +97,7 @@ export default function News() {
|
|||
? `${API_BASE}/api/seek/list`
|
||||
: `${API_BASE}/api/deal/list`
|
||||
|
||||
// 成交行情和寻配号每页500条
|
||||
// 成交行情获取所有数据(不分页)
|
||||
if (activeTab === 'deal') {
|
||||
url += (url.includes('?') ? '&' : '?') + 'page_size=500'
|
||||
if (dealDate) {
|
||||
|
|
@ -660,60 +660,6 @@ export default function News() {
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '13px' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ padding: '8px', textAlign: 'left', color: '#94a3b8', borderBottom: '1px solid rgba(255,255,255,0.1)' }}></th>
|
||||
{packagings.map(p => (
|
||||
<th key={p} style={{ padding: '8px', textAlign: 'center', color: '#94a3b8', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>{p}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(() => {
|
||||
// 计算每个分类的平均价格
|
||||
const catAvg = categories.map(cat => {
|
||||
const prices = []
|
||||
packagings.forEach(pkg => {
|
||||
const d = calcAvg(pkg, cat)
|
||||
if (d) prices.push(d.avg)
|
||||
})
|
||||
const avg = prices.length > 0 ? Math.round(prices.reduce((a,b) => a+b, 0) / prices.length) : 0
|
||||
return { cat, avg }
|
||||
}).filter(c => c.avg > 0)
|
||||
// 分类名称标准化并按固定顺序排序
|
||||
catAvg.sort((a, b) => a.avg - b.avg)
|
||||
const categoryOrder = ['带4号', '带7号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号']
|
||||
const sortedCats = categoryOrder.filter(cat => catAvg.some(c => c.cat === cat))
|
||||
.concat(catAvg.filter(c => !categoryOrder.includes(c.cat)).map(c => c.cat))
|
||||
|
||||
return sortedCats.map(cat => {
|
||||
const rowData = packagings.map(pkg => calcAvg(pkg, cat))
|
||||
const hasData = rowData.some(d => d !== null)
|
||||
if (!hasData) return null
|
||||
return (
|
||||
<tr key={cat}>
|
||||
<td style={{ padding: '8px', color: '#fbbf24', fontWeight: '500', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>{cat}</td>
|
||||
{rowData.map((d, i) => (
|
||||
<td key={i} style={{ padding: '8px', textAlign: 'center', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
{d ? (
|
||||
<div style={{ color: '#22c55e', fontWeight: '600', cursor: 'pointer' }}
|
||||
onClick={() => setDealDetailItems(d.items)}>
|
||||
¥{d.avg.toLocaleString()}
|
||||
<span style={{ color: '#64748b', fontSize: '11px', marginLeft: '4px' }}>({d.count})</span>
|
||||
</div>
|
||||
) : <span style={{ color: '#475569' }}>-</span>}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
})()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,401 @@
|
|||
/**
|
||||
* Wiki - 龙钞百科页面
|
||||
* Version: 1.0.0
|
||||
* 新手入门知识、号码分类、术语解释
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { APP_VERSION } from '../config/version'
|
||||
|
||||
export default function Wiki() {
|
||||
const [activeSection, setActiveSection] = useState('intro')
|
||||
|
||||
// 百科内容数据
|
||||
const sections = {
|
||||
intro: {
|
||||
title: '🐉 什么是龙钞',
|
||||
content: `
|
||||
## 中国人民银行龙钞纪念钞
|
||||
|
||||
**龙钞**(全称:第24届冬季奥林匹克运动会纪念钞)是中国人民银行于2022年发行的纪念钞,包含**冰上运动**和**雪上运动**两枚一套。
|
||||
|
||||
### 基本信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 发行时间 | 2022年12月 |
|
||||
| 发行量 | 2亿套 |
|
||||
| 面值 | 20元 |
|
||||
| 规格 | 145mm × 70mm |
|
||||
|
||||
### 收藏价值
|
||||
|
||||
1. **题材热门** - 冬奥会是国际大型赛事,题材热度高
|
||||
2. **发行量适中** - 2亿的发行量在纪念钞中属于中等
|
||||
3. **设计精美** - 首次采用塑料基片+纸基的组合设计
|
||||
4. **号码玩法多** - 冠字号+8位数字,组合丰富多样
|
||||
`
|
||||
},
|
||||
category: {
|
||||
title: '🔢 号码分类',
|
||||
content: `
|
||||
## 龙钞号码分类大全
|
||||
|
||||
### 顶级号码(收藏价值最高)
|
||||
|
||||
| 分类 | 条件 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 🏆 圆圆号 | 不含0、1、2、3、4、5、7 | 1 |
|
||||
| 🔄 倒置号 | 不含2、3、4、5、7 | 2 |
|
||||
| 🐎 金马王 | 不含1、2、3、4、7 | 3 |
|
||||
| 🐎 金马号 | 不含2、3、4、7 | 4 |
|
||||
| ⛰️ 金山王 | 不含1、2、4、5、7 | 5 |
|
||||
| ⛰️ 天马王 | 不含1、2、4、7 | 6 |
|
||||
|
||||
### 高级号码
|
||||
|
||||
| 分类 | 条件 | 备注 |
|
||||
|------|------|------|
|
||||
| 🏔️ 金山号 | 不含2、4、5、7 | |
|
||||
| 🐎 天马号 | 不含2、4、7 | |
|
||||
| 🌫️ 朦胧王 | 不含1、3、4、5、7 | |
|
||||
| 🌫️ 朦胧号 | 不含3、4、5、7 | |
|
||||
| 💎 如意号 | 不含1、3、4、7 | |
|
||||
| 💎 钻石号 | 不含3、4、7 | |
|
||||
| ♾️ 永恒号 | 不含4、7 | |
|
||||
|
||||
### 普通号码
|
||||
|
||||
| 分类 | 条件 | 备注 |
|
||||
|------|------|------|
|
||||
| 🔥 带7号 | 不含4 | 不带4的号码 |
|
||||
| ⚠️ 带4号 | 包含4 | 传统说法带4不太好 |
|
||||
|
||||
### 分类说明
|
||||
|
||||
- **"无X"规则**:号码中不包含数字X
|
||||
- **就高不就低**:同时满足多个条件时,按最高价值分类
|
||||
- **散钞看8位**:散钞看全部8位数字
|
||||
- **标十看7位**:标十看后7位
|
||||
- **标百看6位**:标百看后6位
|
||||
`
|
||||
},
|
||||
terms: {
|
||||
title: '📖 术语解释',
|
||||
content: `
|
||||
## 收藏术语大全
|
||||
|
||||
### 基础术语
|
||||
|
||||
| 术语 | 解释 |
|
||||
|------|------|
|
||||
| 冠字号 | J+8位数字,如J01234567 |
|
||||
| 散钞 | 单张收藏的钞票 |
|
||||
| 标十 | 10连号,一刀 |
|
||||
| 标百 | 100连号,一捆 |
|
||||
| 整刀 | 100张连号 |
|
||||
| 整捆 | 1000张连号 |
|
||||
|
||||
### 评级术语
|
||||
|
||||
| 术语 | 解释 |
|
||||
|------|------|
|
||||
| 评级 | 由专业机构鉴定真伪、品相 |
|
||||
| 评级公司 | PCGS、NGC、PMG等 |
|
||||
| 分数 | 评级公司给出的品相分数(70-100) |
|
||||
| 69+ | 顶级分数,收藏价值高 |
|
||||
| 68 | 高分,品相极佳 |
|
||||
| 67 | 不错,品相良好 |
|
||||
|
||||
### 状态术语
|
||||
|
||||
| 术语 | 解释 |
|
||||
|------|------|
|
||||
| 自持 | 自己收藏 |
|
||||
| 寄存 | 存放在别人那里 |
|
||||
| 寄售 | 委托他人代卖 |
|
||||
| 送评 | 送去评级公司评级 |
|
||||
| 修复 | 经过专业修复 |
|
||||
|
||||
### 价格术语
|
||||
|
||||
| 术语 | 解释 |
|
||||
|------|------|
|
||||
| 成本价 | 入手的价格 |
|
||||
| 目标价 | 预期出售价格 |
|
||||
| 成交价 | 实际交易价格 |
|
||||
| 行情 | 当前市场的买卖价格 |
|
||||
`
|
||||
},
|
||||
grade: {
|
||||
title: '⭐ 评级知识',
|
||||
content: `
|
||||
## 评级公司介绍
|
||||
|
||||
### 国际三大评级公司
|
||||
|
||||
#### 1. PCGS(美国)
|
||||
- 成立于1986年
|
||||
- 业界权威,评级标准严格
|
||||
- 特别擅长现代币评级
|
||||
|
||||
#### 2. NGC(美国)
|
||||
- 成立于1987年
|
||||
- 评级数量大
|
||||
- 封装设计美观
|
||||
|
||||
#### 3. PMG(美国)
|
||||
- 专注于纸币评级
|
||||
- 评级标准清晰
|
||||
- 国内认知度高
|
||||
|
||||
### 评分标准
|
||||
|
||||
| 分数 | 描述 | 等级 |
|
||||
|------|------|------|
|
||||
| 70 | 完美未流通 | Gem MS |
|
||||
| 69 | 接近完美 | Gem MS |
|
||||
| 68 | 极美 | MS |
|
||||
| 67 | 精美 | MS |
|
||||
| 66 | 优美 | MS |
|
||||
| 65 | 精美 | MS |
|
||||
|
||||
### 评级建议
|
||||
|
||||
1. **高端收藏** - 建议送评69分以上
|
||||
2. **普通收藏** - 67-68分性价比高
|
||||
3. **投资需求** - 选择68分以上的评级钞
|
||||
|
||||
### 注意事项
|
||||
|
||||
- 评级有风险,可能降级
|
||||
- 评级费用几十到几百元不等
|
||||
- 评级周期通常2-4周
|
||||
`
|
||||
},
|
||||
care: {
|
||||
title: '💎 保养指南',
|
||||
content: `
|
||||
## 龙钞保养指南
|
||||
|
||||
### 存放环境
|
||||
|
||||
#### 温度与湿度
|
||||
- **最佳温度**:18-25°C
|
||||
- **最佳湿度**:40-55%
|
||||
- 避免温差大的环境
|
||||
- 避免潮湿(易发霉)
|
||||
|
||||
#### 存放方式
|
||||
- 使用专用纸币保护套
|
||||
- 避免直接用手触摸
|
||||
- 远离阳光直射
|
||||
- 远离热源(暖气、空调)
|
||||
|
||||
### 注意事项
|
||||
|
||||
#### ❌ 禁止行为
|
||||
- 用手指直接触摸钞面
|
||||
- 折叠、弯曲钞票
|
||||
- 在钞面上写字
|
||||
- 暴露在潮湿环境中
|
||||
- 靠近化学物品
|
||||
|
||||
#### ✅ 正确做法
|
||||
- 佩戴手套操作
|
||||
- 使用镊子夹取
|
||||
- 平放保存
|
||||
- 定期检查状态
|
||||
- 使用干燥剂
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 钞票有折痕怎么办?**
|
||||
A: 建议送专业修复,或评级遮盖瑕疵
|
||||
|
||||
**Q: 钞票发黄怎么办?**
|
||||
A: 避免潮湿环境,可使用干燥剂
|
||||
|
||||
**Q: 如何判断品相?**
|
||||
A: 对光查看是否有磨损、污渍、折痕
|
||||
`
|
||||
},
|
||||
platform: {
|
||||
title: '🛒 交易指南',
|
||||
content: `
|
||||
## 交易注意事项
|
||||
|
||||
### 交易平台
|
||||
|
||||
1. **专业钱币交易平台**
|
||||
- 一尘网
|
||||
- 华夏收藏网
|
||||
- 钱币天堂
|
||||
|
||||
2. **二手交易平台**
|
||||
- 闲鱼(需谨慎)
|
||||
|
||||
3. **微信群交易**
|
||||
- 诚信商家直供
|
||||
- 群内私下交易
|
||||
|
||||
### 防骗指南
|
||||
|
||||
#### ⚠️ 常见骗局
|
||||
- 低价诱惑先款后货
|
||||
- 假称货在朋友处
|
||||
- 改号、换号
|
||||
- 假评级假分数
|
||||
|
||||
#### ✅ 安全交易
|
||||
1. **走平台交易**
|
||||
- 使用有保障的平台
|
||||
- 保留交易凭证
|
||||
|
||||
2. **先看后买**
|
||||
- 要求实拍视频
|
||||
- 确认号码无误
|
||||
|
||||
3. **金额较大**
|
||||
- 建议走中介
|
||||
- 或面交交易
|
||||
|
||||
### 价格参考
|
||||
|
||||
- **散钞**:面值+10~50元
|
||||
- **标十**:150-300元
|
||||
- **标百**:1500-3000元
|
||||
- **特殊号码**:价格面议
|
||||
|
||||
### 投资建议
|
||||
|
||||
1. 理性投资,量力而行
|
||||
2. 注重品相,号码为辅
|
||||
3. 长期持有,静待升值
|
||||
4. 分散投资,不要all in
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染Markdown内容(简化版)
|
||||
const renderContent = (content) => {
|
||||
const lines = content.trim().split('\n')
|
||||
return lines.map((line, idx) => {
|
||||
// 标题
|
||||
if (line.startsWith('## ')) {
|
||||
return <h3 key={idx} style={{ color: '#fbbf24', fontSize: '16px', marginTop: '20px', marginBottom: '10px', borderLeft: '3px solid #fbbf24', paddingLeft: '10px' }}>{line.replace('## ', '')}</h3>
|
||||
}
|
||||
// 小标题
|
||||
if (line.startsWith('### ')) {
|
||||
return <h4 key={idx} style={{ color: '#fff', fontSize: '14px', marginTop: '16px', marginBottom: '8px' }}>{line.replace('### ', '')}</h4>
|
||||
}
|
||||
// 表格
|
||||
if (line.startsWith('|') && !line.startsWith('|---')) {
|
||||
const cells = line.split('|').filter(c => c.trim())
|
||||
if (cells.length > 1) {
|
||||
const isHeader = idx > 0 && lines[idx-1]?.includes('---')
|
||||
return (
|
||||
<div key={idx} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))', gap: '4px', padding: '8px 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
{cells.map((cell, i) => (
|
||||
<div key={i} style={{ color: isHeader ? '#94a3b8' : '#e2e8f0', fontSize: '12px', padding: '4px', background: isHeader ? 'rgba(255,255,255,0.02)' : 'transparent' }}>
|
||||
{cell.trim()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 无序列表
|
||||
if (line.startsWith('- ') || line.startsWith('1. ')) {
|
||||
const text = line.replace(/^[-1-9.]\s*/, '')
|
||||
return <div key={idx} style={{ color: '#cbd5e1', fontSize: '13px', paddingLeft: '16px', marginBottom: '6px', position: 'relative' }}><span style={{ position: 'absolute', left: '4px', color: '#fbbf24' }}>•</span>{text}</div>
|
||||
}
|
||||
// 普通段落
|
||||
if (line.trim()) {
|
||||
// 处理加粗
|
||||
let text = line.replace(/\*\*(.*?)\*\*/g, '<strong style="color:#fbbf24">$1</strong>')
|
||||
return <p key={idx} style={{ color: '#cbd5e1', fontSize: '13px', lineHeight: '1.8', marginBottom: '8px' }} dangerouslySetInnerHTML={{ __html: text }} />
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: '#0f172a', paddingBottom: '80px' }}>
|
||||
{/* 头部 */}
|
||||
<div style={{ padding: '20px', background: 'linear-gradient(135deg, #1e293b 0%, #0f172a 100%)', borderBottom: '1px solid rgba(251,191,36,0.2)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<button onClick={() => window.location.hash = '#/home'} style={{ background: 'none', border: 'none', color: '#fbbf24', fontSize: '18px', cursor: 'pointer' }}>←</button>
|
||||
<h1 style={{ color: '#fff', fontSize: '20px', margin: 0 }}>📚 龙钞百科</h1>
|
||||
</div>
|
||||
<span style={{ color: 'rgba(255,255,255,0.3)', fontSize: '11px' }}>v{APP_VERSION}</span>
|
||||
</div>
|
||||
|
||||
{/* 简介文字 */}
|
||||
<p style={{ color: '#94a3b8', fontSize: '13px', margin: 0 }}>
|
||||
新手入门指南 · 号码分类 · 术语解释 · 保养知识
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 导航标签 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: '6px',
|
||||
padding: '12px 16px',
|
||||
overflowX: 'auto',
|
||||
background: 'rgba(255,255,255,0.02)'
|
||||
}}>
|
||||
{Object.entries(sections).map(([key, section]) => (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => setActiveSection(key)}
|
||||
style={{
|
||||
padding: '8px 14px',
|
||||
borderRadius: '20px',
|
||||
border: 'none',
|
||||
fontSize: '12px',
|
||||
whiteSpace: 'nowrap',
|
||||
cursor: 'pointer',
|
||||
background: activeSection === key ? '#fbbf24' : 'rgba(255,255,255,0.08)',
|
||||
color: activeSection === key ? '#1e293b' : '#94a3b8',
|
||||
fontWeight: activeSection === key ? 'bold' : 'normal',
|
||||
transition: 'all 0.2s'
|
||||
}}
|
||||
>
|
||||
{section.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div style={{ padding: '16px' }}>
|
||||
<div style={{
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
borderRadius: '12px',
|
||||
padding: '16px',
|
||||
border: '1px solid rgba(255,255,255,0.05)'
|
||||
}}>
|
||||
{renderContent(sections[activeSection].content)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部提示 */}
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
bottom: '70px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
background: 'rgba(251,191,36,0.1)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '20px',
|
||||
border: '1px solid rgba(251,191,36,0.2)'
|
||||
}}>
|
||||
<span style={{ color: '#fbbf24', fontSize: '12px' }}>
|
||||
💡 了解更多,请访问 jiachenlong.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* YichensBoard - 一尘看板页面
|
||||
* Version: 0.0.3
|
||||
* 更新:
|
||||
* Version: 0.0.5 (2026-05-13)
|
||||
* 更新:增加复制链接功能
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
|
@ -265,7 +265,12 @@ export default function YichensBoard() {
|
|||
<div style={{ color: '#e2e8f0', fontSize: 13, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 300, overflowY: 'auto' }}>
|
||||
{post.content}
|
||||
</div>
|
||||
{post.url && <a href={post.url} target='_blank' rel='noopener noreferrer' style={{ display:'inline-block', marginTop:12, padding:'8px 16px', background:'rgba(59,130,246,0.2)', borderRadius:8, color:'#60a5fa', textDecoration:'none', fontSize:13 }}>查看原帖</a>}
|
||||
{post.url && (
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
|
||||
<a href={post.url} target='_blank' rel='noopener noreferrer' style={{ padding:'8px 16px', background:'rgba(59,130,246,0.2)', borderRadius:8, color:'#60a5fa', textDecoration:'none', fontSize:13 }}>查看原帖</a>
|
||||
<button onClick={() => { navigator.clipboard.writeText(post.url); alert('链接已复制!'); }} style={{ padding:'8px 16px', background:'rgba(34,197,94,0.2)', borderRadius:8, color:'#22c55e', border:'none', cursor:'pointer', fontSize:13 }}>复制链接</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue