Commit 0ebda9bc by luoqi

feat(web): /plans 改工作台入口解析器 — 直落详情(列表页弃用,组件保留未挂载)

根路径/旧链接进来直接到详情工作台,选人规则(纯规则):
  ① 我的「进行中」优先级 TOP1(续上手头工作)
  ② 无 → 召回池 TOP1(开新工作)
  ③ 都无 → 空工作台:左栏照常可筛(自行找人)+ 右侧整体空态(说明 + 刷新)
解析期间居中过场;PLAN_VIEW_OWN 权限门同列表页。旧 PlansListApp 保留在仓库不再挂载。

web tsc 0 + Next 生产构建过。仅本地,未部署。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent dc5de128
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Inbox, RefreshCw } from 'lucide-react';
import { Permission } from '@pac/types';
import { Can } from '@/components/can';
import { Card, CardContent } from '@/components/ui/card';
import { PlansListApp } from '@/components/plans/plans-list-app';
import { plansApi } from '@/components/plans/plans-api';
import { PatientPickerRail } from '@/components/plans/patient-picker-rail';
/**
* /plans — 召回池列表(客服工作台入口)。
* 鉴权:(app) layout 的 AuthGate + PLAN_VIEW_OWN 权限。
* /plans — 工作台入口解析器(原列表页已弃用,组件保留在 plans-list-app.tsx 未挂载)。
*
* 进来直接落到详情工作台,选人规则(纯规则):
* ① 我的「进行中」第一个(优先续上手头工作)
* ② 没有 → 召回池优先级最高的一个(开新工作)
* ③ 都没有 → 空工作台:左栏照常(可自行调筛选找人)+ 右侧整体空态
* 所有旧入口(/ 重定向、各处"返回召回池"链接)自动适配 —— 都会被本页弹回详情。
*/
export default function PlansListPage() {
export default function PlansEntryPage() {
return (
<Can
perm={Permission.PLAN_VIEW_OWN}
fallback={
<main className="container mx-auto max-w-3xl p-8">
<Card>
<CardContent className="py-10 text-center text-sm text-muted-foreground">
需要 PLAN_VIEW_OWN 权限才能查看召回池。请联系管理员。
</CardContent>
</Card>
</main>
}
>
<PlansListApp />
<Can perm={Permission.PLAN_VIEW_OWN}>
<EntryResolver />
</Can>
);
}
function EntryResolver() {
const router = useRouter();
const [phase, setPhase] = useState<'resolving' | 'empty'>('resolving');
useEffect(() => {
let cancelled = false;
(async () => {
try {
// ① 我的进行中(优先级最高的一个)
const mine = await plansApi.list({ view: 'mine', status: 'assigned', sort: 'priority_desc', page: 1, pageSize: 1 });
if (cancelled) return;
if (mine.items[0]) {
router.replace(`/plans/${mine.items[0].id}`);
return;
}
// ② 召回池第一个
const pool = await plansApi.list({ view: 'pool', sort: 'priority_desc', page: 1, pageSize: 1 });
if (cancelled) return;
if (pool.items[0]) {
router.replace(`/plans/${pool.items[0].id}`);
return;
}
setPhase('empty');
} catch {
if (!cancelled) setPhase('empty');
}
})();
return () => {
cancelled = true;
};
}, [router]);
if (phase === 'resolving') {
return (
<div className="flex h-screen items-center justify-center gap-2 text-sm text-muted-foreground">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-teal-500 border-t-transparent" />
正在进入召回工作台…
</div>
);
}
// ③ 空工作台:左栏可用(调筛选自己找人),右侧整体空态
return (
<div className="flex h-screen flex-col overflow-hidden bg-slate-50">
<header className="flex h-12 flex-none items-center gap-2 border-b border-slate-200 bg-white px-4">
<span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-teal-600 text-[12px] font-bold text-white">
PAC
</span>
<h1 className="text-[14px] font-semibold text-slate-900">召回工作台</h1>
</header>
<div className="flex min-h-0 flex-1">
<PatientPickerRail currentPlanId="" />
<div className="flex min-w-0 flex-1 flex-col items-center justify-center gap-3 text-center">
<span className="inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-slate-100 text-slate-400">
<Inbox className="h-7 w-7" />
</span>
<div className="text-[15px] font-medium text-slate-700">当前没有待跟进的召回任务</div>
<p className="max-w-sm text-[12.5px] leading-relaxed text-slate-500">
我的任务与召回池都是空的。可以在左侧调整筛选(视图 / 诊所 / 画像标签)自行查找,
或等召回引擎下次重算后刷新。
</p>
<button
type="button"
onClick={() => window.location.reload()}
className="inline-flex items-center gap-1.5 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-[12.5px] text-slate-600 hover:bg-slate-50"
>
<RefreshCw className="h-3.5 w-3.5" />
刷新
</button>
</div>
</div>
</div>
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment