import { Button, Card, Col, Drawer, Form, Input, Popconfirm, Row, Segmented, Select, Space, Switch, Tag, Tooltip, Typography, message } from "antd"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { DeleteOutlined, EditOutlined, InfoCircleOutlined, PlusOutlined, SearchOutlined, SettingOutlined } from "@ant-design/icons"; import { createParam, deleteParam, pageParams, updateParam } from "@/api"; import { useDict } from "@/hooks/useDict"; import { usePermission } from "@/hooks/usePermission"; import PageHeader from "@/components/shared/PageHeader"; import PageContainer from "@/components/shared/PageContainer"; import ListTable from "@/components/shared/ListTable/ListTable"; import AppPagination from "@/components/shared/AppPagination"; import type { SysParamQuery, SysParamVO } from "@/types"; import "./index.less"; const { Text } = Typography; /** Normalize paramType to title-case for case-insensitive comparison */ const isType = (actual: string | undefined, expected: string) => (actual || "").toLowerCase() === expected.toLowerCase(); export default function SysParams() { const { t } = useTranslation(); const { can } = usePermission(); const { items: statusDict } = useDict("sys_common_status"); const { items: paramTypeDict } = useDict("sys_param_type"); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [queryParams, setQueryParams] = useState({ pageNum: 1, pageSize: 10 }); const [drawerOpen, setDrawerOpen] = useState(false); const [editing, setEditing] = useState(null); const [jsonError, setJsonError] = useState(null); const [form] = Form.useForm(); const paramType = Form.useWatch("paramType", form) as string | undefined; // Per-type validation rules. The submitted paramValue is always a string // (backend stores TEXT), so validators operate on string values. const valueRules = (() => { const required = [{ required: true, message: t("sysParams.paramValue") }]; if (isType(paramType, "Number")) { return [ ...required, { validator: (_: unknown, value: string) => value === "" || value == null || !Number.isNaN(Number(value)) ? Promise.resolve() : Promise.reject(new Error(t("sysParamsExt.numberInvalid"))) } ]; } if (isType(paramType, "JSON")) { return [ ...required, { validator: (_: unknown, value: string) => { if (value === "" || value == null) return Promise.resolve(); try { JSON.parse(value); return Promise.resolve(); } catch { return Promise.reject(new Error(t("sysParamsExt.jsonInvalid"))); } } } ]; } return required; })(); const formatJson = () => { const raw = form.getFieldValue("paramValue"); if (!raw) return; try { const parsed = JSON.parse(raw); form.setFieldValue("paramValue", JSON.stringify(parsed, null, 2)); setJsonError(null); } catch { setJsonError(t("sysParamsExt.jsonInvalid")); } }; const loadData = useCallback(async (query = queryParams) => { setLoading(true); try { const response = await pageParams(query); setData(response.records || []); setTotal(response.total || 0); } finally { setLoading(false); } }, [queryParams]); useEffect(() => { loadData(); }, [loadData]); const handleSearch = (values: any) => { setQueryParams({ ...queryParams, ...values, pageNum: 1 }); }; const handleReset = () => { setQueryParams({ pageNum: 1, pageSize: 10 }); }; const handlePageChange = (page: number, pageSize: number) => { setQueryParams((prev) => ({ ...prev, pageNum: page, pageSize })); }; const openCreate = () => { setEditing(null); setJsonError(null); form.resetFields(); form.setFieldsValue({ isSystem: 0, status: 1 }); setDrawerOpen(true); }; const openEdit = (record: SysParamVO) => { setEditing(record); setJsonError(null); form.setFieldsValue(record); setDrawerOpen(true); }; const handleDelete = async (id: number) => { try { await deleteParam(id); message.success(t("common.success")); loadData(); } catch { } }; const submit = async () => { try { const values = await form.validateFields(); setSaving(true); if (editing) { await updateParam(editing.paramId, values); } else { await createParam(values); } message.success(t("common.success")); setDrawerOpen(false); loadData(); } finally { setSaving(false); } }; const columns = [ { title: t("sysParams.paramKey"), dataIndex: "paramKey", key: "paramKey", render: (text: string, record: SysParamVO) => ( {text} {record.isSystem === 1 && {t("sysParams.isSystem")}} ) }, { title: t("sysParams.paramValue"), dataIndex: "paramValue", key: "paramValue", width: 360, render: (text: string, record: SysParamVO) => { const type = record.paramType; // Boolean: 彩色标签显示 是/否 if (isType(type, "Boolean")) { const isTrue = text === "true"; return {isTrue ? t("sysParamsExt.booleanTrue") : t("sysParamsExt.booleanFalse")}; } // JSON: 等宽字体,以纯字符串显示 if (isType(type, "JSON")) { return ( {text} ); } // Number / String: 等宽代码文本 + Tooltip return ( {text} ); } }, { title: t("sysParams.paramType"), dataIndex: "paramType", key: "paramType", width: 120, render: (type: string) => {type || t("sysParamsExt.defaultType")} }, { title: t("sysParams.description"), dataIndex: "description", key: "description", ellipsis: true }, { title: t("common.status"), dataIndex: "status", width: 80, render: (status: number) => { const item = statusDict.find((dictItem) => dictItem.itemValue === String(status)); return {item ? item.itemLabel : status === 1 ? t("sysParamsExt.enabled") : t("sysParamsExt.disabled")}; } }, { title: t("common.action"), key: "action", width: 110, fixed: "right" as const, render: (_: any, record: SysParamVO) => ( {can("sys_param:update") && } toolbar={
} allowClear style={{ width: 200 }} /> ({ label: item.itemLabel, value: Number(item.itemValue) }))} /> {isType(paramType, "JSON") && (
)} { const v = e && typeof e === "object" && "target" in (e as { target?: { value?: unknown } }) ? (e as { target: { value?: unknown } }).target.value : e; return typeof v === "number" ? String(v) : (v ?? ""); }} extra={isType(paramType, "JSON") && jsonError ? {jsonError} : undefined} > {isType(paramType, "Number") ? ( ) : isType(paramType, "Boolean") ? ( ) : isType(paramType, "JSON") ? ( { const v = form.getFieldValue("paramValue"); if (!v) form.setFieldValue("paramValue", JSON.stringify({ key: "value" })); }} onChange={(e) => { const v = e.target.value; if (!v) { setJsonError(null); return; } try { JSON.parse(v); setJsonError(null); } catch { setJsonError(t("sysParamsExt.jsonInvalid")); } }} /> ) : ( )} {t("sysParams.isSystem")}} name="isSystem" valuePropName="checked" getValueProps={(value) => ({ checked: value === 1 })} getValueFromEvent={(checked) => (checked ? 1 : 0)} >
); }