byteplan-cli 1.0.2 → 1.2.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/package.json +7 -3
- package/skills/byteplan-analysis/SKILL.md +1078 -0
- package/skills/byteplan-api/API_REFERENCE.md +249 -0
- package/skills/byteplan-api/SKILL.md +96 -0
- package/skills/byteplan-api/package.json +16 -0
- package/skills/byteplan-api/scripts/api.js +973 -0
- package/skills/byteplan-excel/SKILL.md +212 -0
- package/skills/byteplan-excel/examples/margin-analysis.json +40 -0
- package/skills/byteplan-excel/package.json +12 -0
- package/skills/byteplan-excel/pnpm-lock.yaml +68 -0
- package/skills/byteplan-excel/scripts/generate_excel.js +156 -0
- package/skills/byteplan-html/SKILL.md +490 -0
- package/skills/byteplan-html/examples/example-output.html +184 -0
- package/skills/byteplan-html/examples/generate-ppt-style-html.js +611 -0
- package/skills/byteplan-html/examples/margin-contribution-analysis.json +152 -0
- package/skills/byteplan-html/package.json +18 -0
- package/skills/byteplan-html/scripts/generate_html.js +517 -0
- package/skills/byteplan-ppt/SKILL.md +394 -0
- package/skills/byteplan-ppt/examples/margin-contribution-analysis.json +152 -0
- package/skills/byteplan-ppt/package.json +16 -0
- package/skills/byteplan-ppt/pnpm-lock.yaml +138 -0
- package/skills/byteplan-ppt/scripts/check_ppt_overlap.js +318 -0
- package/skills/byteplan-ppt/scripts/generate_ppt.js +680 -0
- package/skills/byteplan-video/SKILL.md +606 -0
- package/skills/byteplan-video/examples/sample-video-data.json +82 -0
- package/skills/byteplan-video/remotion-project/package.json +22 -0
- package/skills/byteplan-video/remotion-project/pnpm-lock.yaml +1646 -0
- package/skills/byteplan-video/remotion-project/remotion.config.ts +6 -0
- package/skills/byteplan-video/remotion-project/scene_durations.json +32 -0
- package/skills/byteplan-video/remotion-project/scripts/generate_audio.js +279 -0
- package/skills/byteplan-video/remotion-project/src/DynamicReport.tsx +172 -0
- package/skills/byteplan-video/remotion-project/src/Root.tsx +51 -0
- package/skills/byteplan-video/remotion-project/src/SalesReport.tsx +107 -0
- package/skills/byteplan-video/remotion-project/src/index.tsx +4 -0
- package/skills/byteplan-video/remotion-project/src/scenes/ChartSlide.tsx +201 -0
- package/skills/byteplan-video/remotion-project/src/scenes/CoverSlide.tsx +61 -0
- package/skills/byteplan-video/remotion-project/src/scenes/EndSlide.tsx +60 -0
- package/skills/byteplan-video/remotion-project/src/scenes/InsightSlide.tsx +101 -0
- package/skills/byteplan-video/remotion-project/src/scenes/KpiSlide.tsx +84 -0
- package/skills/byteplan-video/remotion-project/src/scenes/RecommendationSlide.tsx +100 -0
- package/skills/byteplan-video/remotion-project/tsconfig.json +13 -0
- package/skills/byteplan-video/remotion-project/video_data.json +76 -0
- package/skills/byteplan-video/scripts/generate_video.js +270 -0
- package/skills/byteplan-video/templates/package.json +31 -0
- package/skills/byteplan-video/templates/pnpm-lock.yaml +2200 -0
- package/skills/byteplan-video/templates/remotion.config.ts +9 -0
- package/skills/byteplan-video/templates/scripts/generate-audio.ts +55 -0
- package/skills/byteplan-video/templates/src/components/BarChartScene.tsx +153 -0
- package/skills/byteplan-video/templates/src/components/InsightScene.tsx +135 -0
- package/skills/byteplan-video/templates/src/components/LineChartScene.tsx +214 -0
- package/skills/byteplan-video/templates/src/components/SceneFactory.tsx +34 -0
- package/skills/byteplan-video/templates/src/components/SummaryScene.tsx +155 -0
- package/skills/byteplan-video/templates/src/components/TitleScene.tsx +130 -0
- package/skills/byteplan-video/templates/src/compositions/AnalysisVideo.tsx +39 -0
- package/skills/byteplan-video/templates/src/index.tsx +28 -0
- package/skills/byteplan-video/templates/src/register-root.tsx +4 -0
- package/skills/byteplan-video/templates/src/storyboard/types.ts +46 -0
- package/skills/byteplan-video/templates/tsconfig.json +17 -0
- package/skills/byteplan-video/templates/tsconfig.scripts.json +13 -0
- package/skills/byteplan-word/SKILL.md +233 -0
- package/skills/byteplan-word/package.json +12 -0
- package/skills/byteplan-word/pnpm-lock.yaml +120 -0
- package/skills/byteplan-word/scripts/generate_word.js +548 -0
- package/src/cli.js +4 -0
- package/src/commands/skills.js +279 -0
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { execSync, spawn } from 'child_process';
|
|
7
|
+
|
|
8
|
+
// CLI 配置
|
|
9
|
+
const CLI_PACKAGE = 'byteplan-cli';
|
|
10
|
+
const CLI_BIN_NAME = 'byteplan';
|
|
11
|
+
|
|
12
|
+
// 环境配置
|
|
13
|
+
const ENVIRONMENTS = {
|
|
14
|
+
uat: 'https://uatapp.byteplan.com',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const BASIC_AUTH = 'Basic UEM6bkxDbndkSWhpeldieWtIeXVaTTZUcFFEZDdLd0s5SVhESzhMR3NhN1NPVw==';
|
|
18
|
+
|
|
19
|
+
// 当前环境(仅支持 UAT)
|
|
20
|
+
let currentEnv = 'uat';
|
|
21
|
+
|
|
22
|
+
// 是否使用 CLI 模式(默认优先使用 CLI)
|
|
23
|
+
let useCliMode = true;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 检查 byteplan-cli 是否已安装
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function isCliInstalled() {
|
|
30
|
+
try {
|
|
31
|
+
execSync(`${CLI_BIN_NAME} --version`, { stdio: 'pipe' });
|
|
32
|
+
return true;
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 安装 byteplan-cli
|
|
40
|
+
* @returns {Promise<boolean>}
|
|
41
|
+
*/
|
|
42
|
+
async function installCli() {
|
|
43
|
+
console.log(`📦 正在安装 ${CLI_PACKAGE}...`);
|
|
44
|
+
try {
|
|
45
|
+
execSync(`npm i -g ${CLI_PACKAGE}`, { stdio: 'inherit' });
|
|
46
|
+
console.log(`✅ ${CLI_PACKAGE} 安装成功`);
|
|
47
|
+
return true;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error(`❌ ${CLI_PACKAGE} 安装失败: ${e.message}`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 确保 CLI 已安装,如果没有则自动安装
|
|
56
|
+
* @returns {Promise<boolean>}
|
|
57
|
+
*/
|
|
58
|
+
async function ensureCliInstalled() {
|
|
59
|
+
if (isCliInstalled()) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
console.log(`⚠️ 未检测到 ${CLI_PACKAGE}, 正在自动安装...`);
|
|
63
|
+
return await installCli();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 执行 CLI 命令并返回 JSON 结果
|
|
68
|
+
* @param {string[]} args - CLI 参数
|
|
69
|
+
* @returns {Promise<any>}
|
|
70
|
+
*/
|
|
71
|
+
async function execCli(args) {
|
|
72
|
+
// 确保 CLI 已安装
|
|
73
|
+
await ensureCliInstalled();
|
|
74
|
+
|
|
75
|
+
const fullCmd = `${CLI_BIN_NAME} ${args.join(' ')}`;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const result = execSync(fullCmd, {
|
|
79
|
+
encoding: 'utf-8',
|
|
80
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
81
|
+
env: { ...process.env }
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 解析 JSON 输出
|
|
85
|
+
const jsonStr = result.trim();
|
|
86
|
+
if (jsonStr) {
|
|
87
|
+
return JSON.parse(jsonStr);
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// 如果 CLI 执行失败,解析错误输出
|
|
92
|
+
const stderr = e.stderr?.toString() || '';
|
|
93
|
+
if (stderr.includes('error') || stderr.includes('Error')) {
|
|
94
|
+
try {
|
|
95
|
+
const errorJson = JSON.parse(stderr);
|
|
96
|
+
throw new Error(errorJson.message || stderr);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error(`CLI 执行失败: ${stderr || e.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`CLI 执行失败: ${e.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 设置是否使用 CLI 模式
|
|
107
|
+
* @param {boolean} useCli - 是否使用 CLI
|
|
108
|
+
*/
|
|
109
|
+
export function setCliMode(useCli) {
|
|
110
|
+
useCliMode = useCli;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 获取当前模式
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
export function isUsingCli() {
|
|
118
|
+
return useCliMode && isCliInstalled();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 获取 .env 文件路径
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
function getEnvPath() {
|
|
126
|
+
// 优先使用调用方的 .env(当前工作目录),其次使用项目根目录
|
|
127
|
+
const cwd = process.cwd();
|
|
128
|
+
const projectEnvPath = path.join(cwd, '.env');
|
|
129
|
+
if (fs.existsSync(projectEnvPath)) {
|
|
130
|
+
return projectEnvPath;
|
|
131
|
+
}
|
|
132
|
+
// 回退到 skill 目录的 .env
|
|
133
|
+
return path.join(getDirname(), '..', '..', '.env');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 读取 .env 文件内容
|
|
138
|
+
* @returns {string}
|
|
139
|
+
*/
|
|
140
|
+
function readEnvContent() {
|
|
141
|
+
const envPath = getEnvPath();
|
|
142
|
+
if (fs.existsSync(envPath)) {
|
|
143
|
+
return fs.readFileSync(envPath, 'utf-8');
|
|
144
|
+
}
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 保存凭证到 .env 文件
|
|
150
|
+
* @param {Object} credentials - 凭证对象
|
|
151
|
+
* @param {string} credentials.username - 用户名
|
|
152
|
+
* @param {string} credentials.password - 密码
|
|
153
|
+
* @param {string} credentials.access_token - 访问令牌
|
|
154
|
+
* @param {string} credentials.refresh_token - 刷新令牌
|
|
155
|
+
* @param {number} credentials.expires_in - 过期时间(秒)
|
|
156
|
+
*/
|
|
157
|
+
function saveCredentials(credentials) {
|
|
158
|
+
const { username, password, access_token, refresh_token, expires_in } = credentials;
|
|
159
|
+
|
|
160
|
+
// 读取现有 .env 内容
|
|
161
|
+
let envContent = readEnvContent();
|
|
162
|
+
const lines = envContent.split('\n');
|
|
163
|
+
|
|
164
|
+
// 需要更新的配置项(每次登录都更新)
|
|
165
|
+
const keysToUpdate = ['BP_ENV', 'BP_USER', 'BP_PASSWORD', 'USER_NAME', 'PASSWORD', 'ACCESS_TOKEN', 'REFRESH_TOKEN', 'TOKEN_EXPIRES_IN'];
|
|
166
|
+
|
|
167
|
+
// 保留非配置项的行(注释、空行、其他配置)
|
|
168
|
+
const preservedLines = lines.filter(line => {
|
|
169
|
+
const trimmed = line.trim();
|
|
170
|
+
if (!trimmed || trimmed.startsWith('#')) return true;
|
|
171
|
+
const key = trimmed.split('=')[0]?.trim();
|
|
172
|
+
// 保留不属于 keysToUpdate 的配置
|
|
173
|
+
return !keysToUpdate.includes(key);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// 构建新的配置内容(总是写入最新值)
|
|
177
|
+
const configLines = [];
|
|
178
|
+
|
|
179
|
+
// 写入环境配置
|
|
180
|
+
configLines.push(`BP_ENV=${currentEnv}`);
|
|
181
|
+
|
|
182
|
+
// 写入用户名
|
|
183
|
+
configLines.push(`BP_USER=${username}`);
|
|
184
|
+
|
|
185
|
+
// 写入密码(用引号包裹以保护特殊字符)
|
|
186
|
+
configLines.push(`BP_PASSWORD="${password}"`);
|
|
187
|
+
|
|
188
|
+
// 写入 access_token
|
|
189
|
+
configLines.push(`ACCESS_TOKEN=${access_token}`);
|
|
190
|
+
|
|
191
|
+
// 写入 refresh_token
|
|
192
|
+
if (refresh_token) {
|
|
193
|
+
configLines.push(`REFRESH_TOKEN=${refresh_token}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 写入过期时间
|
|
197
|
+
if (expires_in) {
|
|
198
|
+
configLines.push(`TOKEN_EXPIRES_IN=${Date.now() + expires_in * 1000}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 合并内容:保留的行 + 新配置
|
|
202
|
+
const finalLines = [...preservedLines, ...configLines];
|
|
203
|
+
const finalContent = finalLines.join('\n');
|
|
204
|
+
|
|
205
|
+
// 写入文件
|
|
206
|
+
const envPath = getEnvPath();
|
|
207
|
+
fs.writeFileSync(envPath, finalContent, 'utf-8');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 检查 token 是否过期
|
|
212
|
+
* @returns {boolean}
|
|
213
|
+
*/
|
|
214
|
+
function isTokenExpired() {
|
|
215
|
+
const expiresIn = process.env.TOKEN_EXPIRES_IN;
|
|
216
|
+
if (!expiresIn) {
|
|
217
|
+
return true; // 没有过期时间,认为已过期,需要重新登录
|
|
218
|
+
}
|
|
219
|
+
const expiresAt = parseInt(expiresIn, 10);
|
|
220
|
+
// 提前 5 分钟认为过期
|
|
221
|
+
return Date.now() >= expiresAt - 5 * 60 * 1000;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 重新加载 .env 文件(用于 CLI 登录后同步 token)
|
|
226
|
+
*/
|
|
227
|
+
function reloadEnv() {
|
|
228
|
+
const envPath = getEnvPath();
|
|
229
|
+
if (fs.existsSync(envPath)) {
|
|
230
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
231
|
+
const lines = envContent.split('\n');
|
|
232
|
+
for (const line of lines) {
|
|
233
|
+
const trimmed = line.trim();
|
|
234
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
235
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
236
|
+
if (key && valueParts.length > 0) {
|
|
237
|
+
const value = valueParts.join('=').replace(/^["']|["']$/g, '');
|
|
238
|
+
process.env[key.trim()] = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 设置登录环境
|
|
246
|
+
* @param {'uat'} env - 环境名称
|
|
247
|
+
*/
|
|
248
|
+
export function setEnvironment(env) {
|
|
249
|
+
if (ENVIRONMENTS[env]) {
|
|
250
|
+
currentEnv = env;
|
|
251
|
+
} else {
|
|
252
|
+
throw new Error(`未知环境: ${env},仅支持: uat`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 获取当前环境
|
|
258
|
+
* @returns {string}
|
|
259
|
+
*/
|
|
260
|
+
export function getEnvironment() {
|
|
261
|
+
return currentEnv;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 获取当前环境的基础 URL
|
|
266
|
+
* @returns {string}
|
|
267
|
+
*/
|
|
268
|
+
export function getBaseUrl() {
|
|
269
|
+
return ENVIRONMENTS[currentEnv];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 获取 RSA 公钥(UAT 环境)
|
|
274
|
+
* @returns {Promise<{key: string, id: string}>}
|
|
275
|
+
*/
|
|
276
|
+
async function getPublicKey() {
|
|
277
|
+
const baseUrl = getBaseUrl();
|
|
278
|
+
const response = await fetch(`${baseUrl}/base/util/get/publicKey?t=${Date.now()}`, {
|
|
279
|
+
method: 'GET',
|
|
280
|
+
headers: {
|
|
281
|
+
'accept': 'application/json',
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const data = await response.json();
|
|
286
|
+
|
|
287
|
+
// 兼容多种响应格式
|
|
288
|
+
const key = data.data || data.publicKey || data.key || '';
|
|
289
|
+
const id = data.publicKeyId || data.id || '';
|
|
290
|
+
|
|
291
|
+
return { key, id };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* RSA 加密密码(UAT 环境)
|
|
296
|
+
* @param {string} password - 明文密码
|
|
297
|
+
* @param {string} publicKey - RSA 公钥
|
|
298
|
+
* @returns {string} - Base64 编码的加密密码
|
|
299
|
+
*/
|
|
300
|
+
function encryptPassword(password, publicKey) {
|
|
301
|
+
if (!publicKey) {
|
|
302
|
+
throw new Error('公钥为空,无法加密密码');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 清理公钥格式
|
|
306
|
+
let cleanKey = publicKey
|
|
307
|
+
.replace(/-----BEGIN PUBLIC KEY-----/g, '')
|
|
308
|
+
.replace(/-----END PUBLIC KEY-----/g, '')
|
|
309
|
+
.replace(/\s/g, '');
|
|
310
|
+
|
|
311
|
+
// 格式化为 PEM
|
|
312
|
+
const formattedKeyBody = cleanKey.match(/.{1,64}/g)?.join('\n') || cleanKey;
|
|
313
|
+
const formattedKey = `-----BEGIN PUBLIC KEY-----\n${formattedKeyBody}\n-----END PUBLIC KEY-----`;
|
|
314
|
+
|
|
315
|
+
// RSA 加密
|
|
316
|
+
const encrypted = crypto.publicEncrypt(
|
|
317
|
+
{
|
|
318
|
+
key: formattedKey,
|
|
319
|
+
padding: crypto.constants.RSA_PKCS1_PADDING,
|
|
320
|
+
},
|
|
321
|
+
Buffer.from(password, 'utf-8'),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
return encrypted.toString('base64');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 登录获取 token
|
|
329
|
+
* @param {string} username - 用户名(手机号)
|
|
330
|
+
* @param {string} password - 密码
|
|
331
|
+
* @param {'uat'} [env] - 环境,默认为 UAT
|
|
332
|
+
* @returns {Promise<{access_token: string, refresh_token: string, expires_in: number}>}
|
|
333
|
+
*/
|
|
334
|
+
export async function login(username, password, env) {
|
|
335
|
+
// 如果指定了环境,使用指定环境
|
|
336
|
+
if (env && ENVIRONMENTS[env]) {
|
|
337
|
+
setEnvironment(env);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// CLI 模式
|
|
341
|
+
if (useCliMode) {
|
|
342
|
+
await ensureCliInstalled();
|
|
343
|
+
try {
|
|
344
|
+
const result = await execCli(['login', '-u', username, '-p', password, '-e', currentEnv]);
|
|
345
|
+
if (result.error) {
|
|
346
|
+
throw new Error(result.message || '登录失败');
|
|
347
|
+
}
|
|
348
|
+
// CLI 登录成功后,token 已自动保存到 ~/.byteplan/.env
|
|
349
|
+
// 需要重新加载 .env 以获取 token
|
|
350
|
+
reloadEnv();
|
|
351
|
+
return {
|
|
352
|
+
access_token: process.env.ACCESS_TOKEN,
|
|
353
|
+
refresh_token: process.env.REFRESH_TOKEN,
|
|
354
|
+
expires_in: result.expiresIn,
|
|
355
|
+
};
|
|
356
|
+
} catch (e) {
|
|
357
|
+
// CLI 失败时,回退到直接 API 调用
|
|
358
|
+
console.warn(`⚠️ CLI 登录失败,回退到直接 API: ${e.message}`);
|
|
359
|
+
useCliMode = false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 直接 API 调用(回退模式)
|
|
364
|
+
const baseUrl = getBaseUrl();
|
|
365
|
+
const t = Date.now();
|
|
366
|
+
const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substring(2);
|
|
367
|
+
|
|
368
|
+
let bodyParts = [
|
|
369
|
+
`--${boundary}`,
|
|
370
|
+
'Content-Disposition: form-data; name="scope"',
|
|
371
|
+
'',
|
|
372
|
+
'read write',
|
|
373
|
+
`--${boundary}`,
|
|
374
|
+
'Content-Disposition: form-data; name="grant_type"',
|
|
375
|
+
'',
|
|
376
|
+
'password',
|
|
377
|
+
`--${boundary}`,
|
|
378
|
+
'Content-Disposition: form-data; name="username"',
|
|
379
|
+
'',
|
|
380
|
+
`+86${username}`,
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
if (currentEnv === 'uat') {
|
|
384
|
+
// UAT 环境:获取公钥并加密密码
|
|
385
|
+
const { key: publicKey, id: publicKeyId } = await getPublicKey();
|
|
386
|
+
const encryptedPassword = encryptPassword(password, publicKey);
|
|
387
|
+
|
|
388
|
+
bodyParts.push(
|
|
389
|
+
`--${boundary}`,
|
|
390
|
+
`Content-Disposition: form-data; name="publicKeyId"`,
|
|
391
|
+
'',
|
|
392
|
+
publicKeyId,
|
|
393
|
+
);
|
|
394
|
+
bodyParts.push(
|
|
395
|
+
`--${boundary}`,
|
|
396
|
+
'Content-Disposition: form-data; name="password"',
|
|
397
|
+
'',
|
|
398
|
+
encryptedPassword,
|
|
399
|
+
);
|
|
400
|
+
} else {
|
|
401
|
+
// DEV 环境:明文密码
|
|
402
|
+
bodyParts.push(
|
|
403
|
+
`--${boundary}`,
|
|
404
|
+
'Content-Disposition: form-data; name="password"',
|
|
405
|
+
'',
|
|
406
|
+
password,
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
bodyParts.push(`--${boundary}--`);
|
|
411
|
+
|
|
412
|
+
const response = await fetch(`${baseUrl}/base/login?t=${t}`, {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
headers: {
|
|
415
|
+
'accept': 'application/json',
|
|
416
|
+
'authorization': BASIC_AUTH,
|
|
417
|
+
'content-type': `multipart/form-data; boundary=${boundary}`,
|
|
418
|
+
},
|
|
419
|
+
body: bodyParts.join('\r\n'),
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const data = await response.json();
|
|
423
|
+
|
|
424
|
+
if (data.error) {
|
|
425
|
+
throw new Error(`登录失败: ${data.message || data.error_description || JSON.stringify(data)}`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 保存凭证到 .env
|
|
429
|
+
try {
|
|
430
|
+
saveCredentials({
|
|
431
|
+
username,
|
|
432
|
+
password,
|
|
433
|
+
access_token: data.access_token,
|
|
434
|
+
refresh_token: data.refresh_token,
|
|
435
|
+
expires_in: data.expires_in
|
|
436
|
+
});
|
|
437
|
+
} catch (e) {
|
|
438
|
+
console.warn(`⚠️ Token 持久化失败: ${e.message}`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return data;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 使用 .env 中的账号密码登录
|
|
446
|
+
* @param {'uat'} [env] - 环境,默认为 UAT
|
|
447
|
+
* @param {boolean} [forceReLogin=false] - 是否强制重新登录
|
|
448
|
+
* @returns {Promise<{access_token: string, refresh_token: string, expires_in: number}>}
|
|
449
|
+
*/
|
|
450
|
+
export async function loginWithEnv(env, forceReLogin = false) {
|
|
451
|
+
// 重新加载 .env 以获取最新的凭证
|
|
452
|
+
reloadEnv();
|
|
453
|
+
|
|
454
|
+
const username = process.env.BP_USER || process.env.USER_NAME;
|
|
455
|
+
const password = process.env.BP_PASSWORD || process.env.PASSWORD;
|
|
456
|
+
|
|
457
|
+
if (!username || !password) {
|
|
458
|
+
throw new Error('请在 .env 中配置 BP_USER 和 BP_PASSWORD(或 USER_NAME 和 PASSWORD)');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 如果指定了环境,使用指定环境;否则默认使用 UAT
|
|
462
|
+
if (env && ENVIRONMENTS[env]) {
|
|
463
|
+
setEnvironment(env);
|
|
464
|
+
} else {
|
|
465
|
+
setEnvironment('uat');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// 如果没有强制重新登录,先检查是否有可用的缓存 token
|
|
469
|
+
if (!forceReLogin) {
|
|
470
|
+
const cachedToken = process.env.ACCESS_TOKEN;
|
|
471
|
+
if (cachedToken && !isTokenExpired()) {
|
|
472
|
+
return {
|
|
473
|
+
access_token: cachedToken,
|
|
474
|
+
refresh_token: process.env.REFRESH_TOKEN || null,
|
|
475
|
+
expires_in: null,
|
|
476
|
+
_cached: true // 标记为缓存的 token
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// 缓存无效,重新登录
|
|
482
|
+
return login(username, password);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* 获取当前 token
|
|
487
|
+
* @returns {string|null}
|
|
488
|
+
*/
|
|
489
|
+
export function getToken() {
|
|
490
|
+
return process.env.ACCESS_TOKEN || null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* 获取用户信息
|
|
495
|
+
* @param {string} token - access token
|
|
496
|
+
* @returns {Promise<{user: object, tenantList: array}>}
|
|
497
|
+
*/
|
|
498
|
+
export async function getUserInfo(token) {
|
|
499
|
+
// CLI 模式
|
|
500
|
+
if (useCliMode && !token) {
|
|
501
|
+
await ensureCliInstalled();
|
|
502
|
+
try {
|
|
503
|
+
const result = await execCli(['user', 'info']);
|
|
504
|
+
if (result.error) {
|
|
505
|
+
throw new Error(result.message || '获取用户信息失败');
|
|
506
|
+
}
|
|
507
|
+
// 将 CLI 结果转换为原有格式
|
|
508
|
+
return {
|
|
509
|
+
user: {
|
|
510
|
+
id: result.user?.userId,
|
|
511
|
+
userName: result.user?.userName,
|
|
512
|
+
mobile: result.user?.userPhone,
|
|
513
|
+
email: result.user?.email,
|
|
514
|
+
iconUrl: result.user?.avatar,
|
|
515
|
+
tenantId: result.currentTenant?.tenantId,
|
|
516
|
+
tenantName: result.currentTenant?.tenantName,
|
|
517
|
+
tenantCode: result.currentTenant?.tenantCode,
|
|
518
|
+
},
|
|
519
|
+
tenantList: [], // CLI user info 不返回 tenantList
|
|
520
|
+
};
|
|
521
|
+
} catch (e) {
|
|
522
|
+
console.warn(`⚠️ CLI 获取用户信息失败,回退到直接 API: ${e.message}`);
|
|
523
|
+
useCliMode = false;
|
|
524
|
+
// 回退到直接 API,需要先获取 token
|
|
525
|
+
await loginWithEnv();
|
|
526
|
+
token = getToken();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// 直接 API 调用
|
|
531
|
+
const baseUrl = getBaseUrl();
|
|
532
|
+
const t = Date.now();
|
|
533
|
+
const response = await fetch(`${baseUrl}/base/api/home?pageAuthFlag=true&t=${t}`, {
|
|
534
|
+
method: 'GET',
|
|
535
|
+
headers: {
|
|
536
|
+
'accept': 'application/json, text/plain, */*',
|
|
537
|
+
'authorization': `Bearer ${token}`,
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
return response.json();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* 切换租户
|
|
546
|
+
* @param {string} token - access token
|
|
547
|
+
* @param {string} tenantId - 租户ID
|
|
548
|
+
* @returns {Promise<object>}
|
|
549
|
+
*/
|
|
550
|
+
export async function switchTenant(token, tenantId) {
|
|
551
|
+
const baseUrl = getBaseUrl();
|
|
552
|
+
const t = Date.now();
|
|
553
|
+
const response = await fetch(`${baseUrl}/base/api/user/tenant/switch?enabled=1&tenantId=${tenantId}&t=${t}`, {
|
|
554
|
+
method: 'PUT',
|
|
555
|
+
headers: {
|
|
556
|
+
'accept': 'application/json, text/plain, */*',
|
|
557
|
+
'authorization': `Bearer ${token}`,
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
return response.json();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* 默认 menu headers(AI_REPORT 菜单配置)
|
|
566
|
+
*/
|
|
567
|
+
const DEFAULT_MENU_HEADERS = {
|
|
568
|
+
'x-menu-code': 'AI_REPORT',
|
|
569
|
+
'x-menu-id': '2008425412219936770',
|
|
570
|
+
'x-menu-params': 'null',
|
|
571
|
+
'x-page-code': 'ai_report',
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* 查询模型列表
|
|
576
|
+
* @param {string} token - access token
|
|
577
|
+
* @param {object} options - 可选参数
|
|
578
|
+
* @param {number} options.page - 页码,默认 0
|
|
579
|
+
* @param {number} options.size - 每页数量,默认 100
|
|
580
|
+
* @param {object} options.menuHeaders - 自定义 menu headers
|
|
581
|
+
* @returns {Promise<array>}
|
|
582
|
+
*/
|
|
583
|
+
export async function queryModels(token, options = {}) {
|
|
584
|
+
const { page = 0, size = 100, menuHeaders = {} } = options;
|
|
585
|
+
|
|
586
|
+
// CLI 模式
|
|
587
|
+
if (useCliMode && !token) {
|
|
588
|
+
await ensureCliInstalled();
|
|
589
|
+
try {
|
|
590
|
+
const result = await execCli(['model', 'list', '-p', String(page), '-s', String(size)]);
|
|
591
|
+
if (result.error) {
|
|
592
|
+
throw new Error(result.message || '查询模型失败');
|
|
593
|
+
}
|
|
594
|
+
// 将 CLI 结果转换为原有格式(数组)
|
|
595
|
+
return result.models || [];
|
|
596
|
+
} catch (e) {
|
|
597
|
+
console.warn(`⚠️ CLI 查询模型失败,回退到直接 API: ${e.message}`);
|
|
598
|
+
useCliMode = false;
|
|
599
|
+
await loginWithEnv();
|
|
600
|
+
token = getToken();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 直接 API 调用
|
|
605
|
+
const baseUrl = getBaseUrl();
|
|
606
|
+
|
|
607
|
+
const headers = {
|
|
608
|
+
'accept': 'application/json, text/plain, */*',
|
|
609
|
+
'authorization': `Bearer ${token}`,
|
|
610
|
+
...DEFAULT_MENU_HEADERS,
|
|
611
|
+
...menuHeaders,
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const response = await fetch(`${baseUrl}/data/api/model/query?page=${page}&size=${size}`, {
|
|
615
|
+
method: 'GET',
|
|
616
|
+
headers,
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
return response.json();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* 根据模型编码查询模型数据
|
|
624
|
+
* @param {string} token - access token
|
|
625
|
+
* @param {string} modelCode - 模型编码
|
|
626
|
+
* @param {Object} params - 查询参数
|
|
627
|
+
* @param {Object} params.customQuery - 自定义查询条件
|
|
628
|
+
* @param {string[]} params.groupFields - 分组字段
|
|
629
|
+
* @param {Array} params.functions - 聚合函数
|
|
630
|
+
* @param {object} options - 可选配置
|
|
631
|
+
* @param {object} options.menuHeaders - 自定义 menu headers
|
|
632
|
+
* @returns {Promise<any>}
|
|
633
|
+
*/
|
|
634
|
+
export async function getModelData(token, modelCode, params = {}, options = {}) {
|
|
635
|
+
const { customQuery, groupFields, functions } = params;
|
|
636
|
+
const { menuHeaders = {} } = options;
|
|
637
|
+
|
|
638
|
+
// CLI 模式(仅支持简单查询)
|
|
639
|
+
if (useCliMode && !token && !customQuery && !groupFields && !functions) {
|
|
640
|
+
await ensureCliInstalled();
|
|
641
|
+
const pageNum = params.pageNum || 0;
|
|
642
|
+
const pageSize = params.pageSize || 100;
|
|
643
|
+
try {
|
|
644
|
+
const result = await execCli(['data', 'query', modelCode, '-p', String(pageNum), '-s', String(pageSize)]);
|
|
645
|
+
if (result.error) {
|
|
646
|
+
throw new Error(result.message || '查询数据失败');
|
|
647
|
+
}
|
|
648
|
+
// 将 CLI 结果转换为原有格式
|
|
649
|
+
return {
|
|
650
|
+
data: {
|
|
651
|
+
headers: result.headers || [],
|
|
652
|
+
data: result.data || [],
|
|
653
|
+
pageNum: result.pagination?.pageNum || pageNum,
|
|
654
|
+
pageSize: result.pagination?.pageSize || pageSize,
|
|
655
|
+
total: result.pagination?.total || 0,
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
} catch (e) {
|
|
659
|
+
console.warn(`⚠️ CLI 查询数据失败,回退到直接 API: ${e.message}`);
|
|
660
|
+
useCliMode = false;
|
|
661
|
+
await loginWithEnv();
|
|
662
|
+
token = getToken();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// 直接 API 调用
|
|
667
|
+
const baseUrl = getBaseUrl();
|
|
668
|
+
|
|
669
|
+
const headers = {
|
|
670
|
+
'accept': 'application/json, text/plain, */*',
|
|
671
|
+
'authorization': `Bearer ${token}`,
|
|
672
|
+
'content-type': 'application/json',
|
|
673
|
+
...DEFAULT_MENU_HEADERS,
|
|
674
|
+
...menuHeaders,
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
const body = {
|
|
678
|
+
modelCode,
|
|
679
|
+
params: {},
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
if (customQuery) {
|
|
683
|
+
body.params.customQuery = customQuery;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (groupFields && groupFields.length > 0) {
|
|
687
|
+
body.params.groupFields = groupFields;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (functions && functions.length > 0) {
|
|
691
|
+
body.params.functions = functions;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const response = await fetch(`${baseUrl}/foresight/api/bi/multdim/anls/ai/query`, {
|
|
695
|
+
method: 'POST',
|
|
696
|
+
headers,
|
|
697
|
+
body: JSON.stringify(body),
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
return response.json();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* 获取模型字段详情
|
|
705
|
+
* @param {string} token - access token
|
|
706
|
+
* @param {string[]} modelCodes - 模型编码数组
|
|
707
|
+
* @returns {Promise<Object>}
|
|
708
|
+
*/
|
|
709
|
+
export async function getModelColumns(token, modelCodes) {
|
|
710
|
+
// CLI 模式
|
|
711
|
+
if (useCliMode && !token) {
|
|
712
|
+
await ensureCliInstalled();
|
|
713
|
+
try {
|
|
714
|
+
const result = await execCli(['model', 'columns', ...modelCodes]);
|
|
715
|
+
if (result.error) {
|
|
716
|
+
throw new Error(result.message || '获取模型字段失败');
|
|
717
|
+
}
|
|
718
|
+
// 将 CLI 结果转换为原有格式
|
|
719
|
+
const columnsMap = {};
|
|
720
|
+
for (const modelCode of modelCodes) {
|
|
721
|
+
columnsMap[modelCode] = result[modelCode]?.columns || [];
|
|
722
|
+
}
|
|
723
|
+
return { data: columnsMap };
|
|
724
|
+
} catch (e) {
|
|
725
|
+
console.warn(`⚠️ CLI 获取模型字段失败,回退到直接 API: ${e.message}`);
|
|
726
|
+
useCliMode = false;
|
|
727
|
+
await loginWithEnv();
|
|
728
|
+
token = getToken();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 直接 API 调用
|
|
733
|
+
const baseUrl = getBaseUrl();
|
|
734
|
+
const response = await fetch(`${baseUrl}/data/api/model/col/get/model/col/by/codes`, {
|
|
735
|
+
method: 'POST',
|
|
736
|
+
headers: {
|
|
737
|
+
'accept': 'application/json, text/plain, */*',
|
|
738
|
+
'authorization': `Bearer ${token}`,
|
|
739
|
+
'content-type': 'application/json',
|
|
740
|
+
...DEFAULT_MENU_HEADERS,
|
|
741
|
+
},
|
|
742
|
+
body: JSON.stringify(modelCodes),
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
return response.json();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* 获取维度值列表
|
|
750
|
+
* @param {string} token - access token
|
|
751
|
+
* @param {string} dimCode - 维度代码
|
|
752
|
+
* @param {Object} options - 可选参数
|
|
753
|
+
* @returns {Promise<Array>}
|
|
754
|
+
*/
|
|
755
|
+
export async function getDimValues(token, dimCode, options = {}) {
|
|
756
|
+
const { modelCode, colName, page = 0, size = 100, keywords = null } = options;
|
|
757
|
+
|
|
758
|
+
// CLI 模式
|
|
759
|
+
if (useCliMode && !token) {
|
|
760
|
+
await ensureCliInstalled();
|
|
761
|
+
try {
|
|
762
|
+
const args = ['dim', dimCode];
|
|
763
|
+
if (keywords) {
|
|
764
|
+
args.push('-k', keywords);
|
|
765
|
+
}
|
|
766
|
+
const result = await execCli(args);
|
|
767
|
+
if (result.error) {
|
|
768
|
+
throw new Error(result.message || '获取维度值失败');
|
|
769
|
+
}
|
|
770
|
+
// 将 CLI 结果转换为原有格式
|
|
771
|
+
return {
|
|
772
|
+
data: {
|
|
773
|
+
content: result.values || [],
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
} catch (e) {
|
|
777
|
+
console.warn(`⚠️ CLI 获取维度值失败,回退到直接 API: ${e.message}`);
|
|
778
|
+
useCliMode = false;
|
|
779
|
+
await loginWithEnv();
|
|
780
|
+
token = getToken();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// 直接 API 调用
|
|
785
|
+
const baseUrl = getBaseUrl();
|
|
786
|
+
|
|
787
|
+
const response = await fetch(`${baseUrl}/data/api/online/lov/DATA_DIM_VALUE_LOV?dimCode=${dimCode}&page=${page}&size=${size}`, {
|
|
788
|
+
method: 'POST',
|
|
789
|
+
headers: {
|
|
790
|
+
'accept': 'application/json, text/plain, */*',
|
|
791
|
+
'authorization': `Bearer ${token}`,
|
|
792
|
+
'content-type': 'application/json',
|
|
793
|
+
},
|
|
794
|
+
body: JSON.stringify({
|
|
795
|
+
dimCode,
|
|
796
|
+
keywords,
|
|
797
|
+
page,
|
|
798
|
+
size,
|
|
799
|
+
modelCode: modelCode || '',
|
|
800
|
+
colName: colName || '',
|
|
801
|
+
filterMap: {},
|
|
802
|
+
writeOnly: false,
|
|
803
|
+
}),
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
return response.json();
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 获取列表值(LIST类型字段的可选值)
|
|
811
|
+
* @param {string} token - access token
|
|
812
|
+
* @param {string} listCode - 列表代码
|
|
813
|
+
* @param {Object} options - 可选参数
|
|
814
|
+
* @returns {Promise<Array>}
|
|
815
|
+
*/
|
|
816
|
+
export async function getListValues(token, listCode, options = {}) {
|
|
817
|
+
const { modelCode, colName, page = 0, size = 999 } = options;
|
|
818
|
+
|
|
819
|
+
// CLI 模式(暂不支持 LIST,使用直接 API)
|
|
820
|
+
if (useCliMode && !token) {
|
|
821
|
+
await loginWithEnv();
|
|
822
|
+
token = getToken();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// 直接 API 调用
|
|
826
|
+
const baseUrl = getBaseUrl();
|
|
827
|
+
|
|
828
|
+
const response = await fetch(`${baseUrl}/data/api/permission/dimension/query`, {
|
|
829
|
+
method: 'POST',
|
|
830
|
+
headers: {
|
|
831
|
+
'accept': 'application/json, text/plain, */*',
|
|
832
|
+
'authorization': `Bearer ${token}`,
|
|
833
|
+
'content-type': 'application/json',
|
|
834
|
+
},
|
|
835
|
+
body: JSON.stringify({
|
|
836
|
+
total: false,
|
|
837
|
+
page,
|
|
838
|
+
size,
|
|
839
|
+
dimensionType: 'LIST',
|
|
840
|
+
modelColName: colName,
|
|
841
|
+
dimensionCode: listCode,
|
|
842
|
+
modelCode,
|
|
843
|
+
filterMap: {},
|
|
844
|
+
writeOnly: false,
|
|
845
|
+
}),
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
return response.json();
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* 获取LOV值(LOV类型字段的可选值)
|
|
853
|
+
* @param {string} token - access token
|
|
854
|
+
* @param {string} lovCode - LOV代码
|
|
855
|
+
* @param {Object} options - 可选参数
|
|
856
|
+
* @returns {Promise<Array>}
|
|
857
|
+
*/
|
|
858
|
+
export async function getLovValues(token, lovCode, options = {}) {
|
|
859
|
+
const { modelCode, colName, page = 0, size = 100, keywords = null } = options;
|
|
860
|
+
|
|
861
|
+
// CLI 模式
|
|
862
|
+
if (useCliMode && !token) {
|
|
863
|
+
await ensureCliInstalled();
|
|
864
|
+
try {
|
|
865
|
+
const args = ['lov', lovCode];
|
|
866
|
+
if (keywords) {
|
|
867
|
+
args.push('-k', keywords);
|
|
868
|
+
}
|
|
869
|
+
const result = await execCli(args);
|
|
870
|
+
if (result.error) {
|
|
871
|
+
throw new Error(result.message || '获取LOV值失败');
|
|
872
|
+
}
|
|
873
|
+
// 将 CLI 结果转换为原有格式
|
|
874
|
+
return {
|
|
875
|
+
data: {
|
|
876
|
+
content: result.values || [],
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
} catch (e) {
|
|
880
|
+
console.warn(`⚠️ CLI 获取LOV值失败,回退到直接 API: ${e.message}`);
|
|
881
|
+
useCliMode = false;
|
|
882
|
+
await loginWithEnv();
|
|
883
|
+
token = getToken();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// 直接 API 调用
|
|
888
|
+
const baseUrl = getBaseUrl();
|
|
889
|
+
|
|
890
|
+
const response = await fetch(`${baseUrl}/data/api/online/lov/${lovCode}?page=${page}&size=${size}`, {
|
|
891
|
+
method: 'POST',
|
|
892
|
+
headers: {
|
|
893
|
+
'accept': 'application/json, text/plain, */*',
|
|
894
|
+
'authorization': `Bearer ${token}`,
|
|
895
|
+
'content-type': 'application/json',
|
|
896
|
+
},
|
|
897
|
+
body: JSON.stringify({
|
|
898
|
+
keywords,
|
|
899
|
+
page,
|
|
900
|
+
size,
|
|
901
|
+
modelCode: modelCode || '',
|
|
902
|
+
colName: colName || '',
|
|
903
|
+
filterMap: {},
|
|
904
|
+
writeOnly: false,
|
|
905
|
+
}),
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
return response.json();
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* 获取层级值(LEVEL类型字段的可选值)
|
|
913
|
+
* @param {string} token - access token
|
|
914
|
+
* @param {string} levelCode - 层级代码
|
|
915
|
+
* @param {Object} options - 可选参数
|
|
916
|
+
* @returns {Promise<Array>}
|
|
917
|
+
*/
|
|
918
|
+
export async function getLevelValues(token, levelCode, options = {}) {
|
|
919
|
+
const { modelCode, colName, page = 0, size = 999 } = options;
|
|
920
|
+
|
|
921
|
+
// CLI 模式(暂不支持 LEVEL,使用直接 API)
|
|
922
|
+
if (useCliMode && !token) {
|
|
923
|
+
await loginWithEnv();
|
|
924
|
+
token = getToken();
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// 直接 API 调用
|
|
928
|
+
const baseUrl = getBaseUrl();
|
|
929
|
+
|
|
930
|
+
const response = await fetch(`${baseUrl}/data/api/permission/dimension/query`, {
|
|
931
|
+
method: 'POST',
|
|
932
|
+
headers: {
|
|
933
|
+
'accept': 'application/json, text/plain, */*',
|
|
934
|
+
'authorization': `Bearer ${token}`,
|
|
935
|
+
'content-type': 'application/json',
|
|
936
|
+
},
|
|
937
|
+
body: JSON.stringify({
|
|
938
|
+
total: false,
|
|
939
|
+
page,
|
|
940
|
+
size,
|
|
941
|
+
dimensionType: 'DIM_HIERARCHY',
|
|
942
|
+
modelColName: colName,
|
|
943
|
+
dimensionCode: levelCode,
|
|
944
|
+
modelCode,
|
|
945
|
+
filterMap: {},
|
|
946
|
+
writeOnly: false,
|
|
947
|
+
}),
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
return response.json();
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* 获取 api.js 的绝对路径(供其他 skill 引用)
|
|
955
|
+
* @returns {string}
|
|
956
|
+
*/
|
|
957
|
+
export function getApiPath() {
|
|
958
|
+
return path.resolve(getDirname(), 'api.js');
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* 获取当前模块所在目录
|
|
963
|
+
*/
|
|
964
|
+
function getDirname() {
|
|
965
|
+
try {
|
|
966
|
+
return path.dirname(fileURLToPath(import.meta.url));
|
|
967
|
+
} catch {
|
|
968
|
+
return path.dirname(new URL(import.meta.url).pathname);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// 导出 DEFAULT_MENU_HEADERS 供外部使用
|
|
973
|
+
export { DEFAULT_MENU_HEADERS };
|