1. 项目概述:用旧手机打造你的专属股票盯盘设备
如果你和我一样,是个对市场波动有点敏感,但又不想整天被电脑或专业软件绑在屏幕前的普通投资者,这个项目绝对值得你花一个下午折腾一下。它的核心思路很简单:利用你家里那台已经退役、吃灰多年的旧安卓手机,把它变成一个功能专一、信息直观的股票盯盘设备。你可以把它放在书桌一角、床头柜上,或者任何你常待的地方,像看电子相册一样,随时瞥一眼你关心的几只股票或指数的关键信息,比如实时价格、涨跌幅、成交量,而无需打开复杂的交易软件,更不会被各种推送通知和无关信息干扰。
这个想法的灵感,其实就来源于“旧手机DIY”这个最近挺火的概念。大家开始意识到,这些被淘汰的电子设备依然有强大的计算和显示能力,与其让它们报废,不如赋予它们新的、单一的使命。无论是做成智能家居中控屏、电子时钟,还是像我们这里要做的股票盯盘器,都是让旧物重获新生的绝佳方式。它成本极低(几乎为零),个性化程度高,而且完全由你掌控数据展示的逻辑和样式。相比于购买一个功能固定的商业产品,自己动手的乐趣和灵活性是无可替代的。
2. 核心思路与方案选型:为什么是“网页+旧手机”?
在动手之前,我们先来拆解一下实现这个目标有哪几条路可以走,以及我为什么最终选择了“网页应用 + 旧手机常亮显示”这个组合方案。
2.1 可选技术路径分析
开发原生安卓应用:这是最“正统”的方式。用Kotlin或Java写一个App,安装在旧手机上。优点是性能好、可以调用更多系统API(比如熄屏唤醒)。但缺点非常明显:开发门槛高,需要配置开发环境、学习特定语言和框架,对于非专业开发者来说,从零到一的周期太长,且后续任何小的界面调整或数据源变更都需要重新编译和安装,维护成本高。
使用现成的股票App并改造:直接安装券商或第三方股票软件,然后通过手机系统的“专注模式”或“应用锁”功能,限制只能运行这个App。这个方法最快,但问题在于你无法控制App的界面。广告、复杂的导航栏、频繁的推送,都会破坏我们想要的“纯净、专注”的体验。而且,大多数App的界面并非为“常亮盯盘”设计,可能很快就会息屏。
网页应用 + 浏览器全屏模式:这是我们采用的核心方案。它的工作原理是,我们编写一个简单的HTML网页,这个网页通过JavaScript从公开的金融数据API(比如雅虎财经、Alpha Vantage等)获取股票数据,然后用CSS将其美化成一个适合在大屏幕上、一眼就能看清的仪表盘样式。最后,在旧手机的浏览器中打开这个网页,并设置为全屏、禁止休眠。这个方案的优点非常突出:
- 开发简单:只需要HTML、CSS和少量JavaScript知识,网上有大量现成的代码和库可以借鉴。
- 跨平台与易维护:任何有浏览器的设备都能打开。想修改界面?直接改网页文件,刷新一下即可生效,无需重新安装。
- 完全定制:显示哪些数据、如何排版、用什么颜色和字体,全部由你说了算。
- 资源友好:一个优化良好的网页在旧手机上运行起来,比大型原生App要流畅和省电得多。
2.2 我们的技术栈与工具清单
基于“网页应用”方案,我们需要准备以下工具和资源:
- 硬件:一台退役的安卓手机(建议Android 7.0以上,屏幕完好,能连接Wi-Fi)。这是我们的“显示屏”。
- 开发环境:你日常使用的电脑(Windows, macOS, Linux均可)。用于编写和测试网页代码。
- 代码编辑器:VS Code、Sublime Text甚至记事本都可以。我推荐VS Code,因为它对前端代码的提示和插件支持很好。
- 网页三件套:HTML(结构)、CSS(样式)、JavaScript(逻辑与数据获取)。
- 数据源:选择一个免费、稳定、无需复杂认证的股票数据API。本项目我们将使用Yahoo Finance API的一个公共端点,因为它简单直接,无需注册API密钥。例如,获取苹果公司(AAPL)的数据,可以直接访问一个特定格式的URL来获取JSON数据。
- 网络服务(可选):为了让你的手机能随时访问这个网页,你需要把它放在一个能通过互联网访问的地方。最简单的方法是使用电脑的内网穿透工具(如ngrok)临时测试,或者部署到GitHub Pages、Vercel等免费的静态网站托管服务上,这样你的旧手机只要联网就能随时看到最新页面。
注意:公开的金融数据API通常有访问频率限制。对于个人盯盘几只股票的场景,通常不会触发限制,但请不要编写程序进行高频、自动化的大量请求。
3. 实战步骤:从零构建盯盘网页
接下来,我们进入实操环节。我会手把手带你创建一个最基本的股票盯盘页面,并讲解每个步骤的意图和细节。
3.1 第一步:创建网页结构与基础样式
首先,在你的电脑上创建一个新文件夹,比如叫做stock-tracker。在里面创建三个文件:index.html,style.css,script.js。
index.html- 网页骨架这个文件定义了页面的基本结构。我们计划显示一个标题,以及若干个“股票卡片”,每张卡片展示一只股票的信息。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>我的股票盯盘器</title> <link rel="stylesheet" href="style.css"> <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet"> </head> <body> <div class="container"> <header> <h1>📈 个人股票仪表盘</h1> <p id="update-time">最后更新:--</p> </header> <main class="stocks-grid"> <!-- 股票卡片将通过JavaScript动态添加到这里 --> </main> <footer> <p>数据来源:Yahoo Finance | 本地更新,仅供参考</p> </footer> </div> <script src="script.js"></script> </body> </html>关键点解析:
viewport设置确保了页面在不同尺寸屏幕(尤其是手机)上能正确缩放。- 我们引入了
Roboto Mono字体,这是一种等宽字体,显示数字时非常整齐美观。 - 页面主体分为
header(标题和更新时间)、main(股票卡片容器)和footer(页脚说明)。
style.css- 网页美容师这个文件负责让我们的页面看起来像一个专业的仪表盘,而不仅仅是一个列表。
* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Roboto Mono', monospace; background: linear-gradient(135deg, #0f2027, #203a43, #2c5364); /* 深色渐变背景 */ color: #e0e0e0; min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; } header { text-align: center; margin-bottom: 40px; padding: 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } header h1 { font-size: 2.5rem; margin-bottom: 10px; color: #4fc3f7; } #update-time { font-size: 0.9rem; color: #aaa; } .stocks-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); /* 响应式网格 */ gap: 25px; margin-bottom: 40px; } .stock-card { background: rgba(255, 255, 255, 0.05); border-radius: 16px; padding: 25px; border: 1px solid rgba(255, 255, 255, 0.1); transition: transform 0.3s ease, box-shadow 0.3s ease; backdrop-filter: blur(10px); /* 毛玻璃效果 */ } .stock-card:hover { transform: translateY(-5px); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3); } .stock-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .symbol { font-size: 2rem; font-weight: 700; color: #fff; } .name { font-size: 1rem; color: #bbb; max-width: 70%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .price { font-size: 3rem; font-weight: 700; margin: 15px 0; text-align: center; } .change { font-size: 1.5rem; text-align: center; margin-bottom: 20px; padding: 8px 15px; border-radius: 8px; display: inline-block; } .change.positive { color: #4caf50; /* 绿色代表上涨 */ background: rgba(76, 175, 80, 0.1); } .change.negative { color: #f44336; /* 红色代表下跌 */ background: rgba(244, 67, 54, 0.1); } .stock-meta { display: flex; justify-content: space-around; font-size: 0.9rem; color: #aaa; border-top: 1px solid rgba(255, 255, 255, 0.05); padding-top: 15px; } .meta-item { text-align: center; } .meta-label { display: block; font-size: 0.8rem; margin-bottom: 5px; color: #777; } footer { text-align: center; padding: 20px; color: #666; font-size: 0.8rem; border-top: 1px solid rgba(255, 255, 255, 0.05); }设计心得:
- 深色主题:对于需要长时间注视的盯盘设备,深色背景能有效减少视觉疲劳,这也是许多专业交易软件的选择。
- 卡片化设计:每只股票独立成卡,信息集中,视觉上更清晰,也方便后期调整布局。
- 视觉反馈:通过颜色(红跌绿涨)和轻微的悬浮动画,让数据变化一目了然,增加了界面的“可读性”和互动感。
- 响应式网格:
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))这行代码是精髓。它确保无论屏幕大小如何,卡片都会自动调整排列,始终以至少300px的宽度优雅地填充空间,在手机竖屏上会显示为一列,在横屏或平板上可能会显示多列。
3.2 第二步:注入灵魂——用JavaScript获取并展示数据
这是最核心的一步。我们将编写script.js文件,负责从网络获取实时数据,并动态更新到我们刚才创建的HTML页面上。
script.js- 数据抓取与逻辑控制我们首先定义一个股票代码列表,然后编写函数来获取和更新数据。
// 配置:你要关注的股票代码列表(使用雅虎财经的代码格式) const stockSymbols = ['AAPL', 'MSFT', 'GOOGL', 'TSLA', '^GSPC']; // 最后一个是标普500指数 // 雅虎财经API的公共端点(注意:此接口可能随时变更,需留意) const API_ENDPOINT = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=${stockSymbols.join(',')}`; // 主函数:获取所有股票数据 async function fetchAllStocks() { const updateTimeEl = document.getElementById('update-time'); updateTimeEl.textContent = `最后更新:${new Date().toLocaleTimeString()}`; try { const response = await fetch(API_ENDPOINT); if (!response.ok) { throw new Error(`网络响应异常: ${response.status}`); } const data = await response.json(); const quotes = data.quoteResponse.result; // 清空现有卡片容器 const container = document.querySelector('.stocks-grid'); container.innerHTML = ''; // 为每只股票创建卡片 quotes.forEach(stock => { const card = createStockCard(stock); container.appendChild(card); }); } catch (error) { console.error('获取股票数据失败:', error); const container = document.querySelector('.stocks-grid'); container.innerHTML = `<p class="error">数据加载失败,请检查网络连接。${error.message}</p>`; } } // 函数:根据单只股票数据创建卡片DOM元素 function createStockCard(stock) { const card = document.createElement('div'); card.className = 'stock-card'; // 计算涨跌幅和颜色 const regularMarketChange = stock.regularMarketChange?.toFixed(2) || 'N/A'; const regularMarketChangePercent = stock.regularMarketChangePercent?.toFixed(2) || 'N/A'; const isPositive = regularMarketChange >= 0; card.innerHTML = ` <div class="stock-header"> <div> <div class="symbol">${stock.symbol}</div> <div class="name" title="${stock.longName || stock.shortName || 'N/A'}">${stock.longName || stock.shortName || 'N/A'}</div> </div> </div> <div class="price">$${stock.regularMarketPrice?.toFixed(2) || 'N/A'}</div> <div class="change ${isPositive ? 'positive' : 'negative'}"> ${isPositive ? '+' : ''}${regularMarketChange} (${isPositive ? '+' : ''}${regularMarketChangePercent}%) </div> <div class="stock-meta"> <div class="meta-item"> <span class="meta-label">开盘</span> <span>$${stock.regularMarketOpen?.toFixed(2) || 'N/A'}</span> </div> <div class="meta-item"> <span class="meta-label">最高</span> <span>$${stock.regularMarketDayHigh?.toFixed(2) || 'N/A'}</span> </div> <div class="meta-item"> <span class="meta-label">成交量</span> <span>${formatVolume(stock.regularMarketVolume)}</span> </div> </div> `; return card; } // 辅助函数:格式化成交量(如将 1234567 转为 1.23M) function formatVolume(volume) { if (!volume) return 'N/A'; if (volume >= 1e9) return (volume / 1e9).toFixed(2) + 'B'; if (volume >= 1e6) return (volume / 1e6).toFixed(2) + 'M'; if (volume >= 1e3) return (volume / 1e3).toFixed(2) + 'K'; return volume.toString(); } // 页面加载时首次获取数据 document.addEventListener('DOMContentLoaded', fetchAllStocks); // 设置定时器,每30秒自动更新一次数据(可根据需要调整) setInterval(fetchAllStocks, 30 * 1000);代码逻辑深度解析:
- 异步获取(Async/Await):我们使用
async/await语法来处理网络请求,这让代码更清晰,避免了“回调地狱”。fetchAPI 是现代浏览器获取网络资源的标准方式。 - 错误处理(Try...Catch):网络请求充满了不确定性。用
try...catch包裹核心逻辑,一旦请求失败(如网络断开、API变更),能在界面上给用户一个友好的错误提示,而不是让页面白屏或崩溃。 - 数据提取与容错:从API返回的复杂JSON对象中,我们通过可选链操作符
?.来安全地访问可能不存在的属性(如stock.regularMarketPrice?.toFixed(2)),如果属性不存在,则返回‘N/A’,防止程序出错。 - 动态DOM操作:我们没有在HTML里写死卡片,而是用
document.createElement和innerHTML根据数据动态生成。这样做的好处是,只需修改stockSymbols数组,就能轻松增删关注的股票,无需改动HTML结构。 - 定时更新:
setInterval(fetchAllStocks, 30 * 1000)这行代码设置了每30秒自动重新获取一次数据并更新页面,实现了“盯盘”所需的实时性。这个间隔是平衡数据新鲜度和API请求压力的一个折中选择。
3.3 第三步:在旧手机上部署与优化
现在,你的电脑上已经有一个完整的、能本地运行的股票盯盘网页了。用浏览器打开index.html文件,你应该能看到一个美观的仪表盘。接下来,我们要把它搬到旧手机上,并配置成真正的“设备”。
本地网络测试:
- 确保你的电脑和旧手机连接在同一个Wi-Fi网络下。
- 在电脑上找到你的本地IP地址(在命令行输入
ipconfig(Windows) 或ifconfig(Mac/Linux) 查看)。 - 在电脑上启动一个简单的HTTP服务器。如果你安装了Python,可以在项目文件夹里运行
python -m http.server 8000。如果安装了Node.js,可以安装http-server包然后运行。 - 在旧手机的浏览器中,输入
http://<你的电脑IP地址>:8000(例如http://192.168.1.100:8000)。如果能看到页面,说明成功。
云端部署(推荐,实现永久访问): 本地测试只能在家用,我们需要一个公网可访问的地址。最简单免费的方法是使用GitHub Pages。
- 在GitHub上创建一个新的代码仓库(Repository)。
- 将你的
index.html,style.css,script.js三个文件上传到这个仓库。 - 在仓库的Settings(设置)中找到
Pages选项,将Source分支设置为main(或master)并保存。 - 几分钟后,GitHub会为你生成一个
https://<你的用户名>.github.io/<仓库名>/的网址。在任何能联网的设备上访问这个网址,都能看到你的股票盯盘页。
旧手机终极设置:
- 浏览器全屏:在手机浏览器中打开你的盯盘页地址(本地或云端的)。然后使用浏览器的“添加到主屏幕”或“安装应用”功能。在Chrome中,点击菜单 -> “添加到主屏幕”。这会在你的手机桌面上创建一个快捷方式,点击它将以近乎全屏、无浏览器地址栏的模式打开网页,体验更像一个原生App。
- 保持屏幕常亮:进入手机的设置 -> 显示 -> 休眠(不同手机路径可能不同),将自动休眠时间设置为“永不”或最大值(如30分钟)。这是关键一步,否则屏幕一会就灭了。
- 关闭无关通知:在手机设置中,为这个浏览器或这个“应用”关闭所有通知,并开启“勿扰模式”或“专注模式”,确保它不会被其他消息干扰。
- 电源优化:将浏览器或这个“应用”加入手机电池优化的白名单,防止系统为了省电而杀掉它的后台进程。同时,建议将手机一直连接充电器,或放在一个方便充电的位置。
4. 功能扩展与高级玩法
基础版本已经能用了,但我们可以让它更强大、更个性化。这里有几个扩展思路:
4.1 增加更多数据维度
目前的卡片显示了价格、涨跌幅、开盘、最高和成交量。你可以根据自己关注的信息,从API返回的数据中提取更多字段。例如:
- 最低价:
stock.regularMarketDayLow - 市盈率:
stock.trailingPE - 市值:
stock.marketCap(需要用formatVolume函数格式化) - 52周价格范围:
stock.fiftyTwoWeekLow/stock.fiftyTwoWeekHigh
只需在createStockCard函数的HTML模板中添加对应的meta-item即可。
4.2 实现数据持久化与自定义列表
现在股票列表是硬编码在script.js里的。我们可以改进它,让用户能在网页上自己添加、删除股票。
- 前端实现:在网页上增加一个输入框和“添加”按钮。用JavaScript监听点击事件,将输入的股票代码加入一个数组,并立即更新显示。同时,利用浏览器的
localStorage将这个数组保存起来,这样即使刷新页面,你的自定义列表也不会丢失。 - 简易后端(可选):如果你希望在多台设备间同步列表,可以考虑使用一个极简的后端,比如用
json-server模拟一个REST API,或者使用Supabase、Firebase这类BaaS(后端即服务)来存储用户配置。
4.3 添加图表与历史趋势
“盯盘”不仅要看当下,还要看趋势。你可以集成一个轻量级的图表库,如Chart.js或ApexCharts。
- 在每张股票卡片上增加一个“显示图表”的按钮。
- 点击后,可以动态获取该股票的历史数据(雅虎财经也有历史数据接口),然后渲染出一个简单的K线图或折线图,展示当日或近几日的价格走势。这会让你的盯盘器瞬间提升一个档次,更具参考价值。
4.4 价格预警功能(需要后端支持)
这是更进阶的功能。单纯的前端网页无法在后台运行并推送通知。要实现价格预警(例如,当股价跌破某个设定值时发送通知),你需要引入后端服务。
- 思路:编写一个运行在服务器上的脚本(可以用Python、Node.js等),定期(如每分钟)检查你关注的股票价格。
- 触发通知:当价格达到条件时,通过服务器调用手机推送服务(如使用Pushover、Telegram Bot、或者国内的服务号模板消息)将警报发送到你的常用手机上。
- 前端角色:此时,前端盯盘器主要作为可视化仪表盘,而预警逻辑则交给更可靠、能24小时运行的后端服务来处理。
5. 常见问题与排查实录
在制作和使用的过程中,你可能会遇到以下问题。这里是我踩过坑后总结的解决方案:
Q1:页面打开是空白的,或者控制台(Console)报跨域错误(CORS error)。
- 原因与排查:这是因为你直接双击用浏览器打开了本地的
index.html文件(file://协议)。现代浏览器出于安全考虑,默认禁止从file://协议发起的跨域网络请求(我们的JavaScript请求了雅虎的https://接口)。 - 解决:必须通过HTTP服务器访问。按照上文“第三步”的方法,在本地启动一个HTTP服务器(如
python -m http.server),然后通过http://localhost:8000来访问页面。
Q2:数据不更新了,或者显示“N/A”。
- 原因1:API接口变更或限制。雅虎财经的公共接口并非官方稳定API,有时会调整。打开浏览器的开发者工具(F12)切换到“网络(Network)”标签,查看对
finance.yahoo.com的请求是否失败或返回了错误信息。 - 解决:可以尝试搜索其他免费的金融数据API作为备选,如 Alpha Vantage(需免费注册获取API Key)、Twelve Data等。替换
script.js中的API_ENDPOINT和相应的数据解析逻辑即可。 - 原因2:股票代码格式错误。确保你使用的代码是雅虎财经认可的格式。例如,A股股票需要加上后缀,如
000001.SZ(深圳平安银行),港股如0700.HK(腾讯)。 - 解决:去雅虎财经官网搜索一下你想添加的股票,看看它在URL中使用的代码是什么。
Q3:旧手机耗电很快,或者一段时间后网页自动刷新/停止了。
- 原因:这是安卓系统后台省电策略导致的。即使设置了“永不休眠”,系统也可能在长时间无操作后,限制或停止浏览器标签页的活动以节省电量。
- 解决:
- 充电环境:务必让手机连接电源,这是最根本的解决方案。
- 开发者选项:进入手机“设置 -> 关于手机”,连续点击“版本号”7次以激活“开发者选项”。然后在开发者选项中,找到“保持唤醒”或“充电时不锁定屏幕”的选项并开启。
- 应用电池设置:进入手机“设置 -> 应用 -> [你的浏览器] -> 电池”,将电池策略设置为“无限制”或“不受优化”。
- 简单脚本保活:可以在网页的
script.js末尾加一段极其轻微的操作,比如每分钟让页面标题闪烁一下,告诉浏览器这个页面是活跃的。setInterval(() => { document.title = document.title === ‘📈’ ? ‘📉’ : ‘📈’; }, 60000);
Q4:界面在手机上显示不全,或者布局错乱。
- 原因:CSS的响应式设计可能在某些特定屏幕尺寸或浏览器上出现偏差。
- 解决:打开手机浏览器的“开发者工具”(通常可在设置中开启“USB调试”后用电脑Chrome的DevTools远程调试,或使用浏览器自带的响应式设计模式模拟)。检查元素样式,调整
style.css中的minmax(300px, 1fr)这个值,或者调整.stock-card的padding、font-size等,使其完美适配你的手机屏幕。
Q5:我想监控加密货币,怎么改?
- 解决:原理完全一样,只是数据源不同。你需要找到提供加密货币行情的API。例如,CoinGecko、Binance都有公开API。将
API_ENDPOINT替换为相应的API地址,并修改createStockCard函数中解析JSON数据的逻辑,提取出你需要的价格、涨跌幅等字段即可。这正体现了DIY项目的灵活性——数据源和展示逻辑完全由你定义。