294 lines
8.2 KiB
HTML
294 lines
8.2 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8"/>
|
||
<link rel="icon" href="/favicon.ico"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<meta name="baidu-site-verification" content="codeva-mkVpSFmYJm"/>
|
||
<meta name="description" content="意心AI:一站式多模型 AI 平台,提供 AI 服务"/>
|
||
<meta name="description" content="各大主流AI无限制使用,直连,AI,claude ,DeepSeek,open-ai"/>
|
||
<meta name="keywords" content="意心AI, 多模型AI, AI工具"/>
|
||
<meta name="keywords" content="橙子,chengzi,橙子老哥,ccnetcore,意社区"/>
|
||
<meta name="author" content="橙子,chengzi,橙子老哥,ccnetcore"/>
|
||
<meta name="version" content="%VITE_APP_VERSION%"/>
|
||
<meta name="version" content="%VITE_WEB_TITLE%"/>
|
||
<title>%VITE_WEB_TITLE%</title>
|
||
<meta name="viewport"
|
||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||
|
||
<!-- DNS 预解析和预连接 -->
|
||
<link rel="dns-prefetch" href="//api.yourdomain.com">
|
||
<link rel="preconnect" href="//api.yourdomain.com" crossorigin>
|
||
|
||
<!-- 预加载关键资源 -->
|
||
<link rel="preload" href="/src/main.ts" as="script" crossorigin>
|
||
<link rel="modulepreload" href="/src/main.ts">
|
||
|
||
|
||
<style>
|
||
/* 全局样式 */
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 加载动画样式 */
|
||
.loader-container {
|
||
position: fixed;
|
||
inset: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #fff;
|
||
z-index: 1000;
|
||
transition: opacity 0.5s ease;
|
||
}
|
||
|
||
.loader-title {
|
||
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
||
font-weight: bold;
|
||
margin-bottom: 0.5rem;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
.loader-subtitle {
|
||
font-size: 0.875rem;
|
||
color: #555;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.loader-logo {
|
||
font-size: 3rem;
|
||
margin-bottom: 1.5rem;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.pulse-box {
|
||
width: 32px;
|
||
height: 32px;
|
||
background: #000;
|
||
display: inline-block;
|
||
transform-origin: center;
|
||
animation: pulse 1.2s infinite ease-in-out;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% {
|
||
transform: scale(1) rotate(0deg);
|
||
opacity: 1;
|
||
}
|
||
50% {
|
||
transform: scale(1.2) rotate(45deg);
|
||
opacity: 0.8;
|
||
}
|
||
100% {
|
||
transform: scale(1) rotate(90deg);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.loader-text {
|
||
font-size: 1.5rem;
|
||
font-weight: bold;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.loader-progress-bar {
|
||
width: 90%;
|
||
max-width: 400px;
|
||
height: 8px;
|
||
background: #f0f0f0;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.loader-progress {
|
||
height: 100%;
|
||
width: 0%;
|
||
background: #000;
|
||
transition: width 0.3s ease-out;
|
||
}
|
||
</style>
|
||
|
||
|
||
</head>
|
||
|
||
<body>
|
||
<!-- 加载动画容器 -->
|
||
<div id="yixinai-loader" class="loader-container">
|
||
<div class="loader-title">%APP_FULL_NAME%</div>
|
||
<div class="loader-subtitle">海外地址,仅首次访问预计加载约10秒,无需梯子</div>
|
||
<div class="loader-logo">
|
||
<div class="pulse-box"></div>
|
||
</div>
|
||
<div class="loader-progress-bar">
|
||
<div id="loader-progress" class="loader-progress"></div>
|
||
</div>
|
||
<div id="loader-text" class="loader-text" style="font-size: 0.875rem; margin-top: 0.5rem; color: #666;">加载中...</div>
|
||
</div>
|
||
<div id="app"></div>
|
||
|
||
<script>
|
||
// 资源加载进度跟踪 - 增强版
|
||
(function() {
|
||
const progressBar = document.getElementById('loader-progress');
|
||
const loaderText = document.getElementById('loader-text');
|
||
const loader = document.getElementById('yixinai-loader');
|
||
|
||
let progress = 0;
|
||
let resourcesLoaded = false;
|
||
let vueAppMounted = false;
|
||
let appRendered = false;
|
||
|
||
// 更新进度条
|
||
function updateProgress(value, text) {
|
||
progress = Math.min(value, 99);
|
||
if (progressBar) progressBar.style.width = progress.toFixed(1) + '%';
|
||
if (loaderText) loaderText.textContent = text;
|
||
}
|
||
|
||
// 阶段管理
|
||
const stages = {
|
||
init: { weight: 15, name: '初始化' },
|
||
resources: { weight: 35, name: '加载资源' },
|
||
scripts: { weight: 25, name: '执行脚本' },
|
||
render: { weight: 15, name: '渲染页面' },
|
||
complete: { weight: 10, name: '启动应用' }
|
||
};
|
||
|
||
let completedStages = new Set();
|
||
let currentStage = 'init';
|
||
|
||
function calculateProgress() {
|
||
let totalProgress = 0;
|
||
for (const [key, stage] of Object.entries(stages)) {
|
||
if (completedStages.has(key)) {
|
||
totalProgress += stage.weight;
|
||
} else if (key === currentStage) {
|
||
// 当前阶段完成一部分
|
||
totalProgress += stage.weight * 0.5;
|
||
}
|
||
}
|
||
return Math.min(totalProgress, 99);
|
||
}
|
||
|
||
// 阶段完成
|
||
function completeStage(stageName, nextStage) {
|
||
completedStages.add(stageName);
|
||
currentStage = nextStage || stageName;
|
||
const stage = stages[stageName];
|
||
updateProgress(calculateProgress(), stage ? `${stage.name}完成` : '加载中...');
|
||
}
|
||
|
||
// 监听资源加载 - 使用更可靠的方式
|
||
const resourceTimings = [];
|
||
const observer = new PerformanceObserver((list) => {
|
||
const entries = list.getEntries();
|
||
resourceTimings.push(...entries);
|
||
|
||
// 统计未完成资源
|
||
const pendingResources = performance.getEntriesByType('resource')
|
||
.filter(r => !r.responseEnd || r.responseEnd === 0).length;
|
||
|
||
if (pendingResources === 0 && resourceTimings.length > 0) {
|
||
completeStage('resources', 'scripts');
|
||
} else {
|
||
updateProgress(calculateProgress(), `加载资源中... (${resourceTimings.length} 已加载)`);
|
||
}
|
||
});
|
||
|
||
try {
|
||
observer.observe({ entryTypes: ['resource'] });
|
||
} catch (e) {
|
||
// 降级处理
|
||
}
|
||
|
||
// 初始进度
|
||
let initProgress = 0;
|
||
function simulateInitProgress() {
|
||
if (initProgress < stages.init.weight) {
|
||
initProgress += 1;
|
||
updateProgress(initProgress, '正在初始化...');
|
||
if (initProgress < stages.init.weight) {
|
||
setTimeout(simulateInitProgress, 30);
|
||
} else {
|
||
completeStage('init', 'resources');
|
||
}
|
||
}
|
||
}
|
||
simulateInitProgress();
|
||
|
||
// 页面资源加载完成
|
||
window.addEventListener('load', () => {
|
||
completeStage('resources', 'scripts');
|
||
resourcesLoaded = true;
|
||
|
||
// 给脚本执行时间
|
||
setTimeout(() => {
|
||
completeStage('scripts', 'render');
|
||
}, 300);
|
||
|
||
checkAndHideLoader();
|
||
});
|
||
|
||
// 暴露全局方法供 Vue 应用调用 - 分阶段调用
|
||
window.__hideAppLoader = function(stage) {
|
||
if (stage === 'mounted') {
|
||
vueAppMounted = true;
|
||
completeStage('scripts', 'render');
|
||
} else if (stage === 'rendered') {
|
||
appRendered = true;
|
||
completeStage('render', 'complete');
|
||
}
|
||
checkAndHideLoader();
|
||
};
|
||
|
||
// 检查是否可以隐藏加载动画
|
||
function checkAndHideLoader() {
|
||
// 需要满足:资源加载完成、Vue 挂载、页面渲染完成
|
||
if (resourcesLoaded && vueAppMounted && appRendered) {
|
||
completeStage('complete', '');
|
||
updateProgress(100, '加载完成');
|
||
|
||
// 确保最小显示时间,避免闪烁
|
||
const minDisplayTime = 1000;
|
||
const elapsed = Date.now() - startTime;
|
||
const remaining = Math.max(0, minDisplayTime - elapsed);
|
||
|
||
setTimeout(() => {
|
||
if (loader) {
|
||
loader.style.opacity = '0';
|
||
loader.style.transition = 'opacity 0.5s ease';
|
||
setTimeout(() => {
|
||
loader.remove();
|
||
delete window.__hideAppLoader;
|
||
}, 500);
|
||
}
|
||
}, remaining);
|
||
}
|
||
}
|
||
|
||
const startTime = Date.now();
|
||
|
||
// 超时保护:最多显示 30 秒
|
||
setTimeout(() => {
|
||
if (loader && loader.parentNode) {
|
||
console.warn('加载超时,强制隐藏加载动画');
|
||
vueAppMounted = true;
|
||
resourcesLoaded = true;
|
||
appRendered = true;
|
||
checkAndHideLoader();
|
||
}
|
||
}, 60000);
|
||
})();
|
||
</script>
|
||
|
||
<script type="module" src="/src/main.ts"></script>
|
||
</body>
|
||
|
||
</html>
|