@microi.net/cli 4.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/mcp-codex-stdio-adapter.js +189 -0
  4. package/dist/mcp-server.js +972 -0
  5. package/dist/mcp-trae-windows-launcher.cmd +21 -0
  6. package/dist/microi-cli-mcp.js +7 -0
  7. package/dist/microi-cli.js +1645 -0
  8. package/dist/microi-skills.meta.json +335 -0
  9. package/dist/microi.skills/.microi-skills-version.json +6 -0
  10. package/dist/microi.skills/README.md +276 -0
  11. package/dist/microi.skills/ai-engine/SKILL.md +140 -0
  12. package/dist/microi.skills/ai-engine/agents/openai.yaml +4 -0
  13. package/dist/microi.skills/app-store/SKILL.md +105 -0
  14. package/dist/microi.skills/app-store/agents/openai.yaml +4 -0
  15. package/dist/microi.skills/business-blueprint/SKILL.md +184 -0
  16. package/dist/microi.skills/datasource-engine/SKILL.md +89 -0
  17. package/dist/microi.skills/datasource-engine/agents/openai.yaml +4 -0
  18. package/dist/microi.skills/dos-orm/SKILL.md +76 -0
  19. package/dist/microi.skills/dos-orm/references/api-reference.md +229 -0
  20. package/dist/microi.skills/job-engine/SKILL.md +141 -0
  21. package/dist/microi.skills/job-engine/agents/openai.yaml +4 -0
  22. package/dist/microi.skills/message-notification/SKILL.md +113 -0
  23. package/dist/microi.skills/message-notification/agents/openai.yaml +6 -0
  24. package/dist/microi.skills/message-notification/references/contracts.md +99 -0
  25. package/dist/microi.skills/microi-ai-app-auth.js +651 -0
  26. package/dist/microi.skills/microi-ai-application/SKILL.md +80 -0
  27. package/dist/microi.skills/microi-ai-application/agents/openai.yaml +4 -0
  28. package/dist/microi.skills/microi-ai-application/references/frontend-baseline.md +164 -0
  29. package/dist/microi.skills/microi-client-frontend/SKILL.md +562 -0
  30. package/dist/microi.skills/microi-datasource-mapping/SKILL.md +108 -0
  31. package/dist/microi.skills/microi-db-schema/SKILL.md +170 -0
  32. package/dist/microi.skills/microi-db-schema/agents/openai.yaml +4 -0
  33. package/dist/microi.skills/microi-db-schema/references/core-tables.md +695 -0
  34. package/dist/microi.skills/microi-db-schema/references/form-component-options.md +256 -0
  35. package/dist/microi.skills/microi-db-schema/references/schema-overview.md +203 -0
  36. package/dist/microi.skills/microi-db-schema/references/schema.md +647 -0
  37. package/dist/microi.skills/microi-db-schema/references/table-catalog.md +1607 -0
  38. package/dist/microi.skills/microi-deployment/SKILL.md +117 -0
  39. package/dist/microi.skills/microi-deployment/references/deployment-matrix.md +94 -0
  40. package/dist/microi.skills/microi-docs-coverage/SKILL.md +91 -0
  41. package/dist/microi.skills/microi-docs-coverage/references/capability-map.md +65 -0
  42. package/dist/microi.skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +887 -0
  43. package/dist/microi.skills/microi-form-engine/SKILL.md +159 -0
  44. package/dist/microi.skills/microi-form-engine/references/component-catalog.md +116 -0
  45. package/dist/microi.skills/microi-form-engine/references/data-source-events.md +117 -0
  46. package/dist/microi.skills/microi-form-layout/SKILL.md +373 -0
  47. package/dist/microi.skills/microi-frontend-sdk/SKILL.md +304 -0
  48. package/dist/microi.skills/microi-left-right-layout/SKILL.md +132 -0
  49. package/dist/microi.skills/microi-microservice/SKILL.md +115 -0
  50. package/dist/microi.skills/microi-microservice/references/runtime-delivery.md +145 -0
  51. package/dist/microi.skills/microi-mobile-app-quality/SKILL.md +436 -0
  52. package/dist/microi.skills/microi-solution-quotation/SKILL.md +76 -0
  53. package/dist/microi.skills/microi-solution-quotation/agents/openai.yaml +4 -0
  54. package/dist/microi.skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -0
  55. package/dist/microi.skills/microi-system-delivery/SKILL.md +446 -0
  56. package/dist/microi.skills/microi-ui/SKILL.md +321 -0
  57. package/dist/microi.skills/microi-uniapp-frontend/SKILL.md +483 -0
  58. package/dist/microi.skills/microi.v8.js +1758 -0
  59. package/dist/microi.skills/module-engine/SKILL.md +131 -0
  60. package/dist/microi.skills/module-engine/references/module-config.md +174 -0
  61. package/dist/microi.skills/page-engine/SKILL.md +397 -0
  62. package/dist/microi.skills/performance-testing/SKILL.md +207 -0
  63. package/dist/microi.skills/playwright-e2e/SKILL.md +769 -0
  64. package/dist/microi.skills/print-engine/SKILL.md +237 -0
  65. package/dist/microi.skills/production-readonly-audit/SKILL.md +39 -0
  66. package/dist/microi.skills/report-engine/SKILL.md +69 -0
  67. package/dist/microi.skills/report-engine/agents/openai.yaml +4 -0
  68. package/dist/microi.skills/search-engine/SKILL.md +73 -0
  69. package/dist/microi.skills/search-engine/agents/openai.yaml +4 -0
  70. package/dist/microi.skills/spider-engine/SKILL.md +188 -0
  71. package/dist/microi.skills/translate-engine/SKILL.md +91 -0
  72. package/dist/microi.skills/translate-engine/agents/openai.yaml +4 -0
  73. package/dist/microi.skills/ui-design/SKILL.md +1575 -0
  74. package/dist/microi.skills/ui-design/assets/pattern-showcase/app.js +54 -0
  75. package/dist/microi.skills/ui-design/assets/pattern-showcase/index.html +163 -0
  76. package/dist/microi.skills/ui-design/assets/pattern-showcase/styles.css +311 -0
  77. package/dist/microi.skills/ui-design/assets/templates/MCI-DESIGN.md +98 -0
  78. package/dist/microi.skills/ui-design/references/design-pattern-library.md +171 -0
  79. package/dist/microi.skills/ui-design/references/mci-design-contract.md +84 -0
  80. package/dist/microi.skills/ui-design/references/motion-and-media.md +71 -0
  81. package/dist/microi.skills/ui-design/references/product-flow-recipes.md +94 -0
  82. package/dist/microi.skills/uniapp-mall-assets/SKILL.md +105 -0
  83. package/dist/microi.skills/v8-api-config/SKILL.md +272 -0
  84. package/dist/microi.skills/v8-cache-pattern/SKILL.md +286 -0
  85. package/dist/microi.skills/v8-crud-api/SKILL.md +398 -0
  86. package/dist/microi.skills/v8-debugging/SKILL.md +279 -0
  87. package/dist/microi.skills/v8-explorer-tree/SKILL.md +224 -0
  88. package/dist/microi.skills/v8-export-import/SKILL.md +590 -0
  89. package/dist/microi.skills/v8-file-upload/SKILL.md +497 -0
  90. package/dist/microi.skills/v8-formengine-http/SKILL.md +218 -0
  91. package/dist/microi.skills/v8-frontend-events/SKILL.md +349 -0
  92. package/dist/microi.skills/v8-frontend-events/references/bluetooth-print-api.md +107 -0
  93. package/dist/microi.skills/v8-frontend-events/references/bluetooth-print.md +185 -0
  94. package/dist/microi.skills/v8-http-integration/SKILL.md +379 -0
  95. package/dist/microi.skills/v8-image-processing/SKILL.md +187 -0
  96. package/dist/microi.skills/v8-image-processing/agents/openai.yaml +4 -0
  97. package/dist/microi.skills/v8-image-processing/references/api-reference.md +620 -0
  98. package/dist/microi.skills/v8-menu-buttons/SKILL.md +661 -0
  99. package/dist/microi.skills/v8-mongodb/SKILL.md +149 -0
  100. package/dist/microi.skills/v8-mq-mqtt/SKILL.md +227 -0
  101. package/dist/microi.skills/v8-saas-multi-tenant/SKILL.md +193 -0
  102. package/dist/microi.skills/v8-security/SKILL.md +417 -0
  103. package/dist/microi.skills/v8-sql-query/SKILL.md +290 -0
  104. package/dist/microi.skills/v8-table-event/SKILL.md +385 -0
  105. package/dist/microi.skills/v8-template-engine/SKILL.md +165 -0
  106. package/dist/microi.skills/v8-utilities/SKILL.md +79 -0
  107. package/dist/microi.skills/v8-utilities/references/client-api-index.md +136 -0
  108. package/dist/microi.skills/v8-utilities/references/platform-http-routes.md +80 -0
  109. package/dist/microi.skills/v8-utilities/references/server-api-index.md +129 -0
  110. package/dist/microi.skills/v8-workflow/SKILL.md +322 -0
  111. package/dist/microi.skills/workspace-conventions/SKILL.md +479 -0
  112. package/package.json +40 -0
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: microi-solution-quotation
3
+ description: Create or revise compact professional Microi吾码 system solution and quotation Word documents. Use when a user asks for a Microi吾码 解决方案、项目建议书、技术路线、实施周期、市场预估价、优惠报价或一页式 DOCX 报价,尤其适用于低代码、V8引擎、PC/UniApp/微信小程序、物联网和系统集成项目。
4
+ ---
5
+
6
+ # Microi吾码解决方案与报价
7
+
8
+ 生成客户可直接查看或发送的 `.docx` 方案报价。先基于真实需求和资料确定技术边界,再用紧凑商务版式呈现价值、技术路线、总价、周期和限制条件。
9
+
10
+ ## 工作流程
11
+
12
+ 1. 读取需求、PDF、截图、现有 Word 和项目资料;不把尚未确认的硬件协议、第三方接口或平台能力写成已具备。
13
+ 2. 提炼项目目标、核心范围、Microi吾码实现方式、关键技术路线、交付物、风险与前置条件。
14
+ 3. 确定项目名称、版本、日期、开发周期、市场预估价、优惠报价和报价有效期。用户已给值时必须原样采用。
15
+ 4. 使用 `scripts/build_solution_quote.py` 生成 Word;复杂项目可按同一视觉规范扩展。
16
+ 5. 把 DOCX 渲染为 PNG,检查页数、字体、表格、换行和溢出;修正后再交付。缺少 LibreOffice/soffice 时做结构检查,并在交付说明中明确未完成视觉渲染。
17
+
18
+ ## 固定版式
19
+
20
+ - 页面:A4 纵向。
21
+ - 页边距:Word“窄”,上、下、左、右均为 `0.5 in / 1.27 cm / 720 DXA`。
22
+ - 布局:紧凑、商务、可快速扫读。小项目默认一页;不要用缩小到 9pt 以下正文的方式硬塞内容。
23
+ - 字体:中文优先 `Microsoft YaHei`,正文 9-10pt;标题 20-22pt;章节标题 10.5-12pt。
24
+ - 大标题:`Microi吾码 {系统名称} 解决方案与报价`。
25
+ - 副标题必须为:`AI 深度融合 V8引擎 的低代码平台,官网:https://microi.net`。
26
+ - 标题下方放置版本、日期、开发周期、报价有效期;默认版本 `v1.0.0`,默认有效期 15 天。
27
+ - 使用克制的吾码蓝色系;表格必须固定 DXA 宽度并设置单元格内边距,禁止依赖自动调整。
28
+ - 页脚可放版本、日期和官网,不重复正文报价。
29
+
30
+ ## 内容结构
31
+
32
+ 按项目规模裁剪,但至少包含:
33
+
34
+ 1. 项目目标:1 段说明客户要解决的问题和最终结果。
35
+ 2. 解决方案:3-5 个具体范围,明确 Microi 表单引擎、V8 接口/事件、权限、PC 或移动端、第三方/硬件集成的分工。
36
+ 3. 技术路线:用一行可读链路表达核心数据流;物联网项目必须区分“手机本地直连”和“公网云控”。
37
+ 4. 交付范围:源代码/低代码元数据、接口、部署配置、联调、测试和简要操作说明。
38
+ 5. 总价与周期:只展示市场预估总价、优惠总价和开发周期,不列模块价格明细。
39
+ 6. 商务与技术边界:列出需求变更、第三方费用、硬件/云资源、协议资料、样机、账号资质等前置条件。
40
+
41
+ ## 报价规则
42
+
43
+ - 不生成模块级价格表,不把各功能拆分报价。
44
+ - 同时展示:`市场预估价`、`优惠报价`、`开发周期`。
45
+ - 优惠报价应比市场预估价低;所有金额使用人民币并明确“总价”。
46
+ - 用户未提供价格时,可根据范围给出合理市场预估并标注为参考;硬件改造、协议逆向和第三方服务等不确定成本必须排除或写为“另行评估”。
47
+ - 不猜测含税状态、付款比例、质保年限或合同条款;用户未指定时写“以正式合同/确认需求清单为准”。
48
+
49
+ ## Microi吾码技术表达
50
+
51
+ - 后台管理:优先使用表单引擎、菜单/权限和低代码元数据。
52
+ - 业务逻辑:使用 V8 接口引擎、表单事件、定时任务和缓存,不把项目专属逻辑写入平台通用层。
53
+ - 移动端:根据需求选择 UniApp/H5/微信小程序,并通过 HTTPS 调用后端接口。
54
+ - 物联网:推荐 `小程序 -> HTTPS -> Microi V8 API -> MQTT/TLS -> 网关 -> 设备协议`;保留设备本地安全联锁、离线保护和操作审计。
55
+ - 权限:菜单权限与接口业务权限同时设计;设备/客户数据必须做归属与行级校验。
56
+
57
+ ## 生成脚本
58
+
59
+ 准备 UTF-8 JSON 配置并运行:
60
+
61
+ ```powershell
62
+ uv run --with python-docx -- python microi.skills/microi-solution-quotation/scripts/build_solution_quote.py `
63
+ --input .tmp/solution-quote.json `
64
+ --output "AI-Project/客户/项目/Microi吾码系统解决方案与报价.docx"
65
+ ```
66
+
67
+ JSON 常用字段:`system_name`、`project_goal`、`solution_items`、`technical_route`、`deliverables`、`market_price`、`preferential_price`、`development_cycle`、`version`、`date`、`validity_days`、`boundary_note`。
68
+
69
+ ## 工作区与交付检查
70
+
71
+ - 临时脚本、JSON、渲染图和 QA 产物必须写入仓库根目录 `.tmp/`,禁止创建根目录 `tmp/`。
72
+ - 用户认为提取结果有长期价值时,将其整理到用户指定的项目目录,不留在临时目录。
73
+ - 大标题、副标题、版本、日期、周期、市场预估价和优惠报价全部存在。
74
+ - 没有模块明细价格,没有占位符,没有把未确认能力写成已交付。
75
+ - 小项目只有一页;无截断、重叠、孤行、超宽表格和过小字体。
76
+ - 最终回复仅链接正式 DOCX,不链接临时 JSON、生成脚本、PNG 或 PDF QA 文件。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Microi吾码解决方案与报价"
3
+ short_description: "生成紧凑专业的吾码解决方案、周期与总价报价 Word"
4
+ default_prompt: "使用 $microi-solution-quotation 生成一份紧凑的一页 Microi吾码系统解决方案与报价 Word。"
@@ -0,0 +1,296 @@
1
+ import argparse
2
+ import json
3
+ from datetime import date
4
+ from pathlib import Path
5
+
6
+ from docx import Document
7
+ from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
8
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
9
+ from docx.oxml import OxmlElement
10
+ from docx.oxml.ns import qn
11
+ from docx.shared import Inches, Pt, RGBColor
12
+
13
+
14
+ NAVY = "17365D"
15
+ BLUE = "2F75B5"
16
+ PALE_BLUE = "EAF3F9"
17
+ PALE_GRAY = "F4F6F8"
18
+ PALE_GOLD = "FFF3D6"
19
+ GOLD = "B45309"
20
+ INK = "1F2937"
21
+ MUTED = "64748B"
22
+ WHITE = "FFFFFF"
23
+ BORDER = "CBD5E1"
24
+ FONT = "Microsoft YaHei"
25
+ PAGE_WIDTH_DXA = 11909
26
+ MARGIN_DXA = 720
27
+ TABLE_INDENT_DXA = 120
28
+ TABLE_WIDTH_DXA = PAGE_WIDTH_DXA - MARGIN_DXA * 2 - TABLE_INDENT_DXA
29
+
30
+
31
+ def set_run_font(run, size=9.2, bold=False, color=INK):
32
+ run.font.name = FONT
33
+ rfonts = run._element.get_or_add_rPr().get_or_add_rFonts()
34
+ for key in ("w:eastAsia", "w:ascii", "w:hAnsi"):
35
+ rfonts.set(qn(key), FONT)
36
+ run.font.size = Pt(size)
37
+ run.font.bold = bold
38
+ run.font.color.rgb = RGBColor.from_string(color)
39
+
40
+
41
+ def format_paragraph(paragraph, before=0, after=0, line=1.05, align=None):
42
+ fmt = paragraph.paragraph_format
43
+ fmt.space_before = Pt(before)
44
+ fmt.space_after = Pt(after)
45
+ fmt.line_spacing = line
46
+ if align is not None:
47
+ paragraph.alignment = align
48
+
49
+
50
+ def add_paragraph(doc, text, size=9.2, bold=False, color=INK, before=0, after=2, line=1.05, align=None):
51
+ paragraph = doc.add_paragraph()
52
+ format_paragraph(paragraph, before, after, line, align)
53
+ set_run_font(paragraph.add_run(text), size, bold, color)
54
+ return paragraph
55
+
56
+
57
+ def set_cell_shading(cell, fill):
58
+ tc_pr = cell._tc.get_or_add_tcPr()
59
+ shd = tc_pr.find(qn("w:shd"))
60
+ if shd is None:
61
+ shd = OxmlElement("w:shd")
62
+ tc_pr.append(shd)
63
+ shd.set(qn("w:fill"), fill)
64
+
65
+
66
+ def set_cell_margins(cell, top=70, start=110, bottom=70, end=110):
67
+ tc_pr = cell._tc.get_or_add_tcPr()
68
+ tc_mar = tc_pr.first_child_found_in("w:tcMar")
69
+ if tc_mar is None:
70
+ tc_mar = OxmlElement("w:tcMar")
71
+ tc_pr.append(tc_mar)
72
+ for name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
73
+ element = tc_mar.find(qn(f"w:{name}"))
74
+ if element is None:
75
+ element = OxmlElement(f"w:{name}")
76
+ tc_mar.append(element)
77
+ element.set(qn("w:w"), str(value))
78
+ element.set(qn("w:type"), "dxa")
79
+
80
+
81
+ def set_cell_width(cell, width):
82
+ tc_pr = cell._tc.get_or_add_tcPr()
83
+ tc_w = tc_pr.find(qn("w:tcW"))
84
+ if tc_w is None:
85
+ tc_w = OxmlElement("w:tcW")
86
+ tc_pr.append(tc_w)
87
+ tc_w.set(qn("w:w"), str(width))
88
+ tc_w.set(qn("w:type"), "dxa")
89
+
90
+
91
+ def set_table_geometry(table, widths, indent=TABLE_INDENT_DXA):
92
+ total = sum(widths)
93
+ table.autofit = False
94
+ table.alignment = WD_TABLE_ALIGNMENT.LEFT
95
+ tbl_pr = table._tbl.tblPr
96
+ tbl_w = tbl_pr.find(qn("w:tblW"))
97
+ if tbl_w is None:
98
+ tbl_w = OxmlElement("w:tblW")
99
+ tbl_pr.append(tbl_w)
100
+ tbl_w.set(qn("w:w"), str(total))
101
+ tbl_w.set(qn("w:type"), "dxa")
102
+ tbl_ind = tbl_pr.find(qn("w:tblInd"))
103
+ if tbl_ind is None:
104
+ tbl_ind = OxmlElement("w:tblInd")
105
+ tbl_pr.append(tbl_ind)
106
+ tbl_ind.set(qn("w:w"), str(indent))
107
+ tbl_ind.set(qn("w:type"), "dxa")
108
+ grid = table._tbl.tblGrid
109
+ for child in list(grid):
110
+ grid.remove(child)
111
+ for width in widths:
112
+ col = OxmlElement("w:gridCol")
113
+ col.set(qn("w:w"), str(width))
114
+ grid.append(col)
115
+ for row in table.rows:
116
+ for index, cell in enumerate(row.cells):
117
+ set_cell_width(cell, widths[index])
118
+ set_cell_margins(cell)
119
+ cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
120
+
121
+
122
+ def set_table_borders(table, color=BORDER, size="6"):
123
+ tbl_pr = table._tbl.tblPr
124
+ borders = tbl_pr.first_child_found_in("w:tblBorders")
125
+ if borders is None:
126
+ borders = OxmlElement("w:tblBorders")
127
+ tbl_pr.append(borders)
128
+ for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
129
+ tag = f"w:{edge}"
130
+ element = borders.find(qn(tag))
131
+ if element is None:
132
+ element = OxmlElement(tag)
133
+ borders.append(element)
134
+ element.set(qn("w:val"), "single")
135
+ element.set(qn("w:sz"), size)
136
+ element.set(qn("w:space"), "0")
137
+ element.set(qn("w:color"), color)
138
+
139
+
140
+ def add_section_heading(doc, text):
141
+ return add_paragraph(doc, text, size=10.8, bold=True, color=BLUE, before=4, after=1, line=1.0)
142
+
143
+
144
+ def add_bullet(doc, label, detail):
145
+ paragraph = doc.add_paragraph(style="List Bullet")
146
+ format_paragraph(paragraph, before=0, after=1, line=1.03)
147
+ set_run_font(paragraph.add_run(f"{label}:"), 8.8, True, NAVY)
148
+ set_run_font(paragraph.add_run(detail), 8.8, False, INK)
149
+
150
+
151
+ def add_metadata_table(doc, config):
152
+ table = doc.add_table(rows=1, cols=4)
153
+ widths = [TABLE_WIDTH_DXA // 4] * 4
154
+ widths[-1] += TABLE_WIDTH_DXA - sum(widths)
155
+ values = [
156
+ ("版本", config["version"]),
157
+ ("日期", config["date"]),
158
+ ("开发周期", config["development_cycle"]),
159
+ ("报价有效期", f'{config["validity_days"]} 天'),
160
+ ]
161
+ for index, (label, value) in enumerate(values):
162
+ cell = table.cell(0, index)
163
+ set_cell_shading(cell, PALE_GRAY)
164
+ paragraph = cell.paragraphs[0]
165
+ format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
166
+ set_run_font(paragraph.add_run(f"{label}:"), 8.0, True, MUTED)
167
+ set_run_font(paragraph.add_run(str(value)), 8.2, True, NAVY)
168
+ set_table_geometry(table, widths)
169
+ set_table_borders(table, "E2E8F0", "4")
170
+
171
+
172
+ def add_callout(doc, label, detail, fill=PALE_BLUE):
173
+ table = doc.add_table(rows=1, cols=1)
174
+ set_table_geometry(table, [TABLE_WIDTH_DXA])
175
+ set_table_borders(table, "9CC2E5", "7")
176
+ cell = table.cell(0, 0)
177
+ set_cell_shading(cell, fill)
178
+ paragraph = cell.paragraphs[0]
179
+ format_paragraph(paragraph, after=0, line=1.03)
180
+ set_run_font(paragraph.add_run(f"{label}:"), 8.8, True, NAVY)
181
+ set_run_font(paragraph.add_run(detail), 8.8, False, INK)
182
+
183
+
184
+ def add_quote_table(doc, config):
185
+ table = doc.add_table(rows=2, cols=3)
186
+ widths = [TABLE_WIDTH_DXA // 3] * 3
187
+ widths[-1] += TABLE_WIDTH_DXA - sum(widths)
188
+ headers = ["市场预估价(总价)", "吾码优惠报价(总价)", "开发周期"]
189
+ values = [config["market_price"], config["preferential_price"], config["development_cycle"]]
190
+ for index, header in enumerate(headers):
191
+ cell = table.cell(0, index)
192
+ set_cell_shading(cell, NAVY)
193
+ paragraph = cell.paragraphs[0]
194
+ format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
195
+ set_run_font(paragraph.add_run(header), 8.7, True, WHITE)
196
+ for index, value in enumerate(values):
197
+ cell = table.cell(1, index)
198
+ set_cell_shading(cell, PALE_GOLD if index == 1 else PALE_GRAY)
199
+ paragraph = cell.paragraphs[0]
200
+ format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
201
+ set_run_font(paragraph.add_run(str(value)), 12.0 if index == 1 else 10.5, True, GOLD if index == 1 else NAVY)
202
+ set_table_geometry(table, widths)
203
+ set_table_borders(table)
204
+
205
+
206
+ def normalize_config(raw):
207
+ required = [
208
+ "system_name", "project_goal", "solution_items", "technical_route",
209
+ "deliverables", "market_price", "preferential_price",
210
+ "development_cycle", "boundary_note",
211
+ ]
212
+ missing = [name for name in required if not raw.get(name)]
213
+ if missing:
214
+ raise ValueError("Missing required fields: " + ", ".join(missing))
215
+ if not isinstance(raw["solution_items"], list) or not raw["solution_items"]:
216
+ raise ValueError("solution_items must be a non-empty list")
217
+ raw.setdefault("version", "v1.0.0")
218
+ raw.setdefault("date", date.today().isoformat())
219
+ raw.setdefault("validity_days", 15)
220
+ raw.setdefault("subtitle", "AI 深度融合 V8引擎 的低代码平台,官网:https://microi.net")
221
+ raw.setdefault("project_scope", "")
222
+ return raw
223
+
224
+
225
+ def build_document(config, output):
226
+ doc = Document()
227
+ section = doc.sections[0]
228
+ section.page_width = Inches(8.27)
229
+ section.page_height = Inches(11.69)
230
+ section.top_margin = Inches(0.5)
231
+ section.bottom_margin = Inches(0.5)
232
+ section.left_margin = Inches(0.5)
233
+ section.right_margin = Inches(0.5)
234
+ section.header_distance = Inches(0.2)
235
+ section.footer_distance = Inches(0.2)
236
+
237
+ normal = doc.styles["Normal"]
238
+ normal.font.name = FONT
239
+ normal._element.rPr.rFonts.set(qn("w:eastAsia"), FONT)
240
+ normal.font.size = Pt(9.2)
241
+ normal.font.color.rgb = RGBColor.from_string(INK)
242
+
243
+ title = f'Microi吾码 {config["system_name"]} 解决方案与报价'
244
+ add_paragraph(doc, title, size=20.0, bold=True, color=NAVY, after=1, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
245
+ add_paragraph(doc, config["subtitle"], size=9.3, color=MUTED, after=4, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
246
+ add_metadata_table(doc, config)
247
+
248
+ add_section_heading(doc, "一、项目目标")
249
+ add_callout(doc, "目标", config["project_goal"])
250
+
251
+ add_section_heading(doc, "二、解决方案")
252
+ for item in config["solution_items"]:
253
+ if isinstance(item, str):
254
+ add_bullet(doc, "方案", item)
255
+ else:
256
+ add_bullet(doc, item.get("label", "方案"), item.get("detail", ""))
257
+
258
+ add_section_heading(doc, "三、核心技术路线")
259
+ add_callout(doc, "链路", config["technical_route"], fill=PALE_GRAY)
260
+
261
+ add_section_heading(doc, "四、交付范围")
262
+ add_paragraph(doc, config["deliverables"], size=8.7, after=1, line=1.03)
263
+ if config["project_scope"]:
264
+ add_paragraph(doc, config["project_scope"], size=8.3, color=MUTED, after=1, line=1.0)
265
+
266
+ add_section_heading(doc, "五、项目总价与周期")
267
+ add_quote_table(doc, config)
268
+
269
+ add_section_heading(doc, "六、说明与边界")
270
+ add_paragraph(doc, config["boundary_note"], size=8.1, color=MUTED, after=0, line=1.0)
271
+
272
+ footer = section.footer.paragraphs[0]
273
+ format_paragraph(footer, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.RIGHT)
274
+ set_run_font(footer.add_run(f'Microi吾码 | {config["version"]} | {config["date"]} | https://microi.net'), 7.2, False, MUTED)
275
+
276
+ doc.core_properties.title = title
277
+ doc.core_properties.subject = "Microi吾码系统解决方案与报价"
278
+ doc.core_properties.author = "Microi吾码"
279
+ output.parent.mkdir(parents=True, exist_ok=True)
280
+ doc.save(output)
281
+
282
+
283
+ def main():
284
+ parser = argparse.ArgumentParser(description="Build a compact Microi solution quotation DOCX")
285
+ parser.add_argument("--input", required=True, help="UTF-8 JSON configuration file")
286
+ parser.add_argument("--output", required=True, help="Output DOCX path")
287
+ args = parser.parse_args()
288
+ with open(args.input, "r", encoding="utf-8") as handle:
289
+ config = normalize_config(json.load(handle))
290
+ output = Path(args.output).resolve()
291
+ build_document(config, output)
292
+ print(output)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()