refactor: 对接 duoqi-api 文档规范
- API 路径前缀改为 /v1/admin - 分类管理改用服务端分页(page/limit),移除未定义的 search/status 筛选 - 知识卡字段重命名:basic→summary、deep→deepDive - 各页面移除不必要的 limit 参数
This commit is contained in:
parent
b6dc6848af
commit
2c2fc952f9
@ -83,7 +83,7 @@ export function QuestionForm({ question }: QuestionFormProps) {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories({ limit: 100 }).then((res) => setCategories(res.data))
|
||||
fetchCategories({}).then((res) => setCategories(res.data))
|
||||
}, [])
|
||||
|
||||
async function onSubmit(data: FormValues) {
|
||||
|
||||
@ -4,7 +4,7 @@ import { getStoredToken, removeStoredToken } from "./auth"
|
||||
|
||||
export const apiClient = ky.create({
|
||||
baseUrl: API_BASE_URL,
|
||||
prefix: "/admin",
|
||||
prefix: "/v1/admin",
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
({ request }) => {
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { apiClient } from "@/lib/api-client"
|
||||
import type { PaginatedResponse, ApiResponse } from "@/types/api"
|
||||
import type { Category, CategoryFormData, CategoryStatus } from "@/types/category"
|
||||
import type { ApiResponse, PaginatedResponse } from "@/types/api"
|
||||
import type { Category, CategoryFormData } from "@/types/category"
|
||||
|
||||
export interface FetchCategoriesParams {
|
||||
page?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
status?: CategoryStatus
|
||||
}
|
||||
|
||||
export async function fetchCategories(
|
||||
@ -15,8 +13,6 @@ export async function fetchCategories(
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.page) searchParams.set("page", String(params.page))
|
||||
if (params.limit) searchParams.set("limit", String(params.limit))
|
||||
if (params.search) searchParams.set("search", params.search)
|
||||
if (params.status) searchParams.set("status", params.status)
|
||||
|
||||
return apiClient
|
||||
.get("categories", { searchParams })
|
||||
|
||||
@ -6,8 +6,8 @@ export interface KnowledgeCardItem {
|
||||
questionId: string
|
||||
questionStem: string
|
||||
categoryId: string
|
||||
basic: string
|
||||
deep?: string
|
||||
summary: string
|
||||
deepDive?: string
|
||||
sourceRef?: string
|
||||
updatedAt: string
|
||||
}
|
||||
@ -34,8 +34,8 @@ export async function fetchKnowledgeCards(
|
||||
}
|
||||
|
||||
export interface UpdateKnowledgeCardData {
|
||||
basic: string
|
||||
deep?: string
|
||||
summary: string
|
||||
deepDive?: string
|
||||
sourceRef?: string
|
||||
}
|
||||
|
||||
|
||||
@ -4,16 +4,8 @@ import {
|
||||
getCoreRowModel,
|
||||
flexRender,
|
||||
} from "@tanstack/react-table"
|
||||
import { Plus, Search } from "lucide-react"
|
||||
import { Plus } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -31,8 +23,7 @@ import {
|
||||
updateCategory,
|
||||
deleteCategory,
|
||||
} from "@/lib/api/category-api"
|
||||
import { CATEGORY_STATUS_LABELS } from "@/lib/constants"
|
||||
import type { Category, CategoryFormData, CategoryStatus } from "@/types/category"
|
||||
import type { Category, CategoryFormData } from "@/types/category"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@ -41,8 +32,6 @@ export default function CategoriesPage() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [total, setTotal] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
const [search, setSearch] = useState("")
|
||||
const [statusFilter, setStatusFilter] = useState<CategoryStatus | "all">("all")
|
||||
|
||||
// 对话框状态
|
||||
const [formOpen, setFormOpen] = useState(false)
|
||||
@ -58,17 +47,16 @@ export default function CategoriesPage() {
|
||||
const res = await fetchCategories({
|
||||
page,
|
||||
limit: PAGE_SIZE,
|
||||
search: search || undefined,
|
||||
status: statusFilter !== "all" ? statusFilter : undefined,
|
||||
})
|
||||
setCategories(res.data)
|
||||
setTotal(res.pagination.total)
|
||||
} catch {
|
||||
setCategories([])
|
||||
setTotal(0)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [page, search, statusFilter])
|
||||
}, [page])
|
||||
|
||||
useEffect(() => {
|
||||
loadCategories()
|
||||
@ -133,41 +121,6 @@ export default function CategoriesPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 筛选栏 */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative max-w-xs flex-1">
|
||||
<Search className="absolute left-2.5 top-2.5 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索分类名称..."
|
||||
className="pl-9"
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
setSearch(e.target.value)
|
||||
setPage(1)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
value={statusFilter}
|
||||
onValueChange={(val: CategoryStatus | "all") => {
|
||||
setStatusFilter(val)
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-28">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
{Object.entries(CATEGORY_STATUS_LABELS).map(([value, label]) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 表格 */}
|
||||
<div className="rounded-lg border">
|
||||
<Table>
|
||||
|
||||
@ -45,7 +45,7 @@ const DEEP_MAX = 300
|
||||
type CardStatus = "all" | "complete" | "incomplete"
|
||||
|
||||
function getCardStatus(item: KnowledgeCardItem): "complete" | "incomplete" {
|
||||
return item.basic && item.basic.trim().length > 0 ? "complete" : "incomplete"
|
||||
return item.summary && item.summary.trim().length > 0 ? "complete" : "incomplete"
|
||||
}
|
||||
|
||||
export default function KnowledgeCardsPage() {
|
||||
@ -58,7 +58,7 @@ export default function KnowledgeCardsPage() {
|
||||
// 编辑对话框
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [editingCard, setEditingCard] = useState<KnowledgeCardItem | null>(null)
|
||||
const [editForm, setEditForm] = useState<UpdateKnowledgeCardData>({ basic: "", deep: "", sourceRef: "" })
|
||||
const [editForm, setEditForm] = useState<UpdateKnowledgeCardData>({ summary: "", deepDive: "", sourceRef: "" })
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
// 详情对话框
|
||||
@ -66,7 +66,7 @@ export default function KnowledgeCardsPage() {
|
||||
const [detailCard, setDetailCard] = useState<KnowledgeCardItem | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories({ limit: 100 }).then((res) => setCategories(res.data))
|
||||
fetchCategories({}).then((res) => setCategories(res.data))
|
||||
}, [])
|
||||
|
||||
const loadCards = useCallback(async () => {
|
||||
@ -95,8 +95,8 @@ export default function KnowledgeCardsPage() {
|
||||
function openEdit(card: KnowledgeCardItem) {
|
||||
setEditingCard(card)
|
||||
setEditForm({
|
||||
basic: card.basic,
|
||||
deep: card.deep || "",
|
||||
summary: card.summary,
|
||||
deepDive: card.deepDive || "",
|
||||
sourceRef: card.sourceRef || "",
|
||||
})
|
||||
setEditOpen(true)
|
||||
@ -145,10 +145,10 @@ export default function KnowledgeCardsPage() {
|
||||
id: "basicStatus",
|
||||
header: "基础卡",
|
||||
cell: ({ row }) => {
|
||||
const hasBasic = row.original.basic?.trim().length > 0
|
||||
const hasBasic = row.original.summary?.trim().length > 0
|
||||
return (
|
||||
<Badge variant={hasBasic ? "default" : "outline"}>
|
||||
{hasBasic ? `${row.original.basic.length} 字` : "未填写"}
|
||||
{hasBasic ? `${row.original.summary.length} 字` : "未填写"}
|
||||
</Badge>
|
||||
)
|
||||
},
|
||||
@ -157,10 +157,10 @@ export default function KnowledgeCardsPage() {
|
||||
id: "deepStatus",
|
||||
header: "深度卡",
|
||||
cell: ({ row }) => {
|
||||
const hasDeep = (row.original.deep?.trim().length ?? 0) > 0
|
||||
const hasDeep = (row.original.deepDive?.trim().length ?? 0) > 0
|
||||
return (
|
||||
<Badge variant={hasDeep ? "secondary" : "outline"}>
|
||||
{hasDeep ? `${row.original.deep!.length} 字` : "未填写"}
|
||||
{hasDeep ? `${row.original.deepDive!.length} 字` : "未填写"}
|
||||
</Badge>
|
||||
)
|
||||
},
|
||||
@ -312,17 +312,17 @@ export default function KnowledgeCardsPage() {
|
||||
<Badge variant="secondary" className="text-xs">所有用户</Badge>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed bg-muted/30 rounded-md p-3">
|
||||
{detailCard.basic || <span className="italic text-muted-foreground">未填写</span>}
|
||||
{detailCard.summary || <span className="italic text-muted-foreground">未填写</span>}
|
||||
</p>
|
||||
</div>
|
||||
{detailCard.deep && (
|
||||
{detailCard.deepDive && (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Label>深度知识卡</Label>
|
||||
<Badge variant="outline" className="text-xs">Pro 用户</Badge>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed bg-muted/30 rounded-md p-3 text-muted-foreground">
|
||||
{detailCard.deep}
|
||||
{detailCard.deepDive}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@ -364,14 +364,14 @@ export default function KnowledgeCardsPage() {
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label htmlFor="edit-basic">基础知识卡</Label>
|
||||
<span className={`text-xs ${editForm.basic.length > BASIC_MAX ? "text-destructive font-medium" : "text-muted-foreground"}`}>
|
||||
{editForm.basic.length}/{BASIC_MAX}
|
||||
<span className={`text-xs ${editForm.summary.length > BASIC_MAX ? "text-destructive font-medium" : "text-muted-foreground"}`}>
|
||||
{editForm.summary.length}/{BASIC_MAX}
|
||||
</span>
|
||||
</div>
|
||||
<Textarea
|
||||
id="edit-basic"
|
||||
value={editForm.basic}
|
||||
onChange={(e) => setEditForm({ ...editForm, basic: e.target.value })}
|
||||
value={editForm.summary}
|
||||
onChange={(e) => setEditForm({ ...editForm, summary: e.target.value })}
|
||||
rows={3}
|
||||
placeholder="2-3 句趣味解读..."
|
||||
/>
|
||||
@ -380,14 +380,14 @@ export default function KnowledgeCardsPage() {
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label htmlFor="edit-deep">深度知识卡(Pro)</Label>
|
||||
<span className={`text-xs ${editForm.deep && editForm.deep.length > DEEP_MAX ? "text-destructive font-medium" : "text-muted-foreground"}`}>
|
||||
{(editForm.deep?.length ?? 0)}/{DEEP_MAX}
|
||||
<span className={`text-xs ${editForm.deepDive && editForm.deepDive.length > DEEP_MAX ? "text-destructive font-medium" : "text-muted-foreground"}`}>
|
||||
{(editForm.deepDive?.length ?? 0)}/{DEEP_MAX}
|
||||
</span>
|
||||
</div>
|
||||
<Textarea
|
||||
id="edit-deep"
|
||||
value={editForm.deep ?? ""}
|
||||
onChange={(e) => setEditForm({ ...editForm, deep: e.target.value })}
|
||||
value={editForm.deepDive ?? ""}
|
||||
onChange={(e) => setEditForm({ ...editForm, deepDive: e.target.value })}
|
||||
rows={5}
|
||||
placeholder="扩展背景故事、趣味延伸..."
|
||||
/>
|
||||
|
||||
@ -113,7 +113,7 @@ export default function QuestionsPage() {
|
||||
|
||||
// 加载分类列表(用于筛选和列显示)
|
||||
useEffect(() => {
|
||||
fetchCategories({ limit: 100 }).then((res) => setCategories(res.data))
|
||||
fetchCategories({}).then((res) => setCategories(res.data))
|
||||
}, [])
|
||||
|
||||
const loadQuestions = useCallback(async () => {
|
||||
|
||||
@ -55,7 +55,7 @@ export default function SkillTreePage() {
|
||||
|
||||
// 加载分类列表
|
||||
useEffect(() => {
|
||||
fetchCategories({ limit: 100 }).then((res) => setCategories(res.data))
|
||||
fetchCategories({}).then((res) => setCategories(res.data))
|
||||
}, [])
|
||||
|
||||
const loadChapters = useCallback(async () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user