fix(creator): 权限开通后的状态显示 + 同步范围可选可查
- 权限显示:实测今天 display/status 已变成 1,但 tip 是「数据正在更新中,请耐心等待」, 作品仍是 0 条。原逻辑只在未开通时显示提示条,于是会一边写着「数据已开通」一边列不出 作品,自相矛盾。现在只要后台有话要说就显示,并按状态区分措辞。 - 同步范围:原本写死 90 天,界面上既看不见也改不了。现在可选 7/30/90/180/365/730 天, 与后端校验上限一致。 - 新增 creator_account.last_sync_days,记录上次实际使用的范围 —— 否则界面只能说 「同步过了」,说不清覆盖的是哪一段。选择器也会对齐到它,避免上次同步 1 年、这次 点一下悄悄缩回 90 天。
This commit is contained in:
@@ -73,6 +73,9 @@ class CreatorAccount(MonitorBase):
|
|||||||
|
|
||||||
last_checked_at: Mapped[Optional[int]] = mapped_column(BigInteger)
|
last_checked_at: Mapped[Optional[int]] = mapped_column(BigInteger)
|
||||||
last_synced_at: Mapped[Optional[int]] = mapped_column(BigInteger)
|
last_synced_at: Mapped[Optional[int]] = mapped_column(BigInteger)
|
||||||
|
# 上次同步用的时间范围(天,按发布时间)。当前展示的数据就是这个范围的产物 ——
|
||||||
|
# 不记下来的话,界面只能说明"同步过了",说不清是哪一段。
|
||||||
|
last_sync_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||||
last_error: Mapped[Optional[str]] = mapped_column(Text)
|
last_error: Mapped[Optional[str]] = mapped_column(Text)
|
||||||
|
|
||||||
created_at: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
created_at: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ def _account_dict(account: CreatorAccount, note_count: int = 0) -> Dict[str, Any
|
|||||||
"permission_tip": account.permission_tip,
|
"permission_tip": account.permission_tip,
|
||||||
"last_checked_at": account.last_checked_at,
|
"last_checked_at": account.last_checked_at,
|
||||||
"last_synced_at": account.last_synced_at,
|
"last_synced_at": account.last_synced_at,
|
||||||
|
"last_sync_days": account.last_sync_days,
|
||||||
"last_error": account.last_error,
|
"last_error": account.last_error,
|
||||||
"has_cookie": bool(account.cookie),
|
"has_cookie": bool(account.cookie),
|
||||||
"created_at": account.created_at,
|
"created_at": account.created_at,
|
||||||
@@ -344,6 +345,7 @@ async def sync_account(
|
|||||||
)
|
)
|
||||||
|
|
||||||
account.last_synced_at = now
|
account.last_synced_at = now
|
||||||
|
account.last_sync_days = days
|
||||||
account.last_error = None
|
account.last_error = None
|
||||||
account.updated_at = now
|
account.updated_at = now
|
||||||
await refresh_permission(session, account)
|
await refresh_permission(session, account)
|
||||||
@@ -352,6 +354,7 @@ async def sync_account(
|
|||||||
return {
|
return {
|
||||||
"account_id": account_id,
|
"account_id": account_id,
|
||||||
"fetched": len(collected),
|
"fetched": len(collected),
|
||||||
|
"days": days,
|
||||||
"permission_status": account.permission_status,
|
"permission_status": account.permission_status,
|
||||||
"permission_tip": account.permission_tip,
|
"permission_tip": account.permission_tip,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Briefcase,
|
Briefcase,
|
||||||
@@ -11,6 +11,13 @@ import {
|
|||||||
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
import {
|
import {
|
||||||
useCheckCreatorAccount,
|
useCheckCreatorAccount,
|
||||||
useCreatorAccount,
|
useCreatorAccount,
|
||||||
@@ -33,6 +40,17 @@ const PERMISSION_LABEL: Record<CreatorPermissionStatus, { text: string; tone: st
|
|||||||
unknown: { text: '权限未知', tone: 'text-cyber-text-muted' },
|
unknown: { text: '权限未知', tone: 'text-cyber-text-muted' },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 后台按**发布时间**筛选,所以这是"作品发布距今多少天",不是"最近多少天的数据"。
|
||||||
|
// 上限 730 天与后端校验一致。
|
||||||
|
const RANGE_OPTIONS = [
|
||||||
|
{ value: '7', label: '近 7 天' },
|
||||||
|
{ value: '30', label: '近 30 天' },
|
||||||
|
{ value: '90', label: '近 90 天' },
|
||||||
|
{ value: '180', label: '近 180 天' },
|
||||||
|
{ value: '365', label: '近 1 年' },
|
||||||
|
{ value: '730', label: '近 2 年' },
|
||||||
|
]
|
||||||
|
|
||||||
const SUMMARY_TILES: Array<{ key: keyof CreatorNote; label: string }> = [
|
const SUMMARY_TILES: Array<{ key: keyof CreatorNote; label: string }> = [
|
||||||
{ key: 'exposure', label: '曝光' },
|
{ key: 'exposure', label: '曝光' },
|
||||||
{ key: 'views', label: '观看' },
|
{ key: 'views', label: '观看' },
|
||||||
@@ -112,6 +130,16 @@ function AccountPanel({ accountId }: { accountId: number }) {
|
|||||||
const sync = useSyncCreatorAccount()
|
const sync = useSyncCreatorAccount()
|
||||||
const check = useCheckCreatorAccount()
|
const check = useCheckCreatorAccount()
|
||||||
const remove = useDeleteCreatorAccount()
|
const remove = useDeleteCreatorAccount()
|
||||||
|
// 拉取范围是**同步参数**,不是展示筛选:数据按这个范围取回来存库。
|
||||||
|
// 默认 90 天,与后端的默认值一致。
|
||||||
|
const [days, setDays] = useState('90')
|
||||||
|
|
||||||
|
// 选择器对齐到上次实际用的范围。不对齐的话,上次同步了 1 年、这次点同步会悄悄
|
||||||
|
// 缩回 90 天,而界面上没有任何迹象。
|
||||||
|
const lastDays = data?.account.last_sync_days ?? 0
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastDays > 0) setDays(String(lastDays))
|
||||||
|
}, [lastDays])
|
||||||
|
|
||||||
if (isLoading || !data) {
|
if (isLoading || !data) {
|
||||||
return <p className="py-10 text-center text-[11px] font-mono text-cyber-text-muted">加载中…</p>
|
return <p className="py-10 text-center text-[11px] font-mono text-cyber-text-muted">加载中…</p>
|
||||||
@@ -139,6 +167,9 @@ function AccountPanel({ accountId }: { accountId: number }) {
|
|||||||
</span>
|
</span>
|
||||||
<span className="ml-1 text-[10px] font-mono text-cyber-text-muted">
|
<span className="ml-1 text-[10px] font-mono text-cyber-text-muted">
|
||||||
上次同步 {account.last_synced_at ? formatRelative(account.last_synced_at) : '从未'}
|
上次同步 {account.last_synced_at ? formatRelative(account.last_synced_at) : '从未'}
|
||||||
|
{/* 当前列出的作品就是上一次同步范围里的产物,所以要说清是哪一段 ——
|
||||||
|
否则只看"同步过了"分不清覆盖了多少。 */}
|
||||||
|
{account.last_sync_days > 0 && ` · 覆盖近 ${account.last_sync_days} 天发布的`}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
@@ -151,7 +182,23 @@ function AccountPanel({ accountId }: { accountId: number }) {
|
|||||||
<RefreshCw className={`w-3 h-3 mr-1 ${check.isPending ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`w-3 h-3 mr-1 ${check.isPending ? 'animate-spin' : ''}`} />
|
||||||
检测
|
检测
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" disabled={sync.isPending} onClick={() => sync.mutate({ id: account.id })}>
|
<Select value={days} onValueChange={setDays}>
|
||||||
|
<SelectTrigger className="h-7 w-[104px] text-[10px] font-mono">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{RANGE_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value} className="text-[11px]">
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
disabled={sync.isPending}
|
||||||
|
onClick={() => sync.mutate({ id: account.id, days: Number(days) })}
|
||||||
|
>
|
||||||
<RefreshCw className={`w-3 h-3 mr-1 ${sync.isPending ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`w-3 h-3 mr-1 ${sync.isPending ? 'animate-spin' : ''}`} />
|
||||||
同步数据
|
同步数据
|
||||||
</Button>
|
</Button>
|
||||||
@@ -162,18 +209,31 @@ function AccountPanel({ accountId }: { accountId: number }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto terminal-scroll px-3 py-3 space-y-3">
|
<div className="flex-1 overflow-y-auto terminal-scroll px-3 py-3 space-y-3">
|
||||||
{/* 权限状态必须显眼:它解释了"为什么没有数据"。后台的原话照抄,不改写。 */}
|
{/* 只要后台有话要说就显示,**包括已经开通的时候**。
|
||||||
{account.permission_status !== 'active' && (
|
实测开通后的原话是「数据正在更新中,请耐心等待」—— 若只在未开通时才显示,
|
||||||
<div className="flex items-start gap-2 rounded-md border border-cyber-neon-orange/30 bg-cyber-neon-orange/5 px-3 py-2">
|
就会一边写着"数据已开通"、一边列不出作品,自相矛盾。 */}
|
||||||
<Clock className="w-3.5 h-3.5 mt-0.5 flex-shrink-0 text-cyber-neon-orange" />
|
{account.permission_tip && (
|
||||||
|
<div
|
||||||
|
className={`flex items-start gap-2 rounded-md border px-3 py-2 ${
|
||||||
|
account.permission_status === 'active'
|
||||||
|
? 'border-cyber-border-DEFAULT bg-cyber-bg-tertiary/40'
|
||||||
|
: 'border-cyber-neon-orange/30 bg-cyber-neon-orange/5'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Clock
|
||||||
|
className={`w-3.5 h-3.5 mt-0.5 flex-shrink-0 ${
|
||||||
|
account.permission_status === 'active'
|
||||||
|
? 'text-cyber-text-muted'
|
||||||
|
: 'text-cyber-neon-orange'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
<div className="text-[11px] font-mono leading-relaxed">
|
<div className="text-[11px] font-mono leading-relaxed">
|
||||||
<span className={permission.tone}>{permission.text}</span>
|
<span className={permission.tone}>{permission.text}</span>
|
||||||
{account.permission_tip && (
|
<span className="ml-2 text-cyber-text-secondary">{account.permission_tip}</span>
|
||||||
<span className="ml-2 text-cyber-text-secondary">{account.permission_tip}</span>
|
|
||||||
)}
|
|
||||||
<div className="mt-0.5 text-cyber-text-muted">
|
<div className="mt-0.5 text-cyber-text-muted">
|
||||||
数据权限由创作者后台按天开通。在此之前同步会成功但返回 0 条 —— 那是正常的,
|
{account.permission_status === 'active'
|
||||||
不是采集失败。
|
? '权限已开通,但后台的数据可能还在准备中 —— 稍后重新同步即可。'
|
||||||
|
: '数据权限由创作者后台按天开通。在此之前同步会成功但返回 0 条 —— 那是正常的,不是采集失败。'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export interface CreatorAccount {
|
|||||||
permission_tip: string
|
permission_tip: string
|
||||||
last_checked_at: number | null
|
last_checked_at: number | null
|
||||||
last_synced_at: number | null
|
last_synced_at: number | null
|
||||||
|
/** 上次同步用的范围(天)。0 表示从未同步过。 */
|
||||||
|
last_sync_days: number
|
||||||
last_error: string | null
|
last_error: string | null
|
||||||
has_cookie: boolean
|
has_cookie: boolean
|
||||||
created_at: number
|
created_at: number
|
||||||
|
|||||||
Reference in New Issue
Block a user