更新优化

master
cysamurai 1 month ago
parent c9b2cab58b
commit 7825fb0ec6

@ -489,6 +489,7 @@ import { nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
import { appWindow, currentMonitor } from "@tauri-apps/api/window";
import { getVersion } from "@tauri-apps/api/app";
import { invoke } from "@tauri-apps/api/tauri";
import { message as nativeDialogMessage } from "@tauri-apps/api/dialog";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { ElMessage, ElMessageBox, ElDialog, ElProgress } from "element-plus";
import type {
@ -947,7 +948,14 @@ async function handleCheckUpdate() {
);
} catch (error) {
console.error("[update] 检查更新失败", error);
ElMessage.error(`检查更新失败:${String(error)}`);
try {
await nativeDialogMessage(`检查更新失败:${String(error)}`, {
title: "检查更新",
type: "error",
});
} catch {
//
}
} finally {
checkingUpdate.value = false;
}

@ -239,6 +239,8 @@ pub fn check_apt_update(
#[cfg(target_os = "linux")]
{
let output = Command::new("apt-cache")
.env("LC_ALL", "C")
.env("LANGUAGE", "C")
.arg("policy")
.arg(&package_name)
.output()
@ -290,7 +292,7 @@ pub fn check_apt_update(
let _ = app_log(
"info".to_string(),
format!(
"检查更新: apt-cache policy 子进程结束 success=true package={} exitCode={:?} stdout字节={} stderr字节={}",
"检查更新: apt-cache policy 子进程结束 success=true package={} exitCode={:?} stdout字节={} stderr字节={} (LC_ALL=C 以解析英文 Installed/Candidate 标签)",
package_name,
output.status.code(),
stdout.len(),

@ -104,3 +104,31 @@ export async function showErrorNativeWithLog(
): Promise<void> {
return showErrorNative(content, title, { logActions: "file" });
}
/**
*
*/
export async function showInfoNative(
content: string,
title = "提示",
): Promise<void> {
try {
await message(content, { title, type: "info" });
} catch (error) {
throw new Error(`打开提示框失败: ${String(error)}`);
}
}
/**
* Element
*/
export async function showWarningNative(
content: string,
title = "提示",
): Promise<void> {
try {
await message(content, { title, type: "warning" });
} catch (error) {
throw new Error(`打开警告提示框失败: ${String(error)}`);
}
}

@ -1,13 +1,13 @@
<script setup lang="ts">
import { Close, Lock, Minus, User } from "@element-plus/icons-vue";
import { ElDialog, ElForm, ElMessage, ElMessageBox, ElProgress } from "element-plus";
import { ElDialog, ElForm, ElMessage, ElProgress } from "element-plus";
// host/dialog message ElMessage.error
import { getVersion } from "@tauri-apps/api/app";
import { writeText } from "@tauri-apps/api/clipboard";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/tauri";
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
import { api } from "../api";
import { confirmNative, showErrorNative } from "../host/dialog";
import { confirmNative, showErrorNative, showInfoNative, showWarningNative } from "../host/dialog";
import { getAllConfig, mergeConfig } from "../host/config";
import { log } from "../host/logger";
import { setSession } from "../host/session";
@ -46,11 +46,6 @@ const DEFAULT_APT_SOURCE_DEB_LINE =
"deb [arch=amd64 signed-by=/usr/share/keyrings/zyyun-archive-keyring.gpg] http://80.12.140.29:80/apt v10 main";
const aptSourceDebLine = ref(DEFAULT_APT_SOURCE_DEB_LINE);
function buildAptSourceSetupCommand(debLine: string): string {
const escaped = debLine.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
return `echo "${escaped}" | sudo tee /etc/apt/sources.list.d/zyyun.list && sudo apt update`;
}
interface AptUpdateCheckResult {
packageName: string;
currentVersion: string;
@ -89,10 +84,7 @@ const isWindowSelected = computed(() => selectedWin.value !== "");
/**
* 在窄窗口中使用居中消息避免提示框被裁剪
*/
function showMessage(
type: "success" | "warning" | "error",
message: string,
): void {
function showMessage(type: "success" | "warning", message: string): void {
ElMessage({
type,
message,
@ -145,6 +137,7 @@ async function handleLogin(): Promise<void> {
"warn",
`登录失败: 接口未返回有效 queueToken, user=${loginForm.username}`,
);
await showErrorNative("登录失败:服务器未返回有效凭据,请稍后重试。", "登录");
return;
}
@ -179,6 +172,7 @@ async function handleLogin(): Promise<void> {
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
await log("error", `登录失败: user=${loginForm.username}, ${message}`);
await showErrorNative(message || "登录失败", "登录");
} finally {
isLoading.value = false;
}
@ -219,7 +213,7 @@ async function handleWindowLogin(): Promise<void> {
? (initRes as { message?: string }).message
: null) ??
"窗口初始化失败,请重试或更换窗口";
showMessage("error", String(msg));
await showErrorNative(String(msg), "窗口初始化");
await log("warn", `call-terminal/init 未成功: windowUid=${winUid}`);
return;
}
@ -275,7 +269,7 @@ async function handleWindowLogin(): Promise<void> {
await openMainWindow();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
showMessage("error", message || "打开主窗口失败");
await showErrorNative(message || "打开主窗口失败", "打开主窗口");
await log("error", `打开主窗口失败: ${message}`);
} finally {
isLoading.value = false;
@ -289,7 +283,7 @@ async function handleMinimizeClick(event: MouseEvent): Promise<void> {
await minimizeWindow();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
showMessage("error", message || "最小化窗口失败");
await showErrorNative(message || "最小化窗口失败", "最小化");
await log("error", `最小化窗口失败: ${message}`);
}
}
@ -301,7 +295,7 @@ async function handleCloseClick(event: MouseEvent): Promise<void> {
await closeWindow();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
showMessage("error", message || "关闭窗口失败");
await showErrorNative(message || "关闭窗口失败", "关闭窗口");
await log("error", `关闭窗口失败: ${message}`);
}
}
@ -374,7 +368,7 @@ async function runZyyunAptSourceSetupWithProgress(debLine: string): Promise<void
}
/**
* 检查 apt 仓库中是否有新版本并引导复制升级命令
* 检查 apt 仓库中是否有新版本配源与升级均在应用内由固定 Rust 脚本 + pkexec 完成不向用户暴露可手写的 shell 命令
*/
async function handleCheckUpdate(): Promise<void> {
if (checkingUpdate.value) {
@ -409,44 +403,31 @@ async function handleCheckUpdate(): Promise<void> {
"warn",
"检查更新: 已记录自动配置过 apt 源,但候选版本仍不可用,请人工排查网络或 GPG 密钥",
);
showMessage(
"warning",
"本机已自动配置过紫云软件源,但 apt 仍无法解析候选版本。请在终端执行 sudo apt update 后重试,或检查 /usr/share/keyrings/zyyun-archive-keyring.gpg 是否存在。",
await showWarningNative(
"本机已自动配置过紫云软件源,但 apt 仍无法解析候选版本。请联系管理员检查内网仓库、网络或密钥文件;勿在终端自行执行来源不明的更新脚本。",
"检查更新",
);
return;
}
try {
await ElMessageBox.confirm(
"未检测到可用更新源。是否使用管理员权限自动写入紫云软件源并执行 apt 更新?(需要桌面环境的 pkexec 授权)",
"配置更新源",
{
type: "warning",
confirmButtonText: "开始配置",
cancelButtonText: "取消",
distinguishCancelAndClose: true,
},
const doConfigure = await confirmNative({
title: "配置更新源",
message:
"未检测到可用更新源。是否使用管理员权限由本程序自动写入紫云软件源并执行 apt 更新?(将弹出 pkexec 授权,请输入管理员密码;无需在终端自行输入命令。)",
okLabel: "开始配置",
cancelLabel: "取消",
});
if (!doConfigure) {
await log("info", "检查更新(前端): 用户在「配置更新源」对话框选择取消");
await showInfoNative(
[
"未执行自动配置。稍后可再次点击「检查更新」,在提示中选择「开始配置」并输入管理员密码,",
"由本程序自动完成软件源与索引更新。",
"",
"请勿在终端自行编写或执行未经验证的更新命令;若仍无法使用,请联系管理员。",
].join("\n"),
"更新源未配置",
);
} catch {
await log("info", "检查更新(前端): 用户在「配置更新源」对话框选择取消或关闭");
const setupCmd = buildAptSourceSetupCommand(aptSourceDebLine.value);
const body = [
"已取消自动配置。若需手动配置,可将下面一行写入软件源,并在终端执行复制出的命令:",
aptSourceDebLine.value,
"",
"点击「复制命令」后,在终端执行:",
setupCmd,
].join("\n");
const copy = await confirmNative({
title: "更新源未配置",
message: body,
okLabel: "复制命令",
cancelLabel: "关闭",
});
if (copy) {
await writeText(setupCmd);
showMessage("success", "更新源初始化命令已复制到剪贴板");
}
return;
}
@ -463,21 +444,16 @@ async function handleCheckUpdate(): Promise<void> {
if (!result.sourceAvailable) {
await log("warn", "自动配置 apt 源后仍无法解析候选版本");
const setupCmd = buildAptSourceSetupCommand(aptSourceDebLine.value);
const body = [
"自动配置已完成,但 apt 仍无法解析候选版本。请检查网络、GPG 密钥,或在终端执行:",
setupCmd,
].join("\n");
const copy = await confirmNative({
title: "更新源仍不可用",
message: body,
okLabel: "复制命令",
cancelLabel: "关闭",
});
if (copy) {
await writeText(setupCmd);
showMessage("success", "命令已复制到剪贴板");
}
await showErrorNative(
[
"自动配置已完成,但 apt 仍无法解析候选版本。",
"请联系管理员检查内网 apt 仓库、GPG 与网络;请勿在终端自行执行未经验证的安装脚本。",
"",
"您可稍后再次点击「检查更新」重试。",
].join("\n"),
"更新源不可用",
{ logActions: "file" },
);
return;
}
}
@ -492,35 +468,20 @@ async function handleCheckUpdate(): Promise<void> {
"info",
`检查更新提示: 检测到新版本, installed=${result.installedVersion}, candidate=${result.candidateVersion}`,
);
const body = [
`检测到新版本:${result.candidateVersion}`,
`当前版本:${result.installedVersion}`,
"",
"可选:",
"· 点击「应用内升级」将弹出管理员授权pkexec执行固定的 apt 更新与仅升级 call-client与终端命令等价包名写死在程序内。",
"· 点击「复制终端命令」可在终端自行执行 sudo。",
"",
"终端命令:",
result.updateCommand,
].join("\n");
try {
await ElMessageBox.confirm(body, "发现新版本", {
confirmButtonText: "应用内升级(需管理员密码)",
cancelButtonText: "复制终端命令",
distinguishCancelAndClose: true,
type: "info",
});
} catch (action: unknown) {
if (action === "cancel") {
await writeText(result.updateCommand);
await log("info", "检查更新(前端): 用户选择复制终端升级命令");
showMessage("success", "升级命令已复制到剪贴板");
} else {
await log(
"info",
`检查更新(前端): 关闭「发现新版本」或未应用内升级 action=${String(action)}`,
);
}
const doUpgrade = await confirmNative({
title: "发现新版本",
message: [
`检测到新版本:${result.candidateVersion}`,
`当前已安装:${result.installedVersion}`,
"",
"升级将由本程序在授权后固定执行(更新索引并仅升级 call-client需在弹出框中输入管理员密码。",
"请勿在终端自行编写或执行更新脚本。",
].join("\n"),
okLabel: "应用内升级",
cancelLabel: "稍后再说",
});
if (!doUpgrade) {
await log("info", "检查更新(前端): 用户选择稍后再升级");
return;
}
@ -537,7 +498,7 @@ async function handleCheckUpdate(): Promise<void> {
}
showMessage(
"success",
`升级命令已执行完毕。界面显示版本:${appVersion.value}。若仍为旧版本,请重启本客户端后再试。`,
`升级已执行。界面显示版本:${appVersion.value}。若仍为旧版本,请重启本客户端后再试。`,
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);

@ -19,7 +19,6 @@ import {
VideoPlay,
} from "@element-plus/icons-vue";
import { appWindow } from "@tauri-apps/api/window";
import { ElMessage } from "element-plus";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { api } from "../api";
import { getAllConfig } from "../host/config";
@ -27,6 +26,7 @@ import {
confirmNative,
showErrorNative,
showErrorNativeWithLog,
showWarningNative,
} from "../host/dialog";
import { emitTaxerTicketContext, listenMainAction } from "../host/events";
import { log } from "../host/logger";
@ -842,7 +842,7 @@ async function confirmPauseReason(reason: string): Promise<void> {
await runWithPending(async () => {
const pauseReason = reason.trim();
if (!pauseReason) {
ElMessage.warning("请选择暂停原因");
await showWarningNative("请选择暂停原因", "暂停");
return;
}

@ -4,6 +4,7 @@ import { ElMessage } from "element-plus";
import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import { mergeConfig } from "../host/config";
import { showErrorNative } from "../host/dialog";
import { log } from "../host/logger";
import { closeWindow, minimizeWindow } from "../host/window";
import { applyServerIpToHttp } from "../utils/service";
@ -76,7 +77,7 @@ async function handleSave(): Promise<void> {
await router.replace("/login");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
showMessage("error", message || "保存失败");
await showErrorNative(message || "保存失败", "保存服务器地址");
await log("error", `保存服务器地址失败: ${message}`);
} finally {
saving.value = false;

Loading…
Cancel
Save