|
|
@@ -1,31 +1,32 @@
|
|
|
<header class="page-header">
|
|
|
<div><p>USER OPERATIONS</p><h1>用户列表</h1><span>按真实姓名、昵称、手机号、用户 ID 或所属代理查找</span></div>
|
|
|
- <button type="button" (click)="load()" [disabled]="loading()"><lucide-icon [img]="icons.RefreshCw" [size]="17" />刷新</button>
|
|
|
+ <button class="refresh-button" type="button" (click)="refresh()" [disabled]="loading()"><lucide-icon [class.spin]="loading()" [img]="icons.RefreshCw" [size]="17" />{{ loading() ? '正在刷新…' : '刷新数据' }}</button>
|
|
|
</header>
|
|
|
|
|
|
<section class="filters" aria-label="用户筛选">
|
|
|
<label><span>搜索用户</span><div class="search-control"><lucide-icon [img]="icons.Search" [size]="18" /><input [ngModel]="query()" (ngModelChange)="query.set($event)" (keyup.enter)="load(1)" placeholder="姓名、昵称、手机号、用户 ID、代理" /></div></label>
|
|
|
<label><span>账号状态</span><select [ngModel]="status()" (ngModelChange)="status.set($event)"><option value="all">全部状态</option><option value="active">正常</option><option value="disabled">已停用</option></select></label>
|
|
|
+ <label><span>学习状态</span><select [ngModel]="learningStatus()" (ngModelChange)="learningStatus.set($event)"><option value="all">全部学习状态</option><option value="trial">体验用户</option><option value="studying">正在学习</option><option value="completed">已经学完</option><option value="not_started">尚未开始</option><option value="not_applicable">非学员账号</option></select></label>
|
|
|
<div class="filter-actions"><button class="primary" type="button" (click)="load(1)">查询</button><button type="button" (click)="reset()">重置</button></div>
|
|
|
</section>
|
|
|
|
|
|
<section class="table-card">
|
|
|
- <header><div><lucide-icon [img]="icons.Users" [size]="19" /><strong>用户数据</strong><span>{{ page()?.total || 0 }} 人</span></div><small>手机号按当前账号权限显示</small></header>
|
|
|
+ <header><div><lucide-icon [img]="icons.Users" [size]="19" /><strong>会员数据</strong><span>{{ page()?.total || 0 }} 人(已排除管理员)</span></div><small>最后刷新:{{ lastRefreshedAt() | date:'HH:mm:ss' }} · 手机号按权限显示</small></header>
|
|
|
@if (loading() && !page()) { <div class="state"><lucide-icon class="spin" [img]="icons.LoaderCircle" [size]="26" />正在加载用户…</div> }
|
|
|
@else if (error()) { <div class="state error" role="alert">{{ error() }}</div> }
|
|
|
@else {
|
|
|
<div class="table-scroll"><table>
|
|
|
- <thead><tr><th>用户</th><th>手机号</th><th>用户组</th><th>所属代理</th><th>30 分钟</th><th>60 分钟</th><th>注册时间</th><th>状态</th><th><span class="sr-only">操作</span></th></tr></thead>
|
|
|
+ <thead><tr><th>用户</th><th>手机号</th><th>会员组</th><th>学习状态</th><th>所属代理</th><th>30 分钟</th><th>60 分钟</th><th>注册时间</th><th>账号状态</th><th><span class="sr-only">操作</span></th></tr></thead>
|
|
|
<tbody>
|
|
|
@for (row of page()?.items || []; track row.objectId) {
|
|
|
<tr>
|
|
|
<td><div class="user-cell"><strong>{{ row.displayName }}</strong><span>ID {{ row.userId || '—' }} · {{ row.username }}</span></div></td>
|
|
|
- <td>{{ row.mobile || row.mobileMasked || '—' }}</td><td>{{ row.groupName || ('用户组 ' + row.groupId) }}</td><td>{{ row.parentDisplayName || '未绑定' }}</td>
|
|
|
+ <td>{{ row.mobile || row.mobileMasked || '—' }}</td><td>{{ row.groupName || ('用户组 ' + row.groupId) }}</td><td><span class="learning-status" [attr.data-status]="row.learningStatus">{{ row.learningStatusLabel }}</span><small class="status-reason">{{ row.learningStatusReason }}</small></td><td>{{ row.parentDisplayName || '未绑定' }}</td>
|
|
|
<td>{{ row.periods30 | number }}</td><td>{{ row.periods60 | number }}</td><td>{{ row.registeredAt | date:'yyyy-MM-dd' }}</td>
|
|
|
<td><span class="status" [class.disabled]="row.status === 'disabled'">{{ row.statusLabel }}</span></td>
|
|
|
<td><a [routerLink]="['/admin/users', row.objectId]" aria-label="查看用户详情"><lucide-icon [img]="icons.Eye" [size]="17" /></a></td>
|
|
|
</tr>
|
|
|
- } @empty { <tr><td colspan="9"><div class="state">没有符合条件的用户</div></td></tr> }
|
|
|
+ } @empty { <tr><td colspan="10"><div class="state">没有符合条件的用户</div></td></tr> }
|
|
|
</tbody>
|
|
|
</table></div>
|
|
|
<footer class="pagination"><span>第 {{ page()?.page || 1 }} / {{ pageCount() }} 页</span><div><button type="button" aria-label="上一页" [disabled]="(page()?.page || 1) <= 1" (click)="load((page()?.page || 1) - 1)"><lucide-icon [img]="icons.ChevronLeft" [size]="18" /></button><button type="button" aria-label="下一页" [disabled]="(page()?.page || 1) >= pageCount()" (click)="load((page()?.page || 1) + 1)"><lucide-icon [img]="icons.ChevronRight" [size]="18" /></button></div></footer>
|