Communitygithub.com

pharder56k/quote-generator-skill

室内设计报价 Skill:飞书多维表/Excel → 专业 PDF 报价单。MIT。

quote-generator-skill とは?

quote-generator-skill is a Claude Code agent skill that 室内设计报价 Skill:飞书多维表/Excel → 专业 PDF 报价单。MIT。.

対応Claude Code~Codex CLI~Cursor
npx skills add pharder56k/quote-generator-skill

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

全案设计报价系统

本 skill 将工程量清单数据渲染为专业 PDF 报价单,支持封面、总价表、明细页多页输出。

1. 何时使用本 Skill

触发条件

以下场景应使用本 skill:

  • 用户输入 /报价/quote — 生成 PDF 报价单
  • 用户提到要将多维表或 Excel 中的报价数据转为 PDF 报价单

以下场景不应使用本 skill:

  • 用户只是在编辑多维表数据(应使用 lark-base skill)
  • 用户只是在查看报价历史(不需要重新生成)

前置依赖

  • lark-base skill — 读取飞书多维表数据
  • lark-im skill — 发送 PDF 到飞书对话
  • lark-drive skill — 上传 PDF 到飞书文档
  • xlsx skill — 解析 Excel 文件(当用户提供 Excel 时)
  • Node.js 运行环境 + Playwright(已安装于项目目录)

Step 0: 初次使用检查

当用户第一次使用 Skill,或执行命令时遇到依赖相关错误,Agent 应执行以下检查流程。

1. 检查 Node.js 环境

尝试执行 node --version,要求 >= 18。如果失败或版本过低:

2. 检查 Playwright 浏览器

检查项目目录下 npx playwright install chromium 是否已完成。如果 render.js 执行时报 Executable doesn't exist 错误:

  • 在项目目录下执行: npx playwright install chromium
  • 重试渲染

3. 检查 lark-cli

尝试执行 lark-cli --version。如果失败(command not found):

  • 提示用户安装 lark-cli(飞书命令行工具)
  • 安装后执行: lark-cli auth login

4. 检查飞书登录状态

尝试执行 lark-cli base +table-list --base-token LfjJbLrTHacijesHmIjcDtSpnJf 测试登录状态。如果返回认证错误:

  • 提示用户执行: lark-cli auth login
  • 完成后重试

5. 提供飞书模板

用户需要把项目模板复制到自己的飞书空间:

Excel 数据源处理

用户提供 Excel 文件时,不直接解析渲染。引导流程:

  1. 提示:推荐先导入飞书多维表,字段和顺序更好对齐
  2. 引导用户将 Excel 导入飞书:
    • 打开飞书 → 新建多维表 → 导入 → 选择 Excel 文件
    • 或者直接复制模板多维表,将 Excel 数据粘贴进去
  3. 用户提供多维表链接后,继续 /报价 流程

2. 工作流程

用户触发 → 环境检查 → 确认数据源 → 读取数据 → 数据验证 → 确认 Logo → 确认税率 → 确认模板 → 渲染 PDF → 发送飞书

Step 1: 确认数据源

用户需提供以下之一:

  1. 飞书多维表链接 — 如 https://xxx.feishu.cn/base/XXX
  2. Excel 文件 — 本地文件路径或通过对话上传

如果用户未提供,询问用户选择数据源。

Step 2: 读取数据

