feat: 添加版本检查 API,支持从 Cloudflare Function 获取应用版本信息;更新相关配置文件
This commit is contained in:
101
functions/api/version.ts
Normal file
101
functions/api/version.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Cloudflare Pages Function - 版本检查 API
|
||||
* 访问路径: /api/version
|
||||
*/
|
||||
|
||||
interface VersionInfo {
|
||||
version: string;
|
||||
forceUpdate: boolean;
|
||||
updateMessage: string;
|
||||
updateUrl: string;
|
||||
minSupportVersion: string;
|
||||
}
|
||||
|
||||
interface Env {
|
||||
// 可以在这里添加环境变量类型
|
||||
}
|
||||
|
||||
interface VersionConfig {
|
||||
ios: VersionInfo;
|
||||
android: VersionInfo;
|
||||
web: VersionInfo;
|
||||
}
|
||||
|
||||
// 版本配置 - 直接在这里管理版本信息
|
||||
const versionConfig: VersionConfig = {
|
||||
ios: {
|
||||
version: "0.0.1",
|
||||
forceUpdate: false,
|
||||
updateMessage: "修复了一些问题并优化了性能",
|
||||
updateUrl: "https://apps.apple.com/app/id123456789",
|
||||
minSupportVersion: "0.9.0",
|
||||
},
|
||||
android: {
|
||||
version: "0.0.1",
|
||||
forceUpdate: false,
|
||||
updateMessage: "修复了一些问题并优化了性能",
|
||||
updateUrl: "https://play.google.com/store/apps/details?id=riwa.ionic.app",
|
||||
minSupportVersion: "0.9.0",
|
||||
},
|
||||
web: {
|
||||
version: "0.0.1",
|
||||
forceUpdate: false,
|
||||
updateMessage: "修复了一些问题并优化了性能",
|
||||
updateUrl: "",
|
||||
minSupportVersion: "0.9.0",
|
||||
},
|
||||
};
|
||||
|
||||
export const onRequestGet: PagesFunction<Env> = async (context) => {
|
||||
try {
|
||||
const { searchParams } = new URL(context.request.url);
|
||||
const platform = searchParams.get("platform") || "web";
|
||||
const currentVersion = searchParams.get("currentVersion") || "0.0.1";
|
||||
|
||||
// 验证平台参数
|
||||
if (!["ios", "android", "web"].includes(platform)) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Invalid platform parameter" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 获取对应平台的版本信息
|
||||
const versionInfo = versionConfig[platform as keyof VersionConfig];
|
||||
|
||||
// 返回版本信息
|
||||
return new Response(JSON.stringify(versionInfo), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "public, max-age=300", // 缓存5分钟
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Version check error:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Internal server error" }),
|
||||
{
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 支持 CORS 预检请求
|
||||
export const onRequestOptions: PagesFunction<Env> = async () => {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
},
|
||||
});
|
||||
};
|
||||
17
functions/tsconfig.json
Normal file
17
functions/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": ["ESNext"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["@cloudflare/workers-types"],
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -64,6 +64,7 @@
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "catalog:",
|
||||
"@capacitor/cli": "catalog:",
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@iconify-json/bx": "catalog:",
|
||||
"@iconify-json/circle-flags": "catalog:",
|
||||
"@iconify-json/cryptocurrency-color": "catalog:",
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -54,6 +54,9 @@ catalogs:
|
||||
'@capacitor/status-bar':
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20260113.0
|
||||
version: 4.20260113.0
|
||||
'@elysiajs/eden':
|
||||
specifier: ^1.4.5
|
||||
version: 1.4.5
|
||||
@@ -399,6 +402,9 @@ importers:
|
||||
'@capacitor/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 8.0.0
|
||||
'@cloudflare/workers-types':
|
||||
specifier: 'catalog:'
|
||||
version: 4.20260113.0
|
||||
'@iconify-json/bx':
|
||||
specifier: 'catalog:'
|
||||
version: 1.2.2
|
||||
@@ -521,7 +527,7 @@ importers:
|
||||
version: 7.4.0
|
||||
wrangler:
|
||||
specifier: 'catalog:'
|
||||
version: 4.54.0
|
||||
version: 4.54.0(@cloudflare/workers-types@4.20260113.0)
|
||||
|
||||
packages/distribute:
|
||||
dependencies:
|
||||
@@ -1323,6 +1329,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@cloudflare/workers-types@4.20260113.0':
|
||||
resolution: {integrity: sha512-CS2tUdGn1EMAV5GoFYYUfsZ4vwwXiYxwrUiI8ZRkxrJGqkHNGily/5Zf+vt/wh1HSoiCIChNYiuLEoCA/XUybw==}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -10499,6 +10508,8 @@ snapshots:
|
||||
'@cloudflare/workerd-windows-64@1.20251210.0':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workers-types@4.20260113.0': {}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
@@ -19654,7 +19665,7 @@ snapshots:
|
||||
'@cloudflare/workerd-linux-arm64': 1.20251210.0
|
||||
'@cloudflare/workerd-windows-64': 1.20251210.0
|
||||
|
||||
wrangler@4.54.0:
|
||||
wrangler@4.54.0(@cloudflare/workers-types@4.20260113.0):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.4.1
|
||||
'@cloudflare/unenv-preset': 2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251210.0)
|
||||
@@ -19665,6 +19676,7 @@ snapshots:
|
||||
unenv: 2.0.0-rc.24
|
||||
workerd: 1.20251210.0
|
||||
optionalDependencies:
|
||||
'@cloudflare/workers-types': 4.20260113.0
|
||||
fsevents: 2.3.3
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
catalogMode: prefer
|
||||
shellEmulator: true
|
||||
trustPolicy: no-downgrade
|
||||
|
||||
packages:
|
||||
- packages/*
|
||||
|
||||
catalog:
|
||||
'@riwa/api-types': http://192.168.1.7:9528/api/riwa-eden-0.0.138.tgz
|
||||
'@antfu/eslint-config': ^6.6.1
|
||||
'@capacitor-community/barcode-scanner': ^4.0.1
|
||||
'@capacitor-mlkit/barcode-scanning': ^8.0.0
|
||||
'@capacitor/app': 8.0.0
|
||||
'@capacitor/barcode-scanner': ^3.0.0
|
||||
'@capacitor/camera': ^8.0.0
|
||||
'@capacitor/cli': 8.0.0
|
||||
'@capacitor/clipboard': ^8.0.0
|
||||
'@capacitor/core': 8.0.0
|
||||
'@capacitor/file-transfer': ^2.0.0
|
||||
@@ -21,35 +18,8 @@ catalog:
|
||||
'@capacitor/keyboard': 8.0.0
|
||||
'@capacitor/share': ^8.0.0
|
||||
'@capacitor/status-bar': 8.0.0
|
||||
'@cloudflare/workers-types': ^4.20260113.0
|
||||
'@elysiajs/eden': ^1.4.5
|
||||
'@ionic/vue': ^8.7.11
|
||||
'@ionic/vue-router': ^8.7.11
|
||||
'@tailwindcss/vite': ^4.1.18
|
||||
'@vee-validate/zod': ^4.15.1
|
||||
'@vueuse/core': ^14.1.0
|
||||
'@vueuse/integrations': ^14.1.0
|
||||
'@vueuse/router': ^14.1.0
|
||||
'better-auth': ^1.4.6
|
||||
'dayjs': ^1.11.19
|
||||
'ethers': ^6.16.0
|
||||
'html2canvas': ^1.4.1
|
||||
'ionicons': ^8.0.13
|
||||
'jsqr': ^1.4.0
|
||||
'lightweight-charts': ^5.1.0
|
||||
'lodash-es': ^4.17.21
|
||||
'markdown-it': ^14.1.0
|
||||
'pinia': ^3.0.4
|
||||
'qr-scanner-wechat': ^0.1.3
|
||||
'qrcode': ^1.5.4
|
||||
'tailwindcss': ^4.1.18
|
||||
'vconsole': ^3.15.1
|
||||
'vee-validate': ^4.15.1
|
||||
'vue': ^3.5.25
|
||||
'vue-i18n': ^11.2.2
|
||||
'vue-router': ^4.6.3
|
||||
'zod': ^3.24.1
|
||||
'@antfu/eslint-config': ^6.6.1
|
||||
'@capacitor/cli': 8.0.0
|
||||
'@iconify-json/bx': ^1.2.2
|
||||
'@iconify-json/circle-flags': ^1.2.10
|
||||
'@iconify-json/cryptocurrency-color': ^1.2.4
|
||||
@@ -62,32 +32,65 @@ catalog:
|
||||
'@iconify-json/tdesign': ^1.2.11
|
||||
'@iconify/vue': ^5.0.0
|
||||
'@ionic/cli': ^7.2.1
|
||||
'@ionic/vue': ^8.7.11
|
||||
'@ionic/vue-router': ^8.7.11
|
||||
'@riwa/api-types': http://192.168.1.7:9528/api/riwa-eden-0.0.138.tgz
|
||||
'@tailwindcss/vite': ^4.1.18
|
||||
'@types/lodash-es': ^4.17.12
|
||||
'@types/node': ^24.10.2
|
||||
'@types/qrcode': ^1.5.6
|
||||
'@vee-validate/zod': ^4.15.1
|
||||
'@vitejs/plugin-basic-ssl': ^2.1.3
|
||||
'@vitejs/plugin-legacy': ^7.2.1
|
||||
'@vitejs/plugin-vue': ^6.0.2
|
||||
'@vitejs/plugin-vue-jsx': ^5.1.2
|
||||
'@vue/eslint-config-typescript': ^14.6.0
|
||||
'@vue/test-utils': ^2.4.6
|
||||
'cypress': ^15.7.1
|
||||
'dotenv': ^17.2.3
|
||||
'eslint': ^9.39.1
|
||||
'eslint-plugin-format': ^1.1.0
|
||||
'eslint-plugin-vue': ^10.6.2
|
||||
'jiti': ^2.6.1
|
||||
'jsdom': ^27.3.0
|
||||
'lint-staged': ^16.2.7
|
||||
'simple-git-hooks': ^2.13.1
|
||||
'terser': ^5.44.1
|
||||
'typescript': ~5.9.3
|
||||
'unplugin-auto-import': ^20.3.0
|
||||
'unplugin-icons': ^22.5.0
|
||||
'unplugin-vue-components': ^30.0.0
|
||||
'vite': ^7.2.7
|
||||
'vite-plugin-pwa': ^1.2.0
|
||||
'vitest': ^4.0.15
|
||||
'vue-tsc': ^3.1.8
|
||||
'workbox-window': ^7.4.0
|
||||
'wrangler': ^4.54.0
|
||||
'@vueuse/core': ^14.1.0
|
||||
'@vueuse/integrations': ^14.1.0
|
||||
'@vueuse/router': ^14.1.0
|
||||
better-auth: ^1.4.6
|
||||
cypress: ^15.7.1
|
||||
dayjs: ^1.11.19
|
||||
dotenv: ^17.2.3
|
||||
eslint: ^9.39.1
|
||||
eslint-plugin-format: ^1.1.0
|
||||
eslint-plugin-vue: ^10.6.2
|
||||
ethers: ^6.16.0
|
||||
html2canvas: ^1.4.1
|
||||
ionicons: ^8.0.13
|
||||
jiti: ^2.6.1
|
||||
jsdom: ^27.3.0
|
||||
jsqr: ^1.4.0
|
||||
lightweight-charts: ^5.1.0
|
||||
lint-staged: ^16.2.7
|
||||
lodash-es: ^4.17.21
|
||||
markdown-it: ^14.1.0
|
||||
pinia: ^3.0.4
|
||||
qr-scanner-wechat: ^0.1.3
|
||||
qrcode: ^1.5.4
|
||||
simple-git-hooks: ^2.13.1
|
||||
tailwindcss: ^4.1.18
|
||||
terser: ^5.44.1
|
||||
typescript: ~5.9.3
|
||||
unplugin-auto-import: ^20.3.0
|
||||
unplugin-icons: ^22.5.0
|
||||
unplugin-vue-components: ^30.0.0
|
||||
vconsole: ^3.15.1
|
||||
vee-validate: ^4.15.1
|
||||
vite: ^7.2.7
|
||||
vite-plugin-pwa: ^1.2.0
|
||||
vitest: ^4.0.15
|
||||
vue: ^3.5.25
|
||||
vue-i18n: ^11.2.2
|
||||
vue-router: ^4.6.3
|
||||
vue-tsc: ^3.1.8
|
||||
workbox-window: ^7.4.0
|
||||
wrangler: ^4.54.0
|
||||
zod: ^3.24.1
|
||||
|
||||
catalogMode: prefer
|
||||
|
||||
shellEmulator: true
|
||||
|
||||
trustPolicy: off
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { VersionInfo } from "@/api/types";
|
||||
import { App } from "@capacitor/app";
|
||||
import { alertController } from "@ionic/vue";
|
||||
import { client } from "@/api";
|
||||
import { i18n } from "@/locales";
|
||||
|
||||
/**
|
||||
@@ -28,12 +27,6 @@ function compareVersion(version1: string, version2: string): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const updateUrls = {
|
||||
ios: "https://apps.apple.com/app/id123456789",
|
||||
android: "https://play.google.com/store/apps/details?id=riwa.ionic.app",
|
||||
browser: "",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 应用更新检查
|
||||
*/
|
||||
@@ -81,40 +74,53 @@ export function useAppUpdate() {
|
||||
updateUrl?: string;
|
||||
}> {
|
||||
state.isChecking = true;
|
||||
if (import.meta.env.DEV) {
|
||||
const current = await getCurrentVersion();
|
||||
// 开发环境下不检查更新
|
||||
state.currentVersion = current;
|
||||
state.hasUpdate = false;
|
||||
state.latestVersion = current;
|
||||
state.forceUpdate = false;
|
||||
state.updateMessage = "";
|
||||
state.updateUrl = "";
|
||||
state.isChecking = false;
|
||||
return {
|
||||
hasUpdate: false,
|
||||
currentVersion: current,
|
||||
};
|
||||
}
|
||||
try {
|
||||
// 1. 获取当前版本
|
||||
const current = await getCurrentVersion();
|
||||
state.currentVersion = current;
|
||||
// 2. 从服务器检查最新版本
|
||||
// TODO: 后端接口实现后替换为真实 API 调用
|
||||
// const response = await client.api.app.version.get({
|
||||
// query: {
|
||||
// platform: platform === 'ios' ? 'ios' : platform === 'android' ? 'android' : 'web',
|
||||
// currentVersion: current,
|
||||
// },
|
||||
// })
|
||||
|
||||
// 模拟后端返回数据(开发阶段)
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
const versionInfo: VersionInfo = {
|
||||
version: "0.0.1", // 模拟有新版本
|
||||
forceUpdate: false,
|
||||
updateMessage: "修复了一些问题并优化了性能",
|
||||
updateUrl: updateUrls[platform] || "",
|
||||
minSupportVersion: "0.0.1",
|
||||
};
|
||||
// 2. PWA 应用检查 Service Worker 更新
|
||||
if (platform === "browser" && "serviceWorker" in navigator) {
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
if (registration) {
|
||||
// 触发 Service Worker 更新检查
|
||||
await registration.update();
|
||||
}
|
||||
}
|
||||
|
||||
// 真实 API 调用后的逻辑
|
||||
// if (response.error) {
|
||||
// console.error('检查更新失败:', response.error)
|
||||
// return {
|
||||
// hasUpdate: false,
|
||||
// currentVersion: current,
|
||||
// }
|
||||
// }
|
||||
// const versionInfo = response.data as VersionInfo
|
||||
// 3. 从 Cloudflare Function 检查最新版本
|
||||
const apiUrl = window.location.origin;
|
||||
const response = await fetch(
|
||||
`${apiUrl}/api/version?platform=${platform}¤tVersion=${current}`,
|
||||
{ cache: "no-cache" }, // 不使用缓存,确保获取最新版本信息
|
||||
);
|
||||
|
||||
// 3. 版本比较
|
||||
if (!response.ok) {
|
||||
console.error("检查更新失败:", response.statusText);
|
||||
return {
|
||||
hasUpdate: false,
|
||||
currentVersion: current,
|
||||
};
|
||||
}
|
||||
|
||||
const versionInfo: VersionInfo = await response.json();
|
||||
|
||||
// 4. 版本比较
|
||||
const isNewVersion = compareVersion(current, versionInfo.version) < 0;
|
||||
|
||||
// 更新状态
|
||||
@@ -186,7 +192,7 @@ export function useAppUpdate() {
|
||||
text: i18n.global.t("app.update.now"),
|
||||
role: "confirm",
|
||||
handler: () => {
|
||||
openStoreUpdate();
|
||||
applyUpdate();
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -199,7 +205,7 @@ export function useAppUpdate() {
|
||||
text: i18n.global.t("app.update.now"),
|
||||
role: "confirm",
|
||||
handler: () => {
|
||||
openStoreUpdate();
|
||||
applyUpdate();
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -218,22 +224,63 @@ export function useAppUpdate() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Web 应用检查 Service Worker 更新
|
||||
if ("serviceWorker" in navigator) {
|
||||
// PWA 应用更新流程
|
||||
if (platform === "browser" && "serviceWorker" in navigator) {
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
if (registration?.waiting) {
|
||||
// 通知 Service Worker 跳过等待,立即激活
|
||||
registration.waiting.postMessage({ type: "SKIP_WAITING" });
|
||||
|
||||
// 等待 Service Worker 激活后重新加载页面
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
window.location.reload();
|
||||
});
|
||||
return;
|
||||
if (registration) {
|
||||
// 检查是否有等待激活的 Service Worker
|
||||
const waitingWorker = registration.waiting;
|
||||
if (waitingWorker) {
|
||||
// 通知 Service Worker 跳过等待,立即激活
|
||||
(waitingWorker as ServiceWorker).postMessage({ type: "SKIP_WAITING" });
|
||||
|
||||
// 等待控制器变更后重新加载
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
window.location.reload();
|
||||
}, { once: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有正在安装的 Service Worker
|
||||
if (registration.installing) {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.addEventListener("statechange", () => {
|
||||
if (installingWorker.state === "installed" && navigator.serviceWorker.controller) {
|
||||
// 新的 Service Worker 已安装,通知它跳过等待
|
||||
(installingWorker as ServiceWorker).postMessage({ type: "SKIP_WAITING" });
|
||||
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
window.location.reload();
|
||||
}, { once: true });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 手动触发更新检查
|
||||
try {
|
||||
await registration.update();
|
||||
// 等待一小段时间让更新完成
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 如果有等待的 Service Worker,激活它
|
||||
const waitingWorker = registration.waiting;
|
||||
if (waitingWorker) {
|
||||
(waitingWorker as ServiceWorker).postMessage({ type: "SKIP_WAITING" });
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
window.location.reload();
|
||||
}, { once: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Service Worker 更新失败:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有 Service Worker,直接重新加载
|
||||
// 如果以上都没有执行,直接重新加载页面
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
|
||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -1,6 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
/// <reference types="unplugin-icons/types/vue" />
|
||||
/// <reference types="@cloudflare/workers-types" />
|
||||
|
||||
import type { MessageSchema } from "@/locales";
|
||||
import {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"lib": ["ESNext", "DOM", "WebWorker"],
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
Reference in New Issue
Block a user