以工作台为主,以导航为辅 —— 把你的效率工具与常用入口,收进同一处清爽的工作台。
EigerCore 是一个个人效率工作台(Personal Workbench)。它把日常高频使用的小组件(时钟、世界时间、待办、便签、日历、天气、自选股、招投标聚合、实用工具箱、倒计时、实时热搜、常用链接)集中在可拖拽排布的工作台网格上,让你打开页面就能组织一整天;其中常用网站导航作为工作台内的一个小组件存在,是顺手的补充,而非产品主体。
技术栈:Flask · Flask-SQLAlchemy · Flask-Login · Flask-WTF · SQLite · Vue 3 + Vite + TypeScript + Pinia · TailwindCSS(自定义设计系统)· SortableJS · Lucide Icons
- 工作台优先:首屏即工作台,所有效率组件自由增删、拖拽排序、独立配置、显隐自如。
- 导航为辅:常用网址收纳为工作台内的「链接」组件,与效率组件并列排布,随手可达。
- 数据自持:纯前端状态落盘本地,登录后自动同步到账户(服务端为唯一真相源),换设备不丢配置。
| 组件 | 说明 |
|---|---|
| 时钟 / 世界时间 | 本地时间 + 多城市世界时钟;与北京不同日的城市以浅红高亮 |
| 待办 | 轻量任务清单,勾选即归档 |
| 便签 | 随手记录,支持农历与标题 |
| 日历 | 月历视图,标注今日 |
| 天气 | 实时天气,支持自定义城市与密钥 |
| 自选股 | 关注的股票行情速览 |
| 招投标聚合 | 多平台招投标信息一站式聚合 |
| 实用工具箱 | 翻译、计算等常用小工具 |
| 倒计时 | 重要日期倒数 |
| 实时热搜 | 各平台热搜榜聚合 |
| 常用链接(导航) | 收纳常用网址,即「导航为辅」的载体 |
| 能力 | 说明 |
|---|---|
| 账号体系 | 注册 / 登录 / 退出,基于 Flask-Login 的会话管理,未登录访问受保护页面自动跳转登录页 |
| 数据隔离 | 每个用户只能看到、操作自己的分组、链接与工作台实例 |
| 拖拽排序 | SortableJS 拖拽卡片,松手后顺序自动保存 |
| 图标系统 | 每张卡片用 Lucide Icons 渲染图标,表单中填写图标名即可 |
| 设置弹窗 | 主题配色 / 用户资料 / 系统设置(沿用统一设置入口) |
| 主题切换 | 一键切换深色 / 浅色,即时生效并持久化 |
| 自适应布局 | 移动优先,卡片网格随屏幕与「每行数量」设置自适应 |
| 安全 | Jinja2 自动转义防 XSS、密码哈希存储、CSRF 防护、越权拦截、主题白名单、头像 URL 校验 |
EigerCore/
├── run.py # 启动入口(极简,调用 create_app 工厂)
├── init_db.py # 数据库初始化脚本(生成 instance/links.db)
├── requirements.txt # 依赖清单(已锁定版本)
├── README.md # 本文件
├── app/
│ ├── __init__.py # 应用工厂 create_app()(含 CSRF、主题上下文处理器)
│ ├── config.py # 配置:SECRET_KEY / 数据库地址
│ ├── models.py # 数据模型:User、Link、Group、UserSettings、WorkbenchState
│ ├── forms.py # 表单:RegisterForm / LoginForm / LinkForm / GroupForm
│ ├── routes.py # 蓝图 'main' 的全部路由与接口(含 JSON 协商)
│ ├── workbench_api.py # 工作台状态读写接口
│ ├── templates/
│ │ ├── base.html # 基础布局(顶栏 / 主题切换 / 视图切换 工作台·导航)
│ │ ├── login.html # 登录页(工作台品牌介绍)
│ │ ├── register.html # 注册页
│ │ └── dashboard.html # 原 Jinja 仪表盘(导航视图,保留作兼容)
│ └── static/ # 自定义 css/js(含 input.css 设计系统)
├── web/ # Vue 3 + Vite + TS 前端 SPA(工作台主体)
│ ├── src/components/workbench/ # 12 类工作台组件
│ ├── src/stores/workbench.ts # 工作台状态(Pinia)
│ └── dist/ # 构建产物,Flask 直接托管
└── instance/
└── links.db # SQLite 数据库文件(首次 init 自动生成)
要求 Python 3.8+(开发验证环境为 Python 3.13)。
# 1. 进入项目目录
cd EigerCore
# 2. (推荐)创建并激活虚拟环境
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # Linux / macOS
# 3. 安装依赖
pip install -r requirements.txt
# 4. 初始化数据库(生成 instance/links.db 及表结构)
python init_db.py
# 5. 启动开发服务器
python run.py浏览器访问 http://127.0.0.1:5000 即可使用。
前端 web/ 是一个用 Vue 3 + Vite + TypeScript + Pinia 构建的单页应用(SPA),
即产品的工作台主体;构建产物由 Flask 直接托管,与原有 Jinja 接口共用同一套后端,可一键切换。
# 1. 构建前端(产物输出到 web/dist)
cd web && npm install && npm run build
# 2. 以 SPA 模式启动(等价于 USE_SPA=1 python run.py)
python serve_spa.py- 访问 http://127.0.0.1:5000 即进入工作台;未登录会自动跳转到
/login(Jinja 登录页,登录后回到工作台)。 - 关闭 SPA:直接
python run.py(不设置USE_SPA),回退到原 Jinja 仪表盘(导航视图),零回归。 - 通过环境变量
USE_SPA=1/0控制开关;未构建web/dist时即使开启也会自动回退到 Jinja。
适用对象:把 EigerCore 部署到一台干净的服务器 / 云主机 / 面板环境。
核心三步:① 装 Python 依赖 → ② 初始化数据库(
instance/links.db)→ ③ 以生产方式启动(gunicorn)并用 Nginx / 面板反代到 80/443。工作台是 Vue 3 单页应用(SPA),需提前构建
web/dist。可在本机构建后上传,也可在服务器上构建(需 Node.js 18+)。
| 项目 | 要求 | 说明 |
|---|---|---|
| Python | 3.8+(推荐 3.11 / 3.12) | 仅标准库 + Flask 全家桶 |
| pip | 随 Python 自带 | 安装 requirements.txt |
| Node.js | 18+(仅构建前端时需要) | 用 npm run build 生成 web/dist |
| 反向代理 | Nginx / 面板自带 Nginx | 把公网 80/443 转发到本地 5000 |
| 数据库 | 无需额外服务 | 默认 SQLite(单文件,已内置) |
生产 Web 服务器强烈建议用 gunicorn(性能好、可守护)。如未安装:
pip install gunicorn。 开发自测可临时用python serve_spa.py(Flask 自带服务器),但不要用于生产。
以下通用步骤所有环境都要先执行:
# 1) 获取代码(或从本机把整个目录上传到服务器)
git clone https://github.com/praming/EigerCore.git
cd EigerCore
# 2) 创建并激活虚拟环境(强烈建议,避免污染系统 Python)
python -m venv venv
source venv/bin/activate # Linux / macOS
# venv\Scripts\activate # Windows
# 3) 安装依赖
pip install -r requirements.txt
# 4) 初始化数据库(生成 instance/links.db 及表结构;已存在则增量迁移,不丢数据)
python init_db.py构建前端工作台(任选其一):
- 本机构建后上传(推荐、服务器最省事):在开发机执行
cd web && npm install && npm run build,把生成的整个web/dist目录上传到服务器对应web/dist。 - 服务器上构建:服务器装好 Node 18+ 后执行
cd web && npm install && npm run build。
只要
web/dist/index.html存在,并以USE_SPA=1启动,访问站点即进入工作台;若web/dist缺失,会自动回退到原 Jinja 导航页(不会崩)。
无论哪种方式,生产启动的本质都是同一句:
USE_SPA=1 gunicorn -w 2 -b 127.0.0.1:5000 serve_spa:app
serve_spa:app:模块serve_spa.py暴露的 Flaskapp(它内部已设USE_SPA=1)。-w 2:2 个工作进程(SQLite 单文件足够;CPU 核多可加到 3~4)。-b 127.0.0.1:5000:只监听本机回环,由 Nginx / 面板反代对外。
① 用 systemd 托管 gunicorn —— 新建 /etc/systemd/system/eigercore.service:
[Unit]
Description=EigerCore Workbench
After=network.target
[Service]
User=www-data
WorkingDirectory=/opt/EigerCore
Environment=SECRET_KEY=换成一段足够随机的长字符串
Environment=USE_SPA=1
ExecStart=/opt/EigerCore/venv/bin/gunicorn -w 2 -b 127.0.0.1:5000 serve_spa:app
Restart=always
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now eigercore
sudo systemctl status eigercore # 确认 active (running)② 配置 Nginx 反代 —— 新建 /etc/nginx/conf.d/eigercore.conf:
server {
listen 80;
server_name your.domain.com; # 改成你的域名
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}sudo nginx -t && sudo systemctl reload nginx如需在应用层正确拿到客户端真实 IP / 协议,可在
app/__init__.py给 app 加from werkzeug.middleware.proxy_fix import ProxyFix; app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)(详见 FAQ)。
③ HTTPS:用 Let's Encrypt 免费证书(certbot --nginx),或在面板 / CDN 处签发后回源 80。
- 装软件:「软件商店」安装 Python 项目(Python 项目管理器) 与 Nginx。
- 建站点:「网站 → 添加站点」,填域名,PHP 版本选「纯静态」(我们只在面板里借用它的 Nginx 反代)。
- 部署 Python 项目:「Python 项目 → 添加项目」:
- 项目路径:
/www/wwwroot/EigerCore(上传代码处) - Python 版本:选 3.8+
- 框架:Flask
- 启动文件:
serve_spa.py - 启动命令:
gunicorn -w 2 -b 127.0.0.1:5000 serve_spa:app - 端口:
5000 - 勾选「开机启动」
- 「环境变量」里加:
SECRET_KEY=随机串、USE_SPA=1 - 保存并启动
- 项目路径:
- 反代:进该站点「设置 → 反向代理 → 添加反代」,目标 URL 填
http://127.0.0.1:5000,发送域名$host;保存后访问域名即进入工作台。 - 前端:服务器「终端」进
/www/wwwroot/EigerCore/web执行npm install && npm run build(需先在系统里装好 Node 18+);或直接把本机构建好的web/dist上传覆盖。 - HTTPS:站点「SSL」申请证书并强制 HTTPS。
- 运行环境:「网站 → 运行环境」创建 Python 运行环境(选 3.8+,面板自动装好 pip / gunicorn)。
- 建站:「网站 → 创建网站(Python)」:
- 主目录:
/opt/EigerCore(上传代码处) - 运行环境:选上一步创建的环境
- 应用端口:
5000 - 启动文件 / 模块:
serve_spa:app - 启动命令:
gunicorn -w 2 -b 0.0.0.0:5000 serve_spa:app - 高级设置加环境变量:
SECRET_KEY=随机串、USE_SPA=1 - 确认创建并启动(1Panel 会自动生成 Nginx 反代)
- 主目录:
- 前端:「终端」进
web目录执行npm install && npm run build;或上传本机构建好的web/dist。 - HTTPS:站点「证书」绑定并开启 HTTPS 强制跳转。
各面板 UI 文案随版本略有差异,但核心三要素一致:启动模块
serve_spa:app+ 端口5000+ 环境变量USE_SPA=1与SECRET_KEY,再把 Nginx 反代到127.0.0.1:5000。
EigerCore 官方提供 Docker 镜像(Docker Hub:praming/eigercore),单容器 + 一个数据卷即可运行,无需额外数据库、也无需本地代码或构建:
- 用仓库内置的 compose(推荐):克隆后直接执行,自动从 Docker Hub 拉取镜像并启动:
该 compose 采用命名卷持久化(数据由 Docker 托管)。如需把数据固定挂到宿主机指定目录,改用仓库附带的 bind mount 参考文件:
git clone https://github.com/praming/EigerCore.git cd EigerCore docker compose up -d # 拉取 praming/eigercore:latest 并启动(数据落在命名卷 eigercore-instance)
其中docker compose -f docker-compose.server.yml up -d
docker-compose.server.yml已用演示路径/srv/eigercore/data:/app/instance,请按你的服务器把左侧改为实际目录(先mkdir -p该目录)。 - 直接用镜像:
docker run -d --name eigercore -p 5000:5000 -v eigercore-instance:/app/instance praming/eigercore:latest。 - 启动后访问
http://<宿主机>:5000,第一个注册账号自动成为管理员。
镜像默认内置 SQLite,所有可变数据(数据库、会话密钥、上传图标)均落在容器卷
/app/instance。备份该卷即可完成备份与迁移,无需任何.env文件;升级只需docker compose pull && docker compose up -d(仓库 compose 已设pull_policy: always),数据不丢。 将示例中的praming替换为你的 Docker Hub 用户名;锁定版本可用praming/eigercore:1.0.0之类的具体 tag。
备份只需三类文件(全部位于项目目录内):
| 内容 | 路径 | 说明 |
|---|---|---|
| 数据库 | instance/links.db |
全部账号、链接、分组、工作台配置(SQLite 单文件,复制即备份) |
| 前端产物 | web/dist/ |
SPA 构建结果;保留源码可不备份,重建即可 |
| 配置 / 密钥 | .env 或环境变量 / app/config.py |
SECRET_KEY、数据库路径等;务必单独安全保存 SECRET_KEY |
每日自动全量备份脚本(示例 backup.sh):
#!/bin/bash
set -e
SRC=/opt/EigerCore
BAK=/backup/eigercore/$(date +%F)
mkdir -p "$BAK"
# SQLite 单文件;停服再拷可 100% 避免写一半,不停服直接拷通常也安全
cp "$SRC/instance/links.db" "$BAK/links.db"
cp -r "$SRC/web/dist" "$BAK/dist"
chmod -R 600 "$BAK"
echo "backup done: $BAK"加入定时任务:
# crontab -e
0 3 * * * /opt/EigerCore/backup.sh >> /var/log/eigercore-backup.log 2>&1安全提示:
links.db含密码哈希与所有个人数据,备份文件请放在非公开目录并收紧权限(chmod 600),不要随代码库提交。
场景 A:整机迁移(换服务器,保留全部数据)
- 旧服务器:按「3. 备份」打包
instance/links.db+web/dist,并记录SECRET_KEY、Python / Node 版本。 - 新服务器:执行「1. 安装」完成依赖与目录结构;把备份的
instance/links.db放回instance/,web/dist放回web/。 - 用相同
SECRET_KEY启动(否则已有会话 / CSRF 令牌全部失效,用户需重新登录——数据本身不丢)。 - 按「2. 部署」任一方式启动 + 反代。
场景 B:版本升级(拉取新代码)
cd /opt/EigerCore
git pull
source venv/bin/activate
pip install -r requirements.txt # 如有新增依赖
python init_db.py # 增量迁移,补齐新表/列,不丢数据
cd web && npm install && npm run build # 重建前端
# 重启服务:systemd → systemctl restart eigercore;面板 → 在面板点重启场景 C:从 SQLite 换数据库(可选)
默认 SQLite 已满足个人使用。如需换 Postgres / MySQL,修改 app/config.py 的 SQLALCHEMY_DATABASE_URI 并执行 python init_db.py 重新建表,再用数据库自带工具把数据导入(如 sqlite3 ... .dump 后适配 SQL,或用 ETL 工具)。
- 502 / 站点打不开:先
curl 127.0.0.1:5000确认 gunicorn 在跑;再看 Nginx 反代目标是否为127.0.0.1:5000;检查SECRET_KEY/USE_SPA是否注入。 - 工作台白屏但
/login正常:多半是web/dist没构建或路径不对;确认web/dist/index.html存在,且以USE_SPA=1启动。 - 改了代码不生效:gunicorn 需重启(
systemctl restart/ 面板重启);SPA 改动需重新npm run build。 - 忘记
SECRET_KEY:可重置,但所有用户会话会失效、需重新登录;数据不受影响。
- 注册 / 登录:首次打开会跳转到登录页,点击「去注册」创建账号;登录后进入工作台。
- 工作台布局:顶栏在「工作台视图」下提供「组件库」入口,逐类罗列 12 种组件,可添加 / 隐藏 / 重命名 / 设置 / 删除;编辑模式下网格卡片支持拖拽排序、调整尺寸、点齿轮进入设置。
- 导航(常用链接):「常用链接」组件即导航载体,在设置中维护网址分组与卡片;也可通过顶栏切换到「导航视图」查看原分组式导航墙。
- 新增链接:在「常用链接」组件设置中点击新增,填写标题、URL、图标与分组即可。
- 拖拽排序:直接拖动卡片调整顺序,松手后顺序自动保存。
- 编辑 / 删除:卡片右下角提供「编辑」「删除」按钮;删除会弹二次确认。
- 设置:点击右上角「设置」图标打开弹窗:主题 / 配色即时切换;用户资料设置昵称与头像;系统设置调整每行数量与登录保持天数。
- 深色 / 浅色切换:点击右上角太阳 / 月亮按钮一键切换(即时保存)。
工作台是产品的主体视图(顶栏默认进入),定位为个人效率一览:时钟、天气、日历、待办、记事、自选股、工具箱、倒计时、招标信息、常用链接(导航)、实时热搜、世界时间共 12 类小组件,以网格卡片形式自由排布。
后端按账户持久化的核心结构为实例数组 instances:
interface WidgetInstance {
iid: string // 实例唯一 ID(如 w_weather_8f3a)
type: CardId // 组件类型(见下方 12 种)
title?: string // 自定义标题(留空回退默认名)
hidden: boolean // 是否隐藏(仅前端不渲染,不清数据)
showTitle: boolean // 是否显示卡片标题栏
autoHeight: boolean // 满宽卡片是否按内容自适应高度
size: { w: number; h: number } // 网格占位(w=列跨度 1~9,h=行跨度 1~5)
config: Record<string, any> // 该实例的私有配置(数据隔离的关键)
}- 同类组件可添加多次:例如添加多个「天气」实例,每个实例的
config(城市、API Key、刷新策略等)相互独立,互不影响。 - 隐藏 ≠ 删除:隐藏仅把
hidden置 true,前端不再渲染该卡片,用户数据(config)原样保留,可随时取消隐藏恢复显示。 - 删除会清空数据:从组件库删除某实例会将其从
instances中移除,该实例的 config 一并丢失;之后重新添加同类型组件会创建一个全新的默认实例(需重新在设置页自定义)。
顶栏在工作台视图下会显示一个「组件库」按钮(网格图标)。点击打开小组件库弹窗,逐类罗列全部 12 种组件:
- 添加:每个组件类型右侧的「+ 添加」按钮,新增一个独立实例(默认配置、数据隔离)。
- 显示 / 隐藏:每行右侧的开关控制该实例是否在前端显示;关闭即隐藏,数据保留。
- 重命名:铅笔图标可内联修改实例标题(留空回退默认名)。
- 设置:齿轮图标打开该实例的设置弹窗(城市、刷新、字号、持仓等),与卡片设置一致。
- 删除:垃圾桶图标,两步确认(先点删除、再点出现的「确认」)后移除实例。
编辑模式(顶栏铅笔图标)下,工作台网格卡片也支持拖拽排序、调整尺寸、点齿轮进入设置。
工作台状态通过 GET/PUT /api/workbench/state 按登录账户持久化(后端白名单仅持久化 instances 顶层键,JSON 透传存储);切换端口 / 设备只要登录同一账号即可恢复布局与数据;未登录仅以 localStorage 作离线缓存。
「导航」是工作台中以「常用链接」组件形式存在的辅助能力,用于收纳高频网址;同时保留原 Jinja 仪表盘的「导航视图」作为兼容入口。其分组 / 链接的增删改、拖拽排序、图标系统均与工作台共享后端接口与 Lucide 图标规范。
本项目统一使用 Lucide Icons,不再依赖 FontAwesome。
- 卡片 / 按钮图标均通过
<i data-lucide="图标名"></i>声明,页面加载时由lucide.createIcons()统一替换为内联 SVG。 - 后端
Link.icon字段存储的是 Lucide 图标名(单个字符串,如globe、mail、youtube),不再存储fa-xxx类名。 - 查找图标名:前往 https://lucide.dev/icons 搜索,复制图标名称填入即可。
- 品牌图标限制:Lucide 自 v1 起移除了品牌 / Logo 类图标(如
github、twitter、youtube等早期存在过的名称可能已失效)。若需为某网站选图标,请改用通用图标,例如用globe、code、music、shopping-cart等中性图标代替。 - 旧数据迁移:若数据库中已存有 FontAwesome 类名(如
fa-solid fa-link),它们不是合法的 Lucide 名,将不会渲染图标。可进入「编辑」重新填写 Lucide 名,或执行脚本批量清空icon字段以使用默认link图标。
配置集中在 app/config.py 的 Config 类:
| 配置项 | 默认值 | 说明 |
|---|---|---|
SECRET_KEY |
dev-secret-key-change-me |
会话签名 / CSRF 令牌密钥。生产环境务必通过环境变量覆盖 |
SQLALCHEMY_DATABASE_URI |
sqlite:///<项目>/instance/links.db |
数据库文件路径,一般无需修改 |
SQLALCHEMY_TRACK_MODIFICATIONS |
False |
关闭旧版事件追踪,消除警告、提升性能 |
设置生产密钥示例:
# Windows (PowerShell)
$env:SECRET_KEY = "一串足够随机的长字符串"
# Linux / macOS
export SECRET_KEY="一串足够随机的长字符串"| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| GET | / |
已登录进入工作台(SPA),否则跳 /login |
否 |
| GET/POST | /register |
注册 | 否 |
| GET/POST | /login |
登录 | 否 |
| GET | /logout |
退出登录 | 是 |
| GET | /dashboard |
原 Jinja 仪表盘(导航视图,兼容保留);支持 ?group=<id> 按分组筛选 |
是 |
| POST | /link/add |
新增链接(含可选 group_id) |
是 |
| POST | /link/<id>/edit |
编辑链接(含所有权校验) | 是 |
| POST | /link/<id>/delete |
删除链接(含所有权校验) | 是 |
| POST | /update-order |
拖拽排序:接收 {"order": [id1, id2, ...]},批量更新 sort_order |
是 |
| POST | /group/add |
新增分组 | 是 |
| POST | /group/<id>/edit |
编辑分组(含所有权校验) | 是 |
| POST | /group/<id>/delete |
删除分组(其链接移至「未分组」,含所有权校验) | 是 |
| POST | /settings/theme |
设置主题配色(JSON:{"theme": "dark"}) |
是 |
| POST | /settings/profile |
设置用户资料(JSON:{"nickname": "...", "avatar": "https://..."}) |
是 |
| POST | /settings/system |
设置系统项(JSON:{"links_per_row": 4, "session_days": 7}) |
是 |
| GET/PUT | /api/workbench/state |
工作台状态(实例数组 instances)按账户读写;PUT 受 CSRF 保护,白名单仅持久化 instances 顶层键 |
是 |
/update-order 行为细节:
- 请求体为 JSON:
{ "order": [3, 1, 5, 2] },数组元素为链接id,按期望的新顺序排列。 - 后端遍历数组,将每个
id对应的sort_order设为下标 + 1;只更新属于当前用户且真实存在的链接,忽略非法 / 他人id(越权防护)。 - 成功返回
{"status": "ok"};order非列表时返回400。
- XSS 防护:所有用户输入经 Jinja2
{{ }}自动转义输出;项目刻意不使用| safe过滤器(它会关闭转义,反而引入漏洞)。 - 密码安全:密码以
werkzeug.security.generate_password_hash哈希存储,永不保存明文。 - CSRF 防护(全局):通过
flask_wtf.CSRFProtect在app/__init__.py全局启用,覆盖所有 POST/PUT/DELETE/PATCH 请求。- 表单(登录 / 注册 / 增 / 改 / 删):模板渲染
{{ form.csrf_token }}字段随表单提交; - 拖拽排序接口
/update-order走 JSON +fetch:base.html注入<meta name="csrf-token" content="...">,dashboard.html的 fetch 自动带上X-CSRFToken请求头。缺少令牌的请求会被拒绝(返回 400)。
- 表单(登录 / 注册 / 增 / 改 / 删):模板渲染
- 越权防护:编辑 / 删除 / 排序 / 分组 / 设置均校验资源归属当前用户,非法访问返回
403/404。 - 登录失败不区分原因:统一提示「用户名或密码错误」,避免被用于撞库。
- 登录保持天数:登录时读取用户设置中的
session_days,作为login_user(remember=True, duration=...)的有效期。修改后下次登录生效。 - 主题白名单:
/settings/theme仅接受预置主题名,拒绝任意字符串写入data-theme,防止属性注入。 - 头像 URL 校验:
/settings/profile的头像字段仅允许为空或以http://、https://开头,拒绝javascript:等伪协议,避免注入。
- 应用工厂模式:
app/__init__.py的create_app()负责装配 Flask、数据库、登录管理器并注册蓝图,便于测试与多配置扩展。 - 蓝图结构:所有路由位于
app/routes.py的Blueprint('main', __name__),后续可按业务拆分为api、admin等多个蓝图。 - 数据库变更:
init_db.py现已支持增量迁移——对已有数据库会自动ALTER TABLE补齐新增列,并为存量用户补建默认数据,不会丢失已有数据;对全新数据库则直接create_all建表。开发期也可直接删除instance/links.db后重新运行python init_db.py。 - 前端资源:
web/为本地构建的 Vue 3 SPA;设计系统手写于app/static/src/input.css(非 daisyUI),经web/src/style.css接入。 - 本 README 随项目同步维护:任何结构、路由或配置的变更,请同步更新本文档对应章节,并保持「工作台为主、导航为辅」的口径。
Q:工作台和导航是什么关系? A:工作台是产品主体,打开即见;导航是工作台内的「常用链接」组件,是辅助收纳网址的能力,也可通过顶栏切换到独立的「导航视图」(原 Jinja 仪表盘)。
Q:图标不显示?
A:确认填写的是合法的 Lucide 图标名(参见 lucide.dev/icons),且不含 fa- 前缀;品牌类图标(github 等)在 Lucide 中通常不可用,请换通用图标。
Q:拖拽后顺序没保存?
A:打开浏览器控制台(F12)查看保存日志;确认已登录且接口返回 ok。
Q:如何重置数据?
A:停止服务后删除 instance/links.db,重新运行 python init_db.py。
Q:主题 / 每行数量在哪里设置? A:点击右上角「设置」图标打开弹窗:主题配色在「主题 / 配色」区点击切换;每行数量在「系统设置」区选择。
Q:登录保持天数是怎么生效的? A:在「设置 → 系统设置」中填写 1~365 之间的天数,下次登录起,"记住我" cookie 的有效期即为该天数;旧会话不受影响,需重新登录应用新值。