siluzan-tso-cli 1.1.24 → 1.1.25-beta.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 (35) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +535 -140
  3. package/dist/skill/SKILL.md +4 -1
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/assets/pmax-create-template.md +8 -8
  6. package/dist/skill/references/accounts/accounts.md +23 -17
  7. package/dist/skill/references/accounts/currency.md +1 -1
  8. package/dist/skill/references/accounts/finance.md +4 -4
  9. package/dist/skill/references/accounts/open-account-by-media.md +150 -14
  10. package/dist/skill/references/accounts/open-account-google-ui.md +2 -2
  11. package/dist/skill/references/analytics/account-analytics.md +35 -21
  12. package/dist/skill/references/analytics/rag.md +1 -1
  13. package/dist/skill/references/analytics/reporting.md +2 -2
  14. package/dist/skill/references/analytics/website-diagnosis-guide.md +9 -2
  15. package/dist/skill/references/core/agent-conventions.md +2 -0
  16. package/dist/skill/references/core/playbooks.md +3 -2
  17. package/dist/skill/references/core/setup.md +5 -5
  18. package/dist/skill/references/core/skill-authoring.md +1 -1
  19. package/dist/skill/references/core/workflows.md +15 -13
  20. package/dist/skill/references/operations/hosted-automation-monitoring-json.md +1 -1
  21. package/dist/skill/references/operations/hosted-automation-optimize-scale.md +2 -2
  22. package/dist/skill/references/operations/hosted-automation-self-control.md +1 -1
  23. package/dist/skill/report-templates/README.md +1 -1
  24. package/dist/skill/report-templates/REPORT-WORKFLOW.md +5 -0
  25. package/dist/skill/report-templates/google-account-diagnosis-report.md +7 -0
  26. package/dist/skill/report-templates/google-ads-diagnosis.md +51 -2
  27. package/dist/skill/report-templates/google-inquiry-analysis.md +8 -8
  28. package/dist/skill/report-templates/google-period-report.md +2 -2
  29. package/dist/skill/report-templates/okki-weekly-google-client.md +4 -4
  30. package/dist/skill/report-templates/report-template.html +1 -0
  31. package/dist/skill/report-templates/website-diagnosis-report.html +450 -36
  32. package/dist/skill/report-templates/website-diagnosis-report.md +6 -5
  33. package/dist/skill/scripts/install.ps1 +3 -3
  34. package/dist/skill/scripts/install.sh +3 -3
  35. package/package.json +1 -1
@@ -1,11 +1,10 @@
1
1
  <!doctype html>
2
2
  <!--
3
- 网站诊断报告 HTML 骨架(对齐 siluzan-front-end-v2 WebsiteAnalysisReport/v3)
3
+ 网站诊断报告 — 单文件 HTML 终稿(对齐 siluzan-front-end-v2 WebsiteAnalysisReport/v3)
4
4
 
5
- Agent 生成终稿时:
6
- 1. website-diagnosis collect + 规则 JSON 填充各 section
7
- 2. 版式可参考同目录 report-template.html(Tailwind + ECharts CDN)
8
- 3. 勿删除下列 section 结构;可增图表与样式
5
+ 数据:window.__WEBSITE_DIAGNOSIS__(与 tso_agent getWebsiteDiagnosisData.data 同结构)
6
+ 生成:siluzan-tso website-diagnosis render --data ./diagnosis.json [--collect ./collect.json]
7
+ Agent 将诊断 JSON 写入下方锚点后保存为本文件。
9
8
  -->
10
9
  <html lang="zh-CN">
11
10
  <head>
@@ -13,69 +12,484 @@
13
12
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
13
  <title>网站诊断报告</title>
15
14
  <script src="https://cdn.tailwindcss.com"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
