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 = {
|
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>
|
||||||
|
|
||||||
{/* 第二部分:修改密码 */}
|
{/* 第二部分:修改密码 */}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
// 应用筛选条件
|
// 应用筛选条件
|
||||||
// 用户ID筛选(从URL获取)
|
// 用户ID筛选(从URL获取)
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue