jiachenlong/frontend/src.bak/pages/Detail.jsx

389 lines
17 KiB
React
Raw Normal View History

import React, { useState, useEffect } from 'react'
export default function Detail() {
const params = new URLSearchParams(window.location.hash.split('?')[1] || '')
const id = params.get('id')
const [collection, setCollection] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [showDelete, setShowDelete] = useState(false)
const [showImage, setShowImage] = useState(false)
const [currentImageIndex, setCurrentImageIndex] = useState(0)
useEffect(() => {
if (id) {
fetchDetail()
}
}, [id])
const fetchDetail = async () => {
setLoading(true)
const token = localStorage.getItem('token')
try {
const res = await fetch(`/api/collections/${id}`, {
headers: token ? { 'Authorization': 'Bearer ' + token } : {}
})
if (!res.ok) {
const errorData = await res.json()
const errorCode = errorData.error?.code || 'E00000'
const errorMessage = errorData.error?.message || '加载失败'
// 标准错误码处理
if (res.status === 404) {
setError('E00033: 藏品不存在')
} else if (res.status === 401) {
setError('E00010: 未登录或登录已过期')
} else if (res.status === 403) {
setError('E00014: 无权访问此藏品')
} else {
setError(`${errorCode}: ${errorMessage}`)
}
setLoading(false)
return
}
const data = await res.json()
// 后端已返回 camelCase 格式,直接使用
const collectionData = data.data || data
if (collectionData && collectionData.id) {
setCollection(collectionData)
} else {
setError('E00000: 数据格式错误')
}
} catch (e) {
console.error(e)
setError('E00001: 网络连接失败')
}
setLoading(false)
}
const handleEdit = () => {
window.location.hash = '#/edit?id=' + id
}
const goBack = () => {
// 尝试返回到上次筛选的列表页面
const lastUrl = sessionStorage.getItem('lastListUrl')
if (lastUrl) {
sessionStorage.removeItem('lastListUrl')
window.location.hash = '#' + lastUrl
} else {
window.location.hash = '#/list'
}
}
const doDelete = async () => {
const token = localStorage.getItem('token')
try {
const res = await fetch(`/api/collections/${id}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + token }
})
if (res.ok) {
alert('删除成功!')
if (window.refreshList) window.refreshList()
window.location.hash = '#/list'
} else {
alert('删除失败')
}
} catch (e) {
alert('删除失败')
}
setShowDelete(false)
}
const getStatusText = (status) => {
const map = { 'in_collection': '收藏中', 'selling': '出售中', 'sold': '已售', 'grading': '送评中', 'repairing': '修复中', 'transit': '在途中', 'other': '其他' }
return map[status] || status || '-'
}
const getCategoryText = (category) => {
const map = { '自持': '自持', '寄存': '寄存', '寄售': '寄售', '共有': '共有', '寻号': '寻号', '其他': '其他' }
return map[category] || category || '-'
}
const Field = ({ label, value, green }) => (
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '6px 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<span style={{ color: '#94a3b8', fontSize: '13px' }}>{label}</span>
<span style={{ color: green ? '#22c55e' : '#fff', fontSize: '13px', textAlign: 'right', maxWidth: '60%' }}>{value || '-'}</span>
</div>
)
const Section = ({ title, children }) => (
<div style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '10px', padding: '12px', marginBottom: '10px' }}>
<div style={{ color: '#fbbf24', fontSize: '13px', fontWeight: 'bold', marginBottom: '8px' }}>{title}</div>
{children}
</div>
)
if (loading) {
return <div style={{ padding: '40px', textAlign: 'center', color: '#94a3b8' }}>加载中...</div>
}
if (error) {
return (
<div style={{ padding: '40px', textAlign: 'center' }}>
<div style={{ fontSize: '60px' }}></div>
<div style={{ color: '#ef4444', marginTop: '16px', fontSize: '16px', fontWeight: 'bold' }}>{error}</div>
<button onClick={goBack} style={{ marginTop: '20px', padding: '12px 24px', background: '#fbbf24', color: '#1e293b', border: 'none', borderRadius: '8px', cursor: 'pointer' }}>返回列表</button>
</div>
)
}
if (!collection) {
return (
<div style={{ padding: '40px', textAlign: 'center' }}>
<div style={{ fontSize: '60px' }}></div>
<div style={{ color: '#94a3b8', marginTop: '16px' }}>藏品不存在</div>
<button onClick={goBack} style={{ marginTop: '20px', padding: '12px 24px', background: '#fbbf24', color: '#1e293b', border: 'none', borderRadius: '8px', cursor: 'pointer' }}>返回列表</button>
</div>
)
}
return (
<div style={{ background: '#0f172a', minHeight: '100vh', paddingBottom: '80px' }}>
{/* 顶部 */}
<div style={{ padding: '12px 16px', background: 'rgba(255,255,255,0.02)', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'center', position: 'sticky', top: 0, zIndex: 100 }}>
<span style={{ fontSize: '16px', fontWeight: 'bold', color: '#fff' }}>藏品详情</span>
<div style={{ marginLeft: 'auto', display: 'flex', gap: '8px' }}>
<button onClick={goBack} style={{ padding: '6px 12px', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius: '6px', fontSize: '13px' }}>返回</button>
<button onClick={() => setShowDelete(true)} style={{ padding: '6px 12px', background: '#ef4444', color: '#fff', border: 'none', borderRadius: '6px', fontSize: '13px' }}>删除</button>
<button onClick={handleEdit} style={{ padding: '6px 12px', background: '#fbbf24', color: '#1e293b', border: 'none', borderRadius: '6px', fontSize: '13px', fontWeight: '600' }}>编辑</button>
</div>
</div>
{/* 主信息 */}
<div style={{ padding: '16px' }}>
{/* 图片显示 */}
{collection.images && collection.images.length > 0 && (
<div style={{ marginBottom: '16px' }}>
<div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(collection.images.length, 1)}, 1fr)`, gap: '12px' }}>
{collection.images.map((img, index) => (
<div key={img.id || index} style={{ position: 'relative' }}>
<img
src={img.path?.startsWith('http') ? img.path : `/${img.path || `uploads/collections/${img.filename}`}`}
alt={img.originalName || '藏品图片'}
onClick={() => { setCurrentImageIndex(index); setShowImage(true); }}
style={{
width: '100%',
aspectRatio: '1.5',
objectFit: 'contain',
borderRadius: '12px',
border: '2px solid #10b981',
cursor: 'pointer',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
background: '#0f172a'
}}
onError={(e) => {
// 图片加载失败时显示灰色占位符,不显示 logo
e.target.style.display = 'none';
e.target.parentElement.innerHTML = '<div style="width:100%;aspectRatio:1.5;background:rgba(255,255,255,0.05);border-radius:12px;display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,0.3);font-size:14px;">无图片</div>';
}}
/>
{collection.images.length > 1 && (
<div style={{
position: 'absolute',
top: '8px',
left: '8px',
background: 'rgba(0,0,0,0.6)',
color: '#fff',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px'
}}>
{index + 1}/{collection.images.length}
</div>
)}
</div>
))}
</div>
</div>
)}
<div style={{ fontSize: '18px', fontWeight: 'bold', textAlign: 'center', color: '#fff', marginBottom: '8px' }}>{collection.name}</div>
<div style={{ textAlign: 'center', color: '#fbbf24', fontSize: '14px', fontFamily: 'monospace', fontWeight: 'bold' }}>{collection.prefixSerial || '-'}</div>
</div>
{/* 信息区块 */}
<div style={{ padding: '0 16px' }}>
<Section title="基本信息">
<Field label="编号" value={collection.code} />
<Field label="持仓类型" value={getCategoryText(collection.category)} />
<Field label="珍惜度" value={collection.rarity || '-'} green={collection.rarity && collection.rarity !== '通货'} />
<Field label="版别" value={collection.version} />
<Field label="面值" value={collection.denomination} />
<Field label="状态" value={getStatusText(collection.status)} green={collection.status === 'in_collection'} />
<Field label="包装" value={collection.packaging} />
<Field label="创建时间" value={collection.createdAt ? new Date(collection.createdAt).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-') : '-'} />
</Section>
<Section title="评级信息">
<Field label="是否评级" value={collection.isGraded ? '已评级' : '未评级'} />
<Field label="评级公司" value={collection.gradingCompany} />
<Field label="评级分数" value={collection.gradingScore} green={collection.gradingScore} />
<Field label="三星" value={collection.threeStar ? '是' : '否'} green={collection.threeStar} />
</Section>
<Section title="特殊信息">
<Field label="特殊标识" value={collection.specialMark} />
<Field label="号码特征" value={collection.serialFeature} />
<Field label="号码分类" value={collection.numberCategory} />
<Field label="发行方" value={collection.issuer} />
<Field label="发行年份" value={collection.issueYear} />
<Field label="材质" value={collection.material} />
<Field label="发行量" value={collection.issueQuantity} />
</Section>
<Section title="价格信息">
<Field label="成本价" value={collection.costPrice ? '¥' + collection.costPrice : '-'} />
<Field label="目标价" value={collection.targetPrice ? '¥' + collection.targetPrice : '-'} />
<Field label="出售价" value={collection.goalPrice ? '¥' + collection.goalPrice : '-'} green={collection.goalPrice} />
<Field label="修复费" value={collection.repairFee ? '¥' + collection.repairFee : '-'} />
<Field label="评级费" value={collection.gradingFee ? '¥' + collection.gradingFee : '-'} />
<Field label="用途" value={collection.purpose} />
</Section>
{collection.remark && (
<Section title="备注">
<div style={{ color: '#fff', fontSize: '13px', lineHeight: '1.5' }}>{collection.remark}</div>
</Section>
)}
</div>
{/* 删除确认弹窗 */}
{showDelete && (
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '24px', width: '80%', maxWidth: '300px' }}>
<div style={{ color: '#fff', fontSize: '16px', fontWeight: 'bold', marginBottom: '16px', textAlign: 'center' }}>确定删除此藏品</div>
<div style={{ display: 'flex', gap: '12px' }}>
<button onClick={() => setShowDelete(false)} style={{ flex: 1, padding: '12px', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius: '8px', fontSize: '14px' }}>取消</button>
<button onClick={doDelete} style={{ flex: 1, padding: '12px', background: '#ef4444', color: '#fff', border: 'none', borderRadius: '8px', fontSize: '14px' }}>删除</button>
</div>
</div>
</div>
)}
{/* 图片查看器 */}
{showImage && collection.images && collection.images.length > 0 && (
<div
style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.95)', zIndex: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px' }}
onClick={() => setShowImage(false)}
>
{/* 左侧切换按钮 */}
{collection.images.length > 1 && (
<button
onClick={(e) => { e.stopPropagation(); setCurrentImageIndex((currentImageIndex - 1 + collection.images.length) % collection.images.length); }}
style={{
position: 'absolute',
left: '20px',
background: 'rgba(255,255,255,0.2)',
color: '#fff',
border: 'none',
borderRadius: '50%',
width: '50px',
height: '50px',
fontSize: '24px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
</button>
)}
{/* 图片 */}
<img
src={collection.images[currentImageIndex].path?.startsWith('http') ? collection.images[currentImageIndex].path : `/${collection.images[currentImageIndex].path || `uploads/collections/${collection.images[currentImageIndex].filename}`}`}
alt={collection.images[currentImageIndex].originalName || '藏品图片'}
onClick={(e) => e.stopPropagation()}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
/>
{/* 右侧切换按钮 */}
{collection.images.length > 1 && (
<button
onClick={(e) => { e.stopPropagation(); setCurrentImageIndex((currentImageIndex + 1) % collection.images.length); }}
style={{
position: 'absolute',
right: '20px',
background: 'rgba(255,255,255,0.2)',
color: '#fff',
border: 'none',
borderRadius: '50%',
width: '50px',
height: '50px',
fontSize: '24px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
</button>
)}
{/* 图片指示器 */}
{collection.images.length > 1 && (
<div
style={{
position: 'absolute',
bottom: '40px',
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
gap: '10px'
}}
onClick={(e) => e.stopPropagation()}
>
{collection.images.map((_, index) => (
<div
key={index}
onClick={() => setCurrentImageIndex(index)}
style={{
width: '12px',
height: '12px',
borderRadius: '50%',
background: index === currentImageIndex ? '#fbbf24' : 'rgba(255,255,255,0.3)',
cursor: 'pointer',
border: '2px solid #fff'
}}
/>
))}
</div>
)}
{/* 关闭提示 */}
<div
style={{
position: 'absolute',
top: '20px',
right: '20px',
background: 'rgba(255,255,255,0.2)',
color: '#fff',
border: 'none',
borderRadius: '50%',
width: '40px',
height: '40px',
fontSize: '24px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
onClick={() => setShowImage(false)}
>
×
</div>
</div>
)}
</div>
)
}
// v2.7.1