更新优化

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 { appWindow, currentMonitor } from "@tauri-apps/api/window";
import { getVersion } from "@tauri-apps/api/app"; import { getVersion } from "@tauri-apps/api/app";
import { invoke } from "@tauri-apps/api/tauri"; 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 { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { ElMessage, ElMessageBox, ElDialog, ElProgress } from "element-plus"; import { ElMessage, ElMessageBox, ElDialog, ElProgress } from "element-plus";
import type { import type {
@ -947,7 +948,14 @@ async function handleCheckUpdate() {
); );
} catch (error) { } catch (error) {
console.error("[update] 检查更新失败", error); console.error("[update] 检查更新失败", error);
ElMessage.error(`检查更新失败:${String(error)}`); try {
await nativeDialogMessage(`检查更新失败:${String(error)}`, {
title: "检查更新",
type: "error",
});
} catch {
//
}
} finally { } finally {
checkingUpdate.value = false; checkingUpdate.value = false;
} }

@ -239,6 +239,8 @@ pub fn check_apt_update(
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
let output = Command::new("apt-cache") let output = Command::new("apt-cache")
.env("LC_ALL", "C")
.env("LANGUAGE", "C")
.arg("policy") .arg("policy")
.arg(&package_name) .arg(&package_name)
.output() .output()
@ -290,7 +292,7 @@ pub fn check_apt_update(
let _ = app_log( let _ = app_log(
"info".to_string(), "info".to_string(),
format!( 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, package_name,
output.status.code(), output.status.code(),
stdout.len(), stdout.len(),

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

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

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

Loading…
Cancel
Save