cardbuddy 0.21.0 → 0.22.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/dist/cli.js +2 -1
- package/dist/preview-editor/assets/{index-CpDojF__.js → index-CPpLZZyi.js} +122 -100
- package/dist/preview-editor/assets/index-CU8zVA2P.css +1 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/vendored/server/core/cardExport.js +197 -39
- package/dist/vendored/server/core/cardGitCommit.js +190 -0
- package/dist/vendored/server/core/cardPublish.js +36 -2
- package/dist/vendored/server/core/cardPublishVersions.js +301 -4
- package/dist/vendored/server/core/index.js +6 -0
- package/dist/vendored/server/core/remoteCard.js +207 -0
- package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +424 -6
- package/dist/vendored/skills/finmall-card-sync/scripts/slice_upload.mjs +39 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/package-lock.json +7 -24
- package/package.json +6 -6
- package/dist/preview-editor/assets/index-C8VLzU18.css +0 -1
|
@@ -276,8 +276,9 @@ export async function syncCards(opts = {}) {
|
|
|
276
276
|
* @param {number} [opts.maxPages=400] 分页上限保护(50×400=2 万张,防服务端异常死循环)
|
|
277
277
|
* @param {string} [opts.appId]
|
|
278
278
|
* @param {string} [opts.workspaceId]
|
|
279
|
-
* @returns {Promise<Array<{id:string,title:string,cardId:string|number,coverUrl:string}>>}
|
|
280
|
-
* id=英文卡 code(本地目录名同源);coverUrl 为 FinMall
|
|
279
|
+
* @returns {Promise<Array<{id:string,title:string,cardId:string|number,coverUrl:string,picUrl:string,cardClassId:string}>>}
|
|
280
|
+
* id=英文卡 code(本地目录名同源);coverUrl 为 FinMall 图片库绝对地址(可能为空串);
|
|
281
|
+
* picUrl 为原始 `@filestore/...` 站内地址、cardClassId 为卡片类型 id,二者供「编辑基本信息」原样回传。
|
|
281
282
|
*/
|
|
282
283
|
export async function listRemoteCards(opts = {}) {
|
|
283
284
|
const appId = opts.appId || finmallConfig.appId;
|
|
@@ -316,6 +317,11 @@ export async function listRemoteCards(opts = {}) {
|
|
|
316
317
|
title: card.title || '',
|
|
317
318
|
cardId: card.cardId,
|
|
318
319
|
coverUrl: card.coverUrl || '',
|
|
320
|
+
// 原始站内地址(`@filestore/...`):编辑卡片基本信息时要原样回传 picUrl,
|
|
321
|
+
// 若只留 coverUrl(已 URL 化的 http 地址)就再也拼不回这个形态。
|
|
322
|
+
picUrl: (card.raw && card.raw.picUrl) || '',
|
|
323
|
+
// 卡片类型 id:编辑抽屉的「卡片类型」下拉要按它回填当前值
|
|
324
|
+
cardClassId: (card.raw && card.raw.cardClassId) || '',
|
|
319
325
|
});
|
|
320
326
|
}
|
|
321
327
|
pageNo += 1;
|
|
@@ -327,6 +333,190 @@ export async function listRemoteCards(opts = {}) {
|
|
|
327
333
|
}
|
|
328
334
|
}
|
|
329
335
|
|
|
336
|
+
/** 从后台会话中取出 FinMall 页面(列表 / 字典 / 新建 / 编辑共用同一页面上下文) */
|
|
337
|
+
function finmallPage(browser) {
|
|
338
|
+
const context = browser.contexts()[0];
|
|
339
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0];
|
|
340
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请检查凭据或重新触发。');
|
|
341
|
+
return page;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 卡片类型字典(IDC10009.do):新建 / 编辑卡片抽屉里「卡片类型」下拉的数据源。
|
|
346
|
+
*
|
|
347
|
+
* 请求体与响应形态(线上内管抓包实测):
|
|
348
|
+
* body → { channel:'', searchValue:'', applyScene:'1', sassAppId, sassWorkspaceId }
|
|
349
|
+
* resp.body.cardClassList → [{ count:'53', id:'1', name:'账户管理' }, …]
|
|
350
|
+
* `count` 是该类型下现有卡片数,内管直接展示在筛选项旁,这里一并回传备前端使用。
|
|
351
|
+
*
|
|
352
|
+
* @param {{appId?:string,workspaceId?:string}} [opts]
|
|
353
|
+
* @returns {Promise<Array<{id:string,name:string,count:number}>>}
|
|
354
|
+
*/
|
|
355
|
+
export async function listRemoteCardTypes(opts = {}) {
|
|
356
|
+
const appId = opts.appId || finmallConfig.appId;
|
|
357
|
+
const workspaceId = opts.workspaceId || finmallConfig.workspaceId;
|
|
358
|
+
const session = await ensureFinMallSession();
|
|
359
|
+
const browser = session.browser;
|
|
360
|
+
try {
|
|
361
|
+
const page = finmallPage(browser);
|
|
362
|
+
await page.bringToFront();
|
|
363
|
+
const body = await finmallPostWithHeal(
|
|
364
|
+
page,
|
|
365
|
+
`/ubomp-web/IDC10009.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
366
|
+
{
|
|
367
|
+
channel: '',
|
|
368
|
+
searchValue: '',
|
|
369
|
+
applyScene: '1',
|
|
370
|
+
sassAppId: appId,
|
|
371
|
+
sassWorkspaceId: workspaceId,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
return (Array.isArray(body.cardClassList) ? body.cardClassList : []).map((t) => ({
|
|
375
|
+
id: String(t.id),
|
|
376
|
+
name: t.name || '',
|
|
377
|
+
count: Number(t.count || 0),
|
|
378
|
+
}));
|
|
379
|
+
} finally {
|
|
380
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 新建卡片(IDC10002.do)——内管「新建卡片」抽屉的提交接口。
|
|
386
|
+
*
|
|
387
|
+
* 请求体(实测,字段名与顺序均按线上):
|
|
388
|
+
* { channel:'', code, picUrl, cardName, cardClassId, applyScene:'1', sassAppId, sassWorkspaceId }
|
|
389
|
+
* 响应:body.cardId 为新卡的数字主键(后续编辑 / 拉 schema 都以它为键)。
|
|
390
|
+
*
|
|
391
|
+
* 注意:`code` 是英文卡标识,全后端唯一且会作为本地目录名;这里与 normalizeCard 用同一
|
|
392
|
+
* 安全字符集校验,避免把非法标识提交到远端造成不可逆脏数据。
|
|
393
|
+
*
|
|
394
|
+
* @param {{code:string,cardName:string,cardClassId:string|number,picUrl?:string,appId?:string,workspaceId?:string}} input
|
|
395
|
+
* @returns {Promise<{cardId:string, code:string}>}
|
|
396
|
+
*/
|
|
397
|
+
export async function createRemoteCard(input = {}) {
|
|
398
|
+
const appId = input.appId || finmallConfig.appId;
|
|
399
|
+
const workspaceId = input.workspaceId || finmallConfig.workspaceId;
|
|
400
|
+
const code = String(input.code || '').trim();
|
|
401
|
+
const cardName = String(input.cardName || '').trim();
|
|
402
|
+
if (!/^[A-Za-z0-9_-]+$/.test(code)) throw new Error(`卡片编码非法(仅允许字母/数字/_/-):${code || '(空)'}`);
|
|
403
|
+
if (!cardName) throw new Error('卡片名称不能为空');
|
|
404
|
+
if (input.cardClassId == null || String(input.cardClassId) === '') throw new Error('请选择卡片类型');
|
|
405
|
+
|
|
406
|
+
const session = await ensureFinMallSession();
|
|
407
|
+
const browser = session.browser;
|
|
408
|
+
try {
|
|
409
|
+
const page = finmallPage(browser);
|
|
410
|
+
await page.bringToFront();
|
|
411
|
+
const body = await finmallPostWithHeal(
|
|
412
|
+
page,
|
|
413
|
+
`/ubomp-web/IDC10002.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
414
|
+
{
|
|
415
|
+
channel: '',
|
|
416
|
+
code,
|
|
417
|
+
picUrl: String(input.picUrl || ''),
|
|
418
|
+
cardName,
|
|
419
|
+
cardClassId: String(input.cardClassId),
|
|
420
|
+
applyScene: '1',
|
|
421
|
+
sassAppId: appId,
|
|
422
|
+
sassWorkspaceId: workspaceId,
|
|
423
|
+
},
|
|
424
|
+
);
|
|
425
|
+
return { cardId: String(body.cardId || ''), code };
|
|
426
|
+
} finally {
|
|
427
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* 编辑卡片基本信息(IDC10008.do)——内管卡片项「更多操作 → 编辑卡片」的提交接口。
|
|
433
|
+
*
|
|
434
|
+
* 与新建的唯一差异是多一个数字主键 `cardId`(列表 IDC10001 的 cardId,不是英文 code)。
|
|
435
|
+
* `picUrl` 传空串表示清除封面;不传则维持原值——为保证「用户没动封面就不改封面」,
|
|
436
|
+
* 调用方应把当前 picUrl 原样回传(线上内管也是这么做的)。
|
|
437
|
+
*
|
|
438
|
+
* @param {{cardId:string,code:string,cardName:string,cardClassId:string|number,picUrl?:string,appId?:string,workspaceId?:string}} input
|
|
439
|
+
* @returns {Promise<{cardId:string, code:string}>}
|
|
440
|
+
*/
|
|
441
|
+
export async function updateRemoteCard(input = {}) {
|
|
442
|
+
const appId = input.appId || finmallConfig.appId;
|
|
443
|
+
const workspaceId = input.workspaceId || finmallConfig.workspaceId;
|
|
444
|
+
const cardId = String(input.cardId || '');
|
|
445
|
+
const code = String(input.code || '').trim();
|
|
446
|
+
const cardName = String(input.cardName || '').trim();
|
|
447
|
+
if (!cardId) throw new Error('缺少 cardId,无法编辑远端卡片');
|
|
448
|
+
if (!/^[A-Za-z0-9_-]+$/.test(code)) throw new Error(`卡片编码非法(仅允许字母/数字/_/-):${code || '(空)'}`);
|
|
449
|
+
if (!cardName) throw new Error('卡片名称不能为空');
|
|
450
|
+
if (input.cardClassId == null || String(input.cardClassId) === '') throw new Error('请选择卡片类型');
|
|
451
|
+
|
|
452
|
+
const session = await ensureFinMallSession();
|
|
453
|
+
const browser = session.browser;
|
|
454
|
+
try {
|
|
455
|
+
const page = finmallPage(browser);
|
|
456
|
+
await page.bringToFront();
|
|
457
|
+
// 线上实测:IDC10008 的 URL 不带 sassAppId 查询串(只有 transCode 里带),
|
|
458
|
+
// 带了也能用;此处按实测的干净路径发送,减少与官方实现的差异面。
|
|
459
|
+
const body = await finmallPostWithHeal(
|
|
460
|
+
page,
|
|
461
|
+
'/ubomp-web/IDC10008.do',
|
|
462
|
+
{
|
|
463
|
+
channel: '',
|
|
464
|
+
cardId,
|
|
465
|
+
code,
|
|
466
|
+
picUrl: String(input.picUrl || ''),
|
|
467
|
+
cardName,
|
|
468
|
+
cardClassId: String(input.cardClassId),
|
|
469
|
+
applyScene: '1',
|
|
470
|
+
sassAppId: appId,
|
|
471
|
+
sassWorkspaceId: workspaceId,
|
|
472
|
+
},
|
|
473
|
+
`/ubomp-web/IDC10008.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
474
|
+
);
|
|
475
|
+
return { cardId, code, ...(body || {}) };
|
|
476
|
+
} finally {
|
|
477
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 删除远端卡片(IDC10003.do)——内管卡片项「更多操作 → 删除卡片」的提交接口。
|
|
483
|
+
*
|
|
484
|
+
* 请求体(实测,来自线上 `8.de07c148.js` 的 delItemHandle):
|
|
485
|
+
* { channel:'', cardId }
|
|
486
|
+
* 即只认数字主键 cardId(列表行数据的 `id`),**不传英文 code**。
|
|
487
|
+
*
|
|
488
|
+
* 不可逆:删除后该卡在 FinMall 侧的元数据与已发布版本一并消失,无法通过重新新建
|
|
489
|
+
* 恢复同一 cardId。调用方必须在执行前做二次确认。本地 src/cards/<code> 目录不受影响。
|
|
490
|
+
*
|
|
491
|
+
* @param {{cardId:string|number, channel?:string, appId?:string, workspaceId?:string}} input
|
|
492
|
+
* @returns {Promise<{cardId:string}>}
|
|
493
|
+
*/
|
|
494
|
+
export async function deleteRemoteCard(input = {}) {
|
|
495
|
+
const appId = input.appId || finmallConfig.appId;
|
|
496
|
+
const workspaceId = input.workspaceId || finmallConfig.workspaceId;
|
|
497
|
+
const cardId = String(input.cardId || '').trim();
|
|
498
|
+
if (!cardId) throw new Error('缺少 cardId,无法删除远端卡片');
|
|
499
|
+
|
|
500
|
+
const session = await ensureFinMallSession();
|
|
501
|
+
const browser = session.browser;
|
|
502
|
+
try {
|
|
503
|
+
const page = finmallPage(browser);
|
|
504
|
+
await page.bringToFront();
|
|
505
|
+
// 与 IDC10002(新建)同一写法:URL 带 sassAppId / sassWorkspaceId,transCode 取默认(同 URL)
|
|
506
|
+
await finmallPostWithHeal(
|
|
507
|
+
page,
|
|
508
|
+
`/ubomp-web/IDC10003.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
509
|
+
{
|
|
510
|
+
channel: String(input.channel || ''),
|
|
511
|
+
cardId,
|
|
512
|
+
},
|
|
513
|
+
);
|
|
514
|
+
return { cardId };
|
|
515
|
+
} finally {
|
|
516
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
330
520
|
/**
|
|
331
521
|
* 把一张卡片的最新 schema 写回 FinMall(线上编辑器「保存」接口 IDC10010.do)。
|
|
332
522
|
* 与 syncCards 共用同一套已登录浏览器会话与带自愈的 finmallPost,
|
|
@@ -820,9 +1010,40 @@ export async function fetchCardLatestVersion(cardId) {
|
|
|
820
1010
|
}
|
|
821
1011
|
}
|
|
822
1012
|
|
|
1013
|
+
/**
|
|
1014
|
+
* 归一化一条「发布任务」(IDC20002.do 的 versionTaskList[] 项)。
|
|
1015
|
+
* 任务与版本记录是两级概念:一条版本记录(versionId)下可挂多个发布任务(灰度批次),
|
|
1016
|
+
* 取消发布(IDC20003.do)只认任务的 id,不认版本号。
|
|
1017
|
+
*
|
|
1018
|
+
* 实测原始字段:{ id, taskStatus, publishType, publishMode, greyNum, productVersion, zipId, gmtModified, memo }
|
|
1019
|
+
* id=任务主键(即取消发布要传的 taskId) zipId=对应 versionId
|
|
1020
|
+
* taskStatus 与版本状态同套枚举(0=未发布/已停止,1=发布中)
|
|
1021
|
+
* publishType:1=灰度发布 2=正式发布(页面文案)
|
|
1022
|
+
* publishMode:1=白名单 其余=时间窗
|
|
1023
|
+
*
|
|
1024
|
+
* @param {object} raw
|
|
1025
|
+
* @returns {{taskId:string, taskStatus:string, publishType:string, publishMode:string,
|
|
1026
|
+
* greyNum:string, productVersion:string, modifiedAt:string, memo:string}|null}
|
|
1027
|
+
*/
|
|
1028
|
+
function normalizeVersionTask(raw) {
|
|
1029
|
+
if (!raw || raw.id == null) return null;
|
|
1030
|
+
const s = (v) => String(v == null ? '' : v);
|
|
1031
|
+
return {
|
|
1032
|
+
taskId: s(raw.id),
|
|
1033
|
+
taskStatus: s(raw.taskStatus),
|
|
1034
|
+
publishType: s(raw.publishType),
|
|
1035
|
+
publishMode: s(raw.publishMode),
|
|
1036
|
+
greyNum: s(raw.greyNum),
|
|
1037
|
+
productVersion: s(raw.productVersion),
|
|
1038
|
+
modifiedAt: s(raw.gmtModified),
|
|
1039
|
+
memo: s(raw.memo),
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
|
|
823
1043
|
/**
|
|
824
1044
|
* 取远端「发布版本记录」分页列表:IDC10004.do 的 versionList(新→旧,最多一页 pageSize 条)。
|
|
825
|
-
* 供调试面板「导入信息」tab
|
|
1045
|
+
* 供调试面板「导入信息」tab 的「发布版本记录」弹框渲染
|
|
1046
|
+
* (版本号 / 平台 / 状态 / 发布时间 / 操作:停止发布)。
|
|
826
1047
|
* 只读;与 fetchCardLatestVersion / fetchCardHistory 共用同一后台 FinMall 会话
|
|
827
1048
|
* (ensureFinMallSession + finmallPostWithHeal,命中会话失效自动清 session 重登重试一次)。
|
|
828
1049
|
*
|
|
@@ -835,11 +1056,22 @@ export async function fetchCardLatestVersion(cardId) {
|
|
|
835
1056
|
* 页面渲染仅对 0/1 出标签「未发布 / 发布中」,2/3 依枚举名与「停止发布」动作(publishStatus:'3')释义):
|
|
836
1057
|
* 0=未发布 1=发布中 2=已完成 3=已停止
|
|
837
1058
|
*
|
|
1059
|
+
* 每行额外挂 task(活动发布任务,见 normalizeVersionTask):线上「结束发布」按钮作用在任务上,
|
|
1060
|
+
* 版本记录本身不含 taskId,必须再查 IDC20002.do。只对 publishStatus==='1'(发布中)的行补查——
|
|
1061
|
+
* 一页 20 行全量查会把弹框拖到数秒,而其余状态不可能存在进行中的任务。
|
|
1062
|
+
*
|
|
838
1063
|
* @param {string|number} cardId
|
|
839
|
-
* @param {{ pageNo?: number, pageSize?: number }} [opts]
|
|
1064
|
+
* @param {{ pageNo?: number, pageSize?: number, version?: string, withTasks?: boolean }} [opts]
|
|
1065
|
+
* version 非空时按版本号精确筛选(IDC10004 支持 version 入参)——「停止发布」校验归属时用,
|
|
1066
|
+
* 免得为了定位一个旧版本把上千条记录翻一遍;
|
|
1067
|
+
* withTasks=false 时只取版本列表、不补查发布任务(服务端校验版本归属等只读场景用,
|
|
1068
|
+
* 避免为了验证一个 versionId 而把整页 20 行的任务都查一遍)。
|
|
840
1069
|
* @returns {Promise<{ list: Array<object>, maxVersion: string, totalNums: number, totalPages: number }>}
|
|
841
1070
|
*/
|
|
842
|
-
export async function fetchCardPublishVersions(
|
|
1071
|
+
export async function fetchCardPublishVersions(
|
|
1072
|
+
cardId,
|
|
1073
|
+
{ pageNo = 1, pageSize = 10, version = '', withTasks = true } = {},
|
|
1074
|
+
) {
|
|
843
1075
|
if (cardId == null) {
|
|
844
1076
|
throw new Error('缺少 cardId,无法查询发布版本记录(IDC10004.do 需要 cardId)。');
|
|
845
1077
|
}
|
|
@@ -861,7 +1093,7 @@ export async function fetchCardPublishVersions(cardId, { pageNo = 1, pageSize =
|
|
|
861
1093
|
pageNo,
|
|
862
1094
|
pageSize,
|
|
863
1095
|
publishStatus: '',
|
|
864
|
-
version
|
|
1096
|
+
version,
|
|
865
1097
|
cardId: id,
|
|
866
1098
|
sassAppId: appId,
|
|
867
1099
|
sassWorkspaceId: workspaceId,
|
|
@@ -885,8 +1117,45 @@ export async function fetchCardPublishVersions(cardId, { pageNo = 1, pageSize =
|
|
|
885
1117
|
platformStr: platforms.join('、'),
|
|
886
1118
|
publishStatus: status,
|
|
887
1119
|
publishTime: String(raw.createTime == null ? '' : raw.createTime),
|
|
1120
|
+
// 活动发布任务(含取消发布所需的 taskId);由下方按「发布中」补查 IDC20002 填充
|
|
1121
|
+
task: null,
|
|
888
1122
|
};
|
|
889
1123
|
});
|
|
1124
|
+
// 补查发布任务:对**每一行**都查,不按 publishStatus 预筛。
|
|
1125
|
+
// 曾经只查「发布中」的行,理由是「其余状态不可能有进行中任务」,但实测这个前提不成立:
|
|
1126
|
+
// account 卡 29 条版本里 28 条 publishStatus='1',其中 20260025/26/28 的 versionTaskList
|
|
1127
|
+
// 是货真价实的空数组(errorCode=0,非查询失败)——它们经其它入口(顶栏「卡片发布」
|
|
1128
|
+
// IDC10016)上线,压根不建 IDC20002 任务。反过来也不能假设非发布中的行一定没任务。
|
|
1129
|
+
// 「有没有进行中任务」只认 IDC20002 的实际返回,才敢拿它当按钮可用性的依据。
|
|
1130
|
+
// 单行失败不影响整页(该行 task=null)。
|
|
1131
|
+
// 分批并发(6 路):实测一页 20 行串行要 3.3s,并发后 <1s;
|
|
1132
|
+
// 上限压在 6 是为了不给后台 Chrome 的 HTTP 连接池和远端网关添压力。
|
|
1133
|
+
const pending = withTasks ? list.filter((row) => row.versionId) : [];
|
|
1134
|
+
const TASK_CONCURRENCY = 6;
|
|
1135
|
+
for (let i = 0; i < pending.length; i += TASK_CONCURRENCY) {
|
|
1136
|
+
await Promise.all(
|
|
1137
|
+
pending.slice(i, i + TASK_CONCURRENCY).map(async (row) => {
|
|
1138
|
+
try {
|
|
1139
|
+
const taskResp = await finmallPostWithHeal(
|
|
1140
|
+
page,
|
|
1141
|
+
`/ubomp-web/IDC20002.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
1142
|
+
{ packageInfoId: row.versionId },
|
|
1143
|
+
'IDC20002.do',
|
|
1144
|
+
);
|
|
1145
|
+
const rawTasks = Array.isArray(taskResp && taskResp.versionTaskList) ? taskResp.versionTaskList : [];
|
|
1146
|
+
// 一个版本可能有多批灰度任务,取仍在进行中的那一条(已停止的历史任务不提供按钮)
|
|
1147
|
+
const active = rawTasks.map(normalizeVersionTask).filter(Boolean).find((t) => t.taskStatus !== '0');
|
|
1148
|
+
row.task = active || null;
|
|
1149
|
+
} catch (e) {
|
|
1150
|
+
log.warn('查询版本发布任务失败(该行将不显示停止发布)', {
|
|
1151
|
+
version: row.version,
|
|
1152
|
+
error: e && e.message,
|
|
1153
|
+
});
|
|
1154
|
+
row.task = null;
|
|
1155
|
+
}
|
|
1156
|
+
}),
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
890
1159
|
log.info('查询远端发布版本记录', {
|
|
891
1160
|
cardId: id,
|
|
892
1161
|
pageNo,
|
|
@@ -904,6 +1173,155 @@ export async function fetchCardPublishVersions(cardId, { pageNo = 1, pageSize =
|
|
|
904
1173
|
}
|
|
905
1174
|
}
|
|
906
1175
|
|
|
1176
|
+
/**
|
|
1177
|
+
* 取某条版本记录下的发布任务列表:IDC20002.do(GET_CARD_VERSION_TASKS)。
|
|
1178
|
+
*
|
|
1179
|
+
* 线上详情页是「版本 → 任务」两层:版本行展开后是任务子行,「结束发布」按钮落在任务行上,
|
|
1180
|
+
* 调 IDC20003.do 时传的是任务主键(taskInfo.id),不是 versionId。因此要停某个版本的发布,
|
|
1181
|
+
* 必须先经本接口拿到 taskId。
|
|
1182
|
+
*
|
|
1183
|
+
* 实测入参只需 packageInfoId(= IDC10004 返回的 versionId),返回 body.versionTaskList[]:
|
|
1184
|
+
* { id, taskStatus, publishType, publishMode, greyNum, productVersion, zipId, gmtModified, memo }
|
|
1185
|
+
*
|
|
1186
|
+
* @param {string|number} packageInfoId 版本记录主键(versionId)
|
|
1187
|
+
* @returns {Promise<Array<object>>} 归一化任务列表(见 normalizeVersionTask),可能为空数组
|
|
1188
|
+
*/
|
|
1189
|
+
export async function fetchCardVersionTasks(packageInfoId) {
|
|
1190
|
+
if (packageInfoId == null || packageInfoId === '') {
|
|
1191
|
+
throw new Error('缺少 packageInfoId(版本记录 versionId),无法查询发布任务(IDC20002.do)。');
|
|
1192
|
+
}
|
|
1193
|
+
const appId = finmallConfig.appId;
|
|
1194
|
+
const workspaceId = finmallConfig.workspaceId;
|
|
1195
|
+
const id = String(packageInfoId);
|
|
1196
|
+
const session = await ensureFinMallSession();
|
|
1197
|
+
const browser = session.browser;
|
|
1198
|
+
try {
|
|
1199
|
+
const context = browser.contexts()[0];
|
|
1200
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0];
|
|
1201
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请先运行 finmall-session-navigator 登录。');
|
|
1202
|
+
await page.bringToFront();
|
|
1203
|
+
const resp = await finmallPostWithHeal(
|
|
1204
|
+
page,
|
|
1205
|
+
`/ubomp-web/IDC20002.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
1206
|
+
{ packageInfoId: id },
|
|
1207
|
+
'IDC20002.do',
|
|
1208
|
+
);
|
|
1209
|
+
const rawList = Array.isArray(resp && resp.versionTaskList) ? resp.versionTaskList : [];
|
|
1210
|
+
const list = rawList.map(normalizeVersionTask).filter(Boolean);
|
|
1211
|
+
log.info('查询远端发布任务', { versionId: id, count: list.length });
|
|
1212
|
+
return list;
|
|
1213
|
+
} finally {
|
|
1214
|
+
try { await browser.disconnect(); } catch { /* ignore */ }
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* 变更发布任务状态:IDC20003.do(UPDATE_CARD_TASK_STATUS)。
|
|
1220
|
+
*
|
|
1221
|
+
* 「停止发布」在线上对应 taskStatus = '0'(逆向页面 stopTaskHandle:
|
|
1222
|
+
* `updateCardTaskStatus({ taskId: taskInfo.id, taskStatus: statusEnum.UNPUB })`,
|
|
1223
|
+
* UNPUB 与版本状态共用同一枚举,值即 '0')。调用成功后页面刷新公告板列表,
|
|
1224
|
+
* 该版本的发布随之结束。
|
|
1225
|
+
*
|
|
1226
|
+
* 注意:本函数只发请求,不做「taskId 是否属于目标卡」的守卫——守卫在调用方
|
|
1227
|
+
* (server/core/cardPublishVersions.js 会先校验 versionId 属于该卡、taskId 属于该版本)做,
|
|
1228
|
+
* 避免脚本层重复拼装业务语义。
|
|
1229
|
+
*
|
|
1230
|
+
* @param {{ taskId: string|number, taskStatus?: string }} opts
|
|
1231
|
+
* @returns {Promise<object>} 接口 body(成功时通常为空对象)
|
|
1232
|
+
*/
|
|
1233
|
+
export async function updateCardTaskStatus({ taskId, taskStatus = '0' } = {}) {
|
|
1234
|
+
if (taskId == null || taskId === '') {
|
|
1235
|
+
throw new Error('缺少 taskId,无法变更发布任务状态(IDC20003.do 需要 taskId)。');
|
|
1236
|
+
}
|
|
1237
|
+
const appId = finmallConfig.appId;
|
|
1238
|
+
const workspaceId = finmallConfig.workspaceId;
|
|
1239
|
+
const session = await ensureFinMallSession();
|
|
1240
|
+
const browser = session.browser;
|
|
1241
|
+
try {
|
|
1242
|
+
const context = browser.contexts()[0];
|
|
1243
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0];
|
|
1244
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请先运行 finmall-session-navigator 登录。');
|
|
1245
|
+
await page.bringToFront();
|
|
1246
|
+
const body = await finmallPostWithHeal(
|
|
1247
|
+
page,
|
|
1248
|
+
`/ubomp-web/IDC20003.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
1249
|
+
{
|
|
1250
|
+
taskId: String(taskId),
|
|
1251
|
+
taskStatus: String(taskStatus),
|
|
1252
|
+
sassAppId: appId,
|
|
1253
|
+
sassWorkspaceId: workspaceId,
|
|
1254
|
+
},
|
|
1255
|
+
'IDC20003.do',
|
|
1256
|
+
);
|
|
1257
|
+
log.ok('变更远端发布任务状态', { taskId: String(taskId), taskStatus: String(taskStatus) });
|
|
1258
|
+
return body || {};
|
|
1259
|
+
} finally {
|
|
1260
|
+
try { await browser.disconnect(); } catch { /* ignore */ }
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* 为某条版本记录创建一条发布任务:IDC20004.do(PUBLISH_CARD_TASK)。
|
|
1266
|
+
*
|
|
1267
|
+
* 线上详情页版本行的「创建发布」按钮(tooltip:创建发布)打开 800px 抽屉
|
|
1268
|
+
* (card-publish-drawer → card-publish 表单),提交时组装如下 body 调本接口:
|
|
1269
|
+
* { cardId, versionId, version, publishType, publishMode, whitelistIds,
|
|
1270
|
+
* realGreyEndtime, greyNum, memo, greyConfigInfo }
|
|
1271
|
+
* 其中 publishType '2'=正式 / '1'=灰度;灰度时再按 publishMode
|
|
1272
|
+
* '1'=白名单 / '2'=时间窗 带白名单 id 或结束时间 + 灰度人数,高级规则序列化进 greyConfigInfo。
|
|
1273
|
+
*
|
|
1274
|
+
* 这里只做「正式发布」这一条最常用路径(其余字段按页面表单默认值原样下发:
|
|
1275
|
+
* publishMode '1'、greyNum '1'、greyConfigInfo '[]'),灰度发布涉及白名单 / 时间窗 /
|
|
1276
|
+
* 高级规则三张表单,等有真实需求再扩,避免半成品参数把灰度任务配错。
|
|
1277
|
+
*
|
|
1278
|
+
* 与 updateCardTaskStatus 同样只发请求不做归属守卫(守卫在 server/core/cardPublishVersions.js)。
|
|
1279
|
+
* 这条守卫不是可选的:实测 IDC20004 对「不属于该卡的 versionId」也返回
|
|
1280
|
+
* `{"errorCode":"0","errorMsg":"交易成功"}` 并真的建了任务 —— 远端不做归属校验,
|
|
1281
|
+
* 传错 versionId 只会静默建出一条挂在野版本上的任务。
|
|
1282
|
+
*
|
|
1283
|
+
* @param {{ cardId: string|number, versionId: string|number, version?: string, memo?: string }} opts
|
|
1284
|
+
* @returns {Promise<object>} 接口 body(成功时通常为空对象)
|
|
1285
|
+
*/
|
|
1286
|
+
export async function createCardPublishTask({ cardId, versionId, version = "", memo = "" } = {}) {
|
|
1287
|
+
if (cardId == null || cardId === "" || versionId == null || versionId === "") {
|
|
1288
|
+
throw new Error("缺少 cardId / versionId,无法创建发布任务(IDC20004.do 需要两者)。");
|
|
1289
|
+
}
|
|
1290
|
+
const appId = finmallConfig.appId;
|
|
1291
|
+
const workspaceId = finmallConfig.workspaceId;
|
|
1292
|
+
const session = await ensureFinMallSession();
|
|
1293
|
+
const browser = session.browser;
|
|
1294
|
+
try {
|
|
1295
|
+
const context = browser.contexts()[0];
|
|
1296
|
+
const page = context?.pages().find((p) => p.url().includes("finmall.com")) || context?.pages()[0];
|
|
1297
|
+
if (!page) throw new Error("未能在后台 Chrome 中找到 FinMall 页面,请先运行 finmall-session-navigator 登录。");
|
|
1298
|
+
await page.bringToFront();
|
|
1299
|
+
const body = await finmallPostWithHeal(
|
|
1300
|
+
page,
|
|
1301
|
+
`/ubomp-web/IDC20004.do?sassAppId=${appId}&sassWorkspaceId=${workspaceId}`,
|
|
1302
|
+
{
|
|
1303
|
+
cardId: String(cardId),
|
|
1304
|
+
versionId: String(versionId),
|
|
1305
|
+
version: String(version || ""),
|
|
1306
|
+
publishType: "2",
|
|
1307
|
+
publishMode: "1",
|
|
1308
|
+
whitelistIds: "",
|
|
1309
|
+
realGreyEndtime: "",
|
|
1310
|
+
greyNum: "1",
|
|
1311
|
+
memo: String(memo || ""),
|
|
1312
|
+
greyConfigInfo: "[]",
|
|
1313
|
+
sassAppId: appId,
|
|
1314
|
+
sassWorkspaceId: workspaceId,
|
|
1315
|
+
},
|
|
1316
|
+
"IDC20004.do",
|
|
1317
|
+
);
|
|
1318
|
+
log.ok("创建远端发布任务", { cardId: String(cardId), versionId: String(versionId), version: String(version || "") });
|
|
1319
|
+
return body || {};
|
|
1320
|
+
} finally {
|
|
1321
|
+
try { await browser.disconnect(); } catch { /* ignore */ }
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
907
1325
|
/**
|
|
908
1326
|
* 取远端「卡片历史版本时间线」:IDC10013 按 cardId 分页返回 schemaValueList(新→旧)。
|
|
909
1327
|
* 供编辑器「卡片历史记录」tab 渲染 git 式时间线。只读;与 fetchCardLatestVersion 共用
|
|
@@ -245,6 +245,45 @@ async function uploadWithHeal(page, payload) {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
/**
|
|
249
|
+
* 上传单张图片到 FinMall 图片库(CC0000.do),返回 `@filestore/...` 站内地址。
|
|
250
|
+
*
|
|
251
|
+
* 与切图上传是同一条链路(同接口、同会话自愈、同响应解析),差异只在用途:
|
|
252
|
+
* 这里不写 manifest、不改写 schema —— 新建/编辑卡片抽屉只需要一个能写进 `picUrl` 的地址。
|
|
253
|
+
* 返回地址原样取自响应 `body.uploadFileList[].filePath`,代码不拼任何前缀。
|
|
254
|
+
*
|
|
255
|
+
* @param {{fileName:string,base64:string,mime?:string,appId?:string,workspaceId?:string}} input
|
|
256
|
+
* @returns {Promise<{url:string,dimensions:string,fileName:string}>}
|
|
257
|
+
*/
|
|
258
|
+
export async function uploadCardImage(input = {}) {
|
|
259
|
+
const fileName = String(input.fileName || '').trim();
|
|
260
|
+
if (!fileName) throw new Error('缺少文件名,无法上传封面');
|
|
261
|
+
if (!input.base64) throw new Error('缺少图片内容,无法上传封面');
|
|
262
|
+
const mime = input.mime || IMAGE_MIME[path.extname(fileName).toLowerCase()] || 'image/png';
|
|
263
|
+
const appId = input.appId || finmallConfig.appId;
|
|
264
|
+
const workspaceId = input.workspaceId || finmallConfig.workspaceId;
|
|
265
|
+
|
|
266
|
+
const session = await ensureFinMallSession();
|
|
267
|
+
const browser = session.browser;
|
|
268
|
+
try {
|
|
269
|
+
const context = browser.contexts()[0];
|
|
270
|
+
const page = context?.pages().find((p) => p.url().includes('finmall.com')) || context?.pages()[0];
|
|
271
|
+
if (!page) throw new Error('未能在后台 Chrome 中找到 FinMall 页面,请检查凭据或重新触发上传。');
|
|
272
|
+
await page.bringToFront();
|
|
273
|
+
const result = await uploadWithHeal(page, {
|
|
274
|
+
fileName,
|
|
275
|
+
mime,
|
|
276
|
+
base64: input.base64,
|
|
277
|
+
appId,
|
|
278
|
+
workspaceId,
|
|
279
|
+
endpoint: finmallConfig.uploadImgEndpoint,
|
|
280
|
+
});
|
|
281
|
+
return { url: result.url, dimensions: result.dimensions || '', fileName };
|
|
282
|
+
} finally {
|
|
283
|
+
try { await browser.disconnect() } catch { /* ignore */ }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
248
287
|
/** 扫描 src/slices 下的图片真源(扁平落点,manifest.json 等非图片文件自动忽略) */
|
|
249
288
|
function listSlices(slicesRoot) {
|
|
250
289
|
if (!existsSync(slicesRoot)) return [];
|
|
@@ -11,42 +11,25 @@
|
|
|
11
11
|
"playwright": "^1.62.1"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"node_modules/fsevents": {
|
|
15
|
-
"version": "2.3.2",
|
|
16
|
-
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
|
|
17
|
-
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
|
18
|
-
"hasInstallScript": true,
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"optional": true,
|
|
21
|
-
"os": [
|
|
22
|
-
"darwin"
|
|
23
|
-
],
|
|
24
|
-
"engines": {
|
|
25
|
-
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
14
|
"node_modules/playwright": {
|
|
29
|
-
"version": "1.
|
|
30
|
-
"resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.
|
|
31
|
-
"integrity": "sha512-
|
|
15
|
+
"version": "1.64.1",
|
|
16
|
+
"resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.64.1.tgz",
|
|
17
|
+
"integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==",
|
|
32
18
|
"license": "Apache-2.0",
|
|
33
19
|
"dependencies": {
|
|
34
|
-
"playwright-core": "1.
|
|
20
|
+
"playwright-core": "1.64.1"
|
|
35
21
|
},
|
|
36
22
|
"bin": {
|
|
37
23
|
"playwright": "cli.js"
|
|
38
24
|
},
|
|
39
25
|
"engines": {
|
|
40
26
|
"node": ">=20"
|
|
41
|
-
},
|
|
42
|
-
"optionalDependencies": {
|
|
43
|
-
"fsevents": "2.3.2"
|
|
44
27
|
}
|
|
45
28
|
},
|
|
46
29
|
"node_modules/playwright-core": {
|
|
47
|
-
"version": "1.
|
|
48
|
-
"resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.
|
|
49
|
-
"integrity": "sha512-
|
|
30
|
+
"version": "1.64.1",
|
|
31
|
+
"resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.64.1.tgz",
|
|
32
|
+
"integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==",
|
|
50
33
|
"license": "Apache-2.0",
|
|
51
34
|
"bin": {
|
|
52
35
|
"playwright-core": "cli.js"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cardbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Local dev service for the CardBuddy card editor — decouples card CRUD (sync/save/validate/render-status/screenshot) from the static public editor, so the editor can be deployed to any static host while each developer runs a local service against their own card source.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
13
|
"engines": {
|
|
14
|
-
"node": ">=
|
|
14
|
+
"node": ">=22.22"
|
|
15
15
|
},
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
27
27
|
"@vue/server-renderer": "^3.5.40",
|
|
28
28
|
"chalk": "^6.0.0",
|
|
29
|
-
"chokidar": "^
|
|
29
|
+
"chokidar": "^6.0.0",
|
|
30
30
|
"codejar": "^4.3.0",
|
|
31
31
|
"connect": "^3.7.0",
|
|
32
|
-
"dotenv": "^
|
|
33
|
-
"pngjs": "^
|
|
32
|
+
"dotenv": "^18.0.9",
|
|
33
|
+
"pngjs": "^7.0.0",
|
|
34
34
|
"prismjs": "^1.30.0",
|
|
35
35
|
"qrcode": "^1.5.4",
|
|
36
|
-
"selfsigned": "^
|
|
36
|
+
"selfsigned": "^4.0.1",
|
|
37
37
|
"vite": "^8.2.0",
|
|
38
38
|
"vue": "^3.5.40"
|
|
39
39
|
},
|