|
|
|
@ -1,5 +1,11 @@
|
|
|
|
<script setup lang="ts">
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { Close, Lock, Minus, User } from "@element-plus/icons-vue";
|
|
|
|
import {
|
|
|
|
|
|
|
|
Close,
|
|
|
|
|
|
|
|
Lock,
|
|
|
|
|
|
|
|
Minus,
|
|
|
|
|
|
|
|
User,
|
|
|
|
|
|
|
|
WarningFilled,
|
|
|
|
|
|
|
|
} from "@element-plus/icons-vue";
|
|
|
|
import { ElDialog, ElForm, ElMessage, ElProgress } from "element-plus";
|
|
|
|
import { ElDialog, ElForm, ElMessage, ElProgress } from "element-plus";
|
|
|
|
// 报错统一走 host/dialog 原生 message,勿用 ElMessage.error
|
|
|
|
// 报错统一走 host/dialog 原生 message,勿用 ElMessage.error
|
|
|
|
import { getVersion } from "@tauri-apps/api/app";
|
|
|
|
import { getVersion } from "@tauri-apps/api/app";
|
|
|
|
@ -7,11 +13,16 @@ import { listen } from "@tauri-apps/api/event";
|
|
|
|
import { invoke } from "@tauri-apps/api/tauri";
|
|
|
|
import { invoke } from "@tauri-apps/api/tauri";
|
|
|
|
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
|
|
|
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
|
|
|
import { api } from "../api";
|
|
|
|
import { api } from "../api";
|
|
|
|
import { confirmNative, showErrorNative, showInfoNative, showWarningNative } from "../host/dialog";
|
|
|
|
|
|
|
|
import { getAllConfig, mergeConfig } from "../host/config";
|
|
|
|
import { getAllConfig, mergeConfig } from "../host/config";
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
|
|
confirmNative,
|
|
|
|
|
|
|
|
showErrorNative,
|
|
|
|
|
|
|
|
showInfoNative,
|
|
|
|
|
|
|
|
showWarningNative,
|
|
|
|
|
|
|
|
} from "../host/dialog";
|
|
|
|
import { log } from "../host/logger";
|
|
|
|
import { log } from "../host/logger";
|
|
|
|
import { setSession } from "../host/session";
|
|
|
|
import { setSession } from "../host/session";
|
|
|
|
import type { SessionState } from "../host/types";
|
|
|
|
import type { AppConfig, JsonValue, SessionState } from "../host/types";
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
closeWindow,
|
|
|
|
closeWindow,
|
|
|
|
minimizeWindow,
|
|
|
|
minimizeWindow,
|
|
|
|
@ -19,6 +30,13 @@ import {
|
|
|
|
startWindowDragging,
|
|
|
|
startWindowDragging,
|
|
|
|
} from "../host/window";
|
|
|
|
} from "../host/window";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** `config.json` 中持久化「有待升级的 call-client 候选版本」 */
|
|
|
|
|
|
|
|
const PENDING_CALL_CLIENT_UPDATE_KEY = "pending_call_client_update";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 与 Rust `commands::update::PENDING_CALL_CLIENT_UPDATE_SYNCED_EVENT` 一致 */
|
|
|
|
|
|
|
|
const PENDING_CALL_CLIENT_UPDATE_SYNCED_EVENT =
|
|
|
|
|
|
|
|
"pending-call-client-update-synced";
|
|
|
|
|
|
|
|
|
|
|
|
/** el-form 需要稳定引用,勿使用每次渲染新建的 `{ username, password }` 对象 */
|
|
|
|
/** el-form 需要稳定引用,勿使用每次渲染新建的 `{ username, password }` 对象 */
|
|
|
|
const loginForm = reactive({
|
|
|
|
const loginForm = reactive({
|
|
|
|
username: "admin",
|
|
|
|
username: "admin",
|
|
|
|
@ -32,6 +50,9 @@ const selectedWin = ref("");
|
|
|
|
const cachedWinKey = ref("");
|
|
|
|
const cachedWinKey = ref("");
|
|
|
|
const options = ref<Array<{ label: string; value: string }>>([]);
|
|
|
|
const options = ref<Array<{ label: string; value: string }>>([]);
|
|
|
|
const appVersion = ref("0.1.0");
|
|
|
|
const appVersion = ref("0.1.0");
|
|
|
|
|
|
|
|
/** 与 config / apt 检查结果同步:有可升级的候选版本时为 true(仅 UI 提示) */
|
|
|
|
|
|
|
|
const newVersionBadgeVisible = ref(false);
|
|
|
|
|
|
|
|
const newVersionCandidateText = ref("");
|
|
|
|
const checkingUpdate = ref(false);
|
|
|
|
const checkingUpdate = ref(false);
|
|
|
|
const ZYYUN_APT_SETUP_PROGRESS_EVENT = "zyyun-apt-setup-progress";
|
|
|
|
const ZYYUN_APT_SETUP_PROGRESS_EVENT = "zyyun-apt-setup-progress";
|
|
|
|
|
|
|
|
|
|
|
|
@ -39,7 +60,9 @@ const aptSetupDialogVisible = ref(false);
|
|
|
|
const aptSetupPercent = ref(0);
|
|
|
|
const aptSetupPercent = ref(0);
|
|
|
|
const aptSetupHint = ref("");
|
|
|
|
const aptSetupHint = ref("");
|
|
|
|
const aptSetupFinished = ref(false);
|
|
|
|
const aptSetupFinished = ref(false);
|
|
|
|
const aptSetupProgressStatus = ref<"success" | "exception" | undefined>(undefined);
|
|
|
|
const aptSetupProgressStatus = ref<"success" | "exception" | undefined>(
|
|
|
|
|
|
|
|
undefined,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/** 默认紫云 apt 源行;可通过配置项 apt_source_deb_line 覆盖(见 ~/.config/com.ziyun.callclient/config.json) */
|
|
|
|
/** 默认紫云 apt 源行;可通过配置项 apt_source_deb_line 覆盖(见 ~/.config/com.ziyun.callclient/config.json) */
|
|
|
|
const DEFAULT_APT_SOURCE_DEB_LINE =
|
|
|
|
const DEFAULT_APT_SOURCE_DEB_LINE =
|
|
|
|
@ -61,6 +84,89 @@ interface AptSetupProgressPayload {
|
|
|
|
message: string;
|
|
|
|
message: string;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface PendingCallClientUpdateSyncedPayload {
|
|
|
|
|
|
|
|
hasUpdate?: boolean;
|
|
|
|
|
|
|
|
sourceAvailable?: boolean;
|
|
|
|
|
|
|
|
candidateVersion?: string;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Rust 后台 apt 检测落盘 config 后广播,用于刷新角标(与页面生命周期无关) */
|
|
|
|
|
|
|
|
function applyBadgeFromPendingSyncEvent(
|
|
|
|
|
|
|
|
p: PendingCallClientUpdateSyncedPayload | null | undefined,
|
|
|
|
|
|
|
|
): void {
|
|
|
|
|
|
|
|
if (p == null || p.sourceAvailable !== true) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p.hasUpdate === true) {
|
|
|
|
|
|
|
|
const v =
|
|
|
|
|
|
|
|
typeof p.candidateVersion === "string" ? p.candidateVersion.trim() : "";
|
|
|
|
|
|
|
|
if (v) {
|
|
|
|
|
|
|
|
newVersionBadgeVisible.value = true;
|
|
|
|
|
|
|
|
newVersionCandidateText.value = v;
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
clearNewVersionBadge();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const unlistenPendingUpdateSynced = ref<(() => void) | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function syncNewVersionBadgeFromResult(r: AptUpdateCheckResult): void {
|
|
|
|
|
|
|
|
const show = Boolean(r.hasUpdate && r.sourceAvailable);
|
|
|
|
|
|
|
|
newVersionBadgeVisible.value = show;
|
|
|
|
|
|
|
|
newVersionCandidateText.value = show ? r.candidateVersion : "";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function clearNewVersionBadge(): void {
|
|
|
|
|
|
|
|
newVersionBadgeVisible.value = false;
|
|
|
|
|
|
|
|
newVersionCandidateText.value = "";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 与 apt 结果同步角标,并在「源可用」时写入或清除 config 中的待升级记录 */
|
|
|
|
|
|
|
|
async function syncBadgeAndPersistPendingFromResult(
|
|
|
|
|
|
|
|
r: AptUpdateCheckResult,
|
|
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
|
|
syncNewVersionBadgeFromResult(r);
|
|
|
|
|
|
|
|
if (r.hasUpdate && r.sourceAvailable) {
|
|
|
|
|
|
|
|
await persistPendingUpdateToConfig(r.candidateVersion);
|
|
|
|
|
|
|
|
} else if (r.sourceAvailable) {
|
|
|
|
|
|
|
|
await clearPendingUpdateInConfig();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function readPendingCandidateFromConfig(config: AppConfig): string | null {
|
|
|
|
|
|
|
|
const raw = config[PENDING_CALL_CLIENT_UPDATE_KEY];
|
|
|
|
|
|
|
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
|
|
|
|
|
|
const c = (raw as Record<string, JsonValue>).candidateVersion;
|
|
|
|
|
|
|
|
if (typeof c === "string" && c.trim() !== "") {
|
|
|
|
|
|
|
|
return c.trim();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function applyPendingUpdateBadgeFromConfig(config: AppConfig): void {
|
|
|
|
|
|
|
|
const candidate = readPendingCandidateFromConfig(config);
|
|
|
|
|
|
|
|
if (candidate) {
|
|
|
|
|
|
|
|
newVersionBadgeVisible.value = true;
|
|
|
|
|
|
|
|
newVersionCandidateText.value = candidate;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function persistPendingUpdateToConfig(
|
|
|
|
|
|
|
|
candidateVersion: string,
|
|
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
|
|
await mergeConfig({
|
|
|
|
|
|
|
|
[PENDING_CALL_CLIENT_UPDATE_KEY]: { candidateVersion },
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function clearPendingUpdateInConfig(): Promise<void> {
|
|
|
|
|
|
|
|
await mergeConfig({
|
|
|
|
|
|
|
|
[PENDING_CALL_CLIENT_UPDATE_KEY]: null,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let sessionState: SessionState = {
|
|
|
|
let sessionState: SessionState = {
|
|
|
|
empUid: null,
|
|
|
|
empUid: null,
|
|
|
|
winUid: null,
|
|
|
|
winUid: null,
|
|
|
|
@ -137,7 +243,10 @@ async function handleLogin(): Promise<void> {
|
|
|
|
"warn",
|
|
|
|
"warn",
|
|
|
|
`登录失败: 接口未返回有效 queueToken, user=${loginForm.username}`,
|
|
|
|
`登录失败: 接口未返回有效 queueToken, user=${loginForm.username}`,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
await showErrorNative("登录失败:服务器未返回有效凭据,请稍后重试。", "登录");
|
|
|
|
await showErrorNative(
|
|
|
|
|
|
|
|
"登录失败:服务器未返回有效凭据,请稍后重试。",
|
|
|
|
|
|
|
|
"登录",
|
|
|
|
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -235,7 +344,9 @@ async function handleWindowLogin(): Promise<void> {
|
|
|
|
const resolvedWinUid =
|
|
|
|
const resolvedWinUid =
|
|
|
|
Number.isFinite(callerWin) && callerWin > 0 ? callerWin : winUid;
|
|
|
|
Number.isFinite(callerWin) && callerWin > 0 ? callerWin : winUid;
|
|
|
|
const callerEmp = Number(callerInit.empUid);
|
|
|
|
const callerEmp = Number(callerInit.empUid);
|
|
|
|
const resolvedEmpUid = Number.isFinite(callerEmp) ? callerEmp : Number(sessionState.empUid ?? -1);
|
|
|
|
const resolvedEmpUid = Number.isFinite(callerEmp)
|
|
|
|
|
|
|
|
? callerEmp
|
|
|
|
|
|
|
|
: Number(sessionState.empUid ?? -1);
|
|
|
|
|
|
|
|
|
|
|
|
sessionState = {
|
|
|
|
sessionState = {
|
|
|
|
...sessionState,
|
|
|
|
...sessionState,
|
|
|
|
@ -331,22 +442,32 @@ function handleInputFocus(event: Event): void {
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* 自动写入紫云 apt 源并 apt-get update;Rust 通过事件推送进度。
|
|
|
|
* 自动写入紫云 apt 源并 apt-get update;Rust 通过事件推送进度。
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
async function runZyyunAptSourceSetupWithProgress(debLine: string): Promise<void> {
|
|
|
|
async function runZyyunAptSourceSetupWithProgress(
|
|
|
|
|
|
|
|
debLine: string,
|
|
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
aptSetupDialogVisible.value = true;
|
|
|
|
aptSetupDialogVisible.value = true;
|
|
|
|
aptSetupFinished.value = false;
|
|
|
|
aptSetupFinished.value = false;
|
|
|
|
aptSetupPercent.value = 0;
|
|
|
|
aptSetupPercent.value = 0;
|
|
|
|
aptSetupHint.value = "准备中…";
|
|
|
|
aptSetupHint.value = "准备中…";
|
|
|
|
aptSetupProgressStatus.value = undefined;
|
|
|
|
aptSetupProgressStatus.value = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
const unlisten = await listen<AptSetupProgressPayload>(ZYYUN_APT_SETUP_PROGRESS_EVENT, (e) => {
|
|
|
|
const unlisten = await listen<AptSetupProgressPayload>(
|
|
|
|
const p = e.payload;
|
|
|
|
ZYYUN_APT_SETUP_PROGRESS_EVENT,
|
|
|
|
aptSetupPercent.value = Math.min(100, Math.max(0, Number(p.percent) || 0));
|
|
|
|
(e) => {
|
|
|
|
aptSetupHint.value = typeof p.message === "string" ? p.message : "";
|
|
|
|
const p = e.payload;
|
|
|
|
});
|
|
|
|
aptSetupPercent.value = Math.min(
|
|
|
|
|
|
|
|
100,
|
|
|
|
|
|
|
|
Math.max(0, Number(p.percent) || 0),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
aptSetupHint.value = typeof p.message === "string" ? p.message : "";
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const trimmed = debLine.trim();
|
|
|
|
const trimmed = debLine.trim();
|
|
|
|
const debPreview =
|
|
|
|
const debPreview =
|
|
|
|
trimmed.length > 220 ? `${trimmed.slice(0, 220)}…(共 ${trimmed.length} 字符)` : trimmed;
|
|
|
|
trimmed.length > 220
|
|
|
|
|
|
|
|
? `${trimmed.slice(0, 220)}…(共 ${trimmed.length} 字符)`
|
|
|
|
|
|
|
|
: trimmed;
|
|
|
|
await log(
|
|
|
|
await log(
|
|
|
|
"info",
|
|
|
|
"info",
|
|
|
|
`[apt-setup][前端] 即将 invoke setup_zyyun_apt_source, debLen=${trimmed.length}, debPreview=${debPreview}`,
|
|
|
|
`[apt-setup][前端] 即将 invoke setup_zyyun_apt_source, debLen=${trimmed.length}, debPreview=${debPreview}`,
|
|
|
|
@ -355,11 +476,17 @@ async function runZyyunAptSourceSetupWithProgress(debLine: string): Promise<void
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
await invoke("setup_zyyun_apt_source", { debLine: trimmed });
|
|
|
|
await invoke("setup_zyyun_apt_source", { debLine: trimmed });
|
|
|
|
aptSetupProgressStatus.value = "success";
|
|
|
|
aptSetupProgressStatus.value = "success";
|
|
|
|
await log("info", "[apt-setup][前端] setup_zyyun_apt_source invoke 成功结束");
|
|
|
|
await log(
|
|
|
|
|
|
|
|
"info",
|
|
|
|
|
|
|
|
"[apt-setup][前端] setup_zyyun_apt_source invoke 成功结束",
|
|
|
|
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
aptSetupProgressStatus.value = "exception";
|
|
|
|
aptSetupProgressStatus.value = "exception";
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
await log("error", `[apt-setup][前端] setup_zyyun_apt_source invoke 失败: ${message}`);
|
|
|
|
await log(
|
|
|
|
|
|
|
|
"error",
|
|
|
|
|
|
|
|
`[apt-setup][前端] setup_zyyun_apt_source invoke 失败: ${message}`,
|
|
|
|
|
|
|
|
);
|
|
|
|
throw error;
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
|
unlisten();
|
|
|
|
unlisten();
|
|
|
|
@ -368,9 +495,9 @@ async function runZyyunAptSourceSetupWithProgress(debLine: string): Promise<void
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* 检查 apt 仓库中是否有新版本;配源与升级均在应用内由固定 Rust 脚本 + pkexec 完成,不向用户暴露可手写的 shell 命令。
|
|
|
|
* 点击「有新版本」角标:配源、确认并执行 apt 升级(原「检查更新」流程)。
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
async function handleCheckUpdate(): Promise<void> {
|
|
|
|
async function handleUpgradeBadgeClick(): Promise<void> {
|
|
|
|
if (checkingUpdate.value) {
|
|
|
|
if (checkingUpdate.value) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -405,8 +532,9 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
await showWarningNative(
|
|
|
|
await showWarningNative(
|
|
|
|
"本机已自动配置过紫云软件源,但 apt 仍无法解析候选版本。请联系管理员检查内网仓库、网络或密钥文件;勿在终端自行执行来源不明的更新脚本。",
|
|
|
|
"本机已自动配置过紫云软件源,但 apt 仍无法解析候选版本。请联系管理员检查内网仓库、网络或密钥文件;勿在终端自行执行来源不明的更新脚本。",
|
|
|
|
"检查更新",
|
|
|
|
"升级",
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
await syncBadgeAndPersistPendingFromResult(result);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -421,17 +549,21 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
await log("info", "检查更新(前端): 用户在「配置更新源」对话框选择取消");
|
|
|
|
await log("info", "检查更新(前端): 用户在「配置更新源」对话框选择取消");
|
|
|
|
await showInfoNative(
|
|
|
|
await showInfoNative(
|
|
|
|
[
|
|
|
|
[
|
|
|
|
"未执行自动配置。稍后可再次点击「检查更新」,在提示中选择「开始配置」并输入管理员密码,",
|
|
|
|
"未执行自动配置。稍后可再次点击登录页上的「有新版本」提示,在对话框中选择「开始配置」并输入管理员密码,",
|
|
|
|
"由本程序自动完成软件源与索引更新。",
|
|
|
|
"由本程序自动完成软件源与索引更新。",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"请勿在终端自行编写或执行未经验证的更新命令;若仍无法使用,请联系管理员。",
|
|
|
|
"请勿在终端自行编写或执行未经验证的更新命令;若仍无法使用,请联系管理员。",
|
|
|
|
].join("\n"),
|
|
|
|
].join("\n"),
|
|
|
|
"更新源未配置",
|
|
|
|
"更新源未配置",
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
await syncBadgeAndPersistPendingFromResult(result);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await log("info", "检查更新(前端): 用户确认自动配置 apt 源,开始 runZyyunAptSourceSetupWithProgress");
|
|
|
|
await log(
|
|
|
|
|
|
|
|
"info",
|
|
|
|
|
|
|
|
"检查更新(前端): 用户确认自动配置 apt 源,开始 runZyyunAptSourceSetupWithProgress",
|
|
|
|
|
|
|
|
);
|
|
|
|
await runZyyunAptSourceSetupWithProgress(aptSourceDebLine.value);
|
|
|
|
await runZyyunAptSourceSetupWithProgress(aptSourceDebLine.value);
|
|
|
|
await mergeConfig({ zyyun_apt_source_configured: true });
|
|
|
|
await mergeConfig({ zyyun_apt_source_configured: true });
|
|
|
|
await log("info", "已写入配置: zyyun_apt_source_configured=true");
|
|
|
|
await log("info", "已写入配置: zyyun_apt_source_configured=true");
|
|
|
|
@ -449,17 +581,23 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
"自动配置已完成,但 apt 仍无法解析候选版本。",
|
|
|
|
"自动配置已完成,但 apt 仍无法解析候选版本。",
|
|
|
|
"请联系管理员检查内网 apt 仓库、GPG 与网络;请勿在终端自行执行未经验证的安装脚本。",
|
|
|
|
"请联系管理员检查内网 apt 仓库、GPG 与网络;请勿在终端自行执行未经验证的安装脚本。",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"您可稍后再次点击「检查更新」重试。",
|
|
|
|
"您可稍后再次点击「有新版本」重试。",
|
|
|
|
].join("\n"),
|
|
|
|
].join("\n"),
|
|
|
|
"更新源不可用",
|
|
|
|
"更新源不可用",
|
|
|
|
{ logActions: "file" },
|
|
|
|
{ logActions: "file" },
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
await syncBadgeAndPersistPendingFromResult(result);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await syncBadgeAndPersistPendingFromResult(result);
|
|
|
|
|
|
|
|
|
|
|
|
if (!result.hasUpdate) {
|
|
|
|
if (!result.hasUpdate) {
|
|
|
|
await log("info", `检查更新提示: 当前已是最新版本, version=${result.installedVersion}`);
|
|
|
|
await log(
|
|
|
|
|
|
|
|
"info",
|
|
|
|
|
|
|
|
`检查更新提示: 当前已是最新版本, version=${result.installedVersion}`,
|
|
|
|
|
|
|
|
);
|
|
|
|
showMessage("success", `当前已是最新版本(${result.installedVersion})`);
|
|
|
|
showMessage("success", `当前已是最新版本(${result.installedVersion})`);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -476,6 +614,9 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"升级将由本程序在授权后固定执行(更新索引并仅升级 call-client),需在弹出框中输入管理员密码。",
|
|
|
|
"升级将由本程序在授权后固定执行(更新索引并仅升级 call-client),需在弹出框中输入管理员密码。",
|
|
|
|
"请勿在终端自行编写或执行更新脚本。",
|
|
|
|
"请勿在终端自行编写或执行更新脚本。",
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
|
|
|
"说明:升级完成后 apt 会更新磁盘上的程序,但当前进程仍是旧版本。",
|
|
|
|
|
|
|
|
"请在本提示升级流程结束后,完全退出并重新打开本客户端,新版本才会生效。",
|
|
|
|
].join("\n"),
|
|
|
|
].join("\n"),
|
|
|
|
okLabel: "应用内升级",
|
|
|
|
okLabel: "应用内升级",
|
|
|
|
cancelLabel: "稍后再说",
|
|
|
|
cancelLabel: "稍后再说",
|
|
|
|
@ -496,13 +637,23 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
} catch {
|
|
|
|
} catch {
|
|
|
|
// 忽略版本读取失败
|
|
|
|
// 忽略版本读取失败
|
|
|
|
}
|
|
|
|
}
|
|
|
|
showMessage(
|
|
|
|
await showInfoNative(
|
|
|
|
"success",
|
|
|
|
[
|
|
|
|
`升级已执行。界面显示版本:${appVersion.value}。若仍为旧版本,请重启本客户端后再试。`,
|
|
|
|
"应用内升级命令已执行完成。",
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
|
|
|
"apt 已更新本机安装的 call-client 安装包,但当前正在运行的客户端进程仍为旧版本,无法在不退出进程的情况下自动切换为新程序。",
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
|
|
|
"请现在完全退出本客户端(关闭所有窗口,必要时从托盘退出),然后重新打开,新版本才会生效。",
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
|
|
|
`界面当前显示的版本号为:${appVersion.value}(来自当前进程,在重启前可能仍显示旧版本,属正常现象。)`,
|
|
|
|
|
|
|
|
].join("\n"),
|
|
|
|
|
|
|
|
"升级完成",
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
await clearPendingUpdateInConfig();
|
|
|
|
|
|
|
|
clearNewVersionBadge();
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
await showErrorNative(`检查更新失败:${message}`, "检查更新", {
|
|
|
|
await showErrorNative(`升级失败:${message}`, "升级", {
|
|
|
|
logActions: "file",
|
|
|
|
logActions: "file",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
await log("error", `检查更新失败: ${message}`);
|
|
|
|
await log("error", `检查更新失败: ${message}`);
|
|
|
|
@ -536,10 +687,19 @@ onMounted(async () => {
|
|
|
|
selectedWin.value = config.selected_win_key;
|
|
|
|
selectedWin.value = config.selected_win_key;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
window.addEventListener("keydown", handleKeyPress);
|
|
|
|
window.addEventListener("keydown", handleKeyPress);
|
|
|
|
|
|
|
|
applyPendingUpdateBadgeFromConfig(config);
|
|
|
|
|
|
|
|
unlistenPendingUpdateSynced.value = await listen<PendingCallClientUpdateSyncedPayload>(
|
|
|
|
|
|
|
|
PENDING_CALL_CLIENT_UPDATE_SYNCED_EVENT,
|
|
|
|
|
|
|
|
(event) => {
|
|
|
|
|
|
|
|
applyBadgeFromPendingSyncEvent(event.payload);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
onUnmounted(() => {
|
|
|
|
window.removeEventListener("keydown", handleKeyPress);
|
|
|
|
window.removeEventListener("keydown", handleKeyPress);
|
|
|
|
|
|
|
|
unlistenPendingUpdateSynced.value?.();
|
|
|
|
|
|
|
|
unlistenPendingUpdateSynced.value = undefined;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
@ -700,16 +860,25 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
|
|
|
|
<div class="version-info">
|
|
|
|
<div class="version-info">
|
|
|
|
<div class="version-row">
|
|
|
|
<div class="version-row">
|
|
|
|
<div class="version">版本号:V{{ appVersion }}</div>
|
|
|
|
<div class="version">
|
|
|
|
<el-button
|
|
|
|
<span class="version-number-label">版本号:V{{ appVersion }}</span>
|
|
|
|
text
|
|
|
|
<span
|
|
|
|
type="primary"
|
|
|
|
v-if="newVersionBadgeVisible"
|
|
|
|
size="small"
|
|
|
|
role="button"
|
|
|
|
:loading="checkingUpdate"
|
|
|
|
tabindex="0"
|
|
|
|
@click="handleCheckUpdate"
|
|
|
|
class="version-update-badge version-update-badge-red version-update-badge-clickable"
|
|
|
|
>
|
|
|
|
:title="`可升级至 ${newVersionCandidateText},点击执行升级`"
|
|
|
|
检查更新
|
|
|
|
:aria-busy="checkingUpdate"
|
|
|
|
</el-button>
|
|
|
|
@click="handleUpgradeBadgeClick"
|
|
|
|
|
|
|
|
@keydown.enter.prevent="handleUpgradeBadgeClick"
|
|
|
|
|
|
|
|
@keydown.space.prevent="handleUpgradeBadgeClick"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<el-icon class="version-update-icon">
|
|
|
|
|
|
|
|
<WarningFilled />
|
|
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
|
|
<span class="version-update-text">有新版本</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="copyright">© 2023 紫云科技 版权所有</div>
|
|
|
|
<div class="copyright">© 2023 紫云科技 版权所有</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -717,23 +886,23 @@ onUnmounted(() => {
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
<el-dialog
|
|
|
|
v-model="aptSetupDialogVisible"
|
|
|
|
v-model="aptSetupDialogVisible"
|
|
|
|
title="正在配置更新源"
|
|
|
|
title="正在配置更新源"
|
|
|
|
width="440px"
|
|
|
|
width="440px"
|
|
|
|
class="apt-setup-dialog"
|
|
|
|
class="apt-setup-dialog"
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
:close-on-press-escape="aptSetupFinished"
|
|
|
|
:close-on-press-escape="aptSetupFinished"
|
|
|
|
:show-close="aptSetupFinished"
|
|
|
|
:show-close="aptSetupFinished"
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<el-progress
|
|
|
|
<el-progress
|
|
|
|
:percentage="aptSetupPercent"
|
|
|
|
:percentage="aptSetupPercent"
|
|
|
|
:status="aptSetupProgressStatus"
|
|
|
|
:status="aptSetupProgressStatus"
|
|
|
|
:stroke-width="16"
|
|
|
|
:stroke-width="16"
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
<p class="apt-setup-hint">{{ aptSetupHint }}</p>
|
|
|
|
<p class="apt-setup-hint">{{ aptSetupHint }}</p>
|
|
|
|
</el-dialog>
|
|
|
|
</el-dialog>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
@ -912,6 +1081,7 @@ onUnmounted(() => {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
gap: 6px;
|
|
|
|
gap: 6px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -919,6 +1089,52 @@ onUnmounted(() => {
|
|
|
|
margin-bottom: 0;
|
|
|
|
margin-bottom: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.version-number-label {
|
|
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.version-update-badge {
|
|
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
gap: 3px;
|
|
|
|
|
|
|
|
margin-left: 6px;
|
|
|
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.version-update-badge-red {
|
|
|
|
|
|
|
|
color: #f56c6c;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.version-update-badge-clickable {
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
padding: 2px 4px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
|
|
background: rgba(245, 108, 108, 0.12);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:focus-visible {
|
|
|
|
|
|
|
|
outline: 2px solid rgba(245, 108, 108, 0.55);
|
|
|
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&[aria-busy="true"] {
|
|
|
|
|
|
|
|
cursor: wait;
|
|
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.version-update-icon {
|
|
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.version-update-text {
|
|
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
letter-spacing: 0.02em;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.copyright {
|
|
|
|
.copyright {
|
|
|
|
margin-top: 5px;
|
|
|
|
margin-top: 5px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|