|
|
|
|
@ -28,7 +28,13 @@ import {
|
|
|
|
|
showErrorNativeWithLog,
|
|
|
|
|
showWarningNative,
|
|
|
|
|
} from "../host/dialog";
|
|
|
|
|
import { emitTaxerTicketContext, listenMainAction } from "../host/events";
|
|
|
|
|
import {
|
|
|
|
|
emitTaxerNsrClear,
|
|
|
|
|
emitTaxerTicketContext,
|
|
|
|
|
emitTaxerTicketStart,
|
|
|
|
|
listenMainAction,
|
|
|
|
|
} from "../host/events";
|
|
|
|
|
import { buildTaxerContextFromActionData } from "../utils/taxerTicketContext";
|
|
|
|
|
import { log } from "../host/logger";
|
|
|
|
|
import { getSession, setSession } from "../host/session";
|
|
|
|
|
import { startScreenSync, stopScreenSync } from "../host/sync";
|
|
|
|
|
@ -59,6 +65,10 @@ const sessionState = ref<SessionState>({
|
|
|
|
|
winUid: -1,
|
|
|
|
|
queueToken: "",
|
|
|
|
|
refreshToken: "",
|
|
|
|
|
autoCallEnabled: null,
|
|
|
|
|
autoCallWaitSeconds: null,
|
|
|
|
|
autoStartEnabled: null,
|
|
|
|
|
autoStartWaitSeconds: null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 与产品流程一致:账号登录拿到 token + 选窗写入 winUid 之后,才允许等候人数轮询(避免主窗口提前挂载时误启动) */
|
|
|
|
|
@ -87,18 +97,429 @@ let lastQueueCountSkipInvalidWinLogAt = 0;
|
|
|
|
|
let lastQueueCountSkipNoTokenLogAt = 0;
|
|
|
|
|
let unlistenWindowFocusChanged: (() => void) | null = null;
|
|
|
|
|
let unlistenMainAction: (() => void) | null = null;
|
|
|
|
|
/** 自动叫号倒计时(caller-init 的 autoCallWaitSeconds) */
|
|
|
|
|
let autoCallCountdownTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
/** 计时器内部剩余秒数(暂停时保留,恢复后续计) */
|
|
|
|
|
let autoCallTickLeft = 0;
|
|
|
|
|
const autoCallCountdownLeft = ref(0);
|
|
|
|
|
/** 待机轮询得到的等候人数,倒计时期间用于 log-span 展示 */
|
|
|
|
|
const idleQueueCount = ref<number | null>(null);
|
|
|
|
|
/** 自动开始倒计时(caller-init 的 autoStartWaitSeconds,calling 状态) */
|
|
|
|
|
let autoStartCountdownTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
let autoStartTickLeft = 0;
|
|
|
|
|
const autoStartCountdownLeft = ref(0);
|
|
|
|
|
/** 当前呼叫票号展示(自动开始倒计时文案) */
|
|
|
|
|
const callingTicketNoLabel = ref("");
|
|
|
|
|
|
|
|
|
|
const EVALUATING_COUNTDOWN_SEC = 15;
|
|
|
|
|
const DEFAULT_AUTO_CALL_WAIT_SECONDS = 5;
|
|
|
|
|
const DEFAULT_AUTO_START_WAIT_SECONDS = 50;
|
|
|
|
|
const pauseReasonOptions = ["午休", "休息一下", "整理资料", "其他"];
|
|
|
|
|
/**
|
|
|
|
|
* 主窗口是否处于前台焦点(用于高频 isRank 轮询;失焦后暂停以减轻请求)。
|
|
|
|
|
* 等候人数 queue-count 不与该标志绑定,待机时后台仍定时拉取。
|
|
|
|
|
* 主窗口是否处于前台焦点(用于评价 isRank 轮询;失焦后暂停以减轻请求)。
|
|
|
|
|
* 自动叫号不依赖该标志,仅在看得到主窗口未最小化时倒计时。
|
|
|
|
|
*/
|
|
|
|
|
const isMainWindowActive = ref(true);
|
|
|
|
|
/** 主窗口是否已最小化(自动叫号仅在非最小化时运行) */
|
|
|
|
|
const isMainWindowMinimized = ref(false);
|
|
|
|
|
const buttonPanel = ref<"main" | "more" | "pause">("main");
|
|
|
|
|
const isSyncingMainScreen = ref(false);
|
|
|
|
|
const isActionPending = ref(false);
|
|
|
|
|
|
|
|
|
|
/** caller-init 缓存:为 true 时待机且窗口激活则自动倒计时并呼叫 */
|
|
|
|
|
const autoCallEnabled = computed(
|
|
|
|
|
() => sessionState.value.autoCallEnabled === true,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function resolveAutoCallWaitSeconds(): number {
|
|
|
|
|
const raw = sessionState.value.autoCallWaitSeconds;
|
|
|
|
|
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) {
|
|
|
|
|
return Math.floor(raw);
|
|
|
|
|
}
|
|
|
|
|
return DEFAULT_AUTO_CALL_WAIT_SECONDS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshMainWindowMinimizedState(): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const minimized = await appWindow.isMinimized();
|
|
|
|
|
if (isMainWindowMinimized.value === minimized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
isMainWindowMinimized.value = minimized;
|
|
|
|
|
void log(
|
|
|
|
|
"info",
|
|
|
|
|
`主窗口最小化状态: ${minimized ? "已最小化" : "未最小化"}`,
|
|
|
|
|
);
|
|
|
|
|
syncAutoCallCountdown();
|
|
|
|
|
syncAutoStartCountdown();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const detail = error instanceof Error ? error.message : String(error);
|
|
|
|
|
await log("warn", `读取主窗口最小化状态失败: ${detail}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** caller-init 缓存:为 true 且在 calling 时自动倒计时并开始办理 */
|
|
|
|
|
const autoStartEnabled = computed(
|
|
|
|
|
() => sessionState.value.autoStartEnabled === true,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function resolveAutoStartWaitSeconds(): number {
|
|
|
|
|
const raw = sessionState.value.autoStartWaitSeconds;
|
|
|
|
|
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) {
|
|
|
|
|
return Math.floor(raw);
|
|
|
|
|
}
|
|
|
|
|
return DEFAULT_AUTO_START_WAIT_SECONDS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function shouldRunAutoStartCountdown(): boolean {
|
|
|
|
|
return (
|
|
|
|
|
autoStartEnabled.value &&
|
|
|
|
|
!isMainWindowMinimized.value &&
|
|
|
|
|
callStatus.value === "calling" &&
|
|
|
|
|
sessionReadyForQueueCount.value &&
|
|
|
|
|
!isActionPending.value
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isAutoStartCountdownRunning(): boolean {
|
|
|
|
|
return autoStartCountdownTimer !== null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyAutoStartCountdownMessage(left: number): void {
|
|
|
|
|
if (left <= 0 || callStatus.value === "paused") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const ticket = callingTicketNoLabel.value.trim() || "—";
|
|
|
|
|
message.value = `正在呼叫:${ticket},自动开始 ${left} 秒后办理`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyPausedLogMessage(pauseReason: string): void {
|
|
|
|
|
message.value = `窗口已暂停,原因:${pauseReason}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pauseAutoStartCountdownTimerOnly(): void {
|
|
|
|
|
if (autoStartCountdownTimer !== null) {
|
|
|
|
|
clearInterval(autoStartCountdownTimer);
|
|
|
|
|
autoStartCountdownTimer = null;
|
|
|
|
|
}
|
|
|
|
|
if (autoStartTickLeft > 0) {
|
|
|
|
|
autoStartCountdownLeft.value = autoStartTickLeft;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pauseAutoStartCountdown(reason: string): void {
|
|
|
|
|
const hadTimer = isAutoStartCountdownRunning();
|
|
|
|
|
const remaining =
|
|
|
|
|
autoStartTickLeft > 0 ? autoStartTickLeft : autoStartCountdownLeft.value;
|
|
|
|
|
pauseAutoStartCountdownTimerOnly();
|
|
|
|
|
if (remaining > 0) {
|
|
|
|
|
autoStartTickLeft = remaining;
|
|
|
|
|
autoStartCountdownLeft.value = remaining;
|
|
|
|
|
if (callStatus.value !== "paused") {
|
|
|
|
|
applyAutoStartCountdownMessage(remaining);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (hadTimer || remaining > 0) {
|
|
|
|
|
void log("info", `auto-start 倒计时已暂停: ${reason},剩余 ${remaining}s`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopAutoStartCountdown(reason: string): void {
|
|
|
|
|
pauseAutoStartCountdownTimerOnly();
|
|
|
|
|
autoStartTickLeft = 0;
|
|
|
|
|
autoStartCountdownLeft.value = 0;
|
|
|
|
|
void log("info", `auto-start 倒计时已清除: ${reason}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runAutoStartCountdownDisplayTick(): void {
|
|
|
|
|
if (!shouldRunAutoStartCountdown()) {
|
|
|
|
|
pauseAutoStartCountdown("展示 tick 时条件不满足");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
autoStartCountdownLeft.value = autoStartTickLeft;
|
|
|
|
|
applyAutoStartCountdownMessage(autoStartTickLeft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function beginAutoStartCountdownInterval(): void {
|
|
|
|
|
if (autoStartCountdownTimer !== null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
autoStartCountdownTimer = setInterval(() => {
|
|
|
|
|
autoStartTickLeft -= 1;
|
|
|
|
|
if (autoStartTickLeft <= 0) {
|
|
|
|
|
pauseAutoStartCountdownTimerOnly();
|
|
|
|
|
autoStartCountdownLeft.value = 0;
|
|
|
|
|
autoStartTickLeft = 0;
|
|
|
|
|
void log("info", "auto-start 倒计时结束,触发开始办理");
|
|
|
|
|
void startAction({ triggeredByAutoStart: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
runAutoStartCountdownDisplayTick();
|
|
|
|
|
}, 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 从配置的满秒重新计时(仅「非 calling → calling」时调用) */
|
|
|
|
|
function resetAndStartAutoStartCountdown(): void {
|
|
|
|
|
if (!autoStartEnabled.value || callStatus.value !== "calling") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pauseAutoStartCountdownTimerOnly();
|
|
|
|
|
autoStartTickLeft = resolveAutoStartWaitSeconds();
|
|
|
|
|
void log(
|
|
|
|
|
"info",
|
|
|
|
|
`auto-start 倒计时重新开始: ${autoStartTickLeft}s (进入呼叫中)`,
|
|
|
|
|
);
|
|
|
|
|
if (!shouldRunAutoStartCountdown()) {
|
|
|
|
|
autoStartCountdownLeft.value = autoStartTickLeft;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
runAutoStartCountdownDisplayTick();
|
|
|
|
|
beginAutoStartCountdownInterval();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resumeAutoStartCountdown(): void {
|
|
|
|
|
if (!shouldRunAutoStartCountdown()) {
|
|
|
|
|
pauseAutoStartCountdown(
|
|
|
|
|
!autoStartEnabled.value
|
|
|
|
|
? "未开启自动开始"
|
|
|
|
|
: isMainWindowMinimized.value
|
|
|
|
|
? "主窗口已最小化"
|
|
|
|
|
: callStatus.value !== "calling"
|
|
|
|
|
? `callStatus=${callStatus.value}`
|
|
|
|
|
: !sessionReadyForQueueCount.value
|
|
|
|
|
? "会话未就绪"
|
|
|
|
|
: "操作进行中",
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (isAutoStartCountdownRunning()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (autoStartTickLeft <= 0 && autoStartCountdownLeft.value > 0) {
|
|
|
|
|
autoStartTickLeft = autoStartCountdownLeft.value;
|
|
|
|
|
}
|
|
|
|
|
if (autoStartTickLeft <= 0) {
|
|
|
|
|
resetAndStartAutoStartCountdown();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
void log("info", `auto-start 倒计时继续: 剩余 ${autoStartTickLeft}s`);
|
|
|
|
|
runAutoStartCountdownDisplayTick();
|
|
|
|
|
beginAutoStartCountdownInterval();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function syncAutoStartCountdown(): void {
|
|
|
|
|
if (callStatus.value === "paused") {
|
|
|
|
|
pauseAutoStartCountdownTimerOnly();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (shouldRunAutoStartCountdown()) {
|
|
|
|
|
resumeAutoStartCountdown();
|
|
|
|
|
} else if (callStatus.value !== "calling") {
|
|
|
|
|
pauseAutoStartCountdown(`callStatus=${callStatus.value}`);
|
|
|
|
|
} else {
|
|
|
|
|
pauseAutoStartCountdown(
|
|
|
|
|
!autoStartEnabled.value
|
|
|
|
|
? "未开启自动开始"
|
|
|
|
|
: isMainWindowMinimized.value
|
|
|
|
|
? "主窗口已最小化"
|
|
|
|
|
: !sessionReadyForQueueCount.value
|
|
|
|
|
? "会话未就绪"
|
|
|
|
|
: "操作进行中",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleAutoStartOnCallStatusChange(
|
|
|
|
|
newStatus: string,
|
|
|
|
|
oldStatus: string | undefined,
|
|
|
|
|
): void {
|
|
|
|
|
if (
|
|
|
|
|
newStatus === "calling" &&
|
|
|
|
|
oldStatus !== undefined &&
|
|
|
|
|
oldStatus !== "calling"
|
|
|
|
|
) {
|
|
|
|
|
if (autoStartEnabled.value) {
|
|
|
|
|
resetAndStartAutoStartCountdown();
|
|
|
|
|
} else {
|
|
|
|
|
stopAutoStartCountdown("进入呼叫中但未开启自动开始");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (oldStatus === "calling" && newStatus !== "calling") {
|
|
|
|
|
pauseAutoStartCountdown(`离开呼叫中 -> ${newStatus}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function shouldRunAutoCallCountdown(): boolean {
|
|
|
|
|
return (
|
|
|
|
|
autoCallEnabled.value &&
|
|
|
|
|
!isMainWindowMinimized.value &&
|
|
|
|
|
callStatus.value === "idle" &&
|
|
|
|
|
sessionReadyForQueueCount.value &&
|
|
|
|
|
!isActionPending.value
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isAutoCallCountdownRunning(): boolean {
|
|
|
|
|
return autoCallCountdownTimer !== null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyAutoCallCountdownMessage(left: number): void {
|
|
|
|
|
if (left <= 0 || callStatus.value === "paused") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const countLabel =
|
|
|
|
|
idleQueueCount.value === null ? "—" : String(idleQueueCount.value);
|
|
|
|
|
message.value = `当前窗口等候人数:${countLabel},自动叫号 ${left} 秒后呼叫`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pauseAutoCallCountdownTimerOnly(): void {
|
|
|
|
|
if (autoCallCountdownTimer !== null) {
|
|
|
|
|
clearInterval(autoCallCountdownTimer);
|
|
|
|
|
autoCallCountdownTimer = null;
|
|
|
|
|
}
|
|
|
|
|
if (autoCallTickLeft > 0) {
|
|
|
|
|
autoCallCountdownLeft.value = autoCallTickLeft;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pauseAutoCallCountdown(reason: string): void {
|
|
|
|
|
const hadTimer = isAutoCallCountdownRunning();
|
|
|
|
|
const remaining = autoCallTickLeft > 0 ? autoCallTickLeft : autoCallCountdownLeft.value;
|
|
|
|
|
pauseAutoCallCountdownTimerOnly();
|
|
|
|
|
if (remaining > 0) {
|
|
|
|
|
autoCallTickLeft = remaining;
|
|
|
|
|
autoCallCountdownLeft.value = remaining;
|
|
|
|
|
if (callStatus.value !== "paused") {
|
|
|
|
|
applyAutoCallCountdownMessage(remaining);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (hadTimer || remaining > 0) {
|
|
|
|
|
void log("info", `auto-call 倒计时已暂停: ${reason},剩余 ${remaining}s`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopAutoCallCountdown(reason: string): void {
|
|
|
|
|
pauseAutoCallCountdownTimerOnly();
|
|
|
|
|
autoCallTickLeft = 0;
|
|
|
|
|
autoCallCountdownLeft.value = 0;
|
|
|
|
|
void log("info", `auto-call 倒计时已清除: ${reason}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runAutoCallCountdownDisplayTick(): void {
|
|
|
|
|
if (!shouldRunAutoCallCountdown()) {
|
|
|
|
|
pauseAutoCallCountdown("展示 tick 时条件不满足");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
autoCallCountdownLeft.value = autoCallTickLeft;
|
|
|
|
|
applyAutoCallCountdownMessage(autoCallTickLeft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function beginAutoCallCountdownInterval(): void {
|
|
|
|
|
if (autoCallCountdownTimer !== null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
autoCallCountdownTimer = setInterval(() => {
|
|
|
|
|
autoCallTickLeft -= 1;
|
|
|
|
|
if (autoCallTickLeft <= 0) {
|
|
|
|
|
pauseAutoCallCountdownTimerOnly();
|
|
|
|
|
autoCallCountdownLeft.value = 0;
|
|
|
|
|
autoCallTickLeft = 0;
|
|
|
|
|
void log("info", "auto-call 倒计时结束,触发呼叫");
|
|
|
|
|
void callAction({ triggeredByAutoCall: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
runAutoCallCountdownDisplayTick();
|
|
|
|
|
}, 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 从配置的满秒重新计时(仅「非 idle → idle」时调用) */
|
|
|
|
|
function resetAndStartAutoCallCountdown(): void {
|
|
|
|
|
if (!autoCallEnabled.value || callStatus.value !== "idle") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pauseAutoCallCountdownTimerOnly();
|
|
|
|
|
autoCallTickLeft = resolveAutoCallWaitSeconds();
|
|
|
|
|
void log(
|
|
|
|
|
"info",
|
|
|
|
|
`auto-call 倒计时重新开始: ${autoCallTickLeft}s (切换回待机)`,
|
|
|
|
|
);
|
|
|
|
|
void pollQueueCountOnce();
|
|
|
|
|
runAutoCallCountdownDisplayTick();
|
|
|
|
|
beginAutoCallCountdownInterval();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 在已有剩余秒数上继续(最小化恢复、操作结束等) */
|
|
|
|
|
function resumeAutoCallCountdown(): void {
|
|
|
|
|
if (!shouldRunAutoCallCountdown()) {
|
|
|
|
|
pauseAutoCallCountdown(
|
|
|
|
|
!autoCallEnabled.value
|
|
|
|
|
? "未开启自动叫号"
|
|
|
|
|
: isMainWindowMinimized.value
|
|
|
|
|
? "主窗口已最小化"
|
|
|
|
|
: callStatus.value !== "idle"
|
|
|
|
|
? `callStatus=${callStatus.value}`
|
|
|
|
|
: !sessionReadyForQueueCount.value
|
|
|
|
|
? "会话未就绪"
|
|
|
|
|
: "操作进行中",
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (isAutoCallCountdownRunning()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (autoCallTickLeft <= 0 && autoCallCountdownLeft.value > 0) {
|
|
|
|
|
autoCallTickLeft = autoCallCountdownLeft.value;
|
|
|
|
|
}
|
|
|
|
|
if (autoCallTickLeft <= 0) {
|
|
|
|
|
resetAndStartAutoCallCountdown();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
void log("info", `auto-call 倒计时继续: 剩余 ${autoCallTickLeft}s`);
|
|
|
|
|
void pollQueueCountOnce();
|
|
|
|
|
runAutoCallCountdownDisplayTick();
|
|
|
|
|
beginAutoCallCountdownInterval();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function syncAutoCallCountdown(): void {
|
|
|
|
|
if (callStatus.value === "paused") {
|
|
|
|
|
pauseAutoCallCountdownTimerOnly();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (shouldRunAutoCallCountdown()) {
|
|
|
|
|
resumeAutoCallCountdown();
|
|
|
|
|
} else if (callStatus.value !== "idle") {
|
|
|
|
|
pauseAutoCallCountdown(`callStatus=${callStatus.value}`);
|
|
|
|
|
} else {
|
|
|
|
|
pauseAutoCallCountdown(
|
|
|
|
|
!autoCallEnabled.value
|
|
|
|
|
? "未开启自动叫号"
|
|
|
|
|
: isMainWindowMinimized.value
|
|
|
|
|
? "主窗口已最小化"
|
|
|
|
|
: !sessionReadyForQueueCount.value
|
|
|
|
|
? "会话未就绪"
|
|
|
|
|
: "操作进行中",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleAutoCallOnCallStatusChange(
|
|
|
|
|
newStatus: string,
|
|
|
|
|
oldStatus: string | undefined,
|
|
|
|
|
): void {
|
|
|
|
|
if (newStatus === "idle" && oldStatus !== undefined && oldStatus !== "idle") {
|
|
|
|
|
if (autoCallEnabled.value) {
|
|
|
|
|
resetAndStartAutoCallCountdown();
|
|
|
|
|
} else {
|
|
|
|
|
stopAutoCallCountdown("切换回待机但未开启自动叫号");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (oldStatus === "idle" && newStatus !== "idle") {
|
|
|
|
|
pauseAutoCallCountdown(`离开待机 -> ${newStatus}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 记录错误日志。
|
|
|
|
|
*/
|
|
|
|
|
@ -213,6 +634,19 @@ function getActionSfzhm(res: unknown): string {
|
|
|
|
|
return String(data.sfzhm ?? data.idCard ?? "").trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildTaxerContextFromAction(res: unknown) {
|
|
|
|
|
const data = getActionData(res) as Record<string, unknown>;
|
|
|
|
|
return buildTaxerContextFromActionData(
|
|
|
|
|
data,
|
|
|
|
|
getActionTicketNo(res),
|
|
|
|
|
getActionSfzhm(res),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function emitTaxerContextFromAction(res: unknown): Promise<void> {
|
|
|
|
|
await emitTaxerTicketContext(buildTaxerContextFromAction(res));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 提取票据 UID。
|
|
|
|
|
*/
|
|
|
|
|
@ -423,6 +857,8 @@ async function refreshMainSessionStateFromDisk(): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const session = await getSession();
|
|
|
|
|
sessionState.value = {
|
|
|
|
|
...sessionState.value,
|
|
|
|
|
...session,
|
|
|
|
|
empUid: parseOptionalNumber(session.empUid),
|
|
|
|
|
winUid: parseOptionalNumber(session.winUid),
|
|
|
|
|
queueToken:
|
|
|
|
|
@ -443,6 +879,10 @@ async function refreshMainSessionStateFromDisk(): Promise<void> {
|
|
|
|
|
* 轮询当前窗口等候人数。
|
|
|
|
|
*/
|
|
|
|
|
async function pollQueueCountOnce(): Promise<void> {
|
|
|
|
|
const autoCallUiActive =
|
|
|
|
|
autoCallCountdownLeft.value > 0 || isAutoCallCountdownRunning();
|
|
|
|
|
const autoStartUiActive =
|
|
|
|
|
autoStartCountdownLeft.value > 0 || isAutoStartCountdownRunning();
|
|
|
|
|
try {
|
|
|
|
|
const session = await getSession();
|
|
|
|
|
const token =
|
|
|
|
|
@ -481,6 +921,23 @@ async function pollQueueCountOnce(): Promise<void> {
|
|
|
|
|
"info",
|
|
|
|
|
`GET /call-terminal/queue-count: windowUid=${windowUid}, queueCount=${count}`,
|
|
|
|
|
);
|
|
|
|
|
idleQueueCount.value = count;
|
|
|
|
|
if (autoCallUiActive) {
|
|
|
|
|
const left =
|
|
|
|
|
autoCallTickLeft > 0 ? autoCallTickLeft : autoCallCountdownLeft.value;
|
|
|
|
|
if (left > 0) {
|
|
|
|
|
applyAutoCallCountdownMessage(left);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (autoStartUiActive) {
|
|
|
|
|
const left =
|
|
|
|
|
autoStartTickLeft > 0 ? autoStartTickLeft : autoStartCountdownLeft.value;
|
|
|
|
|
if (left > 0) {
|
|
|
|
|
applyAutoStartCountdownMessage(left);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
message.value = `欢迎使用紫云呼叫终端,当前窗口等候人数:${count}`;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await logErr("查询 getQueueCount 失败", error);
|
|
|
|
|
@ -534,6 +991,56 @@ watch(isMainWindowActive, (active) => {
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch(isMainWindowMinimized, () => {
|
|
|
|
|
syncAutoCallCountdown();
|
|
|
|
|
syncAutoStartCountdown();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch(callStatus, (newStatus, oldStatus) => {
|
|
|
|
|
handleAutoCallOnCallStatusChange(newStatus, oldStatus);
|
|
|
|
|
handleAutoStartOnCallStatusChange(newStatus, oldStatus);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch([autoCallEnabled, sessionReadyForQueueCount], () => {
|
|
|
|
|
if (callStatus.value !== "idle") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (autoCallEnabled.value && sessionReadyForQueueCount.value) {
|
|
|
|
|
if (autoCallTickLeft <= 0 && autoCallCountdownLeft.value <= 0) {
|
|
|
|
|
resetAndStartAutoCallCountdown();
|
|
|
|
|
} else {
|
|
|
|
|
syncAutoCallCountdown();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pauseAutoCallCountdown("自动叫号未启用或会话未就绪");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch([autoStartEnabled, sessionReadyForQueueCount], () => {
|
|
|
|
|
if (callStatus.value !== "calling") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (autoStartEnabled.value && sessionReadyForQueueCount.value) {
|
|
|
|
|
if (autoStartTickLeft <= 0 && autoStartCountdownLeft.value <= 0) {
|
|
|
|
|
resetAndStartAutoStartCountdown();
|
|
|
|
|
} else {
|
|
|
|
|
syncAutoStartCountdown();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pauseAutoStartCountdown("自动开始未启用或会话未就绪");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch(isActionPending, (pending) => {
|
|
|
|
|
if (pending) {
|
|
|
|
|
pauseAutoCallCountdown("操作进行中");
|
|
|
|
|
pauseAutoStartCountdown("操作进行中");
|
|
|
|
|
} else {
|
|
|
|
|
syncAutoCallCountdown();
|
|
|
|
|
syncAutoStartCountdown();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const buttons = computed<ActionButton[]>(() => {
|
|
|
|
|
const startOrComplete =
|
|
|
|
|
callStatus.value === "working"
|
|
|
|
|
@ -616,8 +1123,14 @@ async function openTicketList(): Promise<void> {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 发起呼叫或重呼。
|
|
|
|
|
* @param triggeredByAutoCall 为 true 时表示由自动叫号倒计时触发,不清除自动叫号倒计时
|
|
|
|
|
*/
|
|
|
|
|
async function callAction(): Promise<void> {
|
|
|
|
|
async function callAction(options?: {
|
|
|
|
|
triggeredByAutoCall?: boolean;
|
|
|
|
|
}): Promise<void> {
|
|
|
|
|
if (!options?.triggeredByAutoCall) {
|
|
|
|
|
stopAutoCallCountdown("手动点击呼叫");
|
|
|
|
|
}
|
|
|
|
|
if (isActionPending.value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@ -634,15 +1147,13 @@ async function callAction(): Promise<void> {
|
|
|
|
|
ticketUid: callingTkt.value,
|
|
|
|
|
});
|
|
|
|
|
if (isActionSuccess(recallRes)) {
|
|
|
|
|
callingTicketNoLabel.value = getActionTicketNo(recallRes);
|
|
|
|
|
updateLog(`已重呼:${getActionTicketNo(recallRes)},请勿重复点击!`);
|
|
|
|
|
await log(
|
|
|
|
|
"info",
|
|
|
|
|
`重呼成功: ticketNo=${getActionTicketNo(recallRes)}`,
|
|
|
|
|
);
|
|
|
|
|
await emitTaxerTicketContext({
|
|
|
|
|
ticketNo: getActionTicketNo(recallRes),
|
|
|
|
|
sfzhm: getActionSfzhm(recallRes),
|
|
|
|
|
});
|
|
|
|
|
await emitTaxerContextFromAction(recallRes);
|
|
|
|
|
} else {
|
|
|
|
|
updateLog(getActionMessage(recallRes));
|
|
|
|
|
}
|
|
|
|
|
@ -654,15 +1165,13 @@ async function callAction(): Promise<void> {
|
|
|
|
|
callStatus.value = "calling";
|
|
|
|
|
callBtnText.value = "重呼";
|
|
|
|
|
callingTkt.value = getActionTicketUid(res);
|
|
|
|
|
callingTicketNoLabel.value = getActionTicketNo(res);
|
|
|
|
|
updateLog(`正在呼叫:${getActionTicketNo(res)}`);
|
|
|
|
|
await log(
|
|
|
|
|
"info",
|
|
|
|
|
`呼叫成功: ticketNo=${getActionTicketNo(res)}, ticketUid=${getActionTicketUid(res)}`,
|
|
|
|
|
);
|
|
|
|
|
await emitTaxerTicketContext({
|
|
|
|
|
ticketNo: getActionTicketNo(res),
|
|
|
|
|
sfzhm: getActionSfzhm(res),
|
|
|
|
|
});
|
|
|
|
|
await emitTaxerContextFromAction(res);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -692,7 +1201,9 @@ async function abandonAction(): Promise<void> {
|
|
|
|
|
callStatus.value = "idle";
|
|
|
|
|
callBtnText.value = "呼叫";
|
|
|
|
|
callingTkt.value = -1;
|
|
|
|
|
callingTicketNoLabel.value = "";
|
|
|
|
|
updateLog(`弃号成功: ${getActionTicketNo(res)}`);
|
|
|
|
|
await emitTaxerNsrClear();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -705,8 +1216,14 @@ async function abandonAction(): Promise<void> {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 开始办理。
|
|
|
|
|
* @param triggeredByAutoStart 为 true 时表示由自动开始倒计时触发,不清除自动开始倒计时
|
|
|
|
|
*/
|
|
|
|
|
async function startAction(): Promise<void> {
|
|
|
|
|
async function startAction(options?: {
|
|
|
|
|
triggeredByAutoStart?: boolean;
|
|
|
|
|
}): Promise<void> {
|
|
|
|
|
if (!options?.triggeredByAutoStart) {
|
|
|
|
|
stopAutoStartCountdown("手动点击开始");
|
|
|
|
|
}
|
|
|
|
|
if (isActionPending.value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@ -721,6 +1238,7 @@ async function startAction(): Promise<void> {
|
|
|
|
|
if (isActionSuccess(res)) {
|
|
|
|
|
callStatus.value = "working";
|
|
|
|
|
updateLog(`正在办理:${getActionTicketNo(res)}`);
|
|
|
|
|
await emitTaxerTicketStart(buildTaxerContextFromAction(res));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@ -747,6 +1265,7 @@ async function completeAction(): Promise<void> {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await emitTaxerNsrClear();
|
|
|
|
|
callStatus.value = "evaluating";
|
|
|
|
|
callBtnText.value = "呼叫";
|
|
|
|
|
startEvaluatingCountdown(`办理完成:${getActionTicketNo(res)},评价中`);
|
|
|
|
|
@ -855,7 +1374,9 @@ async function confirmPauseReason(reason: string): Promise<void> {
|
|
|
|
|
if (isActionSuccess(res)) {
|
|
|
|
|
callStatus.value = "paused";
|
|
|
|
|
pauseBtnText.value = "恢复";
|
|
|
|
|
updateLog(`暂停中,原因:${pauseReason}`);
|
|
|
|
|
pauseAutoCallCountdown("暂停成功");
|
|
|
|
|
pauseAutoStartCountdown("暂停成功");
|
|
|
|
|
applyPausedLogMessage(pauseReason);
|
|
|
|
|
buttonPanel.value = "main";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@ -963,6 +1484,7 @@ async function handleMoreCommand(
|
|
|
|
|
updateLog("正在打开票号列表...");
|
|
|
|
|
// 主窗口在无边框模式下常驻置顶,先最小化主窗口避免票号列表被遮挡
|
|
|
|
|
await minimizeWindow();
|
|
|
|
|
await refreshMainWindowMinimizedState();
|
|
|
|
|
await openTicketList();
|
|
|
|
|
updateLog("票号列表已打开");
|
|
|
|
|
buttonPanel.value = "main";
|
|
|
|
|
@ -1049,11 +1571,30 @@ onMounted(async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sessionState.value = {
|
|
|
|
|
...session,
|
|
|
|
|
empUid,
|
|
|
|
|
winUid,
|
|
|
|
|
queueToken,
|
|
|
|
|
refreshToken,
|
|
|
|
|
};
|
|
|
|
|
void log(
|
|
|
|
|
"info",
|
|
|
|
|
`MainView 会话: autoCallEnabled=${sessionState.value.autoCallEnabled === true}, autoCallWaitSeconds=${resolveAutoCallWaitSeconds()}, autoStartEnabled=${sessionState.value.autoStartEnabled === true}, autoStartWaitSeconds=${resolveAutoStartWaitSeconds()}`,
|
|
|
|
|
);
|
|
|
|
|
if (
|
|
|
|
|
autoCallEnabled.value &&
|
|
|
|
|
callStatus.value === "idle" &&
|
|
|
|
|
sessionReadyForQueueCount.value
|
|
|
|
|
) {
|
|
|
|
|
resetAndStartAutoCallCountdown();
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
autoStartEnabled.value &&
|
|
|
|
|
callStatus.value === "calling" &&
|
|
|
|
|
sessionReadyForQueueCount.value
|
|
|
|
|
) {
|
|
|
|
|
resetAndStartAutoStartCountdown();
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await logErr("读取 session 失败", error);
|
|
|
|
|
}
|
|
|
|
|
@ -1066,21 +1607,31 @@ onMounted(async () => {
|
|
|
|
|
if (visible) {
|
|
|
|
|
isMainWindowActive.value = true;
|
|
|
|
|
}
|
|
|
|
|
const onFocus = () => {
|
|
|
|
|
isMainWindowActive.value = true;
|
|
|
|
|
isMainWindowActive.value = await appWindow.isFocused();
|
|
|
|
|
await refreshMainWindowMinimizedState();
|
|
|
|
|
|
|
|
|
|
const unlistenFns: Array<() => void> = [];
|
|
|
|
|
unlistenFns.push(
|
|
|
|
|
await appWindow.onFocusChanged(({ payload: focused }) => {
|
|
|
|
|
isMainWindowActive.value = focused;
|
|
|
|
|
if (focused) {
|
|
|
|
|
void refreshMainSessionStateFromDisk();
|
|
|
|
|
};
|
|
|
|
|
const onBlur = () => {
|
|
|
|
|
isMainWindowActive.value = false;
|
|
|
|
|
};
|
|
|
|
|
window.addEventListener("focus", onFocus);
|
|
|
|
|
window.addEventListener("blur", onBlur);
|
|
|
|
|
}
|
|
|
|
|
void refreshMainWindowMinimizedState();
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
unlistenFns.push(
|
|
|
|
|
await appWindow.onResized(() => {
|
|
|
|
|
void refreshMainWindowMinimizedState();
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
unlistenWindowFocusChanged = () => {
|
|
|
|
|
window.removeEventListener("focus", onFocus);
|
|
|
|
|
window.removeEventListener("blur", onBlur);
|
|
|
|
|
for (const unlisten of unlistenFns) {
|
|
|
|
|
unlisten();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await logErr("订阅主窗口焦点事件失败", error);
|
|
|
|
|
await logErr("订阅主窗口焦点/尺寸事件失败", error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unlistenMainAction = await listenMainAction((action, payload) => {
|
|
|
|
|
@ -1105,6 +1656,8 @@ onUnmounted(() => {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
clearEvaluatingCountdown();
|
|
|
|
|
stopAutoCallCountdown("组件卸载");
|
|
|
|
|
stopAutoStartCountdown("组件卸载");
|
|
|
|
|
clearIsRankPolling();
|
|
|
|
|
clearQueueCountPolling("组件卸载");
|
|
|
|
|
if (unlistenWindowFocusChanged) {
|
|
|
|
|
|