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)):
|
||||||
# 更新:
|
# 更新:
|
||||||
|
|
||||||
# 更新:
|
# 更新:
|
||||||
# 检查手机号是否已存在
|
# 检查手机号是否已存在(移除,2026-06-13)
|
||||||
# 更新:
|
|
||||||
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:该手机号已被注册,请更换手机号"
|
|
||||||
# 更新:
|
|
||||||
)
|
|
||||||
# 更新:
|
# 更新:
|
||||||
if user_data.email:
|
if user_data.email:
|
||||||
# 更新:
|
# 更新:
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ def get_information_list(
|
||||||
user_id: Optional[str] = Query(None, description="用户ID,用于获取该用户的行情"),
|
user_id: Optional[str] = Query(None, description="用户ID,用于获取该用户的行情"),
|
||||||
deal_date: Optional[str] = Query(None, description="成交日期过滤,格式YYYY-MM-DD"),
|
deal_date: Optional[str] = Query(None, description="成交日期过滤,格式YYYY-MM-DD"),
|
||||||
page: int = Query(1, ge=1),
|
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),
|
current_user: Optional[User] = Depends(get_current_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
response: Response = None
|
response: Response = None
|
||||||
|
|
@ -880,7 +880,7 @@ def get_deal_stats(
|
||||||
@router.get("/my/list", response_model=List[InformationResponse])
|
@router.get("/my/list", response_model=List[InformationResponse])
|
||||||
def get_my_information_list(
|
def get_my_information_list(
|
||||||
page: int = Query(1, ge=1),
|
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),
|
current_user: User = Depends(get_current_user),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class UserBase(BaseModel):
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
password: str = Field(..., min_length=6)
|
password: str = Field(..., min_length=6)
|
||||||
invite_code: Optional[str] = Field(None, alias="inviteCode") # 填写的邀请码(选填)
|
invite_code: Optional[str] = Field(None, alias="inviteCode") # 填写的邀请码(选填)
|
||||||
|
# 移除手机号和短信验证(2026-06-13)
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(BaseModel):
|
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)
|
## v0.2.3 (2026-05-11)
|
||||||
|
|
||||||
### 前端更新
|
### 前端更新
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
VERSION=v0.1.8
|
VERSION=0.2.5
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
{
|
{
|
||||||
"version": "0.2.4",
|
"version": "0.2.7",
|
||||||
"updated": "2026-05-11",
|
"updated": "2026-07-19",
|
||||||
"modules": {
|
"modules": {
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.3",
|
||||||
"pages": {
|
"pages": {
|
||||||
|
"Login": "0.0.2",
|
||||||
"Add": "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": {
|
"backend": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"routers": {
|
"routers": {
|
||||||
|
"auth": "0.0.2",
|
||||||
"seek": "0.2.0",
|
"seek": "0.2.0",
|
||||||
"purchase": "0.3.0"
|
"purchase": "0.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<title>甲辰收藏 v=0.0.9</title>
|
<title>甲辰收藏 v0.2.5</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="/static/icons/favicon.png" />
|
<link rel="icon" type="image/png" href="/static/icons/favicon.png" />
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 />
|
||||||
|
|
|
||||||
|
|
@ -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: '#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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Login - 登录页面
|
* Login - 登录页面
|
||||||
* Version: 0.0.1
|
* Version: 0.0.2 (2026-06-13)
|
||||||
* 更新:
|
* 更新:移除注册时的手机号和短信验证功能
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
|
@ -23,79 +23,22 @@ export default function Login() {
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
|
||||||
verifyCode: '',
|
|
||||||
inviteCode: ''
|
inviteCode: ''
|
||||||
})
|
})
|
||||||
const [registerLoading, setRegisterLoading] = useState(false)
|
const [registerLoading, setRegisterLoading] = useState(false)
|
||||||
const [registerError, setRegisterError] = useState('')
|
const [registerError, setRegisterError] = useState('')
|
||||||
|
|
||||||
// 短信获取相关
|
|
||||||
const [sendingCode, setSendingCode] = useState(false)
|
|
||||||
const [codeCountdown, setCodeCountdown] = useState(0)
|
|
||||||
const [codeSent, setCodeSent] = useState(false)
|
|
||||||
|
|
||||||
// 生成获取
|
// 生成获取
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
generateCaptcha()
|
generateCaptcha()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 获取倒计时
|
|
||||||
useEffect(() => {
|
|
||||||
if (codeCountdown > 0) {
|
|
||||||
const timer = setTimeout(() => setCodeCountdown(codeCountdown - 1), 1000)
|
|
||||||
return () => clearTimeout(timer)
|
|
||||||
}
|
|
||||||
}, [codeCountdown])
|
|
||||||
|
|
||||||
const generateCaptcha = () => {
|
const generateCaptcha = () => {
|
||||||
const num1 = Math.floor(Math.random() * 10)
|
const num1 = Math.floor(Math.random() * 10)
|
||||||
const num2 = Math.floor(Math.random() * 10)
|
const num2 = Math.floor(Math.random() * 10)
|
||||||
setCaptcha({ num1, num2, answer: '' })
|
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 () => {
|
const handleRegister = async () => {
|
||||||
// 验证输入
|
// 验证输入
|
||||||
|
|
@ -124,33 +67,13 @@ export default function Login() {
|
||||||
return
|
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)
|
setRegisterLoading(true)
|
||||||
setRegisterError('')
|
setRegisterError('')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
username: registerData.username,
|
username: registerData.username,
|
||||||
password: registerData.password,
|
password: registerData.password
|
||||||
phone: registerData.phone,
|
|
||||||
verifyCode: registerData.verifyCode
|
|
||||||
}
|
}
|
||||||
if (registerData.email) {
|
if (registerData.email) {
|
||||||
payload.email = registerData.email
|
payload.email = registerData.email
|
||||||
|
|
@ -175,9 +98,7 @@ export default function Login() {
|
||||||
|
|
||||||
alert('注册成功!请登录')
|
alert('注册成功!请登录')
|
||||||
setShowRegister(false)
|
setShowRegister(false)
|
||||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '', inviteCode: '' })
|
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', inviteCode: '' })
|
||||||
setCodeSent(false)
|
|
||||||
setCodeCountdown(0)
|
|
||||||
generateCaptcha()
|
generateCaptcha()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('注册错误:', err)
|
console.error('注册错误:', err)
|
||||||
|
|
@ -491,68 +412,6 @@ export default function Login() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</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' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<input
|
<input
|
||||||
|
|
@ -656,10 +515,8 @@ export default function Login() {
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowRegister(false)
|
setShowRegister(false)
|
||||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '', inviteCode: '' })
|
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', inviteCode: '' })
|
||||||
setRegisterError('')
|
setRegisterError('')
|
||||||
setCodeSent(false)
|
|
||||||
setCodeCountdown(0)
|
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, minWidth: "90px", width: "auto",
|
flex: 1, minWidth: "90px", width: "auto",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* News - 资讯列表页面
|
* News - 资讯列表页面
|
||||||
* Version: 0.1.0 (2026-05-07)
|
* Version: 0.1.2 (2026-05-13)
|
||||||
* 更新:成交行情列表和详情弹窗列表都按成交日期(deal_date)倒序排序
|
* 更新:移除成交行情价格统计表格,增加获取数量到500
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
|
@ -97,7 +97,7 @@ export default function News() {
|
||||||
? `${API_BASE}/api/seek/list`
|
? `${API_BASE}/api/seek/list`
|
||||||
: `${API_BASE}/api/deal/list`
|
: `${API_BASE}/api/deal/list`
|
||||||
|
|
||||||
// 成交行情和寻配号每页500条
|
// 成交行情获取所有数据(不分页)
|
||||||
if (activeTab === 'deal') {
|
if (activeTab === 'deal') {
|
||||||
url += (url.includes('?') ? '&' : '?') + 'page_size=500'
|
url += (url.includes('?') ? '&' : '?') + 'page_size=500'
|
||||||
if (dealDate) {
|
if (dealDate) {
|
||||||
|
|
@ -660,60 +660,6 @@ export default function News() {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</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 - 一尘看板页面
|
* YichensBoard - 一尘看板页面
|
||||||
* Version: 0.0.3
|
* Version: 0.0.5 (2026-05-13)
|
||||||
* 更新:
|
* 更新:增加复制链接功能
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react'
|
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' }}>
|
<div style={{ color: '#e2e8f0', fontSize: 13, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 300, overflowY: 'auto' }}>
|
||||||
{post.content}
|
{post.content}
|
||||||
</div>
|
</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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue