fix(monitor): 从界面上建的任务永远是小红书任务,平台从没被传上去
现象:站在抖音页面上新建任务,任务跑到小红书列表里去了;界面上还写着「笔记」。
根因在前端:TaskCreatePayload 里**根本没有 platform 字段**,handleSubmit 拼的 payload
自然也不带它,而后端是 `payload.get("platform") or PLATFORM_XHS` —— 于是不管在哪个
平台标签下建任务,落下来的都是小红书任务。以前只支持小红书,两边都看不出问题。
* TaskCreatePayload 补上 platform(并在注释里写明为什么它是必填),handleSubmit 带上
当前平台。
* 更新任务时不带 platform:平台创建后不可更改,带着会让「平台能改」看起来像真的。
* 三处写死的「笔记」改成按平台取措辞(能力矩阵的 target_hints.note_label):
· 任务编辑器的类型选择项「笔记(批量监控指定内容)」
· 「笔记模式下此项不生效」那句提示
· 任务卡片上的类型徽章 —— 它按**任务自己的**平台取词,不是当前平台,因为卡片未必
只出现在同平台的列表里
抖音管它们叫「作品」,小红书叫「笔记」,写死一个对另一个就是错的。
测试 +1:后端这一半也守住 —— 建任务时显式给了平台,就必须落到那个平台,且不得出现在
另一个平台的列表里。前端那半边是 UI,测不了,但后端守住能挡住「给了不用」这类退化。
注意:这次是纯前端漏传,后端那个「缺省回退小红书」的行为本身没变(有测试断言它是
有意为之的兼容行为)。要彻底消灭这类静默错误,可以把缺省值去掉、让 platform 必填 ——
那会破坏 API 兼容性,目前没有任何别的调用方,需要的话说一声。
This commit is contained in:
@@ -177,6 +177,29 @@ class TestTaskCreationGuard:
|
||||
tasks = (await client.get("/api/monitor/tasks")).json()["tasks"]
|
||||
assert {t["platform"] for t in tasks} == {"xhs"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_an_explicit_platform_is_honoured_on_create(self, client):
|
||||
"""建任务时给的平台必须落到那个平台。
|
||||
|
||||
缺省值是小红的(接口早期的兼容行为),所以「在抖音页面建任务」如果没有显式
|
||||
带上 platform,就会安安静静地变成一个小红书任务 —— 不报错,只是出现在另一
|
||||
个列表里。前端那半边已经改成必传;这里守住后端这一半:给了就必须用。
|
||||
"""
|
||||
sec_uid = "MS4wLjABAAAATJPY7LAlaa5X-c8uNdWkvz0jUGgpw4eeXIwu_8BhvqE"
|
||||
created = await client.post(
|
||||
"/api/monitor/tasks",
|
||||
json={"name": "抖音任务", "mode": "creator", "platform": "dy", "targets": [sec_uid]},
|
||||
)
|
||||
assert created.status_code == 201
|
||||
|
||||
assert (await client.get("/api/monitor/tasks", params={"platform": "xhs"})).json()[
|
||||
"tasks"
|
||||
] == []
|
||||
dy_tasks = (
|
||||
await client.get("/api/monitor/tasks", params={"platform": "dy"})
|
||||
).json()["tasks"]
|
||||
assert [t["name"] for t in dy_tasks] == ["抖音任务"]
|
||||
|
||||
|
||||
class TestPlatformScoping:
|
||||
async def _seed_two_platforms(self, client):
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Bell, CalendarClock, Pencil, Play, Trash2 } from 'lucide-react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useDeleteTask, useRunTaskNow, useUpdateTask } from '@/hooks/useMonitor'
|
||||
import { useCurrentPlatform } from '@/hooks/usePlatform'
|
||||
import { formatRelative } from '@/lib/monitorFormat'
|
||||
import type { MonitorTask } from '@/types/monitor'
|
||||
|
||||
@@ -46,6 +47,12 @@ export function TaskCard({ task, selected, onSelect, onEdit }: TaskCardProps) {
|
||||
const updateTask = useUpdateTask()
|
||||
const deleteTask = useDeleteTask()
|
||||
const runNow = useRunTaskNow()
|
||||
// 按**任务自己的**平台取措辞,而不是当前平台 —— 卡片未必只出现在同平台的列表里。
|
||||
// 抖音管它们叫「作品」,小红书叫「笔记」,写死一个对另一个就是错的。
|
||||
const { platforms } = useCurrentPlatform()
|
||||
const noteNoun =
|
||||
platforms.find((entry) => entry.value === task.platform)?.target_hints?.note_label ??
|
||||
'笔记'
|
||||
|
||||
// A suspected cookie failure surfaces here so it is visible without opening
|
||||
// the event feed.
|
||||
@@ -65,7 +72,7 @@ export function TaskCard({ task, selected, onSelect, onEdit }: TaskCardProps) {
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-mono text-sm text-cyber-text-primary truncate">{task.name}</span>
|
||||
<Badge variant="outline" className="text-[10px]">
|
||||
{task.mode === 'creator' ? '博主' : '笔记'}
|
||||
{task.mode === 'creator' ? '博主' : noteNoun}
|
||||
</Badge>
|
||||
<Badge variant={statusVariant(task.last_status)} className="text-[10px]">
|
||||
{STATUS_LABEL[task.last_status] ?? task.last_status}
|
||||
|
||||
@@ -109,7 +109,7 @@ export function TaskEditorDialog({ open, onOpenChange, task }: TaskEditorDialogP
|
||||
const { data: settings } = useSettings()
|
||||
// 示例链接和措辞都由服务端的能力矩阵给 —— 前端不自己判断平台,否则加一个平台
|
||||
// 就要改这里一次,而且很容易漏。
|
||||
const { capability } = useCurrentPlatform()
|
||||
const { capability, platform } = useCurrentPlatform()
|
||||
const hints = capability?.target_hints
|
||||
|
||||
const [name, setName] = useState('')
|
||||
@@ -179,6 +179,9 @@ export function TaskEditorDialog({ open, onOpenChange, task }: TaskEditorDialogP
|
||||
const handleSubmit = () => {
|
||||
const payload: TaskCreatePayload = {
|
||||
name: name.trim(),
|
||||
// 当前平台。漏掉这一项,后端会退回小红书 —— 表现是「在抖音页面建的任务
|
||||
// 跑到小红书列表里去了」,而且不报任何错。
|
||||
platform,
|
||||
mode,
|
||||
interval_minutes: Number(intervalMinutes),
|
||||
schedule_mode: scheduleMode,
|
||||
@@ -199,7 +202,11 @@ export function TaskEditorDialog({ open, onOpenChange, task }: TaskEditorDialogP
|
||||
|
||||
const done = () => onOpenChange(false)
|
||||
if (isEdit && task) {
|
||||
updateTask.mutate({ id: task.id, payload }, { onSuccess: done })
|
||||
// 平台创建后不可更改,更新请求里就不带它了 —— 带着会让「平台能被改」这件事
|
||||
// 看起来像是真的。
|
||||
const updatePayload: Partial<TaskCreatePayload> = { ...payload }
|
||||
delete updatePayload.platform
|
||||
updateTask.mutate({ id: task.id, payload: updatePayload }, { onSuccess: done })
|
||||
} else {
|
||||
createTask.mutate(payload, { onSuccess: done })
|
||||
}
|
||||
@@ -241,7 +248,7 @@ export function TaskEditorDialog({ open, onOpenChange, task }: TaskEditorDialogP
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="creator">博主(监控其作品)</SelectItem>
|
||||
<SelectItem value="note">笔记(批量监控指定内容)</SelectItem>
|
||||
<SelectItem value="note">{`${hints?.note_label ?? '笔记'}(批量监控指定内容)`}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{isEdit && (
|
||||
@@ -432,7 +439,7 @@ export function TaskEditorDialog({ open, onOpenChange, task }: TaskEditorDialogP
|
||||
</>
|
||||
) : (
|
||||
<p className="text-[10px] font-mono text-cyber-neon-orange">
|
||||
笔记模式下此项不生效:你列出的每个链接都会被逐条抓取。
|
||||
{hints?.note_label ?? '笔记'}模式下此项不生效:你列出的每个链接都会被逐条抓取。
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -250,6 +250,13 @@ export interface MonitorOverview {
|
||||
|
||||
export interface TaskCreatePayload {
|
||||
name: string
|
||||
/**
|
||||
* 任务归属的平台。
|
||||
*
|
||||
* **必须显式带上。** 后端在缺省时会退回小红书 —— 那是接口早期的兼容行为,
|
||||
* 于是「在抖音页面上建任务」会安安静静地建出一个小红书任务(遇到过)。
|
||||
*/
|
||||
platform: string
|
||||
mode: MonitorMode
|
||||
interval_minutes: number
|
||||
schedule_mode: ScheduleMode
|
||||
|
||||
Reference in New Issue
Block a user