style(usage-script): unify form input styles with shadcn/ui components

Replace native HTML input elements with shadcn/ui Input and FormLabel
components to ensure consistent styling across the application.

Changes:
- Import Input, FormLabel, Eye, and EyeOff components
- Replace all credential input fields with Input component
- Add show/hide toggle buttons for password fields (API Key, Access Token)
- Replace label/span elements with FormLabel component
- Update timeout and auto-query interval inputs to use Input component
- Improve spacing consistency (space-y-4 for credential config)
- Add proper id attributes for accessibility
- Use muted-foreground for hint text

The form now matches the styling of provider configuration forms
throughout the application.
This commit is contained in:
Jason
2025-11-10 15:42:36 +08:00
parent 7096957b40
commit 2a56a0d889

View File

@@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Play, Wand2 } from "lucide-react"; import { Play, Wand2, Eye, EyeOff } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Provider, UsageScript } from "@/types"; import { Provider, UsageScript } from "@/types";
@@ -16,6 +16,8 @@ import {
DialogFooter, DialogFooter,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { FormLabel } from "@/components/ui/form";
interface UsageScriptModalProps { interface UsageScriptModalProps {
provider: Provider; provider: Provider;
@@ -140,6 +142,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
}, },
); );
// 控制 API Key 的显示/隐藏
const [showApiKey, setShowApiKey] = useState(false);
const [showAccessToken, setShowAccessToken] = useState(false);
const handleSave = () => { const handleSave = () => {
// 验证脚本格式 // 验证脚本格式
if (script.enabled && !script.code.trim()) { if (script.enabled && !script.code.trim()) {
@@ -292,9 +298,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
<> <>
{/* 预设模板选择 */} {/* 预设模板选择 */}
<div> <div>
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100"> <FormLabel className="mb-2">
{t("usageScript.presetTemplate")} {t("usageScript.presetTemplate")}
</label> </FormLabel>
<div className="flex gap-2"> <div className="flex gap-2">
{Object.keys(PRESET_TEMPLATES).map((name) => { {Object.keys(PRESET_TEMPLATES).map((name) => {
const isSelected = selectedTemplate === name; const isSelected = selectedTemplate === name;
@@ -317,93 +323,122 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 凭证配置区域:通用和 NewAPI 模板显示 */} {/* 凭证配置区域:通用和 NewAPI 模板显示 */}
{shouldShowCredentialsConfig && ( {shouldShowCredentialsConfig && (
<div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg"> <div className="space-y-4 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300"> <h4 className="text-sm font-medium text-gray-900 dark:text-gray-100">
{t("usageScript.credentialsConfig")} {t("usageScript.credentialsConfig")}
</h4> </h4>
{/* 通用模板:显示 apiKey + baseUrl */} {/* 通用模板:显示 apiKey + baseUrl */}
{selectedTemplate === TEMPLATE_KEYS.GENERAL && ( {selectedTemplate === TEMPLATE_KEYS.GENERAL && (
<> <>
<label className="block"> <div className="space-y-2">
<span className="text-xs text-gray-600 dark:text-gray-400"> <FormLabel htmlFor="usage-api-key">
API Key API Key
</span> </FormLabel>
<input <div className="relative">
type="password" <Input
value={script.apiKey || ""} id="usage-api-key"
onChange={(e) => type={showApiKey ? "text" : "password"}
setScript({ ...script, apiKey: e.target.value }) value={script.apiKey || ""}
} onChange={(e) =>
placeholder="sk-xxxxx" setScript({ ...script, apiKey: e.target.value })
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" }
/> placeholder="sk-xxxxx"
</label> autoComplete="off"
/>
{script.apiKey && (
<button
type="button"
onClick={() => setShowApiKey(!showApiKey)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
aria-label={showApiKey ? t("apiKeyInput.hide") : t("apiKeyInput.show")}
>
{showApiKey ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
)}
</div>
</div>
<label className="block"> <div className="space-y-2">
<span className="text-xs text-gray-600 dark:text-gray-400"> <FormLabel htmlFor="usage-base-url">
Base URL Base URL
</span> </FormLabel>
<input <Input
id="usage-base-url"
type="text" type="text"
value={script.baseUrl || ""} value={script.baseUrl || ""}
onChange={(e) => onChange={(e) =>
setScript({ ...script, baseUrl: e.target.value }) setScript({ ...script, baseUrl: e.target.value })
} }
placeholder="https://api.example.com" placeholder="https://api.example.com"
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" autoComplete="off"
/> />
</label> </div>
</> </>
)} )}
{/* NewAPI 模板:显示 baseUrl + accessToken + userId */} {/* NewAPI 模板:显示 baseUrl + accessToken + userId */}
{selectedTemplate === TEMPLATE_KEYS.NEW_API && ( {selectedTemplate === TEMPLATE_KEYS.NEW_API && (
<> <>
<label className="block"> <div className="space-y-2">
<span className="text-xs text-gray-600 dark:text-gray-400"> <FormLabel htmlFor="usage-newapi-base-url">
Base URL Base URL
</span> </FormLabel>
<input <Input
id="usage-newapi-base-url"
type="text" type="text"
value={script.baseUrl || ""} value={script.baseUrl || ""}
onChange={(e) => onChange={(e) =>
setScript({ ...script, baseUrl: e.target.value }) setScript({ ...script, baseUrl: e.target.value })
} }
placeholder="https://api.newapi.com" placeholder="https://api.newapi.com"
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" autoComplete="off"
/> />
</label> </div>
<label className="block"> <div className="space-y-2">
<span className="text-xs text-gray-600 dark:text-gray-400"> <FormLabel htmlFor="usage-access-token">
{t("usageScript.accessToken")} {t("usageScript.accessToken")}
</span> </FormLabel>
<input <div className="relative">
type="text" <Input
value={script.accessToken || ""} id="usage-access-token"
onChange={(e) => type={showAccessToken ? "text" : "password"}
setScript({ ...script, accessToken: e.target.value }) value={script.accessToken || ""}
} onChange={(e) =>
placeholder={t("usageScript.accessTokenPlaceholder")} setScript({ ...script, accessToken: e.target.value })
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" }
/> placeholder={t("usageScript.accessTokenPlaceholder")}
</label> autoComplete="off"
/>
{script.accessToken && (
<button
type="button"
onClick={() => setShowAccessToken(!showAccessToken)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
aria-label={showAccessToken ? t("apiKeyInput.hide") : t("apiKeyInput.show")}
>
{showAccessToken ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
)}
</div>
</div>
<label className="block"> <div className="space-y-2">
<span className="text-xs text-gray-600 dark:text-gray-400"> <FormLabel htmlFor="usage-user-id">
{t("usageScript.userId")} {t("usageScript.userId")}
</span> </FormLabel>
<input <Input
id="usage-user-id"
type="text" type="text"
value={script.userId || ""} value={script.userId || ""}
onChange={(e) => onChange={(e) =>
setScript({ ...script, userId: e.target.value }) setScript({ ...script, userId: e.target.value })
} }
placeholder={t("usageScript.userIdPlaceholder")} placeholder={t("usageScript.userIdPlaceholder")}
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" autoComplete="off"
/> />
</label> </div>
</> </>
)} )}
</div> </div>
@@ -411,9 +446,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 脚本编辑器 */} {/* 脚本编辑器 */}
<div> <div>
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100"> <FormLabel className="mb-2">
{t("usageScript.queryScript")} {t("usageScript.queryScript")}
</label> </FormLabel>
<JsonEditor <JsonEditor
value={script.code} value={script.code}
onChange={(code) => setScript({ ...script, code })} onChange={(code) => setScript({ ...script, code })}
@@ -430,14 +465,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 配置选项 */} {/* 配置选项 */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<label className="block"> <div className="space-y-2">
<span className="text-sm font-medium text-gray-900 dark:text-gray-100"> <FormLabel htmlFor="usage-timeout">
{t("usageScript.timeoutSeconds")} {t("usageScript.timeoutSeconds")}
</span> </FormLabel>
<input <Input
id="usage-timeout"
type="number" type="number"
min="2" min={2}
max="30" max={30}
value={script.timeout || 10} value={script.timeout || 10}
onChange={(e) => onChange={(e) =>
setScript({ setScript({
@@ -445,20 +481,20 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
timeout: parseInt(e.target.value), timeout: parseInt(e.target.value),
}) })
} }
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
/> />
</label> </div>
{/* 🆕 自动查询间隔 */} {/* 🆕 自动查询间隔 */}
<label className="block"> <div className="space-y-2">
<span className="text-sm font-medium text-gray-900 dark:text-gray-100"> <FormLabel htmlFor="usage-auto-interval">
{t("usageScript.autoQueryInterval")} {t("usageScript.autoQueryInterval")}
</span> </FormLabel>
<input <Input
id="usage-auto-interval"
type="number" type="number"
min="0" min={0}
max="1440" max={1440}
step="1" step={1}
value={script.autoQueryInterval || 0} value={script.autoQueryInterval || 0}
onChange={(e) => onChange={(e) =>
setScript({ setScript({
@@ -466,12 +502,11 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
autoQueryInterval: parseInt(e.target.value) || 0, autoQueryInterval: parseInt(e.target.value) || 0,
}) })
} }
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
/> />
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400"> <p className="text-xs text-muted-foreground">
{t("usageScript.autoQueryIntervalHint")} {t("usageScript.autoQueryIntervalHint")}
</p> </p>
</label> </div>
</div> </div>
{/* 脚本说明 */} {/* 脚本说明 */}