Skip to content

Commit 7f650aa

Browse files
committed
Optimize UI loading speed
1 parent 6ccc647 commit 7f650aa

6 files changed

Lines changed: 664 additions & 146 deletions

File tree

src/App.css

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* 骨架屏脉动动画 */
2+
@keyframes pulse {
3+
0% {
4+
opacity: 1;
5+
background-color: #f0f0f0;
6+
}
7+
8+
50% {
9+
opacity: 0.4;
10+
background-color: #e0e0e0;
11+
}
12+
13+
100% {
14+
opacity: 1;
15+
background-color: #f0f0f0;
16+
}
17+
}
18+
19+
/* 应用图标样式优化 */
20+
.app-icon {
21+
transition: transform 0.2s ease, opacity 0.3s ease;
22+
}
23+
24+
.app-icon:hover {
25+
transform: scale(1.05);
26+
}
27+
28+
/* 图片加载渐变效果 */
29+
.app-image-container {
30+
position: relative;
31+
overflow: hidden;
32+
}
33+
34+
.app-image-loading {
35+
opacity: 0;
36+
animation: fadeIn 0.3s ease forwards;
37+
}
38+
39+
@keyframes fadeIn {
40+
from {
41+
opacity: 0;
42+
}
43+
44+
to {
45+
opacity: 1;
46+
}
47+
}
48+
49+
/* 骨架屏容器 */
50+
.skeleton-container {
51+
display: flex;
52+
align-items: center;
53+
justify-content: center;
54+
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
55+
background-size: 200% 100%;
56+
animation: shimmer 1.5s infinite;
57+
}
58+
59+
@keyframes shimmer {
60+
0% {
61+
background-position: -200% 0;
62+
}
63+
64+
100% {
65+
background-position: 200% 0;
66+
}
67+
}

src/helpers/api/apiCore.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,4 @@ class APICore {
260260
};
261261
}
262262

263-
export { APICore };
263+
export { APICore, getNginxConfig, getApiKey };

src/helpers/api/appHub.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,11 @@ function GetSettingsBySection(section, params) {
2626
return api.get(`${baseUrl}`, params);
2727
}
2828

29-
export { AppAvailable, AppCatalog, AppInstall, GetSettingsBySection };
29+
// 更新设置
30+
function UpdateSettingsBySection(section, key, value, params) {
31+
const baseUrl = `/settings/${section}`;
32+
return api.put(`${baseUrl}`, null, { ...params, key, value });
33+
}
34+
35+
export { AppAvailable, AppCatalog, AppInstall, GetSettingsBySection, UpdateSettingsBySection };
3036

