feat(admin): 后台列表分页 + 积分显示格式化

9 张 admin 列表表格接服务端分页(共享 Pager 组件,10条/页,alwaysShow 单页也显示分页条);
积分数值统一 pts() 格式化(去除 DecimalField 序列化的多余小数位,整数不再显示 .0000)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-07-07 10:16:40 +08:00
co-authored by Claude Sonnet 5
parent bf20de956c
commit 299b2c9ecd
5 changed files with 51 additions and 19 deletions
@@ -1,11 +1,15 @@
import { useCallback, useEffect, useState } from "react";
import { Server, X } from "lucide-react";
import { adminApi } from "../../api";
import { Pager } from "../../components/pager";
import { IconKitSvg } from "../../components/IconKitSvg";
import type { AdminModel, AdminProvider } from "../../types";
import { pts } from "../stage-config";
type Notify = (type: "success" | "error" | "info", text: string) => void;
const PAGE_SIZE = 10;
const CAPABILITIES = ["text", "image", "video", "vision", "audio", "export"];
function statusPill(active: boolean) {
@@ -20,6 +24,7 @@ const EMPTY_MODEL = { id: "", provider: "", name: "", display_name: "", capabili
export function AdminModelsPage({ notify }: { notify: Notify }) {
const [providers, setProviders] = useState<AdminProvider[]>([]);
const [models, setModels] = useState<AdminModel[]>([]);
const [modelPage, setModelPage] = useState(1);
const [loading, setLoading] = useState(true);
const [provModal, setProvModal] = useState<typeof EMPTY_PROVIDER | null>(null);
const [modelModal, setModelModal] = useState<typeof EMPTY_MODEL | null>(null);
@@ -152,14 +157,14 @@ export function AdminModelsPage({ notify }: { notify: Notify }) {
<div className="admin-detail-subhead" style={{ marginTop: 28 }}>模型</div>
<div className="admin-table-wrap">
<table className="t admin-table">
<thead><tr><th>供应商</th><th>模型</th><th>能力</th><th>单价</th><th>状态</th><th>默认</th><th className="col-actions">操作</th></tr></thead>
<thead><tr><th>供应商</th><th>模型</th><th>能力</th><th>单价(积分/次)</th><th>状态</th><th>默认</th><th className="col-actions">操作</th></tr></thead>
<tbody>
{models.map((m) => (
{models.slice((modelPage - 1) * PAGE_SIZE, modelPage * PAGE_SIZE).map((m) => (
<tr key={m.id}>
<td className="mono">{m.provider_name}</td>
<td>{m.display_name || m.name}</td>
<td><span className="pill neutral"><span className="dot" />{m.capability}</span></td>
<td className="num mono">¥{m.unit_price}</td>
<td className="num mono">{pts(m.unit_price)} 积分</td>
<td>{statusPill(m.status === "active")}</td>
<td>{m.is_default ? <span className="pill info"><span className="dot" />默认</span> : <span className="muted">—</span>}</td>
<td className="col-actions">
@@ -172,6 +177,7 @@ export function AdminModelsPage({ notify }: { notify: Notify }) {
))}
</tbody>
</table>
<Pager page={modelPage} total={models.length} pageSize={PAGE_SIZE} onChange={setModelPage} alwaysShow />
</div>
</>
)}
@@ -246,7 +252,7 @@ export function AdminModelsPage({ notify }: { notify: Notify }) {
</div>
<div className="field-row">
<div className="field">
<label className="field-label">单价</label>
<label className="field-label">单价(积分/次)</label>
<input className="input" type="text" placeholder="0" value={modelModal.unit_price} onChange={(e) => setModelModal((m) => m && ({ ...m, unit_price: e.target.value }))} />
</div>
<div className="field">