16
+ <style>
17
+ .score-ring-track { stroke: #e5e7eb; }
18
+ .status-excellent { color: #00b42a; }
19
+ .status-good { color: #00b42a; }
20
+ .status-normal { color: #ff7d00; }
21
+ .status-poor { color: #f53f3f; }
22
+ .status-absent { color: #f53f3f; }
23
+ .chart-box { width: 100%; height: 320px; }
24
+ .chart-box-sm { width: 100%; height: 260px; }
25
+ @media print {
26
+ body { background: #fff; }
27
+ section { break-inside: avoid; }
28
+ }
29
+ </style>
16
30
  </head>
17
- <body class="bg-slate-50 text-slate-800">
18
- <!-- ReportHeader -->
31
+ <body class="bg-slate-50 text-slate-800 antialiased">
19
32
  <header class="bg-white shadow-sm px-6 py-8 max-w-5xl mx-auto mt-6 rounded-lg">
20
- <h1 class="text-2xl font-bold text-center">网站诊断报告</h1>
21
- <p class="mt-4 text-sm"><span class="text-slate-500">诊断网址:</span><span id="report-url"></span></p>
22
- <p class="text-sm"><span class="text-slate-500">诊断时间:</span><span id="report-time"></span></p>
23
- <p class="text-lg mt-2 font-semibold">
33
+ <h1 class="text-2xl font-bold text-center text-slate-900">网站诊断报告</h1>
34
+ <p class="mt-4 text-sm">
35
+ <span class="text-slate-500">诊断网址:</span>
36
+ <a id="report-url" href="#" target="_blank" rel="noopener" class="text-brand-600 break-all"></a>
37
+ </p>
38
+ <p class="text-sm mt-1">
39
+ <span class="text-slate-500">诊断时间:</span><span id="report-time"></span>
40
+ </p>
41
+ <p class="text-lg mt-3 font-semibold">
24
42
  <span class="text-slate-500">综合得分:</span>
25
- <span id="report-score" class="text-emerald-600"></span>
43
+ <span id="report-score"></span>
44
+ <span id="report-rating" class="ml-2 text-base font-medium"></span>
26
45
  </p>
27
46
  </header>
28
47
 
29
48
  <main class="max-w-5xl mx-auto px-4 py-6 space-y-6">
30
- <!-- HealthOverview -->
31
49
  <section id="health-overview" class="bg-white rounded-lg shadow-sm p-6">
32
- <h2 class="text-lg font-semibold border-b pb-2">健康度总览</h2>
33
- <div id="health-overview-body" class="mt-4 text-sm"></div>
50
+ <h2 class="text-lg font-semibold border-b border-slate-200 pb-2">一、网站健康快览</h2>
51
+ <p id="health-summary" class="mt-3 text-sm text-slate-600"></p>
52
+ <div class="mt-6 grid md:grid-cols-2 gap-6 items-center">
53
+ <div class="flex flex-col items-center" id="score-ring-wrap"></div>
54
+ <div>
55
+ <p class="text-sm text-slate-500 mb-2 text-center md:text-left">各模块健康度分布</p>
56
+ <div id="chart-radar" class="chart-box"></div>
57
+ </div>
58
+ </div>
34
59
  </section>
35
60
 
36
- <!-- RiskMap -->
37
61
  <section id="risk-map" class="bg-white rounded-lg shadow-sm p-6">
38
- <h2 class="text-lg font-semibold border-b pb-2">核心风险与问题</h2>
39
- <ul id="core-issues-list" class="mt-4 space-y-3 text-sm"></ul>
62
+ <h2 class="text-lg font-semibold border-b border-slate-200 pb-2">二、核心风险与模块得分</h2>
63
+ <div id="chart-risk-bar" class="chart-box mt-4"></div>
64
+ <ul id="core-issues-list" class="mt-6 space-y-3 text-sm"></ul>
40
65
  </section>
41
66
 
42
- <!-- ModuleDetail (repeat per m1–m6) -->
43
67
  <section id="module-details" class="bg-white rounded-lg shadow-sm p-6">
44
- <h2 class="text-lg font-semibold border-b pb-2">分模块诊断明细</h2>
68
+ <h2 class="text-lg font-semibold border-b border-slate-200 pb-2">三、分模块诊断明细</h2>
45
69
  <div id="modules-container" class="mt-4 space-y-8"></div>
46
70
  </section>
47
71
 
48
- <!-- LoadingSpeed -->
49
72
  <section id="loading-speed" class="bg-white rounded-lg shadow-sm p-6">
50
- <h2 class="text-lg font-semibold border-b pb-2">加载速度(Lighthouse)</h2>
73
+ <h2 class="text-lg font-semibold border-b border-slate-200 pb-2">四、加载速度(Lighthouse)</h2>
74
+ <div id="lighthouse-callout" class="mt-3 hidden rounded-md bg-amber-50 border border-amber-200 px-4 py-3 text-sm text-amber-900"></div>
75
+ <div id="chart-lighthouse" class="chart-box-sm mt-4"></div>
51
76
  <div id="lighthouse-tables" class="mt-4 overflow-x-auto text-sm"></div>
52
77
  </section>
53
78
 
54
- <!-- PriorityPlan -->
55
79
  <section id="priority-plan" class="bg-white rounded-lg shadow-sm p-6">
56
- <h2 class="text-lg font-semibold border-b pb-2">优先改进计划</h2>
57
- <div id="priority-plan-body" class="mt-4 text-sm"></div>
80
+ <h2 class="text-lg font-semibold border-b border-slate-200 pb-2">五、优先改进计划</h2>
81
+ <div id="priority-plan-body" class="mt-4 text-sm space-y-4"></div>
58
82
  </section>
59
83
 
60
- <!-- LongtermValue -->
61
84
  <section id="longterm-value" class="bg-white rounded-lg shadow-sm p-6 mb-8">
62
- <h2 class="text-lg font-semibold border-b pb-2">投放与长期价值建议</h2>
63
- <div id="longterm-value-body" class="mt-4 text-sm"></div>
85
+ <h2 class="text-lg font-semibold border-b border-slate-200 pb-2">六、投放与长期价值建议</h2>
86
+ <div id="longterm-value-body" class="mt-4 text-sm text-slate-700 leading-relaxed"></div>
64
87
  </section>
65
88
  </main>
66
89
 
90
+ <footer class="max-w-5xl mx-auto px-4 pb-8 text-center text-xs text-slate-400">
91
+ 由丝路赞网站诊断生成 · 打开本文件需联网加载 Tailwind / ECharts CDN
92
+ </footer>
93
+
67
94
  <script>
68
- // 示例:将 Agent 生成的诊断 JSON 赋给 window.__WEBSITE_DIAGNOSIS__ 后调用 renderReport()
69
95
  window.__WEBSITE_DIAGNOSIS__ = window.__WEBSITE_DIAGNOSIS__ || null;
70
96
 
97
+ const RATINGS = [
98
+ { id: "s1", level: "优秀", description: "可直接投放广告", color: "#00b42a", min: 90 },
99
+ { id: "s2", level: "良好", description: "小幅优化后可直接投放广告", color: "#00b42a", min: 80, max: 89.99 },
100
+ { id: "s3", level: "一般", description: "需要重点优化", color: "#ff7d00", min: 70, max: 79.99 },
101
+ { id: "s4", level: "较差", description: "需要大幅改进优化", color: "#f53f3f", min: 60, max: 69.99 },
102
+ { id: "s5", level: "不建议投放", description: "不建议投放广告,需要全面整改", color: "#f53f3f", max: 59.99 },
103
+ ];
104
+
105
+ const MODULE_META = {
106
+ m1: { name: "网站内容及结构诊断", abbreviation: "内容与结构", totalScore: 30 },
107
+ m2: { name: "网站性能诊断", abbreviation: "网站技术性能", totalScore: 25 },
108
+ m3: { name: "营销基础与广告落地页", abbreviation: "营销基础与落地页", totalScore: 15 },
109
+ m4: { name: "用户体验与转化", abbreviation: "用户体验与转化", totalScore: 15 },
110
+ m5: { name: "媒体广告投放辅助", abbreviation: "广告辅助检查", totalScore: 10 },
111
+ m6: { name: "社交媒体辅助", abbreviation: "社交媒体", totalScore: 5 },
112
+ };
113
+
114
+ const ITEM_LABELS = {
115
+ m1i1: "Banner 轮播图", m1i2: "导航栏", m1i3: "首页信息布局", m1i4: "产品类目页", m1i5: "产品详情页",
116
+ m1i6: "公司介绍页", m1i7: "联系我们页", m1i8: "表单提交流程", m1i9: "社交媒体链接",
117
+ m2i1: "加载速度", m2i2: "HTTPS 安全", m2i3: "404 页面", m2i4: "图片清晰度", m2i5: "移动端适配",
118
+ m3i1: "企业邮箱", m3i2: "联系电话", m3i4: "语言一致性", m3i5: "地址地图",
119
+ m4i1: "页面布局", m4i2: "CTA 按钮", m4i3: "表单体验", m4i4: "内容可读性", m4i5: "社媒分享可扩展性",
120
+ m5i1: "落地页速度", m5i2: "询盘转化路径", m5i4: "跟踪与分析",
121
+ m6i1: "内容可引用性", m6i2: "内容可分享性", m6i3: "视觉风格统一",
122
+ };
123
+
124
+ const ITEM_FULL = {
125
+ m1i1: 5, m1i2: 2, m1i3: 4, m1i4: 2, m1i5: 4, m1i6: 3, m1i7: 4, m1i8: 4, m1i9: 2,
126
+ m2i1: 8, m2i2: 5, m2i3: 4, m2i4: 4, m2i5: 4,
127
+ m3i1: 6, m3i2: 3, m3i4: 3, m3i5: 3,
128
+ m4i1: 3, m4i2: 5, m4i3: 3, m4i4: 3, m4i5: 1,
129
+ m5i1: 3, m5i2: 4, m5i4: 3,
130
+ m6i1: 2, m6i2: 2, m6i3: 1,
131
+ };
132
+
133
+ const CORE_ISSUES = {
134
+ ci1: { issue: "首屏缺乏清晰 CTA", impact: "广告落地页效果差", priority: "高" },
135
+ ci2: { issue: "转化跟踪未部署", impact: "无法归因广告效果", priority: "高" },
136
+ ci3: { issue: "移动端显示异常", impact: "移动端转化下降", priority: "高" },
137
+ ci4: { issue: "页面加载缓慢", impact: "首屏加载慢", priority: "高" },
138
+ ci5: { issue: "导航栏与清晰度", impact: "用户找不到关键信息", priority: "中" },
139
+ ci6: { issue: "表单复杂或缺乏清晰", impact: "表单提交率低", priority: "中" },
140
+ ci7: { issue: "类目页与详情页内容不匹配", impact: "广告相关性低", priority: "中" },
141
+ ci8: { issue: "网站语言与广告不一致", impact: "用户体验差", priority: "中" },
142
+ ci9: { issue: "缺少社媒分享入口", impact: "二次传播效率低", priority: "低" },
143
+ ci10: { issue: "品牌风格不统一", impact: "品牌可信度低", priority: "低" },
144
+ };
145
+
146
+ const STATUS_ZH = {
147
+ Excellent: "优秀", Good: "良好", Normal: "一般", Poor: "较差",
148
+ Full: "完整", NeedImprove: "需优化", Absent: "缺失",
149
+ };
150
+
151
+ function getRating(score, ratingId) {
152
+ if (ratingId) {
153
+ const r = RATINGS.find((x) => x.id === ratingId);
154
+ if (r) return r;
155
+ }
156
+ const s = Number(score) || 0;
157
+ for (const r of RATINGS) {
158
+ if (r.min != null && r.max != null && s >= r.min && s <= r.max) return r;
159
+ if (r.min != null && r.max == null && s >= r.min) return r;
160
+ if (r.max != null && r.min == null && s <= r.max) return r;
161
+ }
162
+ return RATINGS[RATINGS.length - 1];
163
+ }
164
+
165
+ function enrichData(raw) {
166
+ if (!raw) return null;
167
+ const data = { ...raw };
168
+ if (!data.analyzedAt) data.analyzedAt = new Date().toISOString();
169
+ const modules = (data.modules || []).map((mod) => {
170
+ const meta = MODULE_META[mod.id] || { name: mod.id, abbreviation: mod.id, totalScore: 0 };
171
+ const items = (mod.items || []).map((it) => ({
172
+ ...it,
173
+ name: it.name || ITEM_LABELS[it.id] || it.id,
174
+ totalScore: it.totalScore != null ? it.totalScore : ITEM_FULL[it.id] || 0,
175
+ }));
176
+ const score = items.reduce((s, it) => s + (Number(it.score) || 0), 0);
177
+ return {
178
+ ...mod,
179
+ ...meta,
180
+ items,
181
+ score: mod.score != null ? Number(mod.score) : score,
182
+ totalScore: meta.totalScore,
183
+ };
184
+ });
185
+ data.modules = modules;
186
+ data.score =
187
+ data.score != null
188
+ ? Number(data.score)
189
+ : modules.reduce((s, m) => s + (Number(m.score) || 0), 0);
190
+ const rating = getRating(data.score, data.ratingId);
191
+ data.ratingId = rating.id;
192
+ data.ratingLabel = data.ratingLabel || rating.level;
193
+ data.ratingDescription = data.ratingDescription || rating.description;
194
+ data.ratingColor = rating.color;
195
+ data.coreIssues = (data.coreIssuesIds || [])
196
+ .map((id) => CORE_ISSUES[id] && { id, ...CORE_ISSUES[id] })
197
+ .filter(Boolean);
198
+ data.lighthouse = data.lighthouse || data.lighthouseResult || null;
199
+ return data;
200
+ }
201
+
202
+ function statusClass(status) {
203
+ const s = String(status || "");
204
+ if (s === "Excellent" || s === "Good" || s === "Full") return "status-good";
205
+ if (s === "Normal" || s === "NeedImprove") return "status-normal";
206
+ return "status-poor";
207
+ }
208
+
209
+ function renderScoreRing(score, color) {
210
+ const pct = Math.min(100, Math.max(0, Number(score) || 0));
211
+ const dash = (pct / 100) * 471;
212
+ return `
213
+ <svg width="180" height="180" viewBox="0 0 180 180" aria-label="综合得分">
214
+ <circle class="score-ring-track" cx="90" cy="90" r="75" fill="none" stroke-width="14"/>
215
+ <circle cx="90" cy="90" r="75" fill="none" stroke="${color}" stroke-width="14"
216
+ stroke-linecap="round" stroke-dasharray="${dash} 471" transform="rotate(-90 90 90)"/>
217
+ </svg>
218
+ <div class="text-center -mt-24">
219
+ <div class="text-4xl font-bold text-slate-900">${pct.toFixed(1)}</div>
220
+ <div class="text-slate-500 text-sm">/ 100</div>
221
+ </div>`;
222
+ }
223
+
224
+ function initRadar(el, modules) {
225
+ if (!window.echarts || !el) return;
226
+ const chart = echarts.init(el);
227
+ chart.setOption({
228
+ radar: {
229
+ indicator: modules.map((m) => ({ name: m.abbreviation, max: m.totalScore || 1 })),
230
+ radius: "62%",
231
+ splitNumber: 4,
232
+ axisName: { color: "#64748b", fontSize: 11 },
233
+ },
234
+ series: [{
235
+ type: "radar",
236
+ data: [{
237
+ value: modules.map((m) => Number(m.score) || 0),
238
+ areaStyle: { color: "rgba(37, 99, 235, 0.2)" },
239
+ lineStyle: { color: "#2563eb", width: 2 },
240
+ itemStyle: { color: "#2563eb" },
241
+ }],
242
+ }],
243
+ });
244
+ window.addEventListener("resize", () => chart.resize());
245
+ }
246
+
247
+ function initRiskBar(el, modules) {
248
+ if (!window.echarts || !el) return;
249
+ const sorted = [...modules].sort((a, b) => (a.score / (a.totalScore || 1)) - (b.score / (b.totalScore || 1)));
250
+ const names = sorted.map((m) => m.abbreviation);
251
+ const totals = sorted.map((m) => m.totalScore);
252
+ const actuals = sorted.map((m) => Number(m.score) || 0);
253
+ const chart = echarts.init(el);
254
+ chart.setOption({
255
+ tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
256
+ grid: { left: 8, right: 56, top: 8, bottom: 8, containLabel: true },
257
+ xAxis: { type: "value", show: false, max: Math.max(...totals, 1) },
258
+ yAxis: {
259
+ type: "category",
260
+ data: names,
261
+ axisLabel: { color: "#64748b", fontSize: 11 },
262
+ axisLine: { show: false },
263
+ axisTick: { show: false },
264
+ },
265
+ series: [
266
+ {
267
+ name: "满分",
268
+ type: "bar",
269
+ data: totals,
270
+ barWidth: 18,
271
+ itemStyle: { color: "#93c5fd", borderRadius: [0, 4, 4, 0] },
272
+ label: {
273
+ show: true,
274
+ position: "right",
275
+ formatter: (p) => `${actuals[p.dataIndex]}/${p.value}`,
276
+ color: "#475569",
277
+ fontSize: 11,
278
+ },
279
+ z: 1,
280
+ },
281
+ {
282
+ name: "得分",
283
+ type: "bar",
284
+ data: actuals,
285
+ barWidth: 18,
286
+ barGap: "-100%",
287
+ itemStyle: { color: "#22c55e", borderRadius: [0, 4, 4, 0] },
288
+ z: 2,
289
+ },
290
+ ],
291
+ });
292
+ window.addEventListener("resize", () => chart.resize());
293
+ }
294
+
295
+ function initLighthouseChart(el, lh) {
296
+ if (!window.echarts || !el || !lh) return;
297
+ const desktop = lh.desktop || {};
298
+ const mobile = lh.mobile || {};
299
+ const chart = echarts.init(el);
300
+ chart.setOption({
301
+ tooltip: { trigger: "axis" },
302
+ legend: { data: ["桌面端", "移动端"], bottom: 0 },
303
+ grid: { left: 48, right: 24, top: 24, bottom: 48 },
304
+ xAxis: { type: "category", data: ["性能得分", "FCP (s)", "SI (s)"] },
305
+ yAxis: { type: "value" },
306
+ series: [
307
+ {
308
+ name: "桌面端",
309
+ type: "bar",
310
+ data: [
311
+ desktop.score ?? 0,
312
+ desktop.firstContentfulPaint != null ? (desktop.firstContentfulPaint / 1000).toFixed(2) : 0,
313
+ desktop.speedIndex != null ? (desktop.speedIndex / 1000).toFixed(2) : 0,
314
+ ],
315
+ itemStyle: { color: "#2563eb" },
316
+ },
317
+ {
318
+ name: "移动端",
319
+ type: "bar",
320
+ data: [
321
+ mobile.score ?? 0,
322
+ mobile.firstContentfulPaint != null ? (mobile.firstContentfulPaint / 1000).toFixed(2) : 0,
323
+ mobile.speedIndex != null ? (mobile.speedIndex / 1000).toFixed(2) : 0,
324
+ ],
325
+ itemStyle: { color: "#f97316" },
326
+ },
327
+ ],
328
+ });
329
+ window.addEventListener("resize", () => chart.resize());
330
+ }
331
+
332
+ function lighthouseTable(lh) {
333
+ if (!lh || (!lh.desktop && !lh.mobile)) return "";
334
+ const row = (label, d, m) => `
335
+ <tr class="border-t border-slate-100">
336
+ <td class="py-2 pr-4 text-slate-600">${label}</td>
337
+ <td class="py-2">${d ?? "—"}</td>
338
+ <td class="py-2">${m ?? "—"}</td>
339
+ </tr>`;
340
+ const fmtMs = (v) => (v != null ? `${(v / 1000).toFixed(2)} s` : "—");
341
+ return `
342
+ <table class="min-w-full text-left">
343
+ <thead><tr class="text-slate-500 border-b">
344
+ <th class="pb-2">指标</th><th class="pb-2">桌面端</th><th class="pb-2">移动端</th>
345
+ </tr></thead>
346
+ <tbody>
347
+ ${row("性能得分", lh.desktop?.score, lh.mobile?.score)}
348
+ ${row("FCP", fmtMs(lh.desktop?.firstContentfulPaint), fmtMs(lh.mobile?.firstContentfulPaint))}
349
+ ${row("SI", fmtMs(lh.desktop?.speedIndex), fmtMs(lh.mobile?.speedIndex))}
350
+ </tbody>
351
+ </table>`;
352
+ }
353
+
354
+ function buildPriorityPlan(data) {
355
+ const blocks = [];
356
+ const priOrder = { 高: 0, 中: 1, 低: 2 };
357
+ const issues = [...(data.coreIssues || [])].sort(
358
+ (a, b) => (priOrder[a.priority] ?? 9) - (priOrder[b.priority] ?? 9),
359
+ );
360
+ if (issues.length) {
361
+ blocks.push("<h3 class=\"font-medium text-slate-800\">核心问题</h3><ul class=\"list-disc pl-5 space-y-1\">");
362
+ for (const ci of issues) {
363
+ blocks.push(
364
+ `<li><span class="font-medium">[${ci.priority}]</span> ${ci.issue} — ${ci.impact}</li>`,
365
+ );
366
+ }
367
+ blocks.push("</ul>");
368
+ }
369
+ const weak = [];
370
+ for (const mod of data.modules || []) {
371
+ for (const it of mod.items || []) {
372
+ const ratio = (Number(it.score) || 0) / (it.totalScore || 1);
373
+ if (ratio < 0.6 && it.suggestion) {
374
+ weak.push({ module: mod.abbreviation, name: it.name, suggestion: it.suggestion, ratio });
375
+ }
376
+ }
377
+ }
378
+ weak.sort((a, b) => a.ratio - b.ratio);
379
+ if (weak.length) {
380
+ blocks.push("<h3 class=\"font-medium text-slate-800 mt-4\">低分子项建议</h3><ol class=\"list-decimal pl-5 space-y-2\">");
381
+ for (const w of weak.slice(0, 12)) {
382
+ blocks.push(`<li><span class="text-slate-600">${w.module} · ${w.name}</span><br/>${w.suggestion}</li>`);
383
+ }
384
+ blocks.push("</ol>");
385
+ }
386
+ return blocks.join("") || "<p class=\"text-slate-500\">暂无待办项。</p>";
387
+ }
388
+
389
+ function longtermCopy(data) {
390
+ const r = getRating(data.score, data.ratingId);
391
+ const lines = [
392
+ `<p><strong>综合评级:${r.level}</strong> — ${r.description}</p>`,
393
+ `<p>当前总分 <strong>${Number(data.score).toFixed(1)}</strong> / 100。`,
394
+ ];
395
+ if (r.id === "s1" || r.id === "s2") {
396
+ lines.push(" 建议:在保持落地页质量的前提下开启或扩大广告投放,并持续监控 Lighthouse 与转化跟踪。</p>");
397
+ } else if (r.id === "s3") {
398
+ lines.push(" 建议:优先处理营销基础、转化路径与加载速度后再加大预算,避免高 CPC 低转化。</p>");
399
+ } else {
400
+ lines.push(" 建议:先完成本报告「优先改进计划」中的高优先级项,再考虑投放;否则预算浪费风险较高。</p>");
401
+ }
402
+ lines.push(
403
+ "<p class=\"mt-2 text-slate-500\">本报告依据页面 HTML 与 Lighthouse 数据生成,未出现的指标请勿臆测。可与 Google 账户分析、广告诊断页配合使用(二者与「网站诊断」数据源不同)。</p>",
404
+ );
405
+ return lines.join("");
406
+ }
407
+
71
408
  function renderReport(data) {
72
- if (!data) return;
73
- document.getElementById("report-url").textContent = data.url || "";
74
- document.getElementById("report-time").textContent = data.analyzedAt || "";
75
- const score = data.score != null ? data.score : "(待汇总)";
76
- document.getElementById("report-score").textContent =
77
- score + " 分 (" + (data.ratingLabel || data.ratingId || "") + ")";
78
- // TODO: 填充 health-overview、core-issues、modules、lighthouse 等(Agent 生成时写完整逻辑或静态 HTML)
409
+ const d = enrichData(data);
410
+ if (!d) return;
411
+
412
+ const urlEl = document.getElementById("report-url");
413
+ if (urlEl && d.url) {
414
+ urlEl.textContent = d.url;
415
+ urlEl.href = d.url;
416
+ }
417
+ document.getElementById("report-time").textContent = d.analyzedAt
418
+ ? new Date(d.analyzedAt).toLocaleString("zh-CN")
419
+ : "—";
420
+ const scoreEl = document.getElementById("report-score");
421
+ scoreEl.textContent = `${Number(d.score).toFixed(1)} 分`;
422
+ scoreEl.style.color = d.ratingColor;
423
+ const ratingEl = document.getElementById("report-rating");
424
+ ratingEl.textContent = `(${d.ratingLabel})`;
425
+ ratingEl.style.color = d.ratingColor;
426
+
427
+ document.getElementById("health-summary").innerHTML =
428
+ `您的网站总评分 <strong>${Number(d.score).toFixed(1)}</strong> 分。${d.ratingDescription}`;
429
+ document.getElementById("score-ring-wrap").innerHTML = renderScoreRing(d.score, d.ratingColor);
430
+ initRadar(document.getElementById("chart-radar"), d.modules);
431
+
432
+ initRiskBar(document.getElementById("chart-risk-bar"), d.modules);
433
+ const ciList = document.getElementById("core-issues-list");
434
+ if (d.coreIssues && d.coreIssues.length) {
435
+ ciList.innerHTML = d.coreIssues
436
+ .map(
437
+ (ci) =>
438
+ `<li class="border-l-4 border-rose-400 pl-3 py-1">
439
+ <span class="font-medium text-rose-700">[${ci.priority}]</span> ${ci.issue}
440
+ <span class="text-slate-500"> — ${ci.impact}</span>
441
+ </li>`,
442
+ )
443
+ .join("");
444
+ } else {
445
+ ciList.innerHTML = '<li class="text-slate-500">未标注核心问题 ID。</li>';
446
+ }
447
+
448
+ const modBox = document.getElementById("modules-container");
449
+ modBox.innerHTML = (d.modules || [])
450
+ .map((mod, idx) => {
451
+ const rows = (mod.items || [])
452
+ .map(
453
+ (it) => `<tr class="border-t border-slate-100">
454
+ <td class="py-2 pr-2">${it.name}</td>
455
+ <td class="py-2 whitespace-nowrap">${it.score ?? 0} / ${it.totalScore}</td>
456
+ <td class="py-2 ${statusClass(it.status)}">${STATUS_ZH[it.status] || it.status || "—"}</td>
457
+ <td class="py-2 text-slate-600 max-w-md">${it.issue || "—"}</td>
458
+ <td class="py-2 text-slate-700 max-w-md">${it.suggestion || "—"}</td>
459
+ </tr>`,
460
+ )
461
+ .join("");
462
+ return `
463
+ <div>
464
+ <h3 class="font-semibold text-slate-900">${idx + 1}. ${mod.name}
465
+ <span class="text-sm font-normal text-slate-500">(${mod.score}/${mod.totalScore})</span>
466
+ </h3>
467
+ <table class="mt-2 w-full text-sm">
468
+ <thead><tr class="text-slate-500 border-b">
469
+ <th class="text-left pb-2">诊断项</th><th class="pb-2">得分</th><th class="pb-2">状态</th>
470
+ <th class="pb-2">问题</th><th class="pb-2">建议</th>
471
+ </tr></thead>
472
+ <tbody>${rows}</tbody>
473
+ </table>
474
+ </div>`;
475
+ })
476
+ .join("");
477
+
478
+ const lh = d.lighthouse;
479
+ const callout = document.getElementById("lighthouse-callout");
480
+ if (d.lighthouseWarning || !lh || (!lh.desktop && !lh.mobile)) {
481
+ callout.classList.remove("hidden");
482
+ callout.textContent =
483
+ d.lighthouseWarning ||
484
+ "未获取 Lighthouse 性能数据(collect 失败或未执行 performance)。加载速度相关子项请依据页面实测说明。";
485
+ } else {
486
+ callout.classList.add("hidden");
487
+ initLighthouseChart(document.getElementById("chart-lighthouse"), lh);
488
+ document.getElementById("lighthouse-tables").innerHTML = lighthouseTable(lh);
489
+ }
490
+
491
+ document.getElementById("priority-plan-body").innerHTML = buildPriorityPlan(d);
492
+ document.getElementById("longterm-value-body").innerHTML = longtermCopy(d);
79
493
  }
80
494
 
81
495
  if (window.__WEBSITE_DIAGNOSIS__) renderReport(window.__WEBSITE_DIAGNOSIS__);
@@ -1,6 +1,6 @@
1
1
  # 网站诊断报告(交付模板)
2
2
 
3
- > **最终交付物:一份可打开的 HTML 文件**(如 `website-diagnosis-report.html`),不是仅 Markdown 摘要。
3
+ > **最终交付物:一份可打开的、带图表的 HTML 文件**(如 `website-diagnosis-report.html`),不是仅 Markdown 摘要。须含:综合得分环、模块雷达图、风险/模块条形图、Lighthouse 对比图(有数据时)。
4
4
  > 数据:`website-diagnosis collect` 落盘 JSON + Agent 按 `assets/website-diagnosis-rules.md` 生成的诊断 JSON(与 tso_agent `getWebsiteDiagnosisData` 的 `data` 同结构)。
5
5
 
6
6
  ---
@@ -25,11 +25,11 @@
25
25
  | 区块 ID / 组件 | 内容 |
26
26
  | -------------- | ---- |
27
27
  | `ReportHeader` | 标题「网站诊断报告」、URL、`analyzedAt`、综合得分与等级色 |
28
- | `HealthOverview` | 总分、行业对比文案、`ratingId` 说明、雷达/概览(可用 ECharts 或静态表) |
29
- | `RiskMap` | `coreIssuesIds` 展开为风险地图/列表(问题、影响、优先级) |
28
+ | `HealthOverview` | 总分、行业对比文案、`ratingId` 说明、**ECharts 雷达图** + 得分环 |
29
+ | `RiskMap` | `coreIssuesIds` 列表 + **模块得分横向条形图**(对齐前端 RiskMap) |
30
30
  | `ModuleDetail` | 6 模块 × 子项:得分、status、issue、suggestion(表格或卡片) |
31
31
  | `PriorityPlan` | 高/中/低优先级改进计划(来自核心问题 + 低分子项) |
32
- | `LoadingSpeed` | `lighthouse` desktop/mobile 指标表;缺失时 callout 说明 |
32
+ | `LoadingSpeed` | **Lighthouse 柱状对比图** + desktop/mobile 指标表;缺失时 callout 说明 |
33
33
  | `LongtermValue` | 投放建议、长期优化价值(与 s1–s5 评级一致) |
34
34
 
35
35
  生成前可对 JSON 做与前端相同的聚合逻辑:各模块 `score` = 子项得分之和,总分 = 各模块之和(参见 `siluzan-front-end-v2` → `useWebsiteGuide/utils` → `parseAIResultData`)。
@@ -41,7 +41,8 @@
41
41
  - 单文件 HTML,`lang` 与用户语言一致(默认 `zh-CN`)。
42
42
  - 可引用 CDN:Tailwind / ECharts(与 `report-template.html` 相同,见该文件头部说明)。
43
43
  - 落盘建议与 `--json-out` 同目录:`website-diagnosis-report.html`。
44
- - **禁止**只交付 Markdown 或纯 JSON 当作最终报告(JSON 可作为附件或中间产物)。
44
+ - **推荐生成方式**:`siluzan-tso website-diagnosis render --data <diagnosis.json> [--collect <collect.json>]`
45
+ - **禁止**只交付 Markdown 或纯 JSON 当作最终报告(JSON 可作为中间产物)。
45
46
 
46
47
  ---
47
48
 
@@ -9,11 +9,11 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-tso-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.1.24'
12
+ $PKG_VERSION = '1.1.25-beta.2'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
- $INSTALL_CMD = 'npm install -g siluzan-tso-cli'
16
- $WEB_BASE = 'https://www.siluzan.com'
15
+ $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
16
+ $WEB_BASE = 'https://www-ci.siluzan.com'
17
17
 
18
18
  # -- Constants ----------------------------------------------------------------
19
19
  $NODE_MAJOR_MIN = 18
@@ -9,11 +9,11 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-tso-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.1.24"
12
+ readonly PKG_VERSION="1.1.25-beta.2"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
- readonly INSTALL_CMD="npm install -g siluzan-tso-cli"
16
- readonly WEB_BASE="https://www.siluzan.com"
15
+ readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
16
+ readonly WEB_BASE="https://www-ci.siluzan.com"
17
17
 
18
18
  # -- Constants ----------------------------------------------------------------
19
19
  readonly NODE_MAJOR_MIN=18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.24",
3
+ "version": "1.1.25-beta.2",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",