|
|
|
|
@ -30,7 +30,7 @@ import {
|
|
|
|
|
} from "../host/dialog";
|
|
|
|
|
import { emitTaxerTicketContext, listenMainAction } from "../host/events";
|
|
|
|
|
import { log } from "../host/logger";
|
|
|
|
|
import { getSession } from "../host/session";
|
|
|
|
|
import { getSession, setSession } from "../host/session";
|
|
|
|
|
import { startScreenSync, stopScreenSync } from "../host/sync";
|
|
|
|
|
import type { SessionState } from "../host/types";
|
|
|
|
|
import {
|
|
|
|
|
@ -60,6 +60,15 @@ const sessionState = ref<SessionState>({
|
|
|
|
|
queueToken: "",
|
|
|
|
|
refreshToken: "",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 与产品流程一致:账号登录拿到 token + 选窗写入 winUid 之后,才允许等候人数轮询(避免主窗口提前挂载时误启动) */
|
|
|
|
|
const sessionReadyForQueueCount = computed(() => {
|
|
|
|
|
const t = sessionState.value.queueToken;
|
|
|
|
|
const tokenOk = typeof t === "string" && t.trim() !== "";
|
|
|
|
|
const w = sessionState.value.winUid;
|
|
|
|
|
const winOk = typeof w === "number" && Number.isFinite(w) && w > 0;
|
|
|
|
|
return tokenOk && winOk;
|
|
|
|
|
});
|
|
|
|
|
const textColor = ref("#99ccff");
|
|
|
|
|
const iconColor = ref("#dcdfe6");
|
|
|
|
|
const message = ref("欢迎使用紫云呼叫终端");
|
|
|
|
|
@ -72,12 +81,19 @@ let evaluatingCountdownTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
let isRankPollingTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
let isRankPollingBusy = false;
|
|
|
|
|
let queueCountPollingTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
/** 避免 windowUid 未就绪时每 15s 重复打同一条跳过日志 */
|
|
|
|
|
let lastQueueCountSkipInvalidWinLogAt = 0;
|
|
|
|
|
/** 避免未登录(无 queueToken)时每 15s 重复打同一条跳过日志 */
|
|
|
|
|
let lastQueueCountSkipNoTokenLogAt = 0;
|
|
|
|
|
let unlistenWindowFocusChanged: (() => void) | null = null;
|
|
|
|
|
let unlistenMainAction: (() => void) | null = null;
|
|
|
|
|
|
|
|
|
|
const EVALUATING_COUNTDOWN_SEC = 15;
|
|
|
|
|
const pauseReasonOptions = ["午休", "休息一下", "整理资料", "其他"];
|
|
|
|
|
/** 主界面挂载时即视为可轮询;失焦后由 window blur 置为 false 暂停 */
|
|
|
|
|
/**
|
|
|
|
|
* 主窗口是否处于前台焦点(用于高频 isRank 轮询;失焦后暂停以减轻请求)。
|
|
|
|
|
* 等候人数 queue-count 不与该标志绑定,待机时后台仍定时拉取。
|
|
|
|
|
*/
|
|
|
|
|
const isMainWindowActive = ref(true);
|
|
|
|
|
const buttonPanel = ref<"main" | "more" | "pause">("main");
|
|
|
|
|
const isSyncingMainScreen = ref(false);
|
|
|
|
|
@ -294,10 +310,11 @@ function clearIsRankPolling(): void {
|
|
|
|
|
/**
|
|
|
|
|
* 清理等待人数轮询。
|
|
|
|
|
*/
|
|
|
|
|
function clearQueueCountPolling(): void {
|
|
|
|
|
function clearQueueCountPolling(reason: string): void {
|
|
|
|
|
if (queueCountPollingTimer !== null) {
|
|
|
|
|
clearInterval(queueCountPollingTimer);
|
|
|
|
|
queueCountPollingTimer = null;
|
|
|
|
|
void log("info", `queue-count 轮询已停止: ${reason}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -399,14 +416,59 @@ function startIsRankPolling(): void {
|
|
|
|
|
}, 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从磁盘会话刷新主界面缓存的 emp/win/token(登录在其它窗口完成后,焦点回到主窗口时对齐)。
|
|
|
|
|
*/
|
|
|
|
|
async function refreshMainSessionStateFromDisk(): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const session = await getSession();
|
|
|
|
|
sessionState.value = {
|
|
|
|
|
empUid: parseOptionalNumber(session.empUid),
|
|
|
|
|
winUid: parseOptionalNumber(session.winUid),
|
|
|
|
|
queueToken:
|
|
|
|
|
typeof session.queueToken === "string" && session.queueToken.trim() !== ""
|
|
|
|
|
? session.queueToken
|
|
|
|
|
: null,
|
|
|
|
|
refreshToken:
|
|
|
|
|
typeof session.refreshToken === "string" && session.refreshToken.trim() !== ""
|
|
|
|
|
? session.refreshToken
|
|
|
|
|
: null,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await logErr("主窗口从磁盘同步 session 失败", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 轮询当前窗口等候人数。
|
|
|
|
|
*/
|
|
|
|
|
async function pollQueueCountOnce(): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const winUidFromSession = Number(sessionState.value.winUid ?? -1);
|
|
|
|
|
const windowUid = winUidFromSession;
|
|
|
|
|
if (windowUid <= 0) {
|
|
|
|
|
const session = await getSession();
|
|
|
|
|
const token =
|
|
|
|
|
typeof session.queueToken === "string" ? session.queueToken.trim() : "";
|
|
|
|
|
if (!token) {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
if (now - lastQueueCountSkipNoTokenLogAt > 60_000) {
|
|
|
|
|
lastQueueCountSkipNoTokenLogAt = now;
|
|
|
|
|
await log(
|
|
|
|
|
"info",
|
|
|
|
|
"GET /call-terminal/queue-count 跳过: 无有效 queueToken(可能已登出或未登录),不发起请求以免触发 401→refresh→「会话已过期」",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const windowUid = Number(session.winUid ?? -1);
|
|
|
|
|
if (!Number.isFinite(windowUid) || windowUid <= 0) {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
if (now - lastQueueCountSkipInvalidWinLogAt > 60_000) {
|
|
|
|
|
lastQueueCountSkipInvalidWinLogAt = now;
|
|
|
|
|
await log(
|
|
|
|
|
"info",
|
|
|
|
|
`GET /call-terminal/queue-count 跳过: windowUid 无效 (${String(session.winUid ?? "null")}),待 session 就绪后再请求`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -415,6 +477,10 @@ async function pollQueueCountOnce(): Promise<void> {
|
|
|
|
|
typeof res.queueCount === "number"
|
|
|
|
|
? res.queueCount
|
|
|
|
|
: Number(res.count ?? 0);
|
|
|
|
|
await log(
|
|
|
|
|
"info",
|
|
|
|
|
`GET /call-terminal/queue-count: windowUid=${windowUid}, queueCount=${count}`,
|
|
|
|
|
);
|
|
|
|
|
message.value = `欢迎使用紫云呼叫终端,当前窗口等候人数:${count}`;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await logErr("查询 getQueueCount 失败", error);
|
|
|
|
|
@ -425,7 +491,11 @@ async function pollQueueCountOnce(): Promise<void> {
|
|
|
|
|
* 开启等待人数轮询。
|
|
|
|
|
*/
|
|
|
|
|
function startQueueCountPolling(): void {
|
|
|
|
|
clearQueueCountPolling();
|
|
|
|
|
clearQueueCountPolling("重启前清理");
|
|
|
|
|
void log(
|
|
|
|
|
"info",
|
|
|
|
|
`queue-count 轮询已启动: interval=15000ms, callStatus=${callStatus.value}, winUid=${sessionState.value.winUid ?? "null"}, hasQueueToken=${Boolean(sessionState.value.queueToken && String(sessionState.value.queueToken).trim() !== "")}`,
|
|
|
|
|
);
|
|
|
|
|
void pollQueueCountOnce();
|
|
|
|
|
queueCountPollingTimer = setInterval(() => {
|
|
|
|
|
void pollQueueCountOnce();
|
|
|
|
|
@ -433,32 +503,28 @@ function startQueueCountPolling(): void {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
callStatus,
|
|
|
|
|
(status) => {
|
|
|
|
|
[callStatus, sessionReadyForQueueCount],
|
|
|
|
|
([status, ready]) => {
|
|
|
|
|
if (status === "evaluating" && isMainWindowActive.value) {
|
|
|
|
|
startIsRankPolling();
|
|
|
|
|
} else {
|
|
|
|
|
clearIsRankPolling();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status === "idle" && isMainWindowActive.value) {
|
|
|
|
|
if (status === "idle" && ready) {
|
|
|
|
|
startQueueCountPolling();
|
|
|
|
|
} else {
|
|
|
|
|
clearQueueCountPolling();
|
|
|
|
|
clearQueueCountPolling(
|
|
|
|
|
status !== "idle"
|
|
|
|
|
? `callStatus=${status}`
|
|
|
|
|
: "会话未就绪(需 queueToken 且 winUid,选窗并打开主窗口后再轮询)",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
watch(isMainWindowActive, (active) => {
|
|
|
|
|
if (callStatus.value === "idle") {
|
|
|
|
|
if (active) {
|
|
|
|
|
startQueueCountPolling();
|
|
|
|
|
} else {
|
|
|
|
|
clearQueueCountPolling();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (callStatus.value === "evaluating") {
|
|
|
|
|
if (active) {
|
|
|
|
|
startIsRankPolling();
|
|
|
|
|
@ -953,28 +1019,46 @@ async function handleMoreCommand(
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const session = await getSession();
|
|
|
|
|
let winUid = parseOptionalNumber(session.winUid);
|
|
|
|
|
const empUid = parseOptionalNumber(session.empUid);
|
|
|
|
|
const queueToken =
|
|
|
|
|
typeof session.queueToken === "string" && session.queueToken.trim() !== ""
|
|
|
|
|
? session.queueToken
|
|
|
|
|
: null;
|
|
|
|
|
const refreshToken =
|
|
|
|
|
typeof session.refreshToken === "string" && session.refreshToken.trim() !== ""
|
|
|
|
|
? session.refreshToken
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
/** 登录页会把上次选择的窗口写入 config.json;主窗口此前只读 session,未做兜底。 */
|
|
|
|
|
if (winUid === null || winUid <= 0) {
|
|
|
|
|
try {
|
|
|
|
|
const config = await getAllConfig();
|
|
|
|
|
const fromConfig = parseOptionalNumber(config.selected_win_uid);
|
|
|
|
|
if (fromConfig !== null && fromConfig > 0) {
|
|
|
|
|
winUid = fromConfig;
|
|
|
|
|
await setSession({ ...session, winUid: fromConfig });
|
|
|
|
|
await log(
|
|
|
|
|
"info",
|
|
|
|
|
`MainView 挂载: session 无有效 winUid,已从 config.selected_win_uid 补全并写回会话: ${fromConfig}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await logErr("读取 config 以补全 winUid 失败", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sessionState.value = {
|
|
|
|
|
empUid: parseOptionalNumber(session.empUid),
|
|
|
|
|
winUid: parseOptionalNumber(session.winUid),
|
|
|
|
|
queueToken:
|
|
|
|
|
typeof session.queueToken === "string" &&
|
|
|
|
|
session.queueToken.trim() !== ""
|
|
|
|
|
? session.queueToken
|
|
|
|
|
: null,
|
|
|
|
|
refreshToken:
|
|
|
|
|
typeof session.refreshToken === "string" &&
|
|
|
|
|
session.refreshToken.trim() !== ""
|
|
|
|
|
? session.refreshToken
|
|
|
|
|
: null,
|
|
|
|
|
empUid,
|
|
|
|
|
winUid,
|
|
|
|
|
queueToken,
|
|
|
|
|
refreshToken,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await logErr("读取 session 失败", error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// session 在 setup 的 watch 之后才就绪,此处再拉一次等候人数轮询,避免 winUid 仍为占位值时从未请求。
|
|
|
|
|
if (callStatus.value === "idle" && isMainWindowActive.value) {
|
|
|
|
|
startQueueCountPolling();
|
|
|
|
|
}
|
|
|
|
|
// queue-count 由 watch([callStatus, sessionReadyForQueueCount]) 在「idle + 已有 token 且 winUid」后启动,勿在此处强行 start。
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const visible = await appWindow.isVisible();
|
|
|
|
|
@ -984,6 +1068,7 @@ onMounted(async () => {
|
|
|
|
|
}
|
|
|
|
|
const onFocus = () => {
|
|
|
|
|
isMainWindowActive.value = true;
|
|
|
|
|
void refreshMainSessionStateFromDisk();
|
|
|
|
|
};
|
|
|
|
|
const onBlur = () => {
|
|
|
|
|
isMainWindowActive.value = false;
|
|
|
|
|
@ -1021,7 +1106,7 @@ onUnmounted(() => {
|
|
|
|
|
}
|
|
|
|
|
clearEvaluatingCountdown();
|
|
|
|
|
clearIsRankPolling();
|
|
|
|
|
clearQueueCountPolling();
|
|
|
|
|
clearQueueCountPolling("组件卸载");
|
|
|
|
|
if (unlistenWindowFocusChanged) {
|
|
|
|
|
unlistenWindowFocusChanged();
|
|
|
|
|
}
|
|
|
|
|
|