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:
@@ -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>
|
||||||
|
|
||||||
{/* 脚本说明 */}
|
{/* 脚本说明 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user