7c1c13526e
- 登录页(仅桌面端)新增"记住密码/自动登录"勾选与"更新版本"按钮:登录成功持久化凭据 (base64 存桌面 localStorage),打开登录页恢复勾选并静默登录;显式登出/切号后关闭自动登录 - 新增 shared/composables/useVersionUpdate 统一版本检测:/api/version/latest 与本机版本比较, 通过 pywebview 桥 get_app_version 读本地 exe 版本,缺桥时降级为可手动更新 - 首页保留 ↻ 图标并加显式"更新版本"按钮,接入新检测逻辑 - 首页背景改画到 .home-root,避免被 SPA 全局样式时序覆盖导致图不显示
145 lines
4.6 KiB
TypeScript
145 lines
4.6 KiB
TypeScript
/**
|
|
* 版本检测 / 更新共享逻辑(登录页、首页共用)
|
|
*
|
|
* 服务端化后桌面页面直载线上 Web:检测"是否有新版"需把线上 /api/version/latest
|
|
* 与"本机 exe 版本"比较。本机版本经 pywebview 桥 get_app_version() 获取(新版客户端
|
|
* 提供);旧客户端无此桥时降级:本机版本为空 → 只要线上有包就允许手动更新。
|
|
*/
|
|
|
|
import { ref } from 'vue'
|
|
import { getPywebviewApi } from '@/shared/bridges/pywebview'
|
|
|
|
function normVersion(value: unknown): string {
|
|
return String(value ?? '').trim().replace(/^[vV]/, '')
|
|
}
|
|
|
|
async function fetchJson(url: string): Promise<Record<string, unknown>> {
|
|
const resp = await window.fetch(url, { credentials: 'same-origin' })
|
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
|
const text = await resp.text()
|
|
if (!text) return {}
|
|
try {
|
|
return JSON.parse(text) as Record<string, unknown>
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 读取本机客户端版本号:优先桌面桥;无桥(旧版 exe / 浏览器环境)返回空串。
|
|
*/
|
|
export async function resolveLocalVersion(): Promise<string> {
|
|
const bridge = getPywebviewApi()
|
|
if (bridge?.get_app_version) {
|
|
try {
|
|
const res = await bridge.get_app_version()
|
|
if (res?.success && res.version) {
|
|
return normVersion(res.version)
|
|
}
|
|
} catch {
|
|
/* 忽略,走空版本降级 */
|
|
}
|
|
}
|
|
return ''
|
|
}
|
|
|
|
/** 拉取线上最新客户端版本(公开接口,无需登录;web_config 无记录时 version 为空)。 */
|
|
export async function fetchLatestVersion(): Promise<{ version: string; fileUrl: string }> {
|
|
const data = await fetchJson('/api/version/latest')
|
|
return { version: normVersion(data.version), fileUrl: String(data.file_url ?? '') }
|
|
}
|
|
|
|
export function useVersionUpdate() {
|
|
const checking = ref(false)
|
|
const updating = ref(false)
|
|
const currentVersion = ref('')
|
|
const latestVersion = ref('')
|
|
const fileUrl = ref('')
|
|
const hasUpdate = ref(false)
|
|
/** 本地版本未知但线上有包时也允许"立即更新",此标志标记可下载 */
|
|
const canDownload = ref(false)
|
|
const hint = ref('')
|
|
const checked = ref(false)
|
|
|
|
async function loadCurrentVersion() {
|
|
if (currentVersion.value) return
|
|
currentVersion.value = await resolveLocalVersion()
|
|
}
|
|
|
|
async function runCheck() {
|
|
if (checking.value) return
|
|
checking.value = true
|
|
hint.value = '正在检测更新...'
|
|
try {
|
|
const local = await resolveLocalVersion()
|
|
currentVersion.value = local
|
|
const latest = await fetchLatestVersion()
|
|
latestVersion.value = latest.version
|
|
fileUrl.value = latest.fileUrl
|
|
checked.value = true
|
|
|
|
if (!latest.version) {
|
|
hasUpdate.value = false
|
|
canDownload.value = false
|
|
hint.value = '暂无版本更新信息'
|
|
return
|
|
}
|
|
if (!local) {
|
|
// 旧客户端无本机版本桥:无法精确比较,线上有包即可手动更新
|
|
hasUpdate.value = false
|
|
canDownload.value = Boolean(latest.fileUrl)
|
|
hint.value = latest.fileUrl
|
|
? `线上最新版本 v${latest.version},可点击下方按钮手动更新`
|
|
: `线上最新版本 v${latest.version},暂无下载地址`
|
|
return
|
|
}
|
|
hasUpdate.value = local !== latest.version
|
|
canDownload.value = hasUpdate.value && Boolean(latest.fileUrl)
|
|
hint.value = hasUpdate.value
|
|
? `发现新版本 v${latest.version}${latest.fileUrl ? '' : ',暂无下载地址'}`
|
|
: `已是最新版本 v${local}`
|
|
} catch {
|
|
hasUpdate.value = false
|
|
canDownload.value = false
|
|
hint.value = '检测更新失败,请稍后重试'
|
|
} finally {
|
|
checking.value = false
|
|
}
|
|
}
|
|
|
|
async function doUpdate() {
|
|
if (updating.value || !fileUrl.value) return
|
|
if (!window.confirm('有更新,是否现在更新?\n更新将下载安装包并重启程序。')) return
|
|
updating.value = true
|
|
hint.value = '正在下载并准备更新,程序将自动退出...'
|
|
try {
|
|
const bridge = getPywebviewApi()
|
|
const result = bridge?.do_update_app
|
|
? await bridge.do_update_app(fileUrl.value)
|
|
: undefined
|
|
hint.value = result?.success
|
|
? '更新已启动,程序即将退出...'
|
|
: (result?.error || '当前环境不支持自动更新')
|
|
} catch {
|
|
hint.value = '请求更新失败,请重试'
|
|
} finally {
|
|
updating.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
checking,
|
|
updating,
|
|
currentVersion,
|
|
latestVersion,
|
|
fileUrl,
|
|
hasUpdate,
|
|
canDownload,
|
|
hint,
|
|
checked,
|
|
loadCurrentVersion,
|
|
runCheck,
|
|
doUpdate,
|
|
}
|
|
}
|