用旧苹果手机当nas监控副屏(二)
前文实现了在苹果手机旧版ios(12.5.8)上nas实时信息的显示,背景是纯色或渐变色,有用但不够炫酷,比图灵智显差那么一些意思。本文在web界面上进行了改进。
ios12.5.8的webview不支持body的background属性设置,无法直接换背景。这里采用img标签显示全屏图片作为背景,再把要显示的信息小卡片放置在图片的指定位置上方。
status2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>NAS监控仪表盘</title>
<style>
/* 基础重置 */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; }
/* 背景容器 */
#bg-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
/* 背景图片样式 */
#bg-image {
width: 100%;
height: 100%;
object-fit: cover;
min-width: 100%;
min-height: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* 内容容器 - 减少遮罩透明度 */
#content {
position: relative;
z-index: 1;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.1); /* 减少遮罩 */
}
/* 数据卡片样式 - 增大透明度 */
.data-card {
position: absolute;
background-color: rgba(0, 20, 40, 0.5); /* 透明度从0.7改为0.5 */
border: 1px solid rgba(100, 210, 255, 0.4); /* 边框更明显 */
border-radius: 12px;
padding: 15px 20px;
text-align: center;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.9),
0 0 5px rgba(0, 0, 0, 0.7),
2px 2px 4px rgba(0, 0, 0, 0.5);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
backdrop-filter: blur(5px); /* 稍微增加模糊 */
-webkit-backdrop-filter: blur(5px); /* Safari支持 */
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* 卡片悬停效果(可选) */
.data-card:hover {
background-color: rgba(0, 20, 40, 0.6); /* 悬停时稍微加深 */
border-color: rgba(100, 210, 255, 0.6);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5),
0 0 25px rgba(100, 210, 255, 0.2);
}
/* 前三个卡片 - 宽度较小 */
.small-card {
min-width: 120px;
max-width: 140px;
}
/* 后三个卡片 - 宽度较大 */
.large-card {
min-width: 180px;
max-width: 220px;
}
.card-title {
font-size: 14px;
font-weight: bold;
color: #64d2ff;
margin-bottom: 8px;
display: block;
letter-spacing: 1px;
}
.card-value {
font-size: 28px;
font-weight: bold;
font-family: 'Courier New', Courier, monospace;
color: #ffffff;
display: block;
line-height: 1.2;
text-shadow: 0 0 10px rgba(100, 210, 255, 0.3); /* 增加文字发光 */
}
/* 运行时间字体较小 */
#uptime-card .card-value {
font-size: 22px;
}
/* 状态指示灯 */
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #34c759;
margin-right: 8px;
box-shadow: 0 0 8px #34c759;
}
/* 前三个卡片位置 - 顶部一行 */
#cpu-card {
top: 15%;
left: 10%;
}
#mem-card {
top: 15%;
left: 50%;
transform: translateX(-50%);
}
#disk-card {
top: 15%;
right: 10%;
}
/* 后三个卡片位置 - 中间垂直排列 */
#net-up-card {
top: 40%;
left: 50%;
transform: translateX(-50%);
}
#net-down-card {
top: 55%;
left: 50%;
transform: translateX(-50%);
}
#uptime-card {
top: 70%;
left: 50%;
transform: translateX(-50%);
}
/* 刷新指示器 */
.refresh-indicator {
position: fixed;
top: 10px;
right: 10px;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(100, 210, 255, 0.3);
z-index: 100;
display: none;
}
.refresh-indicator.active {
display: block;
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { opacity: 0.3; }
50% { opacity: 1; }
100% { opacity: 0.3; }
}
/* 最后更新时间 */
.last-update {
position: fixed;
bottom: 10px;
right: 10px;
color: rgba(255, 255, 255, 0.6); /* 提高可见度 */
font-size: 12px;
font-family: 'Courier New', Courier, monospace;
z-index: 10;
background-color: rgba(0, 0, 0, 0.3); /* 添加半透明背景 */
padding: 5px 10px;
border-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* 备用提示 */
.fallback-message {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
text-align: center;
background: rgba(0,0,0,0.8);
padding: 20px;
border-radius: 10px;
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* iOS优化 */
.data-card {
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
user-select: none;
}
/* 移动端适配 */
@media (max-width: 768px) {
.small-card {
min-width: 100px;
max-width: 120px;
padding: 10px 15px;
}
.large-card {
min-width: 150px;
max-width: 180px;
padding: 12px 18px;
}
.card-value {
font-size: 24px;
}
#uptime-card .card-value {
font-size: 18px;
}
/* 调整位置适应小屏幕 */
#cpu-card {
top: 10%;
left: 5%;
}
#mem-card {
top: 10%;
left: 50%;
transform: translateX(-50%);
}
#disk-card {
top: 10%;
right: 5%;
}
#net-up-card {
top: 35%;
}
#net-down-card {
top: 55%;
}
#uptime-card {
top: 80%;
}
/* 移动端减小透明度 */
.data-card {
background-color: rgba(0, 20, 40, 0.6);
}
}
/* 超小屏幕适配 */
@media (max-width: 480px) {
.small-card {
min-width: 90px;
max-width: 110px;
padding: 8px 12px;
}
.large-card {
min-width: 130px;
max-width: 160px;
padding: 10px 15px;
}
.card-value {
font-size: 20px;
}
#uptime-card .card-value {
font-size: 16px;
}
.card-title {
font-size: 12px;
}
}
</style>
</head>
<body>
<!-- 刷新指示器 -->
<div class="refresh-indicator" id="refresh-indicator"></div>
<!-- 最后更新时间 -->
<div class="last-update" id="last-update">--:--:--</div>
<!-- 背景容器 -->
<div id="bg-container">
<img id="bg-image"
src="dashboard-bg3.jpg"
alt="仪表盘背景"
onerror="showFallback()">
<!-- 备用纯色背景 -->
<div style="position:absolute; top:0; left:0; width:100%; height:100%; background:linear-gradient(135deg, #0f2027 0%, #203a43 100%); display:none;"
id="fallback-bg"></div>
</div>
<!-- 备用提示 -->
<div class="fallback-message" id="fallback-msg">
<p>仪表盘背景加载失败</p>
<p>正在使用简约模式</p>
</div>
<!-- 内容区域 -->
<div id="content">
<!-- 前三个卡片 - 小宽度 -->
<div class="data-card small-card" id="cpu-card">
<span class="card-title"><span class="status-indicator"></span>CPU</span>
<span class="card-value" id="cpu-value">--</span>
</div>
<div class="data-card small-card" id="mem-card">
<span class="card-title"><span class="status-indicator"></span>内存</span>
<span class="card-value" id="mem-value">--</span>
</div>
<div class="data-card small-card" id="disk-card">
<span class="card-title">💾 存储</span>
<span class="card-value" id="disk-value">--</span>
</div>
<!-- 后三个卡片 - 大宽度 -->
<div class="data-card large-card" id="net-up-card">
<span class="card-title">▲ 上传速度</span>
<span class="card-value" id="net-up-value">--</span>
</div>
<div class="data-card large-card" id="net-down-card">
<span class="card-title">▼ 下载速度</span>
<span class="card-value" id="net-down-value">--</span>
</div>
<div class="data-card large-card" id="uptime-card">
<span class="card-title">⏱️ 运行时间</span>
<span class="card-value" id="uptime-value">--</span>
</div>
</div>
<script>
// 全局状态
var lastUpdateTime = null;
var refreshInterval = null;
var isPageVisible = true;
// 显示刷新指示器
function showRefreshIndicator() {
var indicator = document.getElementById('refresh-indicator');
indicator.classList.add('active');
setTimeout(function() {
indicator.classList.remove('active');
}, 1000);
}
// 更新最后更新时间显示
function updateLastUpdateTime() {
var now = new Date();
var timeString = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
document.getElementById('last-update').textContent = '更新: ' + timeString;
lastUpdateTime = now;
}
// 图片加载失败处理
function showFallback() {
document.getElementById('fallback-bg').style.display = 'block';
document.getElementById('fallback-msg').style.display = 'block';
console.log('背景图加载失败,使用备用背景');
}
// 从status.json获取数据
function fetchData() {
showRefreshIndicator();
// iOS 12需要强制清除缓存
var url = 'status.json?_=' + new Date().getTime();
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.timeout = 8000;
// 设置缓存控制
xhr.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
xhr.setRequestHeader('Pragma', 'no-cache');
xhr.setRequestHeader('Expires', '0');
xhr.onload = function() {
if (xhr.status === 200 || xhr.status === 0) {
try {
var data = JSON.parse(xhr.responseText);
updateDisplay(data);
updateLastUpdateTime();
console.log('数据更新成功:', new Date().toLocaleTimeString());
} catch(e) {
console.error('数据解析错误:', e);
showDataError();
}
} else {
console.error('HTTP错误:', xhr.status);
showDataError();
}
};
xhr.onerror = function() {
console.error('网络请求错误');
showDataError();
};
xhr.ontimeout = function() {
console.error('请求超时');
showDataError();
};
try {
xhr.send();
} catch(e) {
console.error('发送请求错误:', e);
showDataError();
}
}
// 更新显示
function updateDisplay(data) {
console.log('更新数据显示');
// CPU
var cpuText = data.cpu || "--";
document.getElementById('cpu-value').textContent = cpuText;
// 内存
var memText = data.memory ? (data.memory.usage || "--") : "--";
document.getElementById('mem-value').textContent = memText;
// 存储
var diskText = data.disk ? (data.disk.usage || "--") : "--";
document.getElementById('disk-value').textContent = diskText;
// 网络 - 直接使用JSON中的完整字符串
var downText = data.network ? (data.network.download_speed || "--") : "--";
var upText = data.network ? (data.network.upload_speed || "--") : "--";
document.getElementById('net-down-value').textContent = downText;
document.getElementById('net-up-value').textContent = upText;
// 运行时间 - 直接显示JSON中的完整字符串
var uptimeText = data.system ? (data.system.uptime || "--") : "--";
document.getElementById('uptime-value').textContent = uptimeText;
// 动态更新状态指示灯颜色
updateIndicatorColor('cpu', extractNumber(cpuText));
updateIndicatorColor('mem', extractNumber(memText));
updateIndicatorColor('disk', extractNumber(diskText));
}
// 从文本中提取数字
function extractNumber(text) {
if (text === "--") return 0;
var match = text.match(/(\d+(\.\d+)?)/);
return match ? parseFloat(match[1]) : 0;
}
// 更新状态指示灯颜色
function updateIndicatorColor(type, value) {
var indicator = document.querySelector('#' + type + '-card .status-indicator');
if (!indicator) return;
// 重置样式
indicator.style.backgroundColor = '#34c759';
indicator.style.boxShadow = '0 0 8px #34c759';
if (value > 80) {
indicator.style.backgroundColor = '#ff3b30';
indicator.style.boxShadow = '0 0 8px #ff3b30';
} else if (value > 60) {
indicator.style.backgroundColor = '#ff9500';
indicator.style.boxShadow = '0 0 8px #ff9500';
}
}
// 数据显示错误
function showDataError() {
var cards = document.querySelectorAll('.data-card');
cards.forEach(function(card) {
var valueElem = card.querySelector('.card-value');
if (valueElem) {
valueElem.textContent = 'ERR';
valueElem.style.color = '#ff6b6b';
}
});
}
// 初始化定时刷新
function initRefreshTimer() {
// 清除现有定时器
if (refreshInterval) {
clearInterval(refreshInterval);
}
// 设置新的定时器 - 每10秒刷新
refreshInterval = setInterval(function() {
if (isPageVisible) {
fetchData();
}
}, 10000);
}
// 页面可见性处理
document.addEventListener('visibilitychange', function() {
isPageVisible = !document.hidden;
console.log('页面可见性:', isPageVisible ? '可见' : '隐藏');
if (isPageVisible) {
// 页面重新可见时立即刷新
fetchData();
initRefreshTimer();
} else {
// 页面隐藏时停止定时器
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
}
});
// 防止iOS页面缓存
window.addEventListener('pageshow', function(event) {
if (event.persisted) {
console.log('页面从缓存恢复,强制刷新');
fetchData();
}
});
// 添加手动刷新功能(点击任意位置刷新)
document.addEventListener('click', function(e) {
// 避免点击刷新指示器时触发
if (e.target.id !== 'refresh-indicator') {
fetchData();
}
});
// 页面加载完成
window.addEventListener('load', function() {
console.log('NAS监控仪表盘初始化...');
// 初始化最后更新时间
updateLastUpdateTime();
// 立即加载数据
fetchData();
// 初始化定时器
initRefreshTimer();
// 检查背景图片
var bgImg = document.getElementById('bg-image');
if (bgImg.complete) {
console.log('背景图片已加载');
} else {
bgImg.onload = function() {
console.log('背景图片加载完成');
};
bgImg.onerror = function() {
console.log('背景图片加载失败');
};
}
// 添加触摸事件支持
document.addEventListener('touchstart', function() {}, {passive: true});
});
// 页面卸载清理
window.addEventListener('beforeunload', function() {
if (refreshInterval) {
clearInterval(refreshInterval);
}
});
// 添加键盘快捷键支持(可选)
document.addEventListener('keydown', function(e) {
if (e.code === 'Space' || e.code === 'KeyR') {
fetchData();
e.preventDefault();
}
});
</script>
</body>
</html>
<img id="bg-image"
src="dashboard-bg3.jpg"
alt="仪表盘背景"
onerror="showFallback()">
更改src对应的图片就可以更换背景图。
各信息标签的位置是通过相对top百分比位置设置的,是针对小屏手机设置,iphone5s的浏览器显示范围640*1000,在更大屏手机上显示可能太松散,可自行调整。
体验
html代码依然是deepseek生成,手动做少量调整。