用一个 Flask 文件,搭一个属于自己的导航首页

举报
yd_291797886 发表于 2026/10/18 12:11:38 2026/10/18
【摘要】 # 用一个 Flask 文件,搭一个属于自己的导航首页> 从一行想法到一个公网可访问、已发布到华为云作品展览馆的导航页——这篇文章记录了完整的过程。## 为什么需要自己的导航页每天打开浏览器,第一件事是干什么?我的习惯是:先去 Google 搜点什么,然后跳到 GitHub 看看通知,再打开华为云控制台查一下资源状态,中途可能还要去 Stack Overflow 找个报错的解法。这些网站散落...

# 用一个 Flask 文件,搭一个属于自己的导航首页

> 从一行想法到一个公网可访问、已发布到华为云作品展览馆的导航页——这篇文章记录了完整的过程。

## 为什么需要自己的导航页

每天打开浏览器,第一件事是干什么?

我的习惯是:先去 Google 搜点什么,然后跳到 GitHub 看看通知,再打开华为云控制台查一下资源状态,中途可能还要去 Stack Overflow 找个报错的解法。这些网站散落在浏览器书签栏里,多了以后找起来反而费劲。

市面上的导航站不少,但要么广告满天飞,要么定制化程度低,要么得注册账号才能保存自己的配置。我就想要一个东西:

- **一个页面,把我常用的网站全放上去**
- **能搜索、能按分类看**
- **够好看,深色主题**
- **代码简单到一眼看懂,随时能改**

于是用 Flask 写了一个单文件导航页。本文记录从开发到公网分享再到发布作品的全过程。

## 它长什么样

先看效果。整个页面分为三个部分:

1. **顶部搜索框**——输入关键词,下方卡片实时过滤,不用按回车
2. **分类区域**——5 个分类(搜索引擎 / 开发者工具 / 云计算 / 社交媒体 / 邮箱),每个分类下是一排网站卡片
3. **网站卡片**——带品牌色圆点图标,鼠标悬停时上浮,点击即跳转

一共 19 个常用网站,覆盖了日常开发和学习中最常去的地方。

```
┌──────────────────────────────────────┐
│         🔍 搜索网站...               │
├──────────────────────────────────────┤
│  搜索引擎                             │
│  [Google] [Bing] [百度] [DuckDuckGo]  │
├──────────────────────────────────────┤
│  开发者工具                           │
│  [GitHub] [Stack Overflow] [MDN] ... │
├──────────────────────────────────────┤
│  ...                                  │
└──────────────────────────────────────┘
```

## 技术选型:为什么是 Flask + 单文件

导航页这种东西,本质就是一个 HTML 页面。用纯 HTML 写也行,但我想让网站列表变成数据,而不是硬编码在 HTML 里——这样增删一个网站只需要改一行 Python 字典,不用碰 HTML。

**Flask** 是最轻量的选择。它自带的 `render_template_string` 可以把一个内联 HTML 模板和 Python 变量渲染到一起,不需要单独的 templates 目录,不需要 Jinja 模板文件,一个 `.py` 文件搞定一切。

没有用 Vue、React 之类的前端框架,因为搜索过滤的逻辑用十几行原生 JavaScript 就能实现,引入框架反而是杀鸡用牛刀。也没有用 CSS 框架,手写 CSS Grid 布局,几十行就搞定了响应式。

最终的技术栈:

| 层 | 选择 | 理由 |
|----|------|------|
| 后端 | Flask 3.1.3 | 最小化的 Python Web 框架 |
| 模板 | 内联 `render_template_string` | 单文件,无需模板目录 |
| 样式 | 手写 CSS Grid | 轻量,无构建步骤 |
| 交互 | 原生 JavaScript | 搜索过滤,无需框架 |
| 部署 | DevBridge 隧道 | 免服务器,公网可访问 |

## 核心实现

### 数据结构:网站列表

所有网站数据用一个 Python 列表表示,每个分类是一个字典,包含名称、图标和链接列表:

