feat(web): 桌面登录页记住密码/自动登录与版本更新,首页版本更新入口与背景修复
- 登录页(仅桌面端)新增"记住密码/自动登录"勾选与"更新版本"按钮:登录成功持久化凭据 (base64 存桌面 localStorage),打开登录页恢复勾选并静默登录;显式登出/切号后关闭自动登录 - 新增 shared/composables/useVersionUpdate 统一版本检测:/api/version/latest 与本机版本比较, 通过 pywebview 桥 get_app_version 读本地 exe 版本,缺桥时降级为可手动更新 - 首页保留 ↻ 图标并加显式"更新版本"按钮,接入新检测逻辑 - 首页背景改画到 .home-root,避免被 SPA 全局样式时序覆盖导致图不显示
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 版本检测 / 更新共享逻辑(登录页、首页共用)
|
||||
*
|
||||
* 服务端化后桌面页面直载线上 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user