mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
feat(report): improve years loading status messaging
This commit is contained in:
@@ -26,6 +26,14 @@
|
|||||||
margin: 0 0 48px;
|
margin: 0 0 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-desc.load-summary {
|
||||||
|
margin: 0 0 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc.load-summary.complete {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.report-sections {
|
.report-sections {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -83,6 +91,14 @@
|
|||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.year-grid-with-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.year-grid {
|
.year-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -95,7 +111,39 @@
|
|||||||
.report-section .year-grid {
|
.report-section .year-grid {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-grid-with-status .year-grid {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-load-status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-load-status.complete {
|
||||||
|
color: color-mix(in srgb, var(--primary) 80%, var(--text-secondary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-ellipsis {
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: bottom;
|
||||||
|
animation: dot-ellipsis 1.2s steps(4, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-load-status.complete .dot-ellipsis,
|
||||||
|
.page-desc.load-summary.complete .dot-ellipsis {
|
||||||
|
animation: none;
|
||||||
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.year-card {
|
.year-card {
|
||||||
@@ -185,3 +233,7 @@
|
|||||||
from { transform: rotate(0deg); }
|
from { transform: rotate(0deg); }
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes dot-ellipsis {
|
||||||
|
to { width: 1.4em; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ import './AnnualReportPage.scss'
|
|||||||
|
|
||||||
type YearOption = number | 'all'
|
type YearOption = number | 'all'
|
||||||
|
|
||||||
|
const formatLoadElapsed = (ms: number) => {
|
||||||
|
const totalSeconds = Math.max(0, ms) / 1000
|
||||||
|
if (totalSeconds < 60) return `${totalSeconds.toFixed(1)}s`
|
||||||
|
const minutes = Math.floor(totalSeconds / 60)
|
||||||
|
const seconds = Math.floor(totalSeconds % 60)
|
||||||
|
return `${minutes}m ${String(seconds).padStart(2, '0')}s`
|
||||||
|
}
|
||||||
|
|
||||||
function AnnualReportPage() {
|
function AnnualReportPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [availableYears, setAvailableYears] = useState<number[]>([])
|
const [availableYears, setAvailableYears] = useState<number[]>([])
|
||||||
@@ -12,12 +20,33 @@ function AnnualReportPage() {
|
|||||||
const [selectedPairYear, setSelectedPairYear] = useState<YearOption | null>(null)
|
const [selectedPairYear, setSelectedPairYear] = useState<YearOption | null>(null)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [isLoadingMoreYears, setIsLoadingMoreYears] = useState(false)
|
const [isLoadingMoreYears, setIsLoadingMoreYears] = useState(false)
|
||||||
|
const [hasYearsLoadFinished, setHasYearsLoadFinished] = useState(false)
|
||||||
|
const [loadElapsedMs, setLoadElapsedMs] = useState(0)
|
||||||
const [isGenerating, setIsGenerating] = useState(false)
|
const [isGenerating, setIsGenerating] = useState(false)
|
||||||
const [loadError, setLoadError] = useState<string | null>(null)
|
const [loadError, setLoadError] = useState<string | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let disposed = false
|
let disposed = false
|
||||||
let taskId = ''
|
let taskId = ''
|
||||||
|
const loadStartedAt = Date.now()
|
||||||
|
let ticker: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
const startTicker = () => {
|
||||||
|
setLoadElapsedMs(0)
|
||||||
|
if (ticker) clearInterval(ticker)
|
||||||
|
ticker = setInterval(() => {
|
||||||
|
if (disposed) return
|
||||||
|
setLoadElapsedMs(Date.now() - loadStartedAt)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopTicker = () => {
|
||||||
|
setLoadElapsedMs(Date.now() - loadStartedAt)
|
||||||
|
if (ticker) {
|
||||||
|
clearInterval(ticker)
|
||||||
|
ticker = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const stopListen = window.electronAPI.annualReport.onAvailableYearsProgress((payload) => {
|
const stopListen = window.electronAPI.annualReport.onAvailableYearsProgress((payload) => {
|
||||||
if (disposed) return
|
if (disposed) return
|
||||||
@@ -48,21 +77,27 @@ function AnnualReportPage() {
|
|||||||
if (payload.done) {
|
if (payload.done) {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
setIsLoadingMoreYears(false)
|
setIsLoadingMoreYears(false)
|
||||||
|
setHasYearsLoadFinished(true)
|
||||||
|
stopTicker()
|
||||||
} else {
|
} else {
|
||||||
setIsLoadingMoreYears(true)
|
setIsLoadingMoreYears(true)
|
||||||
|
setHasYearsLoadFinished(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const startLoad = async () => {
|
const startLoad = async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setIsLoadingMoreYears(true)
|
setIsLoadingMoreYears(true)
|
||||||
|
setHasYearsLoadFinished(false)
|
||||||
setLoadError(null)
|
setLoadError(null)
|
||||||
|
startTicker()
|
||||||
try {
|
try {
|
||||||
const startResult = await window.electronAPI.annualReport.startAvailableYearsLoad()
|
const startResult = await window.electronAPI.annualReport.startAvailableYearsLoad()
|
||||||
if (!startResult.success || !startResult.taskId) {
|
if (!startResult.success || !startResult.taskId) {
|
||||||
setLoadError(startResult.error || '加载年度数据失败')
|
setLoadError(startResult.error || '加载年度数据失败')
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
setIsLoadingMoreYears(false)
|
setIsLoadingMoreYears(false)
|
||||||
|
stopTicker()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
taskId = startResult.taskId
|
taskId = startResult.taskId
|
||||||
@@ -71,6 +106,7 @@ function AnnualReportPage() {
|
|||||||
setLoadError(String(e))
|
setLoadError(String(e))
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
setIsLoadingMoreYears(false)
|
setIsLoadingMoreYears(false)
|
||||||
|
stopTicker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +114,7 @@ function AnnualReportPage() {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposed = true
|
disposed = true
|
||||||
|
stopTicker()
|
||||||
stopListen()
|
stopListen()
|
||||||
if (taskId) {
|
if (taskId) {
|
||||||
void window.electronAPI.annualReport.cancelAvailableYearsLoad(taskId)
|
void window.electronAPI.annualReport.cancelAvailableYearsLoad(taskId)
|
||||||
@@ -109,6 +146,7 @@ function AnnualReportPage() {
|
|||||||
<div className="annual-report-page">
|
<div className="annual-report-page">
|
||||||
<Loader2 size={32} className="spin" style={{ color: 'var(--text-tertiary)' }} />
|
<Loader2 size={32} className="spin" style={{ color: 'var(--text-tertiary)' }} />
|
||||||
<p style={{ color: 'var(--text-tertiary)', marginTop: 16 }}>正在加载年份数据(首批)...</p>
|
<p style={{ color: 'var(--text-tertiary)', marginTop: 16 }}>正在加载年份数据(首批)...</p>
|
||||||
|
<p style={{ color: 'var(--text-tertiary)', marginTop: 8 }}>已耗时 {formatLoadElapsed(loadElapsedMs)}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -134,13 +172,36 @@ function AnnualReportPage() {
|
|||||||
return value === 'all' ? '全部时间' : `${value} 年`
|
return value === 'all' ? '全部时间' : `${value} 年`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadedYearCount = availableYears.length
|
||||||
|
const isYearStatusComplete = hasYearsLoadFinished
|
||||||
|
const renderYearLoadStatus = () => (
|
||||||
|
<div className={`year-load-status ${isYearStatusComplete ? 'complete' : 'loading'}`}>
|
||||||
|
{isYearStatusComplete ? (
|
||||||
|
<>全部年份已加载完毕</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
更多年份加载中<span className="dot-ellipsis" aria-hidden="true">...</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="annual-report-page">
|
<div className="annual-report-page">
|
||||||
<Sparkles size={32} className="header-icon" />
|
<Sparkles size={32} className="header-icon" />
|
||||||
<h1 className="page-title">年度报告</h1>
|
<h1 className="page-title">年度报告</h1>
|
||||||
<p className="page-desc">选择年份,回顾你在微信里的点点滴滴</p>
|
<p className="page-desc">选择年份,回顾你在微信里的点点滴滴</p>
|
||||||
{isLoadingMoreYears && (
|
{loadedYearCount > 0 && (
|
||||||
<p className="page-desc">已显示首批年份,正在补充更多年份...</p>
|
<p className={`page-desc load-summary ${isYearStatusComplete ? 'complete' : 'loading'}`}>
|
||||||
|
{isYearStatusComplete ? (
|
||||||
|
<>已显示 {loadedYearCount} 个年份,年份数据已全部加载完毕。总耗时 {formatLoadElapsed(loadElapsedMs)}</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
已显示 {loadedYearCount} 个年份,正在补充更多年份<span className="dot-ellipsis" aria-hidden="true">...</span>
|
||||||
|
(已耗时 {formatLoadElapsed(loadElapsedMs)})
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="report-sections">
|
<div className="report-sections">
|
||||||
@@ -152,17 +213,20 @@ function AnnualReportPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="year-grid">
|
<div className="year-grid-with-status">
|
||||||
{yearOptions.map(option => (
|
<div className="year-grid">
|
||||||
<div
|
{yearOptions.map(option => (
|
||||||
key={option}
|
<div
|
||||||
className={`year-card ${option === 'all' ? 'all-time' : ''} ${selectedYear === option ? 'selected' : ''}`}
|
key={option}
|
||||||
onClick={() => setSelectedYear(option)}
|
className={`year-card ${option === 'all' ? 'all-time' : ''} ${selectedYear === option ? 'selected' : ''}`}
|
||||||
>
|
onClick={() => setSelectedYear(option)}
|
||||||
<span className="year-number">{option === 'all' ? '全部' : option}</span>
|
>
|
||||||
<span className="year-label">{option === 'all' ? '时间' : '年'}</span>
|
<span className="year-number">{option === 'all' ? '全部' : option}</span>
|
||||||
</div>
|
<span className="year-label">{option === 'all' ? '时间' : '年'}</span>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{renderYearLoadStatus()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -196,17 +260,20 @@ function AnnualReportPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="year-grid">
|
<div className="year-grid-with-status">
|
||||||
{yearOptions.map(option => (
|
<div className="year-grid">
|
||||||
<div
|
{yearOptions.map(option => (
|
||||||
key={`pair-${option}`}
|
<div
|
||||||
className={`year-card ${option === 'all' ? 'all-time' : ''} ${selectedPairYear === option ? 'selected' : ''}`}
|
key={`pair-${option}`}
|
||||||
onClick={() => setSelectedPairYear(option)}
|
className={`year-card ${option === 'all' ? 'all-time' : ''} ${selectedPairYear === option ? 'selected' : ''}`}
|
||||||
>
|
onClick={() => setSelectedPairYear(option)}
|
||||||
<span className="year-number">{option === 'all' ? '全部' : option}</span>
|
>
|
||||||
<span className="year-label">{option === 'all' ? '时间' : '年'}</span>
|
<span className="year-number">{option === 'all' ? '全部' : option}</span>
|
||||||
</div>
|
<span className="year-label">{option === 'all' ? '时间' : '年'}</span>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{renderYearLoadStatus()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user