feat: add zustand for state management and refactor History component

This commit is contained in:
iHeyTang 2025-09-29 15:01:31 +08:00
parent 75372ffab5
commit 64569f843c
6 changed files with 75 additions and 70 deletions

View File

@ -43,7 +43,8 @@
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.0",
"redux": "^5.0.1", "redux": "^5.0.1",
"redux-thunk": "^3.1.0" "redux-thunk": "^3.1.0",
"zustand": "^5.0.8"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.8.1", "@commitlint/cli": "^19.8.1",

View File

@ -35,6 +35,9 @@ importers:
redux-thunk: redux-thunk:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0(redux@5.0.1) version: 3.1.0(redux@5.0.1)
zustand:
specifier: ^5.0.8
version: 5.0.8(@types/react@18.3.24)(immer@10.1.3)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1))
devDependencies: devDependencies:
'@commitlint/cli': '@commitlint/cli':
specifier: ^19.8.1 specifier: ^19.8.1
@ -3579,6 +3582,24 @@ packages:
resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==}
engines: {node: '>=12.20'} engines: {node: '>=12.20'}
zustand@5.0.8:
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots: snapshots:
'@adobe/css-tools@4.4.4': {} '@adobe/css-tools@4.4.4': {}
@ -7510,3 +7531,10 @@ snapshots:
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
yocto-queue@1.2.1: {} yocto-queue@1.2.1: {}
zustand@5.0.8(@types/react@18.3.24)(immer@10.1.3)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)):
optionalDependencies:
'@types/react': 18.3.24
immer: 10.1.3
react: 18.3.1
use-sync-external-store: 1.5.0(react@18.3.1)

View File

@ -1,10 +1,12 @@
import { SdkServer } from "../sdk/sdk-server"; import { SdkServer } from '../sdk/sdk-server';
export function useSdk() { export function useSdk() {
// H5版本暂时返回空对象广告相关功能不需要 // H5版本暂时返回空对象广告相关功能不需要
return {}; return {};
} }
export function useServerSdk(){ export function useServerSdk() {
return new SdkServer(); return new SdkServer();
} }
export const serverSdk = new SdkServer();

View File