```python
CATEGORIES = [
    {
        "name": "搜索引擎",
        "icon": "🔍",
        "color": "#4285F4",
        "links": [
            {"name": "Google", "url": "https://www.google.com", "color": "#4285F4"},
            {"name": "Bing", "url": "https://www.bing.com", "color": "#00897B"},
            {"name": "百度", "url": "https://www.baidu.com", "color": "#2932E1"},
            {"name": "DuckDuckGo", "url": "https://duckduckgo.com", "color": "#DE5833"},
        ],
    },
    {
        "name": "开发者工具",
        "icon": "🛠️",
        "color": "#24292E",
        "links": [
            {"name": "GitHub", "url": "https://github.com", "color": "#24292E"},
            {"name": "Stack Overflow", "url": "https://stackoverflow.com", "color": "#F48024"},
            {"name": "MDN", "url": "https://developer.mozilla.org", "color": "#83D0F9"},
            {"name": "Python 文档", "url": "https://docs.python.org", "color": "#3776AB"},
        ],
    },
    # ... 云计算、社交媒体、邮箱
]
```

想加一个网站?在对应的分类里加一行字典就行。想加一个分类?在列表里加一个字典。数据即配置,这是我喜欢的方式。

### 路由:一个页面搞定

```python
from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
    return render_template_string(TEMPLATE, categories=CATEGORIES)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
```

整个后端就这么多。Flask 接收请求,把 `CATEGORIES` 数据传给内联模板,渲染成 HTML 返回。没有数据库,没有 API,没有认证——一个导航页不需要这些。

### 模板:深色主题 + CSS Grid

模板用 Jinja2 语法写在 Python 字符串里。关键部分:

**CSS Grid 响应式布局**——卡片自动排列,屏幕宽就多放几个,窄就少放:

```css
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
}
```

`auto-fill` 让浏览器根据容器宽度自动决定每行放几个卡片,`minmax(200px, 1fr)` 保证每个卡片最小 200px,剩余空间均匀分配。一行 CSS 实现响应式,不用 media query。

**深色主题**——背景 `#1a1a2e`,卡片 `#16213e`,文字白色,搜索框半透明。配色参考了 GitHub Dark 和 VS Code 的暗色主题,长时间看不累眼。

**卡片 hover 效果**——鼠标悬停时上浮 4px,加一圈品牌色边框:

```css
.card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
    border-color: var(--brand-color);
}
```

### 搜索过滤:十几行 JavaScript

搜索框监听 `input` 事件(每次按键都触发,不需要按回车),遍历所有卡片,根据卡片名称是否包含搜索词来显示或隐藏:

```javascript
searchBox.addEventListener('input', function() {
    const query = this.value.toLowerCase().trim();
    document.querySelectorAll('.card').forEach(card => {
        const name = card.dataset.name.toLowerCase();
        card.style.display = name.includes(query) ? '' : 'none';
    });
    // 隐藏没有可见卡片的分类
    document.querySelectorAll('.category').forEach(cat => {
        const visible = cat.querySelectorAll('.card:not([style*="none"])').length;
        cat.style.display = visible > 0 ? '' : 'none';
    });
});
```

逻辑很简单:输入 → 过滤卡片 → 隐藏空分类。全部在浏览器端完成,不发请求,不刷新页面。

## 让它公网可访问:DevBridge 隧道

写完代码,`python app.py` 跑起来,`http://localhost:8080` 能看了。但只有我自己能看——它跑在我的本地环境里。

要让别人也能访问,传统做法是租一台云服务器,配 Nginx,搞域名解析,一套下来少说半小时。我这次用了 **DevBridge 开发隧道**,几秒钟就把本地服务暴露到了公网。

DevBridge 的原理很简单:它在华为云中继服务器和你的本地服务之间建一条隧道,公网用户访问隧道 URL 时,请求被转发到你的本地端口。你不需要服务器、不需要公网 IP、不需要域名配置。

创建隧道后,我拿到了一个公网 URL:

```
https://duf2tgti-8088.devbridge-s2.hwtunnel.com
```

任何人打开这个链接,看到的就是我本地跑着的导航页。用 curl 验证一下:

