{“content”:”---\nname: quotation-saas-project\ndescription: HS Design Quotation SaaS — multi-tenant Cloudflare Worker + D1 platform for Malaysian interior designers\n---\n\n# Quotation SaaS — HS Design\n\n## 当前状态 (2026-05-04)\n\n### 迁移进度:Notion API → SaaS API ✅\n- quote-app.js: 已修改使用 SaaS API endpoints,Bearer token 认证\n - 本地: /data/data/com.termux/files/home/quote-app-saas.js\n - PC landing repo: D:\\OpenClaw_Home\\.openclaw\\workspace\\projects\\hs-design-landing\\quote-app.js\n - 部署: https://hsdesign-7ni.pages.dev/quote-app.js?v=20260425\n- Worker /dashboard: 直接内嵌干净 HTML,加载 SaaS 版 JS(不再 fetch Pages)\n- Worker /login: SaaS 登录页(Google OAuth + 邮箱/密码)\n\n### 部署路径\n- Worker: quotation.hsdesign.biz (Cloudflare Workers)\n - 部署方式: REST API PUT(wrangler 不可用)\n - 部署命令: curl -X PUT .../workers/scripts/quotation-saas\n- Pages: hsdesign-7ni.pages.dev (JakeBilu/Landing-Page branch main)\n - HTML: HS_Design_Quotation (原始)\n - JS: quote-app.js (SaaS版)\n\n### 遗留 BUG(进行中)\n- loadQuotes() await r.json() 在 try 外 → 已修复本地,需验证\n- saveQuote 重复 data 声明 → 已修复,需验证\n- Playwright 测试超时(browser launch hangs)\n线上地址: https://quotation.hsdesign.biz\n\n- Worker: quotation-saas @ Cloudflare Workers\n- D1 DB: b1381cb9-de0b-4f2b-8122-59a71d05d0ca\n- 自定义域名: quotation.hsdesign.biz(Dashboard → Custom Domains 添加)\n- 全套报价编辑器 Dashboard 已上线\n\n## 架构\n\n- 单 Worker 架构:HTML 页面直接嵌入 Worker JS(用 template literal),无需 Cloudflare Pages\n- 部署方式:REST API PUT /accounts/{id}/workers/scripts/{script}(比 wrangler 快)\n- 认证:Bearer token(base64 JSON {id, email}),存 localStorage\n- 多租户:D1 + account_id 外键隔离\n- 数据库:D1(通过 Cloudflare REST API 查询,非 binding)\n- 订阅:Free(100份) / RM2(无广告) / RM3(200份) / RM5(无限+beta)\n\n## 自定义域名\n\n问题:Zone Routes API 对子域名添加返回 405(Pages 接管了 *.hsdesign.biz)\n\n解法:Dashboard UI → Workers & Pages → 项目 → Custom Domains → Add custom domain\n- Dashboard 的 Add custom domain 会自动创建 CNAME 并验证 DNS\n- 不需要手动调用 API\n\n## Worker 上传(REST API)\n\nbash\ncurl -s -X PUT \"https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/quotation-saas\" \\\n -H \"X-Auth-Email: ida.czia@gmail.com\" \\\n -H \"X-Auth-Key: ${CF_KEY}\" \\\n -H \"Content-Type: application/javascript\" \\\n --data-binary @worker.js\n\n\n## D1 数据库 Schema\n\n\naccounts: id, email, password_hash, name, company, logo_url, color_hex,\n login_method, email_verified, created_at, plan,\n referral_code, referred_by, referral_adfree_months\n\nquotations: id, account_id, qno, name, proj, addr, date, status,\n data (JSON数组), total, created_at, updated_at\n\nsubscriptions: id, account_id, plan, status, started_at, ends_at, created_at\nreferral_credits: id, account_id, amount, used, created_at\nsessions: id, account_id, token_hash, expires_at, created_at\n\n\n## API 端点\n\n| 方法 | 路径 | 说明 |\n|------|------|------|\n| GET | /api/health | 健康检查 |\n| GET | /api/account | 当前用户信息 |\n| PATCH | /api/account | 更新个人资料 |\n| GET | /api/quotations | 列出当前用户所有报价 |\n| POST | /api/quotations | 新建报价 |\n| GET | /api/quotations/:id | 获取单条报价 |\n| PUT | /api/quotations/:id | 更新报价 |\n| DELETE | /api/quotations/:id | 删除报价 |\n| POST | /auth/register | 邮箱注册 |\n| POST | /auth/login | 邮箱登录 |\n\n## Auth 认证流程\n\njavascript\n// 登录成功后返回 token(base64 JSON)\nconst token = btoa(JSON.stringify({ id: account.id, email: account.email }));\n\n// 后续请求带上 Bearer token\nfetch('/api/quotations', {\n headers: { 'Authorization': 'Bearer ' + token }\n});\n\n// Worker 端解析\nfunction getAuthAccount(request) {\n const auth = request.headers.get('Authorization');\n if (!auth) return null;\n try {\n return JSON.parse(atob(auth.slice(7))); // { id, email }\n } catch { return null; }\n}\n\n\n注意:当前 debug 模式:邮箱注册的密码 hash = email 本身(登录时填邮箱地址作为密码)\n\n## Dashboard HTML 嵌入 Worker 的方法\n\n1. 读取 HTML 文件\n2. 用 Python 把 HTML 里的 backtick 和 ${ 转义\n3. 嵌入 Worker JS 的 template literal 里\n4. 上传 Worker\n\npython\nwith open('dashboard.html', 'r', encoding='utf-8') as f:\n html = f.read()\nescaped = html.replace('\\\\', '\\\\\\\\').replace('`', '\\\\`').replace('${', '\\\\${')\n# 然后嵌入 JS: const html = `{escaped}`;\n\n\n## PDF 导出(浏览器打印)\n\nDashboard 里用 window.open() + document.write() 生成打印页面,然后 print():\n\njavascript\nfunction exportPdf() {\n const w = window.open('', '_blank');\n w.document.write(buildPdfHtml());\n w.document.close();\n setTimeout(() => w.print(), 500);\n}\n\n\nbuildPdfHtml() 返回完整 HTML(含 CSS),浏览器打印为 PDF。\n\n## 文件结构\n\n\nquotation-saas/\n worker_full.js # 完整 Worker(含嵌入的 dashboard.html)\n dashboard.html # 独立开发时用的源文件\n\n\n## 已知问题\n\n- 密码 debug 模式:注册时密码 hash = email,登录时填邮箱作密码\n- AdSense:未集成\n- 订阅支付:未集成\n\n## 下一步\n\n1. 修复密码 hash(生产环境要用 bcrypt)\n2. AdSense 集成\n3. 订阅支付(RM2/RM3/RM5)\n4. 用量限制(Free 100份)\n5. Project 栏目(施工图/资料上传)\n6. AI 装修问答助手\n”}