feat: add zustand for state management and refactor History component
This commit is contained in:
parent
75372ffab5
commit
64569f843c
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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}`;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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存储键
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue