717 lines
20 KiB
JavaScript
717 lines
20 KiB
JavaScript
// ==UserScript==
|
|
// @name 云图报告同比复制助手
|
|
// @namespace https://yuntu.oceanengine.com/
|
|
// @version 0.1.0
|
|
// @description 记录最近一次成功创建的云图报告,并一键复制同比报告
|
|
// @author wangxi
|
|
// @match https://yuntu.oceanengine.com/yuntu_brand/ecom/product/segmentedMarketcreation*
|
|
// @match https://yuntu.oceanengine.com/yuntu_brand/ecom/product/segmentedMarketDetail/*
|
|
// @grant GM_getValue
|
|
// @grant GM_setValue
|
|
// @grant GM_xmlhttpRequest
|
|
// @grant GM_addStyle
|
|
// @grant unsafeWindow
|
|
// @connect localhost
|
|
// @connect 127.0.0.1
|
|
// ==/UserScript==
|
|
|
|
(function bootstrap(root, factory) {
|
|
const api = factory(root);
|
|
if (typeof module === "object" && module.exports) {
|
|
module.exports = api;
|
|
return;
|
|
}
|
|
api.init();
|
|
})(typeof globalThis !== "undefined" ? globalThis : this, function factory(root) {
|
|
const TARGET_PATH = "/product_node/v2/api/segmentedMarket/createSegmentedMarket";
|
|
const LOCAL_API_BASES = ["http://localhost:3000", "http://127.0.0.1:3000"];
|
|
const SUPPORTED_PAGE_PATTERNS = [
|
|
/^\/yuntu_brand\/ecom\/product\/segmentedMarketcreation(?:\/.*)?$/,
|
|
/^\/yuntu_brand\/ecom\/product\/segmentedMarketDetail\/.*$/,
|
|
];
|
|
const STORAGE_KEYS = {
|
|
payload: "lastReportPayload",
|
|
meta: "lastReportMeta",
|
|
};
|
|
const EVENT_NAME = "yuntu-report-filling:capture";
|
|
const BUTTON_ID = "yuntu-report-filling-action";
|
|
const STYLE_ID = "yuntu-report-filling-style";
|
|
const INJECT_FLAG = "__yuntuReportFillingHooked__";
|
|
|
|
const runtimeState = {
|
|
isSubmitting: false,
|
|
suppressedRequestBody: null,
|
|
clearSuppressionTimer: null,
|
|
};
|
|
|
|
function log(message, extra) {
|
|
if (extra === undefined) {
|
|
console.log("[YuntuReportFilling]", message);
|
|
return;
|
|
}
|
|
console.log("[YuntuReportFilling]", message, extra);
|
|
}
|
|
|
|
function isTargetCreateReportRequest(url, method) {
|
|
if (String(method || "").toUpperCase() !== "POST") {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const parsed = new URL(url, root.location && root.location.href ? root.location.href : "https://yuntu.oceanengine.com");
|
|
return parsed.pathname === TARGET_PATH && parsed.searchParams.has("aadvid");
|
|
} catch (_error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isSupportedPageUrl(url) {
|
|
try {
|
|
const parsed = new URL(
|
|
url,
|
|
root.location && root.location.href
|
|
? root.location.href
|
|
: "https://yuntu.oceanengine.com",
|
|
);
|
|
return SUPPORTED_PAGE_PATTERNS.some((pattern) =>
|
|
pattern.test(parsed.pathname),
|
|
);
|
|
} catch (_error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getLocalApiBaseCandidates() {
|
|
return [...LOCAL_API_BASES];
|
|
}
|
|
|
|
function extractReportId(responseJson) {
|
|
if (!responseJson || typeof responseJson !== "object") {
|
|
return null;
|
|
}
|
|
|
|
const reportId = responseJson.data && responseJson.data.reportId;
|
|
if (reportId === null || reportId === undefined || reportId === "") {
|
|
return null;
|
|
}
|
|
|
|
return String(reportId);
|
|
}
|
|
|
|
function shiftDateBackOneYear(dateString) {
|
|
const match = String(dateString).match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
if (!match) {
|
|
throw new Error(`Invalid date string: ${dateString}`);
|
|
}
|
|
|
|
const year = Number(match[1]);
|
|
const month = Number(match[2]);
|
|
const day = Number(match[3]);
|
|
const targetYear = year - 1;
|
|
const lastDay = new Date(Date.UTC(targetYear, month, 0)).getUTCDate();
|
|
const safeDay = Math.min(day, lastDay);
|
|
|
|
return `${targetYear}-${String(month).padStart(2, "0")}-${String(safeDay).padStart(2, "0")}`;
|
|
}
|
|
|
|
function deepCloneJson(value) {
|
|
return JSON.parse(JSON.stringify(value));
|
|
}
|
|
|
|
function extractYear(dateString) {
|
|
return String(dateString).slice(0, 4);
|
|
}
|
|
|
|
function buildAutoCopyName(originalName, startTime, endTime) {
|
|
const baseName = originalName || "";
|
|
return `${baseName}${extractYear(startTime)}-${extractYear(endTime)}`;
|
|
}
|
|
|
|
function buildAutoCopyPayload(payload) {
|
|
const cloned = deepCloneJson(payload);
|
|
cloned.startTime = shiftDateBackOneYear(payload.startTime);
|
|
cloned.endTime = shiftDateBackOneYear(payload.endTime);
|
|
cloned.name = buildAutoCopyName(payload.name, cloned.startTime, cloned.endTime);
|
|
return cloned;
|
|
}
|
|
|
|
function buildPersistRequest({
|
|
payload,
|
|
aadvid,
|
|
reportId,
|
|
sourceType,
|
|
sourceReportId,
|
|
}) {
|
|
return {
|
|
reportId: String(reportId),
|
|
sourceType,
|
|
sourceReportId: sourceReportId ? String(sourceReportId) : null,
|
|
aadvid: String(aadvid),
|
|
name: payload.name || "",
|
|
price: Array.isArray(payload.price) ? payload.price : [],
|
|
rules: Array.isArray(payload.rules) ? payload.rules : [],
|
|
analysisDims: Array.isArray(payload.analysisDims) ? payload.analysisDims : [],
|
|
categories: Array.isArray(payload.categories) ? payload.categories : [],
|
|
channels: Array.isArray(payload.channels) ? payload.channels : [],
|
|
startTime: payload.startTime || "",
|
|
endTime: payload.endTime || "",
|
|
periodType: payload.periodType || null,
|
|
userName: payload.userName || null,
|
|
payload,
|
|
};
|
|
}
|
|
|
|
function parseJson(text) {
|
|
return JSON.parse(text);
|
|
}
|
|
|
|
function getAadvidFromUrl(url) {
|
|
try {
|
|
const parsed = new URL(url, root.location.href);
|
|
return parsed.searchParams.get("aadvid");
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getCreateRequestUrl(meta) {
|
|
if (meta && meta.requestUrl) {
|
|
return meta.requestUrl;
|
|
}
|
|
|
|
const aadvid = (meta && meta.aadvid) || getAadvidFromUrl(root.location.href);
|
|
if (!aadvid) {
|
|
throw new Error("未获取到 aadvid");
|
|
}
|
|
|
|
return `https://yuntu.oceanengine.com${TARGET_PATH}?aadvid=${encodeURIComponent(aadvid)}`;
|
|
}
|
|
|
|
function gmGetValueSafe(key, fallbackValue) {
|
|
if (typeof GM_getValue !== "function") {
|
|
return fallbackValue;
|
|
}
|
|
return GM_getValue(key, fallbackValue);
|
|
}
|
|
|
|
function gmSetValueSafe(key, value) {
|
|
if (typeof GM_setValue !== "function") {
|
|
throw new Error("GM_setValue 不可用");
|
|
}
|
|
GM_setValue(key, value);
|
|
}
|
|
|
|
function readStoredPayload() {
|
|
const rawValue = gmGetValueSafe(STORAGE_KEYS.payload, "");
|
|
if (!rawValue) {
|
|
return null;
|
|
}
|
|
|
|
return parseJson(rawValue);
|
|
}
|
|
|
|
function readStoredMeta() {
|
|
const rawValue = gmGetValueSafe(STORAGE_KEYS.meta, "");
|
|
if (!rawValue) {
|
|
return null;
|
|
}
|
|
|
|
return parseJson(rawValue);
|
|
}
|
|
|
|
function writeStoredData(payload, meta) {
|
|
gmSetValueSafe(STORAGE_KEYS.payload, JSON.stringify(payload));
|
|
gmSetValueSafe(STORAGE_KEYS.meta, JSON.stringify(meta));
|
|
}
|
|
|
|
function clearSuppression() {
|
|
runtimeState.suppressedRequestBody = null;
|
|
if (runtimeState.clearSuppressionTimer) {
|
|
root.clearTimeout(runtimeState.clearSuppressionTimer);
|
|
runtimeState.clearSuppressionTimer = null;
|
|
}
|
|
}
|
|
|
|
function suppressNextCapture(requestBodyText) {
|
|
runtimeState.suppressedRequestBody = requestBodyText;
|
|
if (runtimeState.clearSuppressionTimer) {
|
|
root.clearTimeout(runtimeState.clearSuppressionTimer);
|
|
}
|
|
runtimeState.clearSuppressionTimer = root.setTimeout(() => {
|
|
clearSuppression();
|
|
}, 10000);
|
|
}
|
|
|
|
function showToast(message, type) {
|
|
const toast = root.document.createElement("div");
|
|
toast.className = `yuntu-report-filling-toast is-${type || "info"}`;
|
|
toast.textContent = message;
|
|
root.document.body.appendChild(toast);
|
|
|
|
root.requestAnimationFrame(() => {
|
|
toast.classList.add("is-visible");
|
|
});
|
|
|
|
root.setTimeout(() => {
|
|
toast.classList.remove("is-visible");
|
|
root.setTimeout(() => {
|
|
toast.remove();
|
|
}, 240);
|
|
}, 2200);
|
|
}
|
|
|
|
function updateButtonState() {
|
|
const button = root.document.getElementById(BUTTON_ID);
|
|
if (!button) {
|
|
return;
|
|
}
|
|
|
|
const hasPayload = Boolean(readStoredPayload());
|
|
button.disabled = runtimeState.isSubmitting || !hasPayload;
|
|
button.textContent = runtimeState.isSubmitting
|
|
? "创建中..."
|
|
: "一键复制同比报告";
|
|
button.dataset.enabled = String(hasPayload);
|
|
}
|
|
|
|
async function persistToLocalServer(payload) {
|
|
const requestBody = JSON.stringify(payload);
|
|
const bases = getLocalApiBaseCandidates();
|
|
let lastError = null;
|
|
|
|
for (const base of bases) {
|
|
try {
|
|
if (typeof GM_xmlhttpRequest !== "function") {
|
|
const response = await fetch(`${base}/api/reports`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: requestBody,
|
|
});
|
|
const data = await response.json();
|
|
if (!response.ok || !data.success) {
|
|
throw new Error((data.error && data.error.message) || "本地服务请求失败");
|
|
}
|
|
return data;
|
|
}
|
|
|
|
const data = await new Promise((resolve, reject) => {
|
|
GM_xmlhttpRequest({
|
|
method: "POST",
|
|
url: `${base}/api/reports`,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
data: requestBody,
|
|
onload(response) {
|
|
try {
|
|
const json = parseJson(response.responseText);
|
|
if (response.status < 200 || response.status >= 300 || !json.success) {
|
|
reject(new Error((json.error && json.error.message) || "本地服务请求失败"));
|
|
return;
|
|
}
|
|
resolve(json);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
},
|
|
onerror() {
|
|
reject(new Error(`无法连接本地服务: ${base}`));
|
|
},
|
|
});
|
|
});
|
|
|
|
return data;
|
|
} catch (error) {
|
|
lastError = error;
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
`本地服务未启动,请先在 server 目录执行 npm start。${lastError ? ` 原始错误: ${lastError.message}` : ""}`,
|
|
);
|
|
}
|
|
|
|
async function createReportThroughPage(url, payload) {
|
|
const pageFetch =
|
|
typeof unsafeWindow !== "undefined" && unsafeWindow.fetch
|
|
? unsafeWindow.fetch.bind(unsafeWindow)
|
|
: root.fetch.bind(root);
|
|
|
|
const requestBodyText = JSON.stringify(payload);
|
|
suppressNextCapture(requestBodyText);
|
|
|
|
const response = await pageFetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
accept: "application/json, text/plain, */*",
|
|
"content-type": "application/json",
|
|
},
|
|
credentials: "include",
|
|
body: requestBodyText,
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
clearSuppression();
|
|
throw new Error((data && data.message) || `创建报告失败: ${response.status}`);
|
|
}
|
|
|
|
return {
|
|
responseJson: data,
|
|
requestBodyText,
|
|
};
|
|
}
|
|
|
|
async function handleButtonClick() {
|
|
if (runtimeState.isSubmitting) {
|
|
return;
|
|
}
|
|
|
|
const payload = readStoredPayload();
|
|
const meta = readStoredMeta();
|
|
|
|
if (!payload || !meta) {
|
|
updateButtonState();
|
|
showToast("请先手动成功创建一次报告", "error");
|
|
return;
|
|
}
|
|
|
|
runtimeState.isSubmitting = true;
|
|
updateButtonState();
|
|
|
|
try {
|
|
const nextPayload = buildAutoCopyPayload(payload);
|
|
const requestUrl = getCreateRequestUrl(meta);
|
|
const sourceReportId = meta.reportId || null;
|
|
const { responseJson } = await createReportThroughPage(requestUrl, nextPayload);
|
|
const reportId = extractReportId(responseJson);
|
|
|
|
if (!reportId) {
|
|
throw new Error("创建成功,但未获取到 reportId");
|
|
}
|
|
|
|
const nextMeta = {
|
|
reportId,
|
|
aadvid: meta.aadvid || getAadvidFromUrl(requestUrl),
|
|
sourceType: "AUTO_COPY",
|
|
capturedAt: new Date().toISOString(),
|
|
requestUrl,
|
|
};
|
|
|
|
writeStoredData(nextPayload, nextMeta);
|
|
await persistToLocalServer(
|
|
buildPersistRequest({
|
|
payload: nextPayload,
|
|
aadvid: nextMeta.aadvid,
|
|
reportId,
|
|
sourceType: "AUTO_COPY",
|
|
sourceReportId,
|
|
}),
|
|
);
|
|
|
|
showToast(`同比报告创建成功:${reportId}`, "success");
|
|
} catch (error) {
|
|
log("自动创建同比报告失败", error);
|
|
showToast(error.message || "同比报告创建失败", "error");
|
|
} finally {
|
|
runtimeState.isSubmitting = false;
|
|
updateButtonState();
|
|
}
|
|
}
|
|
|
|
async function handleCapturedRequest(detail) {
|
|
if (!detail || !detail.requestBody || !detail.responseText) {
|
|
return;
|
|
}
|
|
|
|
if (runtimeState.suppressedRequestBody && runtimeState.suppressedRequestBody === detail.requestBody) {
|
|
clearSuppression();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const payload = parseJson(detail.requestBody);
|
|
const responseJson = parseJson(detail.responseText);
|
|
const reportId = extractReportId(responseJson);
|
|
if (!reportId) {
|
|
return;
|
|
}
|
|
|
|
const aadvid = getAadvidFromUrl(detail.url) || getAadvidFromUrl(root.location.href);
|
|
if (!aadvid) {
|
|
throw new Error("未从请求中识别到 aadvid");
|
|
}
|
|
|
|
const meta = {
|
|
reportId,
|
|
aadvid,
|
|
sourceType: "MANUAL_CAPTURE",
|
|
capturedAt: new Date().toISOString(),
|
|
requestUrl: detail.url,
|
|
};
|
|
|
|
writeStoredData(payload, meta);
|
|
updateButtonState();
|
|
|
|
await persistToLocalServer(
|
|
buildPersistRequest({
|
|
payload,
|
|
aadvid,
|
|
reportId,
|
|
sourceType: "MANUAL_CAPTURE",
|
|
sourceReportId: null,
|
|
}),
|
|
);
|
|
|
|
showToast(`已记录报告配置:${reportId}`, "success");
|
|
} catch (error) {
|
|
log("记录手动创建报告失败", error);
|
|
showToast(error.message || "记录报告配置失败", "error");
|
|
updateButtonState();
|
|
}
|
|
}
|
|
|
|
function ensureStyles() {
|
|
if (root.document.getElementById(STYLE_ID)) {
|
|
return;
|
|
}
|
|
|
|
const css = `
|
|
#${BUTTON_ID} {
|
|
position: fixed;
|
|
right: 24px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
z-index: 99999;
|
|
width: 148px;
|
|
min-height: 48px;
|
|
padding: 10px 14px;
|
|
border: none;
|
|
border-radius: 14px;
|
|
background: linear-gradient(135deg, #0f766e, #115e59);
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
box-shadow: 0 12px 30px rgba(15, 118, 110, 0.28);
|
|
cursor: pointer;
|
|
transition: transform 160ms ease, opacity 160ms ease, box-shadow 160ms ease;
|
|
}
|
|
|
|
#${BUTTON_ID}:hover:not(:disabled) {
|
|
transform: translateY(-50%) translateX(-2px);
|
|
box-shadow: 0 14px 32px rgba(15, 118, 110, 0.34);
|
|
}
|
|
|
|
#${BUTTON_ID}:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.52;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.yuntu-report-filling-toast {
|
|
position: fixed;
|
|
top: 24px;
|
|
right: 24px;
|
|
z-index: 100000;
|
|
max-width: 360px;
|
|
padding: 12px 16px;
|
|
border-radius: 12px;
|
|
color: #ffffff;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.22);
|
|
opacity: 0;
|
|
transform: translateY(-8px);
|
|
transition: opacity 180ms ease, transform 180ms ease;
|
|
}
|
|
|
|
.yuntu-report-filling-toast.is-visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.yuntu-report-filling-toast.is-success {
|
|
background: #166534;
|
|
}
|
|
|
|
.yuntu-report-filling-toast.is-error {
|
|
background: #b91c1c;
|
|
}
|
|
|
|
.yuntu-report-filling-toast.is-info {
|
|
background: #1d4ed8;
|
|
}
|
|
`;
|
|
|
|
if (typeof GM_addStyle === "function") {
|
|
GM_addStyle(css);
|
|
return;
|
|
}
|
|
|
|
const style = root.document.createElement("style");
|
|
style.id = STYLE_ID;
|
|
style.textContent = css;
|
|
root.document.head.appendChild(style);
|
|
}
|
|
|
|
function ensureButton() {
|
|
if (root.document.getElementById(BUTTON_ID)) {
|
|
return;
|
|
}
|
|
|
|
const button = root.document.createElement("button");
|
|
button.id = BUTTON_ID;
|
|
button.type = "button";
|
|
button.textContent = "一键复制同比报告";
|
|
button.addEventListener("click", handleButtonClick);
|
|
root.document.body.appendChild(button);
|
|
updateButtonState();
|
|
}
|
|
|
|
function injectCaptureHook() {
|
|
if (!root.document || root.document.documentElement.getAttribute(INJECT_FLAG) === "true") {
|
|
return;
|
|
}
|
|
|
|
const script = root.document.createElement("script");
|
|
script.textContent = `
|
|
(() => {
|
|
if (window.${INJECT_FLAG}) {
|
|
return;
|
|
}
|
|
window.${INJECT_FLAG} = true;
|
|
|
|
const eventName = ${JSON.stringify(EVENT_NAME)};
|
|
const targetPath = ${JSON.stringify(TARGET_PATH)};
|
|
|
|
const isTarget = (url, method) => {
|
|
try {
|
|
const parsed = new URL(url, window.location.href);
|
|
return String(method || "").toUpperCase() === "POST"
|
|
&& parsed.pathname === targetPath
|
|
&& parsed.searchParams.has("aadvid");
|
|
} catch (_error) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const serializeBody = (body) => {
|
|
if (typeof body === "string") {
|
|
return body;
|
|
}
|
|
if (!body) {
|
|
return "";
|
|
}
|
|
if (body instanceof URLSearchParams) {
|
|
return body.toString();
|
|
}
|
|
if (body instanceof FormData) {
|
|
return "";
|
|
}
|
|
try {
|
|
return JSON.stringify(body);
|
|
} catch (_error) {
|
|
return "";
|
|
}
|
|
};
|
|
|
|
const dispatchCapture = (detail) => {
|
|
window.dispatchEvent(new CustomEvent(eventName, { detail }));
|
|
};
|
|
|
|
const originalFetch = window.fetch;
|
|
window.fetch = async function patchedFetch(input, init) {
|
|
const requestUrl = input instanceof Request ? input.url : String(input);
|
|
const method = (init && init.method) || (input instanceof Request ? input.method : "GET");
|
|
const requestBody = init && "body" in init ? serializeBody(init.body) : "";
|
|
const response = await originalFetch.apply(this, arguments);
|
|
|
|
if (isTarget(requestUrl, method)) {
|
|
try {
|
|
const cloned = response.clone();
|
|
const responseText = await cloned.text();
|
|
dispatchCapture({
|
|
url: requestUrl,
|
|
method,
|
|
requestBody,
|
|
responseText,
|
|
status: response.status,
|
|
});
|
|
} catch (_error) {}
|
|
}
|
|
|
|
return response;
|
|
};
|
|
|
|
const originalOpen = XMLHttpRequest.prototype.open;
|
|
const originalSend = XMLHttpRequest.prototype.send;
|
|
|
|
XMLHttpRequest.prototype.open = function patchedOpen(method, url) {
|
|
this.__yuntuReportMethod = method;
|
|
this.__yuntuReportUrl = url;
|
|
return originalOpen.apply(this, arguments);
|
|
};
|
|
|
|
XMLHttpRequest.prototype.send = function patchedSend(body) {
|
|
this.__yuntuReportBody = serializeBody(body);
|
|
this.addEventListener("loadend", () => {
|
|
if (!isTarget(this.__yuntuReportUrl, this.__yuntuReportMethod)) {
|
|
return;
|
|
}
|
|
dispatchCapture({
|
|
url: this.__yuntuReportUrl,
|
|
method: this.__yuntuReportMethod,
|
|
requestBody: this.__yuntuReportBody || "",
|
|
responseText: typeof this.responseText === "string" ? this.responseText : "",
|
|
status: this.status,
|
|
});
|
|
}, { once: true });
|
|
return originalSend.apply(this, arguments);
|
|
};
|
|
})();
|
|
`;
|
|
|
|
root.document.documentElement.appendChild(script);
|
|
script.remove();
|
|
root.document.documentElement.setAttribute(INJECT_FLAG, "true");
|
|
}
|
|
|
|
function attachCaptureListener() {
|
|
root.addEventListener(EVENT_NAME, (event) => {
|
|
handleCapturedRequest(event.detail);
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
if (!isSupportedPageUrl(root.location && root.location.href ? root.location.href : "")) {
|
|
return;
|
|
}
|
|
|
|
if (!root.document || !root.document.body) {
|
|
root.addEventListener("DOMContentLoaded", init, { once: true });
|
|
return;
|
|
}
|
|
|
|
ensureStyles();
|
|
ensureButton();
|
|
attachCaptureListener();
|
|
injectCaptureHook();
|
|
updateButtonState();
|
|
}
|
|
|
|
return {
|
|
buildAutoCopyPayload,
|
|
buildAutoCopyName,
|
|
buildPersistRequest,
|
|
extractReportId,
|
|
getLocalApiBaseCandidates,
|
|
init,
|
|
isSupportedPageUrl,
|
|
isTargetCreateReportRequest,
|
|
shiftDateBackOneYear,
|
|
};
|
|
});
|