飞书多维表数据源

  1. 从用户提供的链接中提取 base-token(URL 中 /base/ 后的部分)
  2. 列出表:lark-cli base +table-list --base-token <token>
  3. 找到 项目信息报价明细 两张表的 table_id
  4. 读取项目信息:lark-cli base +record-list --base-token <token> --table-id <项目信息table_id> --format json
  5. 先查视图:lark-cli base +view-list --base-token <token> --table-id <报价明细table_id>,取默认 grid 视图(如「全部项目」)的 view_id
  6. 读取报价明细(必须带 --view-id,否则返回顺序与用户在飞书界面拖拽后的顺序不一致):lark-cli base +record-list --base-token <token> --table-id <报价明细table_id> --view-id <视图ID> --format json --limit 200
  7. 从项目信息中提取:项目名称、工程编号、编制日期、编制人员、联系邮箱、公司Logo、税率、管理费
  8. 从报价明细中提取:区域、工程分类、项目名称、项目特征、单位、数量、综合单价、合价、备注(序号无需读取,渲染时自动生成

Excel 数据源

使用 xlsx skill 解析 Excel,提取相同结构的数据。Excel 可能有多 sheet,需找到包含报价明细的 sheet(通常有"序号"、"工程分类"、"综合单价"等列头)。

Step 3: 数据验证

在渲染 PDF 之前,Agent 必须对读取到的数据进行验证。不要直接跳过。

验证清单(逐项执行):

3.1 项目信息完整性

检查 项目名称工程编号编制日期 三个字段:

  • 任一为空 → 告知用户具体缺少哪个字段,请用户在飞书多维表的「项目信息」表中补充
  • 全部通过 → 继续

3.2 报价明细条目数

检查 items 数组:

  • 为 0 或不存在 → "报价明细中没有数据,请在飞书多维表的「报价明细」表中添加条目后重试"
  • 有数据 → 继续

3.3 逐条字段检查

对每条记录检查必填字段(工程分类、项目名称、单位、数量、综合单价),汇总报告:

数据验证结果:
- 总条目: 104
- 区域缺失: 3 (第 6、9、20 条)
- 工程分类缺失: 2 (第 5、18 条)
- 项目名称缺失: 0
- 单位缺失: 1 (第 12 条)
- 数量缺失: 0
- 综合单价缺失: 3 (第 5、7、22 条)
- 工程分类不在标准列表中: 0

3.4 缺失字段的处理

  • 缺失条目 ≤ 总条目的 20%:告知用户具体哪些条目有问题,询问"是否跳过这些条目继续生成?"
  • 缺失条目 > 20%:要求用户修正数据后重试,不继续渲染
  • 用户确认跳过 → 过滤掉问题条目,用有效数据继续

3.7 区域缺失检查(仅按区域分类时)

当用户选择按区域分类(Step 5.6 选择"按区域")时,额外检查每条记录的 区域 字段:

区域缺失: X (第 X 条)
  • 缺失条目 ≤ 总条目的 20%:告知用户具体哪些条目缺区域,询问"是否将这些条目归入『其他』继续生成?"
  • 缺失条目 > 20%:要求用户补充区域后重试
  • 用户确认 → 缺失区域的条目归入"其他"分组(与 Step 3.4 跳过逻辑互斥,用户二选一:跳过 or 归入其他)

3.5 合价自动修正

对每条记录检查 合价 是否等于 数量 × 综合单价

  • 偏差 < 1 元 → 不做处理
  • 偏差 ≥ 1 元或为空 → 自动计算 合价 = 数量 × 综合单价,值写入渲染数据
  • 修正条目超过 10% → 提示用户"已自动修正 X 条合价数据",建议用户检查多维表公式

3.6 工程分类标准列表

以下为 13 类标准分类:

措施项目、拆除工程、砌筑工程、混凝土及钢筋混凝土工程、
金属结构工程、防水工程、保温隔热工程、楼地面装饰工程、
墙柱面装饰与隔断工程、天棚工程、油漆涂料工程、
其他装饰工程、安装工程

不在列表中的分类仍可渲染,但告知用户"分类 'XXX' 不在标准列表中"。

Step 4: Logo 处理

必须执行,不可跳过。 无论数据源是多维表还是 Excel,都必须检查并处理 Logo。

  1. 从多维表读取:检查项目信息表中的 公司Logo 附件字段
    • 如有附件,使用 media API 下载(drive +download 不支持多维表附件):
      lark-cli api GET /open-apis/drive/v1/medias/{file_token}/download --output ./logo.png
      
    • 将图片转为 base64 data URI,传入渲染数据的 logo_url 字段
  2. 从 Excel 读取:检查 Excel 中是否有 logo 图片(通常嵌入在 sheet 中或作为附件)
    • 如有,提取并转为 data URI
  3. Logo 缺失时(必须询问用户)
    • 如果多维表或 Excel 中没有找到 Logo 图片,必须向用户询问
      • "未在数据源中找到公司 Logo,请问如何处理?"
      • 选项 A:上传 Logo 图片
      • 选项 B:提供 Logo 图片 URL
      • 选项 C:不使用 Logo(PDF 中显示默认 "R M" 文字标识)
    • 不要默认跳过这一步,即使用户说"直接生成"也要确认 Logo 处理方式

Step 5: 确认税率与管理费

优先使用多维表项目信息中的 税率管理费 字段值。如果多维表中没有该字段,则询问用户。

费率规则(税率与管理费通用):

  • 值 > 1 → 视为百分比整数,自动 ÷100(如 3 → 3%、10 → 10%)
  • 值 ≤ 1 → 视为小数直接使用(如 0.03 → 3%、0.1 → 10%)
  • 空值:税率默认 3%,管理费默认 0(不收取)

计算顺序:

合计 = Σ 各条目合价
管理费 = 合计 × 管理费率
增值税 = (合计 + 管理费) × 税率
总计 = 合计 + 管理费 + 增值税

管理费 > 0 时总价表显示「管理费」行;管理费 = 0 时不显示(兼容旧数据)。

Step 5.5: 确认模板风格

必须询问,不可跳过。 即使之前使用过某个模板,也要每次都确认。

询问用户选择模板:

请选择报价单模板风格:
1. Swiss IKB(默认)— 蓝底满版封面 + 双语分类标题
2. Swiss IKB Zebra — 同上 + 内容明细行斑马纹(白/浅蓝交替)
3. B&W — 白底封面 + 浅灰强调,适合黑白打印
4. B&W Zebra — 同上 + 内容明细行斑马纹(白/浅灰交替)

如果用户回复中包含明确的模板名称或编号,直接使用对应模板:

  • swiss-ikb — Swiss IKB(默认)
  • swiss-ikb-zebra — Swiss IKB Zebra
  • bw — B&W 黑白打印版
  • bw-zebra — B&W Zebra 黑白打印斑马纹版

开发中(暂不对外提供): Editorial / Editorial B&W / Card / Card B&W 四个模板代码已存在,待用户后续修改完善后启用。如用户主动要求使用这些模板,可执行 node scripts/render.js --template editorial 等命令,但优先推荐上述 4 个已上线模板。

Step 5.6: 确认分类方式

必须询问,不可跳过。 每次渲染前都要确认,不要默认使用上次的选项。

询问用户选择分类方式:

请选择报价单分类方式:
1. 按区域(玄关、客厅、主卧……)— 总价表按区域汇总,明细表在序号后增加工程分类列
2. 按工程分类(措施项目、拆除工程……)— 传统方式,明细表不含工程分类列

用户回复明确编号或名称后使用对应模式:

  • area — 按区域分类,渲染命令加 --group-by area
  • category — 按工程分类(默认),不传或加 --group-by category

注意:选择"按区域"时,需确保报价明细表的 区域 字段已填写(Step 3.7 会检查)。若数据尚未填写区域,建议优先使用"按工程分类"。

Step 5.7: 区域英文翻译(仅按区域分类时)

区域名每个项目不同,每次渲染前必须动态翻译,禁止套用写死的映射

  1. 从报价明细中收集本次出现的全部区域名(去重)
  2. Agent 将区域名逐条翻译为英文(设计行业惯用翻译,如 玄关→Foyer、客厅→Living Room、阳光房→Sunroom)
  3. 将翻译结果作为 region_names 对象写入渲染数据 JSON:
{
  "region_names": {
    "玄关": "Foyer",
    "阳光房": "Sunroom"
  }
}
  1. 渲染时 render.js 优先使用 region_names 中的翻译(未提供的区域名会回退到内置常见区域映射,再兜底显示 AREA)
  2. 渲染完成后在汇报中列出本次区域翻译对照表,用户可纠正,下次报价重新翻译

Step 6: 渲染 PDF

在项目目录下执行:

node scripts/render.js --input <data.json> --template <模板名> --vat-rate <税率> --group-by <area|category> --output ./output/<项目名称>_<工程编号>.pdf
  • --group-by area — 按区域分类(大分类=区域,明细含工程分类列)
  • --group-by category 或不传 — 按工程分类(原有行为)

渲染数据 JSON 格式:

{
  "项目名称": "xxx",
  "工程编号": "xxx",
  "编制日期": "xxx",
  "编制人员": "xxx",
  "联系邮箱": "xxx",
  "logo_url": "data:image/png;base64,...",
  "税率": 0.08,
  "管理费": 0.1,
  "items": [
    {
      "区域": "玄关",
      "工程分类": "措施项目",
      "项目名称": "脚手架",
      "项目特征": "室内脚手架",
      "单位": "项",
      "数量": 1,
      "综合单价": 3200,
      "合价": 3200
    }
  ]
}

序号自动生成规则序号 字段无需在数据中提供(飞书多维表可不建该列)。渲染时按 分组序号.组内序号 自动生成:按区域分类时 01 区域 → 1.1、1.2、…,02 区域 → 2.1、2.2、…;按工程分类时 措施项目 → 1.x、拆除工程 → 2.x…安装工程 → 13.x。组内顺序 = 飞书记录顺序(多维表中可拖拽记录排序)。

税率与管理费(详见 Step 5):

  • 税率 / 管理费 值 > 1 视为百分比整数(3 → 3%),≤ 1 视为小数(0.08 → 8%)
  • 管理费 = 合计 × 管理费率;增值税 = (合计 + 管理费) × 税率;总计 = 合计 + 管理费 + 增值税
  • 管理费未提供或为 0 → 不收取,总价表不显示管理费行
  • 若未提供 --vat-rate 参数,render.js 优先读取数据中的 税率 字段

Step 7: 保存与发送

PDF 渲染完成后,必须询问用户选择保存方式:

PDF 已生成:<项目名称>_<工程编号>_<模板名>.pdf(XX 条,¥XXX 含税)

请问如何保存?
1. 发送到当前聊天窗口
2. 保存到飞书云文档,推送文档链接
3. 两者都要

根据用户选择执行:

选项 1 — 发送到聊天窗口

lark-cli im +messages-send --type media --file <pdf路径>

选项 2 — 保存到飞书云文档

lark-cli drive +upload --file <pdf路径> --title "<项目名称>_<工程编号>"

获取上传后的文件链接,回复用户:

已保存到飞书云文档:<项目名称>_<工程编号>_<模板名>.pdf
文档链接: https://xxx.feishu.cn/drive/xxx

选项 3 — 两者都要

先执行选项 2(上传),再执行选项 1(发送文件 + 链接)。

清理临时文件

发送/保存完成后,删除渲染过程中产生的临时 JSON 文件和 Logo 下载文件(如有)。

3. 项目结构

quote-generator-skill/
├── package.json                # 项目配置
├── setup.sh                     # 一键安装脚本
├── SKILL.md                     # Skill 定义
├── scripts/
│   ├── render.js               # HTML → PDF 渲染引擎
│   ├── demo.js                 # 开箱即用演示
│   └── generate-large-test.js  # 大型测试数据生成器
├── references/
│   ├── templates/
│   │   ├── content.html        # Handlebars PDF 模板(内容页)
│   │   ├── cover-screen.html   # Swiss IKB 封面独立模板
│   │   ├── cover-bw.html       # B&W 黑白打印封面模板
│   │   ├── swiss-ikb.json      # Swiss IKB 配置
│   │   ├── swiss-ikb-zebra.json # Swiss IKB Zebra 配置
│   │   ├── bw.json             # B&W 黑白打印配置
│   │   └── bw-zebra.json       # B&W Zebra 配置
│   ├── helpers.js              # Handlebars 自定义 helper
│   └── bitable-config.json     # 多维表字段配置
├── docs/plans/                 # 设计文档
├── output/                     # 生成的 PDF 输出目录
└── README.md

4. 模板说明

PDF 模板包含三种页面:

  1. 封面 — 工程名称、编号、日期、编制人员、logo(右上角)
  2. 总价表 — 按工程分类汇总金额 + 合计/增值税/总计
  3. 明细页 — 每页约 8 行数据,含分类标题、明细行、小计行、页码

5. 注意事项

  • 多维表中的 合价 是公式字段(=数量×综合单价),读取时已计算好
  • 序号 无需在飞书中维护:渲染时自动生成(分组序号.组内序号,组内按记录顺序)。若要调整明细顺序,在飞书多维表中拖拽记录排序即可
  • 工程分类 是单选字段,13 个选项对应 13 类工程
  • 区域 是自由文本字段(建议在飞书中改为单选:玄关、客厅、餐厅、厨房、主卧、次卧、卫生间、阳台、书房、衣帽间、走廊、全屋等),仅按区域分类时使用,可为空
  • 区域模式的分组标题显示中英对照(如 Living Room 客厅)。区域名每个项目不同,Agent 每次渲染前按 Step 5.7 动态翻译并写入 region_names;内置常见区域映射仅作兜底,未命中时显示 AREA
  • 单位 是单选字段,支持自定义扩展
  • Logo 支持图片(推荐)和文字两种形式
  • 增值税税率每次由用户指定,不固定
  • 输出 PDF 为 A4 尺寸,适合打印

6. 常见问题处理

错误现象原因Agent 处理方式
command not found: lark-clilark-cli 未安装提示安装 lark-cli,参考 Step 0.3
lark-cli 返回认证错误未登录或 token 过期提示执行 lark-cli auth login
Executable doesn't existPlaywright Chromium 未安装执行 npx playwright install chromium
渲染 PDF 为空或格式混乱数据 JSON 格式不正确检查 项目名称工程编号 非空,items 至少 1 条
明细序号与手动填的不一样序号已改为自动生成正常现象:序号按分组自动重编(如 01 全屋 → 1.1、1.2…)。调整顺序请在飞书拖拽记录
字号/字体异常极少发生(字体已内置为 WOFF2,不依赖外部 CDN)检查项目 references/fonts/ 目录下字体文件是否完整
Logo 下载失败多维表附件 API 权限问题确认用户已授权,或选择"不使用 Logo"
base-token 无法从 URL 提取用户提供的不是多维表链接提示用户提供飞书多维表链接(URL 中应包含 /base/

関連スキル