Files
ele-HeiXiu/src/pages/settings/sections/UpdateSetting.tsx
YoungestSongMo 0e6d996718 refactor: 重组 components/ 和 pages/ 文件结构,按职责分层
components/ 拆为三层:
  - providers/   — 状态管理(App/Theme/SettingsProvider)
  - shell/       — 应用外壳(Layout/NavBar/PageDispatcher)
  - ui/          — 通用 UI 原子(FloatingPanel/LegalTextModal)

pages/ 重命名 + 重组:
  - headers/ → panels/        NavBar 触发的面板页
  - login/   → auth/          认证模块(含注册/忘记密码)
  - settings/blocks/ → sections/
  - home/components/ → panels/ features/ controls/ hooks/
    按职责分:panels(布局壳) + features(功能面板) + controls(表单控件) + hooks

其他清理:
  - 删除空占位 Test3.tsx
  - HomeContext 从 HomeContent 拆出独立文件
  - uploadValidation 内联到 ImageInput
  - 修复过时注释(useUI → controls)
2026-06-15 11:19:06 +08:00

220 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// UpdateSetting — 版本更新区块
// ============================================================
import { useState } from 'react';
import { Card, Button, Typography, Progress, Tag, Modal, Divider } from 'antd';
import {
SyncOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
RocketOutlined,
DownloadOutlined,
FileTextOutlined,
} from '@ant-design/icons';
import { useUpdater } from '@/hooks/use-updater';
import { isDev } from '@/utils/platform';
const { Text, Title } = Typography;
/** 简单渲染 changelogMarkdown → JSX支持 ## / - / --- */
function renderChangelog(markdown: string) {
const lines = markdown.split('\n');
const elements: React.ReactNode[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const key = `cl-${i}`;
if (line.startsWith('## ')) {
elements.push(
<Title key={key} level={5} className="!mt-3! !mb-1!">
{line.slice(3)}
</Title>,
);
continue;
}
if (/^-{3,}$/.test(line.trim())) {
elements.push(<Divider key={key} className="!my-2!" />);
continue;
}
if (line.trim().startsWith('- ')) {
elements.push(
<Text key={key} className="block pl-3 text-sm leading-relaxed">
{line.trim().slice(2)}
</Text>,
);
continue;
}
if (line.trim() === '') {
elements.push(<div key={key} className="h-2" />);
continue;
}
elements.push(
<Text key={key} className="block text-sm leading-relaxed">
{line}
</Text>,
);
}
return elements.length > 0 ? elements : <Text type="secondary"></Text>;
}
export function UpdateSetting() {
const { status, info, progress, error, checkForUpdates, downloadUpdate, installUpdate } =
useUpdater();
const [detailOpen, setDetailOpen] = useState(false);
return (
<Card size="small" title="版本更新" className="rounded-md!">
<div className="flex flex-col gap-3">
{/* ---- idle ---- */}
{status === 'idle' && (
<Text type="secondary" className="text-sm">
</Text>
)}
{/* ---- checking ---- */}
{status === 'checking' && (
<div className="flex items-center gap-2">
<SyncOutlined spin style={{ color: 'var(--color-primary, #4F46E5)' }} />
<Text type="secondary">...</Text>
</div>
)}
{/* ---- no-update ---- */}
{status === 'no-update' && (
<div className="flex items-center gap-2">
<CheckCircleOutlined style={{ color: 'var(--color-success, #52c41a)' }} />
<Text style={{ color: 'var(--color-success, #52c41a)' }}></Text>
</div>
)}
{/* ---- error ---- */}
{status === 'error' && (
<div className="flex items-center gap-2">
<ExclamationCircleOutlined style={{ color: 'var(--color-error, #ff4d4f)' }} />
<Text type="danger">{error?.message || '检查更新失败'}</Text>
</div>
)}
{/* ---- 发现新版本(等待用户决定)---- */}
{status === 'available' && (
<>
<div className="flex items-center gap-2">
<RocketOutlined style={{ color: 'var(--color-primary, #4F46E5)' }} />
<Text strong>{info?.version}</Text>
{info?.forceUpdate && <Tag color="red"></Tag>}
</div>
{info?.releaseNotes && (
<div className="flex items-start gap-2">
<Text
type="secondary"
className="text-xs whitespace-pre-line line-clamp-2 flex-1"
>
{info.releaseNotes
.replace(/^## .+?\n\n?/, '')
.replace(/\n---\n?[\s\S]*$/, '')
.trim()}
</Text>
<Button
type="link"
size="small"
icon={<FileTextOutlined />}
onClick={() => setDetailOpen(true)}
className="!px-1! flex-shrink-0"
>
</Button>
</div>
)}
<Button
type="primary"
size="small"
icon={<DownloadOutlined />}
onClick={downloadUpdate}
className="self-start"
>
</Button>
</>
)}
{/* ---- 下载中 ---- */}
{status === 'downloading' && (
<>
<div className="flex items-center gap-2">
<DownloadOutlined style={{ color: 'var(--color-primary, #4F46E5)' }} />
<Text> v{info?.version}...</Text>
</div>
<Progress
percent={progress.percent}
strokeColor={{ '0%': '#4F46E5', '100%': '#7C3AED' }}
size="small"
/>
</>
)}
{/* ---- 下载完成 ---- */}
{status === 'downloaded' && (
<>
<div className="flex items-center gap-2">
<CheckCircleOutlined style={{ color: 'var(--color-success, #52c41a)' }} />
<Text></Text>
</div>
<Button
type="primary"
icon={<DownloadOutlined />}
onClick={installUpdate}
size="small"
className="self-start"
>
</Button>
</>
)}
{/* ---- 操作按钮 ---- */}
<div className="flex items-center gap-3">
<Button
size="small"
icon={<SyncOutlined spin={status === 'checking'} />}
onClick={checkForUpdates}
disabled={status === 'checking' || status === 'downloading'}
>
</Button>
{isDev() && (
<Text type="secondary" className="text-xs">
5
</Text>
)}
</div>
</div>
{/* ---- 更新详情弹窗 ---- */}
<Modal
title={`更新详情 — v${info?.version || ''}`}
open={detailOpen}
onCancel={() => setDetailOpen(false)}
footer={<Button onClick={() => setDetailOpen(false)}></Button>}
width={520}
>
<div className="max-h-96 overflow-y-auto py-2">
{info?.releaseNotes ? (
renderChangelog(info.releaseNotes)
) : (
<Text type="secondary"></Text>
)}
</div>
</Modal>
</Card>
);
}