/* ============================================================
* 微信工作台 — 登录 / 在线检测 / 登出模块
* 依赖:core.js(window.Core)、wx.js(window.WxN)
* ============================================================ */
(function (global) {
"use strict";
var Core = global.Core;
var WxN = global.WxN;
var $ = function (sel, root) { return (root || document).querySelector(sel); };
function state() { return WxN.state; }
// 登录地区列表
var REGION_LIST = [
{ id: "110000", name: "北京市" }, { id: "120000", name: "天津市" },
{ id: "130000", name: "河北省" }, { id: "140000", name: "山西省" },
{ id: "210000", name: "辽宁省" }, { id: "220000", name: "吉林省" },
{ id: "230000", name: "黑龙江省" }, { id: "310000", name: "上海市" },
{ id: "320000", name: "江苏省" }, { id: "330000", name: "浙江省" },
{ id: "340000", name: "安徽省" }, { id: "350000", name: "福建省" },
{ id: "360000", name: "江西省" }, { id: "370000", name: "山东省" },
{ id: "410000", name: "河南省" }, { id: "420000", name: "湖北省" },
{ id: "430000", name: "湖南省" }, { id: "440000", name: "广东省" },
{ id: "450000", name: "广西省" }, { id: "460000", name: "海南省" },
{ id: "500000", name: "重庆市" }, { id: "510000", name: "四川省" },
{ id: "520000", name: "贵州省" }, { id: "530000", name: "云南省" },
{ id: "540000", name: "西藏" }, { id: "610000", name: "陕西省" },
{ id: "620000", name: "甘肃省" }, { id: "630000", name: "青海省" },
{ id: "640000", name: "宁夏" }
];
/* ==================== 登录卡片 ==================== */
function renderLoginCard() {
var s = state();
var box = $("#wxLoginCard", s.container);
var app = s.app;
if ((!app || !app.appId) || s.qr) {
var qr = s.qr;
var qrImg = qr && qr.img
? '
'
: '
';
var statusText = "等待获取二维码";
if (qr) {
statusText = qr.status === 0 ? "请使用微信扫码" : qr.status === 1 ? "已扫码,请在手机上确认登录" : "登录成功";
if (qr.expired) statusText = "二维码已过期,请重新获取";
}
box.innerHTML =
'' +
'
微信未登录
' +
'
' + qrImg + '
' + statusText + "
" +
'
" +
"
";
$("#wxQrBtn", box).onclick = getQrCode;
return;
}
box.innerHTML =
'' +
'
' +
(app.headImgUrl
? '
 + ')
'
: '
' + Core.esc(Core.initial(app.nickName || app.wxid)) + "
") +
'
' +
'
' + Core.esc(app.nickName || "微信用户") + "
" +
'
' + Core.esc(app.wxid || app.appId) + "
" +
"
" +
(s.online
? '
在线'
: '
离线') +
"
" +
'
' +
'' +
'' +
"
" +
"
";
$("#wxOnlineBtn", box).onclick = function () { checkOnline().then(function () { renderLoginCard(); }); };
$("#wxLogoutBtn", box).onclick = doLogout;
}
/* ---------- 扫码登录 ---------- */
function showRegionDialog() {
return new Promise(function (resolve) {
var optionsHtml = REGION_LIST.map(function (r) {
return '';
}).join('');
var m = Core.modal({
title: '选择登录地区',
body: '请选择您当前所在的省市地区,用于微信登录接入。
' +
'' +
'
' +
'' +
'· 请选择最近的省市,若没有您的地区可自行采购 socks5 代理 IP
' +
'· 平台代理仅做测试,长用建议自己采购独占代理
' +
'',
footer: ''
});
$('.rg-cancel', m.root).onclick = function () { m.close(); resolve(null); };
$('.rg-ok', m.root).onclick = function () {
var regionId = $('#wx-region-select', m.root).value;
m.close();
resolve(regionId);
};
var geoHint = $('#wx-geo-hint', m.root);
if (navigator.geolocation && geoHint) {
geoHint.textContent = '正在尝试定位…';
navigator.geolocation.getCurrentPosition(
function (pos) {
var lat = pos.coords.latitude;
var lng = pos.coords.longitude;
var guessed = guessRegionByCoords(lat, lng);
if (guessed) {
$('#wx-region-select', m.root).value = guessed;
geoHint.innerHTML = '已自动定位到 ' + Core.esc(REGION_LIST.filter(function(r){return r.id===guessed;})[0].name) + ',可手动修改';
} else {
geoHint.textContent = '定位成功但无法匹配地区,请手动选择';
}
},
function () { geoHint.textContent = '无法获取定位,请手动选择地区'; },
{ timeout: 5000 }
);
}
});
}
function guessRegionByCoords(lat, lng) {
var coords = [
{ id: "110000", lat: 39.9, lng: 116.4 }, { id: "120000", lat: 39.1, lng: 117.2 },
{ id: "310000", lat: 31.2, lng: 121.5 }, { id: "440000", lat: 23.1, lng: 113.3 },
{ id: "330000", lat: 30.3, lng: 120.2 }, { id: "320000", lat: 32.1, lng: 118.8 },
{ id: "510000", lat: 30.6, lng: 104.1 }, { id: "420000", lat: 30.6, lng: 114.3 },
{ id: "430000", lat: 28.2, lng: 112.9 }, { id: "370000", lat: 36.7, lng: 117.0 },
{ id: "410000", lat: 34.8, lng: 113.7 }, { id: "130000", lat: 38.0, lng: 114.5 },
{ id: "210000", lat: 41.8, lng: 123.4 }, { id: "610000", lat: 34.3, lng: 108.9 },
{ id: "340000", lat: 31.8, lng: 117.3 }, { id: "350000", lat: 26.1, lng: 119.3 },
{ id: "360000", lat: 28.7, lng: 115.9 }, { id: "500000", lat: 29.6, lng: 106.5 },
{ id: "530000", lat: 25.0, lng: 102.7 }, { id: "520000", lat: 26.6, lng: 106.7 },
{ id: "450000", lat: 22.8, lng: 108.3 }, { id: "460000", lat: 20.0, lng: 110.3 },
{ id: "140000", lat: 37.9, lng: 112.5 }, { id: "220000", lat: 43.9, lng: 125.3 },
{ id: "230000", lat: 45.8, lng: 126.5 }, { id: "620000", lat: 36.1, lng: 103.8 },
{ id: "630000", lat: 36.6, lng: 101.8 }, { id: "640000", lat: 38.5, lng: 106.3 },
{ id: "540000", lat: 29.6, lng: 91.1 }
];
var minDist = Infinity, matched = null;
for (var i = 0; i < coords.length; i++) {
var d = Math.pow(lat - coords[i].lat, 2) + Math.pow(lng - coords[i].lng, 2);
if (d < minDist) { minDist = d; matched = coords[i].id; }
}
return matched;
}
function getQrCode() {
stopLoginPolling();
showRegionDialog().then(function (regionId) {
if (!regionId) return;
Core.loading(true, "正在获取登录二维码…");
Core.wxApi("login/getLoginQrCode", { appId: "", regionId: regionId, type: "mac" })
.then(function (data) {
Core.loading(false);
if (!data || !data.uuid) { Core.toast("获取二维码失败", "err"); return; }
state().qr = { uuid: data.uuid, img: data.qrImgBase64, status: 0, expired: false, appId: data.appId || "", regionId: regionId };
// 将接口返回的 appId 更新到销售的 wx_device_id
var hashMatch = (location.hash || '').match(/wx-advicer=(\d+)/);
var advicerId = hashMatch ? Number(hashMatch[1]) : 0;
if (data.appId && advicerId) {
Core.api("advicer/wx_device/update", { advicerId: advicerId, wxDeviceId: data.appId })
.then(function () { console.log("[getQrCode] wx_device_id updated: " + data.appId); })
.catch(function (e) { console.warn("[getQrCode] update wx_device_id failed:", e); });
}
renderLoginCard();
startLoginPolling();
})
.catch(function (e) { Core.loading(false); Core.toast("获取二维码失败:" + e.message, "err"); });
});
}
function startLoginPolling() {
stopLoginPolling();
var s = state();
s.loginTimer = setInterval(function () {
s = state();
if (!s || !s.qr) return;
Core.wxApi("login/checkLogin", {
appId: s.qr.appId || "", uuid: s.qr.uuid,
autoSliding: true, proxyIp: "", captchCode: ""
}).then(function (data) {
if (!data) return;
if (s.qr) {
s.qr.status = data.status;
if (typeof data.expiredTime === "number" && data.status !== 2 && data.expiredTime <= 0) {
s.qr.expired = true;
}
}
if (data.status === 2) {
stopLoginPolling();
var app = {
appId: s.qr.appId || "",
wxid: data.loginInfo ? data.loginInfo.wxid : "",
nickName: data.nickName || (data.loginInfo && data.loginInfo.nickName) || "",
headImgUrl: data.headImgUrl || ""
};
s.app = app;
Core.store(Core.KEYS.wxApp, app);
s.qr = null;
Core.toast("微信登录成功:" + (app.nickName || app.wxid), "ok");
renderLoginCard();
checkOnlineLoop();
WxN.loadProfile().then(function () { WxN.loadConversations(); });
WxN.loadContacts();
} else {
renderLoginCard();
}
}).catch(function () { });
}, 10000);
}
function stopLoginPolling() {
var s = state();
if (s && s.loginTimer) { clearInterval(s.loginTimer); s.loginTimer = null; }
}
function checkOnlineLoop() {
WxN.connectWs();
checkOnline();
}
function checkOnline() {
var s = state();
if (!s || !s.app || !s.app.appId) {
console.log("[checkOnline] skipped: state=" + !!s + ", app=" + (s && s.app ? s.app.appId : "null"));
return Promise.resolve(false);
}
console.log("[checkOnline] calling with appId=" + s.app.appId);
return Core.wxApi("login/checkOnline", { appId: s.app.appId })
.then(function (v) {
s.online = v === true;
renderLoginCard();
return s.online;
})
.catch(function () {
state().online = false;
renderLoginCard();
return false;
});
}
function resetToOffline() {
WxN.disconnectWs();
var s = state();
s.app = null;
s.online = false;
s.contacts = { friends: [], chatrooms: [], ghs: [] };
s.conversations = [];
s.convMsgs = {};
s.brief = {};
s.msgs = {};
s.active = null;
Core.store(Core.KEYS.wxApp, null);
if (location.hash) history.replaceState(null, '', location.pathname + location.search);
WxN.clearBriefCache();
renderLoginCard();
WxN.renderContacts();
WxN.renderChat();
}
/* ---------- 在线状态 WebSocket 事件 ---------- */
function handleOnlineStatus(data) {
if (!data) return;
var s = state();
var wasOnline = s.online;
s.online = data.online === true;
if (!s.online && wasOnline) {
Core.toast("微信已离线", "warn");
renderLoginCard();
} else if (s.online && !wasOnline) {
Core.toast("微信已恢复在线", "ok");
renderLoginCard();
} else {
renderLoginCard();
}
}
function doLogout() {
var s = state();
Core.confirm("确定退出微信登录?退出后需重新扫码。").then(function (ok) {
if (!ok) return;
Core.wxApi("login/logout", { appId: state().app.appId })
.then(function () {
Core.toast("已退出微信登录", "ok");
resetToOffline();
})
.catch(function (e) {
Core.wxApi("login/checkOnline", { appId: state().app.appId })
.then(function (online) {
if (online !== true) {
Core.toast("微信已离线,自动回到未登录状态", "warn");
resetToOffline();
} else {
Core.toast("退出登录失败:" + e.message, "err");
}
})
.catch(function () { Core.toast("退出登录失败:" + e.message, "err"); });
});
});
}
/* ---------- 消息回调设置 ---------- */
function openCallbackDialog() {
Core.loading(true, "获取回调配置…");
Core.api("wx/callback/info", {}).then(function (info) {
Core.loading(false);
var suggested = (info && info.callbackUrl) || (location.origin + "/api/v1/advicer/wx/callback");
var m = Core.modal({
title: "设置消息回调",
body:
'' +
'
' +
'' +
"· 需为公网可访问地址,微信协议服务会把客户与销售的消息推送到该地址;
" +
"· 配置成功后消息自动入库,用于客户画像迭代、每晚等级评估与 AI 主动对话;
" +
"· 若服务器部署在反向代理后,请把域名替换为实际公网访问地址。" +
"
",
footer: ''
});
$(".mf-cancel", m.root).onclick = m.close;
$(".mf-save", m.root).onclick = function () {
var url = $("#wcb-url", m.root).value.trim();
if (!url) { Core.toast("请输入回调地址", "err"); return; }
Core.loading(true, "正在设置回调…");
Core.api("wx/callback/set", { appId: (state().app && state().app.appId) || "", callbackUrl: url })
.then(function () { Core.loading(false); m.close(); Core.toast("回调地址已设置并生效", "ok"); })
.catch(function (e) { Core.loading(false); Core.toast("设置失败:" + e.message, "err"); });
};
}).catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
}
/* ==================== 导出 ==================== */
WxN.renderLoginCard = renderLoginCard;
WxN.getQrCode = getQrCode;
WxN.checkOnline = checkOnline;
WxN.checkOnlineLoop = checkOnlineLoop;
WxN.resetToOffline = resetToOffline;
WxN.handleOnlineStatus = handleOnlineStatus;
WxN.doLogout = doLogout;
WxN.openCallbackDialog = openCallbackDialog;
WxN.stopLoginPolling = stopLoginPolling;
})(window);