diff --git a/src/app/(main)/devices/page.tsx b/src/app/(main)/devices/page.tsx new file mode 100644 index 0000000..105f340 --- /dev/null +++ b/src/app/(main)/devices/page.tsx @@ -0,0 +1,99 @@ +'use client'; + +import type { ColumnsType } from 'antd/es/table'; +import { Button, Modal, Popconfirm, Space, Table, message } from 'antd'; +import { api, errMsg } from '@/lib/api'; +import { canDo } from '@/lib/auth'; +import { useCursorList } from '@/lib/useCursorList'; +import type { DeviceOnboardingItem } from '@/lib/types'; + +const dt = (v: string) => new Date(v).toLocaleString('zh-CN'); +const EMPTY: Record = {}; + +export default function DevicesPage() { + // 后端按设备聚合、全量返回(next_cursor 恒 null),故无筛选/加载更多。 + const { items, loading, reload } = useCursorList( + '/admin/api/onboarding/devices', + EMPTY, + ); + const canReset = canDo(['operator']); + + const resetDevice = async (d: DeviceOnboardingItem) => { + try { + await api.post(`/admin/api/onboarding/devices/${encodeURIComponent(d.device_id)}/reset`); + message.success('已重置:该设备所有账号下次打开 App 重走引导'); + reload(); + } catch (e) { + message.error(errMsg(e)); + } + }; + + const resetAll = () => { + Modal.confirm({ + title: '确认全部重设新手引导?', + content: + '清空所有设备的引导完成记录,库里所有用户下次打开 App 都会重走一遍新手引导。此操作不可逆。', + okText: '确认全部重设', + okButtonProps: { danger: true }, + cancelText: '取消', + onOk: async () => { + try { + await api.post('/admin/api/onboarding/reset-all', { confirm: true }); + message.success('已全部重设:所有用户下次打开 App 会重走引导'); + reload(); + } catch (e) { + message.error(errMsg(e)); + } + }, + }); + }; + + const columns: ColumnsType = [ + { title: '设备(ANDROID_ID)', dataIndex: 'device_id' }, + { title: '走过引导的账号数', dataIndex: 'account_count', width: 160 }, + { title: '最近完成引导', dataIndex: 'last_completed_at', render: dt, width: 200 }, + { + title: '操作', + key: 'op', + width: 120, + render: (_: unknown, d: DeviceOnboardingItem) => + canReset ? ( + resetDevice(d)} + okText="确认" + cancelText="取消" + > + 重走引导 + + ) : ( + - + ), + }, + ]; + + return ( +
+

设备管理

+

+ 列出走过新手引导的设备(按硬件 ANDROID_ID 聚合)。「重走引导」删除该设备的引导完成记录, + 使其上所有账号下次打开 App 重走一遍。没走过引导的设备本就会引导,不在此列。 +

+ {canReset && ( + + + + )} + + + ); +} diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 8672cc2..cd4b4c5 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -9,6 +9,7 @@ import { FundProjectionScreenOutlined, LogoutOutlined, MessageOutlined, + MobileOutlined, MoneyCollectOutlined, SettingOutlined, TeamOutlined, @@ -23,6 +24,7 @@ const { Sider, Header, Content } = Layout; const MENU = [ { key: '/dashboard', icon: , label: '数据大盘' }, { key: '/users', icon: , label: '用户管理' }, + { key: '/devices', icon: , label: '设备管理' }, { key: '/withdraws', icon: , label: '提现管理' }, { key: '/price-reports', icon: , label: '上报审核' }, { key: '/feedbacks', icon: , label: '反馈工单' }, diff --git a/src/app/(main)/users/page.tsx b/src/app/(main)/users/page.tsx index d88f238..79d8214 100644 --- a/src/app/(main)/users/page.tsx +++ b/src/app/(main)/users/page.tsx @@ -123,28 +123,6 @@ export default function UsersPage() { }); }; - // 一次性操作:点一下把该用户标记为「未看过引导」,下次打开 App 重看一遍,看完后端自动恢复(只触发这一次)。 - // 用户尚未重看前可「撤销」(把 force_onboarding 置回 false),纠正误点。 - const setForceOnboarding = (u: UserListItem, enable: boolean) => { - Modal.confirm({ - title: enable - ? `确认让用户 ${u.phone} 重看一次新手引导?` - : `撤销用户 ${u.phone} 的「重看新手引导」?`, - content: enable - ? '点一下即把该用户标记为「未看过引导」:Ta 下次打开 App 会重走一遍新手引导,看完自动恢复——只触发这一次。仅对已支持该功能的 App 版本生效。' - : '该用户尚未重看,撤销后下次打开 App 不再触发。', - onOk: async () => { - try { - await api.post(`/admin/api/users/${u.id}/force-onboarding`, { enabled: enable }); - message.success(enable ? '已开启:该用户下次打开 App 会重看一次' : '已撤销'); - reload(); - } catch (e) { - message.error(errMsg(e)); - } - }, - }); - }; - const columns: ColumnsType = [ { title: 'ID', dataIndex: 'id', width: 70 }, { title: '手机号', dataIndex: 'phone' }, @@ -177,15 +155,6 @@ export default function UsersPage() { {u.debug_trace_enabled ? '关调试链接' : '开调试链接'} )} - {canStatus && - u.status !== 'deleted' && - (u.force_onboarding ? ( - - 待重看 setForceOnboarding(u, false)}>撤销 - - ) : ( - setForceOnboarding(u, true)}>开启新手引导 - ))} ), }, diff --git a/src/lib/types.ts b/src/lib/types.ts index 869432b..23b151f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -29,13 +29,18 @@ export interface UserListItem { status: string; // 调试链接权限:开了的用户在 App 比价结果页/记录页可复制 price.shaguabijia.com 的 trace 调试链接 debug_trace_enabled: boolean; - // 运营「一键开启新手引导」:true = 该用户下次打开 App 会被强制重走新手引导,走完自动取消 - force_onboarding: boolean; wechat_openid: string | null; created_at: string; last_login_at: string; } +// 设备维度新手引导:一台设备(ANDROID_ID)聚合,走过引导的账号数 + 最近完成时间。 +export interface DeviceOnboardingItem { + device_id: string; + account_count: number; + last_completed_at: string; +} + export interface UserOverview { user: UserListItem; coin_balance: number;