refactor(Phase 3): merge SystemUpdate + AboutUs into Settings page

- New Settings page combines: system update check, version info, auth/copyright
- Remove standalone AboutUs.tsx and SystemUpdate.tsx
- Route: 'about-us' + 'system-update' → 'settings'
- Profile menu: merge '系统设置' + '关于我们' into single '设置' entry
- Sidebar dropdown: add '设置' back to user menu (besides '我的账户')
This commit is contained in:
小鱼开发
2026-05-22 14:48:29 +08:00
parent 34d6f671fe
commit 91774f52ee
5 changed files with 123 additions and 160 deletions
+3 -8
View File
@@ -9,10 +9,7 @@ import Login from './pages/Login/Login';
import VideoCreation from './pages/VideoCreation';
import MyWorks from './pages/ContentManagement/MyWorks';
import VoiceMaterialLibrary from './pages/ContentManagement/VoiceMaterialLibrary';
import AboutUs from './pages/Settings/AboutUs';
import SystemUpdate from './pages/Settings/SystemUpdate';
import Settings from './pages/Settings/Settings';
import Profile from './pages/Profile/Profile';
import UsageDetail from './pages/Profile/UsageDetail';
import ToastContainer from './components/Toast/ToastContainer';
@@ -33,8 +30,7 @@ type PageType =
| 'video-creation'
| 'voice-material'
| 'my-works'
| 'about-us'
| 'system-update'
| 'settings'
| 'profile'
| 'usage-detail';
@@ -43,8 +39,7 @@ const pages: Record<PageType, React.ComponentType> = {
'video-creation': VideoCreation,
'voice-material': VoiceMaterialLibrary,
'my-works': MyWorks,
'about-us': AboutUs,
'system-update': SystemUpdate,
settings: Settings,
profile: Profile,
'usage-detail': UsageDetail,
};
@@ -35,6 +35,7 @@ interface SidebarProps {
const userMenuItems = [
{ id: 'profile', label: '我的账户' },
{ id: 'settings', label: '设置' },
];
export default function Sidebar({ currentPath, onNavigate }: SidebarProps) {
+1 -8
View File
@@ -33,12 +33,6 @@ const SettingsIcon = () => (
</svg>
);
const InfoIcon = () => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" /><line x1="12" y1="16" x2="12" y2="12" /><line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
);
const ChevronRightIcon = ({ className }: { className?: string }) => (
<svg className={className} width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="9 18 15 12 9 6" />
@@ -129,8 +123,7 @@ export default function Profile() {
const menuItems = [
{ label: '使用明细', icon: <FileTextIcon />, onClick: () => navigate('usage-detail') },
{ label: '系统设置', icon: <SettingsIcon />, onClick: () => navigate('system-update') },
{ label: '关于我们', icon: <InfoIcon />, onClick: () => navigate('about-us') },
{ label: '设置', icon: <SettingsIcon />, onClick: () => navigate('settings') },
];
return (
-113
View File
@@ -1,113 +0,0 @@
import { useState, useRef, useCallback } from 'react';
import '../ContentManagement/ContentManagement.css';
import AppHeader from '../../components/Layout/AppHeader';
import EnvironmentSwitchModal from '../../components/Modal/EnvironmentSwitchModal';
import { useNavigation } from '../../contexts/NavigationContext';
import { saveAppConfig } from '../../api/modules/config';
import { invoke } from '@tauri-apps/api/core';
import { toast } from '../../store/uiStore';
const CURRENT_VERSION = __APP_VERSION__;
export default function AboutUs() {
const { navigate, appEnvironment } = useNavigation();
const [showModal, setShowModal] = useState(false);
// 版本号连击检测
const clickCountRef = useRef(0);
const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleVersionClick = useCallback(() => {
clickCountRef.current += 1;
if (clickCountRef.current === 5) {
setShowModal(true);
clickCountRef.current = 0;
if (clickTimerRef.current) {clearTimeout(clickTimerRef.current);}
return;
}
if (clickTimerRef.current) {clearTimeout(clickTimerRef.current);}
clickTimerRef.current = setTimeout(() => {
clickCountRef.current = 0;
}, 2000);
}, []);
const handleSave = async (env: string) => {
try {
await saveAppConfig(env);
// 开发模式下 Vite dev server 重启后无法自动恢复,改用刷新页面
if (import.meta.env.DEV) {
toast.success('配置已保存,即将刷新');
setTimeout(() => {
window.location.reload();
}, 500);
return;
}
toast.success('配置已保存,应用即将重启');
setTimeout(() => {
invoke('restart_app');
}, 800);
} catch {
toast.error('保存配置失败');
}
};
return (
<div className="settings-page">
<AppHeader title="关于我们" showBack onBack={() => navigate('profile')} />
<div className="settings-section">
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
<div className="settings-row">
<span className="settings-row-label"></span>
<span className="settings-row-value"> </span>
</div>
<div className="settings-row" style={{ borderTop: '1px solid var(--border-light)' }}>
<span className="settings-row-label"></span>
<span
className="settings-row-value"
onClick={handleVersionClick}
style={{ cursor: 'default', userSelect: 'none' }}
title={appEnvironment !== 'production' ? `当前环境: ${appEnvironment}` : undefined}
>
v{CURRENT_VERSION}
</span>
</div>
</div>
</div>
<div className="settings-section">
<h2></h2>
<div className="card" style={{ padding: 'var(--spacing-xl)' }}>
<p style={{ lineHeight: 1.8, margin: 0 }}>
使
</p>
</div>
</div>
<div className="settings-section">
<h2></h2>
<div className="card" style={{ padding: 'var(--spacing-xl)' }}>
<p style={{ lineHeight: 1.8, margin: 0 }}>
Copyright 2025 (meijiaka.cn). All rights reserved.
</p>
<p style={{ lineHeight: 1.8, marginTop: 'var(--spacing-md)', marginBottom: 0 }}>
使
</p>
</div>
</div>
<EnvironmentSwitchModal
key={appEnvironment}
open={showModal}
currentEnv={appEnvironment}
onSave={handleSave}
onCancel={() => setShowModal(false)}
/>
</div>
);
}
@@ -1,21 +1,20 @@
/**
*
* ============
*
*
*/
import { useState } from 'react';
import { useState, useRef, useCallback } from 'react';
import { useNavigation } from '../../contexts/NavigationContext';
import AppHeader from '../../components/Layout/AppHeader';
import EnvironmentSwitchModal from '../../components/Modal/EnvironmentSwitchModal';
import { check, type Update, type DownloadEvent } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
import { saveAppConfig } from '../../api/modules/config';
import { invoke } from '@tauri-apps/api/core';
import { toast } from '../../store/uiStore';
import '../ContentManagement/ContentManagement.css';
const CURRENT_VERSION = __APP_VERSION__;
export default function SystemUpdate() {
const { navigate } = useNavigation();
export default function Settings() {
const { navigate, appEnvironment } = useNavigation();
// ── 系统更新状态 ──
const [checking, setChecking] = useState(false);
const [checkResult, setCheckResult] = useState<'none' | 'latest' | 'available'>('none');
const [updateInfo, setUpdateInfo] = useState<Update | null>(null);
@@ -24,7 +23,12 @@ export default function SystemUpdate() {
const [progress, setProgress] = useState(0);
const [downloadedBytes, setDownloadedBytes] = useState(0);
const [totalBytes, setTotalBytes] = useState(0);
const [error, setError] = useState<string | null>(null);
const [updateError, setUpdateError] = useState<string | null>(null);
// ── 环境切换 ──
const [showEnvModal, setShowEnvModal] = useState(false);
const clickCountRef = useRef(0);
const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const formatBytes = (bytes: number) => {
if (bytes === 0) return '0 B';
@@ -37,7 +41,7 @@ export default function SystemUpdate() {
const handleCheck = async () => {
setChecking(true);
setCheckResult('none');
setError(null);
setUpdateError(null);
setUpdateInfo(null);
try {
@@ -49,8 +53,8 @@ export default function SystemUpdate() {
setCheckResult('latest');
}
} catch (err) {
console.error('[SystemUpdate] 检查更新失败:', err);
setError(err instanceof Error ? err.message : '检查更新失败');
console.error('[Settings] 检查更新失败:', err);
setUpdateError(err instanceof Error ? err.message : '检查更新失败');
setCheckResult('none');
} finally {
setChecking(false);
@@ -64,9 +68,8 @@ export default function SystemUpdate() {
setProgress(0);
setDownloadedBytes(0);
setTotalBytes(0);
setError(null);
setUpdateError(null);
// 用局部变量保存总大小,避免 Progress 回调里的闭包问题
let totalSize = 0;
try {
@@ -94,8 +97,8 @@ export default function SystemUpdate() {
setDownloading(false);
setInstalling(true);
} catch (err) {
console.error('[SystemUpdate] 下载安装失败:', err);
setError(err instanceof Error ? err.message : '下载安装失败');
console.error('[Settings] 下载安装失败:', err);
setUpdateError(err instanceof Error ? err.message : '下载安装失败');
setDownloading(false);
}
};
@@ -104,22 +107,57 @@ export default function SystemUpdate() {
try {
await relaunch();
} catch (err) {
setError(err instanceof Error ? err.message : '重启失败');
setUpdateError(err instanceof Error ? err.message : '重启失败');
}
};
// ── 版本号连击 ──
const handleVersionClick = useCallback(() => {
clickCountRef.current += 1;
if (clickCountRef.current === 5) {
setShowEnvModal(true);
clickCountRef.current = 0;
if (clickTimerRef.current) { clearTimeout(clickTimerRef.current); }
return;
}
if (clickTimerRef.current) { clearTimeout(clickTimerRef.current); }
clickTimerRef.current = setTimeout(() => {
clickCountRef.current = 0;
}, 2000);
}, []);
const handleSaveEnv = async (env: string) => {
try {
await saveAppConfig(env);
if (import.meta.env.DEV) {
toast.success('配置已保存,即将刷新');
setTimeout(() => { window.location.reload(); }, 500);
return;
}
toast.success('配置已保存,应用即将重启');
setTimeout(() => { invoke('restart_app'); }, 800);
} catch {
toast.error('保存配置失败');
}
};
return (
<div className="settings-page">
<AppHeader title="系统更新" showBack onBack={() => navigate('profile')} />
<AppHeader title="设置" showBack onBack={() => navigate('profile')} />
{/* 系统更新 */}
<div className="settings-section">
<h2></h2>
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
{/* 当前版本 */}
<div className="settings-row">
<span className="settings-row-label"></span>
<span className="settings-row-value">v{CURRENT_VERSION}</span>
</div>
{/* 检查更新 */}
<div className="settings-row" style={{ borderTop: '1px solid var(--border-light)' }}>
<span className="settings-row-label"></span>
<div className="settings-row-value" style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-md)', justifyContent: 'flex-end' }}>
@@ -141,7 +179,6 @@ export default function SystemUpdate() {
</div>
</div>
{/* 更新详情 & 操作 */}
{checkResult === 'available' && updateInfo && (
<div style={{ padding: '16px 20px', borderTop: '1px solid var(--border-light)', background: 'var(--bg-input)' }}>
{updateInfo.body && (
@@ -151,7 +188,6 @@ export default function SystemUpdate() {
</div>
)}
{/* 下载进度 */}
{downloading && (
<div style={{ marginBottom: 12 }}>
<div style={{ height: 6, background: 'var(--border-light)', borderRadius: 3, overflow: 'hidden' }}>
@@ -164,12 +200,9 @@ export default function SystemUpdate() {
</div>
)}
{/* 按钮 */}
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
{installing ? (
<button className="btn btn-primary btn-sm" onClick={handleRelaunch}>
</button>
<button className="btn btn-primary btn-sm" onClick={handleRelaunch}></button>
) : (
<button className="btn btn-primary btn-sm" onClick={handleDownloadAndInstall} disabled={downloading}>
{downloading ? '下载中...' : '立即更新'}
@@ -179,14 +212,68 @@ export default function SystemUpdate() {
</div>
)}
{/* 错误 */}
{error && (
{updateError && (
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--border-light)', background: '#fef2f2', color: '#dc2626', fontSize: 'var(--font-sm)' }}>
{error}
{updateError}
</div>
)}
</div>
</div>
{/* 关于我们 */}
<div className="settings-section">
<h2></h2>
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
<div className="settings-row">
<span className="settings-row-label"></span>
<span className="settings-row-value"> </span>
</div>
<div className="settings-row" style={{ borderTop: '1px solid var(--border-light)' }}>
<span className="settings-row-label"></span>
<span
className="settings-row-value"
onClick={handleVersionClick}
style={{ cursor: 'default', userSelect: 'none' }}
title={appEnvironment !== 'production' ? `当前环境: ${appEnvironment}` : undefined}
>
v{CURRENT_VERSION}
</span>
</div>
</div>
</div>
{/* 授权信息 */}
<div className="settings-section">
<h2></h2>
<div className="card" style={{ padding: 'var(--spacing-xl)' }}>
<p style={{ lineHeight: 1.8, margin: 0 }}>
使
</p>
</div>
</div>
{/* 版权声明 */}
<div className="settings-section">
<h2></h2>
<div className="card" style={{ padding: 'var(--spacing-xl)' }}>
<p style={{ lineHeight: 1.8, margin: 0 }}>
Copyright 2025 (meijiaka.cn). All rights reserved.
</p>
<p style={{ lineHeight: 1.8, marginTop: 'var(--spacing-md)', marginBottom: 0 }}>
使
</p>
</div>
</div>
<EnvironmentSwitchModal
key={appEnvironment}
open={showEnvModal}
currentEnv={appEnvironment}
onSave={handleSaveEnv}
onCancel={() => setShowEnvModal(false)}
/>
</div>
);
}