/* ============================================================
* AI Scheduler 前端核心库(零依赖,全局命名空间 Core)
* - 统一 API 调用(管理后台 JWT / 微信代理 X-finder-TOKEN)
* - 本地存储(登录态、微信 Token)
* - 通用 UI:toast / modal / confirm / loading / 上传
* ============================================================ */
(function (global) {
"use strict";
var ADMIN_BASE = "/api/v1/admin/advice/admin/";
var WX_BASE = ADMIN_BASE + "wx/";
var UPLOAD_URL = "/api/v1/admin/advice/admin/file/upload";
var KEYS = {
token: "as_admin_token",
admin: "as_admin_info",
wxToken: "as_wx_finder_token",
wxApp: "as_wx_app_info",
wxBrief: "as_wx_brief_cache",
project: "as_active_project"
};
/* ---------------- 本地存储 ---------------- */
function store(key, val) {
if (val === undefined) {
try { return JSON.parse(localStorage.getItem(key)); } catch (e) { return null; }
}
if (val === null) { localStorage.removeItem(key); return; }
localStorage.setItem(key, JSON.stringify(val));
}
/* ---------------- 弹窗基础设施 ---------------- */
function el(html) {
var d = document.createElement("div");
d.innerHTML = html.trim();
return d.firstElementChild;
}
function esc(s) {
if (s === undefined || s === null) return "";
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
}
var toastWrap = null;
function toast(msg, type) {
if (!toastWrap) {
toastWrap = el('
');
document.body.appendChild(toastWrap);
}
var icons = { ok: "✓", err: "✕", info: "ℹ" };
var t = el(
'' +
'' + (icons[type] || icons.info) + "" +
"" + esc(msg) + "
"
);
toastWrap.appendChild(t);
setTimeout(function () {
t.style.transition = "opacity .3s ease, transform .3s ease";
t.style.opacity = "0";
t.style.transform = "translateX(12px)";
setTimeout(function () { t.remove(); }, 320);
}, type === "err" ? 4200 : 2600);
}
var loadingMask = null;
function loading(show, text) {
if (show) {
if (loadingMask) { loadingMask.querySelector(".lm-text").textContent = text || "处理中…"; return; }
loadingMask = el(
'' +
esc(text || "处理中…") + "
"
);
document.body.appendChild(loadingMask);
} else if (loadingMask) {
loadingMask.remove();
loadingMask = null;
}
}
/**
* modal({ title, body, footer, width(''|'wide'|'xwide'), onMount(el) })
* 返回 { close, root }
*/
function modal(opts) {
opts = opts || {};
var widthCls = opts.width ? " " + opts.width : "";
var mask = el(
'' +
'
' + esc(opts.title || "") + '' +
'✕
' +
'
' + (opts.body || "") + "
" +
(opts.footer === null ? "" : '") +
"
"
);
document.body.appendChild(mask);
var api = {
root: mask.querySelector(".modal"),
body: mask.querySelector(".modal-body"),
close: function () { mask.remove(); }
};
mask.querySelector(".x").onclick = api.close;
mask.addEventListener("mousedown", function (e) {
if (e.target === mask) {
// 不关闭:日期/时间选择器弹出层交互时 document.activeElement 仍为 date input
var ae = document.activeElement;
if (ae && (ae.type === "date" || ae.type === "datetime-local")) return;
api.close();
}
});
if (opts.onMount) opts.onMount(api);
return api;
}
function confirm(msg, title) {
return new Promise(function (resolve) {
var m = modal({
title: title || "操作确认",
body: '' + esc(msg) + "
",
footer: ''
});
m.root.querySelector(".cm-no").onclick = function () { m.close(); resolve(false); };
m.root.querySelector(".cm-yes").onclick = function () { m.close(); resolve(true); };
m.root.closest(".modal-mask").querySelector(".x").onclick = function () { m.close(); resolve(false); };
});
}
/* ---------------- 请求封装 ---------------- */
function buildError(msg, code) {
var e = new Error(msg || "请求失败");
e.code = code;
return e;
}
function parseBody(res) {
return res.text().then(function (txt) {
if (!txt) return {};
try { return JSON.parse(txt); } catch (e) { return { message: txt }; }
});
}
/** 管理后台接口:POST JSON,自动携带 JWT,解包 {code,message,data} */
function api(path, body) {
var headers = { "Content-Type": "application/json" };
var token = store(KEYS.token);
if (token) headers["Authorization"] = "Bearer " + token;
return fetch(ADMIN_BASE + path, {
method: "POST", headers: headers, body: JSON.stringify(body || {})
}).then(function (res) {
return parseBody(res).then(function (json) {
// 未登录/鉴权失败
if (res.status === 401 || (json && (json.code === 401 || json.code === 1004))) {
store(KEYS.token, null);
store(KEYS.admin, null);
if (typeof Core.onAuthFail === "function") Core.onAuthFail();
throw buildError(json.message || "登录已过期,请重新登录", 401);
}
if (json && typeof json.code === "number" && json.code !== 0 && json.code !== 200) {
throw buildError(json.message || "请求失败", json.code);
}
return json ? json.data : null;
});
});
}
/**
* 微信代理接口(包装转发,标准 {code,message,data} 格式)
* wxApi('login/getLoginQrCode', {appId:''}) → data 解包后返回
*/
function wxApi(path, body, tokenOverride) {
var payload = {};
for (var k in (body || {})) payload[k] = body[k];
// 自动注入 projectId(后端用于查询项目级 wx_token)
if (!payload.projectId) {
var proj = store(KEYS.project);
if (proj && proj.projectId) payload.projectId = proj.projectId;
}
var headers = { "Content-Type": "application/json" };
var token = store(KEYS.token);
if (token) headers["Authorization"] = "Bearer " + token;
return fetch(WX_BASE + path, {
method: "POST", headers: headers, body: JSON.stringify(payload)
}).then(function (res) {
return parseBody(res).then(function (json) {
if (res.status === 401 || (json && (json.code === 401 || json.code === 1004))) {
store(KEYS.token, null);
store(KEYS.admin, null);
if (typeof Core.onAuthFail === "function") Core.onAuthFail();
throw buildError(json.message || "登录已过期,请重新登录", 401);
}
if (json && typeof json.code === "number" && json.code !== 0 && json.code !== 200) {
throw buildError(json.message || "请求失败", json.code);
}
return json ? json.data : null;
});
});
}
/** 上传文件(multipart)→ {path,url,name,size} */
function upload(file, onProgress) {
var fd = new FormData();
fd.append("file", file);
var token = store(KEYS.token);
return fetch(UPLOAD_URL, {
method: "POST",
headers: token ? { "Authorization": "Bearer " + token } : {},
body: fd
}).then(function (res) {
return parseBody(res).then(function (json) {
if (json && typeof json.code === "number" && json.code !== 0 && json.code !== 200) {
throw buildError(json.message || "上传失败", json.code);
}
return json ? json.data : null;
});
});
}
/* ---------------- 工具函数 ---------------- */
function fmtTime(t, withSec) {
if (!t) return "-";
var d = new Date(t);
if (isNaN(d.getTime())) {
// Go 时间字符串如 2026-09-22T10:00:00+08:00 兼容
d = new Date(String(t).replace(/-/g, "/").replace("T", " ").split("+")[0]);
if (isNaN(d.getTime())) return String(t);
}
var p = function (n) { return n < 10 ? "0" + n : "" + n; };
var s = d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate()) + " " + p(d.getHours()) + ":" + p(d.getMinutes());
if (withSec) s += ":" + p(d.getSeconds());
return s;
}
function nowTime() { return fmtTime(new Date(), true); }
function fmtSize(b) {
if (!b && b !== 0) return "-";
if (b < 1024) return b + " B";
if (b < 1048576) return (b / 1024).toFixed(1) + " KB";
return (b / 1048576).toFixed(2) + " MB";
}
function debounce(fn, ms) {
var t;
return function () {
var args = arguments, self = this;
clearTimeout(t);
t = setTimeout(function () { fn.apply(self, args); }, ms || 300);
};
}
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text).then(function () { toast("已复制到剪贴板", "ok"); });
}
var ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
ta.remove();
toast("已复制到剪贴板", "ok");
return Promise.resolve();
}
function qs(name) {
var m = new URLSearchParams(location.search);
return m.get(name);
}
/** JSON 美化输出(解析失败返回原文) */
function prettyJson(v) {
try {
if (typeof v === "string") return JSON.stringify(JSON.parse(v), null, 2);
return JSON.stringify(v, null, 2);
} catch (e) { return typeof v === "string" ? v : JSON.stringify(v); }
}
/** 安全解析 JSON 字符串(失败抛出带说明错误) */
function parseJsonField(text, label) {
if (!text || !String(text).trim()) return null;
try { return JSON.parse(text); }
catch (e) { throw buildError((label || "JSON") + " 格式错误:" + e.message); }
}
/** 首字符头像(中文取首字) */
function initial(name) {
if (!name) return "?";
return String(name).trim().charAt(0).toUpperCase();
}
function logout() {
store(KEYS.token, null);
store(KEYS.admin, null);
location.reload();
}
/* ---------------- 多气泡工具 ---------------- */
// splitBubbles 将文本按换行拆分为多条消息(模拟真人连发多条短消息)
function splitBubbles(text) {
if (!text) return [];
return String(text).split(/\r?\n+/).map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
}
// sleep 异步等待(发送节奏控制)
function sleep(ms) {
return new Promise(function (resolve) { setTimeout(resolve, ms); });
}
/* ---------------- 导出 ---------------- */
var Core = {
ADMIN_BASE: ADMIN_BASE,
WX_BASE: WX_BASE,
KEYS: KEYS,
store: store,
el: el,
esc: esc,
toast: toast,
loading: loading,
modal: modal,
confirm: confirm,
api: api,
wxApi: wxApi,
upload: upload,
fmtTime: fmtTime,
nowTime: nowTime,
fmtSize: fmtSize,
debounce: debounce,
splitBubbles: splitBubbles,
sleep: sleep,
copyText: copyText,
qs: qs,
prettyJson: prettyJson,
parseJsonField: parseJsonField,
initial: initial,
logout: logout,
onAuthFail: null
};
global.Core = Core;
})(window);