基本功能已初步完善
Some checks failed
build / build (api, amd64, linux) (push) Has been cancelled
build / build (api, arm64, linux) (push) Has been cancelled
build / build (api.exe, amd64, windows) (push) Has been cancelled

This commit is contained in:
CN-JS-HuiBai
2026-04-17 20:41:47 +08:00
parent 25fd919477
commit b3435e5ef8
34 changed files with 3495 additions and 429 deletions

View File

@@ -1,9 +1,10 @@
import { makeSkeletonPage } from "../../runtime/makeSkeletonPage.js";
import { makeRecoveredPage } from "../../runtime/makeRecoveredPage.js";
import ThemeConfigPageView from "./ThemeConfigPage.jsx";
export default makeSkeletonPage({
export default makeRecoveredPage({
title: "Theme Config",
routePath: "/config/theme",
moduleId: "WQt",
featureKey: "config.theme",
notes: ["Bundle contains theme activation and theme config editing flows."],
component: ThemeConfigPageView,
});

View File

@@ -0,0 +1,226 @@
import React, { useEffect, useState } from "../../../recovery-preview/node_modules/react/index.js";
import {
compactText,
requestJson,
} from "../../runtime/client.js";
// Wot: Wrapper structure for the config page
function Wot({ children }) {
return <div className="recovery-live-page" style={{ padding: '2rem' }}>{children}</div>;
}
// Hot: Header component for sections or the page
function Hot({ title, description }) {
return (
<header className="recovery-live-hero" style={{ marginBottom: '2.5rem', borderBottom: '1px solid var(--border-soft)', pb: '1.5rem' }}>
<div style={{ maxWidth: '800px' }}>
<p className="eyebrow" style={{ color: 'var(--brand-color)', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
Theme Customization
</p>
<h2 style={{ fontSize: '2.25rem', marginBottom: '0.75rem' }}>{title}</h2>
<p style={{ color: 'var(--text-muted)', fontSize: '1.1rem', lineHeight: '1.6' }}>{description}</p>
</div>
</header>
);
}
// zot: Setting Item (Zone) component for form fields
function zot({ label, children, span = 1 }) {
return (
<div className={`recovery-table-card ${span === 2 ? 'span-2' : ''}`} style={{ padding: '1.25rem', background: 'var(--card-bg)', borderRadius: '12px', border: '1px solid var(--card-border)' }}>
<label style={{ display: 'block', marginBottom: '0.75rem', fontWeight: '500', color: 'var(--text-main)' }}>{label}</label>
<div className="form-control-wrapper">
{children}
</div>
</div>
);
}
// QKt: The main Nebula configuration component
function QKt() {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [form, setForm] = useState({
nebula_theme_color: 'aurora',
nebula_hero_slogan: '',
nebula_welcome_target: '',
nebula_register_title: '',
nebula_background_url: '',
nebula_metrics_base_url: '',
nebula_default_theme_mode: 'system',
nebula_light_logo_url: '',
nebula_dark_logo_url: '',
nebula_custom_html: '',
nebula_static_cdn_url: '',
});
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
useEffect(() => {
(async () => {
try {
const payload = await requestJson("/config/fetch?key=nebula");
if (payload?.nebula) {
setForm(prev => ({ ...prev, ...payload.nebula }));
}
} catch (err) {
setError("Failed to load configuration: " + err.message);
} finally {
setLoading(false);
}
})();
}, []);
const handleSave = async (e) => {
e.preventDefault();
setSaving(true);
setSuccess("");
setError("");
try {
await requestJson("/config/save", {
method: "POST",
body: form,
});
setSuccess("Nebula configuration updated successfully.");
window.scrollTo({ top: 0, behavior: 'smooth' });
} catch (err) {
setError("Failed to save configuration: " + err.message);
} finally {
setSaving(false);
}
};
if (loading) return <Wot><div className="empty-cell">Initializing Nebula settings...</div></Wot>;
return (
<Wot>
<Hot
title="Nebula Theme settings"
description="Optimize your user dashboard with specific Nebula theme settings and branding options."
/>
{error && <div className="toast toast-error" style={{ marginBottom: '1.5rem' }}>{error}</div>}
{success && <div className="toast toast-success" style={{ marginBottom: '1.5rem' }}>{success}</div>}
<form onSubmit={handleSave} className="modal-grid" style={{ maxWidth: '1200px' }}>
<zot label="Primary Accent Color" span={2}>
<select
value={form.nebula_theme_color}
onChange={e => setForm({...form, nebula_theme_color: e.target.value})}
className="recovery-input"
style={{ width: '100%', height: '42px' }}
>
<option value="aurora">极光蓝 (Aurora Blue)</option>
<option value="sunset">日落橙 (Sunset Orange)</option>
<option value="ember">余烬红 (Ember Red)</option>
<option value="violet">星云紫 (Violet Purple)</option>
</select>
</zot>
<zot label="Hero Slogan">
<input
className="recovery-input"
value={form.nebula_hero_slogan}
onChange={e => setForm({...form, nebula_hero_slogan: e.target.value})}
placeholder="Main visual headline"
/>
</zot>
<zot label="Welcome Target">
<input
className="recovery-input"
value={form.nebula_welcome_target}
onChange={e => setForm({...form, nebula_welcome_target: e.target.value})}
placeholder="Name displayed after WELCOME TO"
/>
</zot>
<zot label="Register Title">
<input
className="recovery-input"
value={form.nebula_register_title}
onChange={e => setForm({...form, nebula_register_title: e.target.value})}
placeholder="Title on registration panel"
/>
</zot>
<zot label="Default Appearance">
<select
value={form.nebula_default_theme_mode}
onChange={e => setForm({...form, nebula_default_theme_mode: e.target.value})}
className="recovery-input"
style={{ width: '100%', height: '42px' }}
>
<option value="system">Adaptive (System)</option>
<option value="dark">Dark Theme</option>
<option value="light">Light Theme</option>
</select>
</zot>
<zot label="Background Image URL">
<input
className="recovery-input"
value={form.nebula_background_url}
onChange={e => setForm({...form, nebula_background_url: e.target.value})}
placeholder="Direct link to background image"
/>
</zot>
<zot label="Metrics API Domain">
<input
className="recovery-input"
value={form.nebula_metrics_base_url}
onChange={e => setForm({...form, nebula_metrics_base_url: e.target.value})}
placeholder="https://stats.example.com"
/>
</zot>
<zot label="Light Mode Logo">
<input
className="recovery-input"
value={form.nebula_light_logo_url}
onChange={e => setForm({...form, nebula_light_logo_url: e.target.value})}
placeholder="Logo for light mode"
/>
</zot>
<zot label="Dark Mode Logo">
<input
className="recovery-input"
value={form.nebula_dark_logo_url}
onChange={e => setForm({...form, nebula_dark_logo_url: e.target.value})}
placeholder="Logo for dark mode"
/>
</zot>
<zot label="Static CDN Assets" span={2}>
<input
className="recovery-input"
value={form.nebula_static_cdn_url}
onChange={e => setForm({...form, nebula_static_cdn_url: e.target.value})}
placeholder="e.g. https://cdn.example.com/nebula (no trailing slash)"
/>
</zot>
<zot label="Custom Scripts / CSS" span={2}>
<textarea
className="recovery-input"
rows={6}
value={form.nebula_custom_html}
onChange={e => setForm({...form, nebula_custom_html: e.target.value})}
placeholder="Add analytics codes or custom styles here"
style={{ fontFamily: 'monospace' }}
/>
</zot>
<div className="span-2" style={{ marginTop: '2rem', display: 'flex', justifyContent: 'flex-end', borderTop: '1px solid var(--border-soft)', paddingTop: '1.5rem' }}>
<button type="submit" className="primary-btn" disabled={saving}>
{saving ? "Persisting..." : "Save Configuration"}
</button>
</div>
</form>
</Wot>
);
}
export default QKt;