v1.2.9 - 分页加载、用户编码管理、统计页优化

This commit is contained in:
甲辰生产 2026-03-22 21:20:47 +08:00
parent 784ea0b0ea
commit 8c101c36e1
5 changed files with 62 additions and 32 deletions

View File

@ -1 +1 @@
VERSION=v1.2.4 VERSION=1.2.9

View File

@ -1 +1 @@
VERSION=1.2.6 VERSION=1.2.9

View File

@ -126,7 +126,8 @@ export default function Admin() {
const updateData = { const updateData = {
username: editingUser.username, username: editingUser.username,
email: editingUser.email, email: editingUser.email,
role: editingUser.role role: editingUser.role,
user_code: editingUser.user_code
} }
// //
@ -145,7 +146,9 @@ export default function Admin() {
if (!res.ok) { if (!res.ok) {
const data = await res.json() const data = await res.json()
throw new Error(data.detail || '更新失败') // : {error: {code, message}} : {detail}
const errorMsg = data.error?.message || data.detail || '更新失败'
throw new Error(errorMsg)
} }
alert('更新成功!') alert('更新成功!')
@ -362,6 +365,18 @@ export default function Admin() {
<option value="admin">管理员</option> <option value="admin">管理员</option>
</select> </select>
</div> </div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>用户编码 <span style={{ color: '#64748b', fontSize: '12px' }}>0001</span></label>
<input
type="text"
value={editingUser.user_code || ''}
onChange={(e) => setEditingUser({ ...editingUser, user_code: e.target.value })}
placeholder="4位数字0001"
maxLength={4}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
</div> </div>
{/* 第二部分:修改密码 */} {/* 第二部分:修改密码 */}

View File

@ -16,8 +16,9 @@ export default function List() {
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const [isAdmin, setIsAdmin] = useState(false) const [isAdmin, setIsAdmin] = useState(false)
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
const [loadingMore, setLoadingMore] = useState(false) const [loadingMore, setLoadingMore] = useState(false)
const [hasMore, setHasMore] = useState(true)
const [total, setTotal] = useState(0)
useEffect(() => { useEffect(() => {
// //
@ -61,28 +62,17 @@ export default function List() {
window.removeEventListener('focus', fetchCollections) window.removeEventListener('focus', fetchCollections)
} }
}, []) }, [])
//
useEffect(() => {
const handleScroll = () => {
if (loading || loadingMore || !hasMore) return
const scrollTop = window.scrollY || document.documentElement.scrollTop
const scrollHeight = document.documentElement.scrollHeight
const clientHeight = document.documentElement.clientHeight
if (scrollTop + clientHeight >= scrollHeight - 100) {
fetchCollections(false)
}
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [loading, loadingMore, hasMore, page])
const fetchCollections = async () => { const fetchCollections = async (reset = true) => {
// set loading based on reset if (!reset && (loading || loadingMore || !hasMore)) return
if (reset) {
setLoading(true)
} else {
setLoadingMore(true)
}
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
try { try {
const res = await fetch('/api/collections?page=1&limit=50', { const res = await fetch(`/api/collections?page=${page}&limit=50`, {
headers: token ? { 'Authorization': 'Bearer ' + token } : {} headers: token ? { 'Authorization': 'Bearer ' + token } : {}
}) })
@ -100,6 +90,12 @@ export default function List() {
if (!Array.isArray(list) && list && Array.isArray(list.items)) { if (!Array.isArray(list) && list && Array.isArray(list.items)) {
list = list.items list = list.items
} }
//
if (data.pagination && data.pagination.total) {
setTotal(data.pagination.total)
} else if (reset) {
setTotal(list.length)
}
// //
// IDURL // IDURL
@ -139,14 +135,17 @@ export default function List() {
console.log(`筛选:${filterType} = ${filter}, 结果:${list.length}`) console.log(`筛选:${filterType} = ${filter}, 结果:${list.length}`)
} }
// if (reset) {
if (page === 1) {
setCollections(list || []) setCollections(list || [])
setPage(1)
} else { } else {
setCollections(prev => [...prev, ...(list || [])]) setCollections(prev => [...prev, ...list])
}
if (list.length < 50) {
setHasMore(false)
} else {
setPage(p => p + 1)
} }
setHasMore((list || []).length >= 50)
if (page > 1) setPage(p => p + 1)
} catch (e) { } catch (e) {
console.error('Fetch collections error:', e) console.error('Fetch collections error:', e)
// //
@ -155,6 +154,7 @@ export default function List() {
window.location.hash = '#/login' window.location.hash = '#/login'
} }
setLoading(false) setLoading(false)
setLoadingMore(false)
} }
const refresh = () => { const refresh = () => {
@ -165,6 +165,22 @@ export default function List() {
window.refreshList = refresh window.refreshList = refresh
return () => { delete window.refreshList } return () => { delete window.refreshList }
}, []) }, [])
//
useEffect(() => {
const handleScroll = () => {
if (loading || loadingMore || !hasMore) return
const scrollTop = window.scrollY || document.documentElement.scrollTop
const scrollHeight = document.documentElement.scrollHeight
const clientHeight = document.documentElement.clientHeight
if (scrollTop + clientHeight >= scrollHeight - 100) {
fetchCollections(false)
}
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [loading, loadingMore, hasMore, page])
const goDetail = (id) => { const goDetail = (id) => {
// URL // URL
@ -454,7 +470,7 @@ export default function List() {
<div style={{ minHeight: '100vh', overflowY: 'auto', background: '#0f172a', paddingBottom: '70px' }}> <div style={{ minHeight: '100vh', overflowY: 'auto', background: '#0f172a', paddingBottom: '70px' }}>
<div style={{ padding: '20px', background: 'rgba(255,255,255,0.02)', borderBottom: '1px solid rgba(255,255,255,0.05)' }}> <div style={{ padding: '20px', background: 'rgba(255,255,255,0.02)', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
<div style={{ color: '#fff', fontSize: '20px', }}>我的藏品 <span style={{ fontSize: '14px', color: '#fbbf24' }}>({filteredCollections.length})</span></div> <div style={{ color: '#fff', fontSize: '20px', }}>我的藏品 <span style={{ fontSize: '14px', color: '#fbbf24' }}>({total || filteredCollections.length})</span></div>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
{filter && filterType && ( {filter && filterType && (
<button <button

View File

@ -159,7 +159,7 @@ export default function Stats() {
<div style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '12px', padding: '16px', marginBottom: '12px' }}> <div style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '12px', padding: '16px', marginBottom: '12px' }}>
<div style={{ color: '#fbbf24', fontSize: '14px', fontWeight: 'bold', marginBottom: '12px' }}>{title}</div> <div style={{ color: '#fbbf24', fontSize: '14px', fontWeight: 'bold', marginBottom: '12px' }}>{title}</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
{getSortedData(data, type).slice(0, 6).map((item, index) => { {getSortedData(data, type).map((item, index) => {
const value = item[valueKey] const value = item[valueKey]
const label = getLabel(type, item[labelKey] || value) const label = getLabel(type, item[labelKey] || value)
const color = getColor(type, value) const color = getColor(type, value)
@ -201,7 +201,6 @@ export default function Stats() {
</div> </div>
{data.length > 6 && ( {data.length > 6 && (
<div style={{ textAlign: 'center', color: '#94a3b8', fontSize: '12px', marginTop: '8px' }}> <div style={{ textAlign: 'center', color: '#94a3b8', fontSize: '12px', marginTop: '8px' }}>
{data.length}显示前 6
</div> </div>
)} )}
</div> </div>