重构获取公共参数

This commit is contained in:
ryan
2026-06-09 15:54:16 +08:00
parent bff09241d3
commit 40e8a7cfa3
18 changed files with 313 additions and 315 deletions
+14 -9
View File
@@ -36,6 +36,11 @@ function persistRedirectTarget(searchParams: ReturnType<typeof useSearchParams>)
}
}
function configBool(value: string | undefined, fallback: boolean) {
if (value === undefined) return fallback
return value === "true"
}
export function LoginForm() {
const router = useRouter()
const searchParams = useSearchParams()
@@ -80,7 +85,7 @@ export function LoginForm() {
const authSourcesQuery = useQuery({
queryKey: ["auth-sources"],
queryFn: () => services.auth.getAuthSources(),
enabled: publicConfigQuery.data?.oidc_login_enabled ?? true,
enabled: configBool(publicConfigQuery.data?.oidc_login_enabled, true),
})
const redirectTarget = useMemo(
@@ -88,8 +93,8 @@ export function LoginForm() {
[searchParams],
)
const capEnabled = publicConfigQuery.data?.cap_login_enabled ?? false
const capAutoSolve = publicConfigQuery.data?.cap_auto_solve ?? true
const capEnabled = configBool(publicConfigQuery.data?.cap_login_enabled, false)
const capAutoSolve = configBool(publicConfigQuery.data?.cap_auto_solve, true)
const loginMutation = useMutation({
mutationFn: (req: LoginRequest) => {
@@ -201,7 +206,6 @@ export function LoginForm() {
const handleRegister = () => {
setErrorMessage("")
const emailRegisterEnabled = publicConfigQuery.data?.email_register_verification_enabled ?? false
if (emailRegisterEnabled) {
if (!email.trim() || !code.trim()) {
toast.error("邮箱和验证码不能为空")
@@ -241,10 +245,11 @@ export function LoginForm() {
}
const registrationEnabled =
(publicConfigQuery.data?.registration_enabled ?? true) &&
(publicConfigQuery.data?.password_register_enabled ?? true)
configBool(publicConfigQuery.data?.registration_enabled, true) &&
configBool(publicConfigQuery.data?.password_register_enabled, true)
const passwordLoginEnabled = publicConfigQuery.data?.password_login_enabled ?? true
const passwordLoginEnabled = configBool(publicConfigQuery.data?.password_login_enabled, true)
const emailRegisterEnabled = configBool(publicConfigQuery.data?.email_register_verification_enabled, false)
const authSources = authSourcesQuery.data ?? []
@@ -379,10 +384,10 @@ export function LoginForm() {
<Input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder={publicConfigQuery.data?.email_register_verification_enabled ? "电子邮箱" : "电子邮箱(可选)"}
placeholder={emailRegisterEnabled ? "电子邮箱" : "电子邮箱(可选)"}
autoComplete="email"
/>
{publicConfigQuery.data?.email_register_verification_enabled && (
{emailRegisterEnabled && (
<div className="flex gap-2">
<Input
value={code}
@@ -124,6 +124,16 @@ function SystemConfigDetailPanel({
</div>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">公共可见</label>
<Switch
checked={(editData.visibility ?? config?.visibility ?? 0) === 1}
onCheckedChange={(checked) => {
onEditDataChange('visibility', checked ? 1 : 0)
}}
/>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">配置类型</label>
<span className={`text-[11px] px-1.5 py-0.5 rounded font-medium ${
@@ -173,6 +183,7 @@ export function SystemConfigs() {
const getInitialEditData = (config: SystemConfig) => ({
value: config.value,
visibility: config.visibility,
description: config.description
})
@@ -181,6 +192,7 @@ export function SystemConfigs() {
await updateSystemConfig(config.key, {
value: editData.value ?? config.value,
visibility: editData.visibility ?? config.visibility,
description: editData.description ?? config.description
})
}
@@ -208,6 +220,7 @@ export function SystemConfigs() {
columns={[
{ header: "配置键", cell: (item) => <span className="font-mono font-medium">{item.key}</span>, width: "200px" },
{ header: "配置值", cell: (item) => <span className="truncate max-w-[120px] inline-block" title={item.value}>{item.value}</span>, width: "120px" },
{ header: "公共可见", cell: (item) => <span>{item.visibility === 1 ? "可见" : "不可见"}</span>, width: "80px" },
{ header: "描述", cell: (item) => <span className="truncate max-w-[200px] inline-block text-muted-foreground" title={item.description}>{item.description}</span>, width: "200px" },
]}
renderDetail={({ selected, hovered, editData, onEditDataChange, onSave, saving }) => (
+5 -5
View File
@@ -234,17 +234,17 @@ export const apiSections: PolicySection[] = [
<div className="space-y-4 text-sm leading-relaxed">
<h3 id="4-1-public-config" className="text-base md:text-lg font-semibold text-foreground mt-4 mb-2">4.1 公共系统配置</h3>
<p><strong>接口:</strong>GET <code className="bg-muted px-1.5 py-0.5 rounded text-xs font-mono">/api/v1/config/public</code></p>
<p><strong>说明:</strong>无感获取当前系统的公开业务设置(如注册是否开启、密码登录是否开启)。供前端页面动态渲染使用。</p>
<p><strong>说明:</strong>无感获取当前系统配置表中公共可见的键值集合。供前端页面动态渲染使用。</p>
<p className="mt-2">返回数据结构样例:</p>
<CodeBlock
code={`{
"error_msg": "",
"data": {
"site_name": "Wavelet",
"registration_enabled": true,
"password_login_enabled": true,
"password_register_enabled": true,
"oidc_login_enabled": true
"registration_enabled": "true",
"password_login_enabled": "true",
"password_register_enabled": "true",
"oidc_login_enabled": "true"
}
}`}
language="json"
+1 -1
View File
@@ -6,7 +6,7 @@ export function RobotsMeta() {
const { config } = usePublicConfig()
// Default to noindex, nofollow if config is loading or if it's explicitly disabled
const enabled = config?.search_engine_indexing_enabled === true
const enabled = config?.search_engine_indexing_enabled === "true"
return (
<meta
+20 -3
View File
@@ -84,6 +84,23 @@ const data = {
],
}
function parseMenuDisplayConfig(raw: string | undefined): Record<string, boolean> {
if (!raw) return {}
try {
const parsed: unknown = JSON.parse(raw)
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {}
return Object.entries(parsed).reduce<Record<string, boolean>>((result, [key, value]) => {
if (typeof value === "boolean") {
result[key] = value
}
return result
}, {})
} catch {
return {}
}
}
/**
* 应用侧边栏组件
* 显示应用侧边栏
@@ -145,17 +162,17 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
}
const navMainFiltered = React.useMemo(() => {
const displayConfig = config?.menu_display_config || {}
const displayConfig = parseMenuDisplayConfig(config?.menu_display_config)
return data.navMain.filter((item) => displayConfig[item.url] !== false)
}, [config])
const adminFiltered = React.useMemo(() => {
const displayConfig = config?.menu_display_config || {}
const displayConfig = parseMenuDisplayConfig(config?.menu_display_config)
return data.admin.filter((item) => displayConfig[item.url] !== false)
}, [config])
const documentFiltered = React.useMemo(() => {
const displayConfig = config?.menu_display_config || {}
const displayConfig = parseMenuDisplayConfig(config?.menu_display_config)
return data.document.filter((item) => displayConfig[item.url] !== false)
}, [config])