优化斗地主
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
// Electron 主进程:窗口生命周期、应用菜单、后端服务器地址配置、外链拦截
|
||||
// 加载策略:开发模式(未打包)优先连 Vite 开发服务器(5173),连不上回退到本地 dist 构建产物;
|
||||
// 打包后固定加载 asar 内的 dist。渲染进程通过 preload 注入的 window.desktop 获取服务器地址
|
||||
const { app, BrowserWindow, Menu, Tray, nativeImage, ipcMain, shell, dialog, net } = require('electron')
|
||||
const { app, BrowserWindow, Menu, Tray, nativeImage, ipcMain, shell, dialog } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const updater = require('./update.cjs')
|
||||
|
||||
// 后端服务器默认地址(可在「菜单 → 服务器设置」中修改,保存在用户数据目录)
|
||||
// 生产桌面端默认连线上;本机调试可在菜单里改回 http://127.0.0.1:8080
|
||||
@@ -62,102 +63,12 @@ ipcMain.handle('nlg:set-server-base', (_e, url) => {
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 版本检查:拉取后端 /api/version/latest 与本地 app.getVersion() 比较
|
||||
// 后端返回 { version, download_url, release_notes, force }
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// 语义化版本比较:返回 -1/0/1(a<b / a==b / a>b),非法版本视为 0.0.0
|
||||
function compareVer(a, b) {
|
||||
const pa = String(a || '0').split('.').map((n) => parseInt(n, 10) || 0)
|
||||
const pb = String(b || '0').split('.').map((n) => parseInt(n, 10) || 0)
|
||||
const len = Math.max(pa.length, pb.length)
|
||||
for (let i = 0; i < len; i++) {
|
||||
const va = pa[i] || 0
|
||||
const vb = pb[i] || 0
|
||||
if (va > vb) return 1
|
||||
if (va < vb) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 拉取一次最新版本信息(用 Electron 内置 net,自动走系统代理)
|
||||
function fetchLatestVersion() {
|
||||
return new Promise((resolve) => {
|
||||
const base = readCfg().serverBase.replace(/\/+$/, '')
|
||||
if (!base) return resolve({ ok: false, msg: '未配置服务器地址' })
|
||||
const req = net.request({
|
||||
method: 'GET',
|
||||
url: base + '/api/version/latest',
|
||||
redirect: 'follow',
|
||||
})
|
||||
let body = ''
|
||||
req.on('response', (resp) => {
|
||||
// 3xx 已由 redirect:'follow' 处理;这里只关心 2xx 最终响应
|
||||
resp.on('data', (chunk) => (body += chunk.toString('utf8')))
|
||||
resp.on('end', () => {
|
||||
try {
|
||||
const parsed = JSON.parse(body)
|
||||
// 后端统一返回 { code, msg, data },code=0 表示成功
|
||||
const data = parsed && parsed.data ? parsed.data : parsed
|
||||
resolve({ ok: true, data })
|
||||
} catch {
|
||||
resolve({ ok: false, msg: '解析版本信息失败' })
|
||||
}
|
||||
})
|
||||
})
|
||||
req.on('error', (err) => resolve({ ok: false, msg: '网络错误:' + err.message }))
|
||||
// 超时兜底(10s)
|
||||
setTimeout(() => {
|
||||
try { req.abort() } catch {}
|
||||
resolve({ ok: false, msg: '请求超时' })
|
||||
}, 10000)
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
// 弹出更新对话框(silent=true 表示自动检查,已是最新版不弹窗)
|
||||
async function checkUpdate(silent = false) {
|
||||
const local = app.getVersion()
|
||||
const r = await fetchLatestVersion()
|
||||
if (!r.ok) {
|
||||
if (!silent) dialog.showErrorBox('检查更新失败', r.msg || '无法连接服务器')
|
||||
return
|
||||
}
|
||||
const { version, download_url: url, release_notes: notes, force } = r.data || {}
|
||||
if (compareVer(version, local) <= 0) {
|
||||
if (!silent) {
|
||||
dialog.showMessageBox(mainWin, {
|
||||
type: 'info',
|
||||
title: '已是最新版本',
|
||||
message: '当前已是最新版本',
|
||||
detail: `版本 ${local}`,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
const buttons = url ? ['立即下载', force ? '确定' : '稍后提醒'] : [force ? '确定' : '稍后提醒']
|
||||
const { response } = await dialog.showMessageBox(mainWin, {
|
||||
type: force ? 'warning' : 'info',
|
||||
title: '发现新版本',
|
||||
message: `发现新版本 ${version}`,
|
||||
detail: notes || '请下载最新版本安装包覆盖安装。',
|
||||
buttons,
|
||||
defaultId: 0,
|
||||
cancelId: buttons.length - 1,
|
||||
noLink: true,
|
||||
})
|
||||
// 点了「立即下载」(按钮 0 且存在下载链接);相对路径拼服务器基址
|
||||
if (response === 0 && url) {
|
||||
const abs = /^https?:\/\//i.test(url)
|
||||
? url
|
||||
: readCfg().serverBase.replace(/\/+$/, '') + (url.startsWith('/') ? url : '/' + url)
|
||||
shell.openExternal(abs)
|
||||
}
|
||||
}
|
||||
|
||||
// IPC:渲染进程可触发检查更新(预留菜单按钮 / 页面入口)
|
||||
ipcMain.handle('nlg:check-update', (_e, silent) => checkUpdate(silent))
|
||||
// 版本检查 / 应用内下载安装(见 update.cjs)
|
||||
updater.init({
|
||||
getServerBase: () => readCfg().serverBase,
|
||||
getMainWindow: () => mainWin,
|
||||
setQuitting: (v) => { isQuitting = v },
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 服务器设置窗口(极简内嵌页面,深色风格与主站一致)
|
||||
@@ -244,7 +155,7 @@ function createTray() {
|
||||
tray.setToolTip('像素游戏厅')
|
||||
tray.setContextMenu(Menu.buildFromTemplate([
|
||||
{ label: '显示主窗口', click: () => showMainWindow() },
|
||||
{ label: '检查更新…', click: () => checkUpdate(false) },
|
||||
{ label: '检查更新…', click: () => updater.checkUpdate(false) },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出',
|
||||
@@ -268,7 +179,7 @@ function buildMenu() {
|
||||
label: '应用',
|
||||
submenu: [
|
||||
{ label: '服务器设置…', accelerator: 'CmdOrCtrl+,', click: openSettings },
|
||||
{ label: '检查更新…', click: () => checkUpdate(false) },
|
||||
{ label: '检查更新…', click: () => updater.checkUpdate(false) },
|
||||
{ type: 'separator' },
|
||||
{ label: '刷新', role: 'reload' },
|
||||
{ label: '强制刷新(忽略缓存)', role: 'forceReload' },
|
||||
@@ -356,7 +267,7 @@ function createWindow() {
|
||||
if (cfg.winMax) mainWin.maximize()
|
||||
mainWin.show()
|
||||
// 启动后 3 秒静默检查一次更新(已是最新版不弹窗,发现新版本才提示)
|
||||
setTimeout(() => { checkUpdate(true).catch(() => {}) }, 3000)
|
||||
setTimeout(() => { updater.checkUpdate(true).catch(() => {}) }, 3000)
|
||||
})
|
||||
// 点关闭:记住窗口状态并隐藏到托盘(冒烟模式 / 真正退出时直接关)
|
||||
mainWin.on('close', (e) => {
|
||||
|
||||
@@ -12,6 +12,8 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
setServerBase: (url) => ipcRenderer.invoke('nlg:set-server-base', url),
|
||||
// 检查桌面端新版本(silent=true 时已是最新版不弹窗,用于启动自动检查)
|
||||
checkUpdate: (silent = false) => ipcRenderer.invoke('nlg:check-update', silent),
|
||||
// 取消正在进行的更新下载
|
||||
cancelUpdateDownload: () => ipcRenderer.invoke('nlg:cancel-update-download'),
|
||||
// 运行环境版本信息
|
||||
versions: {
|
||||
electron: process.versions.electron,
|
||||
|
||||
385
electron/update.cjs
Normal file
385
electron/update.cjs
Normal file
@@ -0,0 +1,385 @@
|
||||
// 桌面端检查更新:拉取 /api/version/latest,应用内下载安装包并启动安装程序
|
||||
const { app, BrowserWindow, dialog, net, ipcMain, shell } = require('electron')
|
||||
const { spawn } = require('child_process')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const DOWNLOAD_TIMEOUT_MS = 15 * 60 * 1000
|
||||
|
||||
let deps = {
|
||||
getServerBase: () => '',
|
||||
getMainWindow: () => null,
|
||||
setQuitting: () => {},
|
||||
}
|
||||
|
||||
let busy = false
|
||||
let abortDownload = null
|
||||
|
||||
function init(next) {
|
||||
deps = { ...deps, ...next }
|
||||
ipcMain.handle('nlg:check-update', (_e, silent) => checkUpdate(!!silent))
|
||||
ipcMain.handle('nlg:cancel-update-download', () => {
|
||||
if (abortDownload) abortDownload()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function compareVer(a, b) {
|
||||
const pa = String(a || '0').split('.').map((n) => parseInt(n, 10) || 0)
|
||||
const pb = String(b || '0').split('.').map((n) => parseInt(n, 10) || 0)
|
||||
const len = Math.max(pa.length, pb.length)
|
||||
for (let i = 0; i < len; i++) {
|
||||
const va = pa[i] || 0
|
||||
const vb = pb[i] || 0
|
||||
if (va > vb) return 1
|
||||
if (va < vb) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function resolveDownloadUrl(url) {
|
||||
const base = String(deps.getServerBase() || '').replace(/\/+$/, '')
|
||||
if (!url) return ''
|
||||
try {
|
||||
const u = new URL(url, base + '/')
|
||||
const m = String(u.pathname || '').match(/\/(?:api\/)?download\/([^/]+)$/i)
|
||||
if (m) {
|
||||
return base + '/api/download/' + encodeURIComponent(decodeURIComponent(m[1]))
|
||||
}
|
||||
return u.href
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(n) {
|
||||
n = Number(n) || 0
|
||||
if (n < 1024) return n + ' B'
|
||||
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB'
|
||||
return (n / 1024 / 1024).toFixed(1) + ' MB'
|
||||
}
|
||||
|
||||
function headerVal(headers, key) {
|
||||
const raw = headers[key] || headers[key.toLowerCase()] || headers[key.toUpperCase()]
|
||||
return Array.isArray(raw) ? raw[0] : (raw || '')
|
||||
}
|
||||
|
||||
function fetchLatestVersion() {
|
||||
return new Promise((resolve) => {
|
||||
const base = String(deps.getServerBase() || '').replace(/\/+$/, '')
|
||||
if (!base) return resolve({ ok: false, msg: '未配置服务器地址' })
|
||||
let settled = false
|
||||
const done = (v) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
resolve(v)
|
||||
}
|
||||
const req = net.request({
|
||||
method: 'GET',
|
||||
url: base + '/api/version/latest',
|
||||
redirect: 'follow',
|
||||
})
|
||||
let body = ''
|
||||
req.on('response', (resp) => {
|
||||
const code = resp.statusCode || 0
|
||||
if (code < 200 || code >= 300) {
|
||||
done({ ok: false, msg: '服务器返回 ' + code })
|
||||
return
|
||||
}
|
||||
resp.on('data', (chunk) => { body += chunk.toString('utf8') })
|
||||
resp.on('end', () => {
|
||||
try {
|
||||
const parsed = JSON.parse(body)
|
||||
if (parsed && typeof parsed.code === 'number' && parsed.code !== 0) {
|
||||
done({ ok: false, msg: parsed.msg || '检查更新失败' })
|
||||
return
|
||||
}
|
||||
const data = parsed && parsed.data ? parsed.data : parsed
|
||||
done({ ok: true, data })
|
||||
} catch {
|
||||
done({ ok: false, msg: '解析版本信息失败' })
|
||||
}
|
||||
})
|
||||
})
|
||||
req.on('error', (err) => done({ ok: false, msg: '网络错误:' + err.message }))
|
||||
const timer = setTimeout(() => {
|
||||
try { req.abort() } catch { /* ignore */ }
|
||||
done({ ok: false, msg: '请求超时' })
|
||||
}, 10000)
|
||||
req.on('close', () => clearTimeout(timer))
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
function downloadInstaller(url, dest, onProgress) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false
|
||||
const finish = (err, val) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
abortDownload = null
|
||||
if (err) reject(err)
|
||||
else resolve(val)
|
||||
}
|
||||
const req = net.request({ method: 'GET', url, redirect: 'follow' })
|
||||
abortDownload = () => {
|
||||
try { req.abort() } catch { /* ignore */ }
|
||||
const e = new Error('已取消')
|
||||
e.cancelled = true
|
||||
finish(e)
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
try { req.abort() } catch { /* ignore */ }
|
||||
finish(new Error('下载超时,请检查网络后重试'))
|
||||
}, DOWNLOAD_TIMEOUT_MS)
|
||||
|
||||
req.on('response', (resp) => {
|
||||
const code = resp.statusCode || 0
|
||||
if (code < 200 || code >= 300) {
|
||||
clearTimeout(timer)
|
||||
finish(new Error('下载失败(HTTP ' + code + ')'))
|
||||
return
|
||||
}
|
||||
const ct = String(headerVal(resp.headers, 'content-type'))
|
||||
if (/text\/html/i.test(ct)) {
|
||||
clearTimeout(timer)
|
||||
finish(new Error('服务器返回了网页而不是安装包。请确认后台已上传安装包。'))
|
||||
return
|
||||
}
|
||||
const total = parseInt(headerVal(resp.headers, 'content-length'), 10) || 0
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
||||
const ws = fs.createWriteStream(dest)
|
||||
let loaded = 0
|
||||
resp.on('data', (chunk) => {
|
||||
loaded += chunk.length
|
||||
if (!ws.write(chunk) && typeof resp.pause === 'function') {
|
||||
resp.pause()
|
||||
ws.once('drain', () => { if (typeof resp.resume === 'function') resp.resume() })
|
||||
}
|
||||
if (onProgress) onProgress(loaded, total)
|
||||
})
|
||||
resp.on('end', () => { ws.end() })
|
||||
resp.on('error', (err) => {
|
||||
clearTimeout(timer)
|
||||
try { ws.destroy() } catch { /* ignore */ }
|
||||
finish(err)
|
||||
})
|
||||
ws.on('finish', () => {
|
||||
clearTimeout(timer)
|
||||
finish(null, { size: loaded, total })
|
||||
})
|
||||
ws.on('error', (err) => {
|
||||
clearTimeout(timer)
|
||||
finish(err)
|
||||
})
|
||||
})
|
||||
req.on('error', (err) => {
|
||||
clearTimeout(timer)
|
||||
finish(err)
|
||||
})
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
function verifyInstaller(filePath, expectSize) {
|
||||
let st
|
||||
try {
|
||||
st = fs.statSync(filePath)
|
||||
} catch {
|
||||
return '安装包不存在'
|
||||
}
|
||||
// 网页被当成 exe 保存时通常只有几 KB;正式安装包约 100MB
|
||||
if (st.size < 64 * 1024) {
|
||||
return '文件过小,不是完整安装包(常见原因:下载地址被网站首页顶替)'
|
||||
}
|
||||
if (expectSize && Math.abs(st.size - expectSize) > 2048) {
|
||||
return `文件大小不符(已下 ${formatBytes(st.size)},期望 ${formatBytes(expectSize)})`
|
||||
}
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
if (ext === '.exe') {
|
||||
const buf = Buffer.alloc(2)
|
||||
const fd = fs.openSync(filePath, 'r')
|
||||
fs.readSync(fd, buf, 0, 2, 0)
|
||||
fs.closeSync(fd)
|
||||
if (buf[0] !== 0x4d || buf[1] !== 0x5a) {
|
||||
return '安装包损坏(不是有效的 Windows 程序)'
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function launchInstaller(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.platform === 'win32') {
|
||||
// start 脱离本进程,避免 app.quit() 把安装程序一起带走
|
||||
const cmd = process.env.ComSpec || 'cmd.exe'
|
||||
const child = spawn(cmd, ['/c', 'start', '', filePath], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
windowsHide: true,
|
||||
})
|
||||
child.once('error', reject)
|
||||
child.unref()
|
||||
setTimeout(resolve, 400)
|
||||
return
|
||||
}
|
||||
shell.openPath(filePath).then((err) => {
|
||||
if (err) reject(new Error(err))
|
||||
else resolve()
|
||||
}).catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
function openProgressWin(version) {
|
||||
const parent = deps.getMainWindow()
|
||||
const win = new BrowserWindow({
|
||||
width: 440,
|
||||
height: 230,
|
||||
parent: parent && !parent.isDestroyed() ? parent : undefined,
|
||||
modal: !!(parent && !parent.isDestroyed()),
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
autoHideMenuBar: true,
|
||||
backgroundColor: '#12142a',
|
||||
title: '正在下载更新',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.cjs'),
|
||||
contextIsolation: true,
|
||||
},
|
||||
})
|
||||
const html = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>下载更新</title>
|
||||
<style>
|
||||
body{margin:0;background:#12142a;color:#e8e9f5;font:14px/1.6 "Microsoft YaHei",sans-serif;padding:22px 26px}
|
||||
h3{margin:0 0 10px;font-size:15px}
|
||||
.bar{height:8px;border-radius:99px;background:#262a52;overflow:hidden;margin:12px 0 8px}
|
||||
.bar i{display:block;height:100%;width:0;background:linear-gradient(135deg,#6d6ff0,#9b6df0);transition:width .15s}
|
||||
.msg{font-size:12px;color:#8d90b3;min-height:18px}
|
||||
.row{display:flex;justify-content:flex-end;margin-top:16px}
|
||||
button{border:0;border-radius:8px;padding:7px 18px;font-size:13px;cursor:pointer;background:#262a52;color:#c6c8e8}
|
||||
</style></head><body>
|
||||
<h3>正在下载 ${String(version || '').replace(/[<>]/g, '')}</h3>
|
||||
<div class="bar"><i id="bar"></i></div>
|
||||
<div class="msg" id="msg">正在连接服务器…</div>
|
||||
<div class="row"><button id="cancel">取消</button></div>
|
||||
<script>
|
||||
window.__setProgress = (pct, text) => {
|
||||
document.getElementById('bar').style.width = Math.max(0, Math.min(100, pct)) + '%'
|
||||
if (text) document.getElementById('msg').textContent = text
|
||||
}
|
||||
document.getElementById('cancel').onclick = async () => {
|
||||
try { await window.desktop.cancelUpdateDownload() } catch (e) {}
|
||||
window.close()
|
||||
}
|
||||
</script>
|
||||
</body></html>`
|
||||
win.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(html))
|
||||
return win
|
||||
}
|
||||
|
||||
function setProgressUI(win, pct, text) {
|
||||
if (!win || win.isDestroyed()) return
|
||||
const js = `window.__setProgress && window.__setProgress(${Number(pct) || 0}, ${JSON.stringify(text)})`
|
||||
win.webContents.executeJavaScript(js).catch(() => {})
|
||||
}
|
||||
|
||||
async function downloadAndInstall({ version, url, size, filename }) {
|
||||
const destName = (filename && path.basename(String(filename))) || ('PixelArcade-Setup-' + version + '.exe')
|
||||
const dest = path.join(app.getPath('temp'), destName)
|
||||
try { fs.unlinkSync(dest) } catch { /* ignore */ }
|
||||
|
||||
const win = openProgressWin(version)
|
||||
win.on('closed', () => {
|
||||
if (abortDownload) abortDownload()
|
||||
})
|
||||
|
||||
try {
|
||||
setProgressUI(win, 0, '正在连接服务器…')
|
||||
const onProg = (loaded, total) => {
|
||||
const t = total || size || 0
|
||||
const pct = t ? Math.min(99, Math.round((loaded / t) * 100)) : 0
|
||||
setProgressUI(win, pct, `已下载 ${formatBytes(loaded)}${t ? ' / ' + formatBytes(t) : ''}`)
|
||||
}
|
||||
let r
|
||||
try {
|
||||
r = await downloadInstaller(url, dest, onProg)
|
||||
} catch (e) {
|
||||
if (e && e.cancelled) throw e
|
||||
const alt = String(url).replace('/api/download/', '/download/')
|
||||
if (alt === url) throw e
|
||||
setProgressUI(win, 0, '正在尝试备用下载地址…')
|
||||
r = await downloadInstaller(alt, dest, onProg)
|
||||
}
|
||||
setProgressUI(win, 99, '正在校验安装包…')
|
||||
const bad = verifyInstaller(dest, size || r.total || 0)
|
||||
if (bad) throw new Error(bad)
|
||||
setProgressUI(win, 100, '下载完成,即将启动安装程序…')
|
||||
await launchInstaller(dest)
|
||||
deps.setQuitting(true)
|
||||
app.quit()
|
||||
} catch (e) {
|
||||
try { if (!win.isDestroyed()) win.close() } catch { /* ignore */ }
|
||||
if (e && e.cancelled) return
|
||||
try { fs.unlinkSync(dest) } catch { /* ignore */ }
|
||||
const parent = deps.getMainWindow()
|
||||
const { response } = await dialog.showMessageBox(parent && !parent.isDestroyed() ? parent : undefined, {
|
||||
type: 'error',
|
||||
title: '下载更新失败',
|
||||
message: '无法下载或安装更新',
|
||||
detail: (e && e.message) || String(e),
|
||||
buttons: ['在浏览器中打开', '确定'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
noLink: true,
|
||||
})
|
||||
if (response === 0 && url) shell.openExternal(url)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkUpdate(silent = false) {
|
||||
if (busy) return
|
||||
const local = app.getVersion()
|
||||
const parent = () => {
|
||||
const w = deps.getMainWindow()
|
||||
return w && !w.isDestroyed() ? w : undefined
|
||||
}
|
||||
const r = await fetchLatestVersion()
|
||||
if (!r.ok) {
|
||||
if (!silent) dialog.showErrorBox('检查更新失败', r.msg || '无法连接服务器')
|
||||
return
|
||||
}
|
||||
const { version, download_url, release_notes: notes, force, size, filename } = r.data || {}
|
||||
if (compareVer(version, local) <= 0) {
|
||||
if (!silent) {
|
||||
dialog.showMessageBox(parent(), {
|
||||
type: 'info',
|
||||
title: '已是最新版本',
|
||||
message: '当前已是最新版本',
|
||||
detail: `版本 ${local}`,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
const url = resolveDownloadUrl(download_url)
|
||||
const buttons = url ? ['立即更新', force ? '确定' : '稍后提醒'] : [force ? '确定' : '稍后提醒']
|
||||
const { response } = await dialog.showMessageBox(parent(), {
|
||||
type: force ? 'warning' : 'info',
|
||||
title: '发现新版本',
|
||||
message: `发现新版本 ${version}`,
|
||||
detail: notes || '将下载安装包并启动安装程序,安装时请先关闭本应用。',
|
||||
buttons,
|
||||
defaultId: 0,
|
||||
cancelId: buttons.length - 1,
|
||||
noLink: true,
|
||||
})
|
||||
if (response === 0 && url) {
|
||||
busy = true
|
||||
try {
|
||||
await downloadAndInstall({ version, url, size, filename })
|
||||
} finally {
|
||||
busy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { init, checkUpdate, compareVer, resolveDownloadUrl }
|
||||
Reference in New Issue
Block a user