[feat]:[FL-153][系统参数管理页面一致性优化]
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -23,12 +23,14 @@ router = APIRouter(prefix="/admin/system-params", tags=["admin-system-params"])
|
||||
|
||||
@router.get("", response_model=SystemParamListResponse)
|
||||
def get_system_params(
|
||||
limit: int = Query(default=50, ge=1, le=200),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
keyword: str | None = Query(default=None),
|
||||
status_filter: str | None = Query(default=None, alias="status"),
|
||||
_: CurrentUser = Depends(require_any_permission("system_param.read", "system_param.manage")),
|
||||
db: Session = Depends(get_db),
|
||||
) -> SystemParamListResponse:
|
||||
return list_system_params(db, keyword=keyword, status_filter=status_filter)
|
||||
return list_system_params(db, limit=limit, offset=offset, keyword=keyword, status_filter=status_filter)
|
||||
|
||||
|
||||
@router.post("", response_model=SystemParamSummary)
|
||||
|
||||
@@ -46,6 +46,8 @@ def serialize_system_param(item: SystemParam) -> SystemParamSummary:
|
||||
def list_system_params(
|
||||
db: Session,
|
||||
*,
|
||||
limit: int,
|
||||
offset: int,
|
||||
keyword: str | None,
|
||||
status_filter: str | None,
|
||||
) -> SystemParamListResponse:
|
||||
@@ -82,7 +84,15 @@ def list_system_params(
|
||||
total_stmt = total_stmt.where(SystemParam.status == status_filter)
|
||||
|
||||
total = db.scalar(total_stmt) or 0
|
||||
items = db.execute(stmt.order_by(SystemParam.updated_at.desc(), SystemParam.id.desc())).scalars().all()
|
||||
items = (
|
||||
db.execute(
|
||||
stmt.order_by(SystemParam.updated_at.desc(), SystemParam.id.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
return SystemParamListResponse(items=[serialize_system_param(item) for item in items], total=total)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
os.environ.setdefault("DATABASE_URL", "sqlite+pysqlite:///:memory:")
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from api.app import models # noqa: F401
|
||||
from api.app.core.database import Base
|
||||
from api.app.models.system_param import SystemParam
|
||||
from api.app.services.system_param_service import list_system_params
|
||||
|
||||
|
||||
class SystemParamServiceTest(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.engine = create_engine(
|
||||
"sqlite+pysqlite://",
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
self.SessionLocal = sessionmaker(
|
||||
bind=self.engine,
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
expire_on_commit=False,
|
||||
)
|
||||
Base.metadata.create_all(bind=self.engine)
|
||||
self.session = self.SessionLocal()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.session.close()
|
||||
Base.metadata.drop_all(bind=self.engine)
|
||||
self.engine.dispose()
|
||||
|
||||
def test_list_system_params_applies_limit_and_offset(self) -> None:
|
||||
for index in range(5):
|
||||
self.session.add(
|
||||
SystemParam(
|
||||
param_key=f"param_{index}",
|
||||
param_name=f"Param {index}",
|
||||
param_value=f"value-{index}",
|
||||
description=f"description-{index}",
|
||||
status="enabled",
|
||||
)
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
page = list_system_params(
|
||||
self.session,
|
||||
limit=2,
|
||||
offset=1,
|
||||
keyword=None,
|
||||
status_filter=None,
|
||||
)
|
||||
|
||||
self.assertEqual(page.total, 5)
|
||||
self.assertEqual(len(page.items), 2)
|
||||
|
||||
def test_list_system_params_filters_before_paginating(self) -> None:
|
||||
self.session.add_all(
|
||||
[
|
||||
SystemParam(param_key="enabled_first", param_name="Enabled First", status="enabled"),
|
||||
SystemParam(param_key="disabled_first", param_name="Disabled First", status="disabled"),
|
||||
SystemParam(param_key="enabled_second", param_name="Enabled Second", status="enabled"),
|
||||
]
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
page = list_system_params(
|
||||
self.session,
|
||||
limit=1,
|
||||
offset=0,
|
||||
keyword="enabled",
|
||||
status_filter="enabled",
|
||||
)
|
||||
|
||||
self.assertEqual(page.total, 2)
|
||||
self.assertEqual(len(page.items), 1)
|
||||
self.assertEqual(page.items[0].status, "enabled")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -78,3 +78,28 @@
|
||||
- 风险与关注点:
|
||||
- 角色编码重复检查依赖现有角色列表 keyword 查询做前端预检查;服务端创建接口和数据库唯一约束仍是最终一致性保护。
|
||||
- 改动仅影响角色管理页前端,不改变后端接口、schema 或权限语义。
|
||||
|
||||
# Work Log - 系统参数管理页一致性优化(FL-153)
|
||||
|
||||
- 背景:
|
||||
- 系统参数管理页需要对齐用户管理页的列表布局、权限呈现、移动卡片、反馈校验与分页交互规范。
|
||||
|
||||
- 本次处理:
|
||||
- 系统参数页新建入口改为仅 `system_param.manage` 权限可见,保持只读权限下的页面呈现与实际操作权限一致。
|
||||
- 表格配置对齐用户管理页:`tableLayout="fixed"`、仅纵向滚动、空态属性顺序、加载态和分页最小 total 处理保持一致。
|
||||
- 移动卡片状态 Tag 文案与颜色对齐用户管理页,并补齐 card view 数据累积的 `requestAnimationFrame` 时序处理。
|
||||
- 新建/编辑弹窗补齐 `autoComplete="off"`、字段长度规则、参数键防抖重复检查和提交前重复检查。
|
||||
- 后端系统参数列表接口补齐 `limit/offset` 查询参数并在 service 层实际分页,修复前端分页参数此前未生效的问题。
|
||||
- 新增 `api/tests/test_system_param_service.py` 覆盖系统参数列表分页与筛选后分页行为。
|
||||
|
||||
- 验证:
|
||||
- 基线:`npm --workspace web exec eslint src/app/admin/users/page.tsx src/app/admin/system-params/page.tsx` 通过,仅用户页存在 1 条既有 unused eslint-disable warning。
|
||||
- 基线:`npm --workspace web exec tsc --noEmit` 通过。
|
||||
- 修改后:`npm --workspace web exec eslint src/app/admin/system-params/page.tsx --max-warnings=0` 通过。
|
||||
- 修改后:`npm --workspace web exec tsc --noEmit` 通过。
|
||||
- 修改后:`python3 -m py_compile api/app/api/v1/system_params.py api/app/services/system_param_service.py api/tests/test_system_param_service.py` 通过。
|
||||
- 修改后:`UV_PYTHON_INSTALL_DIR=/tmp/fquiz-uv-python uv run --cache-dir /tmp/fquiz-uv-cache --python 3.11 --with fastapi --with pydantic-settings --with sqlalchemy --with PyJWT --with argon2-cffi --with email-validator --with bcrypt -m unittest api.tests.test_system_param_service` 通过。
|
||||
|
||||
- 风险与关注点:
|
||||
- 改动涉及系统参数列表接口分页契约,但不改变请求/响应字段结构、权限码或 CRUD 语义。
|
||||
- 当前本机 `python3` 为 3.7.9,不满足 `api/pyproject.toml` 的 `requires-python >=3.10`;后端单测使用 `uv` 管理的 Python 3.11 环境验证。
|
||||
|
||||
@@ -64,8 +64,8 @@ const PARAM_TABLE_VIEWPORT_GAP = 40;
|
||||
const PARAM_TABLE_FALLBACK_RESERVE = 220;
|
||||
|
||||
function paramStatusLabel(status: SystemParamSummary["status"]): string {
|
||||
if (status === "enabled") return "已启用";
|
||||
if (status === "disabled") return "已禁用";
|
||||
if (status === "enabled") return "启用";
|
||||
if (status === "disabled") return "禁用";
|
||||
return status || "-";
|
||||
}
|
||||
|
||||
@@ -92,6 +92,8 @@ export default function AdminSystemParamsPage() {
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const pageCardRef = useRef<HTMLDivElement | null>(null);
|
||||
const keywordDebounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const paramKeyCheckTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [paramKeyValidationError, setParamKeyValidationError] = useState("");
|
||||
|
||||
const canRead = hasPermission("system_param.read") || hasPermission("system_param.manage");
|
||||
const canManage = hasPermission("system_param.manage");
|
||||
@@ -133,18 +135,24 @@ export default function AdminSystemParamsPage() {
|
||||
});
|
||||
}, [queryClient]);
|
||||
|
||||
useTopicSubscription("admin.system-params", useCallback(() => {
|
||||
useTopicSubscription(
|
||||
"admin.system-params",
|
||||
useCallback(() => {
|
||||
if (!user || !canRead) return;
|
||||
void refreshList();
|
||||
}, [refreshList]));
|
||||
}, [canRead, refreshList, user]),
|
||||
);
|
||||
|
||||
const resetForm = useCallback(() => {
|
||||
setEditingId(null);
|
||||
setParamKeyValidationError("");
|
||||
formApi.setFieldsValue(EMPTY_FORM);
|
||||
}, [formApi]);
|
||||
|
||||
const startCreate = useCallback(() => {
|
||||
setError("");
|
||||
setSuccess("");
|
||||
setParamKeyValidationError("");
|
||||
resetForm();
|
||||
setEditorOpen(true);
|
||||
}, [resetForm]);
|
||||
@@ -152,6 +160,7 @@ export default function AdminSystemParamsPage() {
|
||||
const startEdit = useCallback((item: SystemParamSummary) => {
|
||||
setError("");
|
||||
setSuccess("");
|
||||
setParamKeyValidationError("");
|
||||
setEditingId(item.id);
|
||||
formApi.setFieldsValue({
|
||||
param_key: item.param_key,
|
||||
@@ -163,6 +172,60 @@ export default function AdminSystemParamsPage() {
|
||||
setEditorOpen(true);
|
||||
}, [formApi]);
|
||||
|
||||
const validateParamKeyFormat = (paramKey: string): string | null => {
|
||||
const trimmedKey = paramKey.trim();
|
||||
if (!trimmedKey) return null;
|
||||
if (trimmedKey.length < 2) return "参数键至少 2 位";
|
||||
if (trimmedKey.length > 128) return "参数键不能超过 128 位";
|
||||
return null;
|
||||
};
|
||||
|
||||
const checkParamKeyAvailability = async (paramKey: string) => {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
keyword: paramKey,
|
||||
limit: "200",
|
||||
offset: "0",
|
||||
});
|
||||
const response = await fetchWithAuth(`/api/v1/admin/system-params?${params.toString()}`);
|
||||
if (!response.ok) {
|
||||
return { available: false, message: "检查失败" };
|
||||
}
|
||||
const payload = (await response.json()) as SystemParamListResponse;
|
||||
const normalizedKey = paramKey.trim().toLowerCase();
|
||||
const exists = payload.items.some((item) => item.param_key.trim().toLowerCase() === normalizedKey);
|
||||
return {
|
||||
available: !exists,
|
||||
message: exists ? "参数键已存在,请更换后重试" : "参数键可用",
|
||||
};
|
||||
} catch {
|
||||
return { available: false, message: "检查失败" };
|
||||
}
|
||||
};
|
||||
|
||||
const handleParamKeyChange = (value: string) => {
|
||||
if (paramKeyCheckTimeoutRef.current) {
|
||||
clearTimeout(paramKeyCheckTimeoutRef.current);
|
||||
}
|
||||
|
||||
const formatError = validateParamKeyFormat(value);
|
||||
if (formatError) {
|
||||
setParamKeyValidationError(formatError);
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim();
|
||||
if (!trimmedValue || editingId !== null) {
|
||||
setParamKeyValidationError("");
|
||||
return;
|
||||
}
|
||||
|
||||
paramKeyCheckTimeoutRef.current = setTimeout(async () => {
|
||||
const result = await checkParamKeyAvailability(trimmedValue);
|
||||
setParamKeyValidationError(result.available ? "" : result.message);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const closeEditor = useCallback(() => {
|
||||
setEditorOpen(false);
|
||||
resetForm();
|
||||
@@ -179,12 +242,25 @@ export default function AdminSystemParamsPage() {
|
||||
throw new Error("参数键和参数名称不能为空");
|
||||
}
|
||||
|
||||
const paramKey = values.param_key.trim();
|
||||
const paramKeyFormatError = validateParamKeyFormat(paramKey);
|
||||
if (paramKeyFormatError) {
|
||||
setParamKeyValidationError(paramKeyFormatError);
|
||||
throw new Error(paramKeyFormatError);
|
||||
}
|
||||
|
||||
if (editingId === null) {
|
||||
const availabilityCheck = await checkParamKeyAvailability(paramKey);
|
||||
if (!availabilityCheck.available) {
|
||||
setParamKeyValidationError(availabilityCheck.message);
|
||||
throw new Error(availabilityCheck.message);
|
||||
}
|
||||
|
||||
const response = await fetchWithAuth("/api/v1/admin/system-params", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
param_key: values.param_key.trim(),
|
||||
param_key: paramKey,
|
||||
param_name: values.param_name.trim(),
|
||||
param_value: values.param_value,
|
||||
description: values.description,
|
||||
@@ -212,6 +288,10 @@ export default function AdminSystemParamsPage() {
|
||||
}
|
||||
return "updated" as const;
|
||||
},
|
||||
onMutate: () => {
|
||||
setError("");
|
||||
setSuccess("");
|
||||
},
|
||||
onSuccess: async (mode) => {
|
||||
setError("");
|
||||
setSuccess(mode === "created" ? "系统参数已创建" : "系统参数已更新");
|
||||
@@ -274,7 +354,15 @@ export default function AdminSystemParamsPage() {
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const items = useMemo(() => listQuery.data?.items ?? [], [listQuery.data?.items]);
|
||||
const rawItems = useMemo(() => listQuery.data?.items ?? [], [listQuery.data?.items]);
|
||||
const items = useMemo(() => {
|
||||
const total = listQuery.data?.total ?? 0;
|
||||
if (rawItems.length > paginationPageSize && rawItems.length === total) {
|
||||
const start = (paginationCurrent - 1) * paginationPageSize;
|
||||
return rawItems.slice(start, start + paginationPageSize);
|
||||
}
|
||||
return rawItems;
|
||||
}, [listQuery.data?.total, paginationCurrent, paginationPageSize, rawItems]);
|
||||
const listError = listQuery.error instanceof Error ? listQuery.error.message : "";
|
||||
|
||||
useToastFeedback({
|
||||
@@ -284,9 +372,12 @@ export default function AdminSystemParamsPage() {
|
||||
clearSuccess: () => setSuccess(""),
|
||||
});
|
||||
|
||||
// Accumulate loaded params for card view
|
||||
useEffect(() => {
|
||||
if (viewMode === "card" && !listQuery.isLoading) {
|
||||
if (viewMode !== "card" || listQuery.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frameId = window.requestAnimationFrame(() => {
|
||||
if (cardViewPage === 1) {
|
||||
setAllLoadedParams(() => items);
|
||||
} else {
|
||||
@@ -300,7 +391,11 @@ export default function AdminSystemParamsPage() {
|
||||
});
|
||||
}
|
||||
setIsLoadingMore(() => false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(frameId);
|
||||
};
|
||||
}, [items, listQuery.isLoading, viewMode, cardViewPage]);
|
||||
|
||||
// Handle infinite scroll for card view
|
||||
@@ -336,17 +431,14 @@ export default function AdminSystemParamsPage() {
|
||||
return () => cardBody.removeEventListener("scroll", handleScroll);
|
||||
}, [viewMode, isLoadingMore, listQuery.isLoading, listQuery.data?.total, allLoadedParams.length]);
|
||||
|
||||
// Reset card view state when filters change
|
||||
useEffect(() => {
|
||||
setCardViewPage(() => 1);
|
||||
setAllLoadedParams(() => []);
|
||||
}, [statusFilter, trimmedKeyword]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (keywordDebounceTimeoutRef.current) {
|
||||
clearTimeout(keywordDebounceTimeoutRef.current);
|
||||
}
|
||||
if (paramKeyCheckTimeoutRef.current) {
|
||||
clearTimeout(paramKeyCheckTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -389,7 +481,7 @@ export default function AdminSystemParamsPage() {
|
||||
title={
|
||||
<Space className="min-w-0" size={8}>
|
||||
<Typography.Text strong>{param.param_name}</Typography.Text>
|
||||
<Tag color={param.status === "enabled" ? "success" : "default"} bordered={false}>
|
||||
<Tag color={param.status === "enabled" ? "green" : "default"}>
|
||||
{paramStatusLabel(param.status)}
|
||||
</Tag>
|
||||
</Space>
|
||||
@@ -449,20 +541,20 @@ export default function AdminSystemParamsPage() {
|
||||
title: "ID",
|
||||
dataIndex: "id",
|
||||
key: "id",
|
||||
width: 110,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: "参数键",
|
||||
dataIndex: "param_key",
|
||||
key: "param_key",
|
||||
width: 240,
|
||||
width: 180,
|
||||
render: (value: string) => <span className="font-mono text-xs">{value}</span>,
|
||||
},
|
||||
{
|
||||
title: "参数名称",
|
||||
dataIndex: "param_name",
|
||||
key: "param_name",
|
||||
width: 200,
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: "参数值",
|
||||
@@ -478,7 +570,7 @@ export default function AdminSystemParamsPage() {
|
||||
key: "status",
|
||||
width: 120,
|
||||
render: (value: SystemParamSummary["status"]) => (
|
||||
<Tag color={value === "enabled" ? "success" : "default"}>
|
||||
<Tag color={value === "enabled" ? "green" : "default"}>
|
||||
{paramStatusLabel(value)}
|
||||
</Tag>
|
||||
),
|
||||
@@ -496,7 +588,6 @@ export default function AdminSystemParamsPage() {
|
||||
baseColumns.push({
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
fixed: "right",
|
||||
width: 180,
|
||||
render: (_, record) => {
|
||||
const deleteLoading = deletingId === record.id;
|
||||
@@ -648,14 +739,14 @@ export default function AdminSystemParamsPage() {
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<AntCard
|
||||
ref={viewMode === "card" ? pageCardRef : undefined}
|
||||
ref={pageCardRef}
|
||||
className="admin-system-params-page-card"
|
||||
title="系统参数管理"
|
||||
extra={(
|
||||
extra={canManage ? (
|
||||
<Button type="primary" onClick={startCreate}>
|
||||
新建参数
|
||||
</Button>
|
||||
)}
|
||||
) : null}
|
||||
>
|
||||
{viewMode === "card" ? (
|
||||
<Form layout="vertical" style={{ marginBottom: 16 }}>
|
||||
@@ -706,14 +797,14 @@ export default function AdminSystemParamsPage() {
|
||||
>
|
||||
<Table<SystemParamSummary>
|
||||
rowKey="id"
|
||||
loading={listQuery.isFetching}
|
||||
dataSource={items}
|
||||
columns={columns}
|
||||
scroll={{ x: 1120, y: tableScrollY }}
|
||||
loading={listQuery.isLoading}
|
||||
tableLayout="fixed"
|
||||
pagination={{
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
total: listQuery.data?.total ?? 0,
|
||||
total: Math.max(listQuery.data?.total ?? 0, 1),
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
showTotal: () => `共 ${listQuery.data?.total ?? 0} 条`,
|
||||
@@ -723,8 +814,14 @@ export default function AdminSystemParamsPage() {
|
||||
setPagination({ current: page, pageSize });
|
||||
},
|
||||
}}
|
||||
scroll={{ y: tableScrollY }}
|
||||
locale={{
|
||||
emptyText: <Empty description="未找到符合筛选条件的系统参数。" image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||
emptyText: (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description="未找到符合筛选条件的系统参数。"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -736,7 +833,10 @@ export default function AdminSystemParamsPage() {
|
||||
</div>
|
||||
) : allLoadedParams.length === 0 ? (
|
||||
<div className="admin-system-params-card-view-state">
|
||||
<Empty description="未找到符合筛选条件的系统参数。" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description="未找到符合筛选条件的系统参数。"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="admin-system-params-card-view-content">
|
||||
@@ -767,7 +867,7 @@ export default function AdminSystemParamsPage() {
|
||||
|
||||
{canManage && (
|
||||
<Modal
|
||||
title={editingId === null ? "新建系统参数" : `编辑系统参数:${formApi.getFieldValue('param_name')}(ID: ${editingId})`}
|
||||
title={editingId === null ? "新建系统参数" : `编辑系统参数:${formApi.getFieldValue("param_name")}(ID: ${editingId})`}
|
||||
open={editorOpen}
|
||||
onCancel={closeEditor}
|
||||
onOk={() => formApi.submit()}
|
||||
@@ -776,29 +876,59 @@ export default function AdminSystemParamsPage() {
|
||||
confirmLoading={saveMutation.isPending}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form<FormState> form={formApi} layout="vertical" initialValues={EMPTY_FORM} onFinish={() => saveMutation.mutate()}>
|
||||
<Form<FormState>
|
||||
form={formApi}
|
||||
layout="vertical"
|
||||
initialValues={EMPTY_FORM}
|
||||
onFinish={() => saveMutation.mutate()}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Form.Item<FormState>
|
||||
label="参数键"
|
||||
name="param_key"
|
||||
rules={[{ required: true, message: "请输入参数键" }]}
|
||||
validateStatus={paramKeyValidationError ? "error" : ""}
|
||||
help={paramKeyValidationError}
|
||||
rules={[
|
||||
{ required: true, message: "请输入参数键" },
|
||||
{ min: 2, message: "参数键至少 2 位" },
|
||||
{ max: 128, message: "参数键不能超过 128 位" },
|
||||
]}
|
||||
>
|
||||
<Input disabled={editingId !== null} placeholder="请输入参数键" />
|
||||
<Input
|
||||
disabled={editingId !== null}
|
||||
placeholder="请输入参数键"
|
||||
onChange={(event) => handleParamKeyChange(event.target.value)}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FormState>
|
||||
label="参数名称"
|
||||
name="param_name"
|
||||
rules={[{ required: true, message: "请输入参数名称" }]}
|
||||
rules={[
|
||||
{ required: true, message: "请输入参数名称" },
|
||||
{ min: 2, message: "参数名称至少 2 位" },
|
||||
{ max: 128, message: "参数名称不能超过 128 位" },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入参数名称" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FormState> className="md:col-span-2" label="参数值" name="param_value">
|
||||
<Form.Item<FormState>
|
||||
className="md:col-span-2"
|
||||
label="参数值"
|
||||
name="param_value"
|
||||
rules={[{ max: 20000, message: "参数值不能超过 20000 位" }]}
|
||||
>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FormState> className="md:col-span-2" label="说明" name="description">
|
||||
<Form.Item<FormState>
|
||||
className="md:col-span-2"
|
||||
label="说明"
|
||||
name="description"
|
||||
rules={[{ max: 20000, message: "说明不能超过 20000 位" }]}
|
||||
>
|
||||
<Input.TextArea rows={3} />
|
||||
</Form.Item>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user