feat: add Feishu export credential config
This commit is contained in:
parent
07ff72eafa
commit
10f4ef45d3
@ -826,6 +826,24 @@
|
|||||||
let cachedFields = [];
|
let cachedFields = [];
|
||||||
|
|
||||||
return {
|
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) {
|
async preview(rawInput, onProgress) {
|
||||||
const ids = await parseCreatorInputs(rawInput);
|
const ids = await parseCreatorInputs(rawInput);
|
||||||
if (!ids.length) {
|
if (!ids.length) {
|
||||||
@ -1164,6 +1182,50 @@
|
|||||||
color: #2e211a;
|
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-actions,
|
||||||
.xhs-export-mini-actions {
|
.xhs-export-mini-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1577,6 +1639,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="xhs-export-body">
|
<div class="xhs-export-body">
|
||||||
<textarea class="xhs-export-input" placeholder="每行一个达人主页链接或达人 ID,支持批量。"></textarea>
|
<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-status"></div>
|
||||||
<div class="xhs-export-progress is-hidden">
|
<div class="xhs-export-progress is-hidden">
|
||||||
<div class="xhs-export-progress-meta">
|
<div class="xhs-export-progress-meta">
|
||||||
@ -1622,6 +1699,10 @@
|
|||||||
toggle,
|
toggle,
|
||||||
panel,
|
panel,
|
||||||
input: panel.querySelector(".xhs-export-input"),
|
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"]'),
|
localExportButton: panel.querySelector('[data-action="export-local"]'),
|
||||||
feishuExportButton: panel.querySelector('[data-action="export-feishu"]'),
|
feishuExportButton: panel.querySelector('[data-action="export-feishu"]'),
|
||||||
status: panel.querySelector(".xhs-export-status"),
|
status: panel.querySelector(".xhs-export-status"),
|
||||||
@ -1750,6 +1831,26 @@
|
|||||||
.filter(Boolean);
|
.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) {
|
function setStatus(node, message, isError) {
|
||||||
node.textContent = message;
|
node.textContent = message;
|
||||||
node.classList.toggle("is-error", Boolean(isError));
|
node.classList.toggle("is-error", Boolean(isError));
|
||||||
@ -1871,6 +1972,7 @@
|
|||||||
const defaultSelectedFields = SELECTABLE_FIELD_PATHS.slice();
|
const defaultSelectedFields = SELECTABLE_FIELD_PATHS.slice();
|
||||||
|
|
||||||
refs.input.value = typeof persistedInput === "string" ? persistedInput : "";
|
refs.input.value = typeof persistedInput === "string" ? persistedInput : "";
|
||||||
|
loadFeishuConfigForm(controller, refs);
|
||||||
renderFields(
|
renderFields(
|
||||||
refs.fields,
|
refs.fields,
|
||||||
staticFields,
|
staticFields,
|
||||||
@ -1884,6 +1986,14 @@
|
|||||||
refs.panel.classList.toggle("is-open");
|
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 () => {
|
refs.localExportButton.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
const checkedFields = getCheckedFields(refs.fields);
|
const checkedFields = getCheckedFields(refs.fields);
|
||||||
@ -1934,6 +2044,7 @@
|
|||||||
setExportButtonsDisabled(refs, true);
|
setExportButtonsDisabled(refs, true);
|
||||||
hideProgress(refs);
|
hideProgress(refs);
|
||||||
setProgress(refs, 0, "准备导出到飞书...", false);
|
setProgress(refs, 0, "准备导出到飞书...", false);
|
||||||
|
saveFeishuConfigForm(controller, refs);
|
||||||
setStatus(refs.status, "正在读取达人数据,请稍候...", false);
|
setStatus(refs.status, "正在读取达人数据,请稍候...", false);
|
||||||
|
|
||||||
await prepareExportData(controller, refs, checkedFields, 40);
|
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 () => {
|
test("controller uses GM_xmlhttpRequest for Feishu even when page fetch is available", async () => {
|
||||||
const gmUrls = [];
|
const gmUrls = [];
|
||||||
const pageFetchUrls = [];
|
const pageFetchUrls = [];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user