自动叫号,转移
parent
25b4217969
commit
603e07977f
@ -0,0 +1,5 @@
|
|||||||
|
export interface TransferBusiness {
|
||||||
|
businessUid: number;
|
||||||
|
businessName: string;
|
||||||
|
businessCode?: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
import { getAllConfig, mergeConfig } from "../host/config";
|
||||||
|
import type { CallStatus } from "../types/action";
|
||||||
|
|
||||||
|
/** 写入 `config.json`,登录重置 session 后仍可恢复 */
|
||||||
|
export const INTERRUPTED_TICKET_CONFIG_KEY = "interrupted_ticket_snapshot";
|
||||||
|
|
||||||
|
/** 呼叫/开始之后可恢复的流程状态 */
|
||||||
|
export type InterruptedFlowStatus = Extract<
|
||||||
|
CallStatus,
|
||||||
|
"calling" | "working" | "paused"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface InterruptedTicketSnapshot {
|
||||||
|
ticketUid: number;
|
||||||
|
ticketNo: string;
|
||||||
|
flowStatus: InterruptedFlowStatus;
|
||||||
|
windowUid: number;
|
||||||
|
empUid: number;
|
||||||
|
pauseReason?: string;
|
||||||
|
savedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFlowStatus(value: unknown): InterruptedFlowStatus | null {
|
||||||
|
if (value === "calling" || value === "working" || value === "paused") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSnapshot(raw: unknown): InterruptedTicketSnapshot | null {
|
||||||
|
if (!raw || typeof raw !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const record = raw as Record<string, unknown>;
|
||||||
|
const ticketUid = Number(record.ticketUid);
|
||||||
|
const windowUid = Number(record.windowUid);
|
||||||
|
const empUid = Number(record.empUid);
|
||||||
|
const flowStatus = parseFlowStatus(record.flowStatus);
|
||||||
|
if (
|
||||||
|
!Number.isFinite(ticketUid) ||
|
||||||
|
ticketUid <= 0 ||
|
||||||
|
!flowStatus ||
|
||||||
|
!Number.isFinite(windowUid) ||
|
||||||
|
windowUid <= 0 ||
|
||||||
|
!Number.isFinite(empUid) ||
|
||||||
|
empUid <= 0
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const ticketNo = String(record.ticketNo ?? "").trim();
|
||||||
|
const pauseReason =
|
||||||
|
typeof record.pauseReason === "string" && record.pauseReason.trim() !== ""
|
||||||
|
? record.pauseReason.trim()
|
||||||
|
: undefined;
|
||||||
|
const savedAt = Number(record.savedAt);
|
||||||
|
return {
|
||||||
|
ticketUid,
|
||||||
|
ticketNo,
|
||||||
|
flowStatus,
|
||||||
|
windowUid,
|
||||||
|
empUid,
|
||||||
|
pauseReason,
|
||||||
|
savedAt: Number.isFinite(savedAt) && savedAt > 0 ? savedAt : Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取异常中断票号缓存 */
|
||||||
|
export async function readInterruptedTicketSnapshot(): Promise<InterruptedTicketSnapshot | null> {
|
||||||
|
try {
|
||||||
|
const config = await getAllConfig();
|
||||||
|
return parseSnapshot(config[INTERRUPTED_TICKET_CONFIG_KEY]);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 写入异常中断票号缓存 */
|
||||||
|
export async function saveInterruptedTicketSnapshot(
|
||||||
|
snapshot: InterruptedTicketSnapshot,
|
||||||
|
): Promise<void> {
|
||||||
|
await mergeConfig({
|
||||||
|
[INTERRUPTED_TICKET_CONFIG_KEY]: {
|
||||||
|
...snapshot,
|
||||||
|
savedAt: snapshot.savedAt > 0 ? snapshot.savedAt : Date.now(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清除异常中断票号缓存 */
|
||||||
|
export async function clearInterruptedTicketSnapshot(): Promise<void> {
|
||||||
|
await mergeConfig({
|
||||||
|
[INTERRUPTED_TICKET_CONFIG_KEY]: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function interruptedFlowStatusLabel(
|
||||||
|
status: InterruptedFlowStatus,
|
||||||
|
): string {
|
||||||
|
switch (status) {
|
||||||
|
case "calling":
|
||||||
|
return "呼叫中";
|
||||||
|
case "working":
|
||||||
|
return "办理中";
|
||||||
|
case "paused":
|
||||||
|
return "已暂停";
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,125 @@
|
|||||||
|
import type { TransferRequest } from "../types/action";
|
||||||
|
import type { SessionState } from "../host/types";
|
||||||
|
import type { TransferBusiness } from "../types/transfer";
|
||||||
|
import type { ServiceWindow, WindowResponse } from "../types/window";
|
||||||
|
|
||||||
|
function extractArray(raw: unknown): unknown[] {
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
if (!raw || typeof raw !== "object") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const record = raw as Record<string, unknown>;
|
||||||
|
for (const key of [
|
||||||
|
"businesses",
|
||||||
|
"businessList",
|
||||||
|
"list",
|
||||||
|
"items",
|
||||||
|
"records",
|
||||||
|
"data",
|
||||||
|
]) {
|
||||||
|
const value = record[key];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBusinessItem(item: unknown): TransferBusiness | null {
|
||||||
|
if (!item || typeof item !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const record = item as Record<string, unknown>;
|
||||||
|
const businessUid = Number(
|
||||||
|
record.businessUid ?? record.bizUid ?? record.uid ?? record.id ?? -1,
|
||||||
|
);
|
||||||
|
if (!Number.isFinite(businessUid) || businessUid <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const businessName = String(
|
||||||
|
record.businessName ??
|
||||||
|
record.bizName ??
|
||||||
|
record.name ??
|
||||||
|
record.label ??
|
||||||
|
record.title ??
|
||||||
|
"",
|
||||||
|
).trim();
|
||||||
|
const businessCode = String(
|
||||||
|
record.businessCode ?? record.bizCode ?? record.code ?? "",
|
||||||
|
).trim();
|
||||||
|
return {
|
||||||
|
businessUid,
|
||||||
|
businessName: businessName || `业务 ${businessUid}`,
|
||||||
|
businessCode: businessCode || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析 business-list 接口返回(兼容多种字段命名) */
|
||||||
|
export function normalizeBusinessList(raw: unknown): TransferBusiness[] {
|
||||||
|
return extractArray(raw)
|
||||||
|
.map((item) => normalizeBusinessItem(item))
|
||||||
|
.filter((item): item is TransferBusiness => item !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 转移目标窗口列表(排除当前窗口) */
|
||||||
|
export function normalizeTransferWindows(
|
||||||
|
raw: WindowResponse | unknown,
|
||||||
|
currentWindowUid: number,
|
||||||
|
): ServiceWindow[] {
|
||||||
|
const windows = Array.isArray((raw as WindowResponse)?.windows)
|
||||||
|
? (raw as WindowResponse).windows
|
||||||
|
: [];
|
||||||
|
return windows.filter(
|
||||||
|
(win) =>
|
||||||
|
Number.isFinite(win.windowUid) &&
|
||||||
|
win.windowUid > 0 &&
|
||||||
|
win.windowUid !== currentWindowUid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransferTarget =
|
||||||
|
| { kind: "window"; windowUid: number }
|
||||||
|
| { kind: "business"; business: TransferBusiness };
|
||||||
|
|
||||||
|
/** 组装转移接口请求体(窗口与业务二选一) */
|
||||||
|
export function buildTransferRequest(
|
||||||
|
session: SessionState,
|
||||||
|
ticketUid: number,
|
||||||
|
target: TransferTarget,
|
||||||
|
): TransferRequest {
|
||||||
|
const base: TransferRequest = {
|
||||||
|
windowUid: Number(session.winUid ?? 0),
|
||||||
|
empUid: Number(session.empUid ?? 0),
|
||||||
|
ticketUid,
|
||||||
|
targetWindowUid: null,
|
||||||
|
targetBusinessUid: null,
|
||||||
|
resumeToken: "",
|
||||||
|
targetPosition: 0,
|
||||||
|
rank: 0,
|
||||||
|
rankUserName: "",
|
||||||
|
rankUserPhone: "",
|
||||||
|
idCard: "",
|
||||||
|
phone: "",
|
||||||
|
serviceUrl: String(session.ziyunServiceUrl ?? "").trim(),
|
||||||
|
forward: true,
|
||||||
|
read: true,
|
||||||
|
customText: "",
|
||||||
|
pauseReason: "",
|
||||||
|
driver: "",
|
||||||
|
clients: [],
|
||||||
|
};
|
||||||
|
if (target.kind === "window") {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
targetWindowUid: target.windowUid,
|
||||||
|
targetBusinessUid: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
targetWindowUid: null,
|
||||||
|
targetBusinessUid: target.business.businessUid,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,504 @@
|
|||||||
|
<template>
|
||||||
|
<div class="transfer-container">
|
||||||
|
<div class="transfer-header">
|
||||||
|
<div class="transfer-header-title" data-tauri-drag-region @dblclick.prevent.stop>
|
||||||
|
票号转移
|
||||||
|
</div>
|
||||||
|
<div class="transfer-header-actions">
|
||||||
|
<button class="control-button" type="button" @mousedown.stop @click="handleMinimizeClick">
|
||||||
|
<el-icon class="control-icon">
|
||||||
|
<component :is="Minus" />
|
||||||
|
</el-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="control-button"
|
||||||
|
type="button"
|
||||||
|
@mousedown.stop
|
||||||
|
@dblclick.prevent.stop
|
||||||
|
@click="handleCloseClick"
|
||||||
|
>
|
||||||
|
<el-icon class="control-icon">
|
||||||
|
<component :is="Close" />
|
||||||
|
</el-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-loading="loading" class="transfer-body">
|
||||||
|
<p v-if="loadError" class="transfer-error">{{ loadError }}</p>
|
||||||
|
<p v-else class="transfer-hint">目标窗口与目标业务只能二选一</p>
|
||||||
|
<div v-if="!loadError" class="transfer-columns">
|
||||||
|
<section class="transfer-column">
|
||||||
|
<h3 class="transfer-column-title">目标窗口</h3>
|
||||||
|
<el-radio-group
|
||||||
|
v-if="windows.length > 0"
|
||||||
|
v-model="selectedWindowUid"
|
||||||
|
class="transfer-radio-group"
|
||||||
|
@change="onWindowSelectionChange"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
v-for="win in windows"
|
||||||
|
:key="win.windowUid"
|
||||||
|
:value="win.windowUid"
|
||||||
|
class="transfer-radio"
|
||||||
|
>
|
||||||
|
<span class="transfer-radio-label">{{ win.windowName }}</span>
|
||||||
|
<span v-if="win.windowCode" class="transfer-radio-sub">
|
||||||
|
{{ win.windowCode }}
|
||||||
|
</span>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-empty v-else description="暂无可选窗口" :image-size="56" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="transfer-column">
|
||||||
|
<h3 class="transfer-column-title">目标业务</h3>
|
||||||
|
<el-radio-group
|
||||||
|
v-if="businesses.length > 0"
|
||||||
|
v-model="selectedBusinessUid"
|
||||||
|
class="transfer-radio-group"
|
||||||
|
@change="onBusinessSelectionChange"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
v-for="biz in businesses"
|
||||||
|
:key="biz.businessUid"
|
||||||
|
:value="biz.businessUid"
|
||||||
|
class="transfer-radio"
|
||||||
|
>
|
||||||
|
<span class="transfer-radio-label">{{ biz.businessName }}</span>
|
||||||
|
<span v-if="biz.businessCode" class="transfer-radio-sub">
|
||||||
|
{{ biz.businessCode }}
|
||||||
|
</span>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-empty v-else description="暂无可选业务" :image-size="56" />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="transfer-footer">
|
||||||
|
<el-button @click="handleCloseClick">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="submitting"
|
||||||
|
:disabled="loading || Boolean(loadError) || !hasTransferTarget"
|
||||||
|
@click="handleConfirm"
|
||||||
|
>
|
||||||
|
确认转移
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Close, Minus } from "@element-plus/icons-vue";
|
||||||
|
import { appWindow } from "@tauri-apps/api/window";
|
||||||
|
import { ElButton, ElEmpty, ElRadio, ElRadioGroup } from "element-plus";
|
||||||
|
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||||
|
import { api } from "../api";
|
||||||
|
import { showErrorNative, showWarningNative } from "../host/dialog";
|
||||||
|
import { emitTransferDone, listenTransferOpen } from "../host/events";
|
||||||
|
import { log } from "../host/logger";
|
||||||
|
import { getSession, setSession } from "../host/session";
|
||||||
|
import type { SessionState } from "../host/types";
|
||||||
|
import type { TransferBusiness } from "../types/transfer";
|
||||||
|
import type { ServiceWindow } from "../types/window";
|
||||||
|
import { closeTransferWindow, focusNamedWindow, minimizeWindow } from "../host/window";
|
||||||
|
import {
|
||||||
|
buildTransferRequest,
|
||||||
|
normalizeBusinessList,
|
||||||
|
normalizeTransferWindows,
|
||||||
|
type TransferTarget,
|
||||||
|
} from "../utils/transfer";
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const submitting = ref(false);
|
||||||
|
const loadError = ref("");
|
||||||
|
const ticketUid = ref(-1);
|
||||||
|
const currentWindowUid = ref(0);
|
||||||
|
const windows = ref<ServiceWindow[]>([]);
|
||||||
|
const businesses = ref<TransferBusiness[]>([]);
|
||||||
|
const selectedWindowUid = ref<number | undefined>(undefined);
|
||||||
|
const selectedBusinessUid = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
|
const hasTransferTarget = computed(() => {
|
||||||
|
const hasWindow = selectedWindowUid.value !== undefined;
|
||||||
|
const hasBusiness = selectedBusinessUid.value !== undefined;
|
||||||
|
return hasWindow !== hasBusiness;
|
||||||
|
});
|
||||||
|
|
||||||
|
function onWindowSelectionChange(): void {
|
||||||
|
if (selectedWindowUid.value !== undefined) {
|
||||||
|
selectedBusinessUid.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBusinessSelectionChange(): void {
|
||||||
|
if (selectedBusinessUid.value !== undefined) {
|
||||||
|
selectedWindowUid.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTransferTarget(): TransferTarget | null {
|
||||||
|
if (
|
||||||
|
selectedWindowUid.value !== undefined &&
|
||||||
|
selectedBusinessUid.value === undefined
|
||||||
|
) {
|
||||||
|
const windowUid = selectedWindowUid.value;
|
||||||
|
if (windows.value.some((item) => item.windowUid === windowUid)) {
|
||||||
|
return { kind: "window", windowUid };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selectedBusinessUid.value !== undefined &&
|
||||||
|
selectedWindowUid.value === undefined
|
||||||
|
) {
|
||||||
|
const business = businesses.value.find(
|
||||||
|
(item) => item.businessUid === selectedBusinessUid.value,
|
||||||
|
);
|
||||||
|
if (business) {
|
||||||
|
return { kind: "business", business };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unlistenTransferOpen: (() => void) | null = null;
|
||||||
|
let unlistenWindowFocus: (() => void) | null = null;
|
||||||
|
|
||||||
|
function parseOptionalNumber(value: unknown): number | null {
|
||||||
|
if (typeof value === "number" && Number.isFinite(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value === "string" && value.trim() !== "") {
|
||||||
|
const parsed = Number(value);
|
||||||
|
return Number.isFinite(parsed) ? parsed : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTicketUidFromSession(
|
||||||
|
session: SessionState,
|
||||||
|
explicit?: number,
|
||||||
|
): number {
|
||||||
|
if (typeof explicit === "number" && explicit > 0) {
|
||||||
|
return explicit;
|
||||||
|
}
|
||||||
|
const fromTransfer = parseOptionalNumber(session.transferTicketUid);
|
||||||
|
if (fromTransfer !== null && fromTransfer > 0) {
|
||||||
|
return fromTransfer;
|
||||||
|
}
|
||||||
|
const fromActive = parseOptionalNumber(session.activeTicketUid);
|
||||||
|
if (fromActive !== null && fromActive > 0) {
|
||||||
|
return fromActive;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActionData(res: unknown): Record<string, unknown> {
|
||||||
|
const result = (res ?? {}) as { data?: Record<string, unknown> } & Record<string, unknown>;
|
||||||
|
return result.data && typeof result.data === "object" ? result.data : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionSuccess(res: unknown): boolean {
|
||||||
|
return getActionData(res).success === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActionMessage(res: unknown): string {
|
||||||
|
return String(getActionData(res).message ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActionTicketNo(res: unknown): string {
|
||||||
|
return String(getActionData(res).ticketNo ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTransferOptions(explicitTicketUid?: number): Promise<void> {
|
||||||
|
loading.value = true;
|
||||||
|
loadError.value = "";
|
||||||
|
windows.value = [];
|
||||||
|
businesses.value = [];
|
||||||
|
selectedWindowUid.value = undefined;
|
||||||
|
selectedBusinessUid.value = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session = await getSession();
|
||||||
|
currentWindowUid.value = Number(session.winUid ?? 0);
|
||||||
|
ticketUid.value = resolveTicketUidFromSession(session, explicitTicketUid);
|
||||||
|
if (ticketUid.value <= 0) {
|
||||||
|
loadError.value =
|
||||||
|
"未获取到待转移票号,请先在主窗口完成呼叫并开始办理后再打开转移";
|
||||||
|
await log(
|
||||||
|
"warn",
|
||||||
|
`转移窗口: 无有效 ticketUid transferTicketUid=${String(session.transferTicketUid)} activeTicketUid=${String(session.activeTicketUid)}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [winRes, bizRes] = await Promise.all([
|
||||||
|
api.window.list(),
|
||||||
|
api.action.businessList(),
|
||||||
|
]);
|
||||||
|
windows.value = normalizeTransferWindows(winRes, currentWindowUid.value);
|
||||||
|
businesses.value = normalizeBusinessList(bizRes);
|
||||||
|
} catch (error) {
|
||||||
|
loadError.value = error instanceof Error ? error.message : String(error);
|
||||||
|
await log(
|
||||||
|
"error",
|
||||||
|
`转移窗口加载选项失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMinimizeClick(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await minimizeWindow();
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
await showErrorNative(message || "最小化失败", "票号转移");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCloseClick(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await closeTransferWindow();
|
||||||
|
await focusNamedWindow("main");
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
await showErrorNative(message || "关闭失败", "票号转移");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConfirm(): Promise<void> {
|
||||||
|
const target = resolveTransferTarget();
|
||||||
|
if (!target) {
|
||||||
|
await showWarningNative(
|
||||||
|
"请选择目标窗口或目标业务(只能二选一)",
|
||||||
|
"票号转移",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
const session = await getSession();
|
||||||
|
const res = await api.action.transfer(
|
||||||
|
buildTransferRequest(session, ticketUid.value, target),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isActionSuccess(res)) {
|
||||||
|
await setSession({
|
||||||
|
...session,
|
||||||
|
activeTicketUid: null,
|
||||||
|
transferTicketUid: null,
|
||||||
|
});
|
||||||
|
const targetWindowName =
|
||||||
|
target.kind === "window"
|
||||||
|
? windows.value.find((item) => item.windowUid === target.windowUid)
|
||||||
|
?.windowName
|
||||||
|
: undefined;
|
||||||
|
const businessName =
|
||||||
|
target.kind === "business" ? target.business.businessName : undefined;
|
||||||
|
await emitTransferDone({
|
||||||
|
success: true,
|
||||||
|
ticketNo: getActionTicketNo(res),
|
||||||
|
targetWindowName,
|
||||||
|
businessName,
|
||||||
|
message: getActionMessage(res),
|
||||||
|
});
|
||||||
|
await closeTransferWindow();
|
||||||
|
await focusNamedWindow("main");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await showWarningNative(
|
||||||
|
getActionMessage(res) || "转移未成功",
|
||||||
|
"票号转移",
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
await showErrorNative(message || "转移失败", "票号转移");
|
||||||
|
await log("error", `转移提交失败: ${message}`);
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
unlistenTransferOpen = await listenTransferOpen((payload) => {
|
||||||
|
void loadTransferOptions(payload.ticketUid);
|
||||||
|
});
|
||||||
|
unlistenWindowFocus = await appWindow.onFocusChanged(({ payload: focused }) => {
|
||||||
|
if (focused) {
|
||||||
|
void loadTransferOptions();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await log(
|
||||||
|
"error",
|
||||||
|
`转移窗口订阅事件失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
void loadTransferOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (unlistenTransferOpen) {
|
||||||
|
unlistenTransferOpen();
|
||||||
|
}
|
||||||
|
if (unlistenWindowFocus) {
|
||||||
|
unlistenWindowFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.transfer-container {
|
||||||
|
width: 640px;
|
||||||
|
height: 480px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: linear-gradient(135deg, #004d99 0%, #003b7a 100%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 6px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-header-title {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-error {
|
||||||
|
margin: 0;
|
||||||
|
color: #f56c6c;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-hint {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-columns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-column {
|
||||||
|
min-height: 0;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-column-title {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-radio-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-radio {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-right: 0;
|
||||||
|
height: auto;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-radio-label {
|
||||||
|
display: block;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-radio-sub {
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue