优化列表分页和数据加载
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
"""账号管理路由"""
|
"""账号管理路由"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func, or_
|
||||||
from sqlalchemy.orm import Session, joinedload
|
from sqlalchemy.orm import Session, defer, joinedload
|
||||||
|
|
||||||
from ..database import get_db
|
from ..database import get_db
|
||||||
from ..models import User, Account, AuditLog, LoginTask
|
from ..models import User, Account, AuditLog, LoginTask
|
||||||
@@ -16,11 +16,15 @@ from ..services.account_service import (
|
|||||||
router = APIRouter(prefix="/api/accounts", tags=["账号管理"])
|
router = APIRouter(prefix="/api/accounts", tags=["账号管理"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[AccountOut])
|
@router.get("")
|
||||||
def list_accounts(
|
def list_accounts(
|
||||||
assigned_only: bool = Query(False),
|
assigned_only: bool = Query(False),
|
||||||
tag: str = Query(None),
|
tag: str = Query(None),
|
||||||
has_cookie: bool = Query(False),
|
has_cookie: bool = Query(False),
|
||||||
|
search: str = Query(""),
|
||||||
|
page: int | None = Query(None, ge=1),
|
||||||
|
page_size: int = Query(20, ge=1, le=200),
|
||||||
|
include_sensitive: bool = Query(True),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current: User = Depends(get_current_user),
|
current: User = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
@@ -44,7 +48,31 @@ def list_accounts(
|
|||||||
if tag:
|
if tag:
|
||||||
query = query.filter(Account.tag == tag)
|
query = query.filter(Account.tag == tag)
|
||||||
|
|
||||||
accounts = query.order_by(Account.id).options(joinedload(Account.assigned_user)).all()
|
search_text = (search or "").strip()
|
||||||
|
if search_text:
|
||||||
|
pattern = f"%{search_text}%"
|
||||||
|
query = query.filter(or_(
|
||||||
|
Account.username.ilike(pattern),
|
||||||
|
Account.tag.ilike(pattern),
|
||||||
|
Account.remark.ilike(pattern),
|
||||||
|
))
|
||||||
|
|
||||||
|
total = None
|
||||||
|
if page is not None:
|
||||||
|
total = query.order_by(None).count()
|
||||||
|
query = query.order_by(Account.id)
|
||||||
|
if page is not None:
|
||||||
|
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||||
|
|
||||||
|
can_include_sensitive = include_sensitive and user_has_permission(current, "account:view_full")
|
||||||
|
options = [joinedload(Account.assigned_user)]
|
||||||
|
if not can_include_sensitive:
|
||||||
|
options.extend([
|
||||||
|
defer(Account.password),
|
||||||
|
defer(Account.email),
|
||||||
|
defer(Account.email_password),
|
||||||
|
])
|
||||||
|
accounts = query.options(*options).all()
|
||||||
result = []
|
result = []
|
||||||
for acc in accounts:
|
for acc in accounts:
|
||||||
item = AccountOut(
|
item = AccountOut(
|
||||||
@@ -55,14 +83,45 @@ def list_accounts(
|
|||||||
created_at=acc.created_at,
|
created_at=acc.created_at,
|
||||||
)
|
)
|
||||||
# 只有管理员可看完整字段(密码、邮箱等)
|
# 只有管理员可看完整字段(密码、邮箱等)
|
||||||
if user_has_permission(current, "account:view_full"):
|
if can_include_sensitive:
|
||||||
item.password = acc.password
|
item.password = acc.password
|
||||||
item.email = acc.email
|
item.email = acc.email
|
||||||
item.email_password = acc.email_password
|
item.email_password = acc.email_password
|
||||||
result.append(item)
|
result.append(item)
|
||||||
|
if page is not None:
|
||||||
|
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/summary")
|
||||||
|
def accounts_summary(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""账号管理统计,避免前端为了卡片统计拉全量账号。"""
|
||||||
|
query = db.query(Account)
|
||||||
|
if not user_has_permission(current, "account:view_all"):
|
||||||
|
if user_has_permission(current, "account:view_assigned"):
|
||||||
|
query = query.filter(Account.assigned_to == current.id)
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=403, detail="无权查看账号")
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
assigned_count = query.filter(Account.assigned_to.isnot(None)).count()
|
||||||
|
tag_count = (
|
||||||
|
query.filter(Account.tag != "", Account.tag.isnot(None))
|
||||||
|
.with_entities(Account.tag)
|
||||||
|
.distinct()
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"total": total,
|
||||||
|
"assigned_count": assigned_count,
|
||||||
|
"unassigned_count": max(0, total - assigned_count),
|
||||||
|
"tag_count": tag_count,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/import")
|
@router.post("/import")
|
||||||
def import_accounts(
|
def import_accounts(
|
||||||
req: AccountImport,
|
req: AccountImport,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"""Cookie 管理路由"""
|
"""Cookie 管理路由"""
|
||||||
|
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy import or_
|
||||||
|
from sqlalchemy.orm import Session, defer, joinedload
|
||||||
import io
|
import io
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
@@ -38,17 +39,52 @@ def _visible_cookie_tasks_query(db: Session, current: User):
|
|||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
def list_cookies(
|
def list_cookies(
|
||||||
|
search: str = Query(""),
|
||||||
|
page: int | None = Query(None, ge=1),
|
||||||
|
page_size: int = Query(20, ge=1, le=200),
|
||||||
|
include_cookie: bool = Query(True),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current: User = Depends(get_current_user),
|
current: User = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
"""查看登录成功的 Cookie 列表。"""
|
"""查看登录成功的 Cookie 列表。"""
|
||||||
tasks = _visible_cookie_tasks_query(db, current).order_by(LoginTask.finished_at.desc()).all()
|
if not include_cookie:
|
||||||
|
query = _visible_cookie_tasks_query(db, current).options(defer(LoginTask.cookie))
|
||||||
|
else:
|
||||||
|
query = _visible_cookie_tasks_query(db, current).options(
|
||||||
|
joinedload(LoginTask.account).joinedload(Account.assigned_user),
|
||||||
|
)
|
||||||
|
search_text = (search or "").strip()
|
||||||
|
if search_text:
|
||||||
|
pattern = f"%{search_text}%"
|
||||||
|
if user_has_permission(current, "login:view_all"):
|
||||||
|
query = query.join(Account, LoginTask.account_id == Account.id)
|
||||||
|
query = query.outerjoin(User, Account.assigned_to == User.id).filter(or_(
|
||||||
|
Account.username.ilike(pattern),
|
||||||
|
User.username.ilike(pattern),
|
||||||
|
))
|
||||||
|
|
||||||
|
total = None
|
||||||
|
if page is not None:
|
||||||
|
total = query.order_by(None).count()
|
||||||
|
query = query.order_by(LoginTask.finished_at.desc())
|
||||||
|
if page is not None:
|
||||||
|
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||||
|
|
||||||
|
tasks = query.all()
|
||||||
|
|
||||||
# 批量查账号,避免 N+1 查询
|
# 批量查账号,避免 N+1 查询
|
||||||
account_ids = [t.account_id for t in tasks]
|
account_ids = [t.account_id for t in tasks]
|
||||||
accounts_map = {}
|
accounts_map = {}
|
||||||
if account_ids:
|
if account_ids:
|
||||||
accs = db.query(Account).filter(Account.id.in_(account_ids)).all()
|
account_query = db.query(Account).filter(Account.id.in_(account_ids))
|
||||||
|
if not include_cookie:
|
||||||
|
account_query = account_query.options(
|
||||||
|
joinedload(Account.assigned_user),
|
||||||
|
defer(Account.password),
|
||||||
|
defer(Account.email),
|
||||||
|
defer(Account.email_password),
|
||||||
|
)
|
||||||
|
accs = account_query.all()
|
||||||
accounts_map = {a.id: a for a in accs}
|
accounts_map = {a.id: a for a in accs}
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
@@ -63,8 +99,8 @@ def list_cookies(
|
|||||||
"assigned_username": acc.assigned_user.username if acc and acc.assigned_user else None,
|
"assigned_username": acc.assigned_user.username if acc and acc.assigned_user else None,
|
||||||
"created_at": _fmt_dt(t.finished_at),
|
"created_at": _fmt_dt(t.finished_at),
|
||||||
}
|
}
|
||||||
# 只有有 cookie:view 权限才返回 cookie 和密码(用于自定义格式复制)
|
# 分页列表默认只返回预览,复制/导出时再获取完整敏感字段。
|
||||||
if user_has_permission(current, "cookie:view"):
|
if include_cookie and user_has_permission(current, "cookie:view"):
|
||||||
cookie = t.cookie or ""
|
cookie = t.cookie or ""
|
||||||
item["cookie"] = cookie
|
item["cookie"] = cookie
|
||||||
item["cookie_preview"] = cookie[:50] + "..." if len(cookie) > 50 else cookie
|
item["cookie_preview"] = cookie[:50] + "..." if len(cookie) > 50 else cookie
|
||||||
@@ -74,9 +110,60 @@ def list_cookies(
|
|||||||
item["cookie_preview"] = "***"
|
item["cookie_preview"] = "***"
|
||||||
item["account_password"] = ""
|
item["account_password"] = ""
|
||||||
result.append(item)
|
result.append(item)
|
||||||
|
if page is not None:
|
||||||
|
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/summary")
|
||||||
|
def cookies_summary(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Cookie 管理统计,避免前端为了卡片统计拉全量 Cookie。"""
|
||||||
|
query = _visible_cookie_tasks_query(db, current)
|
||||||
|
if user_has_permission(current, "login:view_all"):
|
||||||
|
query = query.join(Account, LoginTask.account_id == Account.id)
|
||||||
|
total = query.count()
|
||||||
|
assigned_count = query.filter(Account.assigned_to.isnot(None)).count()
|
||||||
|
return {
|
||||||
|
"total": total,
|
||||||
|
"assigned_count": assigned_count,
|
||||||
|
"unassigned_count": max(0, total - assigned_count),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{task_id}")
|
||||||
|
def get_cookie(
|
||||||
|
task_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""获取单条 Cookie 详情,供复制操作按需读取完整敏感字段。"""
|
||||||
|
task = _visible_cookie_tasks_query(db, current).filter(LoginTask.id == task_id).first()
|
||||||
|
if not task:
|
||||||
|
raise HTTPException(status_code=404, detail="记录不存在")
|
||||||
|
acc = db.query(Account).filter(Account.id == task.account_id).first()
|
||||||
|
item = {
|
||||||
|
"id": task.id,
|
||||||
|
"batch_id": task.batch_id,
|
||||||
|
"account_id": task.account_id,
|
||||||
|
"account_username": acc.username if acc else "",
|
||||||
|
"assigned_to": acc.assigned_to if acc else None,
|
||||||
|
"assigned_username": acc.assigned_user.username if acc and acc.assigned_user else None,
|
||||||
|
"created_at": _fmt_dt(task.finished_at),
|
||||||
|
"cookie": "",
|
||||||
|
"cookie_preview": "***",
|
||||||
|
"account_password": "",
|
||||||
|
}
|
||||||
|
if user_has_permission(current, "cookie:view"):
|
||||||
|
cookie = task.cookie or ""
|
||||||
|
item["cookie"] = cookie
|
||||||
|
item["cookie_preview"] = cookie[:50] + "..." if len(cookie) > 50 else cookie
|
||||||
|
item["account_password"] = acc.password if acc else ""
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
@router.get("/export")
|
@router.get("/export")
|
||||||
def export_cookies(
|
def export_cookies(
|
||||||
format: str = "csv",
|
format: str = "csv",
|
||||||
|
|||||||
+188
-9
@@ -8,8 +8,8 @@ from datetime import datetime, timezone
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect
|
from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func, or_
|
||||||
from sqlalchemy.orm import Session, joinedload
|
from sqlalchemy.orm import Session, defer, joinedload
|
||||||
|
|
||||||
from core.huya import (
|
from core.huya import (
|
||||||
HuyaCredentialError,
|
HuyaCredentialError,
|
||||||
@@ -134,6 +134,31 @@ def _account_out(account: HuyaAccount, include_cookie: bool = True) -> HuyaAccou
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _account_out_light(account: HuyaAccount, *, has_password: bool = False) -> HuyaAccountOut:
|
||||||
|
"""虎牙账号列表轻量输出,不读取加密字段。"""
|
||||||
|
return HuyaAccountOut(
|
||||||
|
id=account.id,
|
||||||
|
uid=account.uid or "",
|
||||||
|
yyuid=account.yyuid or "",
|
||||||
|
username=account.username or "",
|
||||||
|
has_password=has_password,
|
||||||
|
nickname=account.nickname or "",
|
||||||
|
cookie="",
|
||||||
|
cookie_preview="***",
|
||||||
|
tag=account.tag or "",
|
||||||
|
remark=account.remark or "",
|
||||||
|
status=account.status or "",
|
||||||
|
points=account.points,
|
||||||
|
game_name=account.game_name or "",
|
||||||
|
game_channel=account.game_channel or "",
|
||||||
|
game_phone=account.game_phone or "",
|
||||||
|
assigned_to=account.assigned_to,
|
||||||
|
assigned_username=account.assigned_user.username if account.assigned_user else None,
|
||||||
|
created_at=account.created_at,
|
||||||
|
updated_at=account.updated_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_task_result(result: dict | None, *, include_images: bool = False) -> dict | None:
|
def _sanitize_task_result(result: dict | None, *, include_images: bool = False) -> dict | None:
|
||||||
"""列表接口默认剥离 base64 图片,避免轮询每次传 1MB+ 数据。"""
|
"""列表接口默认剥离 base64 图片,避免轮询每次传 1MB+ 数据。"""
|
||||||
if not isinstance(result, dict):
|
if not isinstance(result, dict):
|
||||||
@@ -196,17 +221,50 @@ def _cookie_out(account: HuyaAccount, include_cookie: bool) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _cookie_out_light(account: HuyaAccount) -> dict:
|
||||||
|
"""虎牙 Cookie 列表轻量输出,不读取完整加密 Cookie。"""
|
||||||
|
account_name = account.nickname or account.username or account.uid or str(account.id)
|
||||||
|
return {
|
||||||
|
"id": account.id,
|
||||||
|
"account_id": account.id,
|
||||||
|
"account_username": account_name,
|
||||||
|
"uid": account.uid or "",
|
||||||
|
"yyuid": account.yyuid or "",
|
||||||
|
"assigned_to": account.assigned_to,
|
||||||
|
"assigned_username": account.assigned_user.username if account.assigned_user else None,
|
||||||
|
"created_at": account.updated_at.isoformat() if account.updated_at else None,
|
||||||
|
"cookie": "",
|
||||||
|
"cookie_preview": "***",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _huya_password_state_map(db: Session, account_ids: list[int]) -> dict[int, bool]:
|
||||||
|
"""不解密密码字段,仅判断密文字段是否非空。"""
|
||||||
|
if not account_ids:
|
||||||
|
return {}
|
||||||
|
rows = (
|
||||||
|
db.query(HuyaAccount.id, (HuyaAccount.account_password != "").label("has_password"))
|
||||||
|
.filter(HuyaAccount.id.in_(account_ids))
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
return {account_id: bool(has_password) for account_id, has_password in rows}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/task-types")
|
@router.get("/task-types")
|
||||||
def task_types(current: User = Depends(require_permission("huya:task"))):
|
def task_types(current: User = Depends(require_permission("huya:task"))):
|
||||||
"""返回当前规划的虎牙任务类型。"""
|
"""返回当前规划的虎牙任务类型。"""
|
||||||
return SUPPORTED_TASK_TYPES
|
return SUPPORTED_TASK_TYPES
|
||||||
|
|
||||||
|
|
||||||
@router.get("/accounts", response_model=list[HuyaAccountOut])
|
@router.get("/accounts")
|
||||||
def list_accounts(
|
def list_accounts(
|
||||||
assigned_only: bool = Query(False),
|
assigned_only: bool = Query(False),
|
||||||
tag: str | None = Query(None),
|
tag: str | None = Query(None),
|
||||||
has_cookie: bool = Query(False),
|
has_cookie: bool = Query(False),
|
||||||
|
search: str = Query(""),
|
||||||
|
page: int | None = Query(None, ge=1),
|
||||||
|
page_size: int = Query(20, ge=1, le=200),
|
||||||
|
include_cookie: bool | None = Query(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current: User = Depends(get_current_user),
|
current: User = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
@@ -218,9 +276,71 @@ def list_accounts(
|
|||||||
query = query.filter(HuyaAccount.tag == tag)
|
query = query.filter(HuyaAccount.tag == tag)
|
||||||
if has_cookie:
|
if has_cookie:
|
||||||
query = query.filter(HuyaAccount.cookie != "")
|
query = query.filter(HuyaAccount.cookie != "")
|
||||||
accounts = query.order_by(HuyaAccount.id.desc()).all()
|
search_text = (search or "").strip()
|
||||||
include_cookie = _can_view_huya_cookie(current)
|
if search_text:
|
||||||
return [_account_out(account, include_cookie=include_cookie) for account in accounts]
|
pattern = f"%{search_text}%"
|
||||||
|
query = query.filter(or_(
|
||||||
|
HuyaAccount.uid.ilike(pattern),
|
||||||
|
HuyaAccount.yyuid.ilike(pattern),
|
||||||
|
HuyaAccount.username.ilike(pattern),
|
||||||
|
HuyaAccount.nickname.ilike(pattern),
|
||||||
|
HuyaAccount.tag.ilike(pattern),
|
||||||
|
HuyaAccount.game_name.ilike(pattern),
|
||||||
|
HuyaAccount.game_phone.ilike(pattern),
|
||||||
|
))
|
||||||
|
|
||||||
|
total = None
|
||||||
|
if page is not None:
|
||||||
|
total = query.order_by(None).count()
|
||||||
|
should_include_cookie = _can_view_huya_cookie(current) if include_cookie is None else (include_cookie and _can_view_huya_cookie(current))
|
||||||
|
query = query.order_by(HuyaAccount.id.desc())
|
||||||
|
if page is not None:
|
||||||
|
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||||
|
|
||||||
|
if not should_include_cookie:
|
||||||
|
query = query.options(defer(HuyaAccount.cookie), defer(HuyaAccount.account_password))
|
||||||
|
accounts = query.all()
|
||||||
|
if should_include_cookie:
|
||||||
|
result = [_account_out(account, include_cookie=True) for account in accounts]
|
||||||
|
else:
|
||||||
|
password_map = _huya_password_state_map(db, [account.id for account in accounts])
|
||||||
|
result = [_account_out_light(account, has_password=password_map.get(account.id, False)) for account in accounts]
|
||||||
|
if page is not None:
|
||||||
|
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/accounts/summary")
|
||||||
|
def accounts_summary(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""虎牙账号统计,避免前端为了卡片统计拉全量账号。"""
|
||||||
|
query = _visible_huya_accounts_query(db, current)
|
||||||
|
total = query.count()
|
||||||
|
assigned_count = query.filter(HuyaAccount.assigned_to.isnot(None)).count()
|
||||||
|
tag_count = (
|
||||||
|
query.filter(HuyaAccount.tag != "", HuyaAccount.tag.isnot(None))
|
||||||
|
.with_entities(HuyaAccount.tag)
|
||||||
|
.distinct()
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
password_ready_count = query.filter(HuyaAccount.account_password != "").count()
|
||||||
|
point_count = query.filter(HuyaAccount.points.isnot(None)).count()
|
||||||
|
bound_count = query.filter(or_(
|
||||||
|
HuyaAccount.game_name != "",
|
||||||
|
HuyaAccount.game_channel != "",
|
||||||
|
HuyaAccount.game_phone != "",
|
||||||
|
)).count()
|
||||||
|
return {
|
||||||
|
"total": total,
|
||||||
|
"assigned_count": assigned_count,
|
||||||
|
"unassigned_count": max(0, total - assigned_count),
|
||||||
|
"tag_count": tag_count,
|
||||||
|
"password_ready_count": password_ready_count,
|
||||||
|
"point_count": point_count,
|
||||||
|
"bound_count": bound_count,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/accounts/import-cookies")
|
@router.post("/accounts/import-cookies")
|
||||||
@@ -826,13 +946,59 @@ def list_tags(
|
|||||||
|
|
||||||
@router.get("/cookies")
|
@router.get("/cookies")
|
||||||
def list_cookies(
|
def list_cookies(
|
||||||
|
search: str = Query(""),
|
||||||
|
page: int | None = Query(None, ge=1),
|
||||||
|
page_size: int = Query(20, ge=1, le=200),
|
||||||
|
include_cookie: bool = Query(True),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current: User = Depends(get_current_user),
|
current: User = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
"""查看当前用户可见的虎牙 Cookie。"""
|
"""查看当前用户可见的虎牙 Cookie。"""
|
||||||
include_cookie = _can_view_huya_cookie(current)
|
should_include_cookie = include_cookie and _can_view_huya_cookie(current)
|
||||||
accounts = _visible_huya_accounts_query(db, current).filter(HuyaAccount.cookie != "").order_by(HuyaAccount.updated_at.desc()).all()
|
query = _visible_huya_accounts_query(db, current).filter(HuyaAccount.cookie != "")
|
||||||
return [_cookie_out(account, include_cookie=include_cookie) for account in accounts]
|
search_text = (search or "").strip()
|
||||||
|
if search_text:
|
||||||
|
pattern = f"%{search_text}%"
|
||||||
|
query = query.outerjoin(User, HuyaAccount.assigned_to == User.id).filter(or_(
|
||||||
|
HuyaAccount.uid.ilike(pattern),
|
||||||
|
HuyaAccount.yyuid.ilike(pattern),
|
||||||
|
HuyaAccount.username.ilike(pattern),
|
||||||
|
HuyaAccount.nickname.ilike(pattern),
|
||||||
|
User.username.ilike(pattern),
|
||||||
|
))
|
||||||
|
total = None
|
||||||
|
if page is not None:
|
||||||
|
total = query.order_by(None).count()
|
||||||
|
query = query.order_by(HuyaAccount.updated_at.desc())
|
||||||
|
if page is not None:
|
||||||
|
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||||
|
if not should_include_cookie:
|
||||||
|
query = query.options(defer(HuyaAccount.cookie), defer(HuyaAccount.account_password))
|
||||||
|
accounts = query.all()
|
||||||
|
result = (
|
||||||
|
[_cookie_out(account, include_cookie=True) for account in accounts]
|
||||||
|
if should_include_cookie
|
||||||
|
else [_cookie_out_light(account) for account in accounts]
|
||||||
|
)
|
||||||
|
if page is not None:
|
||||||
|
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/cookies/summary")
|
||||||
|
def cookies_summary(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""虎牙 Cookie 统计,避免前端为了卡片统计拉全量 Cookie。"""
|
||||||
|
query = _visible_huya_accounts_query(db, current).filter(HuyaAccount.cookie != "")
|
||||||
|
total = query.count()
|
||||||
|
assigned_count = query.filter(HuyaAccount.assigned_to.isnot(None)).count()
|
||||||
|
return {
|
||||||
|
"total": total,
|
||||||
|
"assigned_count": assigned_count,
|
||||||
|
"unassigned_count": max(0, total - assigned_count),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/cookies/export")
|
@router.get("/cookies/export")
|
||||||
@@ -875,6 +1041,19 @@ def export_cookies(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/cookies/{account_id}")
|
||||||
|
def get_cookie(
|
||||||
|
account_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""获取单条虎牙 Cookie 详情,供复制操作按需读取完整敏感字段。"""
|
||||||
|
account = _visible_huya_accounts_query(db, current).filter(HuyaAccount.id == account_id, HuyaAccount.cookie != "").first()
|
||||||
|
if not account:
|
||||||
|
raise HTTPException(status_code=404, detail="记录不存在")
|
||||||
|
return _cookie_out(account, include_cookie=_can_view_huya_cookie(current))
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/cookies/batch")
|
@router.delete("/cookies/batch")
|
||||||
def delete_cookies_batch(
|
def delete_cookies_batch(
|
||||||
account_ids: str = "",
|
account_ids: str = "",
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ import api from './client';
|
|||||||
import type {
|
import type {
|
||||||
AccountItem,
|
AccountItem,
|
||||||
AssignmentsSummary,
|
AssignmentsSummary,
|
||||||
|
BasicSummary,
|
||||||
MessageCountResponse,
|
MessageCountResponse,
|
||||||
MessageDeletedResponse,
|
MessageDeletedResponse,
|
||||||
MessageResponse,
|
MessageResponse,
|
||||||
|
PageParams,
|
||||||
|
PaginatedResponse,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export const accountApi = {
|
export const accountApi = {
|
||||||
list: (params?: { assigned_only?: boolean; tag?: string; has_cookie?: boolean }) =>
|
list: (params?: { assigned_only?: boolean; tag?: string; has_cookie?: boolean; include_sensitive?: boolean }) =>
|
||||||
api.get<AccountItem[], AccountItem[]>('/accounts', { params }),
|
api.get<AccountItem[], AccountItem[]>('/accounts', { params }),
|
||||||
|
listPaged: (params: PageParams & { assigned_only?: boolean; tag?: string; has_cookie?: boolean; include_sensitive?: boolean }) =>
|
||||||
|
api.get<PaginatedResponse<AccountItem>, PaginatedResponse<AccountItem>>('/accounts', { params }),
|
||||||
|
summary: () => api.get<BasicSummary, BasicSummary>('/accounts/summary'),
|
||||||
import: (text: string) => api.post<MessageCountResponse, MessageCountResponse>('/accounts/import', { text }),
|
import: (text: string) => api.post<MessageCountResponse, MessageCountResponse>('/accounts/import', { text }),
|
||||||
assign: (id: number, assigned_to: number | null) =>
|
assign: (id: number, assigned_to: number | null) =>
|
||||||
api.put<MessageResponse, MessageResponse>(`/accounts/${id}/assign`, { assigned_to }),
|
api.put<MessageResponse, MessageResponse>(`/accounts/${id}/assign`, { assigned_to }),
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import api from './client';
|
import api from './client';
|
||||||
import type { CookieItem, MessageDeletedResponse, MessageResponse } from './types';
|
import type { BasicSummary, CookieItem, PageParams, PaginatedResponse, MessageDeletedResponse, MessageResponse } from './types';
|
||||||
|
|
||||||
export const cookieApi = {
|
export const cookieApi = {
|
||||||
list: () => api.get<CookieItem[], CookieItem[]>('/cookies'),
|
list: () => api.get<CookieItem[], CookieItem[]>('/cookies'),
|
||||||
|
listPaged: (params: PageParams & { include_cookie?: boolean }) =>
|
||||||
|
api.get<PaginatedResponse<CookieItem>, PaginatedResponse<CookieItem>>('/cookies', { params }),
|
||||||
|
summary: () => api.get<BasicSummary, BasicSummary>('/cookies/summary'),
|
||||||
|
get: (id: number) => api.get<CookieItem, CookieItem>(`/cookies/${id}`),
|
||||||
exportCsv: (format?: string) => api.get<Blob, Blob>('/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
exportCsv: (format?: string) => api.get<Blob, Blob>('/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
||||||
delete: (id: number) => api.delete<MessageResponse, MessageResponse>(`/cookies/${id}`),
|
delete: (id: number) => api.delete<MessageResponse, MessageResponse>(`/cookies/${id}`),
|
||||||
deleteBatch: (ids: number[]) => api.delete<MessageDeletedResponse, MessageDeletedResponse>('/cookies/batch', { params: { task_ids: ids.join(',') } }),
|
deleteBatch: (ids: number[]) => api.delete<MessageDeletedResponse, MessageDeletedResponse>('/cookies/batch', { params: { task_ids: ids.join(',') } }),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import api from './client';
|
import api from './client';
|
||||||
import type {
|
import type {
|
||||||
|
BasicSummary,
|
||||||
HuyaAccountItem,
|
HuyaAccountItem,
|
||||||
|
HuyaAccountSummary,
|
||||||
HuyaAutoRegisterBatch,
|
HuyaAutoRegisterBatch,
|
||||||
HuyaAutoRegisterRequest,
|
HuyaAutoRegisterRequest,
|
||||||
HuyaAutoRegisterRetryRequest,
|
HuyaAutoRegisterRetryRequest,
|
||||||
@@ -25,12 +27,17 @@ import type {
|
|||||||
MessageCountResponse,
|
MessageCountResponse,
|
||||||
MessageDeletedResponse,
|
MessageDeletedResponse,
|
||||||
MessageResponse,
|
MessageResponse,
|
||||||
|
PageParams,
|
||||||
|
PaginatedResponse,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export const huyaApi = {
|
export const huyaApi = {
|
||||||
taskTypes: () => api.get<Record<string, string>, Record<string, string>>('/huya/task-types'),
|
taskTypes: () => api.get<Record<string, string>, Record<string, string>>('/huya/task-types'),
|
||||||
listAccounts: (params?: { assigned_only?: boolean; tag?: string; has_cookie?: boolean }) =>
|
listAccounts: (params?: { assigned_only?: boolean; tag?: string; has_cookie?: boolean; include_cookie?: boolean }) =>
|
||||||
api.get<HuyaAccountItem[], HuyaAccountItem[]>('/huya/accounts', { params }),
|
api.get<HuyaAccountItem[], HuyaAccountItem[]>('/huya/accounts', { params }),
|
||||||
|
listAccountsPaged: (params: PageParams & { assigned_only?: boolean; tag?: string; has_cookie?: boolean; include_cookie?: boolean }) =>
|
||||||
|
api.get<PaginatedResponse<HuyaAccountItem>, PaginatedResponse<HuyaAccountItem>>('/huya/accounts', { params }),
|
||||||
|
accountsSummary: () => api.get<HuyaAccountSummary, HuyaAccountSummary>('/huya/accounts/summary'),
|
||||||
importCookies: (text: string, tag: string = '') =>
|
importCookies: (text: string, tag: string = '') =>
|
||||||
api.post<HuyaCookieImportResult, HuyaCookieImportResult>('/huya/accounts/import-cookies', { text, tag }),
|
api.post<HuyaCookieImportResult, HuyaCookieImportResult>('/huya/accounts/import-cookies', { text, tag }),
|
||||||
importPasswordAccounts: (text: string, tag: string = '') =>
|
importPasswordAccounts: (text: string, tag: string = '') =>
|
||||||
@@ -76,6 +83,10 @@ export const huyaApi = {
|
|||||||
deleteAccounts: (accountIds: number[]) =>
|
deleteAccounts: (accountIds: number[]) =>
|
||||||
api.delete<MessageDeletedResponse, MessageDeletedResponse>('/huya/accounts/batch', { params: { account_ids: accountIds.join(',') } }),
|
api.delete<MessageDeletedResponse, MessageDeletedResponse>('/huya/accounts/batch', { params: { account_ids: accountIds.join(',') } }),
|
||||||
listCookies: () => api.get<HuyaCookieItem[], HuyaCookieItem[]>('/huya/cookies'),
|
listCookies: () => api.get<HuyaCookieItem[], HuyaCookieItem[]>('/huya/cookies'),
|
||||||
|
listCookiesPaged: (params: PageParams & { include_cookie?: boolean }) =>
|
||||||
|
api.get<PaginatedResponse<HuyaCookieItem>, PaginatedResponse<HuyaCookieItem>>('/huya/cookies', { params }),
|
||||||
|
cookiesSummary: () => api.get<BasicSummary, BasicSummary>('/huya/cookies/summary'),
|
||||||
|
getCookie: (id: number) => api.get<HuyaCookieItem, HuyaCookieItem>(`/huya/cookies/${id}`),
|
||||||
exportCookies: (format?: string) => api.get<Blob, Blob>('/huya/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
exportCookies: (format?: string) => api.get<Blob, Blob>('/huya/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
||||||
deleteCookie: (id: number) => api.delete<MessageResponse, MessageResponse>(`/huya/cookies/${id}`),
|
deleteCookie: (id: number) => api.delete<MessageResponse, MessageResponse>(`/huya/cookies/${id}`),
|
||||||
deleteCookies: (accountIds: number[]) =>
|
deleteCookies: (accountIds: number[]) =>
|
||||||
|
|||||||
@@ -17,6 +17,26 @@ export interface AppInfo {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PageParams {
|
||||||
|
page: number;
|
||||||
|
page_size: number;
|
||||||
|
search?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
items: T[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
page_size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BasicSummary {
|
||||||
|
total: number;
|
||||||
|
assigned_count: number;
|
||||||
|
unassigned_count: number;
|
||||||
|
tag_count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Auth ====================
|
// ==================== Auth ====================
|
||||||
|
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
@@ -172,6 +192,12 @@ export interface HuyaAccountItem {
|
|||||||
updated_at: string | null;
|
updated_at: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HuyaAccountSummary extends BasicSummary {
|
||||||
|
password_ready_count: number;
|
||||||
|
point_count: number;
|
||||||
|
bound_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface HuyaCookieImportResult extends MessageCountResponse {
|
export interface HuyaCookieImportResult extends MessageCountResponse {
|
||||||
skipped: number;
|
skipped: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ interface ConnectOptions {
|
|||||||
onResult?: () => void;
|
onResult?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_LOGS = 1000;
|
||||||
|
|
||||||
function toWebSocketUrl(pathOrUrl: string): string {
|
function toWebSocketUrl(pathOrUrl: string): string {
|
||||||
if (pathOrUrl.startsWith('ws://') || pathOrUrl.startsWith('wss://')) {
|
if (pathOrUrl.startsWith('ws://') || pathOrUrl.startsWith('wss://')) {
|
||||||
return pathOrUrl;
|
return pathOrUrl;
|
||||||
@@ -63,9 +65,9 @@ export function useWebSocketLogs() {
|
|||||||
callbacksRef.current.onResult?.();
|
callbacksRef.current.onResult?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLogs((prev) => [...prev, msg]);
|
setLogs((prev) => [...prev, msg].slice(-MAX_LOGS));
|
||||||
} catch {
|
} catch {
|
||||||
setLogs((prev) => [...prev, { level: 'info', message: String(event.data) }]);
|
setLogs((prev) => [...prev, { level: 'info', message: String(event.data) }].slice(-MAX_LOGS));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
import { message } from '../utils/antdMessage';
|
import { message } from '../utils/antdMessage';
|
||||||
import type { TableProps } from 'antd';
|
import type { TableProps } from 'antd';
|
||||||
import { ImportOutlined, DeleteOutlined, TagOutlined, FilterOutlined } from '@ant-design/icons';
|
import { ImportOutlined, DeleteOutlined, TagOutlined, FilterOutlined } from '@ant-design/icons';
|
||||||
import { accountApi, userApi, type AccountItem, type UserInfo } from '../api/modules';
|
import { accountApi, userApi, type AccountItem, type BasicSummary, type UserInfo } from '../api/modules';
|
||||||
import { usePermissions } from '../hooks/usePermissions';
|
import { usePermissions } from '../hooks/usePermissions';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
import { getErrorMessage } from '../utils/error';
|
import { getErrorMessage } from '../utils/error';
|
||||||
@@ -27,9 +27,12 @@ export default function AccountsPage() {
|
|||||||
const [importText, setImportText] = useState('');
|
const [importText, setImportText] = useState('');
|
||||||
const [importing, setImporting] = useState(false);
|
const [importing, setImporting] = useState(false);
|
||||||
const [tagFilter, setTagFilter] = useState<string>('');
|
const [tagFilter, setTagFilter] = useState<string>('');
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [batchTagInput, setBatchTagInput] = useState('');
|
const [batchTagInput, setBatchTagInput] = useState('');
|
||||||
const [batchTagVisible, setBatchTagVisible] = useState(false);
|
const [batchTagVisible, setBatchTagVisible] = useState(false);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [summary, setSummary] = useState<BasicSummary>({ total: 0, assigned_count: 0, unassigned_count: 0, tag_count: 0 });
|
||||||
const [pageSize, setPageSize] = useState(() => {
|
const [pageSize, setPageSize] = useState(() => {
|
||||||
const v = localStorage.getItem('account_page_size');
|
const v = localStorage.getItem('account_page_size');
|
||||||
return v ? Number(v) || 20 : 20;
|
return v ? Number(v) || 20 : 20;
|
||||||
@@ -47,14 +50,30 @@ export default function AccountsPage() {
|
|||||||
try {
|
try {
|
||||||
const params: { tag?: string } = {};
|
const params: { tag?: string } = {};
|
||||||
if (tagFilter) params.tag = tagFilter;
|
if (tagFilter) params.tag = tagFilter;
|
||||||
const data = await accountApi.list(params);
|
const data = await accountApi.listPaged({
|
||||||
setAccounts(data);
|
...params,
|
||||||
|
page: currentPage,
|
||||||
|
page_size: pageSize,
|
||||||
|
search: searchText.trim() || undefined,
|
||||||
|
include_sensitive: canViewFull,
|
||||||
|
});
|
||||||
|
setAccounts(data.items);
|
||||||
|
setTotal(data.total);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [tagFilter]);
|
}, [canViewFull, currentPage, pageSize, searchText, tagFilter]);
|
||||||
|
|
||||||
|
const loadSummary = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const data = await accountApi.summary();
|
||||||
|
setSummary(data);
|
||||||
|
} catch {
|
||||||
|
// 统计加载失败时不影响主列表操作。
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadUsers = useCallback(async () => {
|
const loadUsers = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -76,9 +95,13 @@ export default function AccountsPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
|
}, [loadAccounts]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (canAssign) loadUsers();
|
if (canAssign) loadUsers();
|
||||||
loadTags();
|
loadTags();
|
||||||
}, [loadAccounts, canAssign, loadUsers, loadTags]);
|
loadSummary();
|
||||||
|
}, [canAssign, loadUsers, loadTags, loadSummary]);
|
||||||
|
|
||||||
const tagColorMap = useMemo(() => {
|
const tagColorMap = useMemo(() => {
|
||||||
const map: Record<string, string> = {};
|
const map: Record<string, string> = {};
|
||||||
@@ -101,6 +124,7 @@ export default function AccountsPage() {
|
|||||||
setImportText('');
|
setImportText('');
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -142,6 +166,7 @@ export default function AccountsPage() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
@@ -154,6 +179,7 @@ export default function AccountsPage() {
|
|||||||
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
@@ -170,6 +196,7 @@ export default function AccountsPage() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
@@ -279,10 +306,24 @@ export default function AccountsPage() {
|
|||||||
placeholder="按标签筛选"
|
placeholder="按标签筛选"
|
||||||
style={{ width: 150 }}
|
style={{ width: 150 }}
|
||||||
value={tagFilter || undefined}
|
value={tagFilter || undefined}
|
||||||
onChange={(val) => setTagFilter(val || '')}
|
onChange={(val) => {
|
||||||
|
setTagFilter(val || '');
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
options={tags.map((t) => ({ value: t, label: t }))}
|
options={tags.map((t) => ({ value: t, label: t }))}
|
||||||
prefix={<FilterOutlined />}
|
prefix={<FilterOutlined />}
|
||||||
/>
|
/>
|
||||||
|
<Input.Search
|
||||||
|
allowClear
|
||||||
|
size="small"
|
||||||
|
placeholder="搜索账号/标签/备注"
|
||||||
|
style={{ width: 220 }}
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchText(e.target.value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{canImport && (
|
{canImport && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@@ -323,16 +364,16 @@ export default function AccountsPage() {
|
|||||||
|
|
||||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small"><Statistic title="账号总数" value={accounts.length} /></Card>
|
<Card size="small"><Statistic title="账号总数" value={summary.total} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small"><Statistic title="标签数" value={tags.length} /></Card>
|
<Card size="small"><Statistic title="标签数" value={summary.tag_count || tags.length} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic
|
<Statistic
|
||||||
title="已分配"
|
title="已分配"
|
||||||
value={accounts.filter((a) => a.assigned_to).length}
|
value={summary.assigned_count}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -340,7 +381,7 @@ export default function AccountsPage() {
|
|||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic
|
<Statistic
|
||||||
title="未分配"
|
title="未分配"
|
||||||
value={accounts.filter((a) => !a.assigned_to).length}
|
value={summary.unassigned_count}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -350,6 +391,7 @@ export default function AccountsPage() {
|
|||||||
rowSelection={canImport ? {
|
rowSelection={canImport ? {
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: (keys) => setSelectedRowKeys(keys),
|
onChange: (keys) => setSelectedRowKeys(keys),
|
||||||
|
preserveSelectedRowKeys: true,
|
||||||
} : undefined}
|
} : undefined}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={accounts}
|
dataSource={accounts}
|
||||||
@@ -359,6 +401,7 @@ export default function AccountsPage() {
|
|||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
total,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: (t) => `共 ${t} 条`,
|
showTotal: (t) => `共 ${t} 条`,
|
||||||
onChange: (page, size) => {
|
onChange: (page, size) => {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function AssignmentsPage() {
|
|||||||
// 加载账号(仅已成功登录过的)
|
// 加载账号(仅已成功登录过的)
|
||||||
const loadAccounts = async () => {
|
const loadAccounts = async () => {
|
||||||
try {
|
try {
|
||||||
const all = await accountApi.list({ has_cookie: true });
|
const all = await accountApi.list({ has_cookie: true, include_sensitive: false });
|
||||||
setAccounts(all);
|
setAccounts(all);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useState, useCallback } from 'react';
|
|||||||
import { Table, Button, Card, Row, Col, Statistic, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown } from 'antd';
|
import { Table, Button, Card, Row, Col, Statistic, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown } from 'antd';
|
||||||
import { message } from '../utils/antdMessage';
|
import { message } from '../utils/antdMessage';
|
||||||
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined } from '@ant-design/icons';
|
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { cookieApi, type CookieItem } from '../api/modules';
|
import { cookieApi, type BasicSummary, type CookieItem } from '../api/modules';
|
||||||
import { usePermissions } from '../hooks/usePermissions';
|
import { usePermissions } from '../hooks/usePermissions';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
import { getErrorMessage } from '../utils/error';
|
import { getErrorMessage } from '../utils/error';
|
||||||
@@ -43,6 +43,8 @@ export default function CookiePage() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [summary, setSummary] = useState<BasicSummary>({ total: 0, assigned_count: 0, unassigned_count: 0 });
|
||||||
const [pageSize, setPageSize] = useState(() => {
|
const [pageSize, setPageSize] = useState(() => {
|
||||||
const v = localStorage.getItem('cookie_page_size');
|
const v = localStorage.getItem('cookie_page_size');
|
||||||
return v ? Number(v) || 20 : 20;
|
return v ? Number(v) || 20 : 20;
|
||||||
@@ -56,19 +58,38 @@ export default function CookiePage() {
|
|||||||
const loadCookies = useCallback(async () => {
|
const loadCookies = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await cookieApi.list();
|
const data = await cookieApi.listPaged({
|
||||||
setCookies(data);
|
page: currentPage,
|
||||||
|
page_size: pageSize,
|
||||||
|
search: searchText.trim() || undefined,
|
||||||
|
include_cookie: false,
|
||||||
|
});
|
||||||
|
setCookies(data.items);
|
||||||
|
setTotal(data.total);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}, [currentPage, pageSize, searchText]);
|
||||||
|
|
||||||
|
const loadSummary = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const data = await cookieApi.summary();
|
||||||
|
setSummary(data);
|
||||||
|
} catch {
|
||||||
|
// 统计加载失败不影响列表操作。
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadCookies();
|
loadCookies();
|
||||||
}, [loadCookies]);
|
}, [loadCookies]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadSummary();
|
||||||
|
}, [loadSummary]);
|
||||||
|
|
||||||
const handleExport = async (format: string = 'csv') => {
|
const handleExport = async (format: string = 'csv') => {
|
||||||
try {
|
try {
|
||||||
const blob = await cookieApi.exportCsv(format);
|
const blob = await cookieApi.exportCsv(format);
|
||||||
@@ -84,33 +105,36 @@ export default function CookiePage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyCookie = (record: CookieItem) => {
|
const handleCopyCookie = async (record: CookieItem) => {
|
||||||
if (!record.cookie) {
|
try {
|
||||||
|
const detail = await cookieApi.get(record.id);
|
||||||
|
if (!detail.cookie) {
|
||||||
message.warning('Cookie 为空');
|
message.warning('Cookie 为空');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const text = `${record.account_username}----${record.account_password || ''}----${record.cookie}`;
|
const text = `${detail.account_username}----${detail.account_password || ''}----${detail.cookie}`;
|
||||||
copyToClipboard(text).then(() => {
|
await copyToClipboard(text);
|
||||||
message.success(`已复制 ${record.account_username} 的 Cookie`);
|
message.success(`已复制 ${detail.account_username} 的 Cookie`);
|
||||||
}).catch(() => {
|
} catch (e: unknown) {
|
||||||
message.error('复制失败');
|
message.error(getErrorMessage(e) || '复制失败');
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopySelected = () => {
|
const handleCopySelected = async () => {
|
||||||
if (selectedRowKeys.length === 0) {
|
if (selectedRowKeys.length === 0) {
|
||||||
message.warning('请先选择 Cookie');
|
message.warning('请先选择 Cookie');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selected = cookies.filter((c) => selectedRowKeys.includes(c.id));
|
try {
|
||||||
|
const selected = await Promise.all(selectedRowKeys.map((key) => cookieApi.get(Number(key))));
|
||||||
const text = selected
|
const text = selected
|
||||||
.map((c) => `${c.account_username}----${c.account_password || ''}----${c.cookie || ''}`)
|
.map((c) => `${c.account_username}----${c.account_password || ''}----${c.cookie || ''}`)
|
||||||
.join('\r\n');
|
.join('\r\n');
|
||||||
copyToClipboard(text).then(() => {
|
await copyToClipboard(text);
|
||||||
message.success(`已复制 ${selected.length} 条 Cookie`);
|
message.success(`已复制 ${selected.length} 条 Cookie`);
|
||||||
}).catch(() => {
|
} catch (e: unknown) {
|
||||||
message.error('复制失败');
|
message.error(getErrorMessage(e) || '复制失败');
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
@@ -119,6 +143,7 @@ export default function CookiePage() {
|
|||||||
message.success('已删除');
|
message.success('已删除');
|
||||||
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
||||||
loadCookies();
|
loadCookies();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
@@ -135,21 +160,12 @@ export default function CookiePage() {
|
|||||||
message.success(res.message);
|
message.success(res.message);
|
||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadCookies();
|
loadCookies();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 筛选
|
|
||||||
const filteredCookies = cookies.filter((c) => {
|
|
||||||
if (!searchText) return true;
|
|
||||||
const s = searchText.toLowerCase();
|
|
||||||
return (
|
|
||||||
c.account_username?.toLowerCase().includes(s) ||
|
|
||||||
c.assigned_username?.toLowerCase().includes(s)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: 'ID', dataIndex: 'id', width: 60, align: 'center' as const },
|
{ title: 'ID', dataIndex: 'id', width: 60, align: 'center' as const },
|
||||||
{
|
{
|
||||||
@@ -206,9 +222,6 @@ export default function CookiePage() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const assignedCount = cookies.filter((c) => c.assigned_to).length;
|
|
||||||
const unassignedCount = cookies.length - assignedCount;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
@@ -254,13 +267,13 @@ export default function CookiePage() {
|
|||||||
</div>
|
</div>
|
||||||
<Row gutter={12} style={{ marginBottom: 16 }}>
|
<Row gutter={12} style={{ marginBottom: 16 }}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small"><Statistic title="Cookie 总数" value={cookies.length} /></Card>
|
<Card size="small"><Statistic title="Cookie 总数" value={summary.total} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic
|
<Statistic
|
||||||
title="已分配"
|
title="已分配"
|
||||||
value={assignedCount}
|
value={summary.assigned_count}
|
||||||
styles={{ content: { color: token.colorSuccess } }}
|
styles={{ content: { color: token.colorSuccess } }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -269,7 +282,7 @@ export default function CookiePage() {
|
|||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic
|
<Statistic
|
||||||
title="未分配"
|
title="未分配"
|
||||||
value={unassignedCount}
|
value={summary.unassigned_count}
|
||||||
styles={{ content: { color: token.colorError } }}
|
styles={{ content: { color: token.colorError } }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -280,7 +293,10 @@ export default function CookiePage() {
|
|||||||
placeholder="搜索账号或分配客服"
|
placeholder="搜索账号或分配客服"
|
||||||
allowClear
|
allowClear
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setSearchText(e.target.value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
style={{ width: 260 }}
|
style={{ width: 260 }}
|
||||||
size="small"
|
size="small"
|
||||||
prefix={<SearchOutlined />}
|
prefix={<SearchOutlined />}
|
||||||
@@ -290,15 +306,17 @@ export default function CookiePage() {
|
|||||||
rowSelection={{
|
rowSelection={{
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: (keys) => setSelectedRowKeys(keys),
|
onChange: (keys) => setSelectedRowKeys(keys),
|
||||||
|
preserveSelectedRowKeys: true,
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={filteredCookies}
|
dataSource={cookies}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="small"
|
size="small"
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
total,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: (t) => `共 ${t} 条`,
|
showTotal: (t) => `共 ${t} 条`,
|
||||||
onChange: (page, size) => {
|
onChange: (page, size) => {
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ export default function DashboardPage() {
|
|||||||
huyaGoodsResult,
|
huyaGoodsResult,
|
||||||
huyaRechargeGoodsResult,
|
huyaRechargeGoodsResult,
|
||||||
] = await Promise.allSettled([
|
] = await Promise.allSettled([
|
||||||
canViewDouyuAccounts ? accountApi.list() : Promise.resolve([]),
|
canViewDouyuAccounts ? accountApi.summary() : Promise.resolve(null),
|
||||||
canViewDouyuTasks ? loginApi.listTasks() : Promise.resolve([]),
|
canViewDouyuTasks ? loginApi.listTasks() : Promise.resolve([]),
|
||||||
canViewCookies ? cookieApi.list() : Promise.resolve([]),
|
canViewCookies ? cookieApi.summary() : Promise.resolve(null),
|
||||||
canViewHuyaAccounts ? huyaApi.listAccounts() : Promise.resolve([]),
|
canViewHuyaAccounts ? huyaApi.accountsSummary() : Promise.resolve(null),
|
||||||
canViewHuyaTasks ? huyaApi.listTasks() : Promise.resolve([]),
|
canViewHuyaTasks ? huyaApi.listTasks() : Promise.resolve([]),
|
||||||
canViewHuyaTasks ? huyaApi.listGoods() : Promise.resolve([]),
|
canViewHuyaTasks ? huyaApi.listGoods() : Promise.resolve([]),
|
||||||
canViewHuyaTasks ? huyaApi.listRechargeGoods() : Promise.resolve([]),
|
canViewHuyaTasks ? huyaApi.listRechargeGoods() : Promise.resolve([]),
|
||||||
@@ -108,24 +108,24 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
if (ignore) return;
|
if (ignore) return;
|
||||||
|
|
||||||
const douyuAccounts = douyuAccountsResult.status === 'fulfilled' ? douyuAccountsResult.value : [];
|
const douyuAccounts = douyuAccountsResult.status === 'fulfilled' ? douyuAccountsResult.value : null;
|
||||||
const douyuTasks = douyuTasksResult.status === 'fulfilled' ? douyuTasksResult.value : [];
|
const douyuTasks = douyuTasksResult.status === 'fulfilled' ? douyuTasksResult.value : [];
|
||||||
const cookies = cookiesResult.status === 'fulfilled' ? cookiesResult.value : [];
|
const cookies = cookiesResult.status === 'fulfilled' ? cookiesResult.value : null;
|
||||||
const huyaAccounts = huyaAccountsResult.status === 'fulfilled' ? huyaAccountsResult.value : [];
|
const huyaAccounts = huyaAccountsResult.status === 'fulfilled' ? huyaAccountsResult.value : null;
|
||||||
const huyaTasks = huyaTasksResult.status === 'fulfilled' ? huyaTasksResult.value : [];
|
const huyaTasks = huyaTasksResult.status === 'fulfilled' ? huyaTasksResult.value : [];
|
||||||
const huyaGoods = huyaGoodsResult.status === 'fulfilled' ? huyaGoodsResult.value : [];
|
const huyaGoods = huyaGoodsResult.status === 'fulfilled' ? huyaGoodsResult.value : [];
|
||||||
const huyaRechargeGoods = huyaRechargeGoodsResult.status === 'fulfilled' ? huyaRechargeGoodsResult.value : [];
|
const huyaRechargeGoods = huyaRechargeGoodsResult.status === 'fulfilled' ? huyaRechargeGoodsResult.value : [];
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
douyu: {
|
douyu: {
|
||||||
accounts: douyuAccounts.length,
|
accounts: douyuAccounts?.total || 0,
|
||||||
tasks: douyuTasks.length,
|
tasks: douyuTasks.length,
|
||||||
success: douyuTasks.filter((task: LoginTaskItem) => task.status === 'success').length,
|
success: douyuTasks.filter((task: LoginTaskItem) => task.status === 'success').length,
|
||||||
failed: countFailed(douyuTasks),
|
failed: countFailed(douyuTasks),
|
||||||
cookies: cookies.length,
|
cookies: cookies?.total || 0,
|
||||||
},
|
},
|
||||||
huya: {
|
huya: {
|
||||||
accounts: huyaAccounts.length,
|
accounts: huyaAccounts?.total || 0,
|
||||||
tasks: huyaTasks.length,
|
tasks: huyaTasks.length,
|
||||||
success: huyaTasks.filter((task: HuyaTaskItem) => task.status === 'success').length,
|
success: huyaTasks.filter((task: HuyaTaskItem) => task.status === 'success').length,
|
||||||
failed: countFailed(huyaTasks),
|
failed: countFailed(huyaTasks),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { DeleteOutlined, FilterOutlined, ImportOutlined, LoginOutlined, MobileOu
|
|||||||
import {
|
import {
|
||||||
huyaApi,
|
huyaApi,
|
||||||
type HuyaAccountItem,
|
type HuyaAccountItem,
|
||||||
|
type HuyaAccountSummary,
|
||||||
type HuyaPasswordLoginBatchItem,
|
type HuyaPasswordLoginBatchItem,
|
||||||
type SupportUserItem,
|
type SupportUserItem,
|
||||||
} from '../api/modules';
|
} from '../api/modules';
|
||||||
@@ -68,6 +69,16 @@ export default function HuyaAccountsPage() {
|
|||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [batchTagInput, setBatchTagInput] = useState('');
|
const [batchTagInput, setBatchTagInput] = useState('');
|
||||||
const [batchTagVisible, setBatchTagVisible] = useState(false);
|
const [batchTagVisible, setBatchTagVisible] = useState(false);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [summary, setSummary] = useState<HuyaAccountSummary>({
|
||||||
|
total: 0,
|
||||||
|
assigned_count: 0,
|
||||||
|
unassigned_count: 0,
|
||||||
|
tag_count: 0,
|
||||||
|
password_ready_count: 0,
|
||||||
|
point_count: 0,
|
||||||
|
bound_count: 0,
|
||||||
|
});
|
||||||
const [pageSize, setPageSize] = useState(() => {
|
const [pageSize, setPageSize] = useState(() => {
|
||||||
const v = localStorage.getItem('huya_account_page_size');
|
const v = localStorage.getItem('huya_account_page_size');
|
||||||
return v ? Number(v) || 20 : 20;
|
return v ? Number(v) || 20 : 20;
|
||||||
@@ -86,14 +97,30 @@ export default function HuyaAccountsPage() {
|
|||||||
try {
|
try {
|
||||||
const params: { tag?: string } = {};
|
const params: { tag?: string } = {};
|
||||||
if (tagFilter) params.tag = tagFilter;
|
if (tagFilter) params.tag = tagFilter;
|
||||||
const data = await huyaApi.listAccounts(params);
|
const data = await huyaApi.listAccountsPaged({
|
||||||
setAccounts(data);
|
...params,
|
||||||
|
page: currentPage,
|
||||||
|
page_size: pageSize,
|
||||||
|
search: searchText.trim() || undefined,
|
||||||
|
include_cookie: false,
|
||||||
|
});
|
||||||
|
setAccounts(data.items);
|
||||||
|
setTotal(data.total);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [tagFilter]);
|
}, [currentPage, pageSize, searchText, tagFilter]);
|
||||||
|
|
||||||
|
const loadSummary = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const data = await huyaApi.accountsSummary();
|
||||||
|
setSummary(data);
|
||||||
|
} catch {
|
||||||
|
// 统计加载失败时不影响主列表操作。
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadUsers = useCallback(async () => {
|
const loadUsers = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -115,9 +142,13 @@ export default function HuyaAccountsPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
|
}, [loadAccounts]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (canAssign) loadUsers();
|
if (canAssign) loadUsers();
|
||||||
loadTags();
|
loadTags();
|
||||||
}, [loadAccounts, canAssign, loadUsers, loadTags]);
|
loadSummary();
|
||||||
|
}, [canAssign, loadUsers, loadTags, loadSummary]);
|
||||||
|
|
||||||
const tagColorMap = useMemo(() => {
|
const tagColorMap = useMemo(() => {
|
||||||
const map: Record<string, string> = {};
|
const map: Record<string, string> = {};
|
||||||
@@ -127,20 +158,6 @@ export default function HuyaAccountsPage() {
|
|||||||
return map;
|
return map;
|
||||||
}, [tags]);
|
}, [tags]);
|
||||||
|
|
||||||
const filteredAccounts = useMemo(() => {
|
|
||||||
const s = searchText.trim().toLowerCase();
|
|
||||||
if (!s) return accounts;
|
|
||||||
return accounts.filter((item) => (
|
|
||||||
item.uid.toLowerCase().includes(s) ||
|
|
||||||
item.yyuid.toLowerCase().includes(s) ||
|
|
||||||
item.username.toLowerCase().includes(s) ||
|
|
||||||
item.nickname.toLowerCase().includes(s) ||
|
|
||||||
item.tag.toLowerCase().includes(s) ||
|
|
||||||
item.game_name.toLowerCase().includes(s) ||
|
|
||||||
item.game_phone.toLowerCase().includes(s)
|
|
||||||
));
|
|
||||||
}, [accounts, searchText]);
|
|
||||||
|
|
||||||
const handleImport = async () => {
|
const handleImport = async () => {
|
||||||
if (!importText.trim()) {
|
if (!importText.trim()) {
|
||||||
message.warning('请先粘贴虎牙 CK');
|
message.warning('请先粘贴虎牙 CK');
|
||||||
@@ -155,6 +172,7 @@ export default function HuyaAccountsPage() {
|
|||||||
setImportTag('');
|
setImportTag('');
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -223,6 +241,7 @@ export default function HuyaAccountsPage() {
|
|||||||
setSmsState('');
|
setSmsState('');
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -245,6 +264,7 @@ export default function HuyaAccountsPage() {
|
|||||||
setPasswordImportTag([]);
|
setPasswordImportTag([]);
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -271,6 +291,7 @@ export default function HuyaAccountsPage() {
|
|||||||
}
|
}
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -285,6 +306,7 @@ export default function HuyaAccountsPage() {
|
|||||||
setSelectedRowKeys((prev) => prev.filter((key) => key !== id));
|
setSelectedRowKeys((prev) => prev.filter((key) => key !== id));
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
@@ -301,6 +323,7 @@ export default function HuyaAccountsPage() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
@@ -345,11 +368,6 @@ export default function HuyaAccountsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const boundCount = accounts.filter((item) => item.game_name || item.game_channel || item.game_phone).length;
|
|
||||||
const pointCount = accounts.filter((item) => item.points !== null && item.points !== undefined).length;
|
|
||||||
const assignedCount = accounts.filter((item) => item.assigned_to).length;
|
|
||||||
const passwordReadyCount = accounts.filter((item) => item.has_password).length;
|
|
||||||
|
|
||||||
const passwordLoginResultColumns: TableProps<HuyaPasswordLoginBatchItem>['columns'] = [
|
const passwordLoginResultColumns: TableProps<HuyaPasswordLoginBatchItem>['columns'] = [
|
||||||
{ title: '账号ID', dataIndex: 'line', width: 80, align: 'center' },
|
{ title: '账号ID', dataIndex: 'line', width: 80, align: 'center' },
|
||||||
{ title: '账号', dataIndex: 'username', width: 160, ellipsis: true },
|
{ title: '账号', dataIndex: 'username', width: 160, ellipsis: true },
|
||||||
@@ -526,7 +544,10 @@ export default function HuyaAccountsPage() {
|
|||||||
placeholder="按标签筛选"
|
placeholder="按标签筛选"
|
||||||
style={{ width: 150 }}
|
style={{ width: 150 }}
|
||||||
value={tagFilter || undefined}
|
value={tagFilter || undefined}
|
||||||
onChange={(value) => setTagFilter(value || '')}
|
onChange={(value) => {
|
||||||
|
setTagFilter(value || '');
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
options={tags.map((tag) => ({ value: tag, label: tag }))}
|
options={tags.map((tag) => ({ value: tag, label: tag }))}
|
||||||
prefix={<FilterOutlined />}
|
prefix={<FilterOutlined />}
|
||||||
/>
|
/>
|
||||||
@@ -586,25 +607,25 @@ export default function HuyaAccountsPage() {
|
|||||||
|
|
||||||
<Row gutter={12} style={{ marginBottom: 16 }}>
|
<Row gutter={12} style={{ marginBottom: 16 }}>
|
||||||
<Col xs={24} sm={8}>
|
<Col xs={24} sm={8}>
|
||||||
<Card size="small"><Statistic title="账号总数" value={accounts.length} /></Card>
|
<Card size="small"><Statistic title="账号总数" value={summary.total} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={4}>
|
<Col xs={24} sm={4}>
|
||||||
<Card size="small"><Statistic title="标签数" value={tags.length} /></Card>
|
<Card size="small"><Statistic title="标签数" value={summary.tag_count || tags.length} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={4}>
|
<Col xs={24} sm={4}>
|
||||||
<Card size="small"><Statistic title="已导入密码" value={passwordReadyCount} /></Card>
|
<Card size="small"><Statistic title="已导入密码" value={summary.password_ready_count} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={4}>
|
<Col xs={24} sm={4}>
|
||||||
<Card size="small"><Statistic title="已查积分" value={pointCount} /></Card>
|
<Card size="small"><Statistic title="已查积分" value={summary.point_count} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={4}>
|
<Col xs={24} sm={4}>
|
||||||
<Card size="small"><Statistic title="已绑定信息" value={boundCount} /></Card>
|
<Card size="small"><Statistic title="已绑定信息" value={summary.bound_count} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={4}>
|
<Col xs={24} sm={4}>
|
||||||
<Card size="small"><Statistic title="已分配" value={assignedCount} /></Card>
|
<Card size="small"><Statistic title="已分配" value={summary.assigned_count} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={4}>
|
<Col xs={24} sm={4}>
|
||||||
<Card size="small"><Statistic title="未分配" value={accounts.length - assignedCount} /></Card>
|
<Card size="small"><Statistic title="未分配" value={summary.unassigned_count} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
@@ -613,12 +634,23 @@ export default function HuyaAccountsPage() {
|
|||||||
placeholder="搜索 UID、昵称、标签、游戏名、手机号"
|
placeholder="搜索 UID、昵称、标签、游戏名、手机号"
|
||||||
allowClear
|
allowClear
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setSearchText(e.target.value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
style={{ width: 300 }}
|
style={{ width: 300 }}
|
||||||
prefix={<SearchOutlined />}
|
prefix={<SearchOutlined />}
|
||||||
/>
|
/>
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<Tag key={tag} color="blue" onClick={() => setSearchText(tag)} style={{ cursor: 'pointer' }}>
|
<Tag
|
||||||
|
key={tag}
|
||||||
|
color="blue"
|
||||||
|
onClick={() => {
|
||||||
|
setSearchText(tag);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
@@ -628,15 +660,17 @@ export default function HuyaAccountsPage() {
|
|||||||
rowSelection={(canImport || canDelete || canAssign) ? {
|
rowSelection={(canImport || canDelete || canAssign) ? {
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: (keys) => setSelectedRowKeys(keys),
|
onChange: (keys) => setSelectedRowKeys(keys),
|
||||||
|
preserveSelectedRowKeys: true,
|
||||||
} : undefined}
|
} : undefined}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={filteredAccounts}
|
dataSource={accounts}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="small"
|
size="small"
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
total,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
onChange: (page, size) => {
|
onChange: (page, size) => {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function HuyaAssignmentsPage() {
|
|||||||
|
|
||||||
const loadAccounts = async () => {
|
const loadAccounts = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await huyaApi.listAccounts({ has_cookie: true });
|
const data = await huyaApi.listAccounts({ has_cookie: true, include_cookie: false });
|
||||||
setAccounts(data);
|
setAccounts(data);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { message } from '../utils/antdMessage';
|
import { message } from '../utils/antdMessage';
|
||||||
import { CopyOutlined, DeleteOutlined, DownloadOutlined, SearchOutlined } from '@ant-design/icons';
|
import { CopyOutlined, DeleteOutlined, DownloadOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { huyaApi, type HuyaCookieItem } from '../api/modules';
|
import { huyaApi, type BasicSummary, type HuyaCookieItem } from '../api/modules';
|
||||||
import { usePermissions } from '../hooks/usePermissions';
|
import { usePermissions } from '../hooks/usePermissions';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
import { getErrorMessage } from '../utils/error';
|
import { getErrorMessage } from '../utils/error';
|
||||||
@@ -40,6 +40,8 @@ export default function HuyaCookiePage() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [summary, setSummary] = useState<BasicSummary>({ total: 0, assigned_count: 0, unassigned_count: 0 });
|
||||||
const [pageSize, setPageSize] = useState(() => {
|
const [pageSize, setPageSize] = useState(() => {
|
||||||
const value = localStorage.getItem('huya_cookie_page_size');
|
const value = localStorage.getItem('huya_cookie_page_size');
|
||||||
return value ? Number(value) || 20 : 20;
|
return value ? Number(value) || 20 : 20;
|
||||||
@@ -54,19 +56,38 @@ export default function HuyaCookiePage() {
|
|||||||
const loadCookies = useCallback(async () => {
|
const loadCookies = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await huyaApi.listCookies();
|
const data = await huyaApi.listCookiesPaged({
|
||||||
setCookies(data);
|
page: currentPage,
|
||||||
|
page_size: pageSize,
|
||||||
|
search: searchText.trim() || undefined,
|
||||||
|
include_cookie: false,
|
||||||
|
});
|
||||||
|
setCookies(data.items);
|
||||||
|
setTotal(data.total);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}, [currentPage, pageSize, searchText]);
|
||||||
|
|
||||||
|
const loadSummary = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const data = await huyaApi.cookiesSummary();
|
||||||
|
setSummary(data);
|
||||||
|
} catch {
|
||||||
|
// 统计加载失败时不影响主列表操作。
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadCookies();
|
loadCookies();
|
||||||
}, [loadCookies]);
|
}, [loadCookies]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadSummary();
|
||||||
|
}, [loadSummary]);
|
||||||
|
|
||||||
const handleExport = async (format: string = 'csv') => {
|
const handleExport = async (format: string = 'csv') => {
|
||||||
try {
|
try {
|
||||||
const blob = await huyaApi.exportCookies(format);
|
const blob = await huyaApi.exportCookies(format);
|
||||||
@@ -82,31 +103,34 @@ export default function HuyaCookiePage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyCookie = (record: HuyaCookieItem) => {
|
const handleCopyCookie = async (record: HuyaCookieItem) => {
|
||||||
if (!record.cookie) {
|
try {
|
||||||
|
const detail = await huyaApi.getCookie(record.id);
|
||||||
|
if (!detail.cookie) {
|
||||||
message.warning('Cookie 为空');
|
message.warning('Cookie 为空');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const text = `${record.account_username}----${record.cookie}`;
|
const text = `${detail.account_username}----${detail.cookie}`;
|
||||||
copyToClipboard(text).then(() => {
|
await copyToClipboard(text);
|
||||||
message.success(`已复制 ${record.account_username} 的 Cookie`);
|
message.success(`已复制 ${detail.account_username} 的 Cookie`);
|
||||||
}).catch(() => {
|
} catch (e: unknown) {
|
||||||
message.error('复制失败');
|
message.error(getErrorMessage(e) || '复制失败');
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopySelected = () => {
|
const handleCopySelected = async () => {
|
||||||
if (selectedRowKeys.length === 0) {
|
if (selectedRowKeys.length === 0) {
|
||||||
message.warning('请先选择 Cookie');
|
message.warning('请先选择 Cookie');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selected = cookies.filter((item) => selectedRowKeys.includes(item.id));
|
try {
|
||||||
|
const selected = await Promise.all(selectedRowKeys.map((key) => huyaApi.getCookie(Number(key))));
|
||||||
const text = selected.map((item) => `${item.account_username}----${item.cookie || ''}`).join('\r\n');
|
const text = selected.map((item) => `${item.account_username}----${item.cookie || ''}`).join('\r\n');
|
||||||
copyToClipboard(text).then(() => {
|
await copyToClipboard(text);
|
||||||
message.success(`已复制 ${selected.length} 条 Cookie`);
|
message.success(`已复制 ${selected.length} 条 Cookie`);
|
||||||
}).catch(() => {
|
} catch (e: unknown) {
|
||||||
message.error('复制失败');
|
message.error(getErrorMessage(e) || '复制失败');
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
@@ -115,6 +139,7 @@ export default function HuyaCookiePage() {
|
|||||||
message.success('已清除');
|
message.success('已清除');
|
||||||
setSelectedRowKeys((prev) => prev.filter((key) => key !== id));
|
setSelectedRowKeys((prev) => prev.filter((key) => key !== id));
|
||||||
loadCookies();
|
loadCookies();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
@@ -130,22 +155,12 @@ export default function HuyaCookiePage() {
|
|||||||
message.success(result.message);
|
message.success(result.message);
|
||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadCookies();
|
loadCookies();
|
||||||
|
loadSummary();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredCookies = cookies.filter((item) => {
|
|
||||||
if (!searchText) return true;
|
|
||||||
const search = searchText.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.account_username.toLowerCase().includes(search) ||
|
|
||||||
item.uid.toLowerCase().includes(search) ||
|
|
||||||
item.yyuid.toLowerCase().includes(search) ||
|
|
||||||
(item.assigned_username || '').toLowerCase().includes(search)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: 'ID', dataIndex: 'id', width: 70, align: 'center' as const },
|
{ title: 'ID', dataIndex: 'id', width: 70, align: 'center' as const },
|
||||||
{
|
{
|
||||||
@@ -204,9 +219,6 @@ export default function HuyaCookiePage() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const assignedCount = cookies.filter((item) => item.assigned_to).length;
|
|
||||||
const unassignedCount = cookies.length - assignedCount;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
@@ -245,16 +257,16 @@ export default function HuyaCookiePage() {
|
|||||||
|
|
||||||
<Row gutter={12} style={{ marginBottom: 16 }}>
|
<Row gutter={12} style={{ marginBottom: 16 }}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small"><Statistic title="Cookie 总数" value={cookies.length} /></Card>
|
<Card size="small"><Statistic title="Cookie 总数" value={summary.total} /></Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="已分配" value={assignedCount} styles={{ content: { color: token.colorSuccess } }} />
|
<Statistic title="已分配" value={summary.assigned_count} styles={{ content: { color: token.colorSuccess } }} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="未分配" value={unassignedCount} styles={{ content: { color: token.colorError } }} />
|
<Statistic title="未分配" value={summary.unassigned_count} styles={{ content: { color: token.colorError } }} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -264,7 +276,10 @@ export default function HuyaCookiePage() {
|
|||||||
placeholder="搜索账号、UID 或分配客服"
|
placeholder="搜索账号、UID 或分配客服"
|
||||||
allowClear
|
allowClear
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setSearchText(e.target.value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
style={{ width: 300 }}
|
style={{ width: 300 }}
|
||||||
size="small"
|
size="small"
|
||||||
prefix={<SearchOutlined />}
|
prefix={<SearchOutlined />}
|
||||||
@@ -275,15 +290,17 @@ export default function HuyaCookiePage() {
|
|||||||
rowSelection={{
|
rowSelection={{
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: (keys) => setSelectedRowKeys(keys),
|
onChange: (keys) => setSelectedRowKeys(keys),
|
||||||
|
preserveSelectedRowKeys: true,
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={filteredCookies}
|
dataSource={cookies}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="small"
|
size="small"
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
total,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
onChange: (page, size) => {
|
onChange: (page, size) => {
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ export default function HuyaTasksPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const [accountResult, taskResult, goodsResult, rechargeGoodsResult, configResult, taskTypeResult, tagResult] = await Promise.allSettled([
|
const [accountResult, taskResult, goodsResult, rechargeGoodsResult, configResult, taskTypeResult, tagResult] = await Promise.allSettled([
|
||||||
huyaApi.listAccounts(),
|
huyaApi.listAccounts({ include_cookie: false }),
|
||||||
huyaApi.listTasks(),
|
huyaApi.listTasks(),
|
||||||
huyaApi.listGoods(),
|
huyaApi.listGoods(),
|
||||||
huyaApi.listRechargeGoods(),
|
huyaApi.listRechargeGoods(),
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default function LoginTasksPage() {
|
|||||||
|
|
||||||
const loadAccounts = useCallback(async () => {
|
const loadAccounts = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await accountApi.list();
|
const data = await accountApi.list({ include_sensitive: false });
|
||||||
setAccounts(data);
|
setAccounts(data);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.error(getErrorMessage(e));
|
message.error(getErrorMessage(e));
|
||||||
|
|||||||
Reference in New Issue
Block a user