# ONE · 一个博客 一个能跑通全流程的博客 MVP:后台登录 → 写文章(长文 / 短文)→ 发布 → 前台按「07 融合 + Twitter 信息流」风格展示。 - 后端:Go(`net/http` + `database/sql`,SQLite / PostgreSQL 双支持),在 `backend/` - 前台 + 后台:Vue 3 + Vue Router + Vite,在 `frontend/`(pnpm 管理依赖) - Markdown:服务端用 `goldmark` 渲染入库,编辑器实时预览用 `marked` + `DOMPurify` ## 一条命令跑起来 ```bash make dev # 后端 8080 + 前端 3000(前端带热更新,已配好 /api 代理) ``` 打开 http://localhost:3000 是前台,http://localhost:3000/admin 是后台。 停掉:`make stop` 想要单端口(前端构建产物由 Go 直接托管,接近生产形态): ```bash make start # 先 pnpm run build,再起 Go,只开 8080 ``` 打开 http://localhost:8080 。 其它:`make web` 只构建前端、`make server` 只起后端、`make test` 跑后端测试、`make clean` 清构建产物、`make db-reset` 清空本地 SQLite 数据。 ## 后台登录 默认账号密码来自环境变量,未设置时是 `admin` / `admin`(启动日志会打印出来): ```bash ONE_ADMIN_USER=admin ONE_ADMIN_PASSWORD=换一个 make start ``` 登录态是服务端签发的 httpOnly cookie(HMAC-SHA256,7 天有效),同时支持 `Authorization: Bearer `,方便用 curl / 脚本写文章。 ## 环境变量 | 变量 | 默认 | 说明 | | --- | --- | --- | | `ONE_ADDR` | `:8080` | 监听地址 | | `ONE_DB_DRIVER` | `sqlite` | `sqlite` 或 `postgres` | | `ONE_DB_DSN` | `./data/one.db` | 数据库连接串(Postgres 示例:`postgres://user:pass@localhost/one?sslmode=disable`) | | `ONE_ADMIN_USER` | `admin` | 后台用户名 | | `ONE_ADMIN_PASSWORD` | `admin` | 后台密码 | | `ONE_SECRET` | 随机生成 | 会话签名密钥;不设的话重启后登录态失效 | | `ONE_SITE_URL` | `http://localhost:8080` | RSS 里的站点地址,部署时务必改成真实域名 | | `ONE_WEB_DIST` | `./frontend/dist` | 前端构建产物目录 | | `ONE_DATA_DIR` | `./data` | SQLite 数据目录 | ## 界面:classic / vivid 前台有两套完整界面,都保留,在后台 **设置 → 界面与自定义** 里整站切换(站点级设置,所有访客同款)。 后台自身的外观不受影响。 | `ui_id` | 说明 | | --- | --- | | `classic`(默认) | 原来的极简版:三栏骨架(左导航 / 正文 / 右栏)、纸感分区线、衬线标题、首字下沉 | | `vivid` | 顶部粘性导航 + 单栏卡片流、渐变配色与背景网格、悬浮卡片、首页 hero;支持站主自定义 CSS | 实现要点,改这套东西前先读: - `ui_id` 写在 `` 上,由 `frontend/src/site.js` 的 `applyTheme()` 设置。 `index.html` 里有一小段 inline script 先读 `localStorage['one.ui']` 设好,避免首帧闪一下 classic。 - vivid 的皮肤**不复用** paper/sage/rose,而是让 `data-theme` 取 `vivid` / `vivid-dark` (明暗仍由访客的 themeMode 决定)。这样两套调色板不会互相打架。 - `frontend/src/ui/vivid.css` 是**纯覆盖层**,在 `main.ts` 里排在 `styles.css` 之后。 **每个选择器都必须以 `html[data-ui="vivid"]` 开头**:scoped 样式编译后是 `.foo[data-v-HASH]` (特指度 0,2,0),而 `html[data-ui="vivid"] .foo` 是 0,2,1 才能稳定取胜;scoped 样式是懒加载 chunk, 在入口 CSS 之后才到位,漏了前缀会变成「有时生效有时不生效」。复合选择器要**整条链**镜像 (如 `.card:hover .layer-front` 要写全,不能只前缀第一段)。该文件里不允许出现 `!important`。 - vivid 把 classic 那 12 个 token 重新赋值(`--paper/--card/--ink/--accent/...`), 所以读 token 的共享组件零改动就换了皮。vivid 自己的变量以 `--v-` 开头。 - 顶部导航与首页 hero 是新增组件(`VividNav.vue` / `VividHero.vue`),由 `App.vue` 在 `ui_id === 'vivid' && !isAdmin` 时渲染;`LeftNav.vue` / `RightRail.vue` 在 vivid 下自身不渲染, 7 个公开视图没有任何改动。 ### 站主自定义 CSS 按分区分段写,存在 settings 的 `custom_css`(一行 JSON,`{分区: CSS}`)。 ``` global 全局 —— 每个前台页面都会加载,放变量和通用微调 home 首页(含标签筛选) post 文章详情(含短文) archive 归档 tags 标签云 + 单个标签下的列表 projects 作品 about 关于 ``` 分区 id 必须与 `backend/internal/store/store.go` 的 `ValidCSSSections` 和 `frontend/src/ui/sections.js` 的 `SECTIONS` 三处保持一致,否则会出现「保存了但不生效」。 - **只在 `ui_id = vivid` 时注入**,并且 `/admin` 下一律清空 —— 写坏样式影响不到后台,classic 也完全不受影响。 - 注入方式是两个 `` 也逃不出去,不需要 CSS 清洗库。 - 上限:单个分区 16 KiB,全部合计 64 KiB。超限的分区会被**静默丢弃**(写的时候就不落库)。 - 后台保存是**整对象 PUT**:请求里没带的字段会变成零值。用 curl 写的时候记得把其他字段一起带上。 可覆盖的变量(写进「全局」段即可整站生效):`--v-accent`、`--v-accent-2`、`--v-accent-3`、 `--v-bg`、`--v-surface`、`--v-surface-2`、`--v-text`、`--v-text-soft`、`--v-muted`、`--v-line`、 `--v-radius`、`--v-radius-pill`、`--v-content-width`、`--v-measure`、`--v-motion`(0 = 关掉全部动效)。 后台「界面与自定义」里内置了几个一键插入的片段(换主色 / 方正圆角 / 关渐变背景 / 首页单列), 它们只是往文本框里塞文本,不落库,插进去还能接着改。 > 注意:目前若将来给站点加 CSP,运行时的 `