feat(web): 主题设置联动 — 扩展 ThemeConfig 品牌字段 + 设置页面表单
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- ThemeConfig 接口增加 brand_name/brand_slogan/brand_features/brand_copyright
- 新增 BrandConfig 接口和 getPublicBrand 公开品牌信息获取
- app store 增加 themeConfig 缓存和 loadThemeConfig 方法
- ThemeSettings 页面增加品牌设置表单(品牌名称/标语/特性/版权)
This commit is contained in:
iven
2026-05-01 17:37:10 +08:00
parent 6eb2bf9c80
commit 669ca44360
3 changed files with 71 additions and 4 deletions

View File

@@ -4,6 +4,10 @@ export interface ThemeConfig {
primary_color?: string; primary_color?: string;
logo_url?: string; logo_url?: string;
sidebar_style?: 'light' | 'dark'; sidebar_style?: 'light' | 'dark';
brand_name?: string;
brand_slogan?: string;
brand_features?: string;
brand_copyright?: string;
} }
export async function getTheme() { export async function getTheme() {
@@ -20,3 +24,26 @@ export async function updateTheme(theme: ThemeConfig) {
); );
return data.data; return data.data;
} }
export interface BrandConfig {
brand_name: string;
brand_slogan: string;
brand_features: string;
brand_copyright: string;
}
const BRAND_DEFAULTS: BrandConfig = {
brand_name: 'HMS 健康管理平台',
brand_slogan: '新一代健康管理平台',
brand_features: '患者管理 · 健康监测 · 随访管理 · AI 智能分析',
brand_copyright: 'HMS 健康管理平台 · ©汕头市智界科技有限公司',
};
export async function getPublicBrand(): Promise<BrandConfig> {
try {
const res = await fetch('/api/v1/public/brand');
const json = await res.json();
if (json?.success && json?.data) return json.data;
} catch {}
return BRAND_DEFAULTS;
}

View File

@@ -1,12 +1,10 @@
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { Form, Input, Select, Button, ColorPicker, message, Typography } from 'antd'; import { Form, Input, Select, Button, ColorPicker, message, Typography, Divider } from 'antd';
import { import {
getTheme, getTheme,
updateTheme, updateTheme,
} from '../../api/themes'; } from '../../api/themes';
// --- Component ---
export default function ThemeSettings() { export default function ThemeSettings() {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [, setLoading] = useState(false); const [, setLoading] = useState(false);
@@ -20,13 +18,20 @@ export default function ThemeSettings() {
primary_color: theme.primary_color || '#1677ff', primary_color: theme.primary_color || '#1677ff',
logo_url: theme.logo_url || '', logo_url: theme.logo_url || '',
sidebar_style: theme.sidebar_style || 'light', sidebar_style: theme.sidebar_style || 'light',
brand_name: theme.brand_name || '',
brand_slogan: theme.brand_slogan || '',
brand_features: theme.brand_features || '',
brand_copyright: theme.brand_copyright || '',
}); });
} catch { } catch {
// Theme may not exist yet; use defaults
form.setFieldsValue({ form.setFieldsValue({
primary_color: '#1677ff', primary_color: '#1677ff',
logo_url: '', logo_url: '',
sidebar_style: 'light', sidebar_style: 'light',
brand_name: '',
brand_slogan: '',
brand_features: '',
brand_copyright: '',
}); });
} }
setLoading(false); setLoading(false);
@@ -40,6 +45,10 @@ export default function ThemeSettings() {
primary_color: string; primary_color: string;
logo_url: string; logo_url: string;
sidebar_style: 'light' | 'dark'; sidebar_style: 'light' | 'dark';
brand_name: string;
brand_slogan: string;
brand_features: string;
brand_copyright: string;
}) => { }) => {
setSaving(true); setSaving(true);
try { try {
@@ -50,6 +59,10 @@ export default function ThemeSettings() {
: (values.primary_color as { toHexString?: () => string }).toHexString?.() ?? String(values.primary_color), : (values.primary_color as { toHexString?: () => string }).toHexString?.() ?? String(values.primary_color),
logo_url: values.logo_url, logo_url: values.logo_url,
sidebar_style: values.sidebar_style, sidebar_style: values.sidebar_style,
brand_name: values.brand_name || undefined,
brand_slogan: values.brand_slogan || undefined,
brand_features: values.brand_features || undefined,
brand_copyright: values.brand_copyright || undefined,
}); });
message.success('主题设置已保存'); message.success('主题设置已保存');
} catch (err: unknown) { } catch (err: unknown) {
@@ -87,6 +100,22 @@ export default function ThemeSettings() {
]} ]}
/> />
</Form.Item> </Form.Item>
<Divider></Divider>
<Form.Item name="brand_name" label="品牌名称">
<Input placeholder="HMS 健康管理平台" />
</Form.Item>
<Form.Item name="brand_slogan" label="品牌标语">
<Input placeholder="新一代健康管理平台" />
</Form.Item>
<Form.Item name="brand_features" label="品牌特性">
<Input placeholder="患者管理 · 健康监测 · 随访管理 · AI 智能分析" />
</Form.Item>
<Form.Item name="brand_copyright" label="版权信息">
<Input placeholder="HMS 健康管理平台 · ©汕头市智界科技有限公司" />
</Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit" loading={saving}> <Button type="primary" htmlType="submit" loading={saving}>

View File

@@ -1,4 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import type { ThemeConfig } from '../api/themes';
import { getTheme } from '../api/themes';
export type ThemeName = 'blue' | 'warm' | 'dark' | 'emerald'; export type ThemeName = 'blue' | 'warm' | 'dark' | 'emerald';
@@ -49,16 +51,25 @@ function loadTheme(): ThemeName {
interface AppState { interface AppState {
theme: ThemeName; theme: ThemeName;
sidebarCollapsed: boolean; sidebarCollapsed: boolean;
themeConfig: ThemeConfig | null;
toggleSidebar: () => void; toggleSidebar: () => void;
setTheme: (theme: ThemeName) => void; setTheme: (theme: ThemeName) => void;
loadThemeConfig: () => Promise<void>;
} }
export const useAppStore = create<AppState>((set) => ({ export const useAppStore = create<AppState>((set) => ({
theme: loadTheme(), theme: loadTheme(),
sidebarCollapsed: false, sidebarCollapsed: false,
themeConfig: null,
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })), toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
setTheme: (theme) => { setTheme: (theme) => {
try { localStorage.setItem(STORAGE_KEY, theme); } catch {} try { localStorage.setItem(STORAGE_KEY, theme); } catch {}
set({ theme }); set({ theme });
}, },
loadThemeConfig: async () => {
try {
const config = await getTheme();
set({ themeConfig: config });
} catch {}
},
})); }));