feat: Establish core backend services including SQLite database, structured logging, and initial owner management capabilities.

This commit is contained in:
2026-02-06 20:53:05 +08:00
parent 98ac10987c
commit 6c8a036018
4 changed files with 199 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback } from 'react'
import { Users, Trash2, RefreshCw, ChevronLeft, ChevronRight, Key, CheckSquare, Square, ShieldCheck, Settings, Clock } from 'lucide-react'
import { Users, Trash2, RefreshCw, ChevronLeft, ChevronRight, Key, CheckSquare, Square, MinusSquare, ShieldCheck, Settings, Clock } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../common'
interface TeamOwner {
@@ -58,6 +58,7 @@ export default function OwnerList({ onStatsChange }: OwnerListProps) {
const [page, setPage] = useState(0)
const [filter, setFilter] = useState<string>('')
const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set())
const [selectAllMode, setSelectAllMode] = useState(false) // 是否全选所有页
const [deleting, setDeleting] = useState(false)
const limit = 20
@@ -93,8 +94,10 @@ export default function OwnerList({ onStatsChange }: OwnerListProps) {
useEffect(() => {
loadOwners()
// 清除选择
setSelectedIds(new Set())
// 清除选择(全选所有页模式除外)
if (!selectAllMode) {
setSelectedIds(new Set())
}
}, [page, filter])
// 加载封禁检查配置
@@ -295,6 +298,7 @@ export default function OwnerList({ onStatsChange }: OwnerListProps) {
// 选择逻辑
const toggleSelect = (id: number) => {
setSelectAllMode(false)
const newSet = new Set(selectedIds)
if (newSet.has(id)) {
newSet.delete(id)
@@ -305,14 +309,40 @@ export default function OwnerList({ onStatsChange }: OwnerListProps) {
}
const toggleSelectAll = () => {
if (selectedIds.size === owners.length) {
if (selectedIds.size === owners.length && !selectAllMode) {
// 当前页已全选,取消全选
setSelectedIds(new Set())
} else {
// 选中当前页所有
setSelectAllMode(false)
setSelectedIds(new Set(owners.map(o => o.id)))
}
}
const isAllSelected = owners.length > 0 && selectedIds.size === owners.length
// 全选所有页
const handleSelectAllPages = async () => {
try {
const params = new URLSearchParams()
if (filter) params.set('status', filter)
const res = await fetch(`/api/db/owners/ids?${params}`)
const data = await res.json()
if (data.code === 0 && data.data.ids) {
setSelectedIds(new Set(data.data.ids))
setSelectAllMode(true)
}
} catch (e) {
console.error('Failed to select all:', e)
}
}
// 取消全选
const handleClearSelection = () => {
setSelectedIds(new Set())
setSelectAllMode(false)
}
const isAllSelected = owners.length > 0 && selectedIds.size === owners.length && !selectAllMode
const isPagePartialSelected = selectedIds.size > 0 && owners.some(o => selectedIds.has(o.id)) && !isAllSelected && !selectAllMode
const totalPages = Math.ceil(total / limit)
@@ -478,14 +508,60 @@ export default function OwnerList({ onStatsChange }: OwnerListProps) {
)}
<CardContent className="flex-1 overflow-hidden p-0">
{/* 全选提示条 */}
{selectedIds.size > 0 && (
<div className="px-4 py-2 bg-blue-50 dark:bg-blue-900/20 border-b border-blue-100 dark:border-blue-800 flex items-center gap-3 text-sm">
{selectAllMode ? (
<>
<span className="text-blue-700 dark:text-blue-300">
<strong>{selectedIds.size}</strong> {filter ? ` "${statusLabels[filter] || filter}" 状态的` : ''}
</span>
<button
onClick={handleClearSelection}
className="text-blue-600 dark:text-blue-400 hover:underline font-medium"
>
</button>
</>
) : isAllSelected && total > owners.length ? (
<>
<span className="text-blue-700 dark:text-blue-300">
<strong>{owners.length}</strong>
</span>
<button
onClick={handleSelectAllPages}
className="text-blue-600 dark:text-blue-400 hover:underline font-medium"
>
{total} {filter ? ` "${statusLabels[filter] || filter}" 状态的` : ''}
</button>
</>
) : (
<span className="text-blue-700 dark:text-blue-300">
<strong>{selectedIds.size}</strong>
{total > owners.length && (
<button
onClick={handleSelectAllPages}
className="ml-3 text-blue-600 dark:text-blue-400 hover:underline font-medium"
>
{total}
</button>
)}
</span>
)}
</div>
)}
<div className="h-full overflow-auto">
<table className="w-full text-sm">
<thead className="bg-slate-50 dark:bg-slate-800 sticky top-0">
<tr>
<th className="text-center p-3 w-10">
<button onClick={toggleSelectAll} className="hover:text-blue-500">
{isAllSelected ? (
{selectAllMode ? (
<CheckSquare className="h-4 w-4 text-blue-500" />
) : isAllSelected ? (
<CheckSquare className="h-4 w-4 text-blue-500" />
) : isPagePartialSelected ? (
<MinusSquare className="h-4 w-4 text-blue-400" />
) : (
<Square className="h-4 w-4" />
)}