全新popiart网站 react项目重构
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

164 lines
6.3 KiB

import { useCallback, useEffect, useMemo, useState } from "react";
import { Message } from "@arco-design/web-react";
import { useTranslation } from "react-i18next";
import { getGatewayApikeyList, setGatewayApikeyStatus } from "@/api/api";
import { useCopyText } from "@/hooks/useCopyText";
import "./index.scss";
interface GatewayApikeyItem {
id: number;
code: string;
apikey: string;
status: number;
createTime?: string | number;
}
function formatDateTime(value?: string | number): string {
if (value == null) return "-";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return String(value);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
const hh = String(date.getHours()).padStart(2, "0");
const mm = String(date.getMinutes()).padStart(2, "0");
return `${String(y)}-${m}-${d} ${hh}:${mm}`;
}
function maskApikey(value: string): string {
if (!value) return "-";
if (value.length <= 10) return value;
return `${value.slice(0, 5)}...${value.slice(-4)}`;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value != null && typeof value === "object";
}
function getString(record: Record<string, unknown>, key: string): string {
const value = record[key];
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
return "";
}
function getNumber(record: Record<string, unknown>, key: string): number | null {
const value = record[key];
const numericValue = typeof value === "number" ? value : typeof value === "string" ? Number(value) : null;
return numericValue != null && Number.isFinite(numericValue) ? numericValue : null;
}
function normalizeGatewayApikeyItem(value: unknown): GatewayApikeyItem | null {
if (!isRecord(value)) return null;
const id = getNumber(value, "id");
if (id == null) return null;
return {
id,
code: getString(value, "code"),
apikey: getString(value, "apikey"),
status: getNumber(value, "status") ?? 2,
createTime: getString(value, "createTime") || getString(value, "create_time") || undefined,
};
}
export default function GatewayApikeyDialog() {
const { t } = useTranslation();
const copyText = useCopyText();
const [loading, setLoading] = useState(false);
const [list, setList] = useState<GatewayApikeyItem[]>([]);
const [updatingId, setUpdatingId] = useState<number | null>(null);
const loadList = useCallback(async () => {
setLoading(true);
try {
const res = await getGatewayApikeyList({ page: 1, pageSize: 20 });
const rows = Array.isArray(res.data?.list) ? res.data.list.map(normalizeGatewayApikeyItem).filter((item): item is GatewayApikeyItem => item != null) : [];
setList(rows);
} catch {
setList([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void loadList();
}, [loadList]);
const hasData = useMemo(() => list.length > 0, [list]);
const handleToggle = useCallback(
async (item: GatewayApikeyItem) => {
if (updatingId != null) return;
const nextEnabled = item.status !== 1;
setUpdatingId(item.id);
try {
await setGatewayApikeyStatus({
id: item.id,
enabled: nextEnabled,
});
setList((prev) =>
prev.map((row) => {
if (row.id !== item.id) return row;
return {
...row,
status: nextEnabled ? 1 : 2,
};
}),
);
} catch {
Message.error(t("gatewayApiKey.updateStatusFailed"));
} finally {
setUpdatingId(null);
}
},
[t, updatingId],
);
return (
<div className="gateway_apikey_dialog">
<h2 className="gateway_apikey_dialog__title">{t("gatewayApiKey.title")}</h2>
{loading ? <div className="gateway_apikey_dialog__statusText">{t("common.loading")}</div> : null}
{!loading && !hasData ? <div className="gateway_apikey_dialog__statusText">{t("gatewayApiKey.empty")}</div> : null}
{list.map((item) => (
<div className="gateway_apikey_dialog__card" key={item.id}>
<div className="gateway_apikey_dialog__row gateway_apikey_dialog__row_header">
<span className="gateway_apikey_dialog__headerText">Apikey</span>
</div>
<div className="gateway_apikey_dialog__row gateway_apikey_dialog__row_user">
<span className="gateway_apikey_dialog__userCode">{item.code}</span>
{/* <span
className={["gateway_apikey_dialog__switch", item.status === 1 ? "gateway_apikey_dialog__switch_on" : "gateway_apikey_dialog__switch_off", updatingId === item.id ? "gateway_apikey_dialog__switch_loading" : ""].join(" ")}
role="switch"
aria-checked={item.status === 1}
aria-label={t("gatewayApiKey.statusAria")}
onClick={() => { void handleToggle(item); }}
>
<span className="gateway_apikey_dialog__switchLabel">{item.status === 1 ? "ON" : "OFF"}</span>
<span className="gateway_apikey_dialog__switchKnob" />
</span> */}
</div>
<div className="gateway_apikey_dialog__detail">
<span className="gateway_apikey_dialog__detailLabel">Key</span>
<span className="gateway_apikey_dialog__detailValue gateway_apikey_dialog__detailValue_key">
{maskApikey(item.apikey)}
<button type="button" className="gateway_apikey_dialog__copyBtn" onClick={() => { void copyText(item.apikey); }} aria-label={t("gatewayApiKey.copyKey")}>
<img className="gateway_apikey_dialog__copyIcon" src={new URL("@/assets/images/ic_copy.png", import.meta.url).href} />
</button>
</span>
</div>
<div className="gateway_apikey_dialog__divider" />
<div className="gateway_apikey_dialog__detail gateway_apikey_dialog__detail_last">
<span className="gateway_apikey_dialog__detailLabel">{t("gatewayApiKey.createdAt")}</span>
<span className="gateway_apikey_dialog__detailValue">{formatDateTime(item.createTime)}</span>
</div>
</div>
))}
</div>
);
}