project/quotation-saas
HS Design 多租�?SaaS 报价工具 �?完整技术文�?> 生成时间�?026-05-06 用于 OpenClaw agent 阅读并继续开�?
关键路径
PC 本地文件
| 用�? | 路径 |
|---|---|
| Worker 代码 | D:\hermes\quotation-worker\ |
| *Landing Page(含报价前端�? | D:\Landing-Page\ |
| OpenClaw Desktop | C:\Users\sozo\.openclaw\ |
| *OpenClaw Data(memory DB�? | D:\OpenClaw_Home\.openclaw\memory\ |
| Obsidian Vault | H:\My Drive\Jakephone\Obsidian Vault\ |
Worker 部署 URL
| URL | 说明 |
|---|---|
https://quotation-saas.ida-czia.workers.dev | Worker 默认域名 |
https://quotation.hsdesign.biz | **主域�?*(自定义域) |
https://qs.hsdesign.biz | 备域�? |
GitHub
| 用�? | Repo |
|---|---|
| Landing Page(含报价页) | JakeBilu/Landing-Page |
| 装修知识库(Dify�? | JakeBilu/renovation-knowledge-base |
Cloudflare 资源 IDs
| 资源 | ID |
|---|---|
| Account ID | f6b7326e471bbe3d1b0a0e2ba770f47d |
| D1 Database UUID | b1381cb9-de0b-4f2b-8122-59a71d05d0ca |
| D1 API Endpoint | https://api.cloudflare.com/client/v4/accounts/f6b7326e471bbe3d1b0a0e2ba770f47d/d1/database/b1381cb9-de0b-4f2b-8122-59a71d05d0ca/query |
| Zone ID | 67e7fef748dbfdefb96a4fc5d682d2bc |
| Zone | hsdesign.biz |
| Worker Route ID | 814a4476d0cc401bb221f7e44855135e |
| 全局 API Key | cfk_r8ECCUq8K0nZjyuJGkbWEelbYX4r3NxyBnuqK5zj38f26a11 |
| Dify App ID | app-jfL3s6bk9ekJ1vK8K22dMsf3 |
| Resend API Key | re_Qr5yUbT5_72JBvNPPskYK5qvEByNHJ6mS |
Worker API Endpoints
Worker:
quotation-worker.js(Service Worker 格式,非 ES Module�?> 注意:当�?Worker 使用addEventListener全局格式,D1 绑定需�?ES Module 格式才能正常工作。当�?D1 通过 REST API 直接调用�?
已实�?Endpoints
| Method | Path | 说明 |
|---|---|---|
| GET | /api/health | 健康检�? |
| POST | /auth/register | 邮箱注册 |
| GET | /api/debug/db | DEBUG:列出所�?D1 �? |
| POST | /api/debug/register-check | DEBUG:检查邮箱是否已注册 |
请求/响应格式
POST /auth/register
// Request
{ "email": "[email protected]", "password": "xxxxxx", "name": "User Name" }
// Response 201
{ "success": true, "id": "uuid" }
// Response 409
{ "error": "Email already registered" }GET /api/debug/db(D1 Schema 查看�?```json // Response { “success”: true, “tables”: [{ “name”: “accounts”, “sql”: “CREATE TABLE…” }] }
---
## D1 数据�?Schema
### 数据库名�?`quotation-saas`(D1 Database UUID: `b1381cb9-de0b-4f2b-8122-59a71d05d0ca`�?
### Tables
#### accounts
```sql
CREATE TABLE accounts (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
password_hash TEXT,
name TEXT,
company TEXT DEFAULT '',
logo_url TEXT DEFAULT '',
color_hex TEXT DEFAULT '#1A73E8',
login_method TEXT DEFAULT 'google',
email_verified INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
plan TEXT DEFAULT 'free',
referral_code TEXT UNIQUE,
referred_by TEXT,
referral_adfree_months INTEGER DEFAULT 0,
session_token TEXT
)
quotations
CREATE TABLE quotations (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
qno TEXT DEFAULT '',
name TEXT DEFAULT '',
proj TEXT DEFAULT '',
addr TEXT DEFAULT '',
date TEXT DEFAULT '',
status TEXT DEFAULT 'Draft',
data TEXT DEFAULT '[]',
total REAL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (account_id) REFERENCES accounts(id)
)subscriptions
CREATE TABLE subscriptions (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
plan TEXT DEFAULT 'free',
status TEXT DEFAULT 'active',
started_at TEXT NOT NULL,
ends_at TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (account_id) REFERENCES accounts(id)
)referral_credits
CREATE TABLE referral_credits (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
amount INTEGER DEFAULT 0,
used INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
FOREIGN KEY (account_id) REFERENCES accounts(id)
)sessions
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
token_hash TEXT NOT NULL,
expires_at TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY (account_id) REFERENCES accounts(id)
)_cf_KV(系统表�?```sql
CREATE TABLE _cf_KV ( key TEXT PRIMARY KEY, value BLOB ) WITHOUT ROWID
---
## 前端文件结构
### D:\Landing-Page\
| 文件 | 大小 | 说明 |
|------|------|------|
| `HS_Design_Quotation.html` | 21KB | 报价工具�?HTML(独�?App 框架�?|
| `quote-app.js` | 37KB | 报价工具完整逻辑 |
| `HS_Design_Projects.html` | 13KB | 项目管理页面 |
| `project-detail.html` | 19KB | 项目详情页面 |
| `staff-portal.html` | 2KB | Staff Portal |
| `index.html` | 38KB | Landing Page 主页�?|
| `wrangler.toml` | 113B | Pages 项目配置 |
### 合并报价 App
- HTML + JS 内联合并后约 62KB
- API 指向:`https://quotation.hsdesign.biz`
- 功能:侧边栏报价列表、Section 分组(木�?水电/油漆)、Item 编辑、Markup 计算、PDF 导出、JSON 导入导出、自动保存草�?
---
## 订阅方案(2026-06-01 重构)
| 方案 | 价格 | 内容 |
|------|------|------|
| Free | RM0/mo | **10** 份报价/月 + 有广告 |
| Plus | **RM4**/mo | **50** 份 + 无广告 + 无watermark |
| Pro | **RM7**/mo | **100** 份/月 + 无广告 + Priority 支持 |
| Pro Max | **RM10**/mo | 无限 + 无广告 + **AI 装修助手** + Priority + Custom branding |
| Referral | - | 推荐 1 人得 1 个月 Plus/Pro |
> 旧方案:Free/Plus/Pro/Pro Max = RM0/2/3/5(2026-05-19 起)
> 当前方案:RM0/4/7/10(2026-06-01 reasonix 重构)
> 战略:free 缩到 10 份逼付费,Pro Max 加 AI 装修助手差异化
---
## 已完成功能 (2026-05-17)
### 用量限制 ✅
- `/api/account/usage` 每次从DB重新读取plan(避免JWT缓存问题)
- `/api/quotations` POST 前检查 quota,超量返回 403 `{"error":"Quota exceeded","limit":100,"count":N}`
- 已验证:101/102 请求正确被拦截
### Stripe 支付框架 ✅(待配置 key)
- `POST /api/subscribe` → 创建 Stripe Checkout Session(未配置key时返回503错误提示)
- `GET /api/subscribe` → 返回定价页面 HTML(无需登录)
- `POST /api/webhook/stripe` → 处理 `checkout.session.completed`、`customer.subscription.deleted`、`invoice.payment_failed`
- 订阅成功后自动更新 `accounts.plan` 和 `subscriptions` 表
- 价格映射:`adfree` → STRIPE_PRICE_ADFREE, `200` → STRIPE_PRICE_200, `unlimited` → STRIPE_PRICE_UNLIMITED
### 定价页 ✅
- `GET /pricing` → 完整定价页(Free/Ad-Free RM5/Professional RM15)
- 登录用户显示当前方案标签和升级按钮,未登录用户显示注册CTA
- FAQ + 14天退款保证 + 支付方式说明
### Referral 系统升级 ✅
- 被推荐人注册时,推荐人自动升级为 Ad-Free(原本只是记录)
- `subscriptions` 表新增 `referred_by` 字段
---
## 待办(Priority)
|| 优先级 | 事项 |
|--------|------|
| 🔴 | **配置 Stripe Keys**(dashboard.stripe.com → Webhooks → LIVE keys) |
| 🟡 | **AdSense 集成**(ca-pub 申请中) |
| 🟡 | Referral 系统完整追踪(referral_credits 表未使用) |
| 🟡 | Dify 装修知识库导入完成 markdown |
| 🟢 | AI 装修助手 Phase 2(Dify knowledge base) |
---
## 当前 Worker 备份
- 本地备份: `C:\Users\IDA\quotation-worker-backup-20260517.js` (531行)
- 部署方式: REST API PUT `/accounts/{id}/workers/scripts/quotation-saas`
---
## Bug Fix — 2026-05-13
### 2026-05-05:Google OAuth sub 字段错误
- **Bug**:`GET /api/account` 返回 500,Worker �?`payload.sub`(undefined)查 account,正确字段是 `account.sub`
- **修复**:改�?`account.sub` + �?`re.findall` 精确提取 JWT payload
- **状�?*:✅ 已部�?
### 2026-05-04:Google OAuth 登录死循�?- **Bug**:Worker callback 返回 HTML + `localStorage.setItem('token', jwt)`,但前端 `getToken()` 读取 `localStorage.getItem('qs_token')` �?key 不匹�?- **修复**:改�?`localStorage.setItem('qs_token', jwt)` 并在 redirect URL �?`?token=XXX`
- **状�?*:✅ 已部�?
### 2026-04�?�?Worker bug
1. `/ads.js` 500:Worker server-side 使用 DOM API (`document.createElement`)
2. `/pricing` 500:`escHtml()` 定义�?route handler 外部,V8 sandbox 无法访问
3. `/api/account` 500:JWT token 解析后没有正确返�?account 数据
4. 注册后无 token
### 2026-05-04:列�?SQL column 名错�?- `project` column �?D1 里实际叫 `proj`
- **状�?*:✅ 已修�?
### 2026-05-03:Google OAuth redirect_uri_mismatch
- 代码 redirect URI:`https://quotation.hsdesign.biz/api/auth/callback`
- Google Cloud Console 只加�?`/callback`,缺少完整路�?- **状�?*:✅ 已修�?
---
## 待办(Priority�?
| 优先�?| 事项 |
|--------|------|
| 🔴 | **用量限制强制执行**(Free 100份超量拦截)�?商业模式核心 |
| 🔴 | **订阅支付流程**(Stripe/Touchngo)|
| 🟡 | **完整报价页嵌�?Worker**(替换简化版 /dashboard)|
| 🟡 | Referral 系统(referral code + 赠送无广告)|
| 🟡 | AdSense 集成(ca-pub 申请中)|
| 🟡 | Dify 装修知识库导入完�?markdown |
| 🟢 | AI 装修助手 Phase 2(Dify knowledge base)|
---
## 部署方法
### Worker 部署(不能用 wrangler�?wrangler deploy 有问题,改用 Cloudflare Workers REST API�?
```bash
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts/{script_name}/subdomain/{subdomain}" \
-H "Authorization: Bearer {API_KEY}" \
-H "Content-Type: application/javascript" \
--data-binary @dist/worker.js
实际已用的部署脚本在:D:\hermes\quotation-worker\(见 deploy_log.txt�?
自定义域名路�?- 问题:Cloudflare Pages 劫持所�?*.hsdesign.biz 子域�?- 解决:Zone-level Routes API
- DNS:
quotation.hsdesign.biz�?192.0.2.1(proxied) - Route:
quotation.hsdesign.biz/*�?quotation-saasworker
技术栈
| 用�? | 技�? |
|---|---|
| 数据�? | Cloudflare D1 |
| 后端 | Cloudflare Workers(Service Worker 格式�? |
| 前端 | Vanilla JS + HTML(独�?App,嵌�?Landing Page�? |
| 认证 | Google OAuth + Email/密码 |
| 邮件 | Resend(免�?100/天) |
| 广告 | Google AdSense |
| AI 助手 | Dify(装修知识库 Chatbot) |
已知问题 / 技术债务
- Worker 格式问题:当�?Worker �?
addEventListener全局格式(非 ES Module),D1 绑定无法通过env.DB访问,需要通过 REST API 绕过 - wrangler.toml 配置错误:
wrangler.toml中有custom_domains字段导致 WARNING - **用量限制未强制执�?:Free 方案 100份上限未实装检�?4. **注册流程不完�?:注册成功后需要返�?JWT token(目前只�?
id�?5. **AdSense 还在申请�?*:ca-pub 未完成申�?
OpenClaw Agent 使用指南
读取 D1 数据�?```javascript
// 通过 Cloudflare REST API(Worker 当前方式�?const res = await fetch(
https://api.cloudflare.com/client/v4/accounts/f6b7326e471bbe3d1b0a0e2ba770f47d/d1/database/b1381cb9-de0b-4f2b-8122-59a71d05d0ca/query,
{
method: ‘POST’,
headers: {
‘X-Auth-Email’: ‘[email protected]’,
‘X-Auth-Key’: env.CF_API_KEY, // 或用全局 API Key
‘Content-Type’: ‘application/json’,
},
body: JSON.stringify({ sql: ‘SELECT * FROM accounts’, params: [] }),
}
);
### 部署 Worker(正确方法)
由于 wrangler 有问题,通过 REST API 上传 Worker JS 文件�?
### 修改前端
直接编辑 `D:\Landing-Page\HS_Design_Quotation.html` �?`quote-app.js`,然后推送到 GitHub `JakeBilu/Landing-Page`�?
---
## 相关 Obsidian 文件
- `projects/hsdesign-landing.md` �?HS Design Landing Page 项目文档
- `projects/ai-renovation-assistant.md` �?AI 装修助手项目文档
- `env/` �?环境变量和凭证存�?
## Bug Fix — 2026-05-13
**Title:** quotation-worker /auth/google 404 + register tab fix (2026-05-13)
**Skill:** [[devops_quotation-saas-debug]]
**Bug 1:** /auth/google returns 404
Root cause: Worker deployed version was stale, missing /auth/google handler
Fix: Re-deploy via REST API: PUT /accounts/{id}/workers/scripts/quotation-saas
**Bug 2:** All pages 404 due to wrong PAGES_URL
Root cause: PAGES_URL = 'https://hsdesign-7ni.pages.dev' (Staff Portal) should be 'https://quotation-saas-frontend.pages.dev' (SaaS frontend)
Fix: Update quotation-worker.js PAGES_URL and redeploy
**Bug 3:** /register shows Login tab instead of Register tab
Root cause: SPA init IIFE calls showAuth() without checking pathname, always shows login tab first
Fix: In Worker, patch the SPA's init IIFE for /register route to add conditional switchTab before showAuth:
if (url.pathname === '/register') { switchTab('register'); }
showAuth();
<!-- BUG FIXES DO NOT EDIT BELOW -->
### 2026-05-19: quotation-saas: /auth/register auth flow fix
【修复】Root redirect: `/` → `/auth/register`(非 /app)
【修复】setToken now stores BOTH `qs_token` AND `token` in localStorage
【修复】/app serves registration page when no token
【验证】新用户注册 → 填写表单 → 点击 Create Account → 直接进 Dashboard
【发现】注册后 Dashboard 显示 "Loading..." 不消失 → `loadQuotes()` 是闭包函数,未绑定到 `window`,调用时找不到 → fetch 失败被吞掉
### 2026-05-19: quotation-saas: Stripe Webhook 配置完成
| 配置 | 值 |
|------|-----|
| Webhook ID | `we_1TY5pT06BaZD1u5Pk2jQZsSs` |
| Endpoint | `https://quotation.hsdesign.biz/api/webhook/stripe` |
| Events | `checkout.session.completed` + `customer.subscription.deleted` |
| Signing Secret | `whsec_Dyw7ie99v14yiXZzvlqrzfrKoL5W714L` |
Worker v7 已部署(135,125 bytes),付款闭环完成:
用户订阅 → Stripe Checkout → 付款成功 → Webhook → 自动升级方案 ✅
### 2026-05-19: quotation-saas: Worker 部署架构确认
- `quotation.hsdesign.biz` 绑定的是 `quotation-saas` script(不是 `quotation-worker-3rxr`)
- 之前所有更新都部署到错误的地方!
- 正确的部署路径:`D:\hermes\quotation-worker\` → REST API → `quotation-saas` worker
### 2026-05-19: Dashboard 完整版 (/dashboard)
- 全新页面:时间问候语、方案徽章、4个统计卡、推荐码卡片
- `/api/referrals` — 推荐统计(人数 + 推荐人报价总数)
- `/api/email` — 发送报价邮件(Resend API)
- Commit: `565f8cd` (index.html v2)
### 2026-05-19: Live API Keys(用户确认)
- Resend API: `re_aTz3KBN1_Axs3g6AzQ6usrRouYQnD39qF`
- Stripe Live Publishable: `pk_live_51TY4cV17t9NNR8bppsjfIffp1W1orauplZT0o2KKvpoc1vlI152DJ2O4fTw3CRmlU1ZLox84c2kijEdNiLdRAumI00FXBSobcn`
### 2026-05-19: 调试方法改进(重要)
- **不要告诉用户去研究 localStorage/DevTools** — 用户不是研究者
- **正确方式**:auth flow 本身就要正确工作,无 token → 显示登录页 → 注册 → 自动进 dashboard
- Bug 复现:清除浏览器数据(无痕),从 `/` 开始测试完整流程
### 2026-05-20: dream-consolidation.py session 格式不匹配修复
- **问题**:dream-consolidation.py 只 glob `*.jsonl`,但 Hermes 当前 session 存储为 `session_*.json` 格式(嵌套 JSON with `messages` array)
- **结果**:找不到当天 session,buffer 永远为空,导致 vault-keeper 3h sync 每次都报 "nothing to sync"
- **修复**:
1. `get_today_session_file()` — 同时查找 `session_*.json` 和 `*.jsonl`,优先当前 .json 格式
2. `load_messages()` — 通过检测首行 JSON 对象是否含 `role` 字段判断格式
- **验证**:修复后成功读取 8 条消息,提取 3 个 knowledge entry 写入 buffer
- **涉及文件**:`D:/scripts/vault-keeper/dream-consolidation.py`
### 2026-05-20: Google OAuth callback 空白页 bug
- **问题**:用户点击 "Continue with Google" 后,callback 返回 HTML 页面(meta refresh),导致 Cloudflare 的 bot 检测页面(显示"Checking your browser..."的空白页)
- **根因**:`/auth/callback` 返回自包含 HTML 页面进行 redirect,而不是 HTTP 302 重定向
- **修复**:改为 HTTP 302 redirect 到 `/auth/register?google_success=1`,设置 HttpOnly cookie
- **涉及修改**:
- 删除 HTML meta refresh body
- 改用 `Response.redirect(url, 302)` 配合 `Location` 和 `Set-Cookie` headers