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.
- package/README.md +166 -0
- package/index.ts +145 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +47 -0
- package/skills/basic-crm/SKILL.md +81 -0
- package/skills/basic-finance/SKILL.md +100 -0
- package/skills/business-monitoring/SKILL.md +120 -0
- package/skills/company-lifecycle/SKILL.md +99 -0
- package/skills/company-registration/SKILL.md +80 -0
- package/skills/finance-tax/SKILL.md +150 -0
- package/skills/hr-assistant/SKILL.md +127 -0
- package/skills/investment-management/SKILL.md +101 -0
- package/skills/legal-assistant/SKILL.md +113 -0
- package/skills/media-ops/SKILL.md +101 -0
- package/skills/procurement-management/SKILL.md +91 -0
- package/skills/project-management/SKILL.md +125 -0
- package/src/api/companies.ts +193 -0
- package/src/api/dashboard.ts +25 -0
- package/src/api/routes.ts +14 -0
- package/src/db/index.ts +63 -0
- package/src/db/migrations.ts +67 -0
- package/src/db/schema.ts +518 -0
- package/src/db/sqlite-adapter.ts +366 -0
- package/src/opc/company-manager.ts +82 -0
- package/src/opc/context-injector.ts +186 -0
- package/src/opc/reminder-service.ts +289 -0
- package/src/opc/types.ts +330 -0
- package/src/opc/workspace-factory.ts +189 -0
- package/src/tools/acquisition-tool.ts +150 -0
- package/src/tools/asset-package-tool.ts +283 -0
- package/src/tools/finance-tool.ts +244 -0
- package/src/tools/hr-tool.ts +211 -0
- package/src/tools/investment-tool.ts +201 -0
- package/src/tools/legal-tool.ts +191 -0
- package/src/tools/lifecycle-tool.ts +251 -0
- package/src/tools/media-tool.ts +174 -0
- package/src/tools/monitoring-tool.ts +207 -0
- package/src/tools/opb-tool.ts +193 -0
- package/src/tools/opc-tool.ts +206 -0
- package/src/tools/procurement-tool.ts +191 -0
- package/src/tools/project-tool.ts +203 -0
- package/src/tools/schemas.ts +163 -0
- package/src/tools/staff-tool.ts +211 -0
- package/src/utils/tool-helper.ts +16 -0
- package/src/web/config-ui.ts +3501 -0
- 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\')">🖶 \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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"').replace(/'/g,''');}"
|
|
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()\">🖶 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)+')\">« \\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 »</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)+'\\')\">✕</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
|
+
}
|