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 { Play, Wand2 } from "lucide-react";
import { Play, Wand2, Eye, EyeOff } from "lucide-react";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import { Provider, UsageScript } from "@/types";
@@ -16,6 +16,8 @@ import {
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { FormLabel } from "@/components/ui/form";
interface UsageScriptModalProps {
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 = () => {
// 验证脚本格式
if (script.enabled && !script.code.trim()) {
@@ -292,9 +298,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
<>
{/* 预设模板选择 */}
<div>
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100">
<FormLabel className="mb-2">
{t("usageScript.presetTemplate")}
</label>
</FormLabel>
<div className="flex gap-2">
{Object.keys(PRESET_TEMPLATES).map((name) => {
const isSelected = selectedTemplate === name;
@@ -317,93 +323,122 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 凭证配置区域:通用和 NewAPI 模板显示 */}
{shouldShowCredentialsConfig && (
<div className="space-y-3 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">
<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-900 dark:text-gray-100">
{t("usageScript.credentialsConfig")}
</h4>
{/* 通用模板:显示 apiKey + baseUrl */}
{selectedTemplate === TEMPLATE_KEYS.GENERAL && (
<>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
<div className="space-y-2">
<FormLabel htmlFor="usage-api-key">
API Key
</span>
<input
type="password"
value={script.apiKey || ""}
onChange={(e) =>
setScript({ ...script, apiKey: e.target.value })
}
placeholder="sk-xxxxx"
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"
/>
</label>
</FormLabel>
<div className="relative">
<Input
id="usage-api-key"
type={showApiKey ? "text" : "password"}
value={script.apiKey || ""}
onChange={(e) =>
setScript({ ...script, apiKey: e.target.value })
}
placeholder="sk-xxxxx"
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">
<span className="text-xs text-gray-600 dark:text-gray-400">
<div className="space-y-2">
<FormLabel htmlFor="usage-base-url">
Base URL
</span>
<input
</FormLabel>
<Input
id="usage-base-url"
type="text"
value={script.baseUrl || ""}
onChange={(e) =>
setScript({ ...script, baseUrl: e.target.value })
}
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 */}
{selectedTemplate === TEMPLATE_KEYS.NEW_API && (
<>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
<div className="space-y-2">
<FormLabel htmlFor="usage-newapi-base-url">
Base URL
</span>
<input
</FormLabel>
<Input
id="usage-newapi-base-url"
type="text"
value={script.baseUrl || ""}
onChange={(e) =>
setScript({ ...script, baseUrl: e.target.value })
}
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">
<span className="text-xs text-gray-600 dark:text-gray-400">
<div className="space-y-2">
<FormLabel htmlFor="usage-access-token">
{t("usageScript.accessToken")}
</span>
<input
type="text"
value={script.accessToken || ""}
onChange={(e) =>
setScript({ ...script, accessToken: e.target.value })
}
placeholder={t("usageScript.accessTokenPlaceholder")}
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"
/>
</label>
</FormLabel>
<div className="relative">
<Input
id="usage-access-token"
type={showAccessToken ? "text" : "password"}
value={script.accessToken || ""}
onChange={(e) =>
setScript({ ...script, accessToken: e.target.value })
}
placeholder={t("usageScript.accessTokenPlaceholder")}
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">
<span className="text-xs text-gray-600 dark:text-gray-400">
<div className="space-y-2">
<FormLabel htmlFor="usage-user-id">
{t("usageScript.userId")}
</span>
<input
</FormLabel>
<Input
id="usage-user-id"
type="text"
value={script.userId || ""}
onChange={(e) =>
setScript({ ...script, userId: e.target.value })
}
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>
@@ -411,9 +446,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 脚本编辑器 */}
<div>
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100">
<FormLabel className="mb-2">
{t("usageScript.queryScript")}
</label>
</FormLabel>
<JsonEditor
value={script.code}
onChange={(code) => setScript({ ...script, code })}
@@ -430,14 +465,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 配置选项 */}
<div className="grid grid-cols-2 gap-4">
<label className="block">
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
<div className="space-y-2">
<FormLabel htmlFor="usage-timeout">
{t("usageScript.timeoutSeconds")}
</span>
<input
</FormLabel>
<Input
id="usage-timeout"
type="number"
min="2"
max="30"
min={2}
max={30}
value={script.timeout || 10}
onChange={(e) =>
setScript({
@@ -445,20 +481,20 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
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">
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
<div className="space-y-2">
<FormLabel htmlFor="usage-auto-interval">
{t("usageScript.autoQueryInterval")}
</span>
<input
</FormLabel>
<Input
id="usage-auto-interval"
type="number"
min="0"
max="1440"
step="1"
min={0}
max={1440}
step={1}
value={script.autoQueryInterval || 0}
onChange={(e) =>
setScript({
@@ -466,12 +502,11 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
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")}
</p>
</label>
</div>
</div>
{/* 脚本说明 */}