用户封禁、后台筛选排序
This commit is contained in:
1 parent
6b263af74f
commit
c45025fc86
14 files changed
+293
-30
No files matched your search
@@ -3,7 +3,7 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { api } from '@/lib/api';
|
||||
import { Button, Card, Empty, Input, Modal, Notice, Select } from '@/components/ui';
|
||||
import { formatDate } from '@/lib/datetime';
|
||||
import { formatDateTime } from '@/lib/datetime';
|
||||
|
||||
interface UserRow {
|
||||
id: number;
|
||||
@@ -44,6 +44,11 @@ export default function UsersPage() {
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
/** 筛选与排序:'-1' / '' 表示全部 */
|
||||
const [statusFilter, setStatusFilter] = useState('-1');
|
||||
const [entitlementFilter, setEntitlementFilter] = useState('');
|
||||
const [sortKey, setSortKey] = useState('id');
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||
const [error, setError] = useState('');
|
||||
const [editing, setEditing] = useState<UserRow | null>(null);
|
||||
const [form, setForm] = useState({ nickname: '', headicon_id: '0', status: '1' });
|
||||
@@ -54,22 +59,36 @@ export default function UsersPage() {
|
||||
const load = useCallback((nextPage: number, nextKeyword: string) => {
|
||||
setError('');
|
||||
api
|
||||
.listUsers(nextPage, PAGE_SIZE, nextKeyword)
|
||||
.listUsers({
|
||||
page: nextPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
keyword: nextKeyword,
|
||||
status: statusFilter === '-1' ? undefined : Number(statusFilter),
|
||||
entitlement: entitlementFilter === 'has' || entitlementFilter === 'none' ? entitlementFilter : undefined,
|
||||
sort: sortKey,
|
||||
order: sortOrder,
|
||||
})
|
||||
.then((res) => {
|
||||
setItems(res.items as unknown as UserRow[]);
|
||||
setTotal(res.total);
|
||||
setPage(res.page);
|
||||
})
|
||||
.catch((err: { message: string }) => setError(err.message));
|
||||
}, []);
|
||||
}, [statusFilter, entitlementFilter, sortKey, sortOrder]);
|
||||
|
||||
// 筛选 / 排序一变,load 的引用就变,这里会带着当前关键词回到第 1 页重拉
|
||||
useEffect(() => {
|
||||
load(1, keyword);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [load]);
|
||||
|
||||
// 发放权益时用的课程下拉,只在首次进页面拉一次
|
||||
useEffect(() => {
|
||||
load(1, '');
|
||||
api
|
||||
.listResource('courses', { page: 1, pageSize: 100 })
|
||||
.then((res) => setCourses(res.items as unknown as CourseRow[]))
|
||||
.catch(() => setCourses([]));
|
||||
}, [load]);
|
||||
}, []);
|
||||
|
||||
const loadEntitlements = (userId: number) => {
|
||||
api
|
||||
@@ -141,8 +160,48 @@ export default function UsersPage() {
|
||||
<Card
|
||||
title={`共 ${total} 位用户`}
|
||||
action={
|
||||
<div className="flex gap-2">
|
||||
<Input value={keyword} onChange={setKeyword} placeholder="昵称或 openid…" />
|
||||
/* 筛选、排序、搜索排在一行;改任意一项都会从第 1 页重新拉 */
|
||||
<div className="flex flex-nowrap items-center gap-2">
|
||||
<div className="w-32">
|
||||
<Select
|
||||
value={statusFilter}
|
||||
onChange={setStatusFilter}
|
||||
options={[
|
||||
{ value: -1, label: '全部状态' },
|
||||
{ value: 1, label: '正常' },
|
||||
{ value: 0, label: '封禁' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-36">
|
||||
<Select
|
||||
value={entitlementFilter}
|
||||
onChange={setEntitlementFilter}
|
||||
options={[
|
||||
{ value: '', label: '全部用户' },
|
||||
{ value: 'has', label: '有已购课程' },
|
||||
{ value: 'none', label: '无已购课程' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-40">
|
||||
<Select
|
||||
value={sortKey}
|
||||
onChange={setSortKey}
|
||||
options={[
|
||||
{ value: 'id', label: '按 ID' },
|
||||
{ value: 'lastLogin', label: '按最后登录时间' },
|
||||
{ value: 'registered', label: '按注册时间' },
|
||||
{ value: 'entitlements', label: '按已购课程数' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="ghost" onClick={() => setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc')}>
|
||||
{sortOrder === 'desc' ? '降序 ↓' : '升序 ↑'}
|
||||
</Button>
|
||||
<div className="w-52">
|
||||
<Input value={keyword} onChange={setKeyword} placeholder="昵称或 openid…" />
|
||||
</div>
|
||||
<Button variant="ghost" onClick={() => load(1, keyword)}>
|
||||
搜索
|
||||
</Button>
|
||||
@@ -176,7 +235,7 @@ export default function UsersPage() {
|
||||
{u.status === 1 ? <span className="text-green-600">正常</span> : <span className="text-red-600">封禁</span>}
|
||||
</td>
|
||||
<td className="px-2 py-2 text-gray-500">
|
||||
{formatDate(u.last_login_at)}
|
||||
{formatDateTime(u.last_login_at)}
|
||||
</td>
|
||||
<td className="px-2 py-2 text-right">
|
||||
<Button variant="ghost" onClick={() => openEdit(u)}>
|
||||
|
||||
Reference in new issue
Block a user