v0.2.7 - 新增龙钞宣传功能:分享海报+百科页面

This commit is contained in:
甲辰生产 2026-07-19 10:30:40 +08:00
parent 02711d73c9
commit 98b3917acc
9 changed files with 935 additions and 7 deletions

View File

@ -1,5 +1,28 @@
# 版本更新记录 # 版本更新记录
## v0.2.7 (2026-07-19) - 龙钞宣传功能
### 前端更新
- **Wiki.jsx (新增)**
- 龙钞百科页面,包含:什么是龙钞、号码分类、术语解释、评级知识、保养指南、交易指南
- 新手入门必备指南
- **SharePoster.jsx (新增)**
- 藏品分享海报组件
- 一键生成精美海报,朋友圈分享
- **List.jsx**
- 新增分享按钮,每个藏品卡片右上角显示"📤 分享"按钮
- 点击生成精美海报,保存到相册
- **Home.jsx**
- 快捷操作新增"📚 龙钞百科"入口
### 依赖更新
- 新增 html2canvas 库,用于生成海报图片
---
## v0.2.5 (2026-05-13) ## v0.2.5 (2026-05-13)
### 前端更新 ### 前端更新

View File

@ -1,14 +1,17 @@
{ {
"version": "0.2.6", "version": "0.2.7",
"updated": "2026-06-13", "updated": "2026-07-19",
"modules": { "modules": {
"frontend": { "frontend": {
"version": "0.2.2", "version": "0.2.3",
"pages": { "pages": {
"Login": "0.0.2", "Login": "0.0.2",
"Add": "0.0.2", "Add": "0.0.2",
"News": "0.1.2", "News": "0.1.2",
"YichensBoard": "0.0.5" "YichensBoard": "0.0.5",
"Wiki": "1.0.0",
"SharePoster": "1.0.0",
"List": "0.1.7"
} }
}, },
"backend": { "backend": {

View File

@ -9,6 +9,7 @@
"version": "1.2.82", "version": "1.2.82",
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"html2canvas": "^1.4.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.1.0" "react-router-dom": "^7.1.0"
@ -1239,6 +1240,15 @@
"proxy-from-env": "^1.1.0" "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": { "node_modules/baseline-browser-mapping": {
"version": "2.10.8", "version": "2.10.8",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz",
@ -1352,6 +1362,15 @@
"url": "https://opencollective.com/express" "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": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -1673,6 +1692,19 @@
"node": ">= 0.4" "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": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -1998,6 +2030,15 @@
"node": ">=0.10.0" "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": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@ -2046,6 +2087,15 @@
"browserslist": ">= 4.21.0" "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": { "node_modules/vite": {
"version": "6.4.2", "version": "6.4.2",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.2.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.2.tgz",

View File

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"html2canvas": "^1.4.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.1.0" "react-router-dom": "^7.1.0"

View File

@ -10,6 +10,7 @@ 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'
import Admin from './pages/Admin' import Admin from './pages/Admin'
import Wiki from './pages/Wiki'
export default function App() { export default function App() {
const [path, setPath] = useState(window.location.hash.slice(1) || '/') const [path, setPath] = useState(window.location.hash.slice(1) || '/')
@ -38,6 +39,7 @@ export default function App() {
if (basePath === '/login') return <Login /> if (basePath === '/login') return <Login />
if (basePath === '/settings') return <Settings /> if (basePath === '/settings') return <Settings />
if (basePath === '/admin') return <Admin /> if (basePath === '/admin') return <Admin />
if (basePath === '/wiki') return <Wiki />
if (basePath.startsWith('/edit')) return <Edit /> if (basePath.startsWith('/edit')) return <Edit />
if (basePath.startsWith('/detail')) return <Detail /> if (basePath.startsWith('/detail')) return <Detail />
return <Home /> return <Home />

View File

@ -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>
)
}

View File

@ -241,6 +241,21 @@ export default function Home() {
<div style={{ color: '#3b82f6', fontSize: '18px', fontWeight: '600', whiteSpace: 'nowrap' }}>我的认购</div> <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 style={{ color: 'rgba(255,255,255,0.5)', fontSize: '10px', marginTop: '2px' }}>认购群入口</div>
</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>
</div> </div>

View File

@ -1,11 +1,12 @@
/** /**
* List - 藏品列表页面 * List - 藏品列表页面
* Version: 0.1.6 * Version: 0.1.7
* 更新新增成交行情页面筛选功能版别包装类型号码分类 * 更新新增分享海报功能
*/ */
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { APP_VERSION } from '../config/version' import { APP_VERSION } from '../config/version'
import SharePoster from '../components/SharePoster'
const API_BASE = localStorage.getItem('API_BASE') || '' const API_BASE = localStorage.getItem('API_BASE') || ''
export default function List() { export default function List() {
@ -34,6 +35,7 @@ export default function List() {
const [dealVersionFilter, setDealVersionFilter] = useState('') // const [dealVersionFilter, setDealVersionFilter] = useState('') //
const [dealPackagingFilter, setDealPackagingFilter] = useState('') // const [dealPackagingFilter, setDealPackagingFilter] = useState('') //
const [dealNumberCatFilter, setDealNumberCatFilter] = useState('') // const [dealNumberCatFilter, setDealNumberCatFilter] = useState('') //
const [shareCollection, setShareCollection] = useState(null) //
useEffect(() => { useEffect(() => {
// //
@ -497,7 +499,26 @@ export default function List() {
} }
const ListItem = ({ item }) => ( 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行编号 + 冠字号 + 包装(蓝色) | 状态 + 持仓类型 + 珍惜度(紫色) */} {/* 第1行编号 + 冠字号 + 包装(蓝色) | 状态 + 持仓类型 + 珍惜度(紫色) */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '4px' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '4px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' }}> <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> <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> </div>
) )
} }

401
frontend/src/pages/Wiki.jsx Normal file
View File

@ -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: `
## 龙钞号码分类大全
### 顶级号码收藏价值最高
| 分类 | 条件 | 优先级 |
|------|------|--------|
| 🏆 圆圆号 | 不含0123457 | 1 |
| 🔄 倒置号 | 不含23457 | 2 |
| 🐎 金马王 | 不含12347 | 3 |
| 🐎 金马号 | 不含2347 | 4 |
| 金山王 | 不含12457 | 5 |
| 天马王 | 不含1247 | 6 |
### 高级号码
| 分类 | 条件 | 备注 |
|------|------|------|
| 🏔 金山号 | 不含2457 | |
| 🐎 天马号 | 不含247 | |
| 🌫 朦胧王 | 不含13457 | |
| 🌫 朦胧号 | 不含3457 | |
| 💎 如意号 | 不含1347 | |
| 💎 钻石号 | 不含347 | |
| 永恒号 | 不含47 | |
### 普通号码
| 分类 | 条件 | 备注 |
|------|------|------|
| 🔥 带7号 | 不含4 | 不带4的号码 |
| 带4号 | 包含4 | 传统说法带4不太好 |
### 分类说明
- **"无X"规则**号码中不包含数字X
- **就高不就低**同时满足多个条件时按最高价值分类
- **散钞看8位**散钞看全部8位数字
- **标十看7位**标十看后7位
- **标百看6位**标百看后6位
`
},
terms: {
title: '📖 术语解释',
content: `
## 收藏术语大全
### 基础术语
| 术语 | 解释 |
|------|------|
| 冠字号 | J+8位数字如J01234567 |
| 散钞 | 单张收藏的钞票 |
| 标十 | 10连号一刀 |
| 标百 | 100连号一捆 |
| 整刀 | 100张连号 |
| 整捆 | 1000张连号 |
### 评级术语
| 术语 | 解释 |
|------|------|
| 评级 | 由专业机构鉴定真伪品相 |
| 评级公司 | PCGSNGCPMG等 |
| 分数 | 评级公司给出的品相分数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>
)
}