feat(V0.2): 版本方案落地 —— 前端/外壳解绑, build 标识统一, 外壳版本展示
Docker Build and Push / build-image (push) Successful in 1m5s

- check-version: 分组校验 (前端 package.json / 外壳 tauri.conf+Cargo),
  两组不再要求相等; 分支前缀校验只针对前端版本
- build-ota: build_id 改为 YYYYMMDDHHmmss (北京时间, 与前端 BUILD_ID
  同格式); sequence 保持毫秒仅用于判新
- 我的页: 当前版本恢复 v0.2.0 · build <原始BUILD_ID>; 新版本附 OTA
  build_id; 底部替换为外壳版本 (Tauri getVersion, Web 端 —)
- README: 合并遗留 readme 全部内容并按决议重写版本管理章节;
  新增 CHANGELOG.md (PATCH 仅明确提出时自增)
This commit is contained in:
jif2.zhang
2026-10-25 21:28:06 +08:00
parent 06aa48f6ad
commit 67be8d8f7b
18 changed files with 700 additions and 338 deletions
+16
View File
@@ -22,6 +22,19 @@ export function getCurrentVersionInfo() {
return { version: APP_VERSION, build: BUILD_ID }
}
// 构建标识统一化: 任意来源 (YYYYMMDDHHmmss / seq-<ms> / 毫秒数) → 'YYYYMMDDHHmmss'
// (北京时间, 与 vite BUILD_ID / OTA manifest.build_id 同格式; 无法解析返回 '')
export function fmtBuildId(v) {
if (v == null) return ''
if (typeof v === 'string' && v.startsWith('seq-')) return fmtBuildId(Number(v.slice(4)))
if (typeof v === 'string' && /^\d{14}$/.test(v)) return v
const ms = Number(v)
if (!Number.isFinite(ms) || ms <= 0) return ''
const d = new Date(ms + 8 * 3600 * 1000)
const p = (n, w) => String(n).padStart(w || 2, '0')
return `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}`
}
// 启动时: 确认当前资源可用 (Tauri only)
// 失败/异常都不抛, 仅 console.warn
export function initOta() {
@@ -114,6 +127,7 @@ async function checkByVersion() {
info: {
version: manifest.version,
sequence: manifest.sequence,
build: manifest.build_id || fmtBuildId(manifest.sequence),
notes: manifest.notes || '',
commit: manifest.commit || '',
mandatory: !!manifest.mandatory,
@@ -136,6 +150,7 @@ async function checkOtaUpdateTauri(devMode) {
info: {
version: r.version,
sequence: r.sequence,
build: fmtBuildId(r.sequence),
notes: r.notes,
mandatory: r.mandatory,
bundle_size: r.bundle_size,
@@ -168,6 +183,7 @@ async function checkOtaUpdateWeb(devMode) {
info: {
version: data.version,
sequence: data.sequence,
build: fmtBuildId(data.build_id || data.sequence),
notes: data.note || '',
commit: data.commit || '',
// web 端没有强制/mandatory 等字段, 仅展示
+15 -12
View File
@@ -2,10 +2,10 @@ import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Avatar, Button, List, ProgressBar, Switch, Toast } from 'antd-mobile'
import { useAuth } from '../auth/AuthContext.jsx'
import { APP_VERSION, BUILD_ID } from '../version.js'
import {
applyOtaUpdate,
checkOtaUpdate,
fmtBuildId,
getCurrentVersionInfo,
getOtaDevMode,
getOtaVersionInfo,
@@ -14,14 +14,7 @@ import {
setOtaDevMode,
} from '../api/ota.js'
// seq/build 时间戳 → 'MM-dd HH:mm' (sequence 为毫秒时间戳, build_id 形如 seq-<ms>)
export function fmtSeqTime(v) {
const ms = typeof v === 'string' && v.startsWith('seq-') ? Number(v.slice(4)) : Number(v)
if (!Number.isFinite(ms) || ms <= 0) return ''
const p = (n) => String(n).padStart(2, '0')
const d = new Date(ms)
return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
}
const inTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
// "我" 页: 顶部 NavBar + 用户头像卡片 + 信息列表 + OTA 升级区
// OTA 区:
@@ -47,6 +40,16 @@ export default function MePage() {
const [otaInfo, setOtaInfo] = useState(null) // 检查结果中的 info
const [otaVersion, setOtaVersion] = useState(null) // hotswap 当前激活的覆盖包版本
const [progress, setProgress] = useState({ received: 0, total: 0 })
const [shellVersion, setShellVersion] = useState('') // 外壳 (APK/桌面包) 版本, 独立维护
// 外壳版本: 经 Tauri API 读取 (Web 端为空, 底部显示 —)
useEffect(() => {
if (!inTauri) return
import('@tauri-apps/api/app')
.then((m) => m.getVersion())
.then(setShellVersion)
.catch((e) => console.warn('[shell] getVersion failed:', e))
}, [])
const [error, setError] = useState('')
// 判新模式: false(默认)=版本号比较, true=build sequence 比较; 值持久化在设备端
const [devMode, setDevMode] = useState(() => getOtaDevMode())
@@ -146,7 +149,7 @@ export default function MePage() {
{/* OTA 升级区 */}
<List className="me-list" mode="card" header="前端升级 (OTA)">
<List.Item extra={'v' + cur.version + (fmtSeqTime(cur.build) ? ' · ' + fmtSeqTime(cur.build) : '')}>当前版本</List.Item>
<List.Item extra={'v' + cur.version + (cur.build ? ' · build ' + cur.build : '')}>当前版本</List.Item>
<List.Item extra={otaVersion?.active ? 'v' + (otaVersion.version || '?') : '内置'}>
运行资源
</List.Item>
@@ -206,7 +209,7 @@ export default function MePage() {
</div>
</List.Item>
{otaState === 'available' && otaInfo && (
<List.Item extra={'v' + otaInfo.version + (fmtSeqTime(otaInfo.sequence) ? ' · ' + fmtSeqTime(otaInfo.sequence) : '')}>
<List.Item extra={'v' + otaInfo.version + (otaInfo.build ? ' · build ' + otaInfo.build : '')}>
新版本
{otaInfo.bundle_size ? ` (${(otaInfo.bundle_size / 1024).toFixed(0)} KB)` : ''}
</List.Item>
@@ -253,7 +256,7 @@ export default function MePage() {
退出登录
</List.Item>
</List>
<div className="me-version">v{APP_VERSION} · build {BUILD_ID}</div>
<div className="me-version">外壳 v{shellVersion || '—'}</div>
</div>
)
}