fix: 自适应表格高度并改用中文标签
This commit is contained in:
@@ -1039,6 +1039,15 @@
|
||||
- 备选方案:后端按编号前缀搜索;未采用,与现接口「名称包含」一致。
|
||||
- 影响:网页线改了 `internal/admin/groups.go` 与 admin-api 7.8(issue 允许)。
|
||||
|
||||
### 复审修复 W-06
|
||||
|
||||
- 日期:2026-09-30
|
||||
- 原条款:用户界面规则长列表内部滚动;PRD F17 界面中文与自助注册数;审查 #56。
|
||||
- 实际做法:列表页 `.page-body` 改为 flex 列,筛选不伸缩,表格 `flex-height`。新增 `utils/labels.ts` 译消息/投递状态与原因(原值 title)。运行参数中文标签、键名放 HelpTip。端列表拆成最近上线/离线。概览展示「其中自助注册」,运行时长按天时分。e2e 断言「定时中」。Playwright 双分辨率截图未跑(本波以 Vitest 验收布局类改动)。
|
||||
- 原因:写死 480 高度双滚动;英文枚举;漏自助数。
|
||||
- 备选方案:一次性密码改弹窗;仍放顶部不参与伸缩。
|
||||
- 影响:投递页不再显示 `scheduled` 英文单元格。
|
||||
|
||||
### S1.1 传输层可注入假实现(Go / JS)
|
||||
|
||||
- 相关文档:DEVELOPMENT 第 9 节单元测试要求「用假的 MQTT/HTTP,不要起真实服务器」。
|
||||
|
||||
@@ -127,7 +127,7 @@ test.describe("后台主路径 W4", () => {
|
||||
await nav(page, "投递记录");
|
||||
await expect(page).toHaveURL(/\/messages/);
|
||||
await expect(page.getByText("w4-e2e-scheduled")).toBeVisible();
|
||||
await expect(page.getByRole("cell", { name: "scheduled", exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("cell", { name: "定时中", exact: true })).toBeVisible();
|
||||
|
||||
await nav(page, "API 令牌");
|
||||
await expect(page).toHaveURL(/\/tokens/);
|
||||
|
||||
@@ -328,6 +328,7 @@ export const mockApi = {
|
||||
endpoints_total: endpoints.length,
|
||||
endpoints_online: endpoints.filter((e) => e.online).length,
|
||||
endpoints_disabled: endpoints.filter((e) => !e.enabled).length,
|
||||
endpoints_self: endpoints.filter((e) => e.source === "self").length,
|
||||
groups_total: groups.length,
|
||||
messages_pending: messages.filter((m) => m.state === "dispatched").length,
|
||||
messages_scheduled: messages.filter((m) => m.state === "scheduled").length,
|
||||
|
||||
@@ -34,8 +34,7 @@ export interface Overview {
|
||||
endpoints_total: number;
|
||||
endpoints_online: number;
|
||||
endpoints_disabled: number;
|
||||
/** A3 补充:自助注册端数量;契约示例未列,前端可选展示 */
|
||||
endpoints_self?: number;
|
||||
endpoints_self: number;
|
||||
groups_total: number;
|
||||
messages_pending: number;
|
||||
messages_scheduled: number;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatUptime, zhLabel, messageStateLabel } from "./labels";
|
||||
|
||||
describe("labels", () => {
|
||||
it("消息状态译成中文", () => {
|
||||
expect(zhLabel(messageStateLabel, "scheduled")).toBe("定时中");
|
||||
expect(zhLabel(messageStateLabel, "unknown")).toBe("unknown");
|
||||
});
|
||||
|
||||
it("运行时长按天时分", () => {
|
||||
expect(formatUptime(90_000)).toBe("1 分");
|
||||
expect(formatUptime(3_661_000)).toBe("1 小时 1 分");
|
||||
expect(formatUptime(90_000_000)).toBe("1 天 1 小时 0 分");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/** 后台界面中文标签;原值可放 tooltip。 */
|
||||
|
||||
export const messageStateLabel: Record<string, string> = {
|
||||
scheduled: "定时中",
|
||||
dispatched: "投递中",
|
||||
completed: "已完成",
|
||||
};
|
||||
|
||||
export const destKindLabel: Record<string, string> = {
|
||||
endpoint: "端",
|
||||
group: "群",
|
||||
};
|
||||
|
||||
export const deliveryStateLabel: Record<string, string> = {
|
||||
pending: "待投递",
|
||||
accepted: "已收下",
|
||||
recalled: "已撤回",
|
||||
expired: "已过期",
|
||||
dropped: "已丢弃",
|
||||
rejected: "已拒绝",
|
||||
};
|
||||
|
||||
export const reasonLabel: Record<string, string> = {
|
||||
disabled: "端已停用",
|
||||
expired: "已过期",
|
||||
recalled: "已撤回",
|
||||
dropped: "已丢弃",
|
||||
rejected: "已拒绝",
|
||||
unauthorized: "未授权",
|
||||
not_found: "不存在",
|
||||
};
|
||||
|
||||
export const settingsFieldLabel: Record<string, string> = {
|
||||
listen: "端监听地址",
|
||||
admin_listen: "后台监听地址",
|
||||
session_idle_days: "会话闲置天数",
|
||||
record_retention_days: "记录保留天数",
|
||||
idempotency_hours: "防重小时数",
|
||||
receipt_retention_days: "回执保留天数",
|
||||
sqlite_synchronous: "SQLite 同步模式",
|
||||
};
|
||||
|
||||
export const limitsLabel: Record<string, string> = {
|
||||
max_body_bytes: "正文上限(字节)",
|
||||
max_meta_bytes: "元数据上限(字节)",
|
||||
max_frame_bytes: "帧上限(字节)",
|
||||
max_ttl_seconds: "TTL 上限(秒)",
|
||||
max_schedule_seconds: "定时上限(秒)",
|
||||
max_group_members: "群成员上限",
|
||||
grace_seconds: "宽限秒数",
|
||||
ack_timeout_seconds: "确认超时(秒)",
|
||||
delivery_window: "投递窗口",
|
||||
receipt_window: "回执窗口",
|
||||
requests_per_second: "每秒请求",
|
||||
max_pending_per_sender: "发送方未完成上限",
|
||||
max_pending_per_receiver: "接收方未完成上限",
|
||||
};
|
||||
|
||||
export function zhLabel(map: Record<string, string>, value: string | undefined | null, fallback = "—"): string {
|
||||
if (value == null || value === "") return fallback;
|
||||
return map[value] ?? value;
|
||||
}
|
||||
|
||||
/** 运行时长:天、时、分。 */
|
||||
export function formatUptime(ms: number): string {
|
||||
const total = Math.max(0, Math.floor(ms / 1000));
|
||||
const days = Math.floor(total / 86400);
|
||||
const hours = Math.floor((total % 86400) / 3600);
|
||||
const mins = Math.floor((total % 3600) / 60);
|
||||
const parts: string[] = [];
|
||||
if (days > 0) parts.push(`${days} 天`);
|
||||
if (hours > 0 || days > 0) parts.push(`${hours} 小时`);
|
||||
parts.push(`${mins} 分`);
|
||||
return parts.join(" ");
|
||||
}
|
||||
@@ -187,10 +187,16 @@ const columns = computed<DataTableColumns<Endpoint>>(() => [
|
||||
render: (r) => (r.login_locked ? "是" : "否"),
|
||||
},
|
||||
{
|
||||
title: "最近在线",
|
||||
title: "最近上线",
|
||||
key: "online_since_ms",
|
||||
width: 160,
|
||||
render: (r) => formatLocalMs(r.online ? r.online_since_ms : r.offline_since_ms),
|
||||
render: (r) => formatLocalMs(r.online_since_ms),
|
||||
},
|
||||
{
|
||||
title: "最近离线",
|
||||
key: "offline_since_ms",
|
||||
width: 160,
|
||||
render: (r) => formatLocalMs(r.offline_since_ms),
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
@@ -496,6 +502,7 @@ function onPageSizeChange(s: number) {
|
||||
<div class="page-body">
|
||||
<LoadFailed v-if="loadError" :description="loadError" @retry="load" />
|
||||
<template v-else>
|
||||
<div class="page-filters">
|
||||
<SecretOnceAlert
|
||||
v-if="onceSecret"
|
||||
:title="onceSecret.title"
|
||||
@@ -527,15 +534,17 @@ function onPageSizeChange(s: number) {
|
||||
<HelpTip>admin=后台开通,self=自助注册。</HelpTip>
|
||||
</span>
|
||||
</n-space>
|
||||
</div>
|
||||
<div class="page-table">
|
||||
<n-data-table
|
||||
remote
|
||||
flex-height
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="rows"
|
||||
:row-key="(r: Endpoint) => r.id"
|
||||
:checked-row-keys="checkedKeys"
|
||||
:max-height="480"
|
||||
:scroll-x="1200"
|
||||
:scroll-x="1400"
|
||||
:pagination="{
|
||||
page,
|
||||
pageSize,
|
||||
@@ -547,6 +556,7 @@ function onPageSizeChange(s: number) {
|
||||
}"
|
||||
@update:checked-row-keys="(k) => (checkedKeys = k)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -688,8 +698,17 @@ function onPageSizeChange(s: number) {
|
||||
}
|
||||
.page-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
.page-filters {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.page-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -293,17 +293,20 @@ async function onTransfer(endpointId: string) {
|
||||
<div class="page-body">
|
||||
<LoadFailed v-if="loadError" :description="loadError" @retry="load" />
|
||||
<template v-else>
|
||||
<div class="page-filters">
|
||||
<n-space style="margin-bottom: 12px">
|
||||
<n-input v-model:value="query" placeholder="群名称" style="width: 200px" data-testid="group-query" @keyup.enter="page = 1; load()" />
|
||||
<n-button @click="page = 1; load()">查询</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
<div class="page-table">
|
||||
<n-data-table
|
||||
remote
|
||||
flex-height
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="rows"
|
||||
:row-key="(r: GroupSummary) => r.id"
|
||||
:max-height="480"
|
||||
:pagination="{
|
||||
page,
|
||||
pageSize,
|
||||
@@ -314,6 +317,7 @@ async function onTransfer(endpointId: string) {
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -408,7 +412,17 @@ async function onTransfer(endpointId: string) {
|
||||
}
|
||||
.page-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
.page-filters {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.page-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
NDescriptionsItem,
|
||||
NInput,
|
||||
NModal,
|
||||
NScrollbar,
|
||||
NSelect,
|
||||
NSpace,
|
||||
} from "naive-ui";
|
||||
@@ -18,6 +17,7 @@ import HelpTip from "@/components/HelpTip.vue";
|
||||
import LoadFailed from "@/components/LoadFailed.vue";
|
||||
import { getMessage, listMessages, type MessageDelivery, type MessageDetail, type MessageSummary } from "@/api/admin";
|
||||
import { formatLocalMs } from "@/utils/time";
|
||||
import { destKindLabel, deliveryStateLabel, messageStateLabel, reasonLabel, zhLabel } from "@/utils/labels";
|
||||
|
||||
const loading = ref(false);
|
||||
const loadError = ref("");
|
||||
@@ -81,15 +81,15 @@ const columns = computed<DataTableColumns<MessageSummary>>(() => [
|
||||
title: "目标",
|
||||
key: "dest",
|
||||
width: 140,
|
||||
render: (r) => `${r.dest_kind}:${r.dest_id}`,
|
||||
render: (r) => `${zhLabel(destKindLabel, r.dest_kind)}:${r.dest_id}`,
|
||||
},
|
||||
{ title: "状态", key: "state", width: 100 },
|
||||
{ title: "状态", key: "state", width: 100, render: (r) => h("span", { title: r.state }, zhLabel(messageStateLabel, r.state)) },
|
||||
{
|
||||
title: "原因",
|
||||
key: "reason",
|
||||
width: 140,
|
||||
ellipsis: { tooltip: true },
|
||||
render: (r) => r.reason || "—",
|
||||
render: (r) => h("span", { title: r.reason || "" }, r.reason ? zhLabel(reasonLabel, r.reason) : "—"),
|
||||
},
|
||||
{
|
||||
title: "投递统计",
|
||||
@@ -122,11 +122,11 @@ const columns = computed<DataTableColumns<MessageSummary>>(() => [
|
||||
|
||||
const deliveryColumns = computed<DataTableColumns<MessageDelivery>>(() => [
|
||||
{ title: "接收端", key: "endpoint_id" },
|
||||
{ title: "状态", key: "state" },
|
||||
{ title: "状态", key: "state", render: (r) => h("span", { title: r.state }, zhLabel(deliveryStateLabel, r.state)) },
|
||||
{
|
||||
title: "原因",
|
||||
key: "reason",
|
||||
render: (r) => r.reason || "—",
|
||||
render: (r) => h("span", { title: r.reason || "" }, r.reason ? zhLabel(reasonLabel, r.reason) : "—"),
|
||||
},
|
||||
{ title: "次数", key: "attempts", width: 60 },
|
||||
{
|
||||
@@ -168,6 +168,7 @@ async function loadMoreDeliveries() {
|
||||
<div class="page-body">
|
||||
<LoadFailed v-if="loadError" :description="loadError" @retry="load" />
|
||||
<template v-else>
|
||||
<div class="page-filters">
|
||||
<n-space style="margin-bottom: 12px" align="center">
|
||||
<n-input v-model:value="filters.sender_id" placeholder="发送方" style="width: 120px" />
|
||||
<n-input v-model:value="filters.endpoint_id" placeholder="接收端" style="width: 120px" />
|
||||
@@ -192,14 +193,16 @@ async function loadMoreDeliveries() {
|
||||
<HelpTip>页面与网络响应均不得出现消息正文。</HelpTip>
|
||||
</span>
|
||||
</n-space>
|
||||
</div>
|
||||
<div class="page-table">
|
||||
<n-data-table
|
||||
remote
|
||||
flex-height
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="rows"
|
||||
:row-key="(r: MessageSummary) => r.seq"
|
||||
:max-height="480"
|
||||
:scroll-x="1100"
|
||||
:scroll-x="1400"
|
||||
:pagination="{
|
||||
page,
|
||||
pageSize,
|
||||
@@ -210,18 +213,19 @@ async function loadMoreDeliveries() {
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<n-modal v-model:show="detailOpen" preset="card" title="投递详情" style="width: 720px">
|
||||
<n-scrollbar v-if="detail" style="max-height: 420px">
|
||||
<template v-if="detail">
|
||||
<n-descriptions label-placement="left" :column="2" size="small" bordered style="margin-bottom: 12px">
|
||||
<n-descriptions-item label="序号">{{ detail.seq }}</n-descriptions-item>
|
||||
<n-descriptions-item label="消息号">{{ detail.id }}</n-descriptions-item>
|
||||
<n-descriptions-item label="发送方">{{ detail.sender_id }}</n-descriptions-item>
|
||||
<n-descriptions-item label="目标">{{ detail.dest_kind }}:{{ detail.dest_id }}</n-descriptions-item>
|
||||
<n-descriptions-item label="状态">{{ detail.state }}</n-descriptions-item>
|
||||
<n-descriptions-item label="原因">{{ detail.reason || "—" }}</n-descriptions-item>
|
||||
<n-descriptions-item label="目标">{{ destKindLabel[detail.dest_kind] || detail.dest_kind }}:{{ detail.dest_id }}</n-descriptions-item>
|
||||
<n-descriptions-item label="状态"><span :title="detail.state">{{ zhLabel(messageStateLabel, detail.state) }}</span></n-descriptions-item>
|
||||
<n-descriptions-item label="原因"><span :title="detail.reason">{{ detail.reason ? zhLabel(reasonLabel, detail.reason) : "—" }}</span></n-descriptions-item>
|
||||
<n-descriptions-item label="类型">{{ detail.content_type }}</n-descriptions-item>
|
||||
<n-descriptions-item label="创建">{{ formatLocalMs(detail.created_at_ms) }}</n-descriptions-item>
|
||||
<n-descriptions-item label="发送">{{ formatLocalMs(detail.send_at_ms) }}</n-descriptions-item>
|
||||
@@ -236,7 +240,7 @@ async function loadMoreDeliveries() {
|
||||
>
|
||||
加载更多
|
||||
</n-button>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="detailOpen = false">关闭</n-button>
|
||||
@@ -255,7 +259,17 @@ async function loadMoreDeliveries() {
|
||||
}
|
||||
.page-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
.page-filters {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.page-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,7 @@ function wrap(Comp: object) {
|
||||
});
|
||||
}
|
||||
|
||||
describe("OverviewView 加载失败", () => {
|
||||
describe("OverviewView", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
mockApi._setSession();
|
||||
@@ -68,7 +68,18 @@ describe("OverviewView 加载失败", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(w.text()).toContain("0.1.0");
|
||||
expect(w.text()).toContain("其中自助注册");
|
||||
expect(w.text()).not.toContain("加载失败");
|
||||
w.unmount();
|
||||
});
|
||||
|
||||
it("成功加载后显示自助注册数", async () => {
|
||||
const w = mount(wrap(OverviewView), {
|
||||
global: { plugins: [createPinia()] },
|
||||
attachTo: document.body,
|
||||
});
|
||||
await flushPromises();
|
||||
expect(w.text()).toContain("其中自助注册");
|
||||
w.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import HelpTip from "@/components/HelpTip.vue";
|
||||
import LoadFailed from "@/components/LoadFailed.vue";
|
||||
import { fetchOverview, type Overview } from "@/api/admin";
|
||||
import { formatLocalMs } from "@/utils/time";
|
||||
import { formatUptime } from "@/utils/labels";
|
||||
|
||||
const loading = ref(true);
|
||||
const loadError = ref("");
|
||||
@@ -41,9 +42,10 @@ onMounted(() => {
|
||||
运行时长
|
||||
<HelpTip>自进程启动起的毫秒数,按本地时区换算展示起点附近时间差。</HelpTip>
|
||||
</template>
|
||||
{{ Math.floor(data.uptime_ms / 1000) }} 秒(约 {{ formatLocalMs(Date.now() - data.uptime_ms) }} 起)
|
||||
{{ formatUptime(data.uptime_ms) }}(约 {{ formatLocalMs(Date.now() - data.uptime_ms) }} 起)
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="端总数">{{ data.endpoints_total }}</n-descriptions-item>
|
||||
<n-descriptions-item label="其中自助注册">{{ data.endpoints_self }}</n-descriptions-item>
|
||||
<n-descriptions-item label="在线端">{{ data.endpoints_online }}</n-descriptions-item>
|
||||
<n-descriptions-item label="已停用">{{ data.endpoints_disabled }}</n-descriptions-item>
|
||||
<n-descriptions-item label="群数量">{{ data.groups_total }}</n-descriptions-item>
|
||||
|
||||
@@ -21,6 +21,7 @@ import LoadFailed from "@/components/LoadFailed.vue";
|
||||
import { changePassword, getSettings, type RuntimeSettings } from "@/api/admin";
|
||||
import { ApiError } from "@/api/http";
|
||||
import { message } from "@/utils/notify";
|
||||
import { limitsLabel } from "@/utils/labels";
|
||||
|
||||
const loading = ref(true);
|
||||
const loadError = ref("");
|
||||
@@ -131,17 +132,42 @@ async function submitPassword() {
|
||||
<LoadFailed v-if="loadError && !settings" :description="loadError" @retry="load" />
|
||||
<template v-else-if="settings">
|
||||
<n-descriptions label-placement="left" :column="2" bordered size="small" style="margin-bottom: 16px">
|
||||
<n-descriptions-item label="listen">{{ settings.listen }}</n-descriptions-item>
|
||||
<n-descriptions-item label="admin_listen">{{ settings.admin_listen || "(空,与端共用)" }}</n-descriptions-item>
|
||||
<n-descriptions-item label="会话闲置天数">{{ settings.session_idle_days }}</n-descriptions-item>
|
||||
<n-descriptions-item label="记录保留天数">{{ settings.record_retention_days }}</n-descriptions-item>
|
||||
<n-descriptions-item label="防重小时数">{{ settings.idempotency_hours }}</n-descriptions-item>
|
||||
<n-descriptions-item label="回执保留天数">{{ settings.receipt_retention_days }}</n-descriptions-item>
|
||||
<n-descriptions-item label="SQLite synchronous">{{ settings.sqlite_synchronous }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>端监听地址 <HelpTip>listen</HelpTip></template>
|
||||
{{ settings.listen }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>后台监听地址 <HelpTip>admin_listen</HelpTip></template>
|
||||
{{ settings.admin_listen || "(空,与端共用)" }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>会话闲置天数 <HelpTip>session_idle_days</HelpTip></template>
|
||||
{{ settings.session_idle_days }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>记录保留天数 <HelpTip>record_retention_days</HelpTip></template>
|
||||
{{ settings.record_retention_days }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>防重小时数 <HelpTip>idempotency_hours</HelpTip></template>
|
||||
{{ settings.idempotency_hours }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>回执保留天数 <HelpTip>receipt_retention_days</HelpTip></template>
|
||||
{{ settings.receipt_retention_days }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>SQLite 同步模式 <HelpTip>sqlite_synchronous</HelpTip></template>
|
||||
{{ settings.sqlite_synchronous }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
<n-text strong style="display: block; margin-bottom: 8px">limits</n-text>
|
||||
<n-text strong style="display: block; margin-bottom: 8px">限额</n-text>
|
||||
<n-descriptions label-placement="left" :column="2" bordered size="small">
|
||||
<n-descriptions-item v-for="(v, k) in settings.limits" :key="k" :label="String(k)">
|
||||
<n-descriptions-item v-for="(v, k) in settings.limits" :key="k">
|
||||
<template #label>
|
||||
{{ limitsLabel[String(k)] || k }}
|
||||
<HelpTip>{{ k }}</HelpTip>
|
||||
</template>
|
||||
{{ v }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
@@ -143,6 +143,7 @@ function onDelete(r: ApiToken) {
|
||||
<div class="page-body">
|
||||
<LoadFailed v-if="loadError" :description="loadError" @retry="load" />
|
||||
<template v-else>
|
||||
<div class="page-filters">
|
||||
<SecretOnceAlert
|
||||
v-if="onceToken"
|
||||
title="令牌只显示一次"
|
||||
@@ -151,13 +152,16 @@ function onDelete(r: ApiToken) {
|
||||
data-testid="token-once"
|
||||
@dismiss="onceToken = null"
|
||||
/>
|
||||
</div>
|
||||
<div class="page-table">
|
||||
<n-data-table
|
||||
flex-height
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="rows"
|
||||
:row-key="(r: ApiToken) => r.id"
|
||||
:max-height="480"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -188,7 +192,17 @@ function onDelete(r: ApiToken) {
|
||||
}
|
||||
.page-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
.page-filters {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.page-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user