diff --git a/yuntu/yuntuEvaluateReportSubmit/scriptcat-yuntu-evaluation-task-watcher.user.js b/yuntu/yuntuEvaluateReportSubmit/scriptcat-yuntu-evaluation-task-watcher.user.js
new file mode 100644
index 0000000..fdecb70
--- /dev/null
+++ b/yuntu/yuntuEvaluateReportSubmit/scriptcat-yuntu-evaluation-task-watcher.user.js
@@ -0,0 +1,1005 @@
+// ==UserScript==
+// @name 结案报告监工
+// @namespace yuntu-evaluation-task-watcher
+// @version 0.2.0
+// @description 监听云图坑位,按配置的报告ID列表依次启动已有报告
+// @match https://yuntu.oceanengine.com/yuntu_brand/ecom/evaluation/task_list*
+// @require https://cdn.jsdelivr.net/npm/sweetalert2@11
+// @grant none
+// @run-at document-idle
+// ==/UserScript==
+
+(function () {
+ "use strict";
+
+ const STORAGE_KEY = "yuntu_evaluation_task_watcher_state_v1";
+ const LAUNCHER_POSITION_KEY = "yuntu_evaluation_task_watcher_launcher_position_v1";
+ const DEFAULT_PAGE_STATE = {
+ enabled: false,
+ importMode: "id",
+ manualTaskIds: [],
+ importKeyword: "",
+ watchTaskIds: [],
+ submittedTaskIds: [],
+ selectedBrandKey: "",
+ webhookUrl: "",
+ };
+ const DEFAULT_CONFIG = {
+ industryVersion: "10005",
+ pollIntervalMs: 30000,
+ taskListRefreshIntervalMs: 60000,
+ submitCooldownMs: 4000,
+ };
+
+ const runtime = {
+ aadvid: "",
+ pageState: { ...DEFAULT_PAGE_STATE },
+ brandContext: null,
+ busy: false,
+ timerId: null,
+ launcherEl: null,
+ dragState: null,
+ suppressClickOnce: false,
+ lastQuotaStatus: "未检查",
+ lastTaskListStatus: "未同步",
+ lastTaskListSyncAt: 0,
+ lastAction: "未执行",
+ latestSourceTasks: [],
+ logs: [],
+ };
+
+ const toast = Swal.mixin({
+ toast: true,
+ position: "top-end",
+ showConfirmButton: false,
+ timer: 2500,
+ timerProgressBar: true,
+ });
+
+ function readStore() {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ return raw ? JSON.parse(raw) : {};
+ } catch (error) {
+ console.error("[结案报告监工] 读取本地存储失败", error);
+ return {};
+ }
+ }
+
+ function writeStore(store) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
+ }
+
+ function loadLauncherPosition() {
+ try {
+ const raw = localStorage.getItem(LAUNCHER_POSITION_KEY);
+ if (!raw) {
+ return null;
+ }
+ const data = JSON.parse(raw);
+ if (typeof data?.x !== "number" || typeof data?.y !== "number") {
+ return null;
+ }
+ return data;
+ } catch (error) {
+ console.error("[结案报告监工] 读取悬浮按钮位置失败", error);
+ return null;
+ }
+ }
+
+ function saveLauncherPosition(position) {
+ localStorage.setItem(LAUNCHER_POSITION_KEY, JSON.stringify(position));
+ }
+
+ function loadPageState(aadvid) {
+ const store = readStore();
+ const saved = store[aadvid] || {};
+ return {
+ ...DEFAULT_PAGE_STATE,
+ ...saved,
+ importMode: ["id", "keyword", "mine"].includes(saved.importMode) ? saved.importMode : "id",
+ manualTaskIds: dedupeTaskIds(saved.manualTaskIds || saved.watchTaskIds || []),
+ importKeyword: String(saved.importKeyword || ""),
+ watchTaskIds: dedupeTaskIds(saved.watchTaskIds || []),
+ submittedTaskIds: dedupeTaskIds(saved.submittedTaskIds || []),
+ selectedBrandKey: String(saved.selectedBrandKey || ""),
+ webhookUrl: String(saved.webhookUrl || ""),
+ };
+ }
+
+ function savePageState() {
+ const store = readStore();
+ store[runtime.aadvid] = {
+ enabled: runtime.pageState.enabled,
+ importMode: runtime.pageState.importMode,
+ manualTaskIds: runtime.pageState.manualTaskIds,
+ importKeyword: runtime.pageState.importKeyword,
+ watchTaskIds: runtime.pageState.watchTaskIds,
+ submittedTaskIds: runtime.pageState.submittedTaskIds,
+ selectedBrandKey: runtime.pageState.selectedBrandKey,
+ webhookUrl: runtime.pageState.webhookUrl,
+ };
+ writeStore(store);
+ }
+
+ function dedupeTaskIds(values) {
+ const unique = new Set();
+ for (const value of values) {
+ const taskId = String(value || "").trim();
+ if (taskId) {
+ unique.add(taskId);
+ }
+ }
+ return Array.from(unique);
+ }
+
+ function parseTaskIds(text) {
+ return dedupeTaskIds(String(text || "").split(/[\s,\n,]+/));
+ }
+
+ function clampLauncherPosition(x, y) {
+ if (!runtime.launcherEl) {
+ return { x, y };
+ }
+
+ const rect = runtime.launcherEl.getBoundingClientRect();
+ const maxX = Math.max(8, window.innerWidth - rect.width - 8);
+ const maxY = Math.max(8, window.innerHeight - rect.height - 8);
+
+ return {
+ x: Math.min(Math.max(8, x), maxX),
+ y: Math.min(Math.max(8, y), maxY),
+ };
+ }
+
+ function applyLauncherPosition(position) {
+ if (!runtime.launcherEl) {
+ return;
+ }
+
+ const next = clampLauncherPosition(position.x, position.y);
+ runtime.launcherEl.style.left = `${next.x}px`;
+ runtime.launcherEl.style.top = `${next.y}px`;
+ runtime.launcherEl.style.right = "auto";
+ runtime.launcherEl.style.bottom = "auto";
+ }
+
+ function getPendingTaskIds() {
+ const submitted = new Set(runtime.pageState.submittedTaskIds);
+ return runtime.pageState.watchTaskIds.filter((taskId) => !submitted.has(taskId));
+ }
+
+ function getAadvidFromUrl() {
+ return new URL(window.location.href).searchParams.get("aadvid") || "";
+ }
+
+ function pushLog(message) {
+ const entry = `${new Date().toLocaleTimeString()} ${message}`;
+ runtime.logs.unshift(entry);
+ runtime.logs = runtime.logs.slice(0, 12);
+ console.log("[结案报告监工]", message);
+ }
+
+ function showToast(icon, title, text) {
+ pushLog(text || title);
+ renderLauncher();
+ return toast.fire({
+ icon,
+ title,
+ text,
+ });
+ }
+
+ function getModeLabel(mode = runtime.pageState.importMode) {
+ if (mode === "keyword") {
+ return "关键词导入";
+ }
+ if (mode === "mine") {
+ return "我创建的";
+ }
+ return "ID导入";
+ }
+
+ function formatSyncTime(timestamp) {
+ if (!timestamp) {
+ return "未同步";
+ }
+ return new Date(timestamp).toLocaleTimeString();
+ }
+
+ function buildHeaders(pathId, contentType) {
+ const headers = {
+ Accept: "application/json, text/plain, */*",
+ "x-request-start": String(Date.now()),
+ };
+
+ if (pathId) {
+ headers["x-path-id"] = pathId;
+ }
+
+ if (contentType) {
+ headers["Content-Type"] = contentType;
+ }
+
+ if (runtime.brandContext?.industryVersion) {
+ headers["x-industry-version"] = runtime.brandContext.industryVersion;
+ }
+
+ return headers;
+ }
+
+ async function requestJson(url, options = {}) {
+ const response = await fetch(url, {
+ method: options.method || "GET",
+ credentials: "include",
+ headers: buildHeaders(options.pathId, options.contentType),
+ body: options.body ? JSON.stringify(options.body) : undefined,
+ referrer: options.referrer || window.location.href,
+ referrerPolicy: "strict-origin-when-cross-origin",
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+
+ return response.json();
+ }
+
+ async function sendWebhookNotification(taskId) {
+ const webhookUrl = String(runtime.pageState.webhookUrl || "").trim();
+ if (!webhookUrl) {
+ return;
+ }
+
+ const pendingCount = getPendingTaskIds().length;
+ const message =
+ `结案报告监工通知:品牌 ${runtime.brandContext.brandName}(${runtime.brandContext.mainBrandId}) ` +
+ `已启动报告 ${taskId},剩余待提交 ${pendingCount} 个。`;
+
+ const response = await fetch(webhookUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ msg_type: "text",
+ content: {
+ text: message,
+ },
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Webhook HTTP ${response.status}`);
+ }
+ }
+
+ function formatBrandOption(meta) {
+ return `${meta.brand_name} (${meta.brand_id}) / ${meta.industry_name} (${meta.industry_id})`;
+ }
+
+ function normalizeBrandMetadata(list) {
+ const candidates = [];
+ const seen = new Set();
+
+ for (const item of Array.isArray(list) ? list : []) {
+ const industryId = String(item?.industry_id || "");
+ if (industryId.length !== 2) {
+ continue;
+ }
+
+ const brandId = String(item?.brand_id || "");
+ const brandName = String(item?.brand_name || "");
+ const industryName = String(item?.industry_name || "");
+ const key = `${brandId}|${industryId}`;
+
+ if (!brandId || seen.has(key)) {
+ continue;
+ }
+
+ seen.add(key);
+ candidates.push({
+ key,
+ brandId,
+ brandName,
+ industryId,
+ industryName,
+ });
+ }
+
+ return candidates;
+ }
+
+ async function fetchUserInfoContext() {
+ const url = `https://yuntu.oceanengine.com/yuntu_ng/api/v1/get_user_info?aadvid=${encodeURIComponent(runtime.aadvid)}`;
+ const data = await requestJson(url, {
+ method: "GET",
+ referrer: window.location.href,
+ });
+
+ const brandMetadata = normalizeBrandMetadata(data?.data?.brandMetadata);
+ if (brandMetadata.length === 0) {
+ throw new Error("未从 get_user_info 中解析到品牌和行业信息");
+ }
+
+ let selected = brandMetadata.find((item) => item.key === runtime.pageState.selectedBrandKey) || null;
+
+ if (!selected && brandMetadata.length > 1) {
+ const inputOptions = {};
+ for (const item of brandMetadata) {
+ inputOptions[item.key] = formatBrandOption(item);
+ }
+
+ const result = await Swal.fire({
+ title: "选择品牌",
+ input: "select",
+ inputOptions,
+ inputPlaceholder: "请选择当前要监听的品牌",
+ confirmButtonText: "确认",
+ allowOutsideClick: false,
+ allowEscapeKey: false,
+ inputValidator: (value) => {
+ if (!value) {
+ return "需要先选择品牌";
+ }
+ return undefined;
+ },
+ text: `aadvid=${runtime.aadvid}`,
+ });
+
+ if (!result.isConfirmed) {
+ throw new Error("未选择品牌,脚本未启动");
+ }
+
+ selected = brandMetadata.find((item) => item.key === result.value) || null;
+ }
+
+ if (!selected) {
+ selected = brandMetadata[0];
+ }
+
+ runtime.pageState.selectedBrandKey = selected.key;
+ savePageState();
+
+ return {
+ aadvid: runtime.aadvid,
+ mainBrandId: selected.brandId,
+ brandName: selected.brandName,
+ level1IndustryId: selected.industryId,
+ industryName: selected.industryName,
+ industryVersion: DEFAULT_CONFIG.industryVersion,
+ currentUserId: String(data?.data?.userInfo?.user?.id || ""),
+ };
+ }
+
+ async function checkQuota() {
+ const url =
+ `https://yuntu.oceanengine.com/yuntu_common/api/v1/evaluation/create/check_task_creation_rate_limit` +
+ `?aadvid=${encodeURIComponent(runtime.brandContext.aadvid)}`;
+
+ const payload = {
+ main_brand_id: runtime.brandContext.mainBrandId,
+ level_1_industry_id: runtime.brandContext.level1IndustryId,
+ };
+
+ const referrer =
+ `https://yuntu.oceanengine.com/yuntu_brand/ecom/evaluation/task_create` +
+ `?business_type=0&industry_version_type=${encodeURIComponent(runtime.brandContext.industryVersion)}` +
+ `&version=0&aadvid=${encodeURIComponent(runtime.brandContext.aadvid)}`;
+
+ const data = await requestJson(url, {
+ method: "POST",
+ pathId: "evaluation_task_create",
+ contentType: "application/json",
+ body: payload,
+ referrer,
+ });
+
+ return {
+ isAbleToCreate: String(data?.data?.is_able_to_create || "") === "1",
+ message: data?.data?.msg || data?.msg || "无响应信息",
+ };
+ }
+
+ async function startExistingTask(taskId) {
+ const url =
+ `https://yuntu.oceanengine.com/yuntu_common/api/v1/ecomTaskList/start_calaulate_evaluation_task` +
+ `?aadvid=${encodeURIComponent(runtime.brandContext.aadvid)}`;
+
+ const payload = {
+ main_brand_id: runtime.brandContext.mainBrandId,
+ level_1_industry_id: runtime.brandContext.level1IndustryId,
+ task_id: String(taskId),
+ };
+
+ const referrer =
+ `https://yuntu.oceanengine.com/yuntu_brand/ecom/evaluation/task_list` +
+ `?aadvid=${encodeURIComponent(runtime.brandContext.aadvid)}`;
+
+ const data = await requestJson(url, {
+ method: "POST",
+ pathId: "evaluation_task_list",
+ contentType: "application/json",
+ body: payload,
+ referrer,
+ });
+
+ return {
+ success: data?.status === 0 && data?.data?.is_success === true,
+ message: data?.msg || "无响应信息",
+ };
+ }
+
+ async function fetchEvaluationTaskList(searchWord = "") {
+ const url =
+ `https://yuntu.oceanengine.com/yuntu_common/api/v1/ecomTaskList/get_evaluation_task_list` +
+ `?aadvid=${encodeURIComponent(runtime.brandContext.aadvid)}`;
+
+ const payload = {
+ main_brand_id: runtime.brandContext.mainBrandId,
+ level_1_industry_id: runtime.brandContext.level1IndustryId,
+ page_num: "1",
+ page_size: "100",
+ order_type: 1,
+ };
+
+ if (searchWord) {
+ payload.search_word = searchWord;
+ }
+
+ const referrer =
+ `https://yuntu.oceanengine.com/yuntu_brand/ecom/evaluation/task_list` +
+ `?aadvid=${encodeURIComponent(runtime.brandContext.aadvid)}`;
+
+ const data = await requestJson(url, {
+ method: "POST",
+ pathId: "evaluation_task_list",
+ contentType: "application/json",
+ body: payload,
+ referrer,
+ });
+
+ const taskList = Array.isArray(data?.data?.task_list) ? data.data.task_list : [];
+ return taskList.map((item) => ({
+ taskId: String(item?.task_id || "").trim(),
+ taskName: String(item?.task_name || "").trim(),
+ taskStatus: Number(item?.task_status),
+ userId: String(item?.user_id || "").trim(),
+ createTime: String(item?.create_time || "").trim(),
+ })).filter((item) => item.taskId);
+ }
+
+ async function syncTaskSource(force = false) {
+ const now = Date.now();
+ if (!force && now - runtime.lastTaskListSyncAt < DEFAULT_CONFIG.taskListRefreshIntervalMs) {
+ return;
+ }
+
+ const mode = runtime.pageState.importMode;
+ let sourceTasks = [];
+
+ if (mode === "id") {
+ const manualIds = dedupeTaskIds(runtime.pageState.manualTaskIds || []);
+ if (manualIds.length === 0) {
+ runtime.latestSourceTasks = [];
+ runtime.pageState.watchTaskIds = [];
+ runtime.lastTaskListStatus = "ID列表为空";
+ runtime.lastTaskListSyncAt = now;
+ savePageState();
+ renderLauncher();
+ return;
+ }
+
+ const selectedIdSet = new Set(manualIds);
+ const taskList = await fetchEvaluationTaskList();
+ sourceTasks = taskList.filter((item) => item.taskStatus === 0 && selectedIdSet.has(item.taskId));
+ } else if (mode === "keyword") {
+ const keyword = String(runtime.pageState.importKeyword || "").trim();
+ if (!keyword) {
+ runtime.latestSourceTasks = [];
+ runtime.pageState.watchTaskIds = [];
+ runtime.lastTaskListStatus = "关键词为空";
+ runtime.lastTaskListSyncAt = now;
+ savePageState();
+ renderLauncher();
+ return;
+ }
+
+ const taskList = await fetchEvaluationTaskList(keyword);
+ sourceTasks = taskList.filter((item) => item.taskStatus === 0);
+ } else {
+ const currentUserId = String(runtime.brandContext.currentUserId || "");
+ const taskList = await fetchEvaluationTaskList();
+ sourceTasks = taskList.filter((item) => item.taskStatus === 0 && item.userId === currentUserId);
+ }
+
+ runtime.latestSourceTasks = sourceTasks;
+ runtime.pageState.watchTaskIds = dedupeTaskIds(sourceTasks.map((item) => item.taskId));
+ runtime.lastTaskListStatus = `${getModeLabel(mode)}匹配 ${sourceTasks.length} 个待提交`;
+ runtime.lastTaskListSyncAt = now;
+ savePageState();
+ renderLauncher();
+ }
+
+ function persistControlDialogValues(root = document) {
+ const modeInput = root.querySelector("#ysw-import-mode");
+ const taskIdsInput = root.querySelector("#ysw-task-ids");
+ const keywordInput = root.querySelector("#ysw-keyword");
+ const webhookInput = root.querySelector("#ysw-webhook-url");
+
+ runtime.pageState.importMode = String(modeInput ? modeInput.value : "id");
+ runtime.pageState.manualTaskIds = parseTaskIds(taskIdsInput ? taskIdsInput.value : "");
+ runtime.pageState.importKeyword = String(keywordInput ? keywordInput.value : "").trim();
+ runtime.pageState.webhookUrl = String(webhookInput ? webhookInput.value : "").trim();
+ savePageState();
+ renderLauncher();
+ }
+
+ function stopPolling(showNotice = true) {
+ if (runtime.timerId !== null) {
+ window.clearInterval(runtime.timerId);
+ runtime.timerId = null;
+ }
+
+ runtime.pageState.enabled = false;
+ savePageState();
+ renderLauncher();
+
+ if (showNotice) {
+ showToast("info", "监听已停止", `aadvid=${runtime.aadvid}`);
+ }
+ }
+
+ function startPolling() {
+ stopPolling(false);
+ runtime.pageState.enabled = true;
+ savePageState();
+ runtime.timerId = window.setInterval(runCycle, DEFAULT_CONFIG.pollIntervalMs);
+ renderLauncher();
+ showToast("success", "开始监听", `${runtime.brandContext.brandName} / aadvid=${runtime.aadvid}`);
+ runCycle();
+ }
+
+ async function runCycle() {
+ if (!runtime.pageState.enabled || runtime.busy) {
+ return;
+ }
+
+ runtime.busy = true;
+ renderLauncher();
+
+ try {
+ await syncTaskSource();
+ } catch (error) {
+ runtime.lastAction = "同步报告列表失败";
+ runtime.lastTaskListStatus = `同步失败: ${error.message}`;
+ await showToast("error", "同步报告列表失败", error.message);
+ runtime.busy = false;
+ renderLauncher();
+ return;
+ }
+
+ const pendingTaskIds = getPendingTaskIds();
+ if (pendingTaskIds.length === 0) {
+ runtime.lastAction = "当前无待提交报告,继续等待";
+ renderLauncher();
+ runtime.busy = false;
+ renderLauncher();
+ return;
+ }
+
+ try {
+ const quota = await checkQuota();
+ runtime.lastQuotaStatus = quota.isAbleToCreate ? "有坑位" : quota.message;
+
+ if (!quota.isAbleToCreate) {
+ runtime.lastAction = "本轮未提交";
+ pushLog(`暂无坑位: ${quota.message}`);
+ return;
+ }
+
+ const nextTaskId = pendingTaskIds[0];
+ runtime.lastAction = `准备启动报告 ${nextTaskId}`;
+
+ const startResult = await startExistingTask(nextTaskId);
+ if (!startResult.success) {
+ runtime.lastAction = `启动失败 ${nextTaskId}`;
+ await showToast("error", "启动失败", `报告 ${nextTaskId} 启动失败: ${startResult.message}`);
+ return;
+ }
+
+ runtime.pageState.submittedTaskIds = dedupeTaskIds([
+ ...runtime.pageState.submittedTaskIds,
+ nextTaskId,
+ ]);
+ savePageState();
+ renderLauncher();
+
+ runtime.lastAction = `已启动报告 ${nextTaskId}`;
+ try {
+ await sendWebhookNotification(nextTaskId);
+ } catch (error) {
+ pushLog(`Webhook 发送失败: ${error.message}`);
+ }
+ runtime.lastTaskListSyncAt = 0;
+ await showToast("success", "启动成功", `报告 ${nextTaskId} 已提交计算`);
+ window.setTimeout(runCycle, DEFAULT_CONFIG.submitCooldownMs);
+ } catch (error) {
+ runtime.lastAction = "执行异常";
+ await showToast("error", "执行异常", error.message);
+ } finally {
+ runtime.busy = false;
+ renderLauncher();
+ }
+ }
+
+ function buildControlHtml() {
+ const pendingCount = getPendingTaskIds().length;
+ const submittedCount = runtime.pageState.submittedTaskIds.length;
+ const logs = runtime.logs.length > 0 ? runtime.logs.slice(0, 8).join("
") : "暂无日志";
+ const currentPendingList =
+ runtime.latestSourceTasks.length > 0
+ ? runtime.latestSourceTasks
+ .slice(0, 20)
+ .map((item) => `