Skip to content

Commit e5fed9a

Browse files
committed
Enhance App and Settings components to support dynamic site settings, including site name, icon, and description. Update favicon and page title based on profile settings. Refactor Navbar to display site name and icon. Add new site settings management features in the Settings page, including input fields for site name, icon, and description, with save functionality. Improve translation support for site settings in i18n service.
1 parent 514e105 commit e5fed9a

5 files changed

Lines changed: 165 additions & 16 deletions

File tree

App.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,18 +249,37 @@ const App: React.FC = () => {
249249
loadConfig();
250250
}, []);
251251

252-
// 动态更新favicon
252+
// 动态更新页面标题、favicon 和 meta description
253253
useEffect(() => {
254-
if (profile.avatar) {
255-
const link = document.querySelector("link[rel*='icon']") as HTMLLinkElement || document.createElement('link');
256-
link.type = 'image/x-icon';
257-
link.rel = 'icon';
258-
link.href = profile.avatar;
259-
if (!document.querySelector("link[rel*='icon']")) {
254+
// 更新页面标题
255+
const siteName = profile.siteSettings?.siteName || profile.name || 'ZenBlog';
256+
document.title = siteName;
257+
258+
// 更新 favicon(优先使用 siteSettings.siteIcon,否则使用 profile.avatar)
259+
const iconUrl = profile.siteSettings?.siteIcon || profile.avatar;
260+
if (iconUrl) {
261+
let link = document.querySelector("link[rel*='icon']") as HTMLLinkElement;
262+
if (!link) {
263+
link = document.createElement('link');
264+
link.rel = 'icon';
260265
document.head.appendChild(link);
261266
}
267+
link.type = iconUrl.endsWith('.svg') ? 'image/svg+xml' : 'image/x-icon';
268+
link.href = iconUrl;
262269
}
263-
}, [profile.avatar]);
270+
271+
// 更新 meta description
272+
const siteDescription = profile.siteSettings?.siteDescription || profile.bio || '';
273+
if (siteDescription) {
274+
let metaDesc = document.querySelector("meta[name='description']") as HTMLMetaElement;
275+
if (!metaDesc) {
276+
metaDesc = document.createElement('meta');
277+
metaDesc.name = 'description';
278+
document.head.appendChild(metaDesc);
279+
}
280+
metaDesc.content = siteDescription;
281+
}
282+
}, [profile.siteSettings, profile.name, profile.avatar, profile.bio]);
264283

