vue-docs-ui
Version:
A modern documentation UI component library built with Vue 3. Create beautiful documentation websites with YAML configuration and Markdown rendering - ready to use out of the box.
386 lines (336 loc) • 9.78 kB
HTML
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>离线模式 - Vue Docs UI</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #3b82f6;
--text-color: #1f2937;
--text-color-light: #6b7280;
--bg-color: #ffffff;
--bg-color-secondary: #f9fafb;
--border-color: #e5e7eb;
--success-color: #10b981;
--warning-color: #f59e0b;
}
@media (prefers-color-scheme: dark) {
:root {
--text-color: #f9fafb;
--text-color-light: #d1d5db;
--bg-color: #111827;
--bg-color-secondary: #1f2937;
--border-color: #374151;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.offline-container {
max-width: 480px;
text-align: center;
background: var(--bg-color-secondary);
border-radius: 16px;
padding: 3rem 2rem;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
border: 1px solid var(--border-color);
}
.offline-icon {
width: 80px;
height: 80px;
margin: 0 auto 2rem;
background: var(--primary-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
opacity: 0.9;
}
.offline-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--text-color);
}
.offline-description {
color: var(--text-color-light);
margin-bottom: 2rem;
font-size: 1rem;
}
.offline-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 8px;
text-decoration: none;
font-weight: 500;
transition: all 0.2s ease;
border: none;
cursor: pointer;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-primary:hover {
background: #2563eb;
transform: translateY(-1px);
}
.btn-secondary {
background: transparent;
color: var(--text-color);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--bg-color);
border-color: var(--primary-color);
}
.network-status {
margin-top: 2rem;
padding: 1rem;
background: var(--bg-color);
border-radius: 8px;
border: 1px solid var(--border-color);
}
.status-indicator {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 0.9rem;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--warning-color);
animation: pulse 2s infinite;
}
.status-dot.online {
background: var(--success-color);
animation: none;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.cached-pages {
margin-top: 2rem;
text-align: left;
}
.cached-pages h3 {
font-size: 1rem;
margin-bottom: 1rem;
color: var(--text-color);
text-align: center;
}
.page-list {
list-style: none;
max-height: 200px;
overflow-y: auto;
}
.page-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border-radius: 6px;
margin-bottom: 0.25rem;
transition: background 0.2s ease;
}
.page-item:hover {
background: var(--bg-color);
}
.page-item a {
color: var(--text-color);
text-decoration: none;
flex: 1;
font-size: 0.9rem;
}
.page-item a:hover {
color: var(--primary-color);
}
.page-icon {
width: 16px;
height: 16px;
opacity: 0.6;
}
@media (max-width: 480px) {
.offline-container {
padding: 2rem 1.5rem;
}
.offline-actions {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
</style>
</head>
<body>
<div class="offline-container">
<div class="offline-icon">
📱
</div>
<h1 class="offline-title">离线模式</h1>
<p class="offline-description">
您当前处于离线状态,但您仍然可以浏览已缓存的文档内容。
</p>
<div class="offline-actions">
<button class="btn btn-primary" onclick="retryConnection()">
🔄 重新连接
</button>
<a href="/" class="btn btn-secondary">
🏠 返回首页
</a>
</div>
<div class="network-status">
<div class="status-indicator">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">检查网络连接中...</span>
</div>
</div>
<div class="cached-pages">
<h3>📚 可离线浏览的页面</h3>
<ul class="page-list" id="cachedPagesList">
<li class="page-item">
<span class="page-icon">📄</span>
<a href="/">首页</a>
</li>
<li class="page-item">
<span class="page-icon">📖</span>
<a href="/guide">使用指南</a>
</li>
<li class="page-item">
<span class="page-icon">⚙️</span>
<a href="/config">配置文档</a>
</li>
<li class="page-item">
<span class="page-icon">🔧</span>
<a href="/api">API 参考</a>
</li>
</ul>
</div>
</div>
<script>
// 网络状态检查
function checkNetworkStatus() {
const statusDot = document.getElementById('statusDot')
const statusText = document.getElementById('statusText')
if (navigator.onLine) {
statusDot.classList.add('online')
statusText.textContent = '网络连接正常'
} else {
statusDot.classList.remove('online')
statusText.textContent = '网络连接断开'
}
}
// 重试连接
function retryConnection() {
const btn = event.target
const originalText = btn.innerHTML
btn.innerHTML = '🔄 连接中...'
btn.disabled = true
// 尝试加载一个小的资源来测试连接
fetch('/manifest.json', {
cache: 'no-cache',
mode: 'no-cors'
})
.then(() => {
// 连接成功,重新加载页面
window.location.reload()
})
.catch(() => {
// 连接失败
btn.innerHTML = '❌ 连接失败'
setTimeout(() => {
btn.innerHTML = originalText
btn.disabled = false
}, 2000)
})
}
// 加载缓存的页面列表
async function loadCachedPages() {
if ('caches' in window) {
try {
const cacheNames = await caches.keys()
const cachedUrls = new Set()
for (const cacheName of cacheNames) {
const cache = await caches.open(cacheName)
const requests = await cache.keys()
requests.forEach(request => {
const url = new URL(request.url)
if (url.pathname !== '/offline.html' &&
!url.pathname.includes('.') &&
url.pathname !== '/') {
cachedUrls.add(url.pathname)
}
})
}
// 更新页面列表
const pagesList = document.getElementById('cachedPagesList')
// 添加发现的缓存页面
cachedUrls.forEach(url => {
const li = document.createElement('li')
li.className = 'page-item'
li.innerHTML = `
<span class="page-icon">📄</span>
<a href="${url}">${url}</a>
`
pagesList.appendChild(li)
})
} catch (error) {
console.error('Failed to load cached pages:', error)
}
}
}
// 监听网络状态变化
window.addEventListener('online', checkNetworkStatus)
window.addEventListener('offline', checkNetworkStatus)
// 初始化
document.addEventListener('DOMContentLoaded', () => {
checkNetworkStatus()
loadCachedPages()
// 定期检查网络状态
setInterval(checkNetworkStatus, 5000)
})
// 键盘快捷键
document.addEventListener('keydown', (event) => {
if (event.key === 'r' && (event.ctrlKey || event.metaKey)) {
event.preventDefault()
retryConnection()
}
if (event.key === 'h' && (event.ctrlKey || event.metaKey)) {
event.preventDefault()
window.location.href = '/'
}
})
</script>
</body>
</html>