v1.2.9 - 分页加载、用户编码管理、统计页优化
This commit is contained in:
parent
784ea0b0ea
commit
8c101c36e1
|
|
@ -1 +1 @@
|
|||
VERSION=1.2.6
|
||||
VERSION=1.2.9
|
||||
|
|
|
|||
|
|
@ -126,7 +126,8 @@ export default function Admin() {
|
|||
const updateData = {
|
||||
username: editingUser.username,
|
||||
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) {
|
||||
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('更新成功!')
|
||||
|
|
@ -362,6 +365,18 @@ export default function Admin() {
|
|||
<option value="admin">管理员</option>
|
||||
</select>
|
||||
</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>
|
||||
|
||||
{/* 第二部分:修改密码 */}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ export default function List() {
|
|||
const [search, setSearch] = useState('')
|
||||
const [isAdmin, setIsAdmin] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
// 检查是否管理员
|
||||
|
|
@ -62,27 +63,16 @@ export default function List() {
|
|||
}
|
||||
}, [])
|
||||
|
||||
// 滚动加载更多
|
||||
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)
|
||||
const fetchCollections = async (reset = true) => {
|
||||
if (!reset && (loading || loadingMore || !hasMore)) return
|
||||
if (reset) {
|
||||
setLoading(true)
|
||||
} else {
|
||||
setLoadingMore(true)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [loading, loadingMore, hasMore, page])
|
||||
|
||||
const fetchCollections = async () => {
|
||||
// set loading based on reset
|
||||
const token = localStorage.getItem('token')
|
||||
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 } : {}
|
||||
})
|
||||
|
||||
|
|
@ -100,6 +90,12 @@ export default function List() {
|
|||
if (!Array.isArray(list) && list && Array.isArray(list.items)) {
|
||||
list = list.items
|
||||
}
|
||||
// 获取总数
|
||||
if (data.pagination && data.pagination.total) {
|
||||
setTotal(data.pagination.total)
|
||||
} else if (reset) {
|
||||
setTotal(list.length)
|
||||
}
|
||||
|
||||
// 应用筛选条件
|
||||
// 用户ID筛选(从URL获取)
|
||||
|
|
@ -139,14 +135,17 @@ export default function List() {
|
|||
console.log(`筛选:${filterType} = ${filter}, 结果:${list.length}条`)
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
if (page === 1) {
|
||||
if (reset) {
|
||||
setCollections(list || [])
|
||||
setPage(1)
|
||||
} 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) {
|
||||
console.error('Fetch collections error:', e)
|
||||
// 网络错误也跳转到登录页
|
||||
|
|
@ -155,6 +154,7 @@ export default function List() {
|
|||
window.location.hash = '#/login'
|
||||
}
|
||||
setLoading(false)
|
||||
setLoadingMore(false)
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
|
|
@ -166,6 +166,22 @@ export default function List() {
|
|||
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) => {
|
||||
// 保存当前列表的URL(包含筛选条件),用于返回时恢复
|
||||
sessionStorage.setItem('lastListUrl', window.location.hash.substring(1))
|
||||
|
|
@ -454,7 +470,7 @@ export default function List() {
|
|||
<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={{ 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' }}>
|
||||
{filter && filterType && (
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -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={{ color: '#fbbf24', fontSize: '14px', fontWeight: 'bold', marginBottom: '12px' }}>{title}</div>
|
||||
<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 label = getLabel(type, item[labelKey] || value)
|
||||
const color = getColor(type, value)
|
||||
|
|
@ -201,7 +201,6 @@ export default function Stats() {
|
|||
</div>
|
||||
{data.length > 6 && (
|
||||
<div style={{ textAlign: 'center', color: '#94a3b8', fontSize: '12px', marginTop: '8px' }}>
|
||||
共{data.length}项,显示前 6 项
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue