Files
hms/apps/web/src/pages/settings/ThemeSettings.tsx
iven 14f431efff feat: systematic functional audit — fix 18 issues across Phase A/B
Phase A (P1 production blockers):
- A1: Apply IP rate limiting to public routes (login/refresh)
- A2: Publish domain events for workflow instance state transitions
  (completed/suspended/resumed/terminated) via outbox pattern
- A3: Replace hardcoded nil UUID default tenant with dynamic DB lookup
- A4: Add GET /api/v1/audit-logs query endpoint with pagination
- A5: Enhance CORS wildcard warning for production environments

Phase B (P2 functional gaps):
- B1: Remove dead erp-common crate (zero references in codebase)
- B2: Refactor 5 settings pages to use typed API modules instead of
  direct client calls; create api/themes.ts; delete dead errors.ts
- B3: Add resume/suspend buttons to InstanceMonitor page
- B4: Remove unused EventHandler trait from erp-core
- B5: Handle task.completed events in message module (send notifications)
- B6: Wire TimeoutChecker as 60s background task
- B7: Auto-skip ServiceTask nodes instead of crashing the process
- B8: Remove empty register_routes() from ErpModule trait and modules
2026-04-12 15:22:28 +08:00

100 lines
2.7 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import { Form, Input, Select, Button, ColorPicker, message, Typography } from 'antd';
import {
getTheme,
updateTheme,
type ThemeConfig,
} from '../../api/themes';
// --- Component ---
export default function ThemeSettings() {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const fetchTheme = useCallback(async () => {
setLoading(true);
try {
const theme = await getTheme();
form.setFieldsValue({
primary_color: theme.primary_color || '#1677ff',
logo_url: theme.logo_url || '',
sidebar_style: theme.sidebar_style || 'light',
});
} catch {
// Theme may not exist yet; use defaults
form.setFieldsValue({
primary_color: '#1677ff',
logo_url: '',
sidebar_style: 'light',
});
}
setLoading(false);
}, [form]);
useEffect(() => {
fetchTheme();
}, [fetchTheme]);
const handleSave = async (values: {
primary_color: string;
logo_url: string;
sidebar_style: 'light' | 'dark';
}) => {
setSaving(true);
try {
await updateTheme({
primary_color:
typeof values.primary_color === 'string'
? values.primary_color
: (values.primary_color as { toHexString?: () => string }).toHexString?.() ?? String(values.primary_color),
logo_url: values.logo_url,
sidebar_style: values.sidebar_style,
});
message.success('主题设置已保存');
} catch (err: unknown) {
const errorMsg =
(err as { response?: { data?: { message?: string } } })?.response?.data
?.message || '保存失败';
message.error(errorMsg);
}
setSaving(false);
};
return (
<div>
<Typography.Title level={5} style={{ marginBottom: 16 }}>
</Typography.Title>
<Form
form={form}
onFinish={handleSave}
layout="vertical"
style={{ maxWidth: 480 }}
>
<Form.Item name="primary_color" label="主色调">
<ColorPicker format="hex" />
</Form.Item>
<Form.Item name="logo_url" label="Logo URL">
<Input placeholder="https://example.com/logo.png" />
</Form.Item>
<Form.Item name="sidebar_style" label="侧边栏风格">
<Select
options={[
{ label: '亮色', value: 'light' },
{ label: '暗色', value: 'dark' },
]}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={saving}>
</Button>
</Form.Item>
</Form>
</div>
);
}