feat(web): 主题设置联动 — 扩展 ThemeConfig 品牌字段 + 设置页面表单
- ThemeConfig 接口增加 brand_name/brand_slogan/brand_features/brand_copyright - 新增 BrandConfig 接口和 getPublicBrand 公开品牌信息获取 - app store 增加 themeConfig 缓存和 loadThemeConfig 方法 - ThemeSettings 页面增加品牌设置表单(品牌名称/标语/特性/版权)
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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}>
|
||||||
保存
|
保存
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user