feat(admin): 商品编辑页 上一个/下一个 导航 #15
@@ -28,6 +28,7 @@ export default function App() {
|
||||
const [tab, setTab] = useState<Tab>("products");
|
||||
const [pending, setPending] = useState<number | null>(null);
|
||||
const [view, setView] = useState<View>({ name: "list" });
|
||||
const [navIds, setNavIds] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authed) return;
|
||||
@@ -190,9 +191,19 @@ export default function App() {
|
||||
) : tab === "submissions" ? (
|
||||
<SubmissionsPage onPending={setPending} />
|
||||
) : view.name === "list" ? (
|
||||
<ProductList onOpen={(id) => setView({ name: "detail", id })} />
|
||||
<ProductList
|
||||
onOpen={(id, ids) => {
|
||||
setNavIds(ids);
|
||||
setView({ name: "detail", id });
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ProductDetail id={view.id} onBack={() => setView({ name: "list" })} />
|
||||
<ProductDetail
|
||||
id={view.id}
|
||||
ids={navIds}
|
||||
onNavigate={(id) => setView({ name: "detail", id })}
|
||||
onBack={() => setView({ name: "list" })}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
} from "../types";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
Save,
|
||||
Trash2,
|
||||
@@ -88,10 +90,18 @@ const inputCls =
|
||||
export default function ProductDetail({
|
||||
id,
|
||||
onBack,
|
||||
ids = [],
|
||||
onNavigate,
|
||||
}: {
|
||||
id: string;
|
||||
onBack: () => void;
|
||||
ids?: string[];
|
||||
onNavigate?: (id: string) => void;
|
||||
}) {
|
||||
const navIndex = ids.indexOf(id);
|
||||
const prevId = navIndex > 0 ? ids[navIndex - 1] : null;
|
||||
const nextId =
|
||||
navIndex >= 0 && navIndex < ids.length - 1 ? ids[navIndex + 1] : null;
|
||||
const [d, setD] = useState<Detail | null>(null);
|
||||
const [brands, setBrands] = useState<Brand[]>([]);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
@@ -226,12 +236,35 @@ export default function ProductDetail({
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" /> 返回列表
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" /> 返回列表
|
||||
</button>
|
||||
{ids.length > 1 && navIndex >= 0 && (
|
||||
<div className="ml-2 flex items-center gap-1 text-sm">
|
||||
<button
|
||||
onClick={() => prevId && onNavigate?.(prevId)}
|
||||
disabled={!prevId}
|
||||
className="flex items-center gap-1 rounded border border-gray-300 px-2 py-1 text-gray-600 hover:bg-gray-50 disabled:opacity-40"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" /> 上一个
|
||||
</button>
|
||||
<span className="text-xs text-gray-400">
|
||||
{navIndex + 1} / {ids.length}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => nextId && onNavigate?.(nextId)}
|
||||
disabled={!nextId}
|
||||
className="flex items-center gap-1 rounded border border-gray-300 px-2 py-1 text-gray-600 hover:bg-gray-50 disabled:opacity-40"
|
||||
>
|
||||
下一个 <ChevronRight className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{msg && <span className="text-sm text-emerald-600">{msg}</span>}
|
||||
{error && <span className="text-sm text-red-600">{error}</span>}
|
||||
|
||||
@@ -27,7 +27,7 @@ function QualityBadge({ score }: { score: number }) {
|
||||
export default function ProductList({
|
||||
onOpen,
|
||||
}: {
|
||||
onOpen: (id: string) => void;
|
||||
onOpen: (id: string, ids: string[]) => void;
|
||||
}) {
|
||||
const [q, setQ] = useState("");
|
||||
const [input, setInput] = useState("");
|
||||
@@ -165,7 +165,7 @@ export default function ProductList({
|
||||
onClose={() => setCreating(false)}
|
||||
onCreated={(id) => {
|
||||
setCreating(false);
|
||||
onOpen(id);
|
||||
onOpen(id, [id]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -271,7 +271,7 @@ export default function ProductList({
|
||||
rows.map((r) => (
|
||||
<tr
|
||||
key={r.id}
|
||||
onClick={() => onOpen(r.id)}
|
||||
onClick={() => onOpen(r.id, rows.map((x) => x.id))}
|
||||
className={`cursor-pointer hover:bg-emerald-50/50 ${
|
||||
selected.has(r.id) ? "bg-emerald-50/60" : ""
|
||||
}`}
|
||||
|
||||
Reference in New Issue
Block a user