galaxy-opc-plugin 0.1.0

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 (46) hide show
  1. package/README.md +166 -0
  2. package/index.ts +145 -0
  3. package/openclaw.plugin.json +17 -0
  4. package/package.json +47 -0
  5. package/skills/basic-crm/SKILL.md +81 -0
  6. package/skills/basic-finance/SKILL.md +100 -0
  7. package/skills/business-monitoring/SKILL.md +120 -0
  8. package/skills/company-lifecycle/SKILL.md +99 -0
  9. package/skills/company-registration/SKILL.md +80 -0
  10. package/skills/finance-tax/SKILL.md +150 -0
  11. package/skills/hr-assistant/SKILL.md +127 -0
  12. package/skills/investment-management/SKILL.md +101 -0
  13. package/skills/legal-assistant/SKILL.md +113 -0
  14. package/skills/media-ops/SKILL.md +101 -0
  15. package/skills/procurement-management/SKILL.md +91 -0
  16. package/skills/project-management/SKILL.md +125 -0
  17. package/src/api/companies.ts +193 -0
  18. package/src/api/dashboard.ts +25 -0
  19. package/src/api/routes.ts +14 -0
  20. package/src/db/index.ts +63 -0
  21. package/src/db/migrations.ts +67 -0
  22. package/src/db/schema.ts +518 -0
  23. package/src/db/sqlite-adapter.ts +366 -0
  24. package/src/opc/company-manager.ts +82 -0
  25. package/src/opc/context-injector.ts +186 -0
  26. package/src/opc/reminder-service.ts +289 -0
  27. package/src/opc/types.ts +330 -0
  28. package/src/opc/workspace-factory.ts +189 -0
  29. package/src/tools/acquisition-tool.ts +150 -0
  30. package/src/tools/asset-package-tool.ts +283 -0
  31. package/src/tools/finance-tool.ts +244 -0
  32. package/src/tools/hr-tool.ts +211 -0
  33. package/src/tools/investment-tool.ts +201 -0
  34. package/src/tools/legal-tool.ts +191 -0
  35. package/src/tools/lifecycle-tool.ts +251 -0
  36. package/src/tools/media-tool.ts +174 -0
  37. package/src/tools/monitoring-tool.ts +207 -0
  38. package/src/tools/opb-tool.ts +193 -0
  39. package/src/tools/opc-tool.ts +206 -0
  40. package/src/tools/procurement-tool.ts +191 -0
  41. package/src/tools/project-tool.ts +203 -0
  42. package/src/tools/schemas.ts +163 -0
  43. package/src/tools/staff-tool.ts +211 -0
  44. package/src/utils/tool-helper.ts +16 -0
  45. package/src/web/config-ui.ts +3501 -0
  46. package/src/web/landing-page.ts +269 -0
@@ -0,0 +1,3501 @@
1
+ /**
2
+ * 星环OPC中心 — 配置管理 Web UI (增强版)
3
+ *
4
+ * 路由: /opc/admin/*
5
+ * 提供仪表盘、公司管理、公司详情、财务总览、监控中心、工具管理六个页面视图
6
+ */
7
+
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import os from "node:os";
11
+ import { spawn } from "node:child_process";
12
+ import https from "node:https";
13
+ import http from "node:http";
14
+ import type { IncomingMessage, ServerResponse } from "node:http";
15
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
16
+ import type { OpcDatabase } from "../db/index.js";
17
+
18
+ const CUSTOM_SKILLS_DIR = path.join(os.homedir(), ".openclaw", "custom-skills");
19
+
20
+ function sendJson(res: ServerResponse, data: unknown, status = 200): void {
21
+ res.writeHead(status, {
22
+ "Content-Type": "application/json; charset=utf-8",
23
+ "Access-Control-Allow-Origin": "*",
24
+ });
25
+ res.end(JSON.stringify(data));
26
+ }
27
+
28
+ function sendHtml(res: ServerResponse, html: string): void {
29
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
30
+ res.end(html);
31
+ }
32
+
33
+ async function readBody(req: IncomingMessage): Promise<string> {
34
+ return new Promise((resolve, reject) => {
35
+ let body = "";
36
+ req.on("data", (chunk: Buffer) => { body += chunk.toString(); });
37
+ req.on("end", () => resolve(body));
38
+ req.on("error", reject);
39
+ });
40
+ }
41
+
42
+ const TOOL_NAMES = [
43
+ { key: "opc_core", label: "核心管理", desc: "公司注册、员工、客户、交易" },
44
+ { key: "opc_finance", label: "财税管理", desc: "发票、增值税、所得税、纳税申报" },
45
+ { key: "opc_legal", label: "法务合同", desc: "合同管理、风险评估、到期提醒" },
46
+ { key: "opc_hr", label: "人力资源", desc: "员工档案、薪资、社保、公积金" },
47
+ { key: "opc_media", label: "新媒体运营", desc: "内容创建、发布排期、数据分析" },
48
+ { key: "opc_project", label: "项目管理", desc: "项目、任务、进度、预算跟踪" },
49
+ { key: "opc_investment", label: "投融资", desc: "融资轮次、投资人、股权结构" },
50
+ { key: "opc_procurement", label: "服务采购", desc: "服务项目、采购订单、费用统计" },
51
+ { key: "opc_lifecycle", label: "生命周期", desc: "里程碑、大事记、时间线、报告" },
52
+ { key: "opc_monitoring", label: "运营监控", desc: "指标记录、告警管理、KPI看板" },
53
+ ];
54
+
55
+ /* ── Helper: month boundaries ─────────────────────────────── */
56
+ function monthBounds(offsetMonths: number): { start: string; end: string } {
57
+ const d = new Date();
58
+ d.setDate(1);
59
+ d.setMonth(d.getMonth() + offsetMonths);
60
+ const start = d.toISOString().slice(0, 10);
61
+ d.setMonth(d.getMonth() + 1);
62
+ d.setDate(0);
63
+ const end = d.toISOString().slice(0, 10);
64
+ return { start, end };
65
+ }
66
+
67
+ function monthLabel(offsetMonths: number): string {
68
+ const d = new Date();
69
+ d.setDate(1);
70
+ d.setMonth(d.getMonth() + offsetMonths);
71
+ return (d.getMonth() + 1) + "月";
72
+ }
73
+
74
+ /* ── API handlers ─────────────────────────────────────────── */
75
+
76
+ interface DashboardRow { total_income: number; total_expense: number }
77
+ interface CountRow { cnt: number }
78
+ interface AmountRow { total: number }
79
+ interface TxRow {
80
+ id: string; company_id: string; type: string; category: string;
81
+ amount: number; description: string; counterparty: string;
82
+ transaction_date: string; created_at: string;
83
+ }
84
+ interface CompanyNameRow { name: string }
85
+ interface AlertRow {
86
+ id: string; company_id: string; title: string; severity: string;
87
+ category: string; status: string; message: string;
88
+ resolved_at: string; created_at: string;
89
+ }
90
+ interface CompanyRow {
91
+ id: string; name: string; industry: string; owner_name: string;
92
+ owner_contact: string; status: string; registered_capital: number;
93
+ description: string; created_at: string; updated_at: string;
94
+ }
95
+ interface InvoiceRow {
96
+ id: string; company_id: string; invoice_number: string; type: string;
97
+ counterparty: string; amount: number; tax_rate: number;
98
+ tax_amount: number; total_amount: number; status: string;
99
+ issue_date: string; notes: string; created_at: string;
100
+ }
101
+ interface TaxRow {
102
+ id: string; company_id: string; period: string; tax_type: string;
103
+ revenue: number; deductible: number; tax_amount: number;
104
+ status: string; due_date: string; filed_date: string;
105
+ notes: string; created_at: string;
106
+ }
107
+ interface HrRow {
108
+ id: string; company_id: string; employee_name: string; position: string;
109
+ salary: number; social_insurance: number; housing_fund: number;
110
+ start_date: string; end_date: string; contract_type: string;
111
+ status: string; notes: string; created_at: string; updated_at: string;
112
+ }
113
+ interface ProjectRow {
114
+ id: string; company_id: string; name: string; description: string;
115
+ status: string; start_date: string; end_date: string;
116
+ budget: number; spent: number; created_at: string; updated_at: string;
117
+ }
118
+ interface TaskRow {
119
+ id: string; project_id: string; company_id: string; title: string;
120
+ description: string; assignee: string; priority: string; status: string;
121
+ due_date: string; hours_estimated: number; hours_actual: number;
122
+ created_at: string; updated_at: string;
123
+ }
124
+ interface ContractRow {
125
+ id: string; company_id: string; title: string; counterparty: string;
126
+ contract_type: string; amount: number; start_date: string;
127
+ end_date: string; status: string; key_terms: string;
128
+ risk_notes: string; reminder_date: string;
129
+ created_at: string; updated_at: string;
130
+ }
131
+ interface RoundRow {
132
+ id: string; company_id: string; round_name: string; amount: number;
133
+ valuation_pre: number; valuation_post: number; status: string;
134
+ lead_investor: string; close_date: string; notes: string; created_at: string;
135
+ }
136
+ interface InvestorRow {
137
+ id: string; round_id: string; company_id: string; name: string;
138
+ type: string; amount: number; equity_percent: number;
139
+ contact: string; notes: string; created_at: string;
140
+ }
141
+ interface MilestoneRow {
142
+ id: string; company_id: string; title: string; category: string;
143
+ target_date: string; completed_date: string; status: string;
144
+ description: string; created_at: string;
145
+ }
146
+ interface LifecycleRow {
147
+ id: string; company_id: string; event_type: string; title: string;
148
+ event_date: string; impact: string; description: string; created_at: string;
149
+ }
150
+ interface MetricRow {
151
+ id: string; company_id: string; name: string; value: number;
152
+ unit: string; category: string; recorded_at: string;
153
+ notes: string; created_at: string;
154
+ }
155
+ interface CategorySum { category: string; total: number }
156
+
157
+ function handleDashboardEnhanced(db: OpcDatabase): unknown {
158
+ const stats = db.getDashboardStats();
159
+
160
+ // Monthly trends (last 6 months)
161
+ const trends: { month: string; income: number; expense: number }[] = [];
162
+ for (let i = -5; i <= 0; i++) {
163
+ const b = monthBounds(i);
164
+ const row = db.queryOne(
165
+ "SELECT COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END),0) as total_income, COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END),0) as total_expense FROM opc_transactions WHERE transaction_date >= ? AND transaction_date <= ?",
166
+ b.start, b.end,
167
+ ) as DashboardRow | null;
168
+ trends.push({
169
+ month: monthLabel(i),
170
+ income: row ? row.total_income : 0,
171
+ expense: row ? row.total_expense : 0,
172
+ });
173
+ }
174
+
175
+ // Expense by category
176
+ const expenseByCategory = db.query(
177
+ "SELECT category, SUM(amount) as total FROM opc_transactions WHERE type='expense' GROUP BY category ORDER BY total DESC",
178
+ ) as CategorySum[];
179
+
180
+ // Active contracts value
181
+ const contractVal = db.queryOne(
182
+ "SELECT COALESCE(SUM(amount),0) as total FROM opc_contracts WHERE status='active'",
183
+ ) as AmountRow;
184
+
185
+ // Active projects count
186
+ const projCount = db.queryOne(
187
+ "SELECT COUNT(*) as cnt FROM opc_projects WHERE status='active'",
188
+ ) as CountRow;
189
+
190
+ // Recent transactions (last 10)
191
+ const recentTx = db.query(
192
+ "SELECT * FROM opc_transactions ORDER BY transaction_date DESC, created_at DESC LIMIT 10",
193
+ ) as TxRow[];
194
+
195
+ // Enrich with company names
196
+ const txWithNames = recentTx.map((tx) => {
197
+ const c = db.queryOne("SELECT name FROM opc_companies WHERE id = ?", tx.company_id) as CompanyNameRow | null;
198
+ return { ...tx, company_name: c ? c.name : "" };
199
+ });
200
+
201
+ // Active alerts
202
+ const alerts = db.query(
203
+ "SELECT * FROM opc_alerts WHERE status='active' ORDER BY CASE severity WHEN 'critical' THEN 0 WHEN 'warning' THEN 1 ELSE 2 END, created_at DESC LIMIT 5",
204
+ ) as AlertRow[];
205
+
206
+ const alertsWithNames = alerts.map((a) => {
207
+ const c = db.queryOne("SELECT name FROM opc_companies WHERE id = ?", a.company_id) as CompanyNameRow | null;
208
+ return { ...a, company_name: c ? c.name : "" };
209
+ });
210
+
211
+ // Month-over-month for current vs last month
212
+ const cur = monthBounds(0);
213
+ const prev = monthBounds(-1);
214
+ const curRow = db.queryOne(
215
+ "SELECT COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END),0) as total_income, COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END),0) as total_expense FROM opc_transactions WHERE transaction_date >= ? AND transaction_date <= ?",
216
+ cur.start, cur.end,
217
+ ) as DashboardRow;
218
+ const prevRow = db.queryOne(
219
+ "SELECT COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END),0) as total_income, COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END),0) as total_expense FROM opc_transactions WHERE transaction_date >= ? AND transaction_date <= ?",
220
+ prev.start, prev.end,
221
+ ) as DashboardRow;
222
+
223
+ // 孵化平台运营方统计(资金闭环视角)
224
+ const incubatorStats = {
225
+ total_companies: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_companies") as { cnt: number }).cnt,
226
+ active_companies: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_companies WHERE status='active'") as { cnt: number }).cnt,
227
+ acquired_companies: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_companies WHERE status='acquired'") as { cnt: number }).cnt,
228
+ total_employees: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_employees") as { cnt: number }).cnt,
229
+ total_revenue: ((db.queryOne("SELECT COALESCE(SUM(amount),0) as total FROM opc_transactions WHERE type='income'") as { total: number }).total),
230
+ financing_fee_income: ((db.queryOne("SELECT COALESCE(SUM(fee_amount),0) as total FROM opc_financing_fees WHERE status='paid'") as { total: number }).total),
231
+ asset_packages: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_asset_packages") as { cnt: number }).cnt,
232
+ sci_loan_facilitated: ((db.queryOne("SELECT COALESCE(SUM(sci_loan_actual),0) as total FROM opc_ct_transfers") as { total: number }).total),
233
+ };
234
+
235
+ return {
236
+ stats,
237
+ trends,
238
+ expenseByCategory,
239
+ activeContractValue: contractVal.total,
240
+ activeProjects: projCount.cnt,
241
+ recentTransactions: txWithNames,
242
+ alerts: alertsWithNames,
243
+ mom: {
244
+ curIncome: curRow.total_income,
245
+ prevIncome: prevRow.total_income,
246
+ curExpense: curRow.total_expense,
247
+ prevExpense: prevRow.total_expense,
248
+ },
249
+ incubator: incubatorStats,
250
+ };
251
+ }
252
+
253
+ function handleCompaniesList(db: OpcDatabase, urlObj: URL): unknown {
254
+ const search = (urlObj.searchParams.get("search") || "").trim();
255
+ const status = urlObj.searchParams.get("status") || "";
256
+ const page = Math.max(1, parseInt(urlObj.searchParams.get("page") || "1", 10));
257
+ const limit = Math.min(100, Math.max(1, parseInt(urlObj.searchParams.get("limit") || "20", 10)));
258
+ const offset = (page - 1) * limit;
259
+
260
+ let countSql = "SELECT COUNT(*) as cnt FROM opc_companies WHERE 1=1";
261
+ let dataSql = "SELECT * FROM opc_companies WHERE 1=1";
262
+ const params: unknown[] = [];
263
+
264
+ if (status) {
265
+ countSql += " AND status = ?";
266
+ dataSql += " AND status = ?";
267
+ params.push(status);
268
+ }
269
+ if (search) {
270
+ const like = "%" + search + "%";
271
+ countSql += " AND (name LIKE ? OR industry LIKE ? OR owner_name LIKE ?)";
272
+ dataSql += " AND (name LIKE ? OR industry LIKE ? OR owner_name LIKE ?)";
273
+ params.push(like, like, like);
274
+ }
275
+
276
+ const countRow = db.queryOne(countSql, ...params) as CountRow;
277
+ const total = countRow.cnt;
278
+
279
+ dataSql += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
280
+ const dataParams = [...params, limit, offset];
281
+ const companies = db.query(dataSql, ...dataParams) as CompanyRow[];
282
+
283
+ // Status counts
284
+ const allCount = (db.queryOne("SELECT COUNT(*) as cnt FROM opc_companies") as CountRow).cnt;
285
+ const activeCount = (db.queryOne("SELECT COUNT(*) as cnt FROM opc_companies WHERE status='active'") as CountRow).cnt;
286
+ const pendingCount = (db.queryOne("SELECT COUNT(*) as cnt FROM opc_companies WHERE status='pending'") as CountRow).cnt;
287
+ const otherCount = allCount - activeCount - pendingCount;
288
+
289
+ return {
290
+ companies,
291
+ total,
292
+ page,
293
+ limit,
294
+ totalPages: Math.ceil(total / limit),
295
+ statusCounts: { all: allCount, active: activeCount, pending: pendingCount, other: otherCount },
296
+ };
297
+ }
298
+
299
+ function handleCompanyDetail(db: OpcDatabase, companyId: string): unknown {
300
+ const company = db.queryOne("SELECT * FROM opc_companies WHERE id = ?", companyId) as CompanyRow | null;
301
+ if (!company) return null;
302
+
303
+ const financeSummary = db.queryOne(
304
+ "SELECT COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END),0) as total_income, COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END),0) as total_expense FROM opc_transactions WHERE company_id = ?",
305
+ companyId,
306
+ ) as DashboardRow;
307
+
308
+ const transactions = db.query(
309
+ "SELECT * FROM opc_transactions WHERE company_id = ? ORDER BY transaction_date DESC LIMIT 50",
310
+ companyId,
311
+ ) as TxRow[];
312
+
313
+ const invoices = db.query(
314
+ "SELECT * FROM opc_invoices WHERE company_id = ? ORDER BY issue_date DESC",
315
+ companyId,
316
+ ) as InvoiceRow[];
317
+
318
+ const taxFilings = db.query(
319
+ "SELECT * FROM opc_tax_filings WHERE company_id = ? ORDER BY due_date DESC",
320
+ companyId,
321
+ ) as TaxRow[];
322
+
323
+ const hrRecords = db.query(
324
+ "SELECT * FROM opc_hr_records WHERE company_id = ? ORDER BY created_at DESC",
325
+ companyId,
326
+ ) as HrRow[];
327
+
328
+ const projects = db.query(
329
+ "SELECT * FROM opc_projects WHERE company_id = ? ORDER BY created_at DESC",
330
+ companyId,
331
+ ) as ProjectRow[];
332
+
333
+ const tasks = db.query(
334
+ "SELECT * FROM opc_tasks WHERE company_id = ? ORDER BY created_at DESC",
335
+ companyId,
336
+ ) as TaskRow[];
337
+
338
+ const contracts = db.query(
339
+ "SELECT * FROM opc_contracts WHERE company_id = ? ORDER BY created_at DESC",
340
+ companyId,
341
+ ) as ContractRow[];
342
+
343
+ const rounds = db.query(
344
+ "SELECT * FROM opc_investment_rounds WHERE company_id = ? ORDER BY created_at DESC",
345
+ companyId,
346
+ ) as RoundRow[];
347
+
348
+ const investors = db.query(
349
+ "SELECT * FROM opc_investors WHERE company_id = ? ORDER BY created_at DESC",
350
+ companyId,
351
+ ) as InvestorRow[];
352
+
353
+ const milestones = db.query(
354
+ "SELECT * FROM opc_milestones WHERE company_id = ? ORDER BY target_date DESC",
355
+ companyId,
356
+ ) as MilestoneRow[];
357
+
358
+ const lifecycleEvents = db.query(
359
+ "SELECT * FROM opc_lifecycle_events WHERE company_id = ? ORDER BY event_date DESC",
360
+ companyId,
361
+ ) as LifecycleRow[];
362
+
363
+ const alerts = db.query(
364
+ "SELECT * FROM opc_alerts WHERE company_id = ? AND status='active' ORDER BY created_at DESC",
365
+ companyId,
366
+ ) as AlertRow[];
367
+
368
+ const contacts = db.query(
369
+ "SELECT * FROM opc_contacts WHERE company_id = ? ORDER BY updated_at DESC",
370
+ companyId,
371
+ );
372
+
373
+ const employees = db.query(
374
+ "SELECT * FROM opc_employees WHERE company_id = ? ORDER BY created_at DESC",
375
+ companyId,
376
+ );
377
+
378
+ // Salary summary
379
+ const salarySum = db.queryOne(
380
+ "SELECT COALESCE(SUM(salary),0) as total_salary, COALESCE(SUM(social_insurance),0) as total_si, COALESCE(SUM(housing_fund),0) as total_hf, COUNT(*) as cnt FROM opc_hr_records WHERE company_id = ? AND status='active'",
381
+ companyId,
382
+ ) as { total_salary: number; total_si: number; total_hf: number; cnt: number };
383
+
384
+ const staffConfig = db.query(
385
+ "SELECT * FROM opc_staff_config WHERE company_id = ? ORDER BY role",
386
+ companyId,
387
+ ) as { id: string; role: string; role_name: string; enabled: number; system_prompt: string; notes: string; created_at: string; updated_at: string }[];
388
+
389
+ const mediaContent = db.query(
390
+ "SELECT id, title, platform, content_type, status, scheduled_date, published_date, metrics, created_at FROM opc_media_content WHERE company_id = ? ORDER BY created_at DESC LIMIT 50",
391
+ companyId,
392
+ ) as { id: string; title: string; platform: string; content_type: string; status: string; scheduled_date: string; published_date: string; metrics: string; created_at: string }[];
393
+
394
+ const procurementOrders = db.query(
395
+ "SELECT o.id, o.title, o.amount, o.status, o.order_date, o.notes, o.created_at, s.name as service_name FROM opc_procurement_orders o LEFT JOIN opc_services s ON o.service_id = s.id WHERE o.company_id = ? ORDER BY o.created_at DESC LIMIT 50",
396
+ companyId,
397
+ ) as { id: string; title: string; service_name: string; amount: number; status: string; order_date: string; notes: string; created_at: string }[];
398
+
399
+ const services = db.query(
400
+ "SELECT * FROM opc_services WHERE company_id = ? ORDER BY status, created_at DESC",
401
+ companyId,
402
+ ) as { id: string; name: string; category: string; provider: string; unit_price: number; billing_cycle: string; status: string; description: string; created_at: string }[];
403
+
404
+ return {
405
+ company,
406
+ finance: {
407
+ income: financeSummary.total_income,
408
+ expense: financeSummary.total_expense,
409
+ net: financeSummary.total_income - financeSummary.total_expense,
410
+ transactions,
411
+ invoices,
412
+ taxFilings,
413
+ },
414
+ hr: { records: hrRecords, salarySummary: salarySum },
415
+ projects: { list: projects, tasks },
416
+ contracts,
417
+ investment: { rounds, investors },
418
+ timeline: { milestones, events: lifecycleEvents },
419
+ alerts,
420
+ contacts,
421
+ employees,
422
+ staffConfig,
423
+ mediaContent,
424
+ procurementOrders,
425
+ services,
426
+ };
427
+ }
428
+
429
+ function handleFinanceOverview(db: OpcDatabase): unknown {
430
+ // 12-month trend
431
+ const trends: { month: string; income: number; expense: number }[] = [];
432
+ for (let i = -11; i <= 0; i++) {
433
+ const b = monthBounds(i);
434
+ const row = db.queryOne(
435
+ "SELECT COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END),0) as total_income, COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END),0) as total_expense FROM opc_transactions WHERE transaction_date >= ? AND transaction_date <= ?",
436
+ b.start, b.end,
437
+ ) as DashboardRow;
438
+ trends.push({ month: monthLabel(i), income: row.total_income, expense: row.total_expense });
439
+ }
440
+
441
+ // Invoice summary
442
+ const invoiceSummary = {
443
+ sales: {
444
+ draft: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='sales' AND status='draft'") as CountRow).cnt,
445
+ issued: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='sales' AND status='issued'") as CountRow).cnt,
446
+ paid: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='sales' AND status='paid'") as CountRow).cnt,
447
+ void: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='sales' AND status='void'") as CountRow).cnt,
448
+ },
449
+ purchase: {
450
+ draft: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='purchase' AND status='draft'") as CountRow).cnt,
451
+ issued: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='purchase' AND status='issued'") as CountRow).cnt,
452
+ paid: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='purchase' AND status='paid'") as CountRow).cnt,
453
+ void: (db.queryOne("SELECT COUNT(*) as cnt FROM opc_invoices WHERE type='purchase' AND status='void'") as CountRow).cnt,
454
+ },
455
+ };
456
+
457
+ // Tax calendar
458
+ const taxFilings = db.query(
459
+ "SELECT t.*, c.name as company_name FROM opc_tax_filings t LEFT JOIN opc_companies c ON t.company_id = c.id ORDER BY CASE t.status WHEN 'pending' THEN 0 WHEN 'filed' THEN 1 ELSE 2 END, t.due_date ASC",
460
+ ) as (TaxRow & { company_name: string })[];
461
+
462
+ return { trends, invoiceSummary, taxFilings };
463
+ }
464
+
465
+ function handleMonitoring(db: OpcDatabase): unknown {
466
+ // Alert counts by severity
467
+ const criticalCount = (db.queryOne("SELECT COUNT(*) as cnt FROM opc_alerts WHERE status='active' AND severity='critical'") as CountRow).cnt;
468
+ const warningCount = (db.queryOne("SELECT COUNT(*) as cnt FROM opc_alerts WHERE status='active' AND severity='warning'") as CountRow).cnt;
469
+ const infoCount = (db.queryOne("SELECT COUNT(*) as cnt FROM opc_alerts WHERE status='active' AND severity='info'") as CountRow).cnt;
470
+
471
+ // All active alerts
472
+ const alerts = db.query(
473
+ "SELECT a.*, c.name as company_name FROM opc_alerts a LEFT JOIN opc_companies c ON a.company_id = c.id WHERE a.status='active' ORDER BY CASE a.severity WHEN 'critical' THEN 0 WHEN 'warning' THEN 1 ELSE 2 END, a.created_at DESC",
474
+ ) as (AlertRow & { company_name: string })[];
475
+
476
+ // Metrics grouped by category
477
+ const latestMetrics = db.query(
478
+ "SELECT m1.* FROM opc_metrics m1 INNER JOIN (SELECT company_id, name, MAX(recorded_at) as max_at FROM opc_metrics GROUP BY company_id, name) m2 ON m1.company_id = m2.company_id AND m1.name = m2.name AND m1.recorded_at = m2.max_at ORDER BY m1.category, m1.name",
479
+ ) as MetricRow[];
480
+
481
+ // Recent 50 metric records
482
+ const recentMetrics = db.query(
483
+ "SELECT m.*, c.name as company_name FROM opc_metrics m LEFT JOIN opc_companies c ON m.company_id = c.id ORDER BY m.recorded_at DESC LIMIT 50",
484
+ ) as (MetricRow & { company_name: string })[];
485
+
486
+ // Metric trend: last 30 days, group by metric name + day
487
+ const metricTrends = db.query(
488
+ `SELECT name, category, unit,
489
+ DATE(recorded_at) as day,
490
+ AVG(value) as avg_value,
491
+ MAX(value) as max_value
492
+ FROM opc_metrics
493
+ WHERE recorded_at >= DATE('now', '-30 days')
494
+ GROUP BY name, DATE(recorded_at)
495
+ ORDER BY name, day`
496
+ ) as { name: string; category: string; unit: string; day: string; avg_value: number; max_value: number }[];
497
+
498
+ return {
499
+ alertCounts: { critical: criticalCount, warning: warningCount, info: infoCount },
500
+ alerts,
501
+ latestMetrics,
502
+ recentMetrics,
503
+ metricTrends,
504
+ };
505
+ }
506
+
507
+ function handleAlertDismiss(db: OpcDatabase, alertId: string): unknown {
508
+ const now = new Date().toISOString();
509
+ const result = db.execute(
510
+ "UPDATE opc_alerts SET status = 'resolved', resolved_at = ? WHERE id = ? AND status = 'active'",
511
+ now, alertId,
512
+ );
513
+ return { ok: result.changes > 0 };
514
+ }
515
+
516
+ /* ── HTML builder ─────────────────────────────────────────── */
517
+
518
+ function buildPageHtml(): string {
519
+ const toolsJson = JSON.stringify(TOOL_NAMES);
520
+ return '<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>' + "\u661F\u73AFOPC\u4E2D\u5FC3 - \u7BA1\u7406\u540E\u53F0" + '</title>\n<link rel="preconnect" href="https://fonts.googleapis.com">\n<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;600;700&display=swap" rel="stylesheet">\n<style>\n' + getCss() + '\n</style>\n</head>\n<body>\n' + getBodyHtml() + '\n<div class="toast" id="toast"></div>\n<script>\nvar TOOLS = ' + toolsJson + ';\n' + getJs() + '\n</script>\n</body>\n</html>';
521
+ }
522
+
523
+ function getCss(): string {
524
+ return ":root{--font:'Instrument Sans','Noto Sans SC',-apple-system,BlinkMacSystemFont,sans-serif;--pri:#0f172a;--pri-l:#334155;--pri-d:#020617;--bg:#fafafa;--card:#ffffff;--tx:#0f172a;--tx2:#6b7280;--tx3:#9ca3af;--bd:#e5e7eb;--ok:#059669;--warn:#d97706;--err:#dc2626;--r:8px;--sh:none;--sh-lg:0 4px 6px -1px rgba(0,0,0,.05)}"
525
+ + "\n*{margin:0;padding:0;box-sizing:border-box}"
526
+ + "\nbody{font-family:var(--font);background:var(--bg);color:var(--tx);min-height:100vh;font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}"
527
+ + "\n@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}"
528
+ // Layout
529
+ + "\n.layout{display:flex;min-height:100vh}"
530
+ // Sidebar — white + right border
531
+ + "\n.sidebar{width:220px;background:var(--card);border-right:1px solid var(--bd);padding:32px 0 24px;flex-shrink:0;position:sticky;top:0;height:100vh;overflow-y:auto}"
532
+ + "\n.sidebar-brand{padding:0 24px 28px;border-bottom:1px solid var(--bd);font-size:16px;font-weight:700;color:var(--tx);letter-spacing:-0.02em;display:flex;align-items:center;gap:10px}"
533
+ + "\n.sidebar-brand svg{flex-shrink:0}"
534
+ + "\n.sidebar-brand small{display:block;font-size:11px;color:var(--tx3);font-weight:400;margin-top:3px;letter-spacing:0.02em;text-transform:uppercase}"
535
+ + "\n.sidebar-nav{padding:20px 12px}"
536
+ + "\n.sidebar-nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;color:var(--tx2);text-decoration:none;border-radius:6px;font-size:13px;font-weight:500;transition:all .15s;margin-bottom:2px;cursor:pointer;border-left:2px solid transparent;position:relative}"
537
+ + "\n.sidebar-nav a:hover{background:#f3f4f6;color:var(--tx)}"
538
+ + "\n.sidebar-nav a.active{background:#f3f4f6;color:var(--tx);font-weight:600;border-left-color:var(--tx)}"
539
+ + "\n.sidebar-nav a .icon{font-size:15px;width:20px;text-align:center;opacity:.6}"
540
+ + "\n.sidebar-nav a.active .icon{opacity:1}"
541
+ // Main
542
+ + "\n.main{flex:1;padding:40px 48px;overflow-y:auto;min-width:0}"
543
+ + "\n.page-header{margin-bottom:32px}"
544
+ + "\n.page-header h1{font-size:22px;font-weight:700;letter-spacing:-0.02em;color:var(--tx)}"
545
+ + "\n.page-header p{color:var(--tx3);font-size:13px;margin-top:6px;font-weight:400}"
546
+ // Stats grid
547
+ + "\n.stats-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px;margin-bottom:28px}"
548
+ + "\n.stat-card{background:var(--card);border-radius:var(--r);padding:24px;border:1px solid var(--bd);transition:box-shadow .2s ease}"
549
+ + "\n.stat-card:hover{box-shadow:var(--sh-lg)}"
550
+ + "\n.stat-card .label{font-size:12px;color:var(--tx3);margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;text-transform:uppercase;letter-spacing:0.04em;font-weight:500}"
551
+ + "\n.stat-card .value{font-size:28px;font-weight:700;color:var(--tx);letter-spacing:-0.02em}"
552
+ + "\n.stat-card .unit{font-size:13px;color:var(--tx3);font-weight:400;letter-spacing:0}"
553
+ + "\n.trend-up{color:var(--ok);font-size:11px;font-weight:600}"
554
+ + "\n.trend-down{color:var(--err);font-size:11px;font-weight:600}"
555
+ // Card
556
+ + "\n.card{background:var(--card);border-radius:var(--r);padding:28px;border:1px solid var(--bd);margin-bottom:20px;transition:box-shadow .2s ease}"
557
+ + "\n.card:hover{box-shadow:var(--sh-lg)}"
558
+ + "\n.card h2{font-size:15px;font-weight:600;margin-bottom:20px;letter-spacing:-0.01em;color:var(--tx)}"
559
+ + "\n.card h3{font-size:13px;font-weight:600;margin-bottom:12px;color:var(--tx2);text-transform:uppercase;letter-spacing:0.03em}"
560
+ + "\n.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px}"
561
+ // Alert banners
562
+ + "\n.alert-banner{padding:12px 16px;border-radius:var(--r);margin-bottom:8px;font-size:13px;display:flex;align-items:center;gap:8px;border:1px solid}"
563
+ + "\n.alert-critical{background:#fef2f2;border-color:#fecaca;color:#991b1b}"
564
+ + "\n.alert-warning{background:#fffbeb;border-color:#fde68a;color:#92400e}"
565
+ + "\n.alert-info{background:#f0f9ff;border-color:#bae6fd;color:#0c4a6e}"
566
+ // Table
567
+ + "\ntable{width:100%;border-collapse:collapse;font-size:13px}"
568
+ + "\nth{text-align:left;padding:12px 16px;color:var(--tx3);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--bd);white-space:nowrap}"
569
+ + "\ntd{padding:14px 16px;border-bottom:none}"
570
+ + "\ntr:nth-child(even) td{background:#f9fafb}"
571
+ + "\ntr:hover td{background:#f3f4f6}"
572
+ + "\ntr.clickable{cursor:pointer}"
573
+ // Badges — line-frame style
574
+ + "\n.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;white-space:nowrap;border:1px solid}"
575
+ + "\n.badge-active,.badge-ok,.badge-paid{background:#f0fdf4;border-color:#86efac;color:#166534}"
576
+ + "\n.badge-pending,.badge-draft{background:#fffbeb;border-color:#fcd34d;color:#92400e}"
577
+ + "\n.badge-suspended,.badge-err,.badge-void,.badge-critical{background:#fef2f2;border-color:#fca5a5;color:#991b1b}"
578
+ + "\n.badge-warning{background:#fff7ed;border-color:#fdba74;color:#9a3412}"
579
+ + "\n.badge-info{background:#f0f9ff;border-color:#7dd3fc;color:#0c4a6e}"
580
+ + "\n.badge-other,.badge-default{background:#f3f4f6;border-color:#d1d5db;color:#4b5563}"
581
+ + "\n.badge-income{background:#f0fdf4;border-color:#86efac;color:#166534}"
582
+ + "\n.badge-expense{background:#fef2f2;border-color:#fca5a5;color:#991b1b}"
583
+ // Search
584
+ + "\n.search-bar{display:flex;gap:8px;margin-bottom:20px}"
585
+ + "\n.search-bar input{flex:1;padding:9px 14px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;outline:none;transition:border-color .15s;font-family:var(--font);background:var(--card)}"
586
+ + "\n.search-bar input:focus{border-color:var(--tx3)}"
587
+ // Status tabs
588
+ + "\n.status-tabs{display:flex;gap:0;margin-bottom:20px;border-bottom:1px solid var(--bd);padding-bottom:0}"
589
+ + "\n.status-tabs button{padding:10px 18px;border:none;background:transparent;font-size:13px;cursor:pointer;color:var(--tx3);border-bottom:2px solid transparent;margin-bottom:-1px;transition:all .15s;font-family:var(--font);font-weight:500}"
590
+ + "\n.status-tabs button.active{color:var(--tx);border-bottom-color:var(--tx);font-weight:600}"
591
+ + "\n.status-tabs button:hover{color:var(--tx2)}"
592
+ // Pagination
593
+ + "\n.pagination{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:20px;font-size:13px;color:var(--tx2)}"
594
+ + "\n.pagination button{padding:7px 14px;border:1px solid var(--bd);background:var(--card);border-radius:var(--r);cursor:pointer;font-size:13px;font-family:var(--font);transition:all .15s}"
595
+ + "\n.pagination button:disabled{opacity:.35;cursor:not-allowed}"
596
+ + "\n.pagination button:hover:not(:disabled){background:#f3f4f6}"
597
+ // Detail header
598
+ + "\n.detail-header{display:flex;align-items:flex-start;gap:24px;margin-bottom:32px}"
599
+ + "\n.detail-header .info{flex:1}"
600
+ + "\n.detail-header .info h1{font-size:22px;font-weight:700;margin-bottom:6px;letter-spacing:-0.02em}"
601
+ + "\n.detail-header .info p{color:var(--tx2);font-size:13px;margin-top:4px;line-height:1.5}"
602
+ + "\n.detail-header .meta{display:flex;gap:20px;margin-top:10px;flex-wrap:wrap}"
603
+ + "\n.detail-header .meta span{font-size:13px;color:var(--tx2)}"
604
+ // Detail tabs
605
+ + "\n.detail-tabs{display:flex;gap:0;margin-bottom:24px;border-bottom:1px solid var(--bd);overflow-x:auto}"
606
+ + "\n.detail-tabs button{padding:10px 16px;border:none;background:transparent;font-size:13px;cursor:pointer;color:var(--tx3);border-bottom:2px solid transparent;margin-bottom:-1px;white-space:nowrap;transition:all .15s;font-family:var(--font);font-weight:500}"
607
+ + "\n.detail-tabs button.active{color:var(--tx);border-bottom-color:var(--tx);font-weight:600}"
608
+ + "\n.detail-tabs button:hover{color:var(--tx2)}"
609
+ // Tab panels
610
+ + "\n.tab-panel{display:none}"
611
+ + "\n.tab-panel.active{display:block;animation:fadeIn .25s ease}"
612
+ // Progress
613
+ + "\n.progress-bar{height:6px;background:#e5e7eb;border-radius:3px;overflow:hidden;flex:1}"
614
+ + "\n.progress-fill{height:100%;border-radius:3px;transition:width .3s}"
615
+ + "\n.progress-green{background:var(--ok)}"
616
+ + "\n.progress-yellow{background:var(--warn)}"
617
+ + "\n.progress-red{background:var(--err)}"
618
+ // Timeline
619
+ + "\n.timeline{position:relative;padding-left:24px}"
620
+ + "\n.timeline::before{content:'';position:absolute;left:8px;top:0;bottom:0;width:1px;background:var(--bd)}"
621
+ + "\n.timeline-item{position:relative;margin-bottom:24px;padding-left:16px}"
622
+ + "\n.timeline-item::before{content:'';position:absolute;left:-20px;top:5px;width:10px;height:10px;border-radius:50%;background:var(--tx3);border:2px solid var(--card)}"
623
+ + "\n.timeline-item .tl-date{font-size:11px;color:var(--tx3);margin-bottom:3px;text-transform:uppercase;letter-spacing:0.03em}"
624
+ + "\n.timeline-item .tl-title{font-weight:600;font-size:14px;color:var(--tx)}"
625
+ + "\n.timeline-item .tl-desc{font-size:13px;color:var(--tx2);margin-top:3px;line-height:1.5}"
626
+ + "\n.timeline-item.milestone::before{background:var(--warn)}"
627
+ // Tool grid
628
+ + "\n.tool-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px}"
629
+ + "\n.tool-card{background:var(--card);border-radius:var(--r);border:1px solid var(--bd);overflow:hidden;transition:box-shadow .2s}"
630
+ + "\n.tool-card:hover{box-shadow:var(--sh-lg)}"
631
+ + "\n.tool-card.disabled{opacity:.55}"
632
+ + "\n.tool-card-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--bd);background:#f9fafb}"
633
+ + "\n.tool-card-header .name{font-weight:600;font-size:14px;color:var(--tx)}"
634
+ + "\n.tool-card-header .key{font-size:11px;color:var(--tx3);font-family:'SF Mono',Consolas,monospace;letter-spacing:0.02em}"
635
+ + "\n.tool-card-body{padding:16px 20px}"
636
+ + "\n.tool-card-body .desc{font-size:13px;color:var(--tx2);margin-bottom:14px;line-height:1.5}"
637
+ + "\n.tool-card-body .field{margin-bottom:14px}"
638
+ + "\n.tool-card-body .field label{display:block;font-size:11px;font-weight:600;color:var(--tx3);margin-bottom:5px;text-transform:uppercase;letter-spacing:0.04em}"
639
+ + "\n.tool-card-body .field select,.tool-card-body .field textarea,.tool-card-body .field input[type=text]{width:100%;padding:8px 12px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);outline:none;transition:border-color .15s;background:var(--card)}"
640
+ + "\n.tool-card-body .field select:focus,.tool-card-body .field textarea:focus,.tool-card-body .field input[type=text]:focus{border-color:var(--tx3)}"
641
+ + "\n.tool-card-body .field textarea{min-height:64px;resize:vertical;line-height:1.5}"
642
+ + "\n.tool-card-footer{padding:12px 20px;border-top:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between;background:#f9fafb}"
643
+ + "\n.tool-expand-btn{background:none;border:none;color:var(--tx2);font-size:12px;cursor:pointer;padding:4px 0;font-family:var(--font);transition:color .15s}"
644
+ + "\n.tool-expand-btn:hover{color:var(--tx)}"
645
+ + "\n.tool-settings{display:none;border-top:1px solid var(--bd);padding:16px 20px;background:#f9fafb}"
646
+ + "\n.tool-settings.open{display:block}"
647
+ // Toggle
648
+ + "\n.toggle{position:relative;width:40px;height:22px;flex-shrink:0}"
649
+ + "\n.toggle input{opacity:0;width:0;height:0}"
650
+ + "\n.toggle .slider{position:absolute;cursor:pointer;inset:0;background:#d1d5db;border-radius:22px;transition:.2s}"
651
+ + "\n.toggle .slider:before{content:'';position:absolute;height:16px;width:16px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}"
652
+ + "\n.toggle input:checked+.slider{background:var(--ok)}"
653
+ + "\n.toggle input:checked+.slider:before{transform:translateX(18px)}"
654
+ // Buttons
655
+ + "\n.btn{padding:7px 16px;border:1px solid var(--bd);background:var(--card);border-radius:var(--r);cursor:pointer;font-size:13px;font-family:var(--font);font-weight:500;transition:all .15s;color:var(--tx)}"
656
+ + "\n.btn:hover{background:#f3f4f6;border-color:#d1d5db}"
657
+ + "\n.btn-sm{padding:5px 12px;font-size:12px}"
658
+ + "\n.btn-pri{background:var(--pri);color:#fff;border-color:var(--pri)}"
659
+ + "\n.btn-pri:hover{background:var(--pri-l);border-color:var(--pri-l)}"
660
+ + "\n.btn-err{background:var(--err);color:#fff;border-color:var(--err)}"
661
+ + "\n.btn-agent{background:#0e7490;color:#fff;border-color:#0e7490;text-decoration:none;display:inline-flex;align-items:center;gap:4px}"
662
+ + "\n.btn-agent:hover{background:#0891b2;border-color:#0891b2;color:#fff}"
663
+ + "\n.btn-agent-lg{padding:8px 18px;font-size:14px;border-radius:8px}"
664
+ + "\n.detail-header-actions{display:flex;align-items:flex-start;padding-top:4px}"
665
+ // SOP guide styles
666
+ + "\n.sop-banner{background:linear-gradient(135deg,#0f172a 0%,#1e3a5f 100%);color:#fff;border-radius:12px;padding:28px 32px;margin-bottom:28px;text-align:center}"
667
+ + "\n.sop-tagline{font-size:28px;font-weight:800;letter-spacing:-0.03em;margin-bottom:8px}"
668
+ + "\n.sop-sub{font-size:14px;opacity:0.75;letter-spacing:0.02em}"
669
+ + "\n.sop-flow{display:flex;flex-direction:column;gap:0}"
670
+ + "\n.sop-step{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:20px 24px;display:flex;gap:20px;align-items:flex-start}"
671
+ + "\n.sop-step-wide{background:var(--card)}"
672
+ + "\n.sop-step-highlight{background:linear-gradient(135deg,#0c4a6e11,#0e749011);border-color:#0e7490}"
673
+ + "\n.sop-step-num{font-size:32px;font-weight:900;color:#0e7490;opacity:0.4;line-height:1;min-width:48px}"
674
+ + "\n.sop-step-body{flex:1;min-width:0}"
675
+ + "\n.sop-step-title{font-size:18px;font-weight:700;margin-bottom:4px}"
676
+ + "\n.sop-step-desc{font-size:13px;color:var(--tx2);margin-bottom:10px}"
677
+ + "\n.sop-step-actions{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}"
678
+ + "\n.sop-tag{background:#0e749018;color:#0e7490;border:1px solid #0e749033;border-radius:4px;padding:2px 8px;font-size:11px;font-family:monospace}"
679
+ + "\n.sop-step-detail{font-size:13px;color:var(--tx2);background:var(--bg);border-radius:8px;padding:12px 16px}"
680
+ + "\n.sop-step-detail ol{margin:6px 0 0 16px;padding:0}"
681
+ + "\n.sop-step-detail li{margin-bottom:4px}"
682
+ + "\n.sop-roles{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}"
683
+ + "\n.sop-roles span{background:var(--card);border:1px solid var(--bd);border-radius:6px;padding:4px 10px;font-size:12px}"
684
+ + "\n.sop-arrow{text-align:center;font-size:20px;color:var(--tx3);padding:4px 0;line-height:1}"
685
+ + "\n.sop-modules{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:12px}"
686
+ + "\n.sop-module{background:var(--bg);border:1px solid var(--bd);border-radius:8px;padding:12px;text-align:center}"
687
+ + "\n.sop-module-icon{font-size:22px;margin-bottom:4px}"
688
+ + "\n.sop-module-name{font-size:13px;font-weight:600;margin-bottom:2px}"
689
+ + "\n.sop-module-tool{font-size:10px;font-family:monospace;color:#0e7490;margin-bottom:2px}"
690
+ + "\n.sop-module-acts{font-size:10px;color:var(--tx3)}"
691
+ + "\n.sop-reminder-tip{margin-top:14px;background:#78350f15;border:1px solid #78350f33;border-radius:8px;padding:10px 14px;font-size:13px;color:#78350f}"
692
+ + "\n.sop-capital-loop{margin-top:16px}"
693
+ + "\n.sop-capital-row{display:grid;align-items:center;gap:0}"
694
+ + "\n.sop-capital-step{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:18px 16px;height:148px;box-sizing:border-box;display:flex;flex-direction:column;gap:4px;transition:box-shadow .2s,border-color .2s}"
695
+ + "\n.sop-capital-step:hover{box-shadow:0 4px 16px rgba(0,0,0,.08);border-color:#93c5fd}"
696
+ + "\n.sop-cap-num{font-size:20px;font-weight:900;color:#2563eb;opacity:0.3;line-height:1}"
697
+ + "\n.sop-cap-title{font-size:14px;font-weight:700;color:var(--tx);margin:4px 0 2px}"
698
+ + "\n.sop-cap-desc{font-size:12px;color:var(--tx2);line-height:1.5;flex:1}"
699
+ + "\n.sop-cap-arrow{display:flex;align-items:center;justify-content:center;padding:0 6px;color:var(--tx3)}"
700
+ + "\n.sop-cap-tag{display:inline-block;background:#eff6ff;color:#2563eb;border:1px solid #bfdbfe;border-radius:4px;padding:2px 7px;font-size:10px;font-family:monospace;font-weight:600;margin-top:2px;width:fit-content}"
701
+ + "\n.sop-quickstart{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:24px;margin-top:24px}"
702
+ + "\n.sop-quickstart h3{font-size:16px;font-weight:700;margin-bottom:16px}"
703
+ + "\n.sop-cmd-list{display:flex;flex-direction:column;gap:10px}"
704
+ + "\n.sop-cmd{display:flex;gap:14px;align-items:flex-start;background:var(--bg);border-radius:8px;padding:12px 14px}"
705
+ + "\n.sop-cmd-num{width:24px;height:24px;background:#0e7490;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0}"
706
+ + "\n.sop-cmd-title{font-size:13px;font-weight:600;margin-bottom:2px}"
707
+ + "\n.sop-cmd-text{font-size:12px;color:var(--tx2);font-family:monospace}"
708
+ // View transitions
709
+ + "\n.view{display:none}"
710
+ + "\n.view.active{display:block;animation:fadeIn .3s ease}"
711
+ // Toast
712
+ + "\n.toast{position:fixed;bottom:24px;right:24px;background:var(--tx);color:#fff;padding:12px 20px;border-radius:var(--r);font-size:13px;font-family:var(--font);opacity:0;transition:opacity .2s;pointer-events:none;z-index:100}"
713
+ + "\n.toast.show{opacity:1}"
714
+ // Skeleton
715
+ + "\n.skeleton{background:linear-gradient(90deg,#e5e7eb 25%,#f3f4f6 50%,#e5e7eb 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:var(--r);min-height:20px}"
716
+ + "\n@keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}"
717
+ // Empty state
718
+ + "\n.empty-state{text-align:center;padding:48px 24px;color:var(--tx3)}"
719
+ + "\n.empty-state .icon{font-size:40px;margin-bottom:12px;opacity:.25}"
720
+ + "\n.empty-state p{font-size:13px}"
721
+ // SVG text
722
+ + "\nsvg text{font-family:var(--font)}"
723
+ + "\n.card svg{max-width:100%}"
724
+ // Back link
725
+ + "\n.back-link{display:inline-flex;align-items:center;gap:6px;color:var(--tx2);text-decoration:none;font-size:13px;margin-bottom:20px;cursor:pointer;font-weight:500;transition:color .15s}"
726
+ + "\n.back-link:hover{color:var(--tx)}"
727
+ // Table overflow
728
+ + "\n.card{overflow-x:auto}"
729
+ // Responsive
730
+ + "\n@media(max-width:1024px){.main{padding:28px 24px}.stats-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}.tool-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}}"
731
+ + "\n@media(max-width:768px){.layout{flex-direction:column}.sidebar{width:100%;padding:12px 0;height:auto;position:relative;border-right:none;border-bottom:1px solid var(--bd)}.sidebar-brand{padding:0 16px 12px;font-size:15px}.sidebar-nav{display:flex;padding:4px 8px;gap:2px;overflow-x:auto;flex-wrap:nowrap}.sidebar-nav a{white-space:nowrap;font-size:12px;padding:6px 10px;gap:6px;border-left:none;border-bottom:2px solid transparent}.sidebar-nav a.active{border-left-color:transparent;border-bottom-color:var(--tx)}.sidebar-nav a .icon{font-size:13px;width:16px}.main{padding:16px}.stats-grid{grid-template-columns:repeat(2,1fr);gap:8px}.stat-card{padding:16px}.stat-card .value{font-size:22px}.grid-2{grid-template-columns:1fr}.detail-header{flex-direction:column}.tool-grid{grid-template-columns:1fr}.page-header h1{font-size:18px}}"
732
+ + "\n.skill-list{display:flex;flex-direction:column;gap:6px}"
733
+ + "\n.skill-item{display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid var(--bd);border-radius:6px;font-size:13px;background:var(--card)}"
734
+ + "\n.skill-badge{font-size:11px;padding:2px 8px;border-radius:20px;font-weight:600}"
735
+ + "\n.badge-builtin{background:#f0fdf4;color:#166534}"
736
+ + "\n.badge-custom{background:#eff6ff;color:#1d4ed8}"
737
+ + "\n.skill-card{background:var(--card);border:1px solid var(--bd);border-radius:var(--r);padding:14px 16px;display:flex;align-items:center;gap:12px}"
738
+ + "\n.skill-card-emoji{font-size:20px;width:32px;text-align:center;flex-shrink:0}"
739
+ + "\n.skill-card-info{flex:1;min-width:0}"
740
+ + "\n.skill-card-name{font-size:13px;font-weight:600;color:var(--tx)}"
741
+ + "\n.skill-card-desc{font-size:12px;color:var(--tx2);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px}"
742
+ + "\n.tab-bar{display:flex;gap:2px;background:var(--bg);border-radius:6px;padding:3px;border:1px solid var(--bd);width:fit-content;margin-bottom:16px}"
743
+ + "\n.tab-bar button{padding:6px 14px;border:none;background:none;border-radius:4px;font-size:13px;cursor:pointer;color:var(--tx2)}"
744
+ + "\n.tab-bar button.active{background:var(--card);color:var(--tx);font-weight:600;box-shadow:0 1px 2px rgba(0,0,0,.06)}"
745
+ + "\n.btn-pdf{background:#1e293b;color:#fff;border:none;padding:6px 14px;border-radius:var(--r);font-size:12px;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:5px}"
746
+ + "\n.btn-pdf:hover{background:#334155}"
747
+ + "\n@media print{"
748
+ + "\n.sidebar,.btn,.btn-pri,.btn-sm,.btn-pdf,button,a.btn{display:none!important}"
749
+ + "\n.layout{display:block}"
750
+ + "\n.main{padding:16px}"
751
+ + "\n.view{display:block!important}"
752
+ + "\n.view:not(.print-target){display:none!important}"
753
+ + "\n.card{break-inside:avoid;box-shadow:none;border:1px solid #e5e7eb}"
754
+ + "\n.stat-card{break-inside:avoid}"
755
+ + "\n}";
756
+ }
757
+
758
+ function getBodyHtml(): string {
759
+ return '<div class="layout">'
760
+ + '<nav class="sidebar">'
761
+ + '<div class="sidebar-brand">'
762
+ + '<svg width="28" height="28" viewBox="0 0 28 28" fill="none"><circle cx="14" cy="14" r="12" stroke="#0f172a" stroke-width="2"/><path d="M9 14h10M14 9v10" stroke="#0f172a" stroke-width="2" stroke-linecap="round"/><circle cx="14" cy="14" r="4" stroke="#0f172a" stroke-width="1.5"/></svg>'
763
+ + '<div>' + "\u661F\u73AFOPC\u4E2D\u5FC3"
764
+ + '<small>' + "\u7BA1\u7406\u540E\u53F0" + '</small>'
765
+ + '</div></div>'
766
+ + '<div class="sidebar-nav">'
767
+ + '<a data-view="dashboard" class="active"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="9" y="1" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="1" y="9" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="9" y="9" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.5"/></svg></span> ' + "\u4EEA\u8868\u76D8" + '</a>'
768
+ + '<a data-view="companies"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="3" width="12" height="12" rx="1" stroke="currentColor" stroke-width="1.5"/><path d="M5 3V1M11 3V1M2 7h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span> ' + "\u516C\u53F8\u7BA1\u7406" + '</a>'
769
+ + '<a data-view="canvas"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="14" height="14" rx="1.5" stroke="currentColor" stroke-width="1.5"/><path d="M5 5h6M5 8h6M5 11h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="11" r="1.5" fill="currentColor"/></svg></span> ' + "OPB \u753B\u5E03" + '</a>'
770
+ + '<a data-view="finance"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 12l3-4 3 2 4-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 14h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span> ' + "\u8D22\u52A1\u603B\u89C8" + '</a>'
771
+ + '<a data-view="monitoring"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3l2 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span> ' + "\u76D1\u63A7\u4E2D\u5FC3" + '</a>'
772
+ + '<a data-view="tools"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="3" stroke="currentColor" stroke-width="1.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span> ' + "\u5DE5\u5177\u7BA1\u7406" + '</a>'
773
+ + '<a data-view="closure"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1L14 4V8C14 11.3 11.3 14.3 8 15C4.7 14.3 2 11.3 2 8V4L8 1Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M5.5 8l1.5 1.5L10.5 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span> ' + "\u8D44\u91D1\u95ED\u73AF" + '</a>'
774
+ + '<a data-view="guide"><span class="icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 2h12v12H2z" stroke="currentColor" stroke-width="1.5" rx="1"/><path d="M5 5h6M5 8h6M5 11h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span> ' + "\u4F7F\u7528\u6307\u5357" + '</a>'
775
+ + '</div>'
776
+ + '</nav>'
777
+ + '<main class="main">'
778
+ + '<div id="view-dashboard" class="view active"><div class="page-header"><h1>' + "\u4EEA\u8868\u76D8" + '</h1><p>' + "\u5E73\u53F0\u6574\u4F53\u8FD0\u8425\u6570\u636E\u6982\u89C8" + '</p></div><div id="dashboard-content"><div class="skeleton" style="height:200px"></div></div></div>'
779
+ + '<div id="view-companies" class="view"><div class="page-header"><h1>' + "\u516C\u53F8\u7BA1\u7406" + '</h1><p>' + "\u641C\u7D22\u3001\u7B5B\u9009\u548C\u7BA1\u7406\u6240\u6709\u6CE8\u518C\u516C\u53F8" + '</p></div><div id="companies-content"><div class="skeleton" style="height:200px"></div></div></div>'
780
+ + '<div id="view-company-detail" class="view"><div id="company-detail-content"><div class="skeleton" style="height:200px"></div></div></div>'
781
+ + '<div id="view-finance" class="view"><div class="page-header" style="display:flex;justify-content:space-between;align-items:flex-start"><div><h1>' + "\u8D22\u52A1\u603B\u89C8" + '</h1><p>' + "\u5E73\u53F0\u6574\u4F53\u8D22\u52A1\u6570\u636E\u5206\u6790" + '</p></div><button class="btn-pdf" onclick="printView(\'finance\')">&#128438; \u5bfc\u51fa PDF</button></div><div id="finance-content"><div class="skeleton" style="height:200px"></div></div></div>'
782
+ + '<div id="view-monitoring" class="view"><div class="page-header"><h1>' + "\u76D1\u63A7\u4E2D\u5FC3" + '</h1><p>' + "\u544A\u8B66\u7BA1\u7406\u4E0E\u8FD0\u8425\u6307\u6807\u76D1\u63A7" + '</p></div><div id="monitoring-content"><div class="skeleton" style="height:200px"></div></div></div>'
783
+ + '<div id="view-tools" class="view"><div class="page-header"><h1>' + "\u5DE5\u5177\u7BA1\u7406" + '</h1><p>' + "\u542F\u7528\u3001\u914D\u7F6E\u5404\u529F\u80FD\u6A21\u5757\uFF0C\u81EA\u5B9A\u4E49\u63D0\u793A\u8BCD\u548C\u4F18\u5148\u7EA7" + '</p></div><div id="tool-list"></div>'
784
+ + '<div id="webhook-section" class="card" style="margin-top:24px"><div class="card-header"><h3 style="margin:0">' + "\u544A\u8B66 Webhook \u63A8\u9001" + '</h3></div><div class="card-body"><p style="color:var(--tx2);font-size:14px;margin-bottom:12px">' + "\u652F\u6301\u98DE\u4E66\u548C\u4F01\u4E1A\u5FAE\u4FE1 Webhook\uFF0C\u5F53\u6709\u65B0\u544A\u8B66\u65F6\u5B9E\u65F6\u63A8\u9001\u901A\u77E5\u3002\u7559\u7A7A\u5219\u4E0D\u63A8\u9001\u3002" + '</p><div style="display:flex;gap:8px;align-items:center"><input id="webhook-url-input" type="url" class="form-input" placeholder="https://open.feishu.cn/..." style="flex:1"><button class="btn btn-primary" onclick="saveWebhookUrl()" style="white-space:nowrap">' + "\u4FDD\u5B58" + '</button><button class="btn" onclick="testWebhook()" style="white-space:nowrap">' + "\u6D4B\u8BD5" + '</button></div></div></div>'
785
+ + '</div>'
786
+ + '<div id="view-guide" class="view"><div id="guide-content"></div></div>'
787
+ + '<div id="view-canvas" class="view"><div class="page-header"><h1>' + "OPB \u4E00\u4EBA\u4F01\u4E1A\u753B\u5E03" + '</h1><p>' + "\u57FA\u4E8E\u300A\u4E00\u4EBA\u4F01\u4E1A\u65B9\u6CD5\u8BBA 2.0\u300B\u7684 16 \u6A21\u5757\u6218\u7565\u753B\u5E03" + '</p></div><div style="margin-bottom:20px"><div style="position:relative;display:inline-block;min-width:260px"><select id="canvas-company-select" onchange="loadCanvas()" style="appearance:none;-webkit-appearance:none;width:100%;padding:10px 40px 10px 16px;font-size:14px;font-weight:500;color:var(--tx);background:var(--card);border:1.5px solid var(--bd);border-radius:8px;cursor:pointer;outline:none;font-family:var(--font);box-shadow:0 1px 3px rgba(0,0,0,.06)"><option value="">' + "\u2014\u00A0\u00A0\u9009\u62E9\u516C\u53F8\u00A0\u00A0\u2014" + '</option></select><span style="pointer-events:none;position:absolute;right:12px;top:50%;transform:translateY(-50%);color:var(--tx2)"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div></div><div id="canvas-content"></div></div>'
788
+ + '<div id="view-closure" class="view"><div class="page-header"><h1>' + "\u8D44\u91D1\u95ED\u73AF" + '</h1><p>' + "\u6536\u5E76\u8D2D\u7BA1\u7406\u3001\u8D44\u4EA7\u5305\u6253\u5305\u3001\u57CE\u6295\u8F6C\u8BA9\u4E0E\u878D\u8D44\u670D\u52A1\u8D39" + '</p></div><div id="closure-content"><div class="skeleton" style="height:200px"></div></div></div>'
789
+ + '</main>'
790
+ + '</div>';
791
+ }
792
+
793
+ function getJs(): string {
794
+ return "if(!localStorage.getItem('openclaw.i18n.locale')){localStorage.setItem('openclaw.i18n.locale','zh-CN');}"
795
+ + "\nvar toolConfig={};"
796
+ + "var companiesState={search:'',status:'',page:1};"
797
+ + "var currentView='dashboard';"
798
+ + "\nfunction esc(s){if(s===null||s===undefined)return '';s=String(s);return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#x27;');}"
799
+ + "\nfunction showView(name){currentView=name;document.querySelectorAll('.view').forEach(function(v){v.classList.remove('active')});document.querySelectorAll('.sidebar-nav a').forEach(function(a){a.classList.remove('active')});var el=document.getElementById('view-'+name);if(el)el.classList.add('active');var nav=document.querySelector('.sidebar-nav a[data-view=\"'+name+'\"]');if(nav)nav.classList.add('active');if(name==='dashboard')loadDashboard();if(name==='companies')loadCompanies();if(name==='finance')loadFinance();if(name==='monitoring')loadMonitoring();if(name==='tools')loadConfig();if(name==='guide')loadGuide();if(name==='canvas')initCanvasView();}"
800
+ + "\nfunction showToast(msg){var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(function(){t.classList.remove('show')},2000);}"
801
+ + "\nfunction fmt(n){if(n>=100000000)return(n/100000000).toFixed(2)+' \\u4ebf';if(n>=10000)return(n/10000).toFixed(1)+' \\u4e07';return n.toLocaleString();}"
802
+ + "\nfunction fmtDate(s){if(!s)return '--';return s.slice(0,10);}"
803
+ + "\nfunction statusBadge(status){var m={'active':'\\u8fd0\\u8425\\u4e2d','pending':'\\u5f85\\u6ce8\\u518c','suspended':'\\u5df2\\u6682\\u505c','terminated':'\\u5df2\\u6ce8\\u9500','acquired':'\\u5df2\\u6536\\u8d2d','packaged':'\\u6253\\u5305\\u4e2d'};var cls=status==='active'?'badge-active':status==='pending'?'badge-pending':status==='suspended'?'badge-suspended':'badge-other';return '<span class=\"badge '+cls+'\">'+(m[status]||esc(status))+'</span>';}"
804
+ + "\nfunction severityBadge(s){var m={'critical':'\\u4e25\\u91cd','warning':'\\u8b66\\u544a','info':'\\u63d0\\u793a'};var c=s==='critical'?'badge-critical':s==='warning'?'badge-warning':'badge-info';return '<span class=\"badge '+c+'\">'+(m[s]||esc(s))+'</span>';}"
805
+ + "\nfunction invoiceStatusBadge(s){var m={'draft':'\\u8349\\u7a3f','issued':'\\u5df2\\u5f00','paid':'\\u5df2\\u4ed8','void':'\\u4f5c\\u5e9f'};var c=s==='paid'?'badge-paid':s==='void'?'badge-void':s==='issued'?'badge-ok':'badge-draft';return '<span class=\"badge '+c+'\">'+(m[s]||esc(s))+'</span>';}"
806
+ + "\nfunction taxStatusBadge(s){var m={'pending':'\\u5f85\\u7533\\u62a5','filed':'\\u5df2\\u7533\\u62a5','paid':'\\u5df2\\u7f34\\u7eb3'};var c=s==='paid'?'badge-paid':s==='filed'?'badge-ok':'badge-draft';return '<span class=\"badge '+c+'\">'+(m[s]||esc(s))+'</span>';}"
807
+ + "\nfunction contractStatusBadge(s){var m={'draft':'\\u8349\\u7a3f','active':'\\u751f\\u6548\\u4e2d','expired':'\\u5df2\\u8fc7\\u671f','terminated':'\\u5df2\\u7ec8\\u6b62','disputed':'\\u4e89\\u8bae\\u4e2d'};var c=s==='active'?'badge-active':s==='draft'?'badge-draft':s==='disputed'?'badge-err':'badge-other';return '<span class=\"badge '+c+'\">'+(m[s]||esc(s))+'</span>';}"
808
+ + "\nfunction projectStatusBadge(s){var m={'planning':'\\u89c4\\u5212\\u4e2d','active':'\\u8fdb\\u884c\\u4e2d','paused':'\\u5df2\\u6682\\u505c','completed':'\\u5df2\\u5b8c\\u6210','cancelled':'\\u5df2\\u53d6\\u6d88'};var c=s==='active'?'badge-active':s==='completed'?'badge-ok':s==='cancelled'?'badge-err':'badge-other';return '<span class=\"badge '+c+'\">'+(m[s]||esc(s))+'</span>';}"
809
+ + "\nfunction trendArrow(cur,prev){if(prev===0)return '';var pct=((cur-prev)/prev*100).toFixed(1);if(cur>prev)return '<span class=\"trend-up\">\\u2191'+pct+'%</span>';if(cur<prev)return '<span class=\"trend-down\">\\u2193'+Math.abs(parseFloat(pct)).toFixed(1)+'%</span>';return '';}"
810
+ + "\nfunction buildSingleBarChart(data,ww,hh,keyX,keyVal,color,label){"
811
+ + "if(!data||!data.length)return '<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6570\\u636e</p></div>';"
812
+ + "var maxV=0;data.forEach(function(d){var v=d[keyVal]||0;if(v>maxV)maxV=v;});"
813
+ + "if(maxV===0)maxV=1;"
814
+ + "var pad=60,bot=30,top_=16,chartW=ww-pad*2,chartH=hh-bot-top_;"
815
+ + "var barW=Math.max(12,Math.floor(chartW/data.length*0.55));"
816
+ + "var s='<svg viewBox=\"0 0 '+ww+' '+hh+'\" style=\"width:100%;max-height:'+hh+'px;display:block\">';"
817
+ + "for(var g=0;g<=4;g++){var gy=top_+chartH-chartH*g/4;var gv=Math.round(maxV*g/4);s+='<line x1=\"'+pad+'\" y1=\"'+gy+'\" x2=\"'+(ww-pad/2)+'\" y2=\"'+gy+'\" stroke=\"#e2e8f0\" stroke-width=\"1\"/>';s+='<text x=\"'+(pad-6)+'\" y=\"'+(gy+4)+'\" text-anchor=\"end\" fill=\"#94a3b8\" font-size=\"10\">'+fmt(gv)+'</text>';}"
818
+ + "data.forEach(function(d,i){var x=pad+i*(chartW/data.length)+(chartW/data.length-barW)/2;var hv=(d[keyVal]||0)/maxV*chartH;"
819
+ + "var grad='grad'+i+keyVal;"
820
+ + "s+='<rect x=\"'+x+'\" y=\"'+(top_+chartH-hv)+'\" width=\"'+barW+'\" height=\"'+hv+'\" fill=\"'+color+'\" rx=\"3\" opacity=\"0.85\"><title>'+esc(d[keyX])+' '+label+': '+fmt(d[keyVal])+'</title></rect>';"
821
+ + "s+='<text x=\"'+(x+barW/2)+'\" y=\"'+(top_+chartH+16)+'\" text-anchor=\"middle\" fill=\"#94a3b8\" font-size=\"11\">'+esc(d[keyX])+'</text>';});"
822
+ + "s+='</svg>';return s;}"
823
+ + "\nfunction buildBarChart(data,ww,hh,keyX,keyA,keyB,colorA,colorB,labelA,labelB){"
824
+ + "if(!data||!data.length)return '<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6570\\u636e</p></div>';"
825
+ + "var maxV=0;data.forEach(function(d){var v=Math.max(d[keyA]||0,d[keyB]||0);if(v>maxV)maxV=v;});"
826
+ + "if(maxV===0)maxV=1;"
827
+ + "var pad=50,bot=30,top_=20,chartW=ww-pad*2,chartH=hh-bot-top_;"
828
+ + "var barW=Math.max(8,Math.floor(chartW/data.length/3));"
829
+ + "var gap=Math.max(4,Math.floor(chartW/data.length)-barW*2);"
830
+ + "var s='<svg viewBox=\"0 0 '+ww+' '+hh+'\" style=\"width:100%;max-height:'+hh+'px;display:block\">';"
831
+ + "for(var g=0;g<=4;g++){var gy=top_+chartH-chartH*g/4;var gv=Math.round(maxV*g/4);s+='<line x1=\"'+pad+'\" y1=\"'+gy+'\" x2=\"'+(ww-pad)+'\" y2=\"'+gy+'\" stroke=\"#e2e8f0\" stroke-width=\"1\"/>';s+='<text x=\"'+(pad-4)+'\" y=\"'+(gy+4)+'\" text-anchor=\"end\" fill=\"#94a3b8\" font-size=\"10\">'+fmt(gv)+'</text>';}"
832
+ + "data.forEach(function(d,i){var x=pad+i*(barW*2+gap)+gap/2;var hA=d[keyA]/maxV*chartH;var hB=d[keyB]/maxV*chartH;"
833
+ + "s+='<rect x=\"'+x+'\" y=\"'+(top_+chartH-hA)+'\" width=\"'+barW+'\" height=\"'+hA+'\" fill=\"'+colorA+'\" rx=\"2\"><title>'+esc(d[keyX])+' '+labelA+': '+fmt(d[keyA])+'</title></rect>';"
834
+ + "s+='<rect x=\"'+(x+barW)+'\" y=\"'+(top_+chartH-hB)+'\" width=\"'+barW+'\" height=\"'+hB+'\" fill=\"'+colorB+'\" rx=\"2\"><title>'+esc(d[keyX])+' '+labelB+': '+fmt(d[keyB])+'</title></rect>';"
835
+ + "s+='<text x=\"'+(x+barW)+'\" y=\"'+(top_+chartH+16)+'\" text-anchor=\"middle\" fill=\"#94a3b8\" font-size=\"11\">'+esc(d[keyX])+'</text>';});"
836
+ + "s+='<rect x=\"'+(ww-pad-140)+'\" y=\"4\" width=\"10\" height=\"10\" fill=\"'+colorA+'\" rx=\"2\"/><text x=\"'+(ww-pad-126)+'\" y=\"13\" fill=\"#64748b\" font-size=\"11\">'+labelA+'</text>';"
837
+ + "s+='<rect x=\"'+(ww-pad-60)+'\" y=\"4\" width=\"10\" height=\"10\" fill=\"'+colorB+'\" rx=\"2\"/><text x=\"'+(ww-pad-46)+'\" y=\"13\" fill=\"#64748b\" font-size=\"11\">'+labelB+'</text>';"
838
+ + "s+='</svg>';return s;}"
839
+ + "\nfunction buildDonutChart(data,size,labelKey,valKey){"
840
+ + "if(!data||!data.length)return '<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6570\\u636e</p></div>';"
841
+ + "var colors=['#0f172a','#64748b','#94a3b8','#cbd5e1','#334155','#475569','#9ca3af','#e2e8f0'];"
842
+ + "var total=0;data.forEach(function(d){total+=d[valKey]||0;});"
843
+ + "if(total===0)return '<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6570\\u636e</p></div>';"
844
+ + "var r=size/2-10,cx=size/2,cy=size/2,circumference=2*Math.PI*r;"
845
+ + "var s='<div style=\"display:flex;align-items:center;gap:20px;flex-wrap:wrap\"><svg width=\"'+size+'\" height=\"'+size+'\" viewBox=\"0 0 '+size+' '+size+'\">';"
846
+ + "var offset=0;"
847
+ + "data.forEach(function(d,i){var pct=(d[valKey]||0)/total;var dash=pct*circumference;var col=colors[i%colors.length];"
848
+ + "s+='<circle cx=\"'+cx+'\" cy=\"'+cy+'\" r=\"'+r+'\" fill=\"none\" stroke=\"'+col+'\" stroke-width=\"20\" stroke-dasharray=\"'+dash+' '+(circumference-dash)+'\" stroke-dashoffset=\"'+(-offset)+'\" transform=\"rotate(-90 '+cx+' '+cy+')\"><title>'+esc(d[labelKey])+': '+fmt(d[valKey])+'</title></circle>';"
849
+ + "offset+=dash;});"
850
+ + "s+='<text x=\"'+cx+'\" y=\"'+(cy-6)+'\" text-anchor=\"middle\" fill=\"#1e293b\" font-size=\"16\" font-weight=\"700\">'+fmt(total)+'</text>';"
851
+ + "s+='<text x=\"'+cx+'\" y=\"'+(cy+12)+'\" text-anchor=\"middle\" fill=\"#94a3b8\" font-size=\"11\">\\u603b\\u989d(\\u5143)</text>';"
852
+ + "s+='</svg><div style=\"display:flex;flex-direction:column;gap:4px\">';"
853
+ + "data.forEach(function(d,i){var col=colors[i%colors.length];var pct=((d[valKey]||0)/total*100).toFixed(1);"
854
+ + "s+='<div style=\"display:flex;align-items:center;gap:6px;font-size:12px\"><span style=\"display:inline-block;width:10px;height:10px;border-radius:2px;background:'+col+'\"></span><span style=\"color:#64748b\">'+esc(d[labelKey])+'</span><span style=\"font-weight:600\">'+fmt(d[valKey])+'</span><span style=\"color:#94a3b8\">('+pct+'%)</span></div>';});"
855
+ + "s+='</div></div>';return s;}"
856
+ + "\nfunction buildLineChart(data,ww,hh,keyX,keyY,color,label){"
857
+ + "if(!data||!data.length)return '<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6570\\u636e</p></div>';"
858
+ + "var maxV=0,minV=Infinity;"
859
+ + "data.forEach(function(d){var v=parseFloat(d[keyY])||0;if(v>maxV)maxV=v;if(v<minV)minV=v;});"
860
+ + "if(maxV===minV){maxV=maxV+1;minV=Math.max(0,minV-1);}"
861
+ + "var pad=50,bot=30,top_=20,chartW=ww-pad*2,chartH=hh-bot-top_;"
862
+ + "var s='<svg viewBox=\"0 0 '+ww+' '+hh+'\" style=\"width:100%;max-height:'+hh+'px;display:block\">';"
863
+ + "for(var g=0;g<=4;g++){var gy=top_+chartH-chartH*g/4;var gv=(minV+(maxV-minV)*g/4).toFixed(1);"
864
+ + "s+='<line x1=\"'+pad+'\" y1=\"'+gy+'\" x2=\"'+(ww-pad)+'\" y2=\"'+gy+'\" stroke=\"#e2e8f0\" stroke-width=\"1\"/>';"
865
+ + "s+='<text x=\"'+(pad-4)+'\" y=\"'+(gy+4)+'\" text-anchor=\"end\" fill=\"#94a3b8\" font-size=\"10\">'+gv+'</text>';}"
866
+ + "var points=data.map(function(d,i){var x=pad+i*chartW/(data.length-1||1);var v=parseFloat(d[keyY])||0;var y=top_+chartH-(v-minV)/(maxV-minV)*chartH;return x+','+y;});"
867
+ + "s+='<polyline points=\"'+points.join(' ')+'\" fill=\"none\" stroke=\"'+color+'\" stroke-width=\"2\" stroke-linejoin=\"round\"/>';"
868
+ + "data.forEach(function(d,i){var x=pad+i*chartW/(data.length-1||1);var v=parseFloat(d[keyY])||0;var y=top_+chartH-(v-minV)/(maxV-minV)*chartH;"
869
+ + "s+='<circle cx=\"'+x+'\" cy=\"'+y+'\" r=\"3\" fill=\"'+color+'\"><title>'+esc(d[keyX])+': '+v+'</title></circle>';});"
870
+ + "var step=Math.ceil(data.length/8);"
871
+ + "data.forEach(function(d,i){if(i%step===0||i===data.length-1){var x=pad+i*chartW/(data.length-1||1);var lbl=d[keyX];if(lbl&&lbl.length>5)lbl=lbl.slice(5);"
872
+ + "s+='<text x=\"'+x+'\" y=\"'+(top_+chartH+16)+'\" text-anchor=\"middle\" fill=\"#94a3b8\" font-size=\"10\">'+esc(lbl)+'</text>';}});"
873
+ + "s+='<rect x=\"'+(ww-pad-60)+'\" y=\"4\" width=\"10\" height=\"10\" fill=\"'+color+'\" rx=\"2\"/>';"
874
+ + "s+='<text x=\"'+(ww-pad-46)+'\" y=\"13\" fill=\"#64748b\" font-size=\"11\">'+label+'</text>';"
875
+ + "s+='</svg>';return s;}"
876
+ + "\nfunction progressBar(spent,budget){"
877
+ + "if(!budget||budget===0)return '<div class=\"progress-bar\"><div class=\"progress-fill progress-green\" style=\"width:0\"></div></div>';"
878
+ + "var pct=Math.min(100,Math.round(spent/budget*100));"
879
+ + "var cls=pct<60?'progress-green':pct<85?'progress-yellow':'progress-red';"
880
+ + "return '<div style=\"display:flex;align-items:center;gap:8px\"><div class=\"progress-bar\"><div class=\"progress-fill '+cls+'\" style=\"width:'+pct+'%\"></div></div><span style=\"font-size:12px;color:#64748b;white-space:nowrap\">'+pct+'%</span></div>';}"
881
+ // ── loadDashboard ──
882
+ + "\nfunction loadDashboard(){"
883
+ + "var el=document.getElementById('dashboard-content');"
884
+ + "el.innerHTML='<div class=\"skeleton\" style=\"height:400px\"></div>';"
885
+ + "fetch('/opc/admin/api/dashboard/enhanced').then(function(r){return r.json()}).then(function(d){"
886
+ + "var h='';"
887
+ // alerts banner
888
+ + "if(d.alerts&&d.alerts.length){"
889
+ + "d.alerts.slice(0,3).forEach(function(a){"
890
+ + "h+='<div class=\"alert-banner alert-'+esc(a.severity)+'\">';"
891
+ + "h+=severityBadge(a.severity)+' ';"
892
+ + "h+='<strong>'+esc(a.company_name||'')+'</strong> '+esc(a.title)+': '+esc(a.message)+'</div>';});}"
893
+ // stat cards
894
+ + "h+='<div class=\"stats-grid\">';"
895
+ + "var cards=["
896
+ + "{l:'\\u516c\\u53f8\\u603b\\u6570',v:d.stats.total_companies,u:'\\u5bb6'},"
897
+ + "{l:'\\u8fd0\\u8425\\u4e2d',v:d.stats.active_companies,u:'\\u5bb6'},"
898
+ + "{l:'\\u603b\\u6536\\u5165',v:fmt(d.stats.total_revenue),u:'\\u5143',t:trendArrow(d.mom.curIncome,d.mom.prevIncome)},"
899
+ + "{l:'\\u603b\\u652f\\u51fa',v:fmt(d.stats.total_expense),u:'\\u5143',t:trendArrow(d.mom.curExpense,d.mom.prevExpense)},"
900
+ + "{l:'\\u4ea4\\u6613\\u7b14\\u6570',v:d.stats.total_transactions,u:'\\u7b14'},"
901
+ + "{l:'\\u5ba2\\u6237\\u6570',v:d.stats.total_contacts,u:'\\u4eba'}"
902
+ + "];"
903
+ + "cards.forEach(function(c){h+='<div class=\"stat-card\"><div class=\"label\">'+c.l+(c.t?' '+c.t:'')+'</div><div class=\"value\">'+c.v+' <span class=\"unit\">'+c.u+'</span></div></div>';});"
904
+ + "h+='</div>';"
905
+ // 孵化平台视角统计
906
+ + "if(d.incubator){"
907
+ + "h+='<div class=\"card\" style=\"margin-bottom:20px\"><div class=\"card-header\"><h3 style=\"margin:0\">\u5b75\u5316\u5e73\u53f0\u8fd0\u8425\u6307\u6807</h3></div><div class=\"card-body\"><div class=\"stats-grid\" style=\"grid-template-columns:repeat(4,1fr)\">';"
908
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\u5df2\u5b75\u5316\u516c\u53f8</div><div class=\"value\">'+d.incubator.total_companies+' <span class=\"unit\">\u5bb6</span></div><div style=\"font-size:12px;color:var(--tx2);margin-top:4px\">\u8fd0\u8425\u4e2d '+d.incubator.active_companies+' | \u5df2\u6536\u8d2d '+d.incubator.acquired_companies+'</div></div>';"
909
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\u5c31\u4e1a\u5c97\u4f4d\u4f30\u7b97</div><div class=\"value\">'+d.incubator.total_employees+' <span class=\"unit\">\u4eba</span></div></div>';"
910
+ + "h+='<div class=\"stat-card\" style=\"border-left:4px solid var(--accent,#0ea5e9)\"><div class=\"label\">\u878d\u8d44\u670d\u52a1\u8d39\u6536\u5165</div><div class=\"value\" style=\"color:var(--accent,#0ea5e9)\">\uFFE5'+fmt(d.incubator.financing_fee_income)+'</div></div>';"
911
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\u52a9\u529b\u57ce\u6295\u79d1\u521b\u8d37</div><div class=\"value\">\uFFE5'+fmt(d.incubator.sci_loan_facilitated)+'</div></div>';"
912
+ + "h+='</div></div></div>';}"
913
+ // charts
914
+ + "h+='<div class=\"grid-2\">';"
915
+ + "h+='<div class=\"card\"><h2>\\u6536\\u652f\\u8d8b\\u52bf (\\u8fd1 6 \\u4e2a\\u6708)</h2>'+buildBarChart(d.trends,500,240,'month','income','expense','#0f172a','#d1d5db','\\u6536\\u5165','\\u652f\\u51fa')+'</div>';"
916
+ + "h+='<div class=\"card\"><h2>\\u652f\\u51fa\\u5206\\u7c7b</h2>'+buildDonutChart(d.expenseByCategory,180,'category','total')+'</div>';"
917
+ + "h+='</div>';"
918
+ // recent transactions
919
+ + "h+='<div class=\"card\"><h2>\\u8fd1\\u671f\\u4ea4\\u6613</h2>';"
920
+ + "if(d.recentTransactions&&d.recentTransactions.length){"
921
+ + "h+='<table><thead><tr><th>\\u516c\\u53f8</th><th>\\u7c7b\\u578b</th><th>\\u5206\\u7c7b</th><th>\\u91d1\\u989d</th><th>\\u5bf9\\u65b9</th><th>\\u65e5\\u671f</th></tr></thead><tbody>';"
922
+ + "d.recentTransactions.forEach(function(tx){"
923
+ + "h+='<tr><td>'+esc(tx.company_name)+'</td><td><span class=\"badge badge-'+(tx.type==='income'?'income':'expense')+'\">'+(tx.type==='income'?'\\u6536\\u5165':'\\u652f\\u51fa')+'</span></td><td>'+esc(tx.category)+'</td><td style=\"font-weight:600\">'+(tx.type==='income'?'+':'-')+fmt(tx.amount)+' \\u5143</td><td>'+esc(tx.counterparty)+'</td><td>'+fmtDate(tx.transaction_date)+'</td></tr>';});"
924
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><div class=\"icon\">\\ud83d\\udcb3</div><p>\\u6682\\u65e0\\u4ea4\\u6613\\u8bb0\\u5f55</p></div>';}"
925
+ + "h+='</div>';"
926
+ + "el.innerHTML=h;"
927
+ + "}).catch(function(e){el.innerHTML='<div class=\"card\"><div class=\"empty-state\"><div class=\"icon\">\\u26a0\\ufe0f</div><p>\\u52a0\\u8f7d\\u5931\\u8d25: '+esc(String(e))+'</p></div></div>';});}"
928
+ // ── loadCompanies ──
929
+ + "\nfunction loadCompanies(){"
930
+ + "var el=document.getElementById('companies-content');"
931
+ + "el.innerHTML='<div class=\"skeleton\" style=\"height:400px\"></div>';"
932
+ + "var q='?page='+companiesState.page+'&limit=20';"
933
+ + "if(companiesState.status)q+='&status='+encodeURIComponent(companiesState.status);"
934
+ + "if(companiesState.search)q+='&search='+encodeURIComponent(companiesState.search);"
935
+ + "fetch('/opc/admin/api/companies/list'+q).then(function(r){return r.json()}).then(function(d){"
936
+ + "var h='';"
937
+ + "h+='<div class=\"search-bar\"><input type=\"text\" id=\"company-search\" placeholder=\"\\u641c\\u7d22\\u516c\\u53f8\\u540d\\u79f0/\\u884c\\u4e1a/\\u8d1f\\u8d23\\u4eba...\" value=\"'+esc(companiesState.search)+'\" onkeydown=\"if(event.key===\\'Enter\\')doCompanySearch()\"/><button class=\"btn btn-pri\" onclick=\"doCompanySearch()\">\\u641c\\u7d22</button><a class=\"btn\" href=\"/opc/admin/api/export/companies\" download>\\u5bfc\\u51fa CSV</a></div>';"
938
+ + "h+='<div class=\"status-tabs\">';"
939
+ + "var tabs=[{k:'',l:'\\u5168\\u90e8('+d.statusCounts.all+')'},{k:'active',l:'\\u8fd0\\u8425\\u4e2d('+d.statusCounts.active+')'},{k:'pending',l:'\\u5f85\\u6ce8\\u518c('+d.statusCounts.pending+')'},{k:'__other',l:'\\u5176\\u4ed6('+d.statusCounts.other+')'}];"
940
+ + "tabs.forEach(function(t){h+='<button class=\"'+(companiesState.status===t.k?'active':'')+'\" onclick=\"filterByStatus(\\''+t.k+'\\')\">'+ t.l+'</button>';});"
941
+ + "h+='</div>';"
942
+ // table
943
+ + "h+='<div class=\"card\">';"
944
+ + "if(d.companies&&d.companies.length){"
945
+ + "h+='<table><thead><tr><th>\\u540d\\u79f0</th><th>\\u884c\\u4e1a</th><th>\\u8d1f\\u8d23\\u4eba</th><th>\\u6ce8\\u518c\\u8d44\\u672c</th><th>\\u72b6\\u6001</th><th>\\u521b\\u5efa\\u65f6\\u95f4</th><th>\\u64cd\\u4f5c</th></tr></thead><tbody>';"
946
+ + "d.companies.forEach(function(c){"
947
+ + "var agentUrl=window.location.protocol+'//'+window.location.host+'/chat?session='+encodeURIComponent('agent:opc-'+c.id+':main');"
948
+ + "h+='<tr class=\"clickable\" onclick=\"showCompany(\\''+esc(c.id)+'\\')\"><td><strong>'+esc(c.name)+'</strong></td><td>'+esc(c.industry)+'</td><td>'+esc(c.owner_name)+'</td><td>'+fmt(c.registered_capital)+' \\u5143</td><td>'+statusBadge(c.status)+'</td><td>'+fmtDate(c.created_at)+'</td><td style=\"white-space:nowrap\"><button class=\"btn btn-sm\" onclick=\"event.stopPropagation();showCompany(\\''+esc(c.id)+'\\')\">' + '\\u8be6\\u60c5' + '</button> <a class=\"btn btn-sm btn-agent\" href=\"'+agentUrl+'\" onclick=\"event.stopPropagation()\" title=\"\\u8fdb\\u5165\\u516c\\u53f8 AI \\u52a9\\u624b\">\\ud83e\\udd16 \\u5bf9\\u8bdd</a> <button class=\"btn btn-sm\" style=\"color:#dc2626;border-color:#fca5a5\" onclick=\"event.stopPropagation();deleteCompany(\\''+esc(c.id)+'\\',\\''+esc(c.name)+'\\')\">\\u5220\\u9664</button></td></tr>';});"
949
+ + "h+='</tbody></table>';"
950
+ + "if(d.totalPages>1){"
951
+ + "h+='<div class=\"pagination\"><button '+(d.page<=1?'disabled':'')+' onclick=\"goPage('+(d.page-1)+')\">\\u4e0a\\u4e00\\u9875</button><span>\\u7b2c '+d.page+' / '+d.totalPages+' \\u9875 (\\u5171 '+d.total+' \\u6761)</span><button '+(d.page>=d.totalPages?'disabled':'')+' onclick=\"goPage('+(d.page+1)+')\">\\u4e0b\\u4e00\\u9875</button></div>';}"
952
+ + "}else{h+='<div class=\"empty-state\"><div class=\"icon\">\\ud83c\\udfe2</div><p>\\u6682\\u65e0\\u516c\\u53f8\\u6570\\u636e</p></div>';}"
953
+ + "h+='</div>';"
954
+ + "el.innerHTML=h;"
955
+ + "}).catch(function(e){el.innerHTML='<div class=\"card\"><div class=\"empty-state\"><p>\\u52a0\\u8f7d\\u5931\\u8d25</p></div></div>';});}"
956
+ + "\nfunction doCompanySearch(){var inp=document.getElementById('company-search');companiesState.search=inp?inp.value:'';companiesState.page=1;loadCompanies();}"
957
+ + "\nfunction deleteCompany(id,name){"
958
+ + "var existing=document.getElementById('confirm-modal');if(existing)existing.remove();"
959
+ + "var modal=document.createElement('div');"
960
+ + "modal.id='confirm-modal';"
961
+ + "modal.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px)';"
962
+ + "modal.innerHTML="
963
+ + "'<div style=\"background:var(--card);border-radius:12px;padding:28px 32px;max-width:420px;width:calc(100% - 32px);box-shadow:0 20px 60px rgba(0,0,0,.18);border:1px solid var(--bd)\">'"
964
+ + "+'<div style=\"display:flex;align-items:center;gap:12px;margin-bottom:16px\">'"
965
+ + "+'<div style=\"width:40px;height:40px;border-radius:50%;background:#fef2f2;display:flex;align-items:center;justify-content:center;flex-shrink:0\">'"
966
+ + "+'<svg width=\"18\" height=\"18\" viewBox=\"0 0 16 16\" fill=\"none\"><path d=\"M8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2zm0 3.5v3m0 2v.5\" stroke=\"#dc2626\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>'"
967
+ + "+'</div>'"
968
+ + "+'<h3 style=\"margin:0;font-size:16px;font-weight:600;color:var(--tx)\">\u5220\u9664\u516c\u53f8</h3>'"
969
+ + "+'</div>'"
970
+ + "+'<p style=\"margin:0 0 8px;font-size:14px;color:var(--tx);line-height:1.6\">\u786e\u5b9a\u8981\u5220\u9664\u516c\u53f8\u300c<strong>'+name+'</strong>\u300d\u5417\uff1f</p>'"
971
+ + "+'<p style=\"margin:0 0 24px;font-size:13px;color:#dc2626;line-height:1.6\">\u8be5\u516c\u53f8\u7684\u6240\u6709\u76f8\u5173\u6570\u636e\uff08\u8d22\u52a1\u3001\u5408\u540c\u3001\u5458\u5de5\u3001\u9879\u76ee\u7b49\uff09\u5c06\u88ab\u6c38\u4e45\u5220\u9664\uff0c\u4e0d\u53ef\u6062\u590d\u3002</p>'"
972
+ + "+'<div style=\"display:flex;gap:8px;justify-content:flex-end\">'"
973
+ + "+'<button id=\"confirm-cancel\" class=\"btn\" style=\"min-width:72px\">\u53d6\u6d88</button>'"
974
+ + "+'<button id=\"confirm-ok\" class=\"btn btn-pri\" style=\"min-width:72px;background:#dc2626;border-color:#dc2626\">\u786e\u5b9a\u5220\u9664</button>'"
975
+ + "+'</div>'"
976
+ + "+'</div>';"
977
+ + "document.body.appendChild(modal);"
978
+ + "modal.addEventListener('click',function(e){if(e.target===modal)modal.remove();});"
979
+ + "document.getElementById('confirm-cancel').onclick=function(){modal.remove();};"
980
+ + "document.getElementById('confirm-ok').onclick=function(){"
981
+ + "modal.remove();"
982
+ + "fetch('/opc/admin/api/companies/'+encodeURIComponent(id),{method:'DELETE'})"
983
+ + ".then(function(r){return r.json()}).then(function(d){"
984
+ + "if(d.ok){showToast('\u516c\u53f8\u300c'+name+'\u300d\u5df2\u5220\u9664');loadCompanies();}"
985
+ + "else{showToast('\u5220\u9664\u5931\u8d25: '+(d.error||'\u672a\u77e5\u9519\u8bef'));}"
986
+ + "}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});"
987
+ + "};}"
988
+ + "\nfunction filterByStatus(s){companiesState.status=s;companiesState.page=1;loadCompanies();}"
989
+ + "\nfunction goPage(p){companiesState.page=p;loadCompanies();}"
990
+ // ── showCompany ──
991
+ + "\nfunction showCompany(id){"
992
+ + "currentView='company-detail';"
993
+ + "document.querySelectorAll('.view').forEach(function(v){v.classList.remove('active')});"
994
+ + "document.getElementById('view-company-detail').classList.add('active');"
995
+ + "document.querySelectorAll('.sidebar-nav a').forEach(function(a){a.classList.remove('active')});"
996
+ + "var nav=document.querySelector('.sidebar-nav a[data-view=\"companies\"]');if(nav)nav.classList.add('active');"
997
+ + "window.location.hash='company/'+id;"
998
+ + "window.currentCompanyId=id;"
999
+ + "var el=document.getElementById('company-detail-content');"
1000
+ + "el.innerHTML='<div class=\"skeleton\" style=\"height:400px\"></div>';"
1001
+ + "fetch('/opc/admin/api/companies/'+encodeURIComponent(id)+'/detail').then(function(r){return r.json()}).then(function(d){"
1002
+ + "if(!d||!d.company){el.innerHTML='<div class=\"card\"><div class=\"empty-state\"><p>\\u516c\\u53f8\\u4e0d\\u5b58\\u5728</p></div></div>';return;}"
1003
+ + "var c=d.company;var h='';"
1004
+ + "var agentChatUrl=window.location.protocol+'//'+window.location.host+'/chat?session='+encodeURIComponent('agent:opc-'+c.id+':main');"
1005
+ + "h+='<a class=\"back-link\" onclick=\"showView(\\'companies\\')\">\\u2190 \\u8fd4\\u56de\\u516c\\u53f8\\u5217\\u8868</a>';"
1006
+ // header
1007
+ + "h+='<div class=\"detail-header\"><div class=\"info\"><h1>'+esc(c.name)+' '+statusBadge(c.status)+'</h1>';"
1008
+ + "h+='<div class=\"meta\"><span>\\u884c\\u4e1a: '+esc(c.industry)+'</span><span>\\u8d1f\\u8d23\\u4eba: '+esc(c.owner_name)+'</span><span>\\u6ce8\\u518c\\u8d44\\u672c: '+fmt(c.registered_capital)+' \\u5143</span></div>';"
1009
+ + "if(c.description)h+='<p style=\"margin-top:8px;color:#64748b;font-size:13px\">'+esc(c.description)+'</p>';"
1010
+ + "h+='</div><div class=\"detail-header-actions\" style=\"display:flex;gap:8px\"><button class=\"btn\" onclick=\"editCompany(\\''+c.id+'\\',\\''+esc(c.name)+'\\',\\''+esc(c.industry)+'\\',\\''+esc(c.owner_name)+'\\',\\''+esc(c.owner_contact)+'\\',\\''+esc(c.description||'')+'\\',\\''+esc(c.registered_capital)+'\\',\\''+esc(c.status)+'\\')\">\\u2712 \\u7f16\\u8f91</button><a class=\"btn btn-agent btn-agent-lg\" href=\"'+agentChatUrl+'\">\\ud83e\\udd16 \\u8fdb\\u5165 AI \\u52a9\\u624b</a></div></div>';"
1011
+ // tabs
1012
+ + "h+='<div class=\"detail-tabs\" id=\"detail-tabs\">';"
1013
+ + "var tabNames=[{k:'overview',l:'\\u6982\\u89c8'},{k:'finance',l:'\\u8d22\\u52a1'},{k:'team',l:'\\u56e2\\u961f'},{k:'projects',l:'\\u9879\\u76ee'},{k:'contracts',l:'\\u5408\\u540c'},{k:'investment',l:'\\u6295\\u878d\\u8d44'},{k:'timeline',l:'\\u65f6\\u95f4\\u7ebf'},{k:'staff',l:'AI\\u5458\\u5de5'},{k:'media',l:'\\u65b0\\u5a92\\u4f53'},{k:'procurement',l:'\\u91c7\\u8d2d'}];"
1014
+ + "tabNames.forEach(function(t,i){h+='<button class=\"'+(i===0?'active':'')+'\" onclick=\"switchDetailTab(\\''+t.k+'\\')\">'+ t.l+'</button>';});"
1015
+ + "h+='</div>';"
1016
+ // ── Tab: overview ──
1017
+ + "h+='<div class=\"tab-panel active\" id=\"dtab-overview\">';"
1018
+ + "h+='<div class=\"stats-grid\">';"
1019
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u603b\\u6536\\u5165</div><div class=\"value\">'+fmt(d.finance.income)+' <span class=\"unit\">\\u5143</span></div></div>';"
1020
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u603b\\u652f\\u51fa</div><div class=\"value\">'+fmt(d.finance.expense)+' <span class=\"unit\">\\u5143</span></div></div>';"
1021
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u51c0\\u5229\\u6da6</div><div class=\"value\">'+fmt(d.finance.net)+' <span class=\"unit\">\\u5143</span></div></div>';"
1022
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5458\\u5de5</div><div class=\"value\">'+(d.hr.salarySummary.cnt||0)+' <span class=\"unit\">\\u4eba</span></div></div>';"
1023
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5408\\u540c</div><div class=\"value\">'+(d.contracts?d.contracts.length:0)+' <span class=\"unit\">\\u4efd</span></div></div>';"
1024
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u9879\\u76ee</div><div class=\"value\">'+(d.projects.list?d.projects.list.length:0)+' <span class=\"unit\">\\u4e2a</span></div></div>';"
1025
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u544a\\u8b66</div><div class=\"value\">'+(d.alerts?d.alerts.length:0)+' <span class=\"unit\">\\u6761</span></div></div>';"
1026
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5ba2\\u6237</div><div class=\"value\">'+(d.contacts?d.contacts.length:0)+' <span class=\"unit\">\\u4eba</span></div></div>';"
1027
+ + "h+='</div>';"
1028
+ // timeline preview
1029
+ + "h+='<div class=\"card\"><h2>\\u65f6\\u95f4\\u7ebf\\u9884\\u89c8</h2>';"
1030
+ + "var tlItems=[];"
1031
+ + "if(d.timeline.milestones)d.timeline.milestones.slice(0,3).forEach(function(m){tlItems.push({date:m.target_date||m.created_at,title:m.title,desc:m.description,type:'milestone'})});"
1032
+ + "if(d.timeline.events)d.timeline.events.slice(0,3).forEach(function(e){tlItems.push({date:e.event_date||e.created_at,title:e.title,desc:e.description,type:'event'})});"
1033
+ + "tlItems.sort(function(a,b){return b.date>a.date?1:-1});"
1034
+ + "if(tlItems.length){h+='<div class=\"timeline\">';tlItems.slice(0,5).forEach(function(t){h+='<div class=\"timeline-item '+(t.type==='milestone'?'milestone':'')+'\"><div class=\"tl-date\">'+fmtDate(t.date)+'</div><div class=\"tl-title\">'+esc(t.title)+'</div><div class=\"tl-desc\">'+esc(t.desc)+'</div></div>';});h+='</div>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u65f6\\u95f4\\u7ebf\\u6570\\u636e</p></div>';}"
1035
+ + "h+='</div>';"
1036
+ + "h+='</div>';"
1037
+ // ── Tab: finance ──
1038
+ + "h+='<div class=\"tab-panel\" id=\"dtab-finance\">';"
1039
+ + "h+='<div class=\"stats-grid\"><div class=\"stat-card\"><div class=\"label\">\\u6536\\u5165</div><div class=\"value\" style=\"color:var(--ok)\">'+fmt(d.finance.income)+'</div></div><div class=\"stat-card\"><div class=\"label\">\\u652f\\u51fa</div><div class=\"value\" style=\"color:var(--err)\">'+fmt(d.finance.expense)+'</div></div><div class=\"stat-card\"><div class=\"label\">\\u51c0\\u5229\\u6da6</div><div class=\"value\">'+fmt(d.finance.net)+'</div></div></div>';"
1040
+ // transactions table
1041
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\"><h2 style=\"margin:0\">\\u4ea4\\u6613\\u8bb0\\u5f55</h2><div style=\"display:flex;gap:8px\"><button class=\"btn btn-pri btn-sm\" onclick=\"addTransaction(\\''+c.id+'\\')\">' + '+ \\u65b0\\u589e\\u4ea4\\u6613' + '</button><a class=\"btn btn-sm\" href=\"/opc/admin/api/export/transactions?company_id='+encodeURIComponent(c.id)+'\" download>\\u5bfc\\u51fa CSV</a></div></div>';"
1042
+ + "if(d.finance.transactions&&d.finance.transactions.length){"
1043
+ + "h+='<table><thead><tr><th>\\u7c7b\\u578b</th><th>\\u5206\\u7c7b</th><th>\\u91d1\\u989d</th><th>\\u5bf9\\u65b9</th><th>\\u63cf\\u8ff0</th><th>\\u65e5\\u671f</th></tr></thead><tbody>';"
1044
+ + "d.finance.transactions.forEach(function(tx){h+='<tr><td><span class=\"badge badge-'+(tx.type==='income'?'income':'expense')+'\">'+(tx.type==='income'?'\\u6536\\u5165':'\\u652f\\u51fa')+'</span></td><td>'+esc(tx.category)+'</td><td style=\"font-weight:600\">'+fmt(tx.amount)+' \\u5143</td><td>'+esc(tx.counterparty)+'</td><td>'+esc(tx.description)+'</td><td>'+fmtDate(tx.transaction_date)+'</td></tr>';});"
1045
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u4ea4\\u6613</p></div>';}"
1046
+ + "h+='</div>';"
1047
+ // invoices
1048
+ + "h+='<div class=\"card\"><h2>\\u53d1\\u7968\\u5217\\u8868</h2>';"
1049
+ + "if(d.finance.invoices&&d.finance.invoices.length){"
1050
+ + "h+='<table><thead><tr><th>\\u53d1\\u7968\\u53f7</th><th>\\u7c7b\\u578b</th><th>\\u5bf9\\u65b9</th><th>\\u91d1\\u989d</th><th>\\u7a0e\\u989d</th><th>\\u72b6\\u6001</th><th>\\u65e5\\u671f</th></tr></thead><tbody>';"
1051
+ + "d.finance.invoices.forEach(function(inv){h+='<tr><td>'+esc(inv.invoice_number)+'</td><td>'+(inv.type==='sales'?'\\u9500\\u9879':'\\u8fdb\\u9879')+'</td><td>'+esc(inv.counterparty)+'</td><td>'+fmt(inv.total_amount)+' \\u5143</td><td>'+fmt(inv.tax_amount)+' \\u5143</td><td>'+invoiceStatusBadge(inv.status)+'</td><td>'+fmtDate(inv.issue_date)+'</td></tr>';});"
1052
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u53d1\\u7968</p></div>';}"
1053
+ + "h+='</div>';"
1054
+ // tax filings
1055
+ + "h+='<div class=\"card\"><h2>\\u7a0e\\u52a1\\u7533\\u62a5</h2>';"
1056
+ + "if(d.finance.taxFilings&&d.finance.taxFilings.length){"
1057
+ + "h+='<table><thead><tr><th>\\u671f\\u95f4</th><th>\\u7a0e\\u79cd</th><th>\\u6536\\u5165</th><th>\\u7a0e\\u989d</th><th>\\u72b6\\u6001</th><th>\\u622a\\u6b62\\u65e5</th></tr></thead><tbody>';"
1058
+ + "d.finance.taxFilings.forEach(function(tf){h+='<tr><td>'+esc(tf.period)+'</td><td>'+esc(tf.tax_type)+'</td><td>'+fmt(tf.revenue)+' \\u5143</td><td>'+fmt(tf.tax_amount)+' \\u5143</td><td>'+taxStatusBadge(tf.status)+'</td><td>'+fmtDate(tf.due_date)+'</td></tr>';});"
1059
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u7a0e\\u52a1\\u8bb0\\u5f55</p></div>';}"
1060
+ + "h+='</div>';"
1061
+ + "h+='</div>';"
1062
+ // ── Tab: team ──
1063
+ + "h+='<div class=\"tab-panel\" id=\"dtab-team\">';"
1064
+ + "h+='<div class=\"stats-grid\"><div class=\"stat-card\"><div class=\"label\">\\u5728\\u804c\\u4eba\\u6570</div><div class=\"value\">'+(d.hr.salarySummary.cnt||0)+'</div></div><div class=\"stat-card\"><div class=\"label\">\\u6708\\u85aa\\u8d44\\u603b\\u989d</div><div class=\"value\">'+fmt(d.hr.salarySummary.total_salary)+'</div></div><div class=\"stat-card\"><div class=\"label\">\\u793e\\u4fdd\\u603b\\u989d</div><div class=\"value\">'+fmt(d.hr.salarySummary.total_si)+'</div></div><div class=\"stat-card\"><div class=\"label\">\\u516c\\u79ef\\u91d1\\u603b\\u989d</div><div class=\"value\">'+fmt(d.hr.salarySummary.total_hf)+'</div></div></div>';"
1065
+ + "h+='<div style=\"margin-bottom:16px\"><button class=\"btn btn-pri btn-sm\" onclick=\"addEmployee(\\''+c.id+'\\')\">' + '+ \\u65b0\\u589e\\u5458\\u5de5' + '</button></div>';"
1066
+ + "h+='<div class=\"card\"><h2>HR \\u8bb0\\u5f55</h2>';"
1067
+ + "if(d.hr.records&&d.hr.records.length){"
1068
+ + "h+='<table><thead><tr><th>\\u59d3\\u540d</th><th>\\u804c\\u4f4d</th><th>\\u85aa\\u8d44</th><th>\\u793e\\u4fdd</th><th>\\u516c\\u79ef\\u91d1</th><th>\\u5408\\u540c\\u7c7b\\u578b</th><th>\\u72b6\\u6001</th></tr></thead><tbody>';"
1069
+ + "d.hr.records.forEach(function(r){var st=r.status==='active'?'badge-active':r.status==='resigned'?'badge-warning':'badge-err';h+='<tr><td><strong>'+esc(r.employee_name)+'</strong></td><td>'+esc(r.position)+'</td><td>'+fmt(r.salary)+'</td><td>'+fmt(r.social_insurance)+'</td><td>'+fmt(r.housing_fund)+'</td><td>'+esc(r.contract_type)+'</td><td><span class=\"badge '+st+'\">'+esc(r.status)+'</span></td></tr>';});"
1070
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0 HR \\u8bb0\\u5f55</p></div>';}"
1071
+ + "h+='</div></div>';"
1072
+ // ── Tab: projects ──
1073
+ + "h+='<div class=\"tab-panel\" id=\"dtab-projects\">';"
1074
+ + "h+='<div style=\"margin-bottom:16px\"><button class=\"btn btn-pri btn-sm\" onclick=\"createProject(\\''+c.id+'\\')\">' + '+ \\u65b0\\u5efa\\u9879\\u76ee' + '</button></div>';"
1075
+ + "if(d.projects.list&&d.projects.list.length){"
1076
+ + "h+='<div class=\"stats-grid\">';"
1077
+ + "d.projects.list.forEach(function(p){"
1078
+ + "h+='<div class=\"stat-card\" style=\"cursor:default\"><div class=\"label\">'+esc(p.name)+' '+projectStatusBadge(p.status)+'</div>';"
1079
+ + "h+='<div style=\"font-size:13px;color:#64748b;margin:8px 0\">\\u9884\\u7b97: '+fmt(p.budget)+' \\u5143 | \\u5df2\\u82b1: '+fmt(p.spent)+' \\u5143</div>';"
1080
+ + "h+=progressBar(p.spent,p.budget);"
1081
+ + "h+='</div>';});"
1082
+ + "h+='</div>';"
1083
+ + "}else{h+='<div class=\"card\"><div class=\"empty-state\"><p>\\u6682\\u65e0\\u9879\\u76ee</p></div></div>';}"
1084
+ // tasks
1085
+ + "if(d.projects.tasks&&d.projects.tasks.length){"
1086
+ + "h+='<div class=\"card\"><h2>\\u4efb\\u52a1\\u5217\\u8868</h2>';"
1087
+ + "h+='<table><thead><tr><th>\\u4efb\\u52a1</th><th>\\u8d1f\\u8d23\\u4eba</th><th>\\u4f18\\u5148\\u7ea7</th><th>\\u72b6\\u6001</th><th>\\u622a\\u6b62\\u65e5</th></tr></thead><tbody>';"
1088
+ + "d.projects.tasks.forEach(function(t){var priCls=t.priority==='urgent'?'badge-critical':t.priority==='high'?'badge-warning':'badge-default';h+='<tr><td>'+esc(t.title)+'</td><td>'+esc(t.assignee)+'</td><td><span class=\"badge '+priCls+'\">'+esc(t.priority)+'</span></td><td>'+esc(t.status)+'</td><td>'+fmtDate(t.due_date)+'</td></tr>';});"
1089
+ + "h+='</tbody></table></div>';}"
1090
+ + "h+='</div>';"
1091
+ // ── Tab: contracts ──
1092
+ + "h+='<div class=\"tab-panel\" id=\"dtab-contracts\">';"
1093
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\"><h2 style=\"margin:0\">\\u5408\\u540c\\u5217\\u8868</h2><div style=\"display:flex;gap:8px\"><button class=\"btn btn-pri btn-sm\" onclick=\"createContract(\\''+c.id+'\\')\">' + '+ \\u65b0\\u5efa\\u5408\\u540c' + '</button><a class=\"btn btn-sm\" href=\"/opc/admin/api/export/contracts\" download>\\u5bfc\\u51fa CSV</a><button class=\"btn-pdf\" style=\"font-size:11px;padding:5px 10px\" onclick=\"printContracts()\">&#128438; PDF</button></div></div>';"
1094
+ + "if(d.contracts&&d.contracts.length){"
1095
+ + "h+='<table><thead><tr><th>\\u6807\\u9898</th><th>\\u5bf9\\u65b9</th><th>\\u91d1\\u989d</th><th>\\u5f00\\u59cb</th><th>\\u7ed3\\u675f</th><th>\\u72b6\\u6001</th><th>\\u98ce\\u9669\\u5907\\u6ce8</th><th>\\u64cd\\u4f5c</th></tr></thead><tbody>';"
1096
+ + "d.contracts.forEach(function(ct){h+='<tr><td><strong>'+esc(ct.title)+'</strong></td><td>'+esc(ct.counterparty)+'</td><td>'+fmt(ct.amount)+' \\u5143</td><td>'+fmtDate(ct.start_date)+'</td><td>'+fmtDate(ct.end_date)+'</td><td>'+contractStatusBadge(ct.status)+'</td><td style=\"font-size:12px;color:#94a3b8\">'+esc(ct.risk_notes||'--')+'</td><td style=\"white-space:nowrap\"><button class=\"btn btn-sm\" onclick=\"editContract(\\''+esc(ct.id)+'\\',\\''+esc(ct.title)+'\\',\\''+esc(ct.counterparty)+'\\',\\''+ct.amount+'\\',\\''+esc(ct.status)+'\\',\\''+esc(ct.start_date)+'\\',\\''+esc(ct.end_date)+'\\',\\''+esc(ct.key_terms||'')+'\\',\\''+esc(ct.risk_notes||'')+'\\')\">' + '\\u2712 \\u7f16\\u8f91' + '</button></td></tr>';});"
1097
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u5408\\u540c</p></div>';}"
1098
+ + "h+='</div></div>';"
1099
+ // ── Tab: investment ──
1100
+ + "h+='<div class=\"tab-panel\" id=\"dtab-investment\">';"
1101
+ // rounds
1102
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\"><h2 style=\"margin:0\">\\u878d\\u8d44\\u8f6e\\u6b21</h2><button class=\"btn btn-pri\" onclick=\"createInvestRound(\\''+c.id+'\\')\">' + '+ \\u65b0\\u5efa\\u8f6e\\u6b21' + '</button></div>';"
1103
+ + "if(d.investment.rounds&&d.investment.rounds.length){"
1104
+ + "h+='<table><thead><tr><th>\\u8f6e\\u6b21</th><th>\\u878d\\u8d44\\u989d</th><th>\\u6295\\u524d\\u4f30\\u503c</th><th>\\u6295\\u540e\\u4f30\\u503c</th><th>\\u9886\\u6295</th><th>\\u72b6\\u6001</th></tr></thead><tbody>';"
1105
+ + "d.investment.rounds.forEach(function(r){h+='<tr><td><strong>'+esc(r.round_name)+'</strong></td><td>'+fmt(r.amount)+' \\u5143</td><td>'+fmt(r.valuation_pre)+' \\u5143</td><td>'+fmt(r.valuation_post)+' \\u5143</td><td>'+esc(r.lead_investor)+'</td><td>'+esc(r.status)+'</td></tr>';});"
1106
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u878d\\u8d44\\u8bb0\\u5f55</p></div>';}"
1107
+ + "h+='</div>';"
1108
+ // investors
1109
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\"><h2 style=\"margin:0\">\\u6295\\u8d44\\u4eba\\u5217\\u8868</h2><button class=\"btn btn-pri\" onclick=\"addInvestor(\\''+c.id+'\\')\">' + '+ \\u65b0\\u589e\\u6295\\u8d44\\u4eba' + '</button></div>';"
1110
+ + "if(d.investment.investors&&d.investment.investors.length){"
1111
+ + "h+='<table><thead><tr><th>\\u540d\\u79f0</th><th>\\u7c7b\\u578b</th><th>\\u6295\\u8d44\\u989d</th><th>\\u80a1\\u6743\\u5360\\u6bd4</th></tr></thead><tbody>';"
1112
+ + "d.investment.investors.forEach(function(inv){h+='<tr><td><strong>'+esc(inv.name)+'</strong></td><td>'+esc(inv.type)+'</td><td>'+fmt(inv.amount)+' \\u5143</td><td>'+inv.equity_percent+'%</td></tr>';});"
1113
+ + "h+='</tbody></table>';"
1114
+ // equity donut
1115
+ + "h+='<div style=\"margin-top:16px\"><h3>\\u80a1\\u6743\\u7ed3\\u6784</h3>'+buildDonutChart(d.investment.investors,160,'name','equity_percent')+'</div>';"
1116
+ + "}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6295\\u8d44\\u4eba</p></div>';}"
1117
+ + "h+='</div></div>';"
1118
+ // ── Tab: timeline ──
1119
+ + "h+='<div class=\"tab-panel\" id=\"dtab-timeline\">';"
1120
+ + "h+='<div class=\"card\">';"
1121
+ + "h+='<div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">';"
1122
+ + "h+='<h2 style=\"margin:0\">\\u516c\\u53f8\\u65f6\\u95f4\\u7ebf</h2>';"
1123
+ + "h+='<div style=\"display:flex;gap:8px\">';"
1124
+ + "h+='<button class=\"btn\" onclick=\"addMilestone(\\''+c.id+'\\')\">' + '+ \\u91cc\\u7a0b\\u7891' + '</button>';"
1125
+ + "h+='<button class=\"btn btn-pri\" onclick=\"addLifecycleEvent(\\''+c.id+'\\')\">' + '+ \\u4e8b\\u4ef6' + '</button>';"
1126
+ + "h+='</div></div>';"
1127
+ + "var allTl=[];"
1128
+ + "if(d.timeline.milestones)d.timeline.milestones.forEach(function(m){allTl.push({date:m.target_date||m.created_at,title:m.title,desc:m.description,type:'milestone',cat:m.category,status:m.status})});"
1129
+ + "if(d.timeline.events)d.timeline.events.forEach(function(e){allTl.push({date:e.event_date||e.created_at,title:e.title,desc:e.description,type:'event',cat:e.event_type,status:''})});"
1130
+ + "allTl.sort(function(a,b){return b.date>a.date?1:-1});"
1131
+ + "if(allTl.length){h+='<div class=\"timeline\">';allTl.forEach(function(t){"
1132
+ + "h+='<div class=\"timeline-item '+(t.type==='milestone'?'milestone':'')+'\">';"
1133
+ + "h+='<div class=\"tl-date\">'+fmtDate(t.date)+' <span class=\"badge badge-'+(t.type==='milestone'?'warning':'info')+'\">'+esc(t.type==='milestone'?'\\u91cc\\u7a0b\\u7891':'\\u4e8b\\u4ef6')+'</span>'+(t.cat?' <span class=\"badge badge-default\">'+esc(t.cat)+'</span>':'')+'</div>';"
1134
+ + "h+='<div class=\"tl-title\">'+esc(t.title)+'</div>';"
1135
+ + "h+='<div class=\"tl-desc\">'+esc(t.desc)+'</div>';"
1136
+ + "h+='</div>';});"
1137
+ + "h+='</div>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u65f6\\u95f4\\u7ebf\\u6570\\u636e</p></div>';}"
1138
+ + "h+='</div></div>';"
1139
+ // ── Tab: staff (AI员工配置) ──
1140
+ + "h+='<div class=\"tab-panel\" id=\"dtab-staff\">';"
1141
+ + "h+='<div class=\"card\">';"
1142
+ + "h+='<div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:20px\">';"
1143
+ + "h+='<h2 style=\"margin:0\">AI \\u5458\\u5de5\\u914d\\u7f6e</h2>';"
1144
+ + "h+='<button class=\"btn btn-pri\" onclick=\"initDefaultStaff(\\''+c.id+'\\')\">' + '\\u4e00\\u952e\\u521d\\u59cb\\u5316\\u9ed8\\u8ba4\\u5c97\\u4f4d' + '</button>';"
1145
+ + "h+='</div>';"
1146
+ + "if(d.staffConfig&&d.staffConfig.length){"
1147
+ + "h+='<table><thead><tr><th>\\u5c97\\u4f4d</th><th>\\u540d\\u79f0</th><th>\\u542f\\u7528</th><th>\\u63d0\\u793a\\u8bcd\\u9884\\u89c8</th><th>\\u66f4\\u65b0\\u65f6\\u95f4</th><th>\\u64cd\\u4f5c</th></tr></thead><tbody>';"
1148
+ + "d.staffConfig.forEach(function(s){"
1149
+ + "var promptPreview=s.system_prompt?esc(s.system_prompt.slice(0,50))+(s.system_prompt.length>50?'...':''):'<span style=\"color:var(--tx3)\">\\u672a\\u914d\\u7f6e</span>';"
1150
+ + "h+='<tr>';"
1151
+ + "h+='<td><code style=\"font-size:11px;background:#f3f4f6;padding:2px 6px;border-radius:4px\">'+esc(s.role)+'</code></td>';"
1152
+ + "h+='<td><strong>'+esc(s.role_name)+'</strong></td>';"
1153
+ + "h+='<td><label class=\"toggle\" style=\"cursor:pointer\" title=\"'+(s.enabled?'\\u70b9\\u51fb\\u505c\\u7528':'\\u70b9\\u51fb\\u542f\\u7528')+'\">';"
1154
+ + "h+='<input type=\"checkbox\" '+(s.enabled?'checked':'')+' onchange=\"toggleStaff(\\''+esc(s.id)+'\\',\\''+esc(c.id)+'\\',\\''+esc(s.role)+'\\',this.checked)\">';"
1155
+ + "h+='<span class=\"slider\"></span></label></td>';"
1156
+ + "h+='<td style=\"max-width:260px;font-size:12px;color:var(--tx2)\">'+promptPreview+'</td>';"
1157
+ + "h+='<td style=\"white-space:nowrap\">'+fmtDate(s.updated_at)+'</td>';"
1158
+ + "h+='<td style=\"white-space:nowrap\"><button class=\"btn btn-sm\" onclick=\"editStaff(\\''+esc(s.id)+'\\',\\''+esc(s.role)+'\\',\\''+esc(s.role_name)+'\\',\\''+c.id+'\\')\">\\u2712 \\u7f16\\u8f91</button></td>';"
1159
+ + "h+='</tr>';"
1160
+ + "});"
1161
+ + "h+='</tbody></table>';"
1162
+ + "}else{"
1163
+ + "h+='<div class=\"empty-state\"><div class=\"icon\">\\ud83e\\udd16</div><p>\\u6682\\u65e0 AI \\u5458\\u5de5\\u914d\\u7f6e</p><p style=\"margin-top:8px;font-size:12px\">\\u70b9\\u51fb\\u53f3\\u4e0a\\u89d2\\u300c\\u4e00\\u952e\\u521d\\u59cb\\u5316\\u9ed8\\u8ba4\\u5c97\\u4f4d\\u300d\\u5373\\u53ef\\u5feb\\u901f\\u521b\\u5efa 6 \\u4e2a AI \\u5458\\u5de5</p></div>';"
1164
+ + "}"
1165
+ + "h+='</div></div>';"
1166
+ // ── Tab: media (新媒体内容) ──
1167
+ + "h+='<div class=\"tab-panel\" id=\"dtab-media\">';"
1168
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\"><h2 style=\"margin:0\">\\u65b0\\u5a92\\u4f53\\u5185\\u5bb9</h2><button class=\"btn btn-pri\" onclick=\"createMedia(\\''+c.id+'\\')\">' + '+ \\u65b0\\u5efa\\u5185\\u5bb9' + '</button></div>';"
1169
+ + "if(d.mediaContent&&d.mediaContent.length){"
1170
+ + "h+='<table><thead><tr><th>\\u6807\\u9898</th><th>\\u5e73\\u53f0</th><th>\\u7c7b\\u578b</th><th>\\u72b6\\u6001</th><th>\\u9884\\u7ea6/\\u53d1\\u5e03\\u65e5\\u671f</th></tr></thead><tbody>';"
1171
+ + "d.mediaContent.forEach(function(m){"
1172
+ + "var statusMap={'draft':'\\u8349\\u7a3f','scheduled':'\\u5df2\\u5b89\\u6392','published':'\\u5df2\\u53d1\\u5e03','archived':'\\u5df2\\u5f52\\u6863'};"
1173
+ + "var statusCls={'draft':'badge-draft','scheduled':'badge-info','published':'badge-active','archived':'badge-other'};"
1174
+ + "h+='<tr><td><strong>'+esc(m.title)+'</strong></td><td>'+esc(m.platform)+'</td><td>'+esc(m.content_type)+'</td><td><span class=\"badge '+(statusCls[m.status]||'badge-other')+'\">'+(statusMap[m.status]||esc(m.status))+'</span></td><td>'+fmtDate(m.scheduled_date||m.published_date)+'</td></tr>';"
1175
+ + "});"
1176
+ + "h+='</tbody></table>';"
1177
+ + "}else{h+='<div class=\"empty-state\"><div class=\"icon\">\\ud83d\\udce3</div><p>\\u6682\\u65e0\\u5185\\u5bb9\\u8bb0\\u5f55\\uff0c\\u70b9\\u51fb\\u300c+ \\u65b0\\u5efa\\u5185\\u5bb9\\u300d\\u5f00\\u59cb\\u521b\\u5efa</p></div>';}"
1178
+ + "h+='</div></div>';"
1179
+ // ── Tab: procurement (服务采购) ──
1180
+ + "h+='<div class=\"tab-panel\" id=\"dtab-procurement\">';"
1181
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\"><h2 style=\"margin:0\">\\u670d\\u52a1\\u91c7\\u8d2d\\u8ba2\\u5355</h2><button class=\"btn btn-pri\" onclick=\"createOrder(\\''+c.id+'\\')\">' + '+ \\u65b0\\u5efa\\u8ba2\\u5355' + '</button></div>';"
1182
+ + "if(d.procurementOrders&&d.procurementOrders.length){"
1183
+ + "var totalProcurement=d.procurementOrders.reduce(function(s,o){return s+(o.amount||0);},0);"
1184
+ + "h+='<div class=\"stats-grid\" style=\"grid-template-columns:repeat(3,1fr);margin-bottom:16px\">';"
1185
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u91c7\\u8d2d\\u8ba2\\u5355</div><div class=\"value\">'+d.procurementOrders.length+' <span class=\"unit\">\\u4efd</span></div></div>';"
1186
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u91c7\\u8d2d\\u603b\\u989d</div><div class=\"value\">'+fmt(totalProcurement)+' <span class=\"unit\">\\u5143</span></div></div>';"
1187
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5df2\\u5b8c\\u6210</div><div class=\"value\">'+d.procurementOrders.filter(function(o){return o.status==='completed';}).length+' <span class=\"unit\">\\u5355</span></div></div>';"
1188
+ + "h+='</div>';"
1189
+ + "h+='<table><thead><tr><th>\\u6807\\u9898</th><th>\\u670d\\u52a1\\u540d\\u79f0</th><th>\\u91d1\\u989d</th><th>\\u72b6\\u6001</th><th>\\u4e0b\\u5355\\u65e5\\u671f</th><th>\\u5907\\u6ce8</th></tr></thead><tbody>';"
1190
+ + "d.procurementOrders.forEach(function(o){"
1191
+ + "var statusMap={'pending':'\\u5f85\\u5904\\u7406','approved':'\\u5df2\\u5ba1\\u6279','completed':'\\u5df2\\u5b8c\\u6210','cancelled':'\\u5df2\\u53d6\\u6d88'};"
1192
+ + "var statusCls={'pending':'badge-draft','approved':'badge-info','completed':'badge-active','cancelled':'badge-err'};"
1193
+ + "h+='<tr><td><strong>'+esc(o.title||'--')+'</strong></td><td>'+esc(o.service_name||'--')+'</td><td>'+fmt(o.amount)+' \\u5143</td><td><span class=\"badge '+(statusCls[o.status]||'badge-other')+'\">'+(statusMap[o.status]||esc(o.status))+'</span></td><td>'+fmtDate(o.order_date||o.created_at)+'</td><td style=\"font-size:12px;color:var(--tx2)\">'+esc(o.notes||'--')+'</td></tr>';"
1194
+ + "});"
1195
+ + "h+='</tbody></table>';"
1196
+ + "}else{h+='<div class=\"empty-state\"><div class=\"icon\">\\ud83d\\uded2</div><p>\\u6682\\u65e0\\u91c7\\u8d2d\\u8ba2\\u5355\\uff0c\\u8bf7\\u5728\\u5bf9\\u8bdd\\u4e2d\\u4f7f\\u7528 opc_procurement \\u5de5\\u5177\\u521b\\u5efa\\u8ba2\\u5355</p></div>';}"
1197
+ + "h+='</div>';"
1198
+ + "if(d.services&&d.services.length){"
1199
+ + "h+='<div class=\"card\" style=\"margin-top:16px\"><h2>\\u670d\\u52a1\\u76ee\\u5f55</h2>';"
1200
+ + "h+='<table><thead><tr><th>\\u540d\\u79f0</th><th>\\u5206\\u7c7b</th><th>\\u63d0\\u4f9b\\u65b9</th><th>\\u5355\\u4ef7</th><th>\\u8ba1\\u8d39\\u5468\\u671f</th><th>\\u72b6\\u6001</th></tr></thead><tbody>';"
1201
+ + "d.services.forEach(function(s){h+='<tr><td><strong>'+esc(s.name)+'</strong></td><td>'+esc(s.category)+'</td><td>'+esc(s.provider)+'</td><td>'+fmt(s.unit_price)+' \\u5143</td><td>'+esc(s.billing_cycle)+'</td><td>'+esc(s.status)+'</td></tr>';});"
1202
+ + "h+='</tbody></table></div>';}"
1203
+ + "h+='</div>';"
1204
+ + "el.innerHTML=h;"
1205
+ + "}).catch(function(e){el.innerHTML='<div class=\"card\"><div class=\"empty-state\"><p>\\u52a0\\u8f7d\\u5931\\u8d25: '+esc(String(e))+'</p></div></div>';});}"
1206
+ + "\nfunction switchDetailTab(name){document.querySelectorAll('.tab-panel').forEach(function(p){p.classList.remove('active')});document.querySelectorAll('.detail-tabs button').forEach(function(b){b.classList.remove('active')});var panel=document.getElementById('dtab-'+name);if(panel)panel.classList.add('active');var btns=document.querySelectorAll('.detail-tabs button');btns.forEach(function(b){var fn=b.getAttribute('onclick')||'';if(fn.indexOf(\"'\"+name+\"'\")>-1||fn.indexOf('\"'+name+'\"')>-1)b.classList.add('active')});}"
1207
+ // ── loadFinance ──
1208
+ + "\nfunction loadFinance(){"
1209
+ + "var el=document.getElementById('finance-content');"
1210
+ + "el.innerHTML='<div class=\"skeleton\" style=\"height:400px\"></div>';"
1211
+ + "fetch('/opc/admin/api/finance/overview').then(function(r){return r.json()}).then(function(d){"
1212
+ + "var h='';"
1213
+ + "h+='<div class=\"grid-2\" style=\"margin-bottom:16px\">';"
1214
+ + "h+='<div class=\"card\"><h2 style=\"margin-bottom:16px\">12 \\u4e2a\\u6708\\u6536\\u5165\\u8d8b\\u52bf</h2>'+buildSingleBarChart(d.trends,460,220,'month','income','#0ea5e9','\\u6536\\u5165')+'</div>';"
1215
+ + "h+='<div class=\"card\"><h2 style=\"margin-bottom:16px\">12 \\u4e2a\\u6708\\u652f\\u51fa\\u8d8b\\u52bf</h2>'+buildSingleBarChart(d.trends,460,220,'month','expense','#f97316','\\u652f\\u51fa')+'</div>';"
1216
+ + "h+='</div>';"
1217
+ // invoice summary
1218
+ + "h+='<div class=\"card\"><h2>\\u53d1\\u7968\\u72b6\\u6001\\u6c47\\u603b</h2><div class=\"grid-2\">';"
1219
+ + "h+='<div><h3>\\u9500\\u9879\\u53d1\\u7968</h3><div class=\"stats-grid\" style=\"grid-template-columns:repeat(4,1fr)\">';"
1220
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u8349\\u7a3f</div><div class=\"value\">'+d.invoiceSummary.sales.draft+'</div></div>';"
1221
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5df2\\u5f00</div><div class=\"value\">'+d.invoiceSummary.sales.issued+'</div></div>';"
1222
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5df2\\u4ed8</div><div class=\"value\" style=\"color:var(--ok)\">'+d.invoiceSummary.sales.paid+'</div></div>';"
1223
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u4f5c\\u5e9f</div><div class=\"value\" style=\"color:var(--err)\">'+d.invoiceSummary.sales.void+'</div></div>';"
1224
+ + "h+='</div></div>';"
1225
+ + "h+='<div><h3>\\u8fdb\\u9879\\u53d1\\u7968</h3><div class=\"stats-grid\" style=\"grid-template-columns:repeat(4,1fr)\">';"
1226
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u8349\\u7a3f</div><div class=\"value\">'+d.invoiceSummary.purchase.draft+'</div></div>';"
1227
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5df2\\u5f00</div><div class=\"value\">'+d.invoiceSummary.purchase.issued+'</div></div>';"
1228
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u5df2\\u4ed8</div><div class=\"value\" style=\"color:var(--ok)\">'+d.invoiceSummary.purchase.paid+'</div></div>';"
1229
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\\u4f5c\\u5e9f</div><div class=\"value\" style=\"color:var(--err)\">'+d.invoiceSummary.purchase.void+'</div></div>';"
1230
+ + "h+='</div></div>';"
1231
+ + "h+='</div></div>';"
1232
+ // tax calendar
1233
+ + "h+='<div class=\"card\"><h2>\\u7a0e\\u52a1\\u65e5\\u5386</h2>';"
1234
+ + "if(d.taxFilings&&d.taxFilings.length){"
1235
+ + "var today=new Date().toISOString().slice(0,10);"
1236
+ + "h+='<table><thead><tr><th>\\u516c\\u53f8</th><th>\\u671f\\u95f4</th><th>\\u7a0e\\u79cd</th><th>\\u6536\\u5165</th><th>\\u7a0e\\u989d</th><th>\\u72b6\\u6001</th><th>\\u622a\\u6b62\\u65e5</th></tr></thead><tbody>';"
1237
+ + "d.taxFilings.forEach(function(tf){var overdue=tf.status==='pending'&&tf.due_date&&tf.due_date<today;h+='<tr style=\"'+(overdue?'background:#fef2f2':'')+'\">';"
1238
+ + "h+='<td>'+esc(tf.company_name||'')+'</td><td>'+esc(tf.period)+'</td><td>'+esc(tf.tax_type)+'</td><td>'+fmt(tf.revenue)+' \\u5143</td><td>'+fmt(tf.tax_amount)+' \\u5143</td><td>'+taxStatusBadge(tf.status)+'</td><td>'+(overdue?'<span style=\"color:var(--err);font-weight:600\">'+fmtDate(tf.due_date)+' \\u903e\\u671f</span>':fmtDate(tf.due_date))+'</td></tr>';});"
1239
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u7a0e\\u52a1\\u7533\\u62a5\\u8bb0\\u5f55</p></div>';}"
1240
+ + "h+='</div>';"
1241
+ + "el.innerHTML=h;"
1242
+ + "}).catch(function(e){el.innerHTML='<div class=\"card\"><div class=\"empty-state\"><p>\\u52a0\\u8f7d\\u5931\\u8d25</p></div></div>';});}"
1243
+ // ── loadMonitoring ──
1244
+ + "\nvar monitoringAlertPage=1;"
1245
+ + "\nfunction loadMonitoring(){"
1246
+ + "monitoringAlertPage=1;"
1247
+ + "var el=document.getElementById('monitoring-content');"
1248
+ + "el.innerHTML='<div class=\"skeleton\" style=\"height:400px\"></div>';"
1249
+ + "fetch('/opc/admin/api/monitoring').then(function(r){return r.json()}).then(function(d){"
1250
+ + "window._monitoringData=d;"
1251
+ + "renderMonitoring(d,1);"
1252
+ + "}).catch(function(e){document.getElementById('monitoring-content').innerHTML='<div class=\"card\"><div class=\"empty-state\"><p>\\u52a0\\u8f7d\\u5931\\u8d25</p></div></div>';});}"
1253
+ + "\nfunction renderMonitoring(d,alertPage){"
1254
+ + "var el=document.getElementById('monitoring-content');"
1255
+ + "var h='';"
1256
+ // severity cards
1257
+ + "h+='<div class=\"stats-grid\" style=\"grid-template-columns:repeat(3,1fr)\">';"
1258
+ + "h+='<div class=\"stat-card\" style=\"border-left:4px solid var(--err)\"><div class=\"label\">\\u4e25\\u91cd</div><div class=\"value\" style=\"color:var(--err)\">'+d.alertCounts.critical+'</div></div>';"
1259
+ + "h+='<div class=\"stat-card\" style=\"border-left:4px solid var(--warn)\"><div class=\"label\">\\u8b66\\u544a</div><div class=\"value\" style=\"color:var(--warn)\">'+d.alertCounts.warning+'</div></div>';"
1260
+ + "h+='<div class=\"stat-card\" style=\"border-left:4px solid #3b82f6\"><div class=\"label\">\\u63d0\\u793a</div><div class=\"value\" style=\"color:#3b82f6\">'+d.alertCounts.info+'</div></div>';"
1261
+ + "h+='</div>';"
1262
+ // KPI trend charts — TOP
1263
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\"><h2 style=\"margin:0\">KPI \\u8d8b\\u52bf</h2><span style=\"font-size:12px;color:var(--tx3)\">\\u8fd130\\u5929</span></div>';"
1264
+ + "if(d.metricTrends&&d.metricTrends.length){"
1265
+ + "var trendMap={};"
1266
+ + "d.metricTrends.forEach(function(r){if(!trendMap[r.name])trendMap[r.name]={unit:r.unit,category:r.category,points:[]};trendMap[r.name].points.push({day:r.day,avg_value:r.avg_value});});"
1267
+ + "var trendColors=['#0f172a','#0ea5e9','#8b5cf6','#f59e0b','#10b981','#ef4444'];"
1268
+ + "var trendCi=0;"
1269
+ + "h+='<div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(400px,1fr));gap:16px\">';"
1270
+ + "Object.keys(trendMap).forEach(function(tname){"
1271
+ + "var tm=trendMap[tname];"
1272
+ + "var tcol=trendColors[trendCi++%trendColors.length];"
1273
+ + "h+='<div style=\"border:1px solid var(--bd);border-radius:var(--r);padding:16px\">';"
1274
+ + "h+='<div style=\"font-size:13px;font-weight:600;margin-bottom:8px\">'+esc(tname)+' <span style=\"font-size:11px;color:var(--tx3);font-weight:400\">('+esc(tm.unit)+')</span></div>';"
1275
+ + "h+=buildLineChart(tm.points,400,160,'day','avg_value',tcol,tname);"
1276
+ + "h+='</div>';"
1277
+ + "});"
1278
+ + "h+='</div>';"
1279
+ + "}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u8d8b\\u52bf\\u6570\\u636e\\uff0c\\u8bf7\\u5148\\u901a\\u8fc7 opc_monitoring \\u5de5\\u5177\\u8bb0\\u5f55\\u6307\\u6807</p></div>';}"
1280
+ + "h+='</div>';"
1281
+ // alert list with pagination
1282
+ + "var PAGE_SIZE=10;"
1283
+ + "var alerts=d.alerts||[];"
1284
+ + "var totalPages=Math.max(1,Math.ceil(alerts.length/PAGE_SIZE));"
1285
+ + "var page=Math.min(Math.max(1,alertPage),totalPages);"
1286
+ + "var pageAlerts=alerts.slice((page-1)*PAGE_SIZE,page*PAGE_SIZE);"
1287
+ + "h+='<div class=\"card\" id=\"alert-card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\">';"
1288
+ + "h+='<h2 style=\"margin:0\">\\u6d3b\\u8dc3\\u544a\\u8b66 <span style=\"font-size:13px;font-weight:400;color:var(--tx3)\">('+alerts.length+')</span></h2>';"
1289
+ + "h+='</div>';"
1290
+ + "if(alerts.length){"
1291
+ + "h+='<table><thead><tr><th>\\u4e25\\u91cd\\u5ea6</th><th>\\u516c\\u53f8</th><th>\\u6807\\u9898</th><th>\\u6d88\\u606f</th><th>\\u65f6\\u95f4</th><th>\\u64cd\\u4f5c</th></tr></thead><tbody>';"
1292
+ + "pageAlerts.forEach(function(a){h+='<tr><td>'+severityBadge(a.severity)+'</td><td>'+esc(a.company_name||'')+'</td><td><strong>'+esc(a.title)+'</strong></td><td style=\"font-size:13px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">'+esc(a.message)+'</td><td>'+fmtDate(a.created_at)+'</td><td><button class=\"btn btn-sm\" onclick=\"dismissAlert(\\''+esc(a.id)+'\\')\">' + '\\u6d88\\u9664' + '</button></td></tr>';});"
1293
+ + "h+='</tbody></table>';"
1294
+ + "if(totalPages>1){"
1295
+ + "h+='<div style=\"display:flex;align-items:center;gap:8px;margin-top:12px;justify-content:flex-end\">';"
1296
+ + "h+='<button class=\"btn btn-sm\" '+(page<=1?'disabled':'')+' onclick=\"renderMonitoring(window._monitoringData,'+(page-1)+')\">&laquo; \\u4e0a\\u4e00\\u9875</button>';"
1297
+ + "h+='<span style=\"font-size:13px;color:var(--tx2)\">'+page+' / '+totalPages+'</span>';"
1298
+ + "h+='<button class=\"btn btn-sm\" '+(page>=totalPages?'disabled':'')+' onclick=\"renderMonitoring(window._monitoringData,'+(page+1)+')\">\\u4e0b\\u4e00\\u9875 &raquo;</button>';"
1299
+ + "h+='</div>';"
1300
+ + "}"
1301
+ + "}else{h+='<div class=\"empty-state\"><div class=\"icon\">\\u2705</div><p>\\u6ca1\\u6709\\u6d3b\\u8dc3\\u544a\\u8b66</p></div>';}"
1302
+ + "h+='</div>';"
1303
+ // metrics overview
1304
+ + "h+='<div class=\"card\"><div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\"><h2 style=\"margin:0\">\\u6307\\u6807\\u6982\\u89c8</h2><button class=\"btn btn-pri\" onclick=\"recordMetric()\">' + '+ \\u8bb0\\u5f55\\u6307\\u6807' + '</button></div>';"
1305
+ + "if(d.latestMetrics&&d.latestMetrics.length){"
1306
+ + "var cats={};d.latestMetrics.forEach(function(m){if(!cats[m.category])cats[m.category]=[];cats[m.category].push(m);});"
1307
+ + "Object.keys(cats).forEach(function(cat){h+='<h3 style=\"margin-top:12px\">'+esc(cat||'\\u672a\\u5206\\u7c7b')+'</h3><div class=\"stats-grid\" style=\"grid-template-columns:repeat(auto-fill,minmax(150px,1fr));margin-bottom:8px\">';cats[cat].forEach(function(m){h+='<div class=\"stat-card\" style=\"padding:12px\"><div class=\"label\">'+esc(m.name)+'</div><div class=\"value\" style=\"font-size:20px\">'+m.value+' <span class=\"unit\">'+esc(m.unit)+'</span></div></div>';});h+='</div>';});"
1308
+ + "}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6307\\u6807\\u6570\\u636e</p></div>';}"
1309
+ + "h+='</div>';"
1310
+ // recent metrics
1311
+ + "h+='<div class=\"card\"><h2>\\u8fd1\\u671f\\u6307\\u6807\\u8bb0\\u5f55</h2>';"
1312
+ + "if(d.recentMetrics&&d.recentMetrics.length){"
1313
+ + "h+='<table><thead><tr><th>\\u516c\\u53f8</th><th>\\u6307\\u6807</th><th>\\u503c</th><th>\\u5355\\u4f4d</th><th>\\u5206\\u7c7b</th><th>\\u65f6\\u95f4</th></tr></thead><tbody>';"
1314
+ + "d.recentMetrics.forEach(function(m){h+='<tr><td>'+esc(m.company_name||'')+'</td><td>'+esc(m.name)+'</td><td style=\"font-weight:600\">'+m.value+'</td><td>'+esc(m.unit)+'</td><td>'+esc(m.category)+'</td><td>'+fmtDate(m.recorded_at)+'</td></tr>';});"
1315
+ + "h+='</tbody></table>';}else{h+='<div class=\"empty-state\"><p>\\u6682\\u65e0\\u6307\\u6807\\u8bb0\\u5f55</p></div>';}"
1316
+ + "h+='</div>';"
1317
+ + "el.innerHTML=h;"
1318
+ + "}"
1319
+ // ── dismissAlert ──
1320
+ + "\nfunction dismissAlert(id){fetch('/opc/admin/api/alerts/'+encodeURIComponent(id)+'/dismiss',{method:'POST'}).then(function(r){return r.json()}).then(function(d){if(d.ok){showToast('\\u544a\\u8b66\\u5df2\\u6d88\\u9664');loadMonitoring();}else{showToast('\\u6d88\\u9664\\u5931\\u8d25');}}).catch(function(){showToast('\\u64cd\\u4f5c\\u5931\\u8d25');});}"
1321
+ // ── AI员工操作 ──
1322
+ + "\nfunction toggleStaff(staffId,companyId,role,enabled){"
1323
+ + "fetch('/opc/admin/api/staff/'+encodeURIComponent(staffId)+'/toggle',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({enabled:enabled?1:0})})"
1324
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){showToast(enabled?'\\u5df2\\u542f\\u7528 '+role:'\\u5df2\\u505c\\u7528 '+role);}"
1325
+ + "else{showToast(d.error||'\\u64cd\\u4f5c\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1326
+ + "\nfunction editStaff(staffId,role,roleName,companyId){"
1327
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1328
+ + "fetch('/opc/admin/api/staff/'+encodeURIComponent(staffId)).then(function(r){return r.json()}).then(function(s){"
1329
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1330
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:560px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1331
+ + "html+='<h2 style=\"margin:0 0 4px\">\\u7f16\\u8f91 AI \\u5458\\u5de5</h2>';"
1332
+ + "html+='<p style=\"color:var(--tx3);font-size:13px;margin-bottom:20px\">\\u5c97\\u4f4d: <code>'+esc(role)+'</code></p>';"
1333
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1334
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u663e\\u793a\\u540d\\u79f0 <input id=\"sf-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" value=\"'+esc(s.role_name||roleName)+'\"></label>';"
1335
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u7cfb\\u7edf\\u63d0\\u793a\\u8bcd (System Prompt) <textarea id=\"sf-prompt\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:160px\">'+esc(s.system_prompt||'')+'</textarea></label>';"
1336
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5907\\u6ce8 <input id=\"sf-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" value=\"'+esc(s.notes||'')+'\"></label>';"
1337
+ + "html+='</div>';"
1338
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1339
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
1340
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveStaff(\\''+staffId+'\\',\\''+companyId+'\\')\">\\u4fdd\\u5b58</button>';"
1341
+ + "html+='</div></div></div>';"
1342
+ + "document.body.insertAdjacentHTML('beforeend',html);"
1343
+ + "}).catch(function(){showToast('\\u52a0\\u8f7d\\u5931\\u8d25');});}"
1344
+ + "\nfunction saveStaff(staffId,companyId){"
1345
+ + "var data={role_name:document.getElementById('sf-name').value,system_prompt:document.getElementById('sf-prompt').value,notes:document.getElementById('sf-notes').value};"
1346
+ + "fetch('/opc/admin/api/staff/'+encodeURIComponent(staffId)+'/edit',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1347
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5df2\\u4fdd\\u5b58');showCompany(companyId);}"
1348
+ + "else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1349
+ + "\nfunction addEmployee(companyId){"
1350
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1351
+ + "var today=new Date().toISOString().slice(0,10);"
1352
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1353
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw\">';"
1354
+ + "html+='<h2 style=\"margin:0 0 20px\">\\u65b0\\u589e\\u5458\\u5de5</h2>';"
1355
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1356
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u59d3\\u540d <input id=\"emp-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1357
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5c97\\u4f4d <input id=\"emp-pos\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1358
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u6708\\u85aa (\\u5143) <input id=\"emp-salary\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1359
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u7528\\u5de5\\u7c7b\\u578b <select id=\"emp-type\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"full_time\">\\u5168\\u804c</option><option value=\"part_time\">\\u517c\\u804c</option><option value=\"contractor\">\\u5408\\u540c\\u5de5</option><option value=\"intern\">\\u5b9e\\u4e60</option></select></label>';"
1360
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5165\\u804c\\u65e5\\u671f <input id=\"emp-date\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\" value=\"'+today+'\"></label>';"
1361
+ + "html+='</div><div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1362
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
1363
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveEmployee(\\''+companyId+'\\')\">' + '\\u4fdd\\u5b58' + '</button>';"
1364
+ + "html+='</div></div></div>';"
1365
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1366
+ + "\nfunction saveEmployee(companyId){"
1367
+ + "var data={company_id:companyId,employee_name:document.getElementById('emp-name').value,position:document.getElementById('emp-pos').value,salary:parseFloat(document.getElementById('emp-salary').value)||0,contract_type:document.getElementById('emp-type').value,start_date:document.getElementById('emp-date').value};"
1368
+ + "fetch('/opc/admin/api/hr/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1369
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5458\\u5de5\\u5df2\\u6dfb\\u52a0');showCompany(companyId);}else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1370
+ + "\nfunction createProject(companyId){"
1371
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1372
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1373
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw\">';"
1374
+ + "html+='<h2 style=\"margin:0 0 20px\">\\u65b0\\u5efa\\u9879\\u76ee</h2>';"
1375
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1376
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u9879\\u76ee\\u540d\\u79f0 <input id=\"pj-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1377
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u63cf\\u8ff0 <textarea id=\"pj-desc\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:72px\"></textarea></label>';"
1378
+ + "html+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px\">';"
1379
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5f00\\u59cb\\u65e5\\u671f <input id=\"pj-start\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\"></label>';"
1380
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u622a\\u6b62\\u65e5\\u671f <input id=\"pj-end\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\"></label>';"
1381
+ + "html+='</div>';"
1382
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u9884\\u7b97 (\\u5143) <input id=\"pj-budget\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1383
+ + "html+='</div><div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1384
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
1385
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveProject(\\''+companyId+'\\')\">' + '\\u4fdd\\u5b58' + '</button>';"
1386
+ + "html+='</div></div></div>';"
1387
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1388
+ + "\nfunction saveProject(companyId){"
1389
+ + "var data={company_id:companyId,name:document.getElementById('pj-name').value,description:document.getElementById('pj-desc').value,start_date:document.getElementById('pj-start').value,end_date:document.getElementById('pj-end').value,budget:parseFloat(document.getElementById('pj-budget').value)||0};"
1390
+ + "fetch('/opc/admin/api/projects/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1391
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u9879\\u76ee\\u5df2\\u521b\\u5efa');showCompany(companyId);}else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1392
+ + "\nfunction initDefaultStaff(companyId){"
1393
+ + "fetch('/opc/admin/api/staff/'+encodeURIComponent(companyId)+'/init',{method:'POST'})"
1394
+ + ".then(function(r){return r.json()}).then(function(d){"
1395
+ + "if(d.ok){showToast('\\u5df2\\u521d\\u59cb\\u5316 '+d.created+' \\u4e2a\\u5c97\\u4f4d');showCompany(companyId);}"
1396
+ + "else{showToast(d.error||'\\u521d\\u59cb\\u5316\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1397
+ // ── 合同编辑 ──
1398
+ + "\nfunction editContract(id,title,counterparty,amount,status,startDate,endDate,keyTerms,riskNotes){"
1399
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1400
+ + "var statusOpts=[['draft','\\u8349\\u7a3f'],['active','\\u751f\\u6548\\u4e2d'],['expired','\\u5df2\\u8fc7\\u671f'],['terminated','\\u5df2\\u7ec8\\u6b62'],['disputed','\\u4e89\\u8bae\\u4e2d']];"
1401
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1402
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:520px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1403
+ + "html+='<h2 style=\"margin:0 0 20px\">\\u7f16\\u8f91\\u5408\\u540c</h2>';"
1404
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1405
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u6807\\u9898 <input id=\"ct-title\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" value=\"'+esc(title)+'\"></label>';"
1406
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5bf9\\u65b9 <input id=\"ct-party\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" value=\"'+esc(counterparty)+'\"></label>';"
1407
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u91d1\\u989d (\\u5143) <input id=\"ct-amount\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" value=\"'+amount+'\"></label>';"
1408
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u72b6\\u6001 <select id=\"ct-status\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\">';"
1409
+ + "statusOpts.forEach(function(o){html+='<option value=\"'+o[0]+'\"'+(status===o[0]?' selected':'')+'>'+o[1]+'</option>';});"
1410
+ + "html+='</select></label>';"
1411
+ + "html+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px\">';"
1412
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5f00\\u59cb\\u65e5\\u671f <input id=\"ct-start\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\" value=\"'+esc(startDate)+'\"></label>';"
1413
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u7ed3\\u675f\\u65e5\\u671f <input id=\"ct-end\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\" value=\"'+esc(endDate)+'\"></label>';"
1414
+ + "html+='</div>';"
1415
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u6838\\u5fc3\\u6761\\u6b3e <textarea id=\"ct-terms\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:64px\">'+esc(keyTerms)+'</textarea></label>';"
1416
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u98ce\\u9669\\u5907\\u6ce8 <textarea id=\"ct-risk\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:48px\">'+esc(riskNotes)+'</textarea></label>';"
1417
+ + "html+='</div>';"
1418
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1419
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
1420
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveContract(\\''+id+'\\')\">' + '\\u4fdd\\u5b58' + '</button>';"
1421
+ + "html+='</div></div></div>';"
1422
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1423
+ + "\nfunction saveContract(id){"
1424
+ + "var data={title:document.getElementById('ct-title').value,counterparty:document.getElementById('ct-party').value,"
1425
+ + "amount:parseFloat(document.getElementById('ct-amount').value)||0,status:document.getElementById('ct-status').value,"
1426
+ + "start_date:document.getElementById('ct-start').value,end_date:document.getElementById('ct-end').value,"
1427
+ + "key_terms:document.getElementById('ct-terms').value,notes:document.getElementById('ct-risk').value};"
1428
+ + "fetch('/opc/admin/api/contracts/'+encodeURIComponent(id)+'/edit',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1429
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5408\\u540c\\u5df2\\u4fdd\\u5b58');showCompany(window.currentCompanyId||'');}"
1430
+ + "else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1431
+ // ── createContract ──
1432
+ + "\nfunction createContract(companyId){"
1433
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1434
+ + "var today=new Date().toISOString().slice(0,10);"
1435
+ + "var typeOpts=[['\\u670d\\u52a1\\u5408\\u540c','\\u670d\\u52a1\\u5408\\u540c'],['\\u91c7\\u8d2d\\u5408\\u540c','\\u91c7\\u8d2d\\u5408\\u540c'],['\\u52b3\\u52a8\\u5408\\u540c','\\u52b3\\u52a8\\u5408\\u540c'],['\\u79df\\u8d41\\u5408\\u540c','\\u79df\\u8d41\\u5408\\u540c'],['\\u5408\\u4f5c\\u534f\\u8bae','\\u5408\\u4f5c\\u534f\\u8bae'],['NDA','NDA'],['\\u5176\\u4ed6','\\u5176\\u4ed6']];"
1436
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1437
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:540px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1438
+ + "html+='<h2 style=\"margin:0 0 20px\">\\u65b0\\u5efa\\u5408\\u540c</h2>';"
1439
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1440
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u6807\\u9898 <input id=\"nc-title\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1441
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5bf9\\u65b9 <input id=\"nc-counterparty\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1442
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5408\\u540c\\u7c7b\\u578b <select id=\"nc-type\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\">';"
1443
+ + "typeOpts.forEach(function(o){html+='<option value=\"'+o[0]+'\">'+o[1]+'</option>';});"
1444
+ + "html+='</select></label>';"
1445
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u91d1\\u989d (\\u5143, \\u53ef\\u4e3a0) <input id=\"nc-amount\" type=\"number\" min=\"0\" step=\"0.01\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" value=\"0\"></label>';"
1446
+ + "html+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px\">';"
1447
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5f00\\u59cb\\u65e5\\u671f <input id=\"nc-start\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\" value=\"'+today+'\"></label>';"
1448
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u7ed3\\u675f\\u65e5\\u671f <input id=\"nc-end\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\"></label>';"
1449
+ + "html+='</div>';"
1450
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u6838\\u5fc3\\u6761\\u6b3e <textarea id=\"nc-terms\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:64px\"></textarea></label>';"
1451
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u98ce\\u9669\\u5907\\u6ce8 <textarea id=\"nc-risk\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:48px\"></textarea></label>';"
1452
+ + "html+='</div>';"
1453
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1454
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
1455
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveNewContract(\\''+companyId+'\\')\">' + '\\u4fdd\\u5b58' + '</button>';"
1456
+ + "html+='</div></div></div>';"
1457
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1458
+ + "\nfunction saveNewContract(companyId){"
1459
+ + "var data={company_id:companyId,title:document.getElementById('nc-title').value,counterparty:document.getElementById('nc-counterparty').value,contract_type:document.getElementById('nc-type').value,amount:parseFloat(document.getElementById('nc-amount').value)||0,start_date:document.getElementById('nc-start').value,end_date:document.getElementById('nc-end').value,key_terms:document.getElementById('nc-terms').value,risk_notes:document.getElementById('nc-risk').value};"
1460
+ + "fetch('/opc/admin/api/contracts/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1461
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5408\\u540c\\u5df2\\u65b0\\u5efa');showCompany(companyId);}"
1462
+ + "else{showToast(d.error||'\\u65b0\\u5efa\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1463
+ // ── addTransaction ──
1464
+ + "\nfunction addTransaction(companyId){"
1465
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1466
+ + "var today=new Date().toISOString().slice(0,10);"
1467
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1468
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1469
+ + "html+='<h2 style=\"margin:0 0 20px\">\\u65b0\\u589e\\u4ea4\\u6613</h2>';"
1470
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1471
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u7c7b\\u578b <select id=\"tx-type\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"income\">\\u6536\\u5165</option><option value=\"expense\">\\u652f\\u51fa</option></select></label>';"
1472
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5206\\u7c7b <input id=\"tx-category\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\\u5982: \\u670d\\u52a1\\u8d39\\u3001\\u529e\\u516c\\u8d39...\"></label>';"
1473
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u91d1\\u989d (\\u5143) <input id=\"tx-amount\" type=\"number\" min=\"0\" step=\"0.01\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" value=\"0\"></label>';"
1474
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u63cf\\u8ff0 <input id=\"tx-desc\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1475
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5bf9\\u65b9 <input id=\"tx-counterparty\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1476
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u4ea4\\u6613\\u65e5\\u671f <input id=\"tx-date\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\" value=\"'+today+'\"></label>';"
1477
+ + "html+='</div>';"
1478
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1479
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
1480
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveTransaction(\\''+companyId+'\\')\">' + '\\u4fdd\\u5b58' + '</button>';"
1481
+ + "html+='</div></div></div>';"
1482
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1483
+ + "\nfunction saveTransaction(companyId){"
1484
+ + "var data={company_id:companyId,type:document.getElementById('tx-type').value,category:document.getElementById('tx-category').value,amount:parseFloat(document.getElementById('tx-amount').value)||0,description:document.getElementById('tx-desc').value,counterparty:document.getElementById('tx-counterparty').value,transaction_date:document.getElementById('tx-date').value};"
1485
+ + "fetch('/opc/admin/api/transactions/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1486
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u4ea4\\u6613\\u5df2\\u65b0\\u589e');showCompany(companyId);}"
1487
+ + "else{showToast(d.error||'\\u65b0\\u589e\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
1488
+ // ── editCompany (内联编辑) ──
1489
+ + "\nfunction editCompany(id,name,industry,ownerName,ownerContact,desc,capital,status){"
1490
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1491
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1492
+ + "html+='<h2 style=\"margin:0 0 20px\">\\u7f16\\u8f91\\u516c\\u53f8\\u4fe1\\u606f</h2>';"
1493
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1494
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u516c\\u53f8\\u540d\\u79f0 <input id=\"ef-name\" class=\"search-bar\" style=\"width:100%;padding:8px 12px;margin-top:4px\" value=\"'+esc(name)+'\"></label>';"
1495
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u884c\\u4e1a <input id=\"ef-industry\" class=\"search-bar\" style=\"width:100%;padding:8px 12px;margin-top:4px\" value=\"'+esc(industry)+'\"></label>';"
1496
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u8d1f\\u8d23\\u4eba <input id=\"ef-owner\" class=\"search-bar\" style=\"width:100%;padding:8px 12px;margin-top:4px\" value=\"'+esc(ownerName)+'\"></label>';"
1497
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u8054\\u7cfb\\u65b9\\u5f0f <input id=\"ef-contact\" class=\"search-bar\" style=\"width:100%;padding:8px 12px;margin-top:4px\" value=\"'+esc(ownerContact)+'\"></label>';"
1498
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u6ce8\\u518c\\u8d44\\u672c (\\u5143) <input id=\"ef-capital\" class=\"search-bar\" style=\"width:100%;padding:8px 12px;margin-top:4px\" type=\"number\" value=\"'+esc(capital)+'\"></label>';"
1499
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u516c\\u53f8\\u72b6\\u6001';"
1500
+ + "html+='<select id=\"ef-status\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\">';"
1501
+ + "var statusOpts=[['pending','\\u5f85\\u6ce8\\u518c'],['active','\\u8fd0\\u8425\\u4e2d'],['suspended','\\u5df2\\u6682\\u505c'],['terminated','\\u5df2\\u6ce8\\u9500'],['acquired','\\u5df2\\u6536\\u8d2d']];"
1502
+ + "statusOpts.forEach(function(o){html+='<option value=\"'+o[0]+'\"'+(status===o[0]?' selected':'')+'>'+o[1]+'</option>';});"
1503
+ + "html+='</select></label>';"
1504
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u516c\\u53f8\\u7b80\\u4ecb <textarea id=\"ef-desc\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:72px\">'+esc(desc)+'</textarea></label>';"
1505
+ + "html+='</div>';"
1506
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1507
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
1508
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveCompany(\\''+id+'\\')\">' + '\\u4fdd\\u5b58' + '</button>';"
1509
+ + "html+='</div></div></div>';"
1510
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1511
+ + "\nfunction saveCompany(id){"
1512
+ + "var data={name:document.getElementById('ef-name').value,industry:document.getElementById('ef-industry').value,"
1513
+ + "owner_name:document.getElementById('ef-owner').value,owner_contact:document.getElementById('ef-contact').value,"
1514
+ + "description:document.getElementById('ef-desc').value,registered_capital:parseFloat(document.getElementById('ef-capital').value)||0,"
1515
+ + "status:document.getElementById('ef-status').value};"
1516
+ + "fetch('/opc/admin/api/companies/'+encodeURIComponent(id)+'/edit',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1517
+ + ".then(function(r){return r.json()}).then(function(d){"
1518
+ + "if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u4fdd\\u5b58\\u6210\\u529f');loadCompanyDetail(id);}"
1519
+ + "else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u64cd\\u4f5c\\u5931\\u8d25');});}"
1520
+ // ── loadGuide (SOP) ──
1521
+ + "\nfunction loadGuide(){var el=document.getElementById('guide-content');if(!el)return;el.innerHTML=renderSopGuide();}"
1522
+ + getGuideJs()
1523
+ // ── loadConfig (tools) ──
1524
+ + "\nfunction loadConfig(){fetch('/opc/admin/api/config').then(function(r){return r.json()}).then(function(data){toolConfig=data;renderTools();}).catch(function(){toolConfig={};renderTools();});}"
1525
+ + "\nfunction renderTools(){"
1526
+ + "var list=document.getElementById('tool-list');"
1527
+ + "var h='<div class=\"tool-grid\">';"
1528
+ + "TOOLS.forEach(function(t){"
1529
+ + "var enabled=toolConfig[t.key]!=='disabled';"
1530
+ + "var prompt_=toolConfig[t.key+'_prompt']||'';"
1531
+ + "var pri=toolConfig[t.key+'_priority']||'normal';"
1532
+ + "var notes=toolConfig[t.key+'_notes']||'';"
1533
+ + "h+='<div class=\"tool-card'+(enabled?'':' disabled')+'\" id=\"tcard-'+esc(t.key)+'\">';"
1534
+ // header
1535
+ + "h+='<div class=\"tool-card-header\"><div><div class=\"name\">'+esc(t.label)+'</div><div class=\"key\">'+esc(t.key)+'</div></div><label class=\"toggle\"><input type=\"checkbox\" '+(enabled?'checked':'')+' onchange=\"toggleTool(\\''+esc(t.key)+'\\',this.checked)\"><span class=\"slider\"></span></label></div>';"
1536
+ // body
1537
+ + "h+='<div class=\"tool-card-body\"><div class=\"desc\">'+esc(t.desc)+'</div>';"
1538
+ + "h+='<div class=\"field\"><label>\\u4f18\\u5148\\u7ea7</label><select id=\"pri-'+esc(t.key)+'\" onchange=\"saveToolField(\\''+esc(t.key)+'\\',\\'priority\\',this.value)\"><option value=\"high\"'+(pri==='high'?' selected':'')+'>\\u9ad8 - \\u4f18\\u5148\\u8c03\\u7528</option><option value=\"normal\"'+(pri==='normal'?' selected':'')+'>\\u6b63\\u5e38</option><option value=\"low\"'+(pri==='low'?' selected':'')+'>\\u4f4e - \\u6309\\u9700\\u8c03\\u7528</option></select></div>';"
1539
+ + "h+='<button class=\"tool-expand-btn\" onclick=\"toggleToolSettings(\\''+esc(t.key)+'\\')\">' + '\\u2699 \\u9ad8\\u7ea7\\u914d\\u7f6e' + '</button>';"
1540
+ + "h+='</div>';"
1541
+ // expandable settings
1542
+ + "h+='<div class=\"tool-settings\" id=\"tsettings-'+esc(t.key)+'\">';"
1543
+ + "h+='<div class=\"field\"><label>\\u81ea\\u5b9a\\u4e49\\u63d0\\u793a\\u8bcd (System Prompt)</label><textarea id=\"prompt-'+esc(t.key)+'\" placeholder=\"\\u8f93\\u5165\\u81ea\\u5b9a\\u4e49\\u6307\\u4ee4\\uff0c\\u5f71\\u54cd\\u8be5\\u5de5\\u5177\\u7684\\u884c\\u4e3a\\u65b9\\u5f0f...\" onblur=\"saveToolField(\\''+esc(t.key)+'\\',\\'prompt\\',this.value)\">'+esc(prompt_)+'</textarea></div>';"
1544
+ + "h+='<div class=\"field\"><label>\\u5907\\u6ce8</label><input type=\"text\" id=\"notes-'+esc(t.key)+'\" placeholder=\"\\u5185\\u90e8\\u5907\\u6ce8\\uff0c\\u4ec5\\u7ba1\\u7406\\u5458\\u53ef\\u89c1...\" value=\"'+esc(notes)+'\" onblur=\"saveToolField(\\''+esc(t.key)+'\\',\\'notes\\',this.value)\"/></div>';"
1545
+ + "h+='</div>';"
1546
+ // footer
1547
+ + "h+='<div class=\"tool-card-footer\"><span style=\"font-size:12px;color:var(--tx2)\">\\u4f18\\u5148\\u7ea7: '+(pri==='high'?'\\u9ad8':pri==='low'?'\\u4f4e':'\\u6b63\\u5e38')+'</span><span class=\"badge '+(enabled?'badge-active':'badge-other')+'\">'+(enabled?'\\u5df2\\u542f\\u7528':'\\u5df2\\u7981\\u7528')+'</span></div>';"
1548
+ + "h+='</div>';});"
1549
+ + "h+='</div>';"
1550
+ + "list.innerHTML=h;"
1551
+ // ── Webhook URL section — only update the input value ──
1552
+ + "var wurl=toolConfig['webhook_url']||'';"
1553
+ + "var winput=document.getElementById('webhook-url-input');if(winput)winput.value=wurl;"
1554
+ + "}"
1555
+ + "\nfunction saveWebhookUrl(){var v=document.getElementById('webhook-url-input').value.trim();toolConfig['webhook_url']=v;saveConfig(function(){showToast(v?'Webhook \\u5df2\\u4fdd\\u5b58':'\\u5df2\\u6e05\\u9664 Webhook');});}"
1556
+ + "\nfunction testWebhook(){var v=document.getElementById('webhook-url-input').value.trim();if(!v){showToast('\\u8bf7\\u5148\\u8f93\\u5165 Webhook \\u5730\\u5740');return;}fetch('/opc/admin/api/webhook-test',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:v})}).then(function(r){return r.json()}).then(function(d){showToast(d.ok?'\\u6d4b\\u8bd5\\u6210\\u529f\\uff0c\\u8bf7\\u67e5\\u770b\\u673a\\u5668\\u4eba\\u6d88\\u606f':'\\u6d4b\\u8bd5\\u5931\\u8d25: '+(d.error||''));}).catch(function(){showToast('\\u8bf7\\u6c42\\u5f02\\u5e38');});}"
1557
+ + "\nfunction toggleTool(key,enabled){toolConfig[key]=enabled?'enabled':'disabled';saveConfig(function(){showToast((enabled?'\\u5df2\\u542f\\u7528':'\\u5df2\\u7981\\u7528')+' '+key);renderTools();});}"
1558
+ + "\nfunction saveToolField(key,field,value){toolConfig[key+'_'+field]=value;saveConfig(function(){showToast('\\u5df2\\u4fdd\\u5b58 '+key+' '+field);});}"
1559
+ + "\nfunction toggleToolSettings(key){var el=document.getElementById('tsettings-'+key);if(el)el.classList.toggle('open');}"
1560
+ + "\nfunction saveConfig(cb){fetch('/opc/admin/api/config',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(toolConfig)}).then(function(){if(cb)cb();}).catch(function(){showToast('\\u4fdd\\u5b58\\u5931\\u8d25');});}"
1561
+ + getSkillsJs();
1562
+ }
1563
+
1564
+ function getSkillsJs(): string {
1565
+ return ""
1566
+ // ── Skills 管理 ──
1567
+ + "\nvar _installedSkillsCache={builtin:[],custom:[]};"
1568
+ + "\nvar skillCreateTab='wizard';"
1569
+ + "\nfunction loadSkills(){"
1570
+ + "var el=document.getElementById('skills-content');if(!el)return;"
1571
+ + "el.innerHTML='<div class=\"skeleton\" style=\"height:200px\"></div>';"
1572
+ + "Promise.all(["
1573
+ + "fetch('/opc/admin/api/skills/installed').then(function(r){return r.json()}).catch(function(){return {builtin:[],custom:[]}}),"
1574
+ + "fetch('/opc/admin/api/companies').then(function(r){return r.json()}).catch(function(){return []}),"
1575
+ + "]).then(function(results){"
1576
+ + "var installed=results[0];var companies=results[1];"
1577
+ + "_installedSkillsCache=installed;"
1578
+ + "renderSkillsView(el,installed,companies);"
1579
+ + "});}"
1580
+ + "\nfunction renderSkillsView(el,installed,companies){"
1581
+ + "var h='';"
1582
+ // Card 1: installed skills
1583
+ + "h+='<div class=\"card\" style=\"margin-bottom:16px\">';"
1584
+ + "h+='<div class=\"card-header\" style=\"display:flex;justify-content:space-between;align-items:center\"><h3 style=\"margin:0\">\u5df2\u5b89\u88c5 Skills</h3><button class=\"btn\" onclick=\"loadSkills()\">\u5237\u65b0</button></div>';"
1585
+ + "h+='<div class=\"card-body\">';"
1586
+ + "if(installed.builtin&&installed.builtin.length){"
1587
+ + "h+='<p style=\"font-size:12px;font-weight:600;color:var(--tx2);margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em\">\u5185\u7f6e Skills</p>';"
1588
+ + "h+='<div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:8px;margin-bottom:16px\">';"
1589
+ + "installed.builtin.forEach(function(sk){"
1590
+ + "var shortDesc=sk.desc?sk.desc.substring(0,55)+(sk.desc.length>55?'...':''):'';var emoji=sk.emoji||'\ud83d\udccc';"
1591
+ + "h+='<div class=\"skill-card\"><div class=\"skill-card-emoji\">'+emoji+'</div><div class=\"skill-card-info\"><div class=\"skill-card-name\">'+esc(sk.name)+'</div>'+(shortDesc?'<div class=\"skill-card-desc\" title=\"'+esc(sk.desc||'')+'\">'+esc(shortDesc)+'</div>':'')+'</div><span class=\"skill-badge badge-builtin\">\u5185\u7f6e</span></div>';"
1592
+ + "});"
1593
+ + "h+='</div>';}"
1594
+ + "if(installed.custom&&installed.custom.length){"
1595
+ + "h+='<p style=\"font-size:12px;font-weight:600;color:var(--tx2);margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em\">\u81ea\u5b9a\u4e49 Skills</p>';"
1596
+ + "h+='<div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:8px;margin-bottom:16px\">';"
1597
+ + "installed.custom.forEach(function(sk){"
1598
+ + "var shortDesc=sk.desc?sk.desc.substring(0,55)+(sk.desc.length>55?'...':''):'';var emoji=sk.emoji||'\u2728';"
1599
+ + "h+='<div class=\"skill-card\"><div class=\"skill-card-emoji\">'+emoji+'</div><div class=\"skill-card-info\"><div class=\"skill-card-name\">'+esc(sk.name)+'</div>'+(shortDesc?'<div class=\"skill-card-desc\" title=\"'+esc(sk.desc||'')+'\">'+esc(shortDesc)+'</div>':'')+'</div><span class=\"skill-badge badge-custom\">\u81ea\u5b9a\u4e49</span><button class=\"btn\" style=\"margin-left:8px;padding:2px 8px;color:#dc2626;border-color:#fca5a5;font-size:12px;flex-shrink:0\" onclick=\"deleteCustomSkill(\\''+esc(sk.name)+'\\')\">&#10005;</button></div>';"
1600
+ + "});"
1601
+ + "h+='</div>';}"
1602
+ + "if((!installed.builtin||!installed.builtin.length)&&(!installed.custom||!installed.custom.length)){"
1603
+ + "h+='<p style=\"color:var(--tx2);font-size:13px\">\u6682\u65e0\u5df2\u5b89\u88c5 Skills</p>';}"
1604
+ + "h+='</div></div>';"
1605
+ // Card 2: company skills config
1606
+ + "h+='<div class=\"card\" style=\"margin-bottom:16px\">';"
1607
+ + "h+='<div class=\"card-header\"><h3 style=\"margin:0\">\u516c\u53f8 Skills \u914d\u7f6e</h3></div>';"
1608
+ + "h+='<div class=\"card-body\">';"
1609
+ + "h+='<p style=\"color:var(--tx2);font-size:13px;margin-bottom:16px\">\u4e3a\u6bcf\u5bb6\u516c\u53f8\u914d\u7f6e\u5176 Agent \u53ef\u7528\u7684 Skills\uff0c\u5f71\u54cd Agent \u53ef\u7528\u5de5\u5177\u548c\u4e0a\u4e0b\u6587\u3002</p>';"
1610
+ + "h+='<div style=\"display:flex;gap:8px;align-items:center;margin-bottom:12px\"><select id=\"skills-company-select\" style=\"flex:1;padding:8px 12px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\" onchange=\"loadCompanySkills()\"><option value=\"\">\u8bf7\u9009\u62e9\u516c\u53f8...</option>';"
1611
+ + "companies.forEach(function(c){h+='<option value=\"'+esc(c.id)+'\">'+esc(c.name)+' ('+esc(c.status)+')</option>';});"
1612
+ + "h+='</select></div>';"
1613
+ + "h+='<div id=\"skills-checkboxes\" style=\"display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px\"></div>';"
1614
+ + "h+='<div style=\"display:flex;gap:8px\"><button class=\"btn btn-pri\" onclick=\"saveCompanySkills()\">\u4fdd\u5b58 Skills</button><span id=\"skills-status\" style=\"font-size:12px;color:var(--tx2);align-self:center\"></span></div>';"
1615
+ + "h+='</div></div>';"
1616
+ // Card 3: GitHub install
1617
+ + "h+='<div class=\"card\" style=\"margin-bottom:16px\">';"
1618
+ + "h+='<div class=\"card-header\"><h3 style=\"margin:0\">\u4ece GitHub \u5b89\u88c5</h3></div>';"
1619
+ + "h+='<div class=\"card-body\">';"
1620
+ + "h+='<p style=\"color:var(--tx2);font-size:13px;margin-bottom:16px\">\u8f93\u5165 GitHub \u4ed3\u5e93\u5730\u5740\uff08\u683c\u5f0f\uff1auser/repo\uff09\uff0c\u5c06 Skill \u5b89\u88c5\u5230 ~/.openclaw/custom-skills/\u3002</p>';"
1621
+ + "h+='<div style=\"display:flex;gap:8px;margin-bottom:12px\"><input id=\"github-repo-input\" type=\"text\" class=\"form-input\" placeholder=\"user/repo \u6216 https://github.com/user/repo\" style=\"flex:1\"><button class=\"btn btn-pri\" onclick=\"installGithubSkill()\">\u5b89\u88c5</button></div>';"
1622
+ + "h+='<div id=\"github-install-status\" style=\"font-size:13px;color:var(--tx2)\"></div>';"
1623
+ + "h+='</div></div>';"
1624
+ // Card 4: create custom skill
1625
+ + "h+='<div class=\"card\">';"
1626
+ + "h+='<div class=\"card-header\"><h3 style=\"margin:0\">\u521b\u5efa\u81ea\u5b9a\u4e49 Skill</h3></div>';"
1627
+ + "h+='<div class=\"card-body\">';"
1628
+ + "h+='<div class=\"tab-bar\"><button id=\"tab-wizard\" class=\"active\" onclick=\"switchSkillTab(\\'wizard\\')\"> \u5411\u5bfc\u6a21\u5f0f</button><button id=\"tab-markdown\" onclick=\"switchSkillTab(\\'markdown\\')\"> Markdown \u7f16\u8f91</button></div>';"
1629
+ // Wizard form
1630
+ + "h+='<div id=\"skill-form-wizard\">';"
1631
+ + "h+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px\">';"
1632
+ + "h+='<div><label style=\"font-size:12px;font-weight:600;color:var(--tx2);display:block;margin-bottom:4px\">Skill \u540d\u79f0 (a-z0-9-)</label><input id=\"skill-name-input\" class=\"form-input\" placeholder=\"my-skill\" type=\"text\" style=\"width:100%\"></div>';"
1633
+ + "h+='<div><label style=\"font-size:12px;font-weight:600;color:var(--tx2);display:block;margin-bottom:4px\">Emoji \u56fe\u6807</label><input id=\"skill-emoji-input\" class=\"form-input\" placeholder=\"\u2728\" type=\"text\" style=\"width:100%\"></div>';"
1634
+ + "h+='</div>';"
1635
+ + "h+='<div style=\"margin-bottom:12px\"><label style=\"font-size:12px;font-weight:600;color:var(--tx2);display:block;margin-bottom:4px\">\u63cf\u8ff0</label><input id=\"skill-desc-input\" class=\"form-input\" placeholder=\"\u8be5 Skill \u7684\u529f\u80fd\u63cf\u8ff0\" type=\"text\" style=\"width:100%\"></div>';"
1636
+ + "h+='<div style=\"margin-bottom:16px\"><label style=\"font-size:12px;font-weight:600;color:var(--tx2);display:block;margin-bottom:4px\">Skill \u5185\u5bb9\uff08\u63d0\u793a\u8bcd / \u6307\u5bfc\u8bed\uff09</label><textarea id=\"skill-content-input\" class=\"form-input\" rows=\"5\" placeholder=\"\u5199\u51fa\u8be5 Skill \u7684\u5177\u4f53\u6307\u5bfc\u5185\u5bb9...\" style=\"width:100%;resize:vertical\"></textarea></div>';"
1637
+ + "h+='</div>';"
1638
+ // Markdown editor
1639
+ + "h+='<div id=\"skill-form-markdown\" style=\"display:none\">';"
1640
+ + "h+='<p style=\"color:var(--tx2);font-size:13px;margin-bottom:8px\">\u76f4\u63a5\u8f93\u5165\u5b8c\u6574\u7684 SKILL.md \u5185\u5bb9\uff0c\u9996\u884c\u5fc5\u987b\u4e3a <code>name: your-skill-name</code>\u3002</p>';"
1641
+ + "h+='<textarea id=\"skill-raw-input\" class=\"form-input\" rows=\"10\" placeholder=\"name: my-skill\\ndescription: \\u63cf\\u8ff0\\n\\n# \\u6307\\u5bfc\\u5185\\u5bb9...\" style=\"width:100%;resize:vertical;font-family:monospace;font-size:13px\"></textarea>';"
1642
+ + "h+='</div>';"
1643
+ + "h+='<div style=\"display:flex;gap:8px;align-items:center\"><button class=\"btn btn-pri\" onclick=\"createSkill()\">\u521b\u5efa Skill</button><span id=\"skill-create-status\" style=\"font-size:12px;color:var(--tx2)\"></span></div>';"
1644
+ + "h+='</div></div>';"
1645
+ + "el.innerHTML=h;}"
1646
+ + "\nfunction switchSkillTab(tab){"
1647
+ + "skillCreateTab=tab;"
1648
+ + "document.getElementById('tab-wizard').className=tab==='wizard'?'active':'';"
1649
+ + "document.getElementById('tab-markdown').className=tab==='markdown'?'active':'';"
1650
+ + "document.getElementById('skill-form-wizard').style.display=tab==='wizard'?'block':'none';"
1651
+ + "document.getElementById('skill-form-markdown').style.display=tab==='markdown'?'block':'none';}"
1652
+ + "\nfunction loadCompanySkills(){"
1653
+ + "var sel=document.getElementById('skills-company-select');if(!sel)return;"
1654
+ + "var companyId=sel.value;if(!companyId){document.getElementById('skills-checkboxes').innerHTML='';return;}"
1655
+ + "fetch('/opc/admin/api/company-skills?company_id='+encodeURIComponent(companyId)).then(function(r){return r.json()}).then(function(d){"
1656
+ + "var enabled=d.skills||[];"
1657
+ + "var allSkills=(_installedSkillsCache.builtin||[]).concat(_installedSkillsCache.custom||[]).map(function(m){return m.name;});"
1658
+ + "if(!allSkills.length)allSkills=enabled.length?enabled:[];"
1659
+ + "var box=document.getElementById('skills-checkboxes');if(!box)return;"
1660
+ + "box.innerHTML='';"
1661
+ + "allSkills.forEach(function(sk){"
1662
+ + "var checked=enabled.indexOf(sk)>-1;"
1663
+ + "var label=document.createElement('label');"
1664
+ + "label.style.cssText='display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border:1px solid var(--bd);border-radius:6px;font-size:13px;cursor:pointer;background:'+(checked?'#f0fdf4':'var(--card)')+';border-color:'+(checked?'#86efac':'var(--bd)')+';';"
1665
+ + "label.innerHTML='<input type=\"checkbox\" value=\"'+esc(sk)+'\"'+(checked?' checked':'')+' style=\"margin:0\" onchange=\"updateSkillLabel(this)\"> '+esc(sk);"
1666
+ + "box.appendChild(label);"
1667
+ + "});"
1668
+ + "document.getElementById('skills-status').textContent='\u5df2\u52a0\u8f7d '+enabled.length+' \u4e2a skills';"
1669
+ + "}).catch(function(e){document.getElementById('skills-status').textContent='\u52a0\u8f7d\u5931\u8d25: '+String(e);});}"
1670
+ + "\nfunction updateSkillLabel(input){"
1671
+ + "var label=input.closest('label');if(!label)return;"
1672
+ + "if(input.checked){label.style.background='#f0fdf4';label.style.borderColor='#86efac';}"
1673
+ + "else{label.style.background='var(--card)';label.style.borderColor='var(--bd)';}}"
1674
+ + "\nfunction saveCompanySkills(){"
1675
+ + "var sel=document.getElementById('skills-company-select');if(!sel)return;"
1676
+ + "var companyId=sel.value;if(!companyId){showToast('\u8bf7\u5148\u9009\u62e9\u516c\u53f8');return;}"
1677
+ + "var checks=document.querySelectorAll('#skills-checkboxes input[type=checkbox]');"
1678
+ + "var skills=[];checks.forEach(function(cb){if(cb.checked)skills.push(cb.value);});"
1679
+ + "fetch('/opc/admin/api/company-skills',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({company_id:companyId,skills:skills})})"
1680
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){showToast('Skills \u5df2\u4fdd\u5b58 ('+skills.length+' \u4e2a)');document.getElementById('skills-status').textContent='\u5df2\u4fdd\u5b58 '+skills.length+' \u4e2a skills';}"
1681
+ + "else{showToast(d.error||'\u4fdd\u5b58\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1682
+ + "\nfunction installGithubSkill(){"
1683
+ + "var repo=document.getElementById('github-repo-input').value.trim();"
1684
+ + "if(!repo){showToast('\u8bf7\u8f93\u5165\u4ed3\u5e93\u5730\u5740');return;}"
1685
+ + "var statusEl=document.getElementById('github-install-status');"
1686
+ + "statusEl.textContent='\u5b89\u88c5\u4e2d...';"
1687
+ + "fetch('/opc/admin/api/skills/github-install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({repo:repo})})"
1688
+ + ".then(function(r){return r.json()}).then(function(d){"
1689
+ + "if(d.ok){statusEl.style.color='#166534';statusEl.textContent=d.message||'\u5b89\u88c5\u6210\u529f\uff01';document.getElementById('github-repo-input').value='';loadSkills();}"
1690
+ + "else{statusEl.style.color='#dc2626';statusEl.textContent='\u5b89\u88c5\u5931\u8d25: '+(d.error||'\u672a\u77e5\u9519\u8bef');}}"
1691
+ + ").catch(function(e){statusEl.style.color='#dc2626';statusEl.textContent='\u8bf7\u6c42\u5f02\u5e38: '+String(e);});}"
1692
+ + "\nfunction createSkill(){"
1693
+ + "var statusEl=document.getElementById('skill-create-status');"
1694
+ + "var body;"
1695
+ + "if(skillCreateTab==='wizard'){"
1696
+ + "var name=document.getElementById('skill-name-input').value.trim();"
1697
+ + "var emoji=document.getElementById('skill-emoji-input').value.trim();"
1698
+ + "var desc=document.getElementById('skill-desc-input').value.trim();"
1699
+ + "var content=document.getElementById('skill-content-input').value.trim();"
1700
+ + "if(!name){showToast('\u8bf7\u8f93\u5165 Skill \u540d\u79f0');return;}"
1701
+ + "body={name:name,description:desc,emoji:emoji,content:content};"
1702
+ + "}else{"
1703
+ + "var raw=document.getElementById('skill-raw-input').value.trim();"
1704
+ + "if(!raw){showToast('\u8bf7\u8f93\u5165 Skill \u5185\u5bb9');return;}"
1705
+ + "var nameMatch=raw.match(/^name:\\s*([\\w-]+)/m);"
1706
+ + "if(!nameMatch){showToast('\u5185\u5bb9\u5fc5\u987b\u5305\u542b name: \u5b57\u6bb5');return;}"
1707
+ + "body={name:nameMatch[1],raw:raw};"
1708
+ + "}"
1709
+ + "statusEl.textContent='\u521b\u5efa\u4e2d...';"
1710
+ + "fetch('/opc/admin/api/skills/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})"
1711
+ + ".then(function(r){return r.json()}).then(function(d){"
1712
+ + "if(d.ok){statusEl.style.color='#166534';statusEl.textContent='\u521b\u5efa\u6210\u529f\uff01';loadSkills();}"
1713
+ + "else{statusEl.style.color='#dc2626';statusEl.textContent='\u5931\u8d25: '+(d.error||'\u672a\u77e5\u9519\u8bef');}}"
1714
+ + ").catch(function(e){statusEl.style.color='#dc2626';statusEl.textContent='\u8bf7\u6c42\u5f02\u5e38: '+String(e);});}"
1715
+ + "\nfunction deleteCustomSkill(name){"
1716
+ + "if(!confirm('\u786e\u5b9a\u8981\u5220\u9664 Skill \"'+name+'\" \u5417\uff1f\u6b64\u64cd\u4f5c\u4e0d\u53ef\u8fd8\u539f\u3002'))return;"
1717
+ + "fetch('/opc/admin/api/skills/custom/'+encodeURIComponent(name),{method:'DELETE'})"
1718
+ + ".then(function(r){return r.json()}).then(function(d){"
1719
+ + "if(d.ok){showToast('Skill \u5df2\u5220\u9664');loadSkills();}"
1720
+ + "else{showToast('\u5220\u9664\u5931\u8d25: '+(d.error||'\u672a\u77e5\u9519\u8bef'));}})"
1721
+ + ".catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1722
+ // ── hash routing ──
1723
+ + "\nfunction handleHash(){var hash=window.location.hash.replace('#','');if(!hash||hash==='dashboard'){showView('dashboard');return;}if(hash==='companies'){showView('companies');return;}if(hash==='finance'){showView('finance');return;}if(hash==='monitoring'){showView('monitoring');return;}if(hash==='tools'){showView('tools');return;}if(hash==='closure'){showView('closure');loadClosure();return;}if(hash==='guide'){showView('guide');return;}if(hash==='canvas'){showView('canvas');return;}if(hash.indexOf('company/')===0){var cid=hash.slice(8);showCompany(cid);return;}showView('dashboard');}"
1724
+ + getClosureJs();
1725
+ }
1726
+
1727
+ function getClosureJs(): string {
1728
+ return ""
1729
+ + "\nfunction loadClosure(){"
1730
+ + "var el=document.getElementById('closure-content');"
1731
+ + "el.innerHTML='<div class=\"skeleton\" style=\"height:200px\"></div>';"
1732
+ + "Promise.all(["
1733
+ + " fetch('/opc/admin/api/closure/summary').then(function(r){return r.json()}),"
1734
+ + " fetch('/opc/admin/api/closure/acquisitions').then(function(r){return r.json()}),"
1735
+ + " fetch('/opc/admin/api/closure/packages').then(function(r){return r.json()}),"
1736
+ + " fetch('/opc/admin/api/closure/transfers').then(function(r){return r.json()})"
1737
+ + "]).then(function(results){"
1738
+ + "var summary=results[0];var acqs=results[1];var pkgs=results[2];var transfers=results[3];"
1739
+ + "var h='';"
1740
+ // 资金闭环模型图(顶部)
1741
+ + "h+='<div class=\"card\" style=\"margin-bottom:20px;background:linear-gradient(135deg,#f0f7ff 0%,#f5f3ff 100%);border-color:#c7d2fe\">';"
1742
+ + "h+='<div class=\"card-body\">';"
1743
+ + "h+='<div style=\"font-size:12px;font-weight:700;color:#4338ca;letter-spacing:0.06em;text-transform:uppercase;margin-bottom:14px\">\\u661f\\u73af OPC \\u8d44\\u91d1\\u95ed\\u73af\\u6a21\\u578b</div>';"
1744
+ + "var loopSteps=['\\u6295\\u8d44\\u53c2\\u80a1|\\u57ce\\u6295\\u516c\\u53f8\\u53c2\\u8d44\\u5b54\\u5316\\u4f01\\u4e1a|\\u5165\\u8d44','\\u670d\\u52a1\\u91c7\\u8d2d|\\u4f01\\u4e1a\\u5411\\u5e73\\u53f0\\u91c7\\u8d2d\\u63d0\\u5347\\u670d\\u52a1|\\u91c7\\u8d2d','\\u8d44\\u91d1\\u56de\\u6d41|\\u670d\\u52a1\\u8d39\\u6536\\u5165\\u56de\\u6d41\\u5e73\\u53f0|\\u56de\\u6d41','\\u8d44\\u4ea7\\u8f6c\\u8ba9|\\u6253\\u5305\\u4f18\\u8d28\\u8d44\\u4ea7\\u8f6c\\u8ba9\\u57ce\\u6295|\\u8f6c\\u8ba9','\\u878d\\u8d44\\u670d\\u52a1\\u8d39|\\u57ce\\u6295\\u878d\\u8d44\\u6536\\u53d6\\u670d\\u52a1\\u8d39\\u7528|\\u8d39\\u7528'];"
1745
+ + "var loopColors=['#2563eb','#7c3aed','#0891b2','#059669','#d97706'];"
1746
+ + "var loopBg=['#eff6ff','#f5f3ff','#ecfeff','#ecfdf5','#fffbeb'];"
1747
+ + "var loopBd=['#bfdbfe','#ddd6fe','#a5f3fc','#6ee7b7','#fde68a'];"
1748
+ + "function mkLoopCard(i){var s=loopSteps[i];var parts=s.split('|');return '<div style=\"background:'+loopBg[i]+';border:1px solid '+loopBd[i]+';border-radius:10px;padding:14px 12px;box-sizing:border-box;height:120px;display:flex;flex-direction:column;gap:3px\"><div style=\"font-size:11px;font-weight:800;color:'+loopColors[i]+';opacity:0.4\">'+(i+1)+'</div><div style=\"font-size:13px;font-weight:700;color:'+loopColors[i]+'\">'+parts[0]+'</div><div style=\"font-size:11px;color:#475569;line-height:1.5;flex:1\">'+parts[1]+'</div><div style=\"font-size:10px;font-weight:700;color:'+loopColors[i]+';background:white;padding:2px 6px;border-radius:4px;width:fit-content\">'+parts[2]+'</div></div>';}"
1749
+ + "var loopArrow='<div style=\"display:flex;align-items:center;justify-content:center;padding:0 8px;color:#94a3b8\"><svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\"><path d=\"M5 9h8M10 6l3 3-3 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg></div>';"
1750
+ // Single row: all 5 steps
1751
+ + "h+='<div style=\"display:flex;align-items:center;gap:0\">';"
1752
+ + "for(var li=0;li<loopSteps.length;li++){h+=mkLoopCard(li);if(li<loopSteps.length-1)h+=loopArrow;}"
1753
+ + "h+='</div>';"
1754
+ + "h+='</div></div>';"
1755
+ // 汇总卡片
1756
+ + "h+='<div class=\"stats-grid\" style=\"grid-template-columns:repeat(4,1fr);margin-bottom:24px\">';"
1757
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\u6536\u5e76\u8d2d\u6848\u4f8b</div><div class=\"value\">'+summary.total_acquisitions+'</div></div>';"
1758
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\u8d44\u4ea7\u5305\u6570\u91cf</div><div class=\"value\">'+summary.total_packages+'</div></div>';"
1759
+ + "h+='<div class=\"stat-card\"><div class=\"label\">\u57ce\u6295\u8f6c\u8ba9\u603b\u989d</div><div class=\"value\">\xA5'+fmt(summary.total_transfer_price)+'</div></div>';"
1760
+ + "h+='<div class=\"stat-card\" style=\"border-left:4px solid var(--accent,#0ea5e9)\"><div class=\"label\">\u878d\u8d44\u670d\u52a1\u8d39\u6536\u5165</div><div class=\"value\" style=\"color:var(--accent,#0ea5e9)\">\xA5'+fmt(summary.total_financing_fee)+'</div></div>';"
1761
+ + "h+='</div>';"
1762
+ // 收并购列表
1763
+ + "h+='<div class=\"card\"><div class=\"card-header\" style=\"display:flex;justify-content:space-between;align-items:center\"><h3 style=\"margin:0\">\u6536\u5e76\u8d2d\u6848\u4f8b</h3><button class=\"btn btn-pri\" onclick=\"createAcquisition()\">' + '+ \u65b0\u5efa\u6536\u5e76\u8d2d' + '</button></div><div class=\"card-body\">';"
1764
+ + "if(acqs.length===0){h+='<p style=\"color:var(--tx2)\">\u6682\u65e0\u6536\u5e76\u8d2d\u8bb0\u5f55</p>';}"
1765
+ + "else{"
1766
+ + "h+='<table class=\"data-table\"><thead><tr><th>\u516c\u53f8</th><th>\u89e6\u53d1\u539f\u56e0</th><th>\u6536\u8d2d\u4ef7\u683c</th><th>\u4e8f\u635f\u91d1\u989d</th><th>\u7a0e\u52a1\u6293\u9664</th><th>\u72b6\u6001</th></tr></thead><tbody>';"
1767
+ + "acqs.forEach(function(a){"
1768
+ + "var statusMap={'evaluating':'\u8bc4\u4f30\u4e2d','in_progress':'\u8fdb\u884c\u4e2d','completed':'\u5df2\u5b8c\u6210','cancelled':'\u5df2\u53d6\u6d88'};"
1769
+ + "h+='<tr><td>'+esc(a.company_name||a.company_id)+'</td><td>'+esc(a.trigger_reason)+'</td><td>\xA5'+fmt(a.acquisition_price)+'</td><td>\xA5'+fmt(a.loss_amount)+'</td><td>\xA5'+fmt(a.tax_deduction)+'</td><td>'+esc(statusMap[a.status]||a.status)+'</td></tr>';"
1770
+ + "});"
1771
+ + "h+='</tbody></table>';}"
1772
+ + "h+='</div></div>';"
1773
+ // 资产包列表
1774
+ + "h+='<div class=\"card\" style=\"margin-top:16px\"><div class=\"card-header\" style=\"display:flex;justify-content:space-between;align-items:center\"><h3 style=\"margin:0\">\u8d44\u4ea7\u5305</h3><button class=\"btn btn-pri\" onclick=\"createAssetPackage()\">' + '+ \u65b0\u5efa\u8d44\u4ea7\u5305' + '</button></div><div class=\"card-body\">';"
1775
+ + "if(pkgs.length===0){h+='<p style=\"color:var(--tx2)\">\u6682\u65e0\u8d44\u4ea7\u5305</p>';}"
1776
+ + "else{"
1777
+ + "h+='<table class=\"data-table\"><thead><tr><th>\u540d\u79f0</th><th>\u5305\u542b\u516c\u53f8\u6570</th><th>\u79d1\u521b\u8ba4\u5b9a\u6570</th><th>\u603b\u4f30\u503c</th><th>\u72b6\u6001</th></tr></thead><tbody>';"
1778
+ + "pkgs.forEach(function(p){"
1779
+ + "var statusMap={'assembling':'\u6253\u5305\u4e2d','ready':'\u5df2\u5c31\u7eea','transferred':'\u5df2\u8f6c\u8ba9','closed':'\u5df2\u5173\u95ed'};"
1780
+ + "h+='<tr><td>'+esc(p.name)+'</td><td>'+p.company_count+'</td><td>'+p.sci_tech_certified+'</td><td>\xA5'+fmt(p.total_valuation)+'</td><td>'+esc(statusMap[p.status]||p.status)+'</td></tr>';"
1781
+ + "});"
1782
+ + "h+='</tbody></table>';}"
1783
+ + "h+='</div></div>';"
1784
+ // 城投转让列表
1785
+ + "h+='<div class=\"card\" style=\"margin-top:16px\"><div class=\"card-header\" style=\"display:flex;justify-content:space-between;align-items:center\"><h3 style=\"margin:0\">\u57ce\u6295\u8f6c\u8ba9\u4e0e\u79d1\u521b\u8d37</h3><button class=\"btn btn-pri\" onclick=\"createCtTransfer()\">' + '+ \u65b0\u5efa\u8f6c\u8ba9' + '</button></div><div class=\"card-body\">';"
1786
+ + "if(transfers.length===0){h+='<p style=\"color:var(--tx2)\">\u6682\u65e0\u8f6c\u8ba9\u8bb0\u5f55</p>';}"
1787
+ + "else{"
1788
+ + "h+='<table class=\"data-table\"><thead><tr><th>\u8d44\u4ea7\u5305</th><th>\u57ce\u6295\u516c\u53f8</th><th>\u8f6c\u8ba9\u4ef7\u683c</th><th>\u76ee\u6807\u79d1\u521b\u8d37</th><th>\u5b9e\u9645\u79d1\u521b\u8d37</th><th>\u72b6\u6001</th></tr></thead><tbody>';"
1789
+ + "transfers.forEach(function(t){"
1790
+ + "var statusMap={'negotiating':'\u6d3d\u8c08\u4e2d','signed':'\u5df2\u7b7e\u7ea6','completed':'\u5df2\u5b8c\u6210','cancelled':'\u5df2\u53d6\u6d88'};"
1791
+ + "h+='<tr><td>'+esc(t.package_name||t.package_id)+'</td><td>'+esc(t.ct_company)+'</td><td>\xA5'+fmt(t.transfer_price)+'</td><td>\xA5'+fmt(t.sci_loan_target)+'</td><td>\xA5'+fmt(t.sci_loan_actual)+'</td><td>'+esc(statusMap[t.status]||t.status)+'</td></tr>';"
1792
+ + "});"
1793
+ + "h+='</tbody></table>';}"
1794
+ + "h+='</div></div>';"
1795
+ + "el.innerHTML=h;"
1796
+ + "}).catch(function(){el.innerHTML='<p style=\"color:var(--err)\">\u52a0\u8f7d\u5931\u8d25</p>';});}"
1797
+ // ── 资金闭环 CREATE 函数 ──
1798
+ + "\nfunction createAcquisition(){"
1799
+ + "fetch('/opc/admin/api/companies?limit=200').then(function(r){return r.json()}).then(function(companies){"
1800
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1801
+ + "var opts=companies.map(function(c){return '<option value=\"'+esc(c.id)+'\">'+esc(c.name)+'</option>';}).join('');"
1802
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1803
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:520px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1804
+ + "html+='<h2 style=\"margin:0 0 20px\">\u65b0\u5efa\u6536\u5e76\u8d2d</h2>';"
1805
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1806
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u76ee\u6807\u516c\u53f8 <select id=\"acq-cid\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"\">\u8bf7\u9009\u62e9...</option>'+opts+'</select></label>';"
1807
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u89e6\u53d1\u539f\u56e0 <input id=\"acq-reason\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u5982\uff1a\u8fde\u7eed\u4e8f\u635f\u3001\u5e02\u573a\u8d4f\u7f29\"></label>';"
1808
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6536\u8d2d\u4ef7\u683c (\u5143) <input id=\"acq-price\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1809
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u7d2f\u8ba1\u4e8f\u635f (\u5143) <input id=\"acq-loss\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1810
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5907\u6ce8 <input id=\"acq-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1811
+ + "html+='</div>';"
1812
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1813
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1814
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveAcquisition()\">\u521b\u5efa</button>';"
1815
+ + "html+='</div></div></div>';"
1816
+ + "document.body.insertAdjacentHTML('beforeend',html);"
1817
+ + "}).catch(function(){showToast('\u52a0\u8f7d\u5931\u8d25');});}"
1818
+ + "\nfunction saveAcquisition(){"
1819
+ + "var data={company_id:document.getElementById('acq-cid').value,trigger_reason:document.getElementById('acq-reason').value,acquisition_price:Number(document.getElementById('acq-price').value)||0,loss_amount:Number(document.getElementById('acq-loss').value)||0,notes:document.getElementById('acq-notes').value};"
1820
+ + "if(!data.company_id){showToast('\u8bf7\u9009\u62e9\u76ee\u6807\u516c\u53f8');return;}"
1821
+ + "if(!data.trigger_reason){showToast('\u8bf7\u586b\u5199\u89e6\u53d1\u539f\u56e0');return;}"
1822
+ + "fetch('/opc/admin/api/closure/acquisitions/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1823
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u6536\u5e76\u8d2d\u5df2\u521b\u5efa');loadClosure();}"
1824
+ + "else{showToast(d.error||'\u521b\u5efa\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1825
+ // createAssetPackage
1826
+ + "\nfunction createAssetPackage(){"
1827
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1828
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1829
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw\">';"
1830
+ + "html+='<h2 style=\"margin:0 0 20px\">\u65b0\u5efa\u8d44\u4ea7\u5305</h2>';"
1831
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1832
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8d44\u4ea7\u5305\u540d\u79f0 <input id=\"pkg-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u5982\uff1a\u4ed1\u548c\u533a2026Q1\u79d1\u521b\u8d44\u4ea7\u5305\"></label>';"
1833
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8d44\u4ea7\u5305\u63cf\u8ff0 <textarea id=\"pkg-desc\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:80px\"></textarea></label>';"
1834
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5907\u6ce8 <input id=\"pkg-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1835
+ + "html+='</div>';"
1836
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1837
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1838
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveAssetPackage()\">\u521b\u5efa</button>';"
1839
+ + "html+='</div></div></div>';"
1840
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1841
+ + "\nfunction saveAssetPackage(){"
1842
+ + "var data={name:document.getElementById('pkg-name').value,description:document.getElementById('pkg-desc').value,notes:document.getElementById('pkg-notes').value};"
1843
+ + "if(!data.name){showToast('\u8bf7\u586b\u5199\u8d44\u4ea7\u5305\u540d\u79f0');return;}"
1844
+ + "fetch('/opc/admin/api/closure/packages/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1845
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u8d44\u4ea7\u5305\u5df2\u521b\u5efa');loadClosure();}"
1846
+ + "else{showToast(d.error||'\u521b\u5efa\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1847
+ // createCtTransfer
1848
+ + "\nfunction createCtTransfer(){"
1849
+ + "fetch('/opc/admin/api/closure/packages').then(function(r){return r.json()}).then(function(pkgs){"
1850
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1851
+ + "var opts=pkgs.filter(function(p){return p.status!=='closed';}).map(function(p){return '<option value=\"'+esc(p.id)+'\">'+esc(p.name)+'</option>';}).join('');"
1852
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1853
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:520px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1854
+ + "html+='<h2 style=\"margin:0 0 20px\">\u65b0\u5efa\u57ce\u6295\u8f6c\u8ba9</h2>';"
1855
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1856
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8d44\u4ea7\u5305 <select id=\"ctt-pkg\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"\">\u8bf7\u9009\u62e9...</option>'+opts+'</select></label>';"
1857
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u57ce\u6295\u516c\u53f8\u540d\u79f0 <input id=\"ctt-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u5982\uff1a\u4ed1\u548c\u5de5\u53d1\u96c6\u56e2\"></label>';"
1858
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8f6c\u8ba9\u4ef7\u683c (\u5143) <input id=\"ctt-price\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1859
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u76ee\u6807\u79d1\u521b\u8d37 (\u5143) <input id=\"ctt-loan\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1860
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8f6c\u8ba9\u65e5\u671f <input id=\"ctt-date\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1861
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5907\u6ce8 <input id=\"ctt-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1862
+ + "html+='</div>';"
1863
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1864
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1865
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveCtTransfer()\">\u521b\u5efa</button>';"
1866
+ + "html+='</div></div></div>';"
1867
+ + "document.body.insertAdjacentHTML('beforeend',html);"
1868
+ + "}).catch(function(){showToast('\u52a0\u8f7d\u5931\u8d25');});}"
1869
+ + "\nfunction saveCtTransfer(){"
1870
+ + "var data={package_id:document.getElementById('ctt-pkg').value,ct_company:document.getElementById('ctt-name').value,transfer_price:Number(document.getElementById('ctt-price').value)||0,sci_loan_target:Number(document.getElementById('ctt-loan').value)||0,transfer_date:document.getElementById('ctt-date').value,notes:document.getElementById('ctt-notes').value};"
1871
+ + "if(!data.package_id){showToast('\u8bf7\u9009\u62e9\u8d44\u4ea7\u5305');return;}"
1872
+ + "if(!data.ct_company){showToast('\u8bf7\u586b\u5199\u57ce\u6295\u516c\u53f8\u540d\u79f0');return;}"
1873
+ + "fetch('/opc/admin/api/closure/transfers/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1874
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u57ce\u6295\u8f6c\u8ba9\u5df2\u521b\u5efa');loadClosure();}"
1875
+ + "else{showToast(d.error||'\u521b\u5efa\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1876
+ // ── 融资 CREATE 函数 ──
1877
+ + "\nfunction createInvestRound(companyId){"
1878
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1879
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1880
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:520px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1881
+ + "html+='<h2 style=\"margin:0 0 20px\">\u65b0\u5efa\u878d\u8d44\u8f6e\u6b21</h2>';"
1882
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1883
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8f6e\u6b21\u540d\u79f0 <input id=\"ir-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u5982\uff1a\u5929\u4f7f\u8f6e\u3001A\u8f6e\"></label>';"
1884
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u878d\u8d44\u91d1\u989d (\u5143) <input id=\"ir-amount\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1885
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6295\u524d\u4f30\u503c (\u5143) <input id=\"ir-pre\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1886
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6295\u540e\u4f30\u503c (\u5143) <input id=\"ir-post\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1887
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u9886\u6295\u65b9 <input id=\"ir-lead\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1888
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5173\u95ed\u65e5\u671f <input id=\"ir-date\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1889
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5907\u6ce8 <input id=\"ir-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1890
+ + "html+='</div>';"
1891
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1892
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1893
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveInvestRound(\\''+companyId+'\\')\">\u521b\u5efa</button>';"
1894
+ + "html+='</div></div></div>';"
1895
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1896
+ + "\nfunction saveInvestRound(companyId){"
1897
+ + "var data={company_id:companyId,round_name:document.getElementById('ir-name').value,amount:Number(document.getElementById('ir-amount').value)||0,valuation_pre:Number(document.getElementById('ir-pre').value)||0,valuation_post:Number(document.getElementById('ir-post').value)||0,lead_investor:document.getElementById('ir-lead').value,close_date:document.getElementById('ir-date').value,notes:document.getElementById('ir-notes').value};"
1898
+ + "if(!data.round_name){showToast('\u8bf7\u586b\u5199\u8f6e\u6b21\u540d\u79f0');return;}"
1899
+ + "fetch('/opc/admin/api/investment/rounds/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1900
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u878d\u8d44\u8f6e\u6b21\u5df2\u521b\u5efa');showCompany(companyId);}"
1901
+ + "else{showToast(d.error||'\u521b\u5efa\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1902
+ + "\nfunction addInvestor(companyId){"
1903
+ + "fetch('/opc/admin/api/investment/rounds?company_id='+encodeURIComponent(companyId)).then(function(r){return r.json()}).then(function(rounds){"
1904
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1905
+ + "var opts=rounds.map(function(r){return '<option value=\"'+esc(r.id)+'\">'+esc(r.round_name)+'</option>';}).join('');"
1906
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1907
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:520px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
1908
+ + "html+='<h2 style=\"margin:0 0 20px\">\u65b0\u589e\u6295\u8d44\u4eba</h2>';"
1909
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1910
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5173\u8054\u878d\u8d44\u8f6e\u6b21 <select id=\"inv-round\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"\">\u8bf7\u9009\u62e9...</option>'+opts+'</select></label>';"
1911
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6295\u8d44\u4eba\u540d\u79f0 <input id=\"inv-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1912
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u7c7b\u578b <select id=\"inv-type\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"individual\">\u4e2a\u4eba</option><option value=\"institution\">\u673a\u6784</option><option value=\"government\">\u653f\u5e9c</option><option value=\"other\">\u5176\u4ed6</option></select></label>';"
1913
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6295\u8d44\u91d1\u989d (\u5143) <input id=\"inv-amount\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1914
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u80a1\u6743\u5360\u6bd4 (%) <input id=\"inv-equity\" type=\"number\" step=\"0.01\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0.00\"></label>';"
1915
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8054\u7cfb\u65b9\u5f0f <input id=\"inv-contact\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1916
+ + "html+='</div>';"
1917
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1918
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1919
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveInvestor(\\''+companyId+'\\')\">\u6dfb\u52a0</button>';"
1920
+ + "html+='</div></div></div>';"
1921
+ + "document.body.insertAdjacentHTML('beforeend',html);"
1922
+ + "}).catch(function(){showToast('\u52a0\u8f7d\u5931\u8d25');});}"
1923
+ + "\nfunction saveInvestor(companyId){"
1924
+ + "var data={company_id:companyId,round_id:document.getElementById('inv-round').value,name:document.getElementById('inv-name').value,type:document.getElementById('inv-type').value,amount:Number(document.getElementById('inv-amount').value)||0,equity_percent:Number(document.getElementById('inv-equity').value)||0,contact:document.getElementById('inv-contact').value};"
1925
+ + "if(!data.name){showToast('\u8bf7\u586b\u5199\u6295\u8d44\u4eba\u540d\u79f0');return;}"
1926
+ + "fetch('/opc/admin/api/investment/investors/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1927
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u6295\u8d44\u4eba\u5df2\u6dfb\u52a0');showCompany(companyId);}"
1928
+ + "else{showToast(d.error||'\u6dfb\u52a0\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1929
+ // ── 生命周期 CREATE 函数 ──
1930
+ + "\nfunction addMilestone(companyId){"
1931
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1932
+ + "var today=new Date().toISOString().slice(0,10);"
1933
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1934
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw\">';"
1935
+ + "html+='<h2 style=\"margin:0 0 20px\">\u6dfb\u52a0\u91cc\u7a0b\u7891</h2>';"
1936
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1937
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6807\u9898 <input id=\"ms-title\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1938
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5206\u7c7b <select id=\"ms-cat\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"business\">\u5546\u4e1a</option><option value=\"legal\">\u6cd5\u52a1</option><option value=\"technical\">\u6280\u672f</option><option value=\"financial\">\u8d22\u52a1</option><option value=\"hr\">\u4eba\u529b</option><option value=\"other\">\u5176\u4ed6</option></select></label>';"
1939
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u76ee\u6807\u65e5\u671f <input id=\"ms-date\" type=\"date\" value=\"'+today+'\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1940
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u63cf\u8ff0 <textarea id=\"ms-desc\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:60px\"></textarea></label>';"
1941
+ + "html+='</div>';"
1942
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1943
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1944
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveMilestone(\\''+companyId+'\\')\">\u6dfb\u52a0</button>';"
1945
+ + "html+='</div></div></div>';"
1946
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1947
+ + "\nfunction saveMilestone(companyId){"
1948
+ + "var data={company_id:companyId,title:document.getElementById('ms-title').value,category:document.getElementById('ms-cat').value,target_date:document.getElementById('ms-date').value,description:document.getElementById('ms-desc').value};"
1949
+ + "if(!data.title){showToast('\u8bf7\u586b\u5199\u6807\u9898');return;}"
1950
+ + "fetch('/opc/admin/api/lifecycle/milestones/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1951
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u91cc\u7a0b\u7891\u5df2\u6dfb\u52a0');showCompany(companyId);}"
1952
+ + "else{showToast(d.error||'\u6dfb\u52a0\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1953
+ + "\nfunction addLifecycleEvent(companyId){"
1954
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1955
+ + "var today=new Date().toISOString().slice(0,10);"
1956
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1957
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw\">';"
1958
+ + "html+='<h2 style=\"margin:0 0 20px\">\u6dfb\u52a0\u4e8b\u4ef6</h2>';"
1959
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1960
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6807\u9898 <input id=\"ev-title\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1961
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u4e8b\u4ef6\u7c7b\u578b <select id=\"ev-type\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"founding\">\u521b\u7acb</option><option value=\"product\">\u4ea7\u54c1</option><option value=\"partnership\">\u5408\u4f5c</option><option value=\"legal\">\u6cd5\u52a1</option><option value=\"financial\">\u8d22\u52a1</option><option value=\"team\">\u56e2\u961f</option><option value=\"market\">\u5e02\u573a</option><option value=\"other\">\u5176\u4ed6</option></select></label>';"
1962
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u4e8b\u4ef6\u65e5\u671f <input id=\"ev-date\" type=\"date\" value=\"'+today+'\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1963
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5f71\u54cd\u8bc4\u4f30 <input id=\"ev-impact\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u5982\uff1a\u6d88\u6781/\u79ef\u6781\"></label>';"
1964
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u63cf\u8ff0 <textarea id=\"ev-desc\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:60px\"></textarea></label>';"
1965
+ + "html+='</div>';"
1966
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1967
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1968
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveLifecycleEvent(\\''+companyId+'\\')\">\u6dfb\u52a0</button>';"
1969
+ + "html+='</div></div></div>';"
1970
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
1971
+ + "\nfunction saveLifecycleEvent(companyId){"
1972
+ + "var data={company_id:companyId,title:document.getElementById('ev-title').value,event_type:document.getElementById('ev-type').value,event_date:document.getElementById('ev-date').value,impact:document.getElementById('ev-impact').value,description:document.getElementById('ev-desc').value};"
1973
+ + "if(!data.title){showToast('\u8bf7\u586b\u5199\u6807\u9898');return;}"
1974
+ + "fetch('/opc/admin/api/lifecycle/events/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
1975
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u4e8b\u4ef6\u5df2\u6dfb\u52a0');showCompany(companyId);}"
1976
+ + "else{showToast(d.error||'\u6dfb\u52a0\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
1977
+ // ── 监控 recordMetric ──
1978
+ + "\nfunction recordMetric(){"
1979
+ + "fetch('/opc/admin/api/companies?limit=200').then(function(r){return r.json()}).then(function(companies){"
1980
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
1981
+ + "var opts=companies.map(function(c){return '<option value=\"'+esc(c.id)+'\">'+esc(c.name)+'</option>';}).join('');"
1982
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
1983
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw\">';"
1984
+ + "html+='<h2 style=\"margin:0 0 20px\">\u8bb0\u5f55\u6307\u6807</h2>';"
1985
+ + "html+='<div style=\"display:grid;gap:12px\">';"
1986
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u516c\u53f8 <select id=\"mt-cid\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"\">\u8bf7\u9009\u62e9...</option>'+opts+'</select></label>';"
1987
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6307\u6807\u540d\u79f0 <input id=\"mt-name\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u5982\uff1a\u6708\u6d3b\u8dc3\u7528\u6237MAU\"></label>';"
1988
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u6307\u6807\u503c <input id=\"mt-value\" type=\"number\" step=\"any\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
1989
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5355\u4f4d <input id=\"mt-unit\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u4eba/\u5143/\u4ef6...\"></label>';"
1990
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5206\u7c7b <input id=\"mt-cat\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\u5982\uff1a\u7528\u6237\u589e\u957f/\u8d22\u52a1\u6307\u6807\"></label>';"
1991
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5907\u6ce8 <input id=\"mt-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
1992
+ + "html+='</div>';"
1993
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
1994
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
1995
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveMetric()\">\u8bb0\u5f55</button>';"
1996
+ + "html+='</div></div></div>';"
1997
+ + "document.body.insertAdjacentHTML('beforeend',html);"
1998
+ + "}).catch(function(){showToast('\u52a0\u8f7d\u5931\u8d25');});}"
1999
+ + "\nfunction saveMetric(){"
2000
+ + "var data={company_id:document.getElementById('mt-cid').value,name:document.getElementById('mt-name').value,value:Number(document.getElementById('mt-value').value)||0,unit:document.getElementById('mt-unit').value,category:document.getElementById('mt-cat').value,notes:document.getElementById('mt-notes').value};"
2001
+ + "if(!data.company_id){showToast('\u8bf7\u9009\u62e9\u516c\u53f8');return;}"
2002
+ + "if(!data.name){showToast('\u8bf7\u586b\u5199\u6307\u6807\u540d\u79f0');return;}"
2003
+ + "fetch('/opc/admin/api/monitoring/metrics/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
2004
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u6307\u6807\u5df2\u8bb0\u5f55');loadMonitoring();}"
2005
+ + "else{showToast(d.error||'\u8bb0\u5f55\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
2006
+ // ── 采购订单 createOrder ──
2007
+ + "\nfunction createOrder(companyId){"
2008
+ + "fetch('/opc/admin/api/services?company_id='+encodeURIComponent(companyId)).then(function(r){return r.json()}).then(function(services){"
2009
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
2010
+ + "var opts='<option value=\"\">\u65e0\u5173\u8054\u670d\u52a1</option>'+services.map(function(s){return '<option value=\"'+esc(s.id)+'\">'+esc(s.name)+'</option>';}).join('');"
2011
+ + "var today=new Date().toISOString().slice(0,10);"
2012
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
2013
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
2014
+ + "html+='<h2 style=\"margin:0 0 20px\">\u65b0\u5efa\u91c7\u8d2d\u8ba2\u5355</h2>';"
2015
+ + "html+='<div style=\"display:grid;gap:12px\">';"
2016
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8ba2\u5355\u6807\u9898 <input id=\"ord-title\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
2017
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5173\u8054\u670d\u52a1 <select id=\"ord-svc\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\">'+opts+'</select></label>';"
2018
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u91d1\u989d (\u5143) <input id=\"ord-amount\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
2019
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u4e0b\u5355\u65e5\u671f <input id=\"ord-date\" type=\"date\" value=\"'+today+'\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
2020
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5907\u6ce8 <input id=\"ord-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
2021
+ + "html+='</div>';"
2022
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
2023
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
2024
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveOrder(\\''+companyId+'\\')\">\u521b\u5efa</button>';"
2025
+ + "html+='</div></div></div>';"
2026
+ + "document.body.insertAdjacentHTML('beforeend',html);"
2027
+ + "}).catch(function(){"
2028
+ // fallback without services
2029
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
2030
+ + "var today=new Date().toISOString().slice(0,10);"
2031
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
2032
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:480px;max-width:96vw\">';"
2033
+ + "html+='<h2 style=\"margin:0 0 20px\">\u65b0\u5efa\u91c7\u8d2d\u8ba2\u5355</h2>';"
2034
+ + "html+='<div style=\"display:grid;gap:12px\">';"
2035
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u8ba2\u5355\u6807\u9898 <input id=\"ord-title\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
2036
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u91d1\u989d (\u5143) <input id=\"ord-amount\" type=\"number\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"0\"></label>';"
2037
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u4e0b\u5355\u65e5\u671f <input id=\"ord-date\" type=\"date\" value=\"'+today+'\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
2038
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\u5907\u6ce8 <input id=\"ord-notes\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\"></label>';"
2039
+ + "html+='</div>';"
2040
+ + "html+='<div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
2041
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\u53d6\u6d88</button>';"
2042
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveOrder(\\''+companyId+'\\')\">\u521b\u5efa</button>';"
2043
+ + "html+='</div></div></div>';"
2044
+ + "document.body.insertAdjacentHTML('beforeend',html);"
2045
+ + "});}"
2046
+ + "\nfunction saveOrder(companyId){"
2047
+ + "var svcEl=document.getElementById('ord-svc');"
2048
+ + "var data={company_id:companyId,title:document.getElementById('ord-title').value,service_id:svcEl?svcEl.value:'',amount:Number(document.getElementById('ord-amount').value)||0,order_date:document.getElementById('ord-date').value,notes:document.getElementById('ord-notes').value};"
2049
+ + "if(!data.title){showToast('\u8bf7\u586b\u5199\u8ba2\u5355\u6807\u9898');return;}"
2050
+ + "fetch('/opc/admin/api/procurement/orders/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
2051
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\u8ba2\u5355\u5df2\u521b\u5efa');showCompany(companyId);}"
2052
+ + "else{showToast(d.error||'\u521b\u5efa\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
2053
+ + "\nfunction createMedia(companyId){"
2054
+ + "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
2055
+ + "var today=new Date().toISOString().slice(0,10);"
2056
+ + "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
2057
+ + "html+='<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:32px;width:560px;max-width:96vw;max-height:90vh;overflow-y:auto\">';"
2058
+ + "html+='<h2 style=\"margin:0 0 20px\">\\u65b0\\u5efa\\u5185\\u5bb9</h2>';"
2059
+ + "html+='<div style=\"display:grid;gap:12px\">';"
2060
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u6807\\u9898 <input id=\"mc-title\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px\" placeholder=\"\\u5185\\u5bb9\\u6807\\u9898\"></label>';"
2061
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5e73\\u53f0 <select id=\"mc-platform\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"\\u5fae\\u4fe1\">\\u5fae\\u4fe1</option><option value=\"\\u6296\\u97f3\">\\u6296\\u97f3</option><option value=\"\\u5c0f\\u7ea2\\u4e66\">\\u5c0f\\u7ea2\\u4e66</option><option value=\"\\u5fae\\u535a\">\\u5fae\\u535a</option><option value=\"B\\u7ad9\">B\\u7ad9</option><option value=\"\\u5c0f\\u7ea2\\u4e66\">\\u5c0f\\u7ea2\\u4e66</option><option value=\"\\u5176\\u4ed6\">\\u5176\\u4ed6</option></select></label>';"
2062
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u7c7b\\u578b <select id=\"mc-type\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"\\u56fe\\u6587\">\\u56fe\\u6587</option><option value=\"\\u77ed\\u89c6\\u9891\">\\u77ed\\u89c6\\u9891</option><option value=\"\\u957f\\u89c6\\u9891\">\\u957f\\u89c6\\u9891</option><option value=\"\\u76f4\\u64ad\">\\u76f4\\u64ad</option><option value=\"\\u5386\\u60f3\\u52a8\\u6001\">\\u5386\\u60f3\\u52a8\\u6001</option></select></label>';"
2063
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u5185\\u5bb9\\u6b63\\u6587 <textarea id=\"mc-body\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-family:var(--font);font-size:13px;resize:vertical;min-height:120px\" placeholder=\"\\u5185\\u5bb9\\u6b63\\u6587/\\u811a\\u672c\\u63cf\\u8ff0\\u2026\"></textarea></label>';"
2064
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u72b6\\u6001 <select id=\"mc-status\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px;font-family:var(--font);background:var(--card)\"><option value=\"draft\">\\u8349\\u7a3f</option><option value=\"scheduled\">\\u5df2\\u5b89\\u6392</option><option value=\"published\">\\u5df2\\u53d1\\u5e03</option></select></label>';"
2065
+ + "html+='<label style=\"font-size:12px;font-weight:600;color:var(--tx2)\">\\u9884\\u7ea6\\u53d1\\u5e03\\u65e5\\u671f <input id=\"mc-date\" type=\"date\" style=\"width:100%;padding:8px 12px;margin-top:4px;border:1px solid var(--bd);border-radius:var(--r);font-size:13px\"></label>';"
2066
+ + "html+='</div><div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:20px\">';"
2067
+ + "html+='<button class=\"btn\" onclick=\"document.getElementById(\\'edit-modal\\').remove()\">\\u53d6\\u6d88</button>';"
2068
+ + "html+='<button class=\"btn btn-pri\" onclick=\"saveMedia(\\''+companyId+'\\')\" >\\u4fdd\\u5b58</button>';"
2069
+ + "html+='</div></div></div>';"
2070
+ + "document.body.insertAdjacentHTML('beforeend',html);}"
2071
+ + "\nfunction saveMedia(companyId){"
2072
+ + "var data={company_id:companyId,title:document.getElementById('mc-title').value,platform:document.getElementById('mc-platform').value,content_type:document.getElementById('mc-type').value,body:document.getElementById('mc-body').value,status:document.getElementById('mc-status').value,scheduled_date:document.getElementById('mc-date').value||null};"
2073
+ + "if(!data.title){showToast('\\u8bf7\\u586b\\u5199\\u5185\\u5bb9\\u6807\\u9898');return;}"
2074
+ + "fetch('/opc/admin/api/media/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
2075
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5185\\u5bb9\\u5df2\\u521b\\u5efa');showCompany(companyId);}else{showToast(d.error||'\\u521b\\u5efa\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
2076
+ // ── printView ──
2077
+ + "\nfunction printView(viewId){"
2078
+ + "document.querySelectorAll('.view').forEach(function(v){v.classList.remove('print-target');});"
2079
+ + "var el=document.getElementById('view-'+viewId);if(el)el.classList.add('print-target');"
2080
+ + "window.print();"
2081
+ + "if(el)el.classList.remove('print-target');}"
2082
+ + "\nfunction printContracts(){"
2083
+ + "document.querySelectorAll('.view').forEach(function(v){v.classList.remove('print-target');});"
2084
+ + "var el=document.getElementById('view-company-detail');if(el)el.classList.add('print-target');"
2085
+ + "window.print();"
2086
+ + "if(el)el.classList.remove('print-target');}"
2087
+ + "\ndocument.querySelectorAll('.sidebar-nav a').forEach(function(a){a.addEventListener('click',function(e){var v=a.getAttribute('data-view');if(v){window.location.hash=v;}});});"
2088
+ + "\nwindow.onhashchange=handleHash;"
2089
+ // init
2090
+ + "\nhandleHash();"
2091
+ + getCanvasJs();
2092
+ }
2093
+
2094
+ function getGuideJs(): string {
2095
+ // Professional docs-style guide page with left TOC + right content
2096
+ // All Chinese text uses \uXXXX Unicode escapes
2097
+ return ""
2098
+ + "\nfunction renderSopGuide(){"
2099
+ + "var h='';"
2100
+ // ── Docs layout wrapper ──
2101
+ + "h+='<div style=\"display:flex;gap:0;min-height:100vh;position:relative\">';"
2102
+ // ── Left sticky TOC ──
2103
+ + "h+='<div id=\"guide-toc\" style=\"width:220px;flex-shrink:0;position:sticky;top:0;height:calc(100vh - 80px);overflow-y:auto;padding:24px 0 24px 0;border-right:1px solid var(--bd)\">';"
2104
+ + "h+='<div style=\"padding:0 20px 12px;font-size:11px;font-weight:700;color:var(--tx3);letter-spacing:0.08em;text-transform:uppercase\">\\u76ee\\u5f55</div>';"
2105
+ + "var tocItems=["
2106
+ + "{id:'g-bg',n:'\\u9879\\u76ee\\u80cc\\u666f'},"
2107
+ + "{id:'g-opb',n:'\\u4ec0\\u4e48\\u662f\\u4e00\\u4eba\\u4f01\\u4e1a'},"
2108
+ + "{id:'g-logic',n:'\\u4e09\\u5927\\u5e95\\u5c42\\u903b\\u8f91'},"
2109
+ + "{id:'g-track',n:'\\u8d5b\\u9053\\u9009\\u62e9'},"
2110
+ + "{id:'g-canvas',n:'OPB \\u753b\\u5e03 16 \\u6a21\\u5757'},"
2111
+ + "{id:'g-flow',n:'\\u5e73\\u53f0\\u4f7f\\u7528\\u6d41\\u7a0b'},"
2112
+ + "{id:'g-tools',n:'AI \\u5de5\\u5177\\u8bf4\\u660e'},"
2113
+ + "{id:'g-cmds',n:'\\u5e38\\u7528\\u5bf9\\u8bdd\\u6307\\u4ee4'},"
2114
+ + "{id:'g-hb',n:'Heartbeat \\u6a21\\u5f0f'}"
2115
+ + "];"
2116
+ + "tocItems.forEach(function(t){"
2117
+ + "h+='<a href=\"#'+t.id+'\" onclick=\"guideTocClick(event,\\''+t.id+'\\')\" style=\"display:block;padding:7px 20px;font-size:13px;color:var(--tx2);text-decoration:none;border-left:2px solid transparent;transition:all 0.15s;cursor:pointer\" data-toc=\"'+t.id+'\">'+t.n+'</a>';"
2118
+ + "});"
2119
+ + "h+='</div>';" // end toc
2120
+ // ── Right content area ──
2121
+ + "h+='<div style=\"flex:1;min-width:0;padding:0 48px 48px 40px;max-width:860px\">';"
2122
+
2123
+ // ── Page title ──
2124
+ + "h+='<div style=\"padding:32px 0 28px;border-bottom:1px solid var(--bd);margin-bottom:36px\">';"
2125
+ + "h+='<div style=\"display:inline-flex;align-items:center;gap:8px;background:#eff6ff;color:#1d4ed8;font-size:12px;font-weight:600;padding:4px 12px;border-radius:20px;margin-bottom:14px;letter-spacing:0.02em\">\\u2b50 \\u661f\\u73af OPC \\u4e2d\\u5fc3</div>';"
2126
+ + "h+='<h1 style=\"font-size:28px;font-weight:800;letter-spacing:-0.03em;color:var(--tx);margin:0 0 10px\">\\u5b8c\\u6574\\u4f7f\\u7528\\u6307\\u5357</h1>';"
2127
+ + "h+='<p style=\"font-size:15px;color:var(--tx2);line-height:1.6;max-width:600px;margin:0\">\\u57fa\\u4e8e\\u300a\\u4e00\\u4eba\\u4f01\\u4e1a\\u65b9\\u6cd5\\u8bba 2.0\\u300b\\u7684 AI \\u8d4b\\u80fd\\u4e00\\u4eba\\u516c\\u53f8\\u5b8c\\u6574\\u64cd\\u4f5c\\u6307\\u5357\\uff0c\\u5305\\u542b\\u5e73\\u53f0\\u80cc\\u666f\\u3001OPB \\u65b9\\u6cd5\\u8bba\\u548c\\u5b8c\\u6574\\u5de5\\u4f5c\\u6d41\\u7a0b</p>';"
2128
+ + "h+='<div style=\"display:flex;gap:24px;margin-top:20px\">';"
2129
+ + "h+='<div style=\"text-align:center\"><div style=\"font-size:22px;font-weight:800;color:#1d4ed8\">7</div><div style=\"font-size:12px;color:var(--tx2);margin-top:2px\">AI \\u5de5\\u5177</div></div>';"
2130
+ + "h+='<div style=\"width:1px;background:var(--bd)\"></div>';"
2131
+ + "h+='<div style=\"text-align:center\"><div style=\"font-size:22px;font-weight:800;color:#1d4ed8\">16</div><div style=\"font-size:12px;color:var(--tx2);margin-top:2px\">OPB \\u6a21\\u5757</div></div>';"
2132
+ + "h+='<div style=\"width:1px;background:var(--bd)\"></div>';"
2133
+ + "h+='<div style=\"text-align:center\"><div style=\"font-size:22px;font-weight:800;color:#1d4ed8\">6</div><div style=\"font-size:12px;color:var(--tx2);margin-top:2px\">\\u5de5\\u4f5c\\u6d41\\u7a0b</div></div>';"
2134
+ + "h+='<div style=\"width:1px;background:var(--bd)\"></div>';"
2135
+ + "h+='<div style=\"text-align:center\"><div style=\"font-size:22px;font-weight:800;color:#1d4ed8\">1</div><div style=\"font-size:12px;color:var(--tx2);margin-top:2px\">\\u4eba\\u8fd0\\u8425</div></div>';"
2136
+ + "h+='</div>';"
2137
+ + "h+='</div>';"
2138
+ // ── Section 1: 项目背景与意义 ──
2139
+ + "h+='<section id=\"g-bg\" style=\"margin-bottom:48px\">';"
2140
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2141
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2142
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">\\u9879\\u76ee\\u80cc\\u666f\\u4e0e\\u610f\\u4e49</h2>';"
2143
+ + "h+='</div>';"
2144
+ + "h+='<p style=\"font-size:14px;line-height:1.8;color:var(--tx2);margin-bottom:16px\">\\u661f\\u73af OPC \\u4e2d\\u5fc3\\uff08One Person Company Center\\uff09\\u662f\\u4e00\\u4e2a\\u57fa\\u4e8e AI \\u7684\\u4e00\\u4eba\\u516c\\u53f8\\u7efc\\u5408\\u8fd0\\u8425\\u5e73\\u53f0\\uff0c\\u4e13\\u4e3a\\u72ec\\u7acb\\u521b\\u4e1a\\u8005\\u3001\\u81ea\\u5a92\\u4f53\\u3001\\u6570\\u5b57\\u4e2a\\u4f53\\u8bbe\\u8ba1\\u3002\\u5b83\\u5c06\\u590d\\u6742\\u7684\\u4f01\\u4e1a\\u8fd0\\u8425\\u5de5\\u4f5c\\u4ea4\\u7ed9 AI \\u56e2\\u961f\\uff0c\\u8ba9\\u521b\\u529e\\u4eba\\u4e13\\u6ce8\\u4e8e\\u6838\\u5fc3\\u4ef7\\u503c\\u521b\\u9020\\u3002</p>';"
2145
+ + "h+='<p style=\"font-size:14px;line-height:1.8;color:var(--tx2);margin-bottom:24px\">\\u672c\\u5e73\\u53f0\\u7406\\u5ff5\\u6765\\u81ea Easy Chen \\u6240\\u8457\\u300a\\u4e00\\u4eba\\u4f01\\u4e1a\\u65b9\\u6cd5\\u8bba 2.0\\u300b\\uff1a\\u5728 AI \\u65f6\\u4ee3\\uff0c\\u4e00\\u4e2a\\u4eba\\u5b8c\\u5168\\u53ef\\u4ee5\\u8fd0\\u8425\\u4e00\\u5bb6\\u5177\\u5907\\u5b8c\\u6574\\u529f\\u80fd\\u7684\\u516c\\u53f8\\uff0c\\u5b9e\\u73b0\\u8d44\\u4ea7\\u5f0f\\u6536\\u5165\\u548c\\u590d\\u5229\\u589e\\u957f\\u3002</p>';"
2146
+ + "h+='<div style=\"display:grid;grid-template-columns:repeat(3,1fr);gap:12px\">';"
2147
+ + "h+='<div style=\"padding:16px 20px;background:var(--card);border:1px solid var(--bd);border-radius:10px;border-top:3px solid #2563eb\">';"
2148
+ + "h+='<div style=\"font-size:12px;color:var(--tx3);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em\">\\u5e73\\u53f0\\u5b9a\\u4f4d</div>';"
2149
+ + "h+='<div style=\"font-size:16px;font-weight:700;color:var(--tx)\">AI \\u8d4b\\u80fd</div>';"
2150
+ + "h+='<div style=\"font-size:12px;color:var(--tx2);margin-top:4px\">\\u4e00\\u4eba\\u516c\\u53f8</div>';"
2151
+ + "h+='</div>';"
2152
+ + "h+='<div style=\"padding:16px 20px;background:var(--card);border:1px solid var(--bd);border-radius:10px;border-top:3px solid #6366f1\">';"
2153
+ + "h+='<div style=\"font-size:12px;color:var(--tx3);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em\">\\u7406\\u8bba\\u57fa\\u7840</div>';"
2154
+ + "h+='<div style=\"font-size:16px;font-weight:700;color:var(--tx)\">OPB \\u65b9\\u6cd5\\u8bba</div>';"
2155
+ + "h+='<div style=\"font-size:12px;color:var(--tx2);margin-top:4px\">Easy Chen \\u8457</div>';"
2156
+ + "h+='</div>';"
2157
+ + "h+='<div style=\"padding:16px 20px;background:var(--card);border:1px solid var(--bd);border-radius:10px;border-top:3px solid #10b981\">';"
2158
+ + "h+='<div style=\"font-size:12px;color:var(--tx3);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em\">\\u6838\\u5fc3\\u80fd\\u529b</div>';"
2159
+ + "h+='<div style=\"font-size:16px;font-weight:700;color:var(--tx)\">7 \\u5de5\\u5177</div>';"
2160
+ + "h+='<div style=\"font-size:12px;color:var(--tx2);margin-top:4px\">\\u5168\\u80fd AI \\u56e2\\u961f</div>';"
2161
+ + "h+='</div>';"
2162
+ + "h+='</div>';"
2163
+ + "h+='</section>';"
2164
+
2165
+ // ── Section 2: 什么是一人企业 ──
2166
+ + "h+='<section id=\"g-opb\" style=\"margin-bottom:48px\">';"
2167
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2168
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2169
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">\\u4ec0\\u4e48\\u662f\\u4e00\\u4eba\\u4f01\\u4e1a</h2>';"
2170
+ + "h+='</div>';"
2171
+ + "h+='<p style=\"font-size:14px;line-height:1.8;color:var(--tx2);margin-bottom:20px\">\\u4e00\\u4eba\\u4f01\\u4e1a\\u662f\\u201c\\u4ee5\\u4e2a\\u4f53\\u6216\\u4e2a\\u4eba\\u54c1\\u724c\\u4e3a\\u4e3b\\u5bfc\\u7684\\u4e1a\\u52a1\\u4f53\\u201d\\u3002\\u5b83\\u4e0d\\u7b49\\u4e8e\\u4e2a\\u4f53\\u6237\\uff0c\\u4e5f\\u4e0d\\u540c\\u4e8e\\u4f20\\u7edf\\u521b\\u4e1a\\u516c\\u53f8\\uff0c\\u800c\\u662f\\u4e00\\u79cd\\u5168\\u65b0\\u7684\\u5546\\u4e1a\\u6a21\\u5f0f\\u3002</p>';"
2172
+ + "h+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px\">';"
2173
+ + "h+='<div style=\"padding:16px 20px;background:var(--bg);border-radius:10px;border:1px solid var(--bd)\">';"
2174
+ + "h+='<div style=\"font-size:12px;font-weight:700;color:#ef4444;margin-bottom:8px;display:flex;align-items:center;gap:6px\">';"
2175
+ + "h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\"><circle cx=\"7\" cy=\"7\" r=\"6.5\" stroke=\"#ef4444\"/><path d=\"M4.5 4.5l5 5M9.5 4.5l-5 5\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>';"
2176
+ + "h+='\\u4e0d\\u662f\\u4e2a\\u4f53\\u6237</div>';"
2177
+ + "h+='<p style=\"font-size:13px;line-height:1.7;color:var(--tx2);margin:0\">\\u4e2a\\u4f53\\u6237\\u662f\\u6cd5\\u5f8b\\u767b\\u8bb0\\u5f62\\u5f0f\\uff0c\\u4e00\\u4eba\\u4f01\\u4e1a\\u662f\\u5546\\u4e1a\\u6a21\\u5f0f\\u3002\\u4e00\\u4eba\\u4f01\\u4e1a\\u53ef\\u4ee5\\u662f\\u516c\\u53f8\\u3001\\u4e2a\\u4f53\\u6237\\u6216\\u65e0\\u6ce8\\u518c\\u7684\\u4e2a\\u4eba\\u54c1\\u724c\\u3002</p>';"
2178
+ + "h+='</div>';"
2179
+ + "h+='<div style=\"padding:16px 20px;background:var(--bg);border-radius:10px;border:1px solid var(--bd)\">';"
2180
+ + "h+='<div style=\"font-size:12px;font-weight:700;color:#ef4444;margin-bottom:8px;display:flex;align-items:center;gap:6px\">';"
2181
+ + "h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\"><circle cx=\"7\" cy=\"7\" r=\"6.5\" stroke=\"#ef4444\"/><path d=\"M4.5 4.5l5 5M9.5 4.5l-5 5\" stroke=\"#ef4444\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>';"
2182
+ + "h+='\\u4e0d\\u540c\\u4e8e\\u521d\\u521b\\u516c\\u53f8</div>';"
2183
+ + "h+='<p style=\"font-size:13px;line-height:1.7;color:var(--tx2);margin:0\">\\u521d\\u521b\\u516c\\u53f8\\u76ee\\u6807\\u662f\\u878d\\u8d44\\u548c\\u4e0a\\u5e02\\uff0c\\u4e00\\u4eba\\u4f01\\u4e1a\\u76ee\\u6807\\u662f\\u6301\\u7eed\\u6027\\u73b0\\u91d1\\u6d41\\u548c\\u8d44\\u4ea7\\u7d2f\\u79ef\\u3002</p>';"
2184
+ + "h+='</div>';"
2185
+ + "h+='</div>';"
2186
+ + "h+='<div style=\"background:linear-gradient(135deg,#1d4ed8 0%,#6366f1 100%);border-radius:10px;padding:20px 24px;color:#fff\">';"
2187
+ + "h+='<div style=\"font-weight:700;font-size:13px;margin-bottom:12px;opacity:0.85;text-transform:uppercase;letter-spacing:0.05em\">\\u4e00\\u4eba\\u4f01\\u4e1a\\u4e09\\u5927\\u6838\\u5fc3\\u7279\\u5f81</div>';"
2188
+ + "h+='<div style=\"display:flex;gap:10px;flex-wrap:wrap\">';"
2189
+ + "h+='<span style=\"background:rgba(255,255,255,0.15);padding:6px 14px;border-radius:6px;font-size:13px;font-weight:600\">\\u8d44\\u4ea7\\u5f0f\\u6536\\u5165</span>';"
2190
+ + "h+='<span style=\"background:rgba(255,255,255,0.15);padding:6px 14px;border-radius:6px;font-size:13px;font-weight:600\">\\u6760\\u6746\\u6548\\u5e94</span>';"
2191
+ + "h+='<span style=\"background:rgba(255,255,255,0.15);padding:6px 14px;border-radius:6px;font-size:13px;font-weight:600\">\\u65e0\\u9700\\u5927\\u91cf\\u96c7\\u4eba\\u53ef\\u6269\\u5c55</span>';"
2192
+ + "h+='</div>';"
2193
+ + "h+='</div>';"
2194
+ + "h+='</section>';"
2195
+
2196
+ // ── Section 3: 三大底层逻辑 ──
2197
+ + "h+='<section id=\"g-logic\" style=\"margin-bottom:48px\">';"
2198
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2199
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2200
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">\\u4e09\\u5927\\u5e95\\u5c42\\u903b\\u8f91</h2>';"
2201
+ + "h+='</div>';"
2202
+ + "h+='<div style=\"display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px\">';"
2203
+ + "h+='<div style=\"padding:20px;border:1px solid #bfdbfe;border-radius:10px;background:#eff6ff\">';"
2204
+ + "h+='<div style=\"width:32px;height:32px;background:#2563eb;border-radius:8px;display:flex;align-items:center;justify-content:center;margin-bottom:12px\">';"
2205
+ + "h+='<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><path d=\"M8 2L14 6V10L8 14L2 10V6L8 2Z\" stroke=\"white\" stroke-width=\"1.5\" stroke-linejoin=\"round\"/></svg>';"
2206
+ + "h+='</div>';"
2207
+ + "h+='<div style=\"font-weight:700;font-size:14px;margin-bottom:8px;color:#1d4ed8\">\\u4ee5\\u5c0f\\u535a\\u5927</div>';"
2208
+ + "h+='<p style=\"font-size:13px;line-height:1.7;color:#1e40af;margin-bottom:10px\">\\u96f6\\u8fb9\\u9645\\u6210\\u672c\\u4ea7\\u54c1\\u53ef\\u65e0\\u9650\\u590d\\u523b\\uff0c\\u65e0\\u9700\\u8bb8\\u53ef\\u5c31\\u53ef\\u5168\\u7403\\u63a8\\u5e7f\\u3002</p>';"
2209
+ + "h+='<ul style=\"font-size:12px;color:#1e40af;padding-left:14px;margin:0;line-height:1.8\">';"
2210
+ + "h+='<li>\\u8f6f\\u4ef6 / \\u5de5\\u5177</li><li>\\u5185\\u5bb9 / \\u5a92\\u4f53</li><li>\\u8bfe\\u7a0b / \\u77e5\\u8bc6\\u4ea7\\u54c1</li>';"
2211
+ + "h+='</ul>';"
2212
+ + "h+='</div>';"
2213
+ + "h+='<div style=\"padding:20px;border:1px solid #ddd6fe;border-radius:10px;background:#f5f3ff\">';"
2214
+ + "h+='<div style=\"width:32px;height:32px;background:#6366f1;border-radius:8px;display:flex;align-items:center;justify-content:center;margin-bottom:12px\">';"
2215
+ + "h+='<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><rect x=\"2\" y=\"2\" width=\"5\" height=\"5\" rx=\"1\" fill=\"white\"/><rect x=\"9\" y=\"2\" width=\"5\" height=\"5\" rx=\"1\" fill=\"white\" opacity=\"0.7\"/><rect x=\"2\" y=\"9\" width=\"5\" height=\"5\" rx=\"1\" fill=\"white\" opacity=\"0.7\"/><rect x=\"9\" y=\"9\" width=\"5\" height=\"5\" rx=\"1\" fill=\"white\" opacity=\"0.5\"/></svg>';"
2216
+ + "h+='</div>';"
2217
+ + "h+='<div style=\"font-weight:700;font-size:14px;margin-bottom:8px;color:#4338ca\">\\u8d44\\u4ea7\\u4e0e\\u88ab\\u52a8\\u6536\\u5165</div>';"
2218
+ + "h+='<p style=\"font-size:13px;line-height:1.7;color:#3730a3;margin-bottom:10px\">\\u8d44\\u4ea7\\u5728\\u4f60\\u4e0d\\u5de5\\u4f5c\\u65f6\\u8fd8\\u5728\\u5c06\\u94b1\\u6253\\u5165\\u53e3\\u888b\\u3002\\u4e09\\u5927\\u8d44\\u4ea7\\u6c60\\uff1a</p>';"
2219
+ + "h+='<ul style=\"font-size:12px;color:#3730a3;padding-left:14px;margin:0;line-height:1.8\">';"
2220
+ + "h+='<li>\\u5185\\u5bb9\\u8d44\\u4ea7\\u6c60</li><li>\\u4ea7\\u54c1\\u8d44\\u4ea7\\u6c60</li><li>\\u5ba2\\u6237\\u8d44\\u4ea7\\u6c60</li>';"
2221
+ + "h+='</ul>';"
2222
+ + "h+='</div>';"
2223
+ + "h+='<div style=\"padding:20px;border:1px solid #a7f3d0;border-radius:10px;background:#ecfdf5\">';"
2224
+ + "h+='<div style=\"width:32px;height:32px;background:#10b981;border-radius:8px;display:flex;align-items:center;justify-content:center;margin-bottom:12px\">';"
2225
+ + "h+='<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><path d=\"M2 12C2 12 4 8 8 8C12 8 14 4 14 4\" stroke=\"white\" stroke-width=\"1.5\" stroke-linecap=\"round\"/><path d=\"M10 4L14 4L14 8\" stroke=\"white\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>';"
2226
+ + "h+='</div>';"
2227
+ + "h+='<div style=\"font-weight:700;font-size:14px;margin-bottom:8px;color:#065f46\">\\u6eda\\u96ea\\u7403\\u6548\\u5e94</div>';"
2228
+ + "h+='<p style=\"font-size:13px;line-height:1.7;color:#047857;margin-bottom:10px\">\\u5185\\u5bb9\\u8d44\\u4ea7\\u968f\\u65f6\\u95f4\\u590d\\u5229\\uff0c\\u5c71\\u5934\\u8d8a\\u6eda\\u8d8a\\u5927\\u3002\\u957f\\u671f\\u4e3b\\u4e49\\u52dd\\u8fc7\\u77ed\\u671f\\u88ab\\u52a8\\u3002</p>';"
2229
+ + "h+='<ul style=\"font-size:12px;color:#047857;padding-left:14px;margin:0;line-height:1.8\">';"
2230
+ + "h+='<li>\\u5185\\u5bb9\\u8d44\\u4ea7\\u590d\\u5229\\u589e\\u957f</li><li>\\u54c1\\u724c\\u548c\\u4fe1\\u4efb\\u7d2f\\u79ef</li><li>\\u641c\\u7d22\\u6d41\\u91cf\\u590d\\u5229</li>';"
2231
+ + "h+='</ul>';"
2232
+ + "h+='</div>';"
2233
+ + "h+='</div>';"
2234
+ + "h+='</section>';"
2235
+
2236
+ // ── Section 4: 赛道选择框架 ──
2237
+ + "h+='<section id=\"g-track\" style=\"margin-bottom:48px\">';"
2238
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2239
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2240
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">\\u8d5b\\u9053\\u9009\\u62e9\\u6846\\u67b6</h2>';"
2241
+ + "h+='</div>';"
2242
+ + "h+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px\">';"
2243
+ + "h+='<div style=\"padding:16px 20px;border:1px dashed #fca5a5;border-radius:10px;background:#fef2f2\">';"
2244
+ + "h+='<div style=\"font-weight:700;color:#dc2626;margin-bottom:8px;font-size:13px;display:flex;align-items:center;gap:6px\">';"
2245
+ + "h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\"><circle cx=\"7\" cy=\"7\" r=\"6.5\" stroke=\"#dc2626\"/><path d=\"M4.5 4.5l5 5M9.5 4.5l-5 5\" stroke=\"#dc2626\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>';"
2246
+ + "h+='\\u907f\\u5f00\\uff1a\\u5927\\u4f17\\u521a\\u9700</div>';"
2247
+ + "h+='<p style=\"font-size:13px;line-height:1.7;color:#991b1b;margin:0\">\\u5927\\u4f17\\u5e02\\u573a\\u7ade\\u4e89\\u6fc0\\u70c8\\uff0c\\u8d44\\u672c\\u548c\\u56e2\\u961f\\u89c4\\u6a21\\u8981\\u6c42\\u9ad8\\uff0c\\u4e00\\u4eba\\u4f01\\u4e1a\\u96be\\u4ee5\\u5b58\\u6d3b\\u3002</p>';"
2248
+ + "h+='</div>';"
2249
+ + "h+='<div style=\"padding:16px 20px;border:1px solid #6ee7b7;border-radius:10px;background:#ecfdf5\">';"
2250
+ + "h+='<div style=\"font-weight:700;color:#065f46;margin-bottom:8px;font-size:13px;display:flex;align-items:center;gap:6px\">';"
2251
+ + "h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\"><circle cx=\"7\" cy=\"7\" r=\"6.5\" stroke=\"#10b981\"/><path d=\"M4 7l2 2 4-4\" stroke=\"#10b981\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>';"
2252
+ + "h+='\\u76ee\\u6807\\uff1a\\u5c0f\\u4f17\\u5f3a\\u9700</div>';"
2253
+ + "h+='<p style=\"font-size:13px;line-height:1.7;color:#065f46;margin:0\">\\u5c0f\\u4f17\\u5e02\\u573a\\u7ade\\u4e89\\u5c11\\uff0c\\u7528\\u6237\\u613f\\u610f\\u4ed8\\u8d39\\uff0c\\u4e00\\u4eba\\u4f01\\u4e1a\\u5bb9\\u6613\\u5360\\u636e\\u4f18\\u52bf\\u5730\\u4f4d\\u3002</p>';"
2254
+ + "h+='</div>';"
2255
+ + "h+='</div>';"
2256
+ + "h+='<div style=\"font-size:13px;font-weight:700;color:var(--tx);margin-bottom:10px\">\\u975e\\u7ade\\u4e89\\u7b56\\u7565\\uff08\\u4e09\\u79cd\\u8def\\u5f84\\uff09</div>';"
2257
+ + "h+='<div style=\"display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:20px\">';"
2258
+ + "h+='<div style=\"padding:14px 16px;background:var(--bg);border-radius:8px;border:1px solid var(--bd)\">';"
2259
+ + "h+='<div style=\"font-size:12px;font-weight:700;color:#2563eb;margin-bottom:6px\">\\u6210\\u4e3a\\u751f\\u6001\\u7684\\u4e00\\u90e8\\u5206</div>';"
2260
+ + "h+='<p style=\"font-size:12px;color:var(--tx2);margin:0;line-height:1.6\">\\u5728\\u5df2\\u6709\\u5e73\\u53f0\\u4e0a\\u8865\\u5145\\u7f3a\\u5c11\\u7684\\u4e1c\\u897f</p>';"
2261
+ + "h+='</div>';"
2262
+ + "h+='<div style=\"padding:14px 16px;background:var(--bg);border-radius:8px;border:1px solid var(--bd)\">';"
2263
+ + "h+='<div style=\"font-size:12px;font-weight:700;color:#6366f1;margin-bottom:6px\">\\u5dee\\u5f02\\u5316\\u5b9a\\u4f4d</div>';"
2264
+ + "h+='<p style=\"font-size:12px;color:var(--tx2);margin:0;line-height:1.6\">\\u5728\\u540c\\u7c7b\\u8d5b\\u9053\\u4e2d\\u627e\\u5230\\u72ec\\u7279\\u89d2\\u5ea6</p>';"
2265
+ + "h+='</div>';"
2266
+ + "h+='<div style=\"padding:14px 16px;background:var(--bg);border-radius:8px;border:1px solid var(--bd)\">';"
2267
+ + "h+='<div style=\"font-size:12px;font-weight:700;color:#10b981;margin-bottom:6px\">\\u521b\\u5efa\\u65b0\\u7c7b\\u76ee</div>';"
2268
+ + "h+='<p style=\"font-size:12px;color:var(--tx2);margin:0;line-height:1.6\">\\u5b9a\\u4e49\\u5168\\u65b0\\u7c7b\\u76ee\\uff0c\\u6210\\u4e3a\\u7b2c\\u4e00</p>';"
2269
+ + "h+='</div>';"
2270
+ + "h+='</div>';"
2271
+ + "h+='<div style=\"font-size:13px;font-weight:700;color:var(--tx);margin-bottom:10px\">\\u7ed3\\u6784\\u6027\\u4f18\\u52bf</div>';"
2272
+ + "h+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px\">';"
2273
+ + "h+='<div style=\"padding:14px 16px;background:var(--bg);border-radius:8px;border:1px solid var(--bd)\">';"
2274
+ + "h+='<div style=\"font-size:13px;font-weight:600;margin-bottom:4px;color:var(--tx)\">\\u526f\\u4ea7\\u54c1\\u4f18\\u52bf</div>';"
2275
+ + "h+='<p style=\"font-size:12px;color:var(--tx2);margin:0;line-height:1.6\">\\u5229\\u7528\\u5df2\\u6709\\u5de5\\u4f5c\\u7684\\u526f\\u4ea7\\u54c1\\u521b\\u4e1a\\uff0c\\u8fb9\\u969b\\u6210\\u672c\\u8d8b\\u8fd1\\u4e8e\\u96f6</p>';"
2276
+ + "h+='</div>';"
2277
+ + "h+='<div style=\"padding:14px 16px;background:var(--bg);border-radius:8px;border:1px solid var(--bd)\">';"
2278
+ + "h+='<div style=\"font-size:13px;font-weight:600;margin-bottom:4px;color:var(--tx)\">\\u4fe1\\u606f\\u5dee\\u4f18\\u52bf</div>';"
2279
+ + "h+='<p style=\"font-size:12px;color:var(--tx2);margin:0;line-height:1.6\">\\u6301\\u6709\\u72ec\\u7279\\u4fe1\\u606f\\u3001\\u8d44\\u6e90\\u6216\\u6280\\u80fd\\uff0c\\u5efa\\u7acb\\u96be\\u4ee5\\u590d\\u5236\\u7684\\u58c1\\u5792</p>';"
2280
+ + "h+='</div>';"
2281
+ + "h+='</div>';"
2282
+ + "h+='</section>';"
2283
+
2284
+ // ── Section 5: OPB画布 16模块 ──
2285
+ + "h+='<section id=\"g-canvas\" style=\"margin-bottom:48px\">';"
2286
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2287
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2288
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">OPB \\u753b\\u5e03 16 \\u6a21\\u5757</h2>';"
2289
+ + "h+='</div>';"
2290
+ + "h+='<p style=\"font-size:14px;color:var(--tx2);margin-bottom:16px;line-height:1.7\">OPB\\uff08One Person Business\\uff09\\u753b\\u5e03\\u662f\\u4e00\\u4eba\\u4f01\\u4e1a\\u7684\\u6218\\u7565\\u89c4\\u5212\\u5de5\\u5177\\uff0c\\u5305\\u542b 16 \\u4e2a\\u6a21\\u5757\\uff0c\\u5168\\u9762\\u63cf\\u8ff0\\u4e1a\\u52a1\\u6a21\\u5f0f\\u3002</p>';"
2291
+ + "h+='<div style=\"display:grid;grid-template-columns:repeat(4,1fr);gap:8px\">';"
2292
+ + "var canvas16=["
2293
+ + "{k:'track',l:'\\u8d5b\\u9053',d:'\\u4e1a\\u52a1\\u6240\\u5728\\u7684\\u9886\\u57df\\u548c\\u5e02\\u573a\\u5206\\u7c7b'},"
2294
+ + "{k:'target_customer',l:'\\u76ee\\u6807\\u5ba2\\u6237',d:'\\u5177\\u4f53\\u7528\\u6237\\u753b\\u50cf\\uff1a\\u4eba\\u7fa4\\u3001\\u75db\\u70b9\\u3001\\u573a\\u666f'},"
2295
+ + "{k:'pain_point',l:'\\u75db\\u70b9',d:'\\u5ba2\\u6237\\u6838\\u5fc3\\u75db\\u70b9\\u548c\\u672a\\u6ee1\\u8db3\\u9700\\u6c42'},"
2296
+ + "{k:'solution',l:'\\u89e3\\u51b3\\u65b9\\u6848',d:'\\u4ea7\\u54c1/\\u670d\\u52a1\\u5982\\u4f55\\u89e3\\u51b3\\u75db\\u70b9'},"
2297
+ + "{k:'unique_value',l:'\\u72ec\\u7279\\u4ef7\\u503c',d:'\\u4e0e\\u7ade\\u4e89\\u5bf9\\u624b\\u7684\\u5dee\\u5f02\\u5316\\u4f18\\u52bf'},"
2298
+ + "{k:'channels',l:'\\u6e20\\u9053',d:'\\u83b7\\u5ba2\\u6e20\\u9053\\uff1a\\u5185\\u5bb9/\\u793e\\u7fa4/\\u53e3\\u7891/\\u5e7f\\u544a'},"
2299
+ + "{k:'revenue_model',l:'\\u6536\\u5165\\u6a21\\u5f0f',d:'\\u5982\\u4f55\\u53d8\\u73b0\\uff1a\\u8ba2\\u9605/\\u4e00\\u6b21\\u6027/\\u4f63\\u4f63/\\u53d6\\u6210'},"
2300
+ + "{k:'cost_structure',l:'\\u6210\\u672c\\u7ed3\\u6784',d:'\\u4e3b\\u8981\\u6210\\u672c\\u9879\\u76ee\\u548c\\u56fa\\u53d8\\u6bd4\\u4f8b'},"
2301
+ + "{k:'key_resources',l:'\\u5173\\u952e\\u8d44\\u6e90',d:'\\u6280\\u80fd\\u3001\\u5185\\u5bb9\\u3001\\u793e\\u7fa4\\u3001\\u54c1\\u724c\\u3001\\u5de5\\u5177'},"
2302
+ + "{k:'key_activities',l:'\\u5173\\u952e\\u6d3b\\u52a8',d:'\\u6bcf\\u5929\\u5fc5\\u987b\\u505a\\u7684\\u6838\\u5fc3\\u4e8b\\u9879'},"
2303
+ + "{k:'key_partners',l:'\\u5173\\u952e\\u5408\\u4f5c',d:'\\u521b\\u4f5c\\u8054\\u8054\\u3001\\u5de5\\u5177\\u4f9b\\u5e94\\u5546\\u3001\\u5206\\u53d1\\u5e73\\u53f0'},"
2304
+ + "{k:'unfair_advantage',l:'\\u4e0d\\u516c\\u5e73\\u4f18\\u52bf',d:'\\u96be\\u4ee5\\u590d\\u5236\\u7684\\u72ec\\u7279\\u4f18\\u52bf\\u548c\\u58c1\\u5792'},"
2305
+ + "{k:'metrics',l:'\\u5173\\u952e\\u6307\\u6807',d:'\\u8861\\u91cf\\u4e1a\\u52a1\\u5065\\u5eb7\\u7684\\u6838\\u5fc3\\u6307\\u6807'},"
2306
+ + "{k:'non_compete',l:'\\u975e\\u7ade\\u4e89\\u7b56\\u7565',d:'\\u5982\\u4f55\\u907f\\u514d\\u6b63\\u9762\\u7ade\\u4e89\\u7684\\u7b56\\u7565'},"
2307
+ + "{k:'scaling_strategy',l:'\\u6269\\u5c55\\u7b56\\u7565',d:'\\u4e0d\\u96c7\\u4eba\\u60c5\\u51b5\\u4e0b\\u5982\\u4f55\\u6269\\u5927\\u6536\\u5165'},"
2308
+ + "{k:'notes',l:'\\u5907\\u6ce8',d:'\\u5176\\u4ed6\\u91cd\\u8981\\u4e8b\\u9879\\u548c\\u8865\\u5145\\u8bf4\\u660e'}"
2309
+ + "];"
2310
+ + "canvas16.forEach(function(m,i){"
2311
+ + "h+='<div style=\"padding:12px;border:1px solid var(--bd);border-radius:8px;background:var(--card);transition:box-shadow 0.15s;cursor:default\" onmouseenter=\"this.style.boxShadow=\\'0 2px 8px rgba(0,0,0,0.08)\\'\" onmouseleave=\"this.style.boxShadow=\\'none\\'\">';"
2312
+ + "h+='<div style=\"font-size:10px;color:var(--tx3);margin-bottom:4px;font-weight:600;text-transform:uppercase;letter-spacing:0.04em\">#'+(i+1)+'</div>';"
2313
+ + "h+='<div style=\"font-size:13px;font-weight:700;margin-bottom:4px;color:var(--tx)\">'+esc(m.l)+'</div>';"
2314
+ + "h+='<div style=\"font-size:11px;color:var(--tx2);line-height:1.5\">'+esc(m.d)+'</div>';"
2315
+ + "h+='</div>';"
2316
+ + "});"
2317
+ + "h+='</div>';"
2318
+ + "h+='<div style=\"margin-top:14px;padding:12px 16px;background:#eff6ff;border-radius:8px;border:1px solid #bfdbfe;font-size:13px;color:#1d4ed8;display:flex;align-items:center;gap:8px\">';"
2319
+ + "h+='<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><circle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"#2563eb\" stroke-width=\"1.5\"/><path d=\"M8 7v4M8 5v1\" stroke=\"#2563eb\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>';"
2320
+ + "h+='\\u5728 <b>OPB \\u753b\\u5e03</b> \\u83dc\\u5355\\u4e2d\\u5c55\\u5f00\\u753b\\u5e03\\uff0c\\u6216\\u5bf9 Agent \\u8bf4\\u201c\\u67e5\\u770b\\u516c\\u53f8 {id} \\u7684 OPB \\u753b\\u5e03\\u201d';"
2321
+ + "h+='</div>';"
2322
+ + "h+='</section>';"
2323
+
2324
+ // ── Section 6: 星环OPC平台使用流程 (6步) ──
2325
+ + "h+='<section id=\"g-flow\" style=\"margin-bottom:48px\">';"
2326
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2327
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2328
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">\\u5e73\\u53f0\\u4f7f\\u7528\\u6d41\\u7a0b\\uff086 \\u6b65\\uff09</h2>';"
2329
+ + "h+='</div>';"
2330
+ + "h+='<div class=\"sop-flow\">';"
2331
+ // Step 1
2332
+ + "h+='<div class=\"sop-step\"><div class=\"sop-step-num\">01</div><div class=\"sop-step-body\">';"
2333
+ + "h+='<div class=\"sop-step-title\">\\u6ce8\\u518c\\u516c\\u53f8</div>';"
2334
+ + "h+='<div class=\"sop-step-desc\">\\u767b\\u8bb0\\u4e00\\u4eba\\u516c\\u53f8\\u57fa\\u672c\\u4fe1\\u606f\\uff0c\\u81ea\\u52a8\\u521b\\u5efa\\u4e13\\u5c5e AI Agent</div>';"
2335
+ + "h+='<div class=\"sop-step-actions\"><span class=\"sop-tag\">opc_manage</span><span class=\"sop-tag\">register_company</span></div>';"
2336
+ + "h+='<div class=\"sop-step-detail\"><b>\\u64cd\\u4f5c\\u6b65\\u9aa4:</b><ol><li>\\u8f93\\u5165\\u516c\\u53f8\\u540d\\u79f0\\u3001\\u884c\\u4e1a\\u3001\\u521b\\u529e\\u4eba\\u4fe1\\u606f</li><li>AI \\u81ea\\u52a8\\u521b\\u5efa\\u516c\\u53f8\\u8bb0\\u5f55\\u548c\\u4e13\\u5c5e Agent</li><li>\\u8fd4\\u56de\\u516c\\u53f8 ID \\u7528\\u4e8e\\u540e\\u7eed\\u64cd\\u4f5c</li></ol></div>';"
2337
+ + "h+='</div></div>';"
2338
+ + "h+='<div class=\"sop-arrow\">\\u2193</div>';"
2339
+ // Step 2
2340
+ + "h+='<div class=\"sop-step\"><div class=\"sop-step-num\">02</div><div class=\"sop-step-body\">';"
2341
+ + "h+='<div class=\"sop-step-title\">\\u6fc0\\u6d3b\\u516c\\u53f8</div>';"
2342
+ + "h+='<div class=\"sop-step-desc\">\\u5c06\\u516c\\u53f8\\u72b6\\u6001\\u4ece\\u201c\\u5f85\\u6ce8\\u518c\\u201d\\u53d8\\u66f4\\u4e3a\\u201c\\u8fd0\\u8425\\u4e2d\\u201d\\uff0c\\u5f00\\u542f\\u5168\\u529f\\u80fd\\u6a21\\u5757</div>';"
2343
+ + "h+='<div class=\"sop-step-actions\"><span class=\"sop-tag\">opc_manage</span><span class=\"sop-tag\">activate_company</span></div>';"
2344
+ + "h+='<div class=\"sop-step-detail\"><b>\\u64cd\\u4f5c\\u6b65\\u9aa4:</b><ol><li>\\u786e\\u8ba4\\u516c\\u53f8\\u57fa\\u672c\\u4fe1\\u606f\\u5b8c\\u6574</li><li>\\u6267\\u884c\\u6fc0\\u6d3b\\u64cd\\u4f5c</li><li>\\u516c\\u53f8\\u72b6\\u6001\\u53d8\\u66f4\\u4e3a active</li></ol></div>';"
2345
+ + "h+='</div></div>';"
2346
+ + "h+='<div class=\"sop-arrow\">\\u2193</div>';"
2347
+ // Step 3
2348
+ + "h+='<div class=\"sop-step\"><div class=\"sop-step-num\">03</div><div class=\"sop-step-body\">';"
2349
+ + "h+='<div class=\"sop-step-title\">\\u914d\\u7f6e AI \\u56e2\\u961f</div>';"
2350
+ + "h+='<div class=\"sop-step-desc\">\\u4e00\\u952e\\u521d\\u59cb\\u5316 6 \\u4e2a AI \\u5c97\\u4f4d\\uff0c\\u8ba9\\u516c\\u53f8\\u62e5\\u6709\\u5b8c\\u6574 AI \\u56e2\\u961f</div>';"
2351
+ + "h+='<div class=\"sop-step-actions\"><span class=\"sop-tag\">opc_hr</span><span class=\"sop-tag\">init_default_staff</span></div>';"
2352
+ + "h+='<div class=\"sop-step-detail\"><b>\\u9ed8\\u8ba4\\u521d\\u59cb\\u5316 6 \\u4e2a\\u5c97\\u4f4d:</b>';"
2353
+ + "h+='<div class=\"sop-roles\"><span>\\ud83d\\udcc4 \\u884c\\u653f\\u52a9\\u7406</span><span>\\ud83d\\udc65 HR \\u4e13\\u5458</span><span>\\ud83d\\udcb0 \\u8d22\\u52a1\\u987e\\u95ee</span><span>\\u2696 \\u6cd5\\u52a1\\u52a9\\u7406</span><span>\\ud83d\\udce3 \\u5e02\\u573a\\u63a8\\u5e7f</span><span>\\ud83d\\udcca \\u8fd0\\u8425\\u7ecf\\u7406</span></div></div>';"
2354
+ + "h+='</div></div>';"
2355
+ + "h+='<div class=\"sop-arrow\">\\u2193</div>';"
2356
+ // Step 4
2357
+ + "h+='<div class=\"sop-step sop-step-wide\"><div class=\"sop-step-num\">04</div><div class=\"sop-step-body\">';"
2358
+ + "h+='<div class=\"sop-step-title\">\\u65e5\\u5e38\\u8fd0\\u8425</div>';"
2359
+ + "h+='<div class=\"sop-step-desc\">\\u901a\\u8fc7\\u5bf9\\u8bdd AI \\u5b8c\\u6210\\u65e5\\u5e38\\u4e1a\\u52a1\\u64cd\\u4f5c\\uff0c\\u7cfb\\u7edf\\u81ea\\u52a8\\u63d0\\u9192\\u91cd\\u8981\\u4e8b\\u9879</div>';"
2360
+ + "h+='<div class=\"sop-modules\">';"
2361
+ + "var mods=[{icon:'\\ud83d\\udcb0',name:'\\u8d22\\u52a1\\u8bb0\\u8d26',tool:'opc_manage',acts:'add_transaction / finance_summary'},{icon:'\\ud83d\\udcc4',name:'\\u53d1\\u7968\\u7ba1\\u7406',tool:'opc_finance',acts:'create_invoice / list_invoices'},{icon:'\\u2696',name:'\\u5408\\u540c\\u7ba1\\u7406',tool:'opc_legal',acts:'create_contract / list_contracts'},{icon:'\\ud83d\\udc65',name:'\\u5458\\u5de5\\u7ba1\\u7406',tool:'opc_hr',acts:'add_employee / payroll_summary'},{icon:'\\ud83d\\udce3',name:'\\u5185\\u5bb9\\u8fd0\\u8425',tool:'opc_media',acts:'create_content / publish'},{icon:'\\ud83d\\udcc8',name:'\\u9879\\u76ee\\u7ba1\\u7406',tool:'opc_project',acts:'create_project / update_task'}];"
2362
+ + "mods.forEach(function(m){h+='<div class=\"sop-module\"><div class=\"sop-module-icon\">'+m.icon+'</div><div class=\"sop-module-name\">'+esc(m.name)+'</div><div class=\"sop-module-tool\">'+esc(m.tool)+'</div><div class=\"sop-module-acts\">'+esc(m.acts)+'</div></div>';});"
2363
+ + "h+='</div>';"
2364
+ + "h+='<div class=\"sop-reminder-tip\">\\ud83d\\udd14 \\u81ea\\u52a8\\u63d0\\u9192\\u5df2\\u5f00\\u542f\\uff1a\\u7a0e\\u52a1\\u7533\\u62a5\\u3001\\u5408\\u540c\\u5230\\u671f\\u3001\\u73b0\\u91d1\\u6d41\\u9884\\u8b66\\u5c06\\u81ea\\u52a8\\u751f\\u6210\\u5230\\u76d1\\u63a7\\u4e2d\\u5fc3</div>';"
2365
+ + "h+='</div></div>';"
2366
+ + "h+='<div class=\"sop-arrow\">\\u2193</div>';"
2367
+ // Step 5 - capital loop
2368
+ + "h+='<div class=\"sop-step sop-step-highlight\"><div class=\"sop-step-num\">05</div><div class=\"sop-step-body\">';"
2369
+ + "h+='<div class=\"sop-step-title\">\\u8d44\\u91d1\\u95ed\\u73af</div>';"
2370
+ + "h+='<div class=\"sop-step-desc\">\\u661f\\u73af OPC \\u5e73\\u53f0\\u6838\\u5fc3\\u8d44\\u91d1\\u95ed\\u73af\\u6a21\\u578b</div>';"
2371
+ + "h+='<div class=\"sop-capital-loop\">';"
2372
+ + "var cloop=[{c:'\\u6295\\u8d44\\u53c2\\u80a1',d:'\\u57ce\\u6295\\u516c\\u53f8\\u53c2\\u8d44\\u5b54\\u5316\\u4f01\\u4e1a',t:'opc_investment'},{c:'\\u670d\\u52a1\\u91c7\\u8d2d',d:'\\u4f01\\u4e1a\\u5411\\u5e73\\u53f0\\u91c7\\u8d2d\\u63d0\\u5347\\u670d\\u52a1',t:'opc_procurement'},{c:'\\u8d44\\u91d1\\u56de\\u6d41',d:'\\u670d\\u52a1\\u8d39\\u6536\\u5165\\u56de\\u6d41\\u5e73\\u53f0',t:'opc_finance'},{c:'\\u8d44\\u4ea7\\u8f6c\\u8ba9',d:'\\u6253\\u5305\\u4f18\\u8d28\\u8d44\\u4ea7\\u8f6c\\u8ba9\\u57ce\\u6295',t:'opc_lifecycle'},{c:'\\u878d\\u8d44\\u670d\\u52a1\\u8d39',d:'\\u57ce\\u6295\\u878d\\u8d44\\u6536\\u53d6\\u670d\\u52a1\\u8d39\\u7528',t:'opc_investment'}];"
2373
+ + "var arrowSvg='<div class=\"sop-cap-arrow\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\"><path d=\"M5 10h10M12 7l3 3-3 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg></div>';"
2374
+ + "var downArrowSvg='<div style=\"text-align:center;padding:8px 0;color:var(--tx3)\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\"><path d=\"M10 5v10M7 12l3 3 3-3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg></div>';"
2375
+ + "function makeStep(s,i){return '<div class=\"sop-capital-step\"><div class=\"sop-cap-num\">'+(i+1)+'</div><div class=\"sop-cap-title\">'+esc(s.c)+'</div><div class=\"sop-cap-desc\">'+esc(s.d)+'</div><div class=\"sop-cap-tag\">'+esc(s.t)+'</div></div>';}"
2376
+ // Single row: all 5 steps with arrows
2377
+ + "h+='<div style=\"display:flex;align-items:center;gap:0\">';"
2378
+ + "for(var si=0;si<cloop.length;si++){h+=makeStep(cloop[si],si);if(si<cloop.length-1)h+=arrowSvg;}"
2379
+ + "h+='</div>';"
2380
+ + "h+='</div></div></div>';"
2381
+ + "h+='<div class=\"sop-arrow\">\\u2193</div>';"
2382
+ // Step 6
2383
+ + "h+='<div class=\"sop-step\"><div class=\"sop-step-num\">06</div><div class=\"sop-step-body\">';"
2384
+ + "h+='<div class=\"sop-step-title\">\\u751f\\u547d\\u5468\\u671f\\u62a5\\u544a</div>';"
2385
+ + "h+='<div class=\"sop-step-desc\">\\u5b9a\\u671f\\u751f\\u6210\\u516c\\u53f8\\u8fd0\\u8425\\u62a5\\u544a\\uff0c\\u8ddf\\u8e2a\\u6210\\u957f\\u8f68\\u8ff9</div>';"
2386
+ + "h+='<div class=\"sop-step-actions\"><span class=\"sop-tag\">opc_lifecycle</span><span class=\"sop-tag\">generate_report</span></div>';"
2387
+ + "h+='<div class=\"sop-step-detail\"><b>\\u62a5\\u544a\\u5305\\u542b:</b> \\u516c\\u53f8\\u57fa\\u672c\\u4fe1\\u606f\\u3001\\u8d22\\u52a1\\u6982\\u51b5\\u3001\\u91cc\\u7a0b\\u7891\\u8fdb\\u5c55\\u3001\\u5458\\u5de5\\u4eba\\u6570\\u3001\\u5408\\u540c\\u72b6\\u6001\\u3001\\u4e0b\\u4e00\\u9636\\u6bb5\\u8ba1\\u5212</div>';"
2388
+ + "h+='</div></div>';"
2389
+ + "h+='</div>';" // end sop-flow
2390
+ + "h+='</section>';"
2391
+
2392
+ // ── Section 7: AI工具说明 ──
2393
+ + "h+='<section id=\"g-tools\" style=\"margin-bottom:48px\">';"
2394
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2395
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2396
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">AI \\u5de5\\u5177\\u8bf4\\u660e</h2>';"
2397
+ + "h+='</div>';"
2398
+ + "h+='<div class=\"tool-grid\">';"
2399
+ + "var tools7=["
2400
+ + "{k:'opc_manage',l:'\\u516c\\u53f8\\u7ba1\\u7406',d:'\\u6ce8\\u518c\\u516c\\u53f8\\u3001\\u6fc0\\u6d3b\\u3001\\u8d22\\u52a1\\u8bb0\\u8d26\\u3001\\u76d1\\u63a7\\u3001\\u751f\\u547d\\u5468\\u671f\\u62a5\\u544a',acts:['register_company','activate_company','add_transaction','finance_summary','list_companies']},"
2401
+ + "{k:'opc_opb',l:'OPB \\u753b\\u5e03',d:'\\u521b\\u5efa\\u548c\\u66f4\\u65b0\\u4e00\\u4eba\\u4f01\\u4e1a 16 \\u6a21\\u5757\\u753b\\u5e03\\uff0c\\u5e2e\\u52a9\\u6de8\\u5316\\u4e1a\\u52a1\\u6a21\\u5f0f',acts:['get_canvas','save_canvas','create_canvas','analyze_canvas']},"
2402
+ + "{k:'opc_finance',l:'\\u8d22\\u52a1\\u7ba1\\u7406',d:'\\u53d1\\u7968\\u521b\\u5efa\\u3001\\u67e5\\u8be2\\uff0c\\u8d22\\u52a1\\u62a5\\u544a\\u751f\\u6210',acts:['create_invoice','list_invoices','get_invoice','finance_report']},"
2403
+ + "{k:'opc_legal',l:'\\u6cd5\\u52a1\\u52a9\\u7406',d:'\\u5408\\u540c\\u7ba1\\u7406\\uff0c\\u5408\\u89c4\\u68c0\\u67e5\\uff0c\\u6cd5\\u5f8b\\u6587\\u4ef6\\u5b58\\u6863',acts:['create_contract','list_contracts','get_contract','compliance_check']},"
2404
+ + "{k:'opc_hr',l:'HR \\u4e13\\u5458',d:'\\u5458\\u5de5\\u5c55\\u5f00\\u548c\\u79bb\\u804c\\u3001\\u85aa\\u8d44\\u7ba1\\u7406\\u3001\\u9ed8\\u8ba4\\u5c97\\u4f4d\\u521d\\u59cb\\u5316',acts:['add_employee','offboard_employee','payroll_summary','init_default_staff']},"
2405
+ + "{k:'opc_media',l:'\\u5185\\u5bb9\\u8fd0\\u8425',d:'\\u5185\\u5bb9\\u521b\\u4f5c\\u3001\\u76f8\\u518c\\u7ba1\\u7406\\u3001\\u5185\\u5bb9\\u65e5\\u5386\\u89c4\\u5212',acts:['create_content','list_content','publish_content','content_calendar']},"
2406
+ + "{k:'opc_project',l:'\\u9879\\u76ee\\u7ba1\\u7406',d:'\\u9879\\u76ee\\u521b\\u5efa\\u548c\\u8ddf\\u8e2a\\u3001\\u4efb\\u52a1\\u66f4\\u65b0\\u3001\\u91cc\\u7a0b\\u7891\\u7ba1\\u7406',acts:['create_project','list_projects','update_task','milestone_report']}"
2407
+ + "];"
2408
+ + "tools7.forEach(function(t){"
2409
+ + "h+='<div class=\"tool-card\" style=\"margin-bottom:0\">';"
2410
+ + "h+='<div class=\"tool-card-header\"><div><div class=\"name\">'+esc(t.l)+'</div><div class=\"key\">'+esc(t.k)+'</div></div></div>';"
2411
+ + "h+='<div class=\"tool-card-body\"><div class=\"desc\">'+esc(t.d)+'</div>';"
2412
+ + "h+='<div style=\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\">';"
2413
+ + "t.acts.forEach(function(a){h+='<span class=\"sop-tag\" style=\"font-size:11px\">'+esc(a)+'</span>';});"
2414
+ + "h+='</div></div>';"
2415
+ + "h+='</div>';"
2416
+ + "});"
2417
+ + "h+='</div>';"
2418
+ + "h+='</section>';"
2419
+
2420
+ // ── Section 8: 常用对话指令 ──
2421
+ + "h+='<section id=\"g-cmds\" style=\"margin-bottom:48px\">';"
2422
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2423
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2424
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">\\u5e38\\u7528\\u5bf9\\u8bdd\\u6307\\u4ee4</h2>';"
2425
+ + "h+='</div>';"
2426
+ + "h+='<div class=\"sop-cmd-list\">';"
2427
+ + "var cmds8=["
2428
+ + "{t:'\\u6ce8\\u518c\\u516c\\u53f8',c:'\\u5e2e\\u6211\\u6ce8\\u518c\\u4e00\\u5bb6\\u516c\\u53f8\\uff1a\\u540d\\u79f0[\\u516c\\u53f8\\u540d]\\uff0c\\u884c\\u4e1a[\\u884c\\u4e1a]\\uff0c\\u521b\\u529e\\u4eba[\\u59d3\\u540d]'},"
2429
+ + "{t:'\\u6fc0\\u6d3b\\u516c\\u53f8',c:'\\u6fc0\\u6d3b\\u516c\\u53f8 {company_id}'},"
2430
+ + "{t:'\\u5f00\\u542f AI \\u56e2\\u961f',c:'\\u4e3a\\u516c\\u53f8 {company_id} \\u521d\\u59cb\\u5316\\u9ed8\\u8ba4 AI \\u5c97\\u4f4d'},"
2431
+ + "{t:'\\u67e5\\u770b\\u516c\\u53f8\\u5217\\u8868',c:'\\u5217\\u51fa\\u6240\\u6709\\u516c\\u53f8'},"
2432
+ + "{t:'\\u8d22\\u52a1\\u8bb0\\u8d26',c:'\\u5e2e\\u6211\\u8bb0\\u5f55\\u4e00\\u7b14\\u6536\\u5165\\uff1a\\u91d1\\u989d 5000 \\u5143\\uff0c\\u6765\\u81ea\\u5ba2\\u6237 ABC'},"
2433
+ + "{t:'\\u521b\\u5efa\\u53d1\\u7968',c:'\\u4e3a {company_id} \\u521b\\u5efa\\u53d1\\u7968\\uff0c\\u91d1\\u989d 3000 \\u5143'},"
2434
+ + "{t:'\\u5408\\u540c\\u7ba1\\u7406',c:'\\u521b\\u5efa\\u4e00\\u4efd\\u670d\\u52a1\\u5408\\u540c\\uff0c\\u5ba2\\u6237 XYZ\\uff0c\\u91d1\\u989d 10000 \\u5143'},"
2435
+ + "{t:'OPB \\u753b\\u5e03',c:'\\u67e5\\u770b\\u516c\\u53f8 {company_id} \\u7684 OPB \\u753b\\u5e03'},"
2436
+ + "{t:'\\u5185\\u5bb9\\u521b\\u4f5c',c:'\\u4e3a\\u516c\\u53f8 {company_id} \\u521b\\u5efa\\u4e00\\u7bc7\\u6807\\u9898\\u4e3a[\\u6807\\u9898]\\u7684\\u516c\\u4f17\\u53f7\\u6587\\u7ae0'},"
2437
+ + "{t:'\\u9879\\u76ee\\u8ddf\\u8e2a',c:'\\u521b\\u5efa\\u9879\\u76ee\\uff1a[\\u9879\\u76ee\\u540d]\\uff0c\\u622a\\u6b62\\u65e5\\u671f [\\u65e5\\u671f]'},"
2438
+ + "{t:'\\u751f\\u547d\\u5468\\u671f\\u62a5\\u544a',c:'\\u751f\\u6210\\u516c\\u53f8 {company_id} \\u8fd0\\u8425\\u62a5\\u544a'},"
2439
+ + "{t:'\\u8d22\\u52a1\\u6458\\u8981',c:'\\u67e5\\u770b\\u516c\\u53f8 {company_id} \\u672c\\u6708\\u8d22\\u52a1\\u60c5\\u51b5'}"
2440
+ + "];"
2441
+ + "cmds8.forEach(function(c,i){h+='<div class=\"sop-cmd\"><div class=\"sop-cmd-num\">'+(i+1)+'</div><div class=\"sop-cmd-body\"><div class=\"sop-cmd-title\">'+esc(c.t)+'</div><div class=\"sop-cmd-text\">'+esc(c.c)+'</div></div></div>';});"
2442
+ + "h+='</div>';"
2443
+ + "h+='</section>';"
2444
+
2445
+ // ── Section 9: Heartbeat自主工作模式 ──
2446
+ + "h+='<section id=\"g-hb\" style=\"margin-bottom:48px\">';"
2447
+ + "h+='<div style=\"display:flex;align-items:center;gap:10px;margin-bottom:20px\">';"
2448
+ + "h+='<div style=\"width:3px;height:20px;background:#2563eb;border-radius:2px\"></div>';"
2449
+ + "h+='<h2 style=\"font-size:18px;font-weight:700;margin:0;color:var(--tx)\">Heartbeat \\u81ea\\u4e3b\\u5de5\\u4f5c\\u6a21\\u5f0f</h2>';"
2450
+ + "h+='</div>';"
2451
+ + "h+='<p style=\"font-size:15px;line-height:1.7;margin-bottom:16px\">OpenClaw \\u5177\\u5907 <b>Heartbeat</b> \\u81ea\\u4e3b Agent \\u80fd\\u529b\\uff0c\\u53ef\\u5728\\u65e0\\u4eba\\u5e72\\u9884\\u7684\\u60c5\\u51b5\\u4e0b\\u6301\\u7eed\\u8fd0\\u884c\\u81ea\\u52a8\\u5316\\u4e1a\\u52a1\\u6d41\\u7a0b\\u3002</p>';"
2452
+ + "h+='<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px\">';"
2453
+ + "h+='<div>';"
2454
+ + "h+='<div style=\"font-weight:700;margin-bottom:8px\">\\u53ef\\u81ea\\u52a8\\u5316\\u7684\\u5de5\\u4f5c</div>';"
2455
+ + "h+='<ul style=\"list-style:none;padding:0;margin:0\">';"
2456
+ + "var autoTasks=['\\u7a0e\\u52a1\\u7533\\u62a5\\u63d0\\u9192\\uff08\\u5206\\u5b63\\u5ea6\\uff09','\\u5408\\u540c\\u5230\\u671f\\u9884\\u8b66\\uff08\\u63d0\\u524d 7 \\u5929\\uff09','\\u73b0\\u91d1\\u6d41\\u9884\\u8b66\\uff08\\u4f4e\\u4e8e\\u9608\\u5024\\uff09','\\u5185\\u5bb9\\u53d1\\u5e03\\u65e5\\u5386\\u6267\\u884c','\\u5458\\u5de5\\u85aa\\u8d44\\u8ba1\\u7b97\\u63d0\\u9192','\\u9879\\u76ee\\u8fdb\\u5ea6\\u81ea\\u52a8\\u66f4\\u65b0','\\u8fd0\\u8425\\u62a5\\u544a\\u5b9a\\u671f\\u751f\\u6210'];"
2457
+ + "autoTasks.forEach(function(t){h+='<li style=\"padding:4px 0;font-size:13px;display:flex;align-items:center;gap:8px\"><span style=\"color:#10b981\">\\u2713</span>'+esc(t)+'</li>';});"
2458
+ + "h+='</ul>';"
2459
+ + "h+='</div>';"
2460
+ + "h+='<div>';"
2461
+ + "h+='<div style=\"font-weight:700;margin-bottom:8px\">\\u5f00\\u542f\\u65b9\\u5f0f</div>';"
2462
+ + "h+='<div style=\"padding:16px;background:var(--bg,#f8fafc);border-radius:8px;border:1px solid var(--bd,#e2e8f0);margin-bottom:12px\">';"
2463
+ + "h+='<div style=\"font-size:13px;line-height:1.6\">';"
2464
+ + "h+='<p style=\"margin:0 0 8px\"><b>1. \\u914d\\u7f6e Webhook</b></p>';"
2465
+ + "h+='<p style=\"font-size:12px;color:var(--tx2,#64748b);margin:0 0 12px\">\\u5728\\u300a\\u5de5\\u5177\\u914d\\u7f6e\\u300b\\u9875\\u9762\\u586b\\u5199 Webhook \\u5730\\u5740\\uff0c\\u7528\\u4e8e\\u63a5\\u6536\\u5b9a\\u65f6\\u89e6\\u53d1\\u7684\\u81ea\\u52a8\\u5316\\u6d88\\u606f</p>';"
2466
+ + "h+='<p style=\"margin:0 0 8px\"><b>2. \\u76d1\\u63a7\\u4e2d\\u5fc3</b></p>';"
2467
+ + "h+='<p style=\"font-size:12px;color:var(--tx2,#64748b);margin:0 0 12px\">\\u5728\\u300a\\u76d1\\u63a7\\u4e2d\\u5fc3\\u300b\\u67e5\\u770b\\u5df2\\u81ea\\u52a8\\u751f\\u6210\\u7684\\u63d0\\u9192\\u548c\\u544a\\u8b66\\u4e8b\\u9879</p>';"
2468
+ + "h+='<p style=\"margin:0 0 8px\"><b>3. \\u5bf9\\u8bdd\\u6fc0\\u6d3b</b></p>';"
2469
+ + "h+='<p style=\"font-size:12px;color:var(--tx2,#64748b);margin:0\">\\u5728 Agent \\u5bf9\\u8bdd\\u4e2d\\u8f93\\u5165\\u5185\\u5bb9\\u5373\\u53ef\\u89e6\\u53d1\\u76f8\\u5e94\\u81ea\\u52a8\\u5316\\u6d41\\u7a0b</p>';"
2470
+ + "h+='</div>';"
2471
+ + "h+='</div>';"
2472
+ + "h+='<div style=\"padding:12px;background:rgba(14,165,233,0.1);border-radius:8px;border:1px solid rgba(14,165,233,0.3)\">';"
2473
+ + "h+='<div style=\"font-size:12px;color:var(--tx2,#64748b);line-height:1.5\">\\ud83d\\udca1 <b>\\u63d0\\u793a</b>\\uff1a\\u5f53 Heartbeat \\u68c0\\u6d4b\\u5230\\u5f02\\u5e38\\u65f6\\uff0c\\u4f1a\\u81ea\\u52a8\\u53d1\\u9001\\u901a\\u77e5\\u5230\\u5df2\\u914d\\u7f6e\\u7684 Webhook\\uff0c\\u5b9e\\u73b0\\u771f\\u6b63\\u7684\\u516c\\u53f8\\u81ea\\u52a8\\u5316\\u8fd0\\u8425\\u3002</div>';"
2474
+ + "h+='</div>';"
2475
+ + "h+='</div>';"
2476
+ + "h+='</div>';"
2477
+ + "h+='</section>';"
2478
+
2479
+ // ── Quick start card ──
2480
+ + "h+='<div style=\"background:linear-gradient(135deg,#1e293b 0%,#1e3a5f 100%);border-radius:12px;padding:28px 32px;margin-bottom:24px\">';"
2481
+ + "h+='<h3 style=\"font-size:16px;font-weight:700;color:#fff;margin:0 0 4px\">\\u5feb\\u901f\\u5f00\\u59cb</h3>';"
2482
+ + "h+='<p style=\"font-size:13px;color:rgba(255,255,255,0.6);margin:0 0 20px\">\\u5168\\u6d41\\u7a0b\\u4e00\\u952e\\u6307\\u4ee4\\uff0c\\u5c06\\u4e0b\\u9762\\u6307\\u4ee4\\u8f93\\u5165 Agent \\u5bf9\\u8bdd\\u5373\\u53ef\\u5f00\\u59cb</p>';"
2483
+ + "h+='<div style=\"display:flex;flex-direction:column;gap:8px\">';"
2484
+ + "var qcmds=[{t:'\\u6ce8\\u518c\\u516c\\u53f8',c:'\\u5e2e\\u6211\\u6ce8\\u518c\\u4e00\\u5bb6\\u516c\\u53f8\\uff1a\\u540d\\u79f0[\\u516c\\u53f8\\u540d]\\uff0c\\u884c\\u4e1a[\\u884c\\u4e1a]\\uff0c\\u521b\\u529e\\u4eba[\\u59d3\\u540d]'},{t:'\\u6fc0\\u6d3b\\u516c\\u53f8',c:'\\u6fc0\\u6d3b\\u516c\\u53f8 {company_id}'},{t:'\\u5f00\\u542f AI \\u56e2\\u961f',c:'\\u4e3a\\u516c\\u53f8 {company_id} \\u521d\\u59cb\\u5316\\u9ed8\\u8ba4 AI \\u5c97\\u4f4d'},{t:'\\u8fdb\\u5165 Agent \\u5bf9\\u8bdd',c:'\\u5728\\u516c\\u53f8\\u5217\\u8868\\u70b9\\u51fb\\u5bf9\\u8bdd\\u6309\\u94ae'},{t:'\\u65e5\\u5e38\\u8bb0\\u8d26',c:'\\u5e2e\\u6211\\u8bb0\\u5f55\\u4e00\\u7b14\\u6536\\u5165\\uff1a\\u91d1\\u989d 5000 \\u5143\\uff0c\\u6765\\u81ea\\u5ba2\\u6237 ABC'},{t:'\\u67e5\\u770b\\u63d0\\u9192',c:'\\u5728\\u76d1\\u63a7\\u4e2d\\u5fc3\\u67e5\\u770b\\u81ea\\u52a8\\u751f\\u6210\\u7684\\u63d0\\u9192\\u548c\\u544a\\u8b66'}];"
2485
+ + "qcmds.forEach(function(c,i){"
2486
+ + "h+='<div style=\"display:flex;gap:12px;align-items:flex-start;background:rgba(255,255,255,0.06);border-radius:8px;padding:12px 14px\">';"
2487
+ + "h+='<div style=\"width:22px;height:22px;background:#2563eb;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex-shrink:0\">'+(i+1)+'</div>';"
2488
+ + "h+='<div><div style=\"font-size:13px;font-weight:600;color:#fff;margin-bottom:2px\">'+esc(c.t)+'</div>';"
2489
+ + "h+='<div style=\"font-size:12px;color:rgba(255,255,255,0.5);font-family:monospace\">'+esc(c.c)+'</div></div>';"
2490
+ + "h+='</div>';"
2491
+ + "});"
2492
+ + "h+='</div>';"
2493
+ + "h+='</div>';"
2494
+
2495
+ // close content area + docs layout wrapper
2496
+ + "h+='</div>';" // end right content area
2497
+ + "h+='</div>';" // end docs layout flex row
2498
+
2499
+ + "return h;"
2500
+ + "}"
2501
+ // guideTocClick helper
2502
+ + "\nfunction guideTocClick(e,id){"
2503
+ + "e.preventDefault();"
2504
+ + "var el=document.getElementById(id);if(!el)return;"
2505
+ + "el.scrollIntoView({behavior:'smooth',block:'start'});"
2506
+ + "document.querySelectorAll('[data-toc]').forEach(function(a){a.style.borderLeftColor='transparent';a.style.color='var(--tx2)';a.style.fontWeight='400';});"
2507
+ + "var active=document.querySelector('[data-toc=\"'+id+'\"]');"
2508
+ + "if(active){active.style.borderLeftColor='#2563eb';active.style.color='#2563eb';active.style.fontWeight='600';}"
2509
+ + "}";
2510
+ }
2511
+
2512
+ function getCanvasJs(): string {
2513
+ return "\nfunction initCanvasView(){"
2514
+ + "fetch('/opc/admin/api/companies').then(function(r){return r.json()}).then(function(companies){"
2515
+ + "var sel=document.getElementById('canvas-company-select');"
2516
+ + "var curVal=sel.value;"
2517
+ + "while(sel.options.length>1)sel.remove(1);"
2518
+ + "companies.forEach(function(c){var o=document.createElement('option');o.value=c.id;o.textContent=c.name+(c.status!=='active'?' (\u5df2\u6682\u505c)':'');sel.appendChild(o);});"
2519
+ + "if(curVal)sel.value=curVal;"
2520
+ + "if(companies.length===1)sel.value=companies[0].id;"
2521
+ + "if(sel.value)loadCanvas();"
2522
+ + "else{document.getElementById('canvas-content').innerHTML='<div class=\"empty-state\" style=\"margin-top:40px\"><p>\u8bf7\u5148\u9009\u62e9\u516c\u53f8</p></div>';}"
2523
+ + "}).catch(function(){document.getElementById('canvas-content').innerHTML='<div class=\"empty-state\"><p>\u52a0\u8f7d\u5931\u8d25</p></div>';});}"
2524
+ + "\nfunction loadCanvas(){"
2525
+ + "var companyId=document.getElementById('canvas-company-select').value;"
2526
+ + "if(!companyId){document.getElementById('canvas-content').innerHTML='<div class=\"empty-state\" style=\"margin-top:40px\"><p>\u8bf7\u9009\u62e9\u516c\u53f8</p></div>';return;}"
2527
+ + "document.getElementById('canvas-content').innerHTML='<div class=\"skeleton\" style=\"height:400px\"></div>';"
2528
+ + "fetch('/opc/admin/api/canvas?company_id='+encodeURIComponent(companyId)).then(function(r){return r.json()}).then(function(d){"
2529
+ + "renderCanvas(d,companyId);"
2530
+ + "}).catch(function(){document.getElementById('canvas-content').innerHTML='<div class=\"empty-state\"><p>\u52a0\u8f7d\u5931\u8d25</p></div>';});}"
2531
+ + "\nvar CANVAS_FIELDS=["
2532
+ + "{key:'track',label:'\u8d5b\u9053',placeholder:'\u6240\u5904\u884c\u4e1a\u6216\u7ec6\u5206\u5e02\u573a\uff0c\u5982\uff1a\u72ec\u7acb\u8bbe\u8ba1\u5e08\u670d\u52a1',group:'customer',icon:'\ud83c\udfaf'},"
2533
+ + "{key:'target_customer',label:'\u76ee\u6807\u5ba2\u6237',placeholder:'\u5177\u4f53\u7684\u4eba\u7fa4\u753b\u50cf\uff0c\u5982\uff1a30-45\u5c81\u4e2d\u5c0f\u4f01\u4e1a\u521b\u59cb\u4eba',group:'customer',icon:'\ud83d\udc65'},"
2534
+ + "{key:'pain_point',label:'\u6838\u5fc3\u75db\u70b9',placeholder:'\u5ba2\u6237\u6700\u75db\u7684\u95ee\u9898\u662f\u4ec0\u4e48\uff1f',group:'customer',icon:'\ud83d\udd25'},"
2535
+ + "{key:'solution',label:'\u89e3\u51b3\u65b9\u6848',placeholder:'\u4f60\u5982\u4f55\u89e3\u51b3\u4e0a\u8ff0\u75db\u70b9\uff1f',group:'value',icon:'\ud83d\udca1'},"
2536
+ + "{key:'unique_value',label:'\u72ec\u7279\u4ef7\u503c\u4e3b\u5f20',placeholder:'\u4e3a\u4ec0\u4e48\u5ba2\u6237\u8981\u9009\u62e9\u4f60\u800c\u4e0d\u662f\u7ade\u4e89\u5bf9\u624b\uff1f',group:'value',icon:'\u2728'},"
2537
+ + "{key:'channels',label:'\u83b7\u5ba2\u6e20\u9053',placeholder:'\u901a\u8fc7\u4ec0\u4e48\u65b9\u5f0f\u627e\u5230\u76ee\u6807\u5ba2\u6237\uff1f\u5982\uff1a\u516c\u4f17\u53f7\u3001\u8f6c\u4ecb\u3001SEO',group:'value',icon:'\ud83d\udce1'},"
2538
+ + "{key:'revenue_model',label:'\u6536\u5165\u6a21\u5f0f',placeholder:'\u5982\u4f55\u53d8\u73b0\uff1f\u9879\u76ee\u5236\u3001\u8ba2\u9605\u5236\u3001\u8bfe\u7a0b\u3001\u5e7f\u544a\u7b49',group:'ops',icon:'\ud83d\udcb0'},"
2539
+ + "{key:'cost_structure',label:'\u6210\u672c\u7ed3\u6784',placeholder:'\u4e3b\u8981\u6210\u672c\u9879\uff1a\u65f6\u95f4\u3001\u5de5\u5177\u3001\u5916\u5305\u3001\u8425\u9500',group:'ops',icon:'\ud83d\udcca'},"
2540
+ + "{key:'key_resources',label:'\u5173\u952e\u8d44\u6e90',placeholder:'\u4e09\u4e2a\u6c60\u5b50\uff1a\u5185\u5bb9\u6c60\u3001\u4ea7\u54c1\u6c60\u3001\u5ba2\u6237\u6c60',group:'ops',icon:'\ud83c\udfdb'},"
2541
+ + "{key:'key_activities',label:'\u5173\u952e\u6d3b\u52a8',placeholder:'\u6bcf\u5929/\u6bcf\u5468\u5fc5\u505a\u7684\u6838\u5fc3\u5de5\u4f5c',group:'ops',icon:'\u26a1'},"
2542
+ + "{key:'key_partners',label:'\u5173\u952e\u5408\u4f5c',placeholder:'\u54ea\u4e9b\u4eba\u6216\u673a\u6784\u53ef\u4ee5\u653e\u5927\u4f60\u7684\u80fd\u529b\uff1f',group:'ops',icon:'\ud83e\udd1d'},"
2543
+ + "{key:'unfair_advantage',label:'\u4e0d\u516c\u5e73\u4f18\u52bf',placeholder:'\u4eba\u4e0d\u8f7b\u6613\u590d\u5236\u7684\u72ec\u7279\u8d44\u6e90\uff1a\u4e13\u4e1a\u8d44\u8bc1\u3001\u72ec\u5bb6\u4fe1\u6e90\u3001\u5706\u5b50\u8d44\u6e90',group:'strategy',icon:'\ud83d\udd12'},"
2544
+ + "{key:'metrics',label:'\u5173\u952e\u6307\u6807',placeholder:'\u8861\u91cf\u4e1a\u52a1\u5065\u5eb7\u7684 KPI\uff1a\u5ba2\u5355\u6570\u3001\u6708\u6536\u5165\u3001\u5ba2\u6237\u6ee1\u610f\u5ea6',group:'strategy',icon:'\ud83d\udcc8'},"
2545
+ + "{key:'non_compete',label:'\u975e\u7ade\u4e89\u7b56\u7565',placeholder:'\u5982\u4f55\u907f\u5f00\u76f4\u63a5\u7ade\u4e89\uff1f\u7ec6\u5206\u5c0f\u4f17\u5e02\u573a\u3001\u6700\u7ec8\u5ba2\u6237\u5b9a\u4f4d\u7b49',group:'strategy',icon:'\ud83e\uddf0'},"
2546
+ + "{key:'scaling_strategy',label:'\u89c4\u6a21\u5316\u8def\u5f84',placeholder:'\u672a\u6765\u5982\u4f55\u8d85\u8d8a\u4e2a\u4eba\u65f6\u95f4\u5929\u82b1\u677f\uff1f\u8bfe\u7a0b\u5316\u3001\u5de5\u5177\u5316\u3001\u5343\u5929\u8ba1\u5212',group:'strategy',icon:'\ud83d\ude80'},"
2547
+ + "{key:'notes',label:'\u5907\u6ce8',placeholder:'\u5176\u4ed6\u8865\u5145\u8bf4\u660e',group:'notes',icon:'\ud83d\udcdd'}"
2548
+ + "];"
2549
+ + "\nvar CANVAS_GROUPS={"
2550
+ + "customer:{label:'\ud83d\udc64 \u5ba2\u6237\u5c42',color:'#3b82f6',bg:'#eff6ff',border:'#bfdbfe'},"
2551
+ + "value:{label:'\u2728 \u4ef7\u503c\u5c42',color:'#8b5cf6',bg:'#f5f3ff',border:'#ddd6fe'},"
2552
+ + "ops:{label:'\u2699\ufe0f \u8fd0\u8425\u5c42',color:'#f59e0b',bg:'#fffbeb',border:'#fde68a'},"
2553
+ + "strategy:{label:'\ud83c\udfaf \u6218\u7565\u5c42',color:'#10b981',bg:'#ecfdf5',border:'#a7f3d0'},"
2554
+ + "notes:{label:'\ud83d\udcdd \u5907\u6ce8',color:'#64748b',bg:'#f8fafc',border:'#e2e8f0'}"
2555
+ + "};"
2556
+ + "\nfunction renderCanvas(d,companyId){"
2557
+ + "var el=document.getElementById('canvas-content');"
2558
+ + "var canvas=d.canvas;"
2559
+ + "if(!canvas){"
2560
+ + "el.innerHTML='<div style=\"text-align:center;padding:80px 40px\">';"
2561
+ + "el.innerHTML+='<div style=\"font-size:48px;margin-bottom:16px\">\ud83d\uddbc\ufe0f</div>';"
2562
+ + "el.innerHTML+='<h3 style=\"margin:0 0 8px;color:var(--tx)\">\u5c1a\u672a\u521b\u5efa OPB \u753b\u5e03</h3>';"
2563
+ + "el.innerHTML+='<p style=\"color:var(--tx2);margin-bottom:24px;font-size:14px\">\u57fa\u4e8e\u300a\u4e00\u4eba\u4f01\u4e1a\u65b9\u6cd5\u8bba 2.0\u300b\u7cfb\u7edf\u5316\u8bbe\u8ba1\u4f60\u7684\u4e1a\u52a1\u6218\u7565\u84dd\u56fe</p>';"
2564
+ + "el.innerHTML+='<button class=\"btn btn-pri\" style=\"padding:10px 28px;font-size:14px\" onclick=\"initCanvas(\\''+companyId+'\\')\">\u521d\u59cb\u5316 OPB \u753b\u5e03</button></div>';"
2565
+ + "return;}"
2566
+ + "var pct=d.completion||0;"
2567
+ + "var pctColor=pct<30?'#ef4444':pct<70?'#f59e0b':'#10b981';"
2568
+ + "var h='<div class=\"card\" style=\"margin-bottom:20px;border-top:3px solid #0f172a\">';"
2569
+ + "h+='<div class=\"card-body\" style=\"display:flex;align-items:center;gap:20px;padding:16px 20px\">';"
2570
+ + "h+='<div style=\"flex:1\">';"
2571
+ + "h+='<div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px\">';"
2572
+ + "h+='<span style=\"font-size:13px;font-weight:600;color:var(--tx)\">\u753b\u5e03\u5b8c\u6210\u5ea6</span>';"
2573
+ + "h+='<span style=\"font-size:22px;font-weight:700;color:'+pctColor+'\">'+pct+'%</span>';"
2574
+ + "h+='</div>';"
2575
+ + "h+='<div style=\"background:#e2e8f0;border-radius:999px;height:10px;overflow:hidden\">';"
2576
+ + "h+='<div style=\"width:'+pct+'%;background:'+pctColor+';height:100%;border-radius:999px;transition:width .4s ease\"></div></div>';"
2577
+ + "h+='<div style=\"font-size:12px;color:var(--tx2);margin-top:6px\">';"
2578
+ + "h+='\u5df2\u586b '+d.filled+' \u4e2a\u6a21\u5757\uff0c\u8fd8\u6709 '+((d.total_fields||15)-d.filled)+' \u4e2a\u6a21\u5757\u5f85\u5b8c\u5584</div></div>';"
2579
+ + "h+='<button class=\"btn btn-pri\" onclick=\"saveCanvas(\\''+companyId+'\\')\" style=\"white-space:nowrap;padding:10px 24px\">\ud83d\udcbe \u4fdd\u5b58\u753b\u5e03</button>';"
2580
+ + "h+='</div></div>';"
2581
+ + "var groupOrder=['customer','value','ops','strategy','notes'];"
2582
+ + "groupOrder.forEach(function(gk){"
2583
+ + "var gFields=CANVAS_FIELDS.filter(function(f){return f.group===gk;});"
2584
+ + "if(!gFields.length)return;"
2585
+ + "var g=CANVAS_GROUPS[gk];"
2586
+ + "h+='<div style=\"margin-bottom:8px\">';"
2587
+ + "h+='<div style=\"font-size:11px;font-weight:700;color:'+g.color+';letter-spacing:.06em;text-transform:uppercase;margin-bottom:10px;display:flex;align-items:center;gap:6px\">';"
2588
+ + "h+='<span style=\"display:inline-block;width:3px;height:14px;background:'+g.color+';border-radius:2px\"></span>'+esc(g.label)+'</div>';"
2589
+ + "h+='<div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px;margin-bottom:20px\">';"
2590
+ + "gFields.forEach(function(f){"
2591
+ + "var val=canvas[f.key]||'';"
2592
+ + "var filled=val.trim()!=='';"
2593
+ + "h+='<div style=\"background:var(--card);border:1px solid '+(filled?g.color:'var(--bd)')+';border-left:3px solid '+g.color+';border-radius:8px;overflow:hidden;transition:border-color .2s\">';"
2594
+ + "h+='<div style=\"padding:10px 12px 6px;display:flex;align-items:center;gap:6px\">';"
2595
+ + "h+='<span style=\"font-size:14px\">'+f.icon+'</span>';"
2596
+ + "h+='<span style=\"font-size:12px;font-weight:600;color:var(--tx)\">'+esc(f.label)+'</span>';"
2597
+ + "if(filled)h+='<span style=\"margin-left:auto;font-size:10px;background:'+g.bg+';color:'+g.color+';padding:1px 6px;border-radius:20px;font-weight:600\">\u5df2\u586b</span>';"
2598
+ + "h+='</div>';"
2599
+ + "h+='<textarea id=\"canvas-'+f.key+'\" rows=\"3\" style=\"width:100%;padding:8px 12px;border:none;border-top:1px solid var(--bd);font-size:13px;font-family:var(--font);resize:vertical;background:'+(filled?g.bg:'var(--bg)')+';color:var(--tx);box-sizing:border-box;outline:none\" placeholder=\"'+esc(f.placeholder)+'\">'+esc(val)+'</textarea>';"
2600
+ + "h+='</div>';"
2601
+ + "});"
2602
+ + "h+='</div></div>';"
2603
+ + "});"
2604
+ + "el.innerHTML=h;}"
2605
+ + "\nfunction initCanvas(companyId){"
2606
+ + "fetch('/opc/admin/api/canvas',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({company_id:companyId})})"
2607
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok||d.canvas){loadCanvas();}else{showToast(d.error||'\u521b\u5efa\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
2608
+ + "\nfunction saveCanvas(companyId){"
2609
+ + "var data={company_id:companyId};"
2610
+ + "CANVAS_FIELDS.forEach(function(f){var el=document.getElementById('canvas-'+f.key);if(el)data[f.key]=el.value;});"
2611
+ + "fetch('/opc/admin/api/canvas',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
2612
+ + ".then(function(r){return r.json()}).then(function(d){if(d.ok){showToast('\u753b\u5e03\u5df2\u4fdd\u5b58');loadCanvas();}else{showToast(d.error||'\u4fdd\u5b58\u5931\u8d25');}}).catch(function(){showToast('\u8bf7\u6c42\u5931\u8d25');});}"
2613
+ + ";";
2614
+ }
2615
+
2616
+ /* ── Route registration ───────────────────────────────────── */
2617
+
2618
+ export function registerConfigUi(api: OpenClawPluginApi, db: OpcDatabase): void {
2619
+ api.registerHttpHandler(async (req, res) => {
2620
+ const rawUrl = req.url ?? "";
2621
+ const urlObj = new URL(rawUrl, "http://localhost");
2622
+ const pathname = urlObj.pathname;
2623
+ const method = req.method?.toUpperCase() ?? "GET";
2624
+
2625
+ if (!pathname.startsWith("/opc/admin")) {
2626
+ return false;
2627
+ }
2628
+
2629
+ try {
2630
+ // Config API: GET
2631
+ if (pathname === "/opc/admin/api/config" && method === "GET") {
2632
+ const rows = db.query("SELECT key, value FROM opc_tool_config") as { key: string; value: string }[];
2633
+ const config: Record<string, string> = {};
2634
+ for (const row of rows) {
2635
+ config[row.key] = row.value;
2636
+ }
2637
+ sendJson(res, config);
2638
+ return true;
2639
+ }
2640
+
2641
+ // Config API: POST
2642
+ if (pathname === "/opc/admin/api/config" && method === "POST") {
2643
+ const body = await readBody(req);
2644
+ const data = JSON.parse(body) as Record<string, string>;
2645
+ for (const [key, value] of Object.entries(data)) {
2646
+ db.execute(
2647
+ `INSERT INTO opc_tool_config (key, value) VALUES (?, ?)
2648
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
2649
+ key, value,
2650
+ );
2651
+ }
2652
+ sendJson(res, { ok: true });
2653
+ return true;
2654
+ }
2655
+
2656
+ // Webhook Test API: POST
2657
+ if (pathname === "/opc/admin/api/webhook-test" && method === "POST") {
2658
+ const body = await readBody(req);
2659
+ const { url } = JSON.parse(body) as { url?: string };
2660
+ if (!url) {
2661
+ sendJson(res, { ok: false, error: "url required" }, 400);
2662
+ return true;
2663
+ }
2664
+ await new Promise<void>((resolve) => {
2665
+ try {
2666
+ const isFeishu = url.includes("feishu.cn") || url.includes("larksuite.com");
2667
+ const text = "【星环OPC中心】Webhook 测试消息,连接正常 ✓";
2668
+ const bodyStr = isFeishu
2669
+ ? JSON.stringify({ msg_type: "text", content: { text } })
2670
+ : JSON.stringify({ msgtype: "text", text: { content: text } });
2671
+ const parsed = new URL(url);
2672
+ const transport = parsed.protocol === "https:" ? https : http;
2673
+ const reqOut = transport.request(
2674
+ {
2675
+ hostname: parsed.hostname,
2676
+ port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
2677
+ path: parsed.pathname + parsed.search,
2678
+ method: "POST",
2679
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(bodyStr) },
2680
+ },
2681
+ (r) => {
2682
+ r.resume();
2683
+ sendJson(res, r.statusCode && r.statusCode < 400 ? { ok: true } : { ok: false, error: `HTTP ${r.statusCode}` });
2684
+ resolve();
2685
+ },
2686
+ );
2687
+ reqOut.on("error", (err) => { sendJson(res, { ok: false, error: err.message }); resolve(); });
2688
+ reqOut.write(bodyStr);
2689
+ reqOut.end();
2690
+ } catch (err) {
2691
+ sendJson(res, { ok: false, error: err instanceof Error ? err.message : String(err) });
2692
+ resolve();
2693
+ }
2694
+ });
2695
+ return true;
2696
+ }
2697
+
2698
+ // Enhanced Dashboard API
2699
+ if (pathname === "/opc/admin/api/dashboard/enhanced" && method === "GET") {
2700
+ sendJson(res, handleDashboardEnhanced(db));
2701
+ return true;
2702
+ }
2703
+
2704
+ // Companies List API (with search/filter/pagination)
2705
+ if (pathname === "/opc/admin/api/companies/list" && method === "GET") {
2706
+ sendJson(res, handleCompaniesList(db, urlObj));
2707
+ return true;
2708
+ }
2709
+
2710
+ // Company Detail API
2711
+ const detailMatch = pathname.match(/^\/opc\/admin\/api\/companies\/([^/]+)\/detail$/);
2712
+ if (detailMatch && method === "GET") {
2713
+ const result = handleCompanyDetail(db, detailMatch[1]);
2714
+ if (!result) {
2715
+ sendJson(res, { error: "Company not found" }, 404);
2716
+ } else {
2717
+ sendJson(res, result);
2718
+ }
2719
+ return true;
2720
+ }
2721
+
2722
+ // Finance Overview API
2723
+ if (pathname === "/opc/admin/api/finance/overview" && method === "GET") {
2724
+ sendJson(res, handleFinanceOverview(db));
2725
+ return true;
2726
+ }
2727
+
2728
+ // Monitoring API
2729
+ if (pathname === "/opc/admin/api/monitoring" && method === "GET") {
2730
+ sendJson(res, handleMonitoring(db));
2731
+ return true;
2732
+ }
2733
+
2734
+ // Alert Dismiss API
2735
+ const alertMatch = pathname.match(/^\/opc\/admin\/api\/alerts\/([^/]+)\/dismiss$/);
2736
+ if (alertMatch && method === "POST") {
2737
+ sendJson(res, handleAlertDismiss(db, alertMatch[1]));
2738
+ return true;
2739
+ }
2740
+
2741
+ // ── CSV 导出 API ────────────────────────────────────────────
2742
+
2743
+ // ── 内联编辑 PATCH API ──────────────────────────────────────
2744
+
2745
+ const companyEditMatch = pathname.match(/^\/opc\/admin\/api\/companies\/([^/]+)\/edit$/);
2746
+ if (companyEditMatch && method === "PATCH") {
2747
+ const body = await readBody(req);
2748
+ const data = JSON.parse(body) as Record<string, unknown>;
2749
+ const updated = db.updateCompany(companyEditMatch[1], data);
2750
+ if (!updated) { sendJson(res, { ok: false, error: "公司不存在" }, 404); return true; }
2751
+ sendJson(res, { ok: true, company: updated });
2752
+ return true;
2753
+ }
2754
+
2755
+ const contactEditMatch = pathname.match(/^\/opc\/admin\/api\/contacts\/([^/]+)\/edit$/);
2756
+ if (contactEditMatch && method === "PATCH") {
2757
+ const body = await readBody(req);
2758
+ const data = JSON.parse(body) as Record<string, unknown>;
2759
+ const updated = db.updateContact(contactEditMatch[1], data);
2760
+ if (!updated) { sendJson(res, { ok: false, error: "联系人不存在" }, 404); return true; }
2761
+ sendJson(res, { ok: true, contact: updated });
2762
+ return true;
2763
+ }
2764
+
2765
+ const contractEditMatch = pathname.match(/^\/opc\/admin\/api\/contracts\/([^/]+)\/edit$/);
2766
+ if (contractEditMatch && method === "PATCH") {
2767
+ const body = await readBody(req);
2768
+ const data = JSON.parse(body) as Record<string, string>;
2769
+ const ALLOWED = new Set(["title", "counterparty", "amount", "status",
2770
+ "start_date", "end_date", "signed_date", "key_terms", "notes"]);
2771
+ const safeData: Record<string, string> = {};
2772
+ for (const [k, v] of Object.entries(data)) {
2773
+ if (ALLOWED.has(k)) safeData[k] = v;
2774
+ }
2775
+ const now = new Date().toISOString();
2776
+ db.execute(
2777
+ `UPDATE opc_contracts SET ${Object.keys(safeData).map(k => `${k} = ?`).join(", ")}, updated_at = ?
2778
+ WHERE id = ?`,
2779
+ ...Object.values(safeData), now, contractEditMatch[1],
2780
+ );
2781
+ sendJson(res, { ok: true });
2782
+ return true;
2783
+ }
2784
+ function sendCsv(filename: string, rows: Record<string, unknown>[]): void {
2785
+ if (rows.length === 0) {
2786
+ res.writeHead(200, {
2787
+ "Content-Type": "text/csv; charset=utf-8-sig",
2788
+ "Content-Disposition": `attachment; filename="${filename}"`,
2789
+ });
2790
+ res.end("\uFEFF");
2791
+ return;
2792
+ }
2793
+ const headers = Object.keys(rows[0]);
2794
+ const escape = (v: unknown) => {
2795
+ const s = v === null || v === undefined ? "" : String(v);
2796
+ return s.includes(",") || s.includes('"') || s.includes("\n")
2797
+ ? `"${s.replace(/"/g, '""')}"` : s;
2798
+ };
2799
+ const csv = [
2800
+ headers.join(","),
2801
+ ...rows.map(r => headers.map(h => escape(r[h])).join(",")),
2802
+ ].join("\r\n");
2803
+ res.writeHead(200, {
2804
+ "Content-Type": "text/csv; charset=utf-8-sig",
2805
+ "Content-Disposition": `attachment; filename="${filename}"`,
2806
+ });
2807
+ res.end("\uFEFF" + csv);
2808
+ }
2809
+
2810
+ if (pathname === "/opc/admin/api/export/companies" && method === "GET") {
2811
+ const rows = db.query(
2812
+ `SELECT id, name, industry, status, owner_name, owner_contact,
2813
+ registered_capital, description, created_at FROM opc_companies
2814
+ ORDER BY created_at DESC`,
2815
+ ) as Record<string, unknown>[];
2816
+ sendCsv("companies.csv", rows);
2817
+ return true;
2818
+ }
2819
+
2820
+ if (pathname === "/opc/admin/api/export/contracts" && method === "GET") {
2821
+ const rows = db.query(
2822
+ `SELECT c.id, co.name as company_name, c.title, c.counterparty, c.contract_type,
2823
+ c.amount, c.status, c.start_date, c.end_date, c.signed_date, c.created_at
2824
+ FROM opc_contracts c LEFT JOIN opc_companies co ON c.company_id = co.id
2825
+ ORDER BY c.created_at DESC`,
2826
+ ) as Record<string, unknown>[];
2827
+ sendCsv("contracts.csv", rows);
2828
+ return true;
2829
+ }
2830
+
2831
+ if (pathname === "/opc/admin/api/export/transactions" && method === "GET") {
2832
+ const companyFilter = urlObj.searchParams.get("company_id");
2833
+ const rows = companyFilter
2834
+ ? db.query(
2835
+ `SELECT t.id, co.name as company_name, t.type, t.category, t.amount,
2836
+ t.description, t.transaction_date, t.created_at
2837
+ FROM opc_transactions t LEFT JOIN opc_companies co ON t.company_id = co.id
2838
+ WHERE t.company_id = ? ORDER BY t.transaction_date DESC`,
2839
+ companyFilter,
2840
+ ) as Record<string, unknown>[]
2841
+ : db.query(
2842
+ `SELECT t.id, co.name as company_name, t.type, t.category, t.amount,
2843
+ t.description, t.transaction_date, t.created_at
2844
+ FROM opc_transactions t LEFT JOIN opc_companies co ON t.company_id = co.id
2845
+ ORDER BY t.transaction_date DESC`,
2846
+ ) as Record<string, unknown>[];
2847
+ sendCsv("transactions.csv", rows);
2848
+ return true;
2849
+ }
2850
+
2851
+ // Closure API: summary
2852
+ if (pathname === "/opc/admin/api/closure/summary" && method === "GET") {
2853
+ const acqSummary = db.queryOne(
2854
+ `SELECT COUNT(*) as total_acquisitions,
2855
+ COALESCE(SUM(loss_amount),0) as total_loss,
2856
+ COALESCE(SUM(tax_deduction),0) as total_tax_deduction
2857
+ FROM opc_acquisition_cases`,
2858
+ ) as Record<string, number>;
2859
+ const pkgSummary = db.queryOne(
2860
+ `SELECT COUNT(*) as total_packages FROM opc_asset_packages`,
2861
+ ) as Record<string, number>;
2862
+ const transferSummary = db.queryOne(
2863
+ `SELECT COALESCE(SUM(transfer_price),0) as total_transfer_price,
2864
+ COALESCE(SUM(sci_loan_actual),0) as total_sci_loan
2865
+ FROM opc_ct_transfers`,
2866
+ ) as Record<string, number>;
2867
+ const feeSummary = db.queryOne(
2868
+ `SELECT COALESCE(SUM(fee_amount),0) as total_financing_fee,
2869
+ COALESCE(SUM(CASE WHEN status='paid' THEN fee_amount ELSE 0 END),0) as collected_fee
2870
+ FROM opc_financing_fees`,
2871
+ ) as Record<string, number>;
2872
+ sendJson(res, { ...acqSummary, ...pkgSummary, ...transferSummary, ...feeSummary });
2873
+ return true;
2874
+ }
2875
+
2876
+ // Closure API: acquisitions list
2877
+ if (pathname === "/opc/admin/api/closure/acquisitions" && method === "GET") {
2878
+ const rows = db.query(
2879
+ `SELECT a.*, c.name as company_name FROM opc_acquisition_cases a
2880
+ LEFT JOIN opc_companies c ON a.company_id = c.id
2881
+ ORDER BY a.created_at DESC`,
2882
+ );
2883
+ sendJson(res, rows);
2884
+ return true;
2885
+ }
2886
+
2887
+ // Closure API: asset packages list
2888
+ if (pathname === "/opc/admin/api/closure/packages" && method === "GET") {
2889
+ sendJson(res, db.query("SELECT * FROM opc_asset_packages ORDER BY created_at DESC"));
2890
+ return true;
2891
+ }
2892
+
2893
+ // Closure API: ct transfers list
2894
+ if (pathname === "/opc/admin/api/closure/transfers" && method === "GET") {
2895
+ const rows = db.query(
2896
+ `SELECT t.*, p.name as package_name FROM opc_ct_transfers t
2897
+ LEFT JOIN opc_asset_packages p ON t.package_id = p.id
2898
+ ORDER BY t.created_at DESC`,
2899
+ );
2900
+ sendJson(res, rows);
2901
+ return true;
2902
+ }
2903
+
2904
+ // ── Staff API ───────────────────────────────────────────────
2905
+
2906
+ // GET single staff record
2907
+ const staffGetMatch = pathname.match(/^\/opc\/admin\/api\/staff\/([^/]+)$/);
2908
+ if (staffGetMatch && method === "GET") {
2909
+ const row = db.queryOne("SELECT * FROM opc_staff_config WHERE id = ?", staffGetMatch[1]);
2910
+ if (!row) { sendJson(res, { error: "记录不存在" }, 404); return true; }
2911
+ sendJson(res, row);
2912
+ return true;
2913
+ }
2914
+
2915
+ // PATCH toggle enabled
2916
+ const staffToggleMatch = pathname.match(/^\/opc\/admin\/api\/staff\/([^/]+)\/toggle$/);
2917
+ if (staffToggleMatch && method === "PATCH") {
2918
+ const body = await readBody(req);
2919
+ const { enabled } = JSON.parse(body) as { enabled: number };
2920
+ const now = new Date().toISOString();
2921
+ db.execute(
2922
+ "UPDATE opc_staff_config SET enabled = ?, updated_at = ? WHERE id = ?",
2923
+ enabled, now, staffToggleMatch[1],
2924
+ );
2925
+ sendJson(res, { ok: true });
2926
+ return true;
2927
+ }
2928
+
2929
+ // PATCH edit staff
2930
+ const staffEditMatch = pathname.match(/^\/opc\/admin\/api\/staff\/([^/]+)\/edit$/);
2931
+ if (staffEditMatch && method === "PATCH") {
2932
+ const body = await readBody(req);
2933
+ const { role_name, system_prompt, notes } = JSON.parse(body) as { role_name?: string; system_prompt?: string; notes?: string };
2934
+ const now = new Date().toISOString();
2935
+ const sets: string[] = ["updated_at = ?"];
2936
+ const vals: unknown[] = [now];
2937
+ if (role_name !== undefined) { sets.push("role_name = ?"); vals.push(role_name); }
2938
+ if (system_prompt !== undefined) { sets.push("system_prompt = ?"); vals.push(system_prompt); }
2939
+ if (notes !== undefined) { sets.push("notes = ?"); vals.push(notes); }
2940
+ vals.push(staffEditMatch[1]);
2941
+ db.execute(`UPDATE opc_staff_config SET ${sets.join(", ")} WHERE id = ?`, ...vals);
2942
+ sendJson(res, { ok: true });
2943
+ return true;
2944
+ }
2945
+
2946
+ // POST init default staff for a company
2947
+ const staffInitMatch = pathname.match(/^\/opc\/admin\/api\/staff\/([^/]+)\/init$/);
2948
+ if (staffInitMatch && method === "POST") {
2949
+ const companyId = staffInitMatch[1];
2950
+ const company = db.queryOne("SELECT id FROM opc_companies WHERE id = ?", companyId);
2951
+ if (!company) { sendJson(res, { ok: false, error: "公司不存在" }, 404); return true; }
2952
+ const BUILTIN: Record<string, { name: string; prompt: string; skills: string[] }> = {
2953
+ admin: { name: "行政助理", prompt: "你是公司行政助理,负责日程管理、文件归档、会议安排、行政事务协调。用专业、简洁的方式处理行政工作。", skills: ["schedule", "document", "meeting"] },
2954
+ hr: { name: "HR 专员", prompt: "你是公司 HR 专员,负责员工招聘、入职手续、薪酬核算、劳动合同管理、社保公积金事务。熟悉劳动法规。", skills: ["recruit", "payroll", "labor-law"] },
2955
+ finance: { name: "财务顾问", prompt: "你是公司财务顾问,负责账务记录、发票管理、税务申报、现金流分析、财务报表。熟悉中国财税法规。", skills: ["bookkeeping", "tax", "invoice", "cashflow"] },
2956
+ legal: { name: "法务助理", prompt: "你是公司法务助理,负责合同审查、风险评估、合规检查、法律文件起草。熟悉中国商业法律。", skills: ["contract-review", "compliance", "risk-assessment"] },
2957
+ marketing: { name: "市场推广", prompt: "你是公司市场推广专员,负责品牌推广、内容营销、社交媒体运营、客户获取策略。", skills: ["content", "social-media", "brand"] },
2958
+ ops: { name: "运营经理", prompt: "你是公司运营经理,负责项目管理、流程优化、供应链协调、KPI 跟踪与分析。", skills: ["project-mgmt", "process", "kpi"] },
2959
+ };
2960
+ const now = new Date().toISOString();
2961
+ let created = 0;
2962
+ for (const [role, def] of Object.entries(BUILTIN)) {
2963
+ const exists = db.queryOne("SELECT id FROM opc_staff_config WHERE company_id = ? AND role = ?", companyId, role);
2964
+ if (exists) continue;
2965
+ const id = db.genId();
2966
+ db.execute(
2967
+ `INSERT INTO opc_staff_config (id, company_id, role, role_name, enabled, system_prompt, skills, notes, created_at, updated_at)
2968
+ VALUES (?, ?, ?, ?, 1, ?, ?, '', ?, ?)`,
2969
+ id, companyId, role, def.name, def.prompt, JSON.stringify(def.skills), now, now,
2970
+ );
2971
+ created++;
2972
+ }
2973
+ sendJson(res, { ok: true, created });
2974
+ return true;
2975
+ }
2976
+
2977
+ // ── Company edit: allow status field ─────────────────────────
2978
+
2979
+ // Transaction create
2980
+ if (pathname === "/opc/admin/api/transactions/create" && method === "POST") {
2981
+ const body = await readBody(req);
2982
+ const data = JSON.parse(body) as Record<string, unknown>;
2983
+ const id = db.genId();
2984
+ const now = new Date().toISOString();
2985
+ db.execute(
2986
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
2987
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2988
+ id, data.company_id, data.type, data.category, Number(data.amount) || 0,
2989
+ data.description ?? "", data.counterparty ?? "",
2990
+ data.transaction_date ?? now.slice(0, 10), now,
2991
+ );
2992
+ sendJson(res, { ok: true, id });
2993
+ return true;
2994
+ }
2995
+
2996
+ // HR record create
2997
+ if (pathname === "/opc/admin/api/hr/create" && method === "POST") {
2998
+ const body = await readBody(req);
2999
+ const data = JSON.parse(body) as Record<string, unknown>;
3000
+ const id = db.genId();
3001
+ const now = new Date().toISOString();
3002
+ db.execute(
3003
+ `INSERT INTO opc_hr_records (id, company_id, employee_name, position, salary, social_insurance, housing_fund, start_date, end_date, contract_type, status, notes, created_at, updated_at)
3004
+ VALUES (?, ?, ?, ?, ?, 0, 0, ?, '', ?, 'active', '', ?, ?)`,
3005
+ id, data.company_id, data.employee_name, data.position,
3006
+ Number(data.salary) || 0, data.start_date ?? "",
3007
+ data.contract_type ?? "full_time", now, now,
3008
+ );
3009
+ sendJson(res, { ok: true, id });
3010
+ return true;
3011
+ }
3012
+
3013
+ // Project create
3014
+ if (pathname === "/opc/admin/api/projects/create" && method === "POST") {
3015
+ const body = await readBody(req);
3016
+ const data = JSON.parse(body) as Record<string, unknown>;
3017
+ const id = db.genId();
3018
+ const now = new Date().toISOString();
3019
+ db.execute(
3020
+ `INSERT INTO opc_projects (id, company_id, name, description, status, start_date, end_date, budget, spent, created_at, updated_at)
3021
+ VALUES (?, ?, ?, ?, 'planning', ?, ?, ?, 0, ?, ?)`,
3022
+ id, data.company_id, data.name, data.description ?? "",
3023
+ data.start_date ?? "", data.end_date ?? "",
3024
+ Number(data.budget) || 0, now, now,
3025
+ );
3026
+ sendJson(res, { ok: true, id });
3027
+ return true;
3028
+ }
3029
+
3030
+ // Contract create
3031
+ if (pathname === "/opc/admin/api/contracts/create" && method === "POST") {
3032
+ const body = await readBody(req);
3033
+ const data = JSON.parse(body) as Record<string, unknown>;
3034
+ const id = db.genId();
3035
+ const now = new Date().toISOString();
3036
+ db.execute(
3037
+ `INSERT INTO opc_contracts (id, company_id, title, counterparty, contract_type, amount, start_date, end_date, status, key_terms, risk_notes, reminder_date, signed_date, notes, created_at, updated_at)
3038
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'draft', ?, ?, '', '', '', ?, ?)`,
3039
+ id, data.company_id, data.title, data.counterparty,
3040
+ data.contract_type ?? "其他", Number(data.amount) || 0,
3041
+ data.start_date ?? "", data.end_date ?? "",
3042
+ data.key_terms ?? "", data.risk_notes ?? "", now, now,
3043
+ );
3044
+ sendJson(res, { ok: true, id });
3045
+ return true;
3046
+ }
3047
+
3048
+ // Closure API: create acquisition
3049
+ if (pathname === "/opc/admin/api/closure/acquisitions/create" && method === "POST") {
3050
+ const body = await readBody(req);
3051
+ const data = JSON.parse(body) as Record<string, unknown>;
3052
+ const id = db.genId();
3053
+ const now = new Date().toISOString();
3054
+ const lossAmount = Number(data.loss_amount) || 0;
3055
+ const taxDeduction = lossAmount * 0.25;
3056
+ db.execute(
3057
+ `INSERT INTO opc_acquisition_cases
3058
+ (id, company_id, acquirer_id, case_type, status, trigger_reason,
3059
+ acquisition_price, loss_amount, tax_deduction, initiated_date, notes, created_at, updated_at)
3060
+ VALUES (?, ?, 'starriver', 'acquisition', 'evaluating', ?, ?, ?, ?, date('now'), ?, ?, ?)`,
3061
+ id, data.company_id, data.trigger_reason,
3062
+ Number(data.acquisition_price) || 0, lossAmount, taxDeduction,
3063
+ data.notes ?? "", now, now,
3064
+ );
3065
+ db.execute("UPDATE opc_companies SET status = 'acquired', updated_at = ? WHERE id = ?", now, data.company_id);
3066
+ sendJson(res, { ok: true, id });
3067
+ return true;
3068
+ }
3069
+
3070
+ // Closure API: create asset package
3071
+ if (pathname === "/opc/admin/api/closure/packages/create" && method === "POST") {
3072
+ const body = await readBody(req);
3073
+ const data = JSON.parse(body) as Record<string, unknown>;
3074
+ const id = db.genId();
3075
+ const now = new Date().toISOString();
3076
+ db.execute(
3077
+ `INSERT INTO opc_asset_packages
3078
+ (id, name, description, status, total_valuation, company_count, sci_tech_certified, notes, created_at, updated_at)
3079
+ VALUES (?, ?, ?, 'assembling', 0, 0, 0, ?, ?, ?)`,
3080
+ id, data.name, data.description ?? "", data.notes ?? "", now, now,
3081
+ );
3082
+ sendJson(res, { ok: true, id });
3083
+ return true;
3084
+ }
3085
+
3086
+ // Closure API: create CT transfer
3087
+ if (pathname === "/opc/admin/api/closure/transfers/create" && method === "POST") {
3088
+ const body = await readBody(req);
3089
+ const data = JSON.parse(body) as Record<string, unknown>;
3090
+ const id = db.genId();
3091
+ const now = new Date().toISOString();
3092
+ db.execute(
3093
+ `INSERT INTO opc_ct_transfers
3094
+ (id, package_id, ct_company, transfer_price, status, sci_loan_target, sci_loan_actual, transfer_date, notes, created_at, updated_at)
3095
+ VALUES (?, ?, ?, ?, 'negotiating', ?, 0, ?, ?, ?, ?)`,
3096
+ id, data.package_id, data.ct_company, Number(data.transfer_price) || 0,
3097
+ Number(data.sci_loan_target) || 0, data.transfer_date ?? "", data.notes ?? "", now, now,
3098
+ );
3099
+ db.execute("UPDATE opc_asset_packages SET status = 'transferred', updated_at = ? WHERE id = ?", now, data.package_id);
3100
+ sendJson(res, { ok: true, id });
3101
+ return true;
3102
+ }
3103
+
3104
+ // Investment: list rounds by company
3105
+ if (pathname === "/opc/admin/api/investment/rounds" && method === "GET") {
3106
+ const companyId = urlObj.searchParams.get("company_id") ?? "";
3107
+ const rows = companyId
3108
+ ? db.query("SELECT * FROM opc_investment_rounds WHERE company_id = ? ORDER BY created_at", companyId)
3109
+ : db.query("SELECT * FROM opc_investment_rounds ORDER BY created_at DESC");
3110
+ sendJson(res, rows);
3111
+ return true;
3112
+ }
3113
+
3114
+ // Investment: create round
3115
+ if (pathname === "/opc/admin/api/investment/rounds/create" && method === "POST") {
3116
+ const body = await readBody(req);
3117
+ const data = JSON.parse(body) as Record<string, unknown>;
3118
+ const id = db.genId();
3119
+ const now = new Date().toISOString();
3120
+ db.execute(
3121
+ `INSERT INTO opc_investment_rounds
3122
+ (id, company_id, round_name, amount, valuation_pre, valuation_post, status, lead_investor, close_date, notes, created_at)
3123
+ VALUES (?, ?, ?, ?, ?, ?, 'planning', ?, ?, ?, ?)`,
3124
+ id, data.company_id, data.round_name, Number(data.amount) || 0,
3125
+ Number(data.valuation_pre) || 0, Number(data.valuation_post) || 0,
3126
+ data.lead_investor ?? "", data.close_date ?? "", data.notes ?? "", now,
3127
+ );
3128
+ sendJson(res, { ok: true, id });
3129
+ return true;
3130
+ }
3131
+
3132
+ // Investment: create investor
3133
+ if (pathname === "/opc/admin/api/investment/investors/create" && method === "POST") {
3134
+ const body = await readBody(req);
3135
+ const data = JSON.parse(body) as Record<string, unknown>;
3136
+ const id = db.genId();
3137
+ const now = new Date().toISOString();
3138
+ db.execute(
3139
+ `INSERT INTO opc_investors
3140
+ (id, round_id, company_id, name, type, amount, equity_percent, contact, notes, created_at)
3141
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, '', ?)`,
3142
+ id, data.round_id ?? "", data.company_id, data.name,
3143
+ data.type ?? "individual", Number(data.amount) || 0,
3144
+ Number(data.equity_percent) || 0, data.contact ?? "", now,
3145
+ );
3146
+ sendJson(res, { ok: true, id });
3147
+ return true;
3148
+ }
3149
+
3150
+ // Lifecycle: create milestone
3151
+ if (pathname === "/opc/admin/api/lifecycle/milestones/create" && method === "POST") {
3152
+ const body = await readBody(req);
3153
+ const data = JSON.parse(body) as Record<string, unknown>;
3154
+ const id = db.genId();
3155
+ const now = new Date().toISOString();
3156
+ db.execute(
3157
+ `INSERT INTO opc_milestones (id, company_id, title, category, target_date, status, description, created_at)
3158
+ VALUES (?, ?, ?, ?, ?, 'pending', ?, ?)`,
3159
+ id, data.company_id, data.title, data.category ?? "business",
3160
+ data.target_date ?? "", data.description ?? "", now,
3161
+ );
3162
+ sendJson(res, { ok: true, id });
3163
+ return true;
3164
+ }
3165
+
3166
+ // Lifecycle: create event
3167
+ if (pathname === "/opc/admin/api/lifecycle/events/create" && method === "POST") {
3168
+ const body = await readBody(req);
3169
+ const data = JSON.parse(body) as Record<string, unknown>;
3170
+ const id = db.genId();
3171
+ const now = new Date().toISOString();
3172
+ db.execute(
3173
+ `INSERT INTO opc_lifecycle_events (id, company_id, event_type, title, event_date, impact, description, created_at)
3174
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3175
+ id, data.company_id, data.event_type ?? "", data.title,
3176
+ data.event_date ?? now.slice(0, 10), data.impact ?? "", data.description ?? "", now,
3177
+ );
3178
+ sendJson(res, { ok: true, id });
3179
+ return true;
3180
+ }
3181
+
3182
+ // Monitoring: create metric
3183
+ if (pathname === "/opc/admin/api/monitoring/metrics/create" && method === "POST") {
3184
+ const body = await readBody(req);
3185
+ const data = JSON.parse(body) as Record<string, unknown>;
3186
+ const id = db.genId();
3187
+ const now = new Date().toISOString();
3188
+ db.execute(
3189
+ `INSERT INTO opc_metrics (id, company_id, name, value, unit, category, recorded_at, notes, created_at)
3190
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3191
+ id, data.company_id, data.name, Number(data.value) || 0,
3192
+ data.unit ?? "", data.category ?? "", now, data.notes ?? "", now,
3193
+ );
3194
+ sendJson(res, { ok: true, id });
3195
+ return true;
3196
+ }
3197
+
3198
+ // Services: list by company
3199
+ if (pathname === "/opc/admin/api/services" && method === "GET") {
3200
+ const companyId = urlObj.searchParams.get("company_id") ?? "";
3201
+ const rows = companyId
3202
+ ? db.query("SELECT * FROM opc_services WHERE company_id = ? ORDER BY status, name", companyId)
3203
+ : db.query("SELECT * FROM opc_services ORDER BY created_at DESC");
3204
+ sendJson(res, rows);
3205
+ return true;
3206
+ }
3207
+
3208
+ // Procurement: create order
3209
+ if (pathname === "/opc/admin/api/procurement/orders/create" && method === "POST") {
3210
+ const body = await readBody(req);
3211
+ const data = JSON.parse(body) as Record<string, unknown>;
3212
+ const id = db.genId();
3213
+ const now = new Date().toISOString();
3214
+ db.execute(
3215
+ `INSERT INTO opc_procurement_orders (id, company_id, service_id, title, amount, status, order_date, notes, created_at)
3216
+ VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?)`,
3217
+ id, data.company_id, data.service_id ?? "", data.title,
3218
+ Number(data.amount) || 0, data.order_date ?? now.slice(0, 10), data.notes ?? "", now,
3219
+ );
3220
+ sendJson(res, { ok: true, id });
3221
+ return true;
3222
+ }
3223
+
3224
+ // Media: create content
3225
+ if (pathname === "/opc/admin/api/media/create" && method === "POST") {
3226
+ const body = await readBody(req);
3227
+ const data = JSON.parse(body) as Record<string, unknown>;
3228
+ const id = db.genId();
3229
+ const now = new Date().toISOString();
3230
+ db.execute(
3231
+ `INSERT INTO opc_media_content (id, company_id, title, platform, content_type, body, status, scheduled_date, created_at)
3232
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3233
+ id, data.company_id, data.title, data.platform ?? "",
3234
+ data.content_type ?? "", data.body ?? "",
3235
+ data.status ?? "draft", data.scheduled_date ?? null, now,
3236
+ );
3237
+ sendJson(res, { ok: true, id });
3238
+ return true;
3239
+ }
3240
+
3241
+ // OPB Canvas API: GET /opc/admin/api/canvas?company_id=xxx
3242
+ if (pathname === "/opc/admin/api/canvas" && method === "GET") {
3243
+ const companyId = urlObj.searchParams.get("company_id") ?? "";
3244
+ if (!companyId) { sendJson(res, { error: "company_id required" }, 400); return true; }
3245
+ const canvas = db.queryOne("SELECT * FROM opc_opb_canvas WHERE company_id = ?", companyId) as Record<string, unknown> | null;
3246
+ if (!canvas) { sendJson(res, { canvas: null }); return true; }
3247
+ const OPB_FIELD_KEYS = ["track","target_customer","pain_point","solution","unique_value","channels","revenue_model","cost_structure","key_resources","key_activities","key_partners","unfair_advantage","metrics","non_compete","scaling_strategy"];
3248
+ const filled = OPB_FIELD_KEYS.filter(f => canvas[f] && String(canvas[f]).trim() !== "").length;
3249
+ const completion = Math.round(filled / OPB_FIELD_KEYS.length * 100);
3250
+ sendJson(res, { canvas, completion, total_fields: OPB_FIELD_KEYS.length, filled });
3251
+ return true;
3252
+ }
3253
+
3254
+ // OPB Canvas API: POST /opc/admin/api/canvas (init)
3255
+ if (pathname === "/opc/admin/api/canvas" && method === "POST") {
3256
+ const body = await readBody(req);
3257
+ const data = JSON.parse(body) as Record<string, string>;
3258
+ const companyId = data.company_id;
3259
+ if (!companyId) { sendJson(res, { error: "company_id required" }, 400); return true; }
3260
+ const existing = db.queryOne("SELECT id FROM opc_opb_canvas WHERE company_id = ?", companyId) as { id: string } | null;
3261
+ if (existing) { sendJson(res, { ok: true, canvas: db.queryOne("SELECT * FROM opc_opb_canvas WHERE id = ?", existing.id) }); return true; }
3262
+ const id = db.genId();
3263
+ const now = new Date().toISOString();
3264
+ const OPB_FIELD_KEYS = ["track","target_customer","pain_point","solution","unique_value","channels","revenue_model","cost_structure","key_resources","key_activities","key_partners","unfair_advantage","metrics","non_compete","scaling_strategy","notes"];
3265
+ db.execute(
3266
+ `INSERT INTO opc_opb_canvas (id, company_id, ${OPB_FIELD_KEYS.join(", ")}, created_at, updated_at)
3267
+ VALUES (?, ?, ${OPB_FIELD_KEYS.map(() => "?").join(", ")}, ?, ?)`,
3268
+ id, companyId, ...OPB_FIELD_KEYS.map(f => data[f] ?? ""), now, now,
3269
+ );
3270
+ sendJson(res, { ok: true, canvas: db.queryOne("SELECT * FROM opc_opb_canvas WHERE id = ?", id) });
3271
+ return true;
3272
+ }
3273
+
3274
+ // OPB Canvas API: PUT /opc/admin/api/canvas (update)
3275
+ if (pathname === "/opc/admin/api/canvas" && method === "PUT") {
3276
+ const body = await readBody(req);
3277
+ const data = JSON.parse(body) as Record<string, string>;
3278
+ const companyId = data.company_id;
3279
+ if (!companyId) { sendJson(res, { error: "company_id required" }, 400); return true; }
3280
+ const existing = db.queryOne("SELECT id FROM opc_opb_canvas WHERE company_id = ?", companyId) as { id: string } | null;
3281
+ if (!existing) { sendJson(res, { error: "canvas not found" }, 404); return true; }
3282
+ const OPB_FIELD_KEYS = ["track","target_customer","pain_point","solution","unique_value","channels","revenue_model","cost_structure","key_resources","key_activities","key_partners","unfair_advantage","metrics","non_compete","scaling_strategy","notes"];
3283
+ const updates: string[] = [];
3284
+ const vals: unknown[] = [];
3285
+ for (const f of OPB_FIELD_KEYS) {
3286
+ if (data[f] !== undefined) { updates.push(`${f} = ?`); vals.push(data[f]); }
3287
+ }
3288
+ const now = new Date().toISOString();
3289
+ updates.push("updated_at = ?");
3290
+ vals.push(now, existing.id);
3291
+ if (updates.length > 1) {
3292
+ db.execute(`UPDATE opc_opb_canvas SET ${updates.join(", ")} WHERE id = ?`, ...vals);
3293
+ }
3294
+ sendJson(res, { ok: true, canvas: db.queryOne("SELECT * FROM opc_opb_canvas WHERE id = ?", existing.id) });
3295
+ return true;
3296
+ }
3297
+
3298
+ // Skills API: GET installed skills (builtin + custom)
3299
+ if (pathname === "/opc/admin/api/skills/installed" && method === "GET") {
3300
+ // 扫描 SKILL.md 目录,解析 name/description/emoji
3301
+ function scanSkillsDir(dir: string, source: string): { name: string; desc: string; emoji: string; source: string }[] {
3302
+ const result: { name: string; desc: string; emoji: string; source: string }[] = [];
3303
+ try {
3304
+ if (!fs.existsSync(dir)) return result;
3305
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
3306
+ for (const entry of entries) {
3307
+ if (!entry.isDirectory()) continue;
3308
+ const skillMdPath = path.join(dir, entry.name, "SKILL.md");
3309
+ if (!fs.existsSync(skillMdPath)) continue;
3310
+ const content = fs.readFileSync(skillMdPath, "utf8");
3311
+ const nameMatch = content.match(/^name:\s*(.+)$/m);
3312
+ const descSingleMatch = content.match(/^description:\s*(?!\|)(.+)$/m);
3313
+ const descBlockMatch = content.match(/^description:\s*\|\s*\n([\s\S]*?)(?=\n\S|\n---|\n$|$)/m);
3314
+ const rawDesc = descSingleMatch
3315
+ ? descSingleMatch[1].trim()
3316
+ : descBlockMatch
3317
+ ? descBlockMatch[1].replace(/^\s+/gm, "").split("\n")[0].trim()
3318
+ : "";
3319
+ const emojiMatch = content.match(/"emoji"\s*:\s*"([^"]+)"/);
3320
+ result.push({
3321
+ name: nameMatch ? nameMatch[1].trim() : entry.name,
3322
+ desc: rawDesc,
3323
+ emoji: emojiMatch ? emojiMatch[1].trim() : "",
3324
+ source,
3325
+ });
3326
+ }
3327
+ } catch (_) { /* ignore */ }
3328
+ return result;
3329
+ }
3330
+
3331
+ // Windows 路径修复:去掉 file:/// 前缀里的前导斜杠
3332
+ const thisFile = new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, "$1");
3333
+ const thisDir = path.dirname(thisFile);
3334
+ // OPC 插件自带 Skills: extensions/opc-platform/skills/
3335
+ const opcSkillsDir = path.resolve(thisDir, "../../../skills");
3336
+ // OpenClaw 内置 Skills: openclaw/skills/ (向上6级: web/config-ui.ts -> src -> opc-platform -> extensions -> openclaw)
3337
+ const builtinSkillsDir = path.resolve(thisDir, "../../../../../../skills");
3338
+
3339
+ const opcSkills = scanSkillsDir(opcSkillsDir, "opc");
3340
+ const builtinSkills = scanSkillsDir(builtinSkillsDir, "openclaw");
3341
+ const custom = scanSkillsDir(CUSTOM_SKILLS_DIR, "custom");
3342
+
3343
+ sendJson(res, { builtin: [...opcSkills, ...builtinSkills], custom });
3344
+ return true;
3345
+ }
3346
+
3347
+ // Skills API: POST github-install { repo }
3348
+ if (pathname === "/opc/admin/api/skills/github-install" && method === "POST") {
3349
+ const body = await readBody(req);
3350
+ const { repo } = JSON.parse(body) as { repo?: string };
3351
+ if (!repo) { sendJson(res, { ok: false, error: "repo required" }, 400); return true; }
3352
+ // Parse owner/repo from URL or "owner/repo"
3353
+ const cleaned = repo.replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "").trim();
3354
+ if (!/^[\w.-]+\/[\w.-]+$/.test(cleaned)) {
3355
+ sendJson(res, { ok: false, error: "无效的仓库格式,请使用 user/repo" }, 400); return true;
3356
+ }
3357
+ const repoName = cleaned.split("/")[1];
3358
+ fs.mkdirSync(CUSTOM_SKILLS_DIR, { recursive: true });
3359
+ const targetDir = path.join(CUSTOM_SKILLS_DIR, repoName);
3360
+ const gitUrl = `https://github.com/${cleaned}.git`;
3361
+ const args = fs.existsSync(targetDir)
3362
+ ? ["-C", targetDir, "pull"]
3363
+ : ["clone", gitUrl, targetDir];
3364
+ const result = await new Promise<{ ok: boolean; output: string }>((resolve) => {
3365
+ const proc = spawn("git", args, { timeout: 60000 });
3366
+ let out = "";
3367
+ proc.stdout?.on("data", (d: Buffer) => { out += d.toString(); });
3368
+ proc.stderr?.on("data", (d: Buffer) => { out += d.toString(); });
3369
+ proc.on("close", (code) => resolve({ ok: code === 0, output: out }));
3370
+ proc.on("error", (e) => resolve({ ok: false, output: e.message }));
3371
+ });
3372
+ if (result.ok) {
3373
+ sendJson(res, { ok: true, dir: targetDir, message: `已安装到 ${targetDir}` });
3374
+ } else {
3375
+ sendJson(res, { ok: false, error: result.output || "git 命令失败" });
3376
+ }
3377
+ return true;
3378
+ }
3379
+
3380
+ // Skills API: POST create { name, description?, emoji?, content? } or { name, raw }
3381
+ if (pathname === "/opc/admin/api/skills/create" && method === "POST") {
3382
+ const body = await readBody(req);
3383
+ const data = JSON.parse(body) as Record<string, string>;
3384
+ const skillName = data.name?.trim();
3385
+ if (!skillName || !/^[a-z0-9-]+$/.test(skillName)) {
3386
+ sendJson(res, { ok: false, error: "name 只能包含小写字母、数字和连字符" }, 400); return true;
3387
+ }
3388
+ const skillDir = path.join(CUSTOM_SKILLS_DIR, skillName);
3389
+ fs.mkdirSync(skillDir, { recursive: true });
3390
+ const skillMdPath = path.join(skillDir, "SKILL.md");
3391
+ let mdContent: string;
3392
+ if (data.raw) {
3393
+ mdContent = data.raw;
3394
+ } else {
3395
+ const emojiStr = data.emoji?.trim() || "✨";
3396
+ const descStr = data.description?.trim() || "";
3397
+ const contentStr = data.content?.trim() || "";
3398
+ mdContent = `---\nname: ${skillName}\ndescription: ${descStr}\nmetadata: {"openclaw":{"emoji":"${emojiStr}"}}\n---\n\n${contentStr}`;
3399
+ }
3400
+ fs.writeFileSync(skillMdPath, mdContent, "utf8");
3401
+ sendJson(res, { ok: true, path: skillMdPath });
3402
+ return true;
3403
+ }
3404
+
3405
+ // Skills API: DELETE /opc/admin/api/skills/custom/:name
3406
+ const skillDeleteMatch = pathname.match(/^\/opc\/admin\/api\/skills\/custom\/([^/]+)$/);
3407
+ if (skillDeleteMatch && method === "DELETE") {
3408
+ const skillName = decodeURIComponent(skillDeleteMatch[1]);
3409
+ if (!skillName || skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
3410
+ sendJson(res, { ok: false, error: "无效的 skill 名称" }, 400); return true;
3411
+ }
3412
+ const skillDir = path.join(CUSTOM_SKILLS_DIR, skillName);
3413
+ try {
3414
+ fs.rmSync(skillDir, { recursive: true, force: true });
3415
+ sendJson(res, { ok: true });
3416
+ } catch (e) {
3417
+ sendJson(res, { ok: false, error: e instanceof Error ? e.message : String(e) });
3418
+ }
3419
+ return true;
3420
+ }
3421
+
3422
+ // Company Skills API: GET ?company_id=xxx
3423
+ if (pathname === "/opc/admin/api/company-skills" && method === "GET") {
3424
+ const companyId = urlObj.searchParams.get("company_id") ?? "";
3425
+ if (!companyId) { sendJson(res, { error: "company_id required" }, 400); return true; }
3426
+ const row = db.queryOne("SELECT value FROM opc_tool_config WHERE key = ?", `company_skills_${companyId}`) as { value: string } | null;
3427
+ const skills: string[] = row ? (JSON.parse(row.value) as string[]) : [];
3428
+ sendJson(res, { company_id: companyId, skills });
3429
+ return true;
3430
+ }
3431
+
3432
+ // Company Skills API: POST { company_id, skills[] }
3433
+ if (pathname === "/opc/admin/api/company-skills" && method === "POST") {
3434
+ const body = await readBody(req);
3435
+ const { company_id, skills } = JSON.parse(body) as { company_id?: string; skills?: string[] };
3436
+ if (!company_id) { sendJson(res, { error: "company_id required" }, 400); return true; }
3437
+ const key = `company_skills_${company_id}`;
3438
+ const value = JSON.stringify(skills ?? []);
3439
+ db.execute(
3440
+ `INSERT INTO opc_tool_config (key, value) VALUES (?, ?)
3441
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
3442
+ key, value,
3443
+ );
3444
+ sendJson(res, { ok: true, company_id, skills });
3445
+ return true;
3446
+ }
3447
+
3448
+ // Companies list API (for dropdowns)
3449
+ if (pathname === "/opc/admin/api/companies" && method === "GET") {
3450
+ const rows = db.query("SELECT id, name, industry, status FROM opc_companies ORDER BY name");
3451
+ sendJson(res, rows);
3452
+ return true;
3453
+ }
3454
+
3455
+ // Company delete API: DELETE /opc/admin/api/companies/:id
3456
+ const companyDeleteMatch = pathname.match(/^\/opc\/admin\/api\/companies\/([^/]+)$/);
3457
+ if (companyDeleteMatch && method === "DELETE") {
3458
+ const companyId = companyDeleteMatch[1];
3459
+ const company = db.queryOne("SELECT id, name FROM opc_companies WHERE id = ?", companyId) as { id: string; name: string } | null;
3460
+ if (!company) { sendJson(res, { ok: false, error: "公司不存在" }, 404); return true; }
3461
+ // 级联删除所有关联表数据
3462
+ const RELATED_TABLES = [
3463
+ "opc_transactions", "opc_contacts", "opc_employees", "opc_invoices",
3464
+ "opc_tax_filings", "opc_contracts", "opc_hr_records", "opc_media_content",
3465
+ "opc_tasks", "opc_projects", "opc_investment_rounds", "opc_investors",
3466
+ "opc_services", "opc_procurement_orders", "opc_milestones",
3467
+ "opc_lifecycle_events", "opc_metrics", "opc_alerts",
3468
+ "opc_acquisition_cases", "opc_asset_packages", "opc_staff_config",
3469
+ "opc_opb_canvas",
3470
+ ];
3471
+ for (const table of RELATED_TABLES) {
3472
+ try { db.execute(`DELETE FROM ${table} WHERE company_id = ?`, companyId); } catch (_) { /* 表可能不存在 */ }
3473
+ }
3474
+ // 删除资产包明细(通过 package_id 关联)
3475
+ try {
3476
+ const pkgIds = db.query("SELECT id FROM opc_asset_packages WHERE company_id = ?", companyId) as { id: string }[];
3477
+ for (const pkg of pkgIds) {
3478
+ db.execute("DELETE FROM opc_asset_package_items WHERE package_id = ?", pkg.id);
3479
+ }
3480
+ } catch (_) { /* ignore */ }
3481
+ // 删除 opc_tool_config 中 company_skills_ 前缀的记录
3482
+ try { db.execute("DELETE FROM opc_tool_config WHERE key = ?", `company_skills_${companyId}`); } catch (_) { /* ignore */ }
3483
+ // 最后删除公司本身
3484
+ db.execute("DELETE FROM opc_companies WHERE id = ?", companyId);
3485
+ api.logger.info(`opc: 已删除公司 ${company.name} (${companyId}) 及全部关联数据`);
3486
+ sendJson(res, { ok: true, name: company.name });
3487
+ return true;
3488
+ }
3489
+
3490
+ // Serve HTML page for all other /opc/admin paths
3491
+ sendHtml(res, buildPageHtml());
3492
+ return true;
3493
+ } catch (err) {
3494
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
3495
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
3496
+ return true;
3497
+ }
3498
+ });
3499
+
3500
+ api.logger.info("opc: 已注册配置管理 UI (/opc/admin)");
3501
+ }