@ -156,6 +156,8 @@
align-items: center; align-items: center;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
text-decoration: none;
color: #1d1f22;
} }
.item-thumbnail { .item-thumbnail {

View File

@ -1,23 +1,19 @@
import { useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { serverSdk } from '../../hooks/useSdk';
import { useServerSdk } from '../../hooks/index';
import { useI18n } from '../../hooks/useI18n'; import { useI18n } from '../../hooks/useI18n';
import { i18nManager } from '../../i18n/manager';
import { authService } from '../../services/auth'; import { authService } from '../../services/auth';
// import LanguageSwitcher from '../../components/LanguageSwitcher'; // 已隐藏 - 只支持英语
import './index.css'; import './index.css';
export default function History() { export default function History() {
const { t } = useI18n(); const { t } = useI18n();
const navigate = useNavigate();
const [records, setRecords] = useState<any[]>([]); const [records, setRecords] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState<boolean | null>(null); const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const serverSdk = useServerSdk();
const [loginRedirecting, setLoginRedirecting] = useState(false); const [loginRedirecting, setLoginRedirecting] = useState(false);
const checkLoginStatus = async () => { const checkLoginStatus = useCallback(async () => {
try { try {
const loginStatus = await authService.checkLogin(); const loginStatus = await authService.checkLogin();
setIsLoggedIn(loginStatus); setIsLoggedIn(loginStatus);
@ -27,9 +23,9 @@ export default function History() {
setIsLoggedIn(false); setIsLoggedIn(false);
return false; return false;
} }
}; }, []);
const loadRecords = async () => { const loadRecords = useCallback(async () => {
try { try {
const logs = await serverSdk.getMineLogs(); const logs = await serverSdk.getMineLogs();
setRecords(logs || []); setRecords(logs || []);
@ -38,7 +34,7 @@ export default function History() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; }, []);
useEffect(() => { useEffect(() => {
const initPage = async () => { const initPage = async () => {
@ -51,25 +47,10 @@ export default function History() {
}; };
initPage(); initPage();
// Set page title
i18nManager.updateNavigationBarTitle('history');
}, []); }, []);
// Regularly update progress of generating tasks
useEffect(() => {
const hasGeneratingTasks = records.some(record => record.status === 'processing');
if (!hasGeneratingTasks) return;
const interval = setInterval(() => {
// Trigger re-render to update progress
setRecords(prevRecords => [...prevRecords]);
}, 1000);
return () => clearInterval(interval);
}, [records]);
// Pull to refresh // Pull to refresh
const handleRefresh = async () => { const handleRefresh = useCallback(async () => {
setRefreshing(true); setRefreshing(true);
try { try {
await loadRecords(); await loadRecords();
@ -78,41 +59,16 @@ export default function History() {
} finally { } finally {
setRefreshing(false); setRefreshing(false);
} }
}; }, [loadRecords]);
// Click history record item const handleLogin = useCallback(async () => {
const handleItemClick = (record: any) => {
console.log('record', record);
if (record.status === 'failed') {
console.error('Refresh failed:', record.errorMessage);
// Show error message
} else {
console.log('record.paymentId', record);
// Navigate to generation page to view progress
navigate(`/result?taskId=${record.paymentId}`);
}
};
const formatTime = (timeStr: string) => {
const date = new Date(timeStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
const handleLogin = async () => {
try { try {
setLoginRedirecting(true); setLoginRedirecting(true);
await authService.login('/history'); await authService.login('/history');
} catch (error) { } catch (error) {
console.error('Login failed:', error); console.error('Login failed:', error);
} }
}; }, []);
if (loginRedirecting) { if (loginRedirecting) {
return ( return (
@ -122,6 +78,14 @@ export default function History() {
); );
} }
if (loading) {
return (
<div className="loading-state">
<span className="loading-text">{t('common.loading')}</span>
</div>
);
}
return ( return (
<div className="history"> <div className="history">
{/* Settings area */} {/* Settings area */}
@ -130,11 +94,7 @@ export default function History() {
{refreshing && <div className="refresh-indicator">Refreshing...</div>} {refreshing && <div className="refresh-indicator">Refreshing...</div>}
</div> </div>
<div className="history-grid"> <div className="history-grid">
{loading ? ( {isLoggedIn === false ? (
<div className="loading-state">
<span className="loading-text">{t('common.loading')}</span>
</div>
) : isLoggedIn === false ? (
<div className="login-required-state"> <div className="login-required-state">
<img className="login-icon-image" src="/assets/icons/user.png" alt="login" /> <img className="login-icon-image" src="/assets/icons/user.png" alt="login" />
<div className="login-content"> <div className="login-content">
@ -155,7 +115,7 @@ export default function History() {
// const progress = calculateProgress(record); // Temporarily unused // const progress = calculateProgress(record); // Temporarily unused
const isGenerating = record.status === 'processing'; const isGenerating = record.status === 'processing';
return ( return (
<div key={record.id} className="history-item" onClick={() => handleItemClick(record)}> <a key={record.id} className="history-item" href={`/result?taskId=${record.paymentId}`}>
<div className="item-thumbnail"> <div className="item-thumbnail">
{record.status === 'completed' && record.outputResult ? ( {record.status === 'completed' && record.outputResult ? (
<img className="thumbnail-image" src={record.inputImages?.[0] || ''} alt="thumbnail" /> <img className="thumbnail-image" src={record.inputImages?.[0] || ''} alt="thumbnail" />
@ -183,7 +143,7 @@ export default function History() {
<span className="item-time">{formatTime(record.createdAt)}</span> <span className="item-time">{formatTime(record.createdAt)}</span>
</div> </div>
</div> </div>
</div> </a>
); );
}) })
)} )}
@ -192,3 +152,15 @@ export default function History() {
</div> </div>
); );
} }
const formatTime = (timeStr: string) => {
const date = new Date(timeStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};

View File

@ -4,7 +4,7 @@ import { IToken, IUser } from "./types";
// ==================== 常量定义 ==================== // ==================== 常量定义 ====================
const TOKEN_STORAGE_KEY = 'access_token'; // OAuth访问令牌存储键 export const TOKEN_STORAGE_KEY = 'access_token'; // OAuth访问令牌存储键
const TOKEN_UID_KEY = 'user_uid'; // 用户ID存储键 const TOKEN_UID_KEY = 'user_uid'; // 用户ID存储键
/** /**