265284
useEffect(() => {
266285
const hideLoader = () => {

components/Navbar.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,13 @@ const Navbar: React.FC<NavbarProps> = ({ isAdmin, onToggleAdmin, profile }) => {
6161
<div className="flex items-center space-x-10">
6262
<Link to="/" className="flex items-center space-x-2 group">
6363
<img
64-
src={profile.avatar}
65-
alt={profile.name}
64+
src={profile.siteSettings?.siteIcon || profile.avatar}
65+
alt={profile.siteSettings?.siteName || profile.name}
6666
className="w-8 h-8 rounded-lg object-cover border-2 border-indigo-100 dark:border-indigo-900 group-hover:border-indigo-300 dark:group-hover:border-indigo-700 transition-all shadow-sm"
6767
/>
68-
<span className="text-xl font-black tracking-tight text-gray-900 dark:text-gray-100 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors">ZenBlog</span>
68+
<span className="text-xl font-black tracking-tight text-gray-900 dark:text-gray-100 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors">
69+
{profile.siteSettings?.siteName || profile.name || 'ZenBlog'}
70+
</span>
6971
</Link>
7072

7173
<div className="hidden md:flex space-x-8">

pages/Settings.tsx

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState } from 'react';
2-
import { Shield, Github, Save, CheckCircle, Database, ExternalLink, AlertTriangle, Layers, User, Settings as SettingsIcon } from 'lucide-react';
2+
import { Shield, Github, Save, CheckCircle, Database, ExternalLink, AlertTriangle, Layers, User, Settings as SettingsIcon, Globe } from 'lucide-react';
33
import { GitHubConfig, Profile } from '../types';
44
import { motion } from 'framer-motion';
55
import { useLanguage } from '../App';
@@ -25,8 +25,14 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
2525
const [avatar, setAvatar] = useState(profile.avatar);
2626
const [githubUrl, setGithubUrl] = useState(profile.socials.github || '');
2727

28+
// 站点设置
29+
const [siteName, setSiteName] = useState(profile.siteSettings?.siteName || '');
30+
const [siteIcon, setSiteIcon] = useState(profile.siteSettings?.siteIcon || '');
31+
const [siteDescription, setSiteDescription] = useState(profile.siteSettings?.siteDescription || '');
32+
2833
const [savingConfig, setSavingConfig] = useState(false);
2934
const [savingProfile, setSavingProfile] = useState(false);
35+
const [savingSiteSettings, setSavingSiteSettings] = useState(false);
3036

3137
// 保存GitHub配置(仅保存token,repository信息从config.json读取)
3238
const handleSaveConfig = async () => {
@@ -63,6 +69,23 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
6369
}
6470
};
6571

72+
// 保存站点设置
73+
const handleSaveSiteSettings = async () => {
74+
setSavingSiteSettings(true);
75+
try {
76+
await onSaveProfile({
77+
...profile,
78+
siteSettings: {
79+
siteName,
80+
siteIcon,
81+
siteDescription
82+
}
83+
});
84+
} finally {
85+
setSavingSiteSettings(false);
86+
}
87+
};
88+
6689
return (
6790
<div className="max-w-5xl mx-auto pb-20 md:pb-0">
6891
{/* 桌面端顶部 */}
@@ -77,7 +100,7 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
77100
<div className="flex gap-3">
78101
<button
79102
onClick={handleSaveConfig}
80-
disabled={savingConfig || savingProfile}
103+
disabled={savingConfig || savingProfile || savingSiteSettings}
81104
className="flex items-center justify-center px-6 py-4 border-2 border-indigo-300 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 rounded-[1.5rem] font-black hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-all active:scale-95 disabled:opacity-50 uppercase tracking-widest text-xs"
82105
>
83106
{savingConfig ? (
@@ -89,7 +112,7 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
89112
</button>
90113
<button
91114
onClick={handleSaveProfile}
92-
disabled={savingConfig || savingProfile}
115+
disabled={savingConfig || savingProfile || savingSiteSettings}
93116
className="flex items-center justify-center px-6 py-4 border-2 border-indigo-300 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 rounded-[1.5rem] font-black hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-all active:scale-95 disabled:opacity-50 uppercase tracking-widest text-xs"
94117
>
95118
{savingProfile ? (
@@ -99,6 +122,18 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
99122
)}
100123
{savingProfile ? t.settings.saving : t.settings.saveProfile}
101124
</button>
125+
<button
126+
onClick={handleSaveSiteSettings}
127+
disabled={savingConfig || savingProfile || savingSiteSettings}
128+
className="flex items-center justify-center px-6 py-4 border-2 border-indigo-300 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 rounded-[1.5rem] font-black hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-all active:scale-95 disabled:opacity-50 uppercase tracking-widest text-xs"
129+
>
130+
{savingSiteSettings ? (
131+
<div className="w-5 h-5 border-2 border-indigo-600 dark:text-indigo-400 border-t-transparent rounded-full animate-spin mr-2"></div>
132+
) : (
133+
<Globe size={18} className="mr-2" />
134+
)}
135+
{savingSiteSettings ? t.settings.saving : t.settings.saveSiteSettings}
136+
</button>
102137
</div>
103138
</div>
104139

@@ -239,6 +274,59 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
239274
</div>
240275
</div>
241276
</motion.section>
277+
278+
{/* 站点设置 */}
279+
<motion.section
280+
initial={{ opacity: 0, y: 20 }}
281+
animate={{ opacity: 1, y: 0 }}
282+
transition={{ delay: 0.2 }}
283+
className="bg-white dark:bg-gray-800 p-4 md:p-10 rounded-xl md:rounded-[3rem] shadow-sm border border-gray-100 dark:border-gray-700"
284+
>
285+
<div className="flex items-center space-x-3 mb-5 md:mb-10">
286+
<div className="p-2 md:p-3 bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded-xl md:rounded-2xl">
287+
<Globe size={20} className="md:w-6 md:h-6" />
288+
</div>
289+
<div>
290+
<h2 className="text-lg md:text-2xl font-black text-gray-900 dark:text-gray-100 tracking-tight">{t.settings.siteSettings}</h2>
291+
<p className="text-xs text-gray-400 dark:text-gray-500 font-bold uppercase tracking-widest mt-0.5">{t.settings.siteSettingsDesc}</p>
292+
</div>
293+
</div>
294+
295+
<div className="space-y-4 md:space-y-8">
296+
<div>
297+
<label className="block text-xs md:text-sm font-black text-gray-900 dark:text-gray-100 uppercase tracking-widest mb-2 md:mb-3">{t.settings.siteName}</label>
298+
<input
299+
type="text"
300+
value={siteName}
301+
onChange={(e) => setSiteName(e.target.value)}
302+
placeholder="例如:我的个人博客"
303+
className="w-full px-3 md:px-5 py-2.5 md:py-4 bg-gray-50 dark:bg-gray-700 border border-gray-100 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg md:rounded-2xl focus:ring-2 md:focus:ring-4 focus:ring-indigo-50 dark:focus:ring-indigo-900/30 outline-none transition-all font-bold"
304+
/>
305+
</div>
306+
<div>
307+
<label className="block text-xs md:text-sm font-black text-gray-900 dark:text-gray-100 uppercase tracking-widest mb-2 md:mb-3">{t.settings.siteIcon}</label>
308+
<input
309+
type="text"
310+
value={siteIcon}
311+
onChange={(e) => setSiteIcon(e.target.value)}
312+
placeholder="https://example.com/favicon.ico"
313+
className="w-full px-3 md:px-5 py-2.5 md:py-4 bg-gray-50 dark:bg-gray-700 border border-gray-100 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg md:rounded-2xl focus:ring-2 md:focus:ring-4 focus:ring-indigo-50 dark:focus:ring-indigo-900/30 outline-none transition-all"
314+
/>
315+
<p className="mt-2 text-[10px] text-gray-400 dark:text-gray-500 font-bold uppercase tracking-widest italic">{t.settings.siteIconHint}</p>
316+
</div>
317+
<div>
318+
<label className="block text-xs md:text-sm font-black text-gray-900 dark:text-gray-100 uppercase tracking-widest mb-2 md:mb-3">{t.settings.siteDescription}</label>
319+
<textarea
320+
value={siteDescription}
321+
onChange={(e) => setSiteDescription(e.target.value)}
322+
rows={3}
323+
placeholder="例如:分享技术、生活与思考的个人博客"
324+
className="w-full px-3 md:px-5 py-2.5 md:py-4 bg-gray-50 dark:bg-gray-700 border border-gray-100 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg md:rounded-2xl focus:ring-2 md:focus:ring-4 focus:ring-indigo-50 dark:focus:ring-indigo-900/30 outline-none resize-none transition-all font-medium leading-relaxed"
325+
/>
326+
<p className="mt-2 text-[10px] text-gray-400 dark:text-gray-500 font-bold uppercase tracking-widest italic">{t.settings.siteDescriptionHint}</p>
327+
</div>
328+
</div>
329+
</motion.section>
242330
</div>
243331

244332
<aside className="space-y-4 md:space-y-8">
@@ -278,7 +366,7 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
278366
<div className="flex gap-2">
279367
<button
280368
onClick={handleSaveConfig}
281-
disabled={savingConfig || savingProfile}
369+
disabled={savingConfig || savingProfile || savingSiteSettings}
282370
className="flex-1 flex items-center justify-center px-4 py-3 border-2 border-indigo-300 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 rounded-xl font-black hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-all active:scale-95 disabled:opacity-50 uppercase tracking-widest text-xs"
283371
>
284372
{savingConfig ? (
@@ -295,7 +383,7 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
295383
</button>
296384
<button
297385
onClick={handleSaveProfile}
298-
disabled={savingConfig || savingProfile}
386+
disabled={savingConfig || savingProfile || savingSiteSettings}
299387
className="flex-1 flex items-center justify-center px-4 py-3 border-2 border-indigo-300 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 rounded-xl font-black hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-all active:scale-95 disabled:opacity-50 uppercase tracking-widest text-xs"
300388
>
301389
{savingProfile ? (
@@ -310,6 +398,23 @@ const Settings: React.FC<SettingsProps> = ({ config, profile, onSaveConfig, onSa
310398
</>
311399
)}
312400
</button>
401+
<button
402+
onClick={handleSaveSiteSettings}
403+
disabled={savingConfig || savingProfile || savingSiteSettings}
404+
className="flex-1 flex items-center justify-center px-4 py-3 border-2 border-indigo-300 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 rounded-xl font-black hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-all active:scale-95 disabled:opacity-50 uppercase tracking-widest text-xs"
405+
>
406+
{savingSiteSettings ? (
407+
<>
408+
<div className="w-4 h-4 border-2 border-indigo-600 dark:text-indigo-400 border-t-transparent rounded-full animate-spin mr-2"></div>
409+
{t.settings.saving}
410+
</>
411+
) : (
412+
<>
413+
<Globe size={16} className="mr-1.5" />
414+
{t.settings.saveSiteSettings}
415+
</>
416+
)}
417+
</button>
313418
</div>
314419
</motion.div>
315420
</div>

services/i18n.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ export const translations = {
209209
enterToken: '请输入GitHub Token',
210210
configRequired: '请先确保 config.json 已正确配置',
211211
configReadOnly: '配置信息从 config.json 读取,不可在此修改',
212+
siteSettings: '站点设置',
213+
siteSettingsDesc: '配置网站的基本信息',
214+
siteName: '网站名称',
215+
siteIcon: '网站图标',
216+
siteIconHint: '图标链接(favicon)',
217+
siteDescription: '网站描述',
218+
siteDescriptionHint: '用于 SEO 和社交媒体分享',
219+
saveSiteSettings: '保存站点设置',
212220
},
213221
common: {
214222
loading: '正在同步内容...',
@@ -431,6 +439,14 @@ export const translations = {
431439
enterToken: 'Please enter GitHub Token',
432440
configRequired: 'Please ensure config.json is properly configured',
433441
configReadOnly: 'Configuration is read from config.json and cannot be modified here',
442+
siteSettings: 'Site Settings',
443+
siteSettingsDesc: 'Configure basic site information',
444+
siteName: 'Site Name',
445+
siteIcon: 'Site Icon',
446+
siteIconHint: 'Icon URL (favicon)',
447+
siteDescription: 'Site Description',
448+
siteDescriptionHint: 'For SEO and social media sharing',
449+
saveSiteSettings: 'Save Site Settings',
434450
},
435451
common: {
436452
loading: 'Synchronizing Content...',

types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ export interface AboutData {
6666
games?: Game[]; // 喜欢玩的游戏
6767
}
6868

69+
export interface SiteSettings {
70+
siteName?: string; // 网站名
71+
siteIcon?: string; // 网站图标链接
72+
siteDescription?: string; // 网站描述
73+
}
74+
6975
export interface Profile {
7076
name: string;
7177
bio: string;
@@ -76,6 +82,7 @@ export interface Profile {
7682
email?: string;
7783
};
7884
about?: AboutData; // 关于页面数据
85+
siteSettings?: SiteSettings; // 站点设置
7986
}
8087

8188
export interface GitHubConfig {

0 commit comments

Comments
 (0)