src/helpers/api/configManager.js

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import cockpit from 'cockpit';
2+
import { getNginxConfig, getApiKey } from './apiCore';
3+
4+
class ConfigManager {
5+
constructor() {
6+
this.config = null;
7+
this.initializing = false;
8+
this.initPromise = null;
9+
this.cacheTimeout = 24 * 60 * 60 * 1000; // 24小时缓存
10+
}
11+
12+
async initialize() {
13+
if (this.config) {
14+
console.log('[ConfigManager] Using cached config:', this.config);
15+
return this.config;
16+
}
17+
18+
if (this.initializing) {
19+
console.log('[ConfigManager] Initialization in progress, waiting...');
20+
return this.initPromise;
21+
}
22+
23+
console.log('[ConfigManager] Starting config initialization...');
24+
this.initializing = true;
25+
this.initPromise = this._fetchConfig();
26+
27+
try {
28+
this.config = await this.initPromise;
29+
return this.config;
30+
} finally {
31+
this.initializing = false;
32+
}
33+
}
34+
35+
async _fetchConfig() {
36+
console.time('[ConfigManager] Total config fetch time');
37+
38+
try {
39+
// 先尝试从本地缓存获取(如果最近获取过且未过期)
40+
const cachedConfig = this._getCachedConfig();
41+
if (cachedConfig && this._isCacheValid(cachedConfig)) {
42+
console.log('[ConfigManager] Using valid cache from localStorage');
43+
console.timeEnd('[ConfigManager] Total config fetch time');
44+
return cachedConfig.config;
45+
}
46+
47+
console.log('[ConfigManager] Fetching fresh config...');
48+
49+
// 并行获取所有配置(利用apiCore中已有的缓存机制)
50+
console.time('[ConfigManager] Config fetch');
51+
const [nginxPort, apiKey] = await Promise.all([
52+
getNginxConfig(),
53+
getApiKey().catch(() => null) // API key 可选,失败时返回 null
54+
]);
55+
console.timeEnd('[ConfigManager] Config fetch');
56+
57+
const config = {
58+
nginxPort,
59+
apiKey,
60+
baseURL: `${window.location.protocol}//${window.location.hostname}:${nginxPort}`,
61+
apiURL: `${window.location.protocol}//${window.location.hostname}:${nginxPort}/api`,
62+
timestamp: Date.now()
63+
};
64+
65+
// 缓存到本地存储
66+
this._saveToCache(config);
67+
68+
console.log('[ConfigManager] Config initialized successfully:', config);
69+
console.timeEnd('[ConfigManager] Total config fetch time');
70+
71+
return config;
72+
} catch (error) {
73+
console.error('[ConfigManager] Config initialization failed:', error);
74+
75+
// 尝试使用过期的缓存作为fallback
76+
const cachedConfig = this._getCachedConfig();
77+
if (cachedConfig) {
78+
console.warn('[ConfigManager] Using expired cache as fallback');
79+
return cachedConfig.config;
80+
}
81+
82+
// 最后的fallback:默认配置
83+
console.warn('[ConfigManager] Using default config as last resort');
84+
const defaultConfig = this._getDefaultConfig();
85+
console.timeEnd('[ConfigManager] Total config fetch time');
86+
return defaultConfig;
87+
}
88+
}
89+
90+
_getCachedConfig() {
91+
try {
92+
const cached = localStorage.getItem('websoft9_app_config_cache');
93+
return cached ? JSON.parse(cached) : null;
94+
} catch (error) {
95+
console.warn('[ConfigManager] Failed to read cache:', error);
96+
return null;
97+
}
98+
}
99+
100+
_isCacheValid(cachedData) {
101+
if (!cachedData || !cachedData.timestamp) {
102+
return false;
103+
}
104+
return (Date.now() - cachedData.timestamp) < this.cacheTimeout;
105+
}
106+
107+
_saveToCache(config) {
108+
try {
109+
const cacheData = {
110+
config,
111+
timestamp: Date.now()
112+
};
113+
localStorage.setItem('websoft9_app_config_cache', JSON.stringify(cacheData));
114+
console.log('[ConfigManager] Config saved to cache');
115+
} catch (error) {
116+
console.warn('[ConfigManager] Failed to save cache:', error);
117+
}
118+
}
119+
120+
_getDefaultConfig() {
121+
return {
122+
nginxPort: 9000,
123+
apiKey: null,
124+
baseURL: `${window.location.protocol}//${window.location.hostname}:9000`,
125+
apiURL: `${window.location.protocol}//${window.location.hostname}:9000/api`,
126+
timestamp: Date.now()
127+
};
128+
}
129+
130+
getConfig() {
131+
return this.config || this._getDefaultConfig();
132+
}
133+
134+
// 清除缓存(用于调试或强制刷新)
135+
clearCache() {
136+
try {
137+
localStorage.removeItem('websoft9_app_config_cache');
138+
this.config = null;
139+
this.initializing = false;
140+
this.initPromise = null;
141+
console.log('[ConfigManager] Cache cleared');
142+
} catch (error) {
143+
console.warn('[ConfigManager] Failed to clear cache:', error);
144+
}
145+
}
146+
147+
// 预加载配置(可选,用于进一步优化首次加载)
148+
async preload() {
149+
if (!this.config && !this.initializing) {
150+
// 后台预加载,不阻塞UI
151+
this.initialize().catch(error => {
152+
console.warn('[ConfigManager] Preload failed:', error);
153+
});
154+
}
155+
}
156+
157+
// 获取配置的简化方法
158+
async getBaseURL() {
159+
const config = await this.initialize();
160+
return config.baseURL;
161+
}
162+
163+
async getApiURL() {
164+
const config = await this.initialize();
165+
return config.apiURL;
166+
}
167+
168+
async getNginxPort() {
169+
const config = await this.initialize();
170+
return config.nginxPort;
171+
}
172+
}
173+
174+
// 单例实例
175+
const configManager = new ConfigManager();
176+
177+
// 开发环境下暴露到全局,方便调试
178+
if (process.env.NODE_ENV === 'development') {
179+
window.configManager = configManager;
180+
}
181+
182+
export default configManager;

src/helpers/api/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
2-
import { AppAvailable, AppCatalog, AppInstall, GetSettingsBySection } from './appHub';
2+
import { AppAvailable, AppCatalog, AppInstall, GetSettingsBySection, UpdateSettingsBySection } from './appHub';
33

44
export {
5-
AppAvailable, AppCatalog, AppInstall, GetSettingsBySection
5+
AppAvailable, AppCatalog, AppInstall, GetSettingsBySection, UpdateSettingsBySection
66
};

0 commit comments

Comments
 (0)