```bash
curl -s https://duf2tgti-8088.devbridge-s2.hwtunnel.com | head -5
# HTTP 200,返回正确的 HTML 内容
```

搞定。从写完代码到公网可访问,中间没有买服务器、没有配 Nginx、没有搞 DNS。

## 发布到华为云作品展览馆

导航页跑起来了,公网也能访问了,最后一步是把它发布到华为云开发者作品展览馆——一个面向开发者的作品展示平台。

发布流程用了一个自动化 skill,整体分 8 步:

### 1. 身份认证:STS 临时凭证

发布前需要验证调用者身份。这里用了一个巧妙的设计:通过 IAM 自委托 `SELF_VERIFY` 生成 **STS 临时凭证**(有效期 900 秒),只把临时 AK/SK 传给平台,永久密钥始终留在本地。平台用临时凭证调用 `STS.GetCallerIdentity` 解析出账号 ID,完成身份验证。

### 2. 封面截图

skill 自动用 Playwright 启动一个 headless Chromium,访问本地运行的导航页,截取首页,然后合成一张 16:9 的封面图(1280×720)。封面布局有几种可选,这次用的是 `spotlight`(单图聚焦),效果不错。

### 3. 代码推送 GitCode

在 GitCode 上创建仓库,把代码推上去。这样展览馆页面会关联源码链接,别人不仅能看你的演示,还能直接看代码。

### 4. 详情打包

把 README.md 和一张架构图打包成 zip 上传。架构图也是自动生成的——用一个 HTML 文件描述三层架构(浏览器 → Flask → 模板),Playwright 截图转 PNG。

### 5. 提交发布

所有材料齐了——作品名称、简介、封面图、详情包、Git 仓库地址、隧道 URL——打包成一个 JSON,调用发布接口。平台返回 `201 Created`,作品上线。

最终结果:

- **展览馆链接**:https://gallery.developer.huaweicloud.com/gallery/0064a05989c00000
- **所属活动**:闯关活动
- **成长积分**:+200

## 一些设计思考

回过头来看这个项目,有几个值得聊的点。

**为什么不存数据库?** 导航页的数据量极小(19 个链接),用 Python 字典直接写在代码里,改起来最直接。引入数据库反而增加了复杂度——要建表、要写 CRUD、要考虑迁移。对于这个规模的应用,数据即代码是最合理的选择。

**为什么不用前端框架?** 搜索过滤是唯一的交互逻辑,十几行原生 JS 搞定。用 React/Vue 的话,光项目脚手架就比整个应用代码还多。技术选型应该匹配问题规模,而不是追新。

**为什么用内联模板?** 正常 Flask 项目会把模板放在 `templates/` 目录下。但这个项目只有一个页面,拆成两个文件反而增加了认知开销。`render_template_string` 让一切都在一个文件里,读代码时不用跳来跳去。

**单文件的边界在哪里?** 如果导航页要加用户系统、加自定义分类管理、加数据持久化,单文件就会变得臃肿。到那时再拆分也不迟——从单文件起步,按需演进,比一开始就过度设计好。

## 如何使用

如果你想基于这个项目改一个自己的导航页,很简单:

```bash
# 克隆仓库
git clone https://gitcode.com/tttttqqqqq/nav-portal.git
cd nav-portal

# 安装依赖
pip install -r requirements.txt

# 启动
python app.py
```

浏览器打开 `http://localhost:8080`,然后修改 `app.py` 里的 `CATEGORIES` 列表,换成你自己常用的网站就行了。

## 总结

这个项目本身不复杂——一个 Flask 单文件、19 个网站链接、一个搜索框。但围绕它走完了完整的一圈:开发 → 本地运行 → 公网分享 → 代码托管 → 作品发布。每一环都用了最轻量的方式,没有过度工程。

有时候最好的架构就是没有架构。一个文件能搞定的事,不要用三个。

---

*项目地址*:[GitCode](https://gitcode.com/tttttqqqqq/nav-portal)
*在线演示*:https://duf2tgti-8088.devbridge-s2.hwtunnel.com
*作品展览馆*:https://gallery.developer.huaweicloud.com/gallery/0064a05989c00000

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。