feat: add Feishu export credential config
This commit is contained in:
parent
07ff72eafa
commit
10f4ef45d3
@ -826,6 +826,24 @@
|
||||
let cachedFields = [];
|
||||
|
||||
return {
|
||||
getFeishuCredentials() {
|
||||
return resolveFeishuCredentials(settings);
|
||||
},
|
||||
|
||||
saveFeishuCredentials(credentials) {
|
||||
const appId = String((credentials && credentials.appId) || "").trim();
|
||||
const appSecret = String((credentials && credentials.appSecret) || "").trim();
|
||||
if (!appId || !appSecret) {
|
||||
throw new Error("请填写飞书应用 app_id 和 app_secret。");
|
||||
}
|
||||
saveLocal(FEISHU_APP_ID_STORAGE_KEY, appId);
|
||||
saveLocal(FEISHU_APP_SECRET_STORAGE_KEY, appSecret);
|
||||
return {
|
||||
appId,
|
||||
appSecret,
|
||||
};
|
||||
},
|
||||
|
||||
async preview(rawInput, onProgress) {
|
||||
const ids = await parseCreatorInputs(rawInput);
|
||||
if (!ids.length) {
|
||||
@ -1164,6 +1182,50 @@
|
||||
color: #2e211a;
|
||||
}
|
||||
|
||||
.xhs-export-config {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.xhs-export-config-panel {
|
||||
display: none;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(123, 83, 52, 0.12);
|
||||
}
|
||||
|
||||
.xhs-export-config.is-open .xhs-export-config-panel {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.xhs-export-config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.xhs-export-config-field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.xhs-export-config-label {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
color: #5e412f;
|
||||
}
|
||||
|
||||
.xhs-export-config-input {
|
||||
border: 1px solid rgba(141, 88, 51, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 9px 10px;
|
||||
font-size: 12px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
color: #2e211a;
|
||||
}
|
||||
|
||||
.xhs-export-actions,
|
||||
.xhs-export-mini-actions {
|
||||
display: flex;
|
||||
@ -1577,6 +1639,21 @@
|
||||
</div>
|
||||
<div class="xhs-export-body">
|
||||
<textarea class="xhs-export-input" placeholder="每行一个达人主页链接或达人 ID,支持批量。"></textarea>
|
||||
<div class="xhs-export-config">
|
||||
<button class="xhs-export-select-trigger" type="button" data-action="toggle-feishu-config" aria-expanded="false">飞书导出配置</button>
|
||||
<div class="xhs-export-config-panel">
|
||||
<div class="xhs-export-config-grid">
|
||||
<label class="xhs-export-config-field">
|
||||
<span class="xhs-export-config-label">飞书 app_id</span>
|
||||
<input class="xhs-export-config-input" data-config="feishu-app-id" type="text" autocomplete="off" placeholder="cli_xxx">
|
||||
</label>
|
||||
<label class="xhs-export-config-field">
|
||||
<span class="xhs-export-config-label">飞书 app_secret</span>
|
||||
<input class="xhs-export-config-input" data-config="feishu-app-secret" type="password" autocomplete="off" placeholder="请输入 app_secret">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="xhs-export-status"></div>
|
||||
<div class="xhs-export-progress is-hidden">
|
||||
<div class="xhs-export-progress-meta">
|
||||
@ -1622,6 +1699,10 @@
|
||||
toggle,
|
||||
panel,
|
||||
input: panel.querySelector(".xhs-export-input"),
|
||||
feishuConfig: panel.querySelector(".xhs-export-config"),
|
||||
feishuConfigTrigger: panel.querySelector('[data-action="toggle-feishu-config"]'),
|
||||
feishuAppIdInput: panel.querySelector('[data-config="feishu-app-id"]'),
|
||||
feishuAppSecretInput: panel.querySelector('[data-config="feishu-app-secret"]'),
|
||||
localExportButton: panel.querySelector('[data-action="export-local"]'),
|
||||
feishuExportButton: panel.querySelector('[data-action="export-feishu"]'),
|
||||
status: panel.querySelector(".xhs-export-status"),
|
||||
@ -1750,6 +1831,26 @@
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function loadFeishuConfigForm(controller, refs) {
|
||||
if (!controller || !refs) {
|
||||
return;
|
||||
}
|
||||
const credentials = controller.getFeishuCredentials();
|
||||
if (refs.feishuAppIdInput) {
|
||||
refs.feishuAppIdInput.value = credentials.appId || "";
|
||||
}
|
||||
if (refs.feishuAppSecretInput) {
|
||||
refs.feishuAppSecretInput.value = credentials.appSecret || "";
|
||||
}
|
||||
}
|
||||
|
||||
function saveFeishuConfigForm(controller, refs) {
|
||||
return controller.saveFeishuCredentials({
|
||||
appId: refs.feishuAppIdInput ? refs.feishuAppIdInput.value : "",
|
||||
appSecret: refs.feishuAppSecretInput ? refs.feishuAppSecretInput.value : "",
|
||||
});
|
||||
}
|
||||
|
||||
function setStatus(node, message, isError) {
|
||||
node.textContent = message;
|
||||
node.classList.toggle("is-error", Boolean(isError));
|
||||
@ -1871,6 +1972,7 @@
|
||||
const defaultSelectedFields = SELECTABLE_FIELD_PATHS.slice();
|
||||
|
||||
refs.input.value = typeof persistedInput === "string" ? persistedInput : "";
|
||||
loadFeishuConfigForm(controller, refs);
|
||||
renderFields(
|
||||
refs.fields,
|
||||
staticFields,
|
||||
@ -1884,6 +1986,14 @@
|
||||
refs.panel.classList.toggle("is-open");
|
||||
});
|
||||
|
||||
if (refs.feishuConfigTrigger && refs.feishuConfig) {
|
||||
refs.feishuConfigTrigger.addEventListener("click", () => {
|
||||
const open = !refs.feishuConfig.classList.contains("is-open");
|
||||
refs.feishuConfig.classList.toggle("is-open", open);
|
||||
refs.feishuConfigTrigger.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
});
|
||||
}
|
||||
|
||||
refs.localExportButton.addEventListener("click", async () => {
|
||||
try {
|
||||
const checkedFields = getCheckedFields(refs.fields);
|
||||
@ -1934,6 +2044,7 @@
|
||||
setExportButtonsDisabled(refs, true);
|
||||
hideProgress(refs);
|
||||
setProgress(refs, 0, "准备导出到飞书...", false);
|
||||
saveFeishuConfigForm(controller, refs);
|
||||
setStatus(refs.status, "正在读取达人数据,请稍候...", false);
|
||||
|
||||
await prepareExportData(controller, refs, checkedFields, 40);
|
||||
|
||||
@ -218,6 +218,83 @@ test("controller reads Feishu credentials from localStorage instead of bundled s
|
||||
}
|
||||
});
|
||||
|
||||
test("controller saves Feishu credentials for later exports", async () => {
|
||||
const previousLocalStorage = global.localStorage;
|
||||
const previousGm = global.GM_xmlhttpRequest;
|
||||
const storage = new Map();
|
||||
const sentAuthBodies = [];
|
||||
|
||||
global.GM_xmlhttpRequest = undefined;
|
||||
global.localStorage = {
|
||||
getItem(key) {
|
||||
return storage.has(key) ? storage.get(key) : null;
|
||||
},
|
||||
setItem(key, value) {
|
||||
storage.set(key, value);
|
||||
},
|
||||
};
|
||||
|
||||
async function fetchImpl(url, options) {
|
||||
const body = options && options.body ? JSON.parse(options.body) : null;
|
||||
if (String(url).startsWith(api.API_BASE)) {
|
||||
return okJson({
|
||||
code: 0,
|
||||
data: {
|
||||
id: "60379f3c000000000101e53f",
|
||||
userId: "60379f3c000000000101e53f",
|
||||
name: "达人 A",
|
||||
},
|
||||
});
|
||||
}
|
||||
if (String(url).includes("/auth/v3/tenant_access_token/internal")) {
|
||||
sentAuthBodies.push(body);
|
||||
return okJson({ code: 0, tenant_access_token: "tenant-token" });
|
||||
}
|
||||
if (String(url).includes("/sheets/v3/spreadsheets")) {
|
||||
return okJson({
|
||||
code: 0,
|
||||
data: {
|
||||
spreadsheet: {
|
||||
spreadsheet_token: "spreadsheet-token",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (String(url).includes("/metainfo")) {
|
||||
return okJson({ code: 0, data: { sheets: [{ sheetId: "sheet-a" }] } });
|
||||
}
|
||||
if (String(url).includes("/values_batch_update")) {
|
||||
return okJson({ code: 0, data: {} });
|
||||
}
|
||||
throw new Error(`unexpected url: ${url}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = api.createExportController({ fetchImpl });
|
||||
controller.saveFeishuCredentials({
|
||||
appId: " cli_saved ",
|
||||
appSecret: " secret_saved ",
|
||||
});
|
||||
await controller.preview("60379f3c000000000101e53f");
|
||||
await controller.exportFeishuSpreadsheet(["userId", "name"]);
|
||||
|
||||
assert.equal(storage.get("xhs-pgy-export:feishu-app-id"), JSON.stringify("cli_saved"));
|
||||
assert.equal(
|
||||
storage.get("xhs-pgy-export:feishu-app-secret"),
|
||||
JSON.stringify("secret_saved"),
|
||||
);
|
||||
assert.deepEqual(sentAuthBodies, [
|
||||
{
|
||||
app_id: "cli_saved",
|
||||
app_secret: "secret_saved",
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
global.localStorage = previousLocalStorage;
|
||||
global.GM_xmlhttpRequest = previousGm;
|
||||
}
|
||||
});
|
||||
|
||||
test("controller uses GM_xmlhttpRequest for Feishu even when page fetch is available", async () => {
|
||||
const gmUrls = [];
|
||||
const pageFetchUrls = [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user