diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2eddb2e..4c6fe2c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -149,4 +149,4 @@ TypeScript 文档:https://www.typescriptlang.org/ Capacitor 文档:https://capacitorjs.com/docs/getting-started TailwindCSS 文档:https://tailwindcss.com/docs/installation/using-vite TradingView Charting Library 文档:https://www.tradingview.com/widget-docs/getting-started/#getting-started -PWA 文档:https://vite-pwa-org.netlify.app/ \ No newline at end of file +PWA 文档:https://vite-pwa-org.netlify.app/ diff --git a/APP_UPDATE.md b/APP_UPDATE.md index 9249bee..00957e1 100644 --- a/APP_UPDATE.md +++ b/APP_UPDATE.md @@ -20,16 +20,16 @@ ```vue ``` @@ -38,6 +38,18 @@ onMounted(async () => { 在设置页面添加"检查更新"按钮: ```vue + + - - ``` ### 3. 自定义更新提示 @@ -86,19 +86,20 @@ const { updateMessage, checkForUpdate, openStoreUpdate, -} = useAppUpdate() +} = useAppUpdate(); async function checkUpdate() { - const result = await checkForUpdate() + const result = await checkForUpdate(); if (result.hasUpdate) { // 自定义提示逻辑 if (result.forceUpdate) { // 强制更新 - 阻止用户继续使用 - showForceUpdateModal() - } else { + showForceUpdateModal(); + } + else { // 可选更新 - 显示提示但允许跳过 - showOptionalUpdateToast() + showOptionalUpdateToast(); } } } @@ -108,26 +109,28 @@ async function checkUpdate() { ### 4. 显示版本信息 ```vue + + - - ``` ## API 参考 diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md index c06c232..c8b703b 100644 --- a/VERSION_MANAGEMENT.md +++ b/VERSION_MANAGEMENT.md @@ -51,23 +51,23 @@ curl https://your-domain.com/version.json ```typescript // Node.js / Elysia 示例 -app.get('/api/app/version', async (ctx) => { +app.get("/api/app/version", async (ctx) => { // 从前端静态资源读取版本 - const response = await fetch('https://your-frontend-domain.com/version.json') - const frontendVersion = await response.json() - - const { platform, currentVersion } = ctx.query - + const response = await fetch("https://your-frontend-domain.com/version.json"); + const frontendVersion = await response.json(); + + const { platform, currentVersion } = ctx.query; + return { version: frontendVersion.version, forceUpdate: compareVersion(currentVersion, frontendVersion.version) < 0, - updateMessage: '修复了一些问题', - updateUrl: platform === 'ios' - ? 'https://apps.apple.com/app/xxx' - : 'https://play.google.com/store/apps/details?id=xxx', - minSupportVersion: '0.0.1', - } -}) + updateMessage: "修复了一些问题", + updateUrl: platform === "ios" + ? "https://apps.apple.com/app/xxx" + : "https://play.google.com/store/apps/details?id=xxx", + minSupportVersion: "0.0.1", + }; +}); ``` **方式 2:部署时同步到后端** @@ -88,18 +88,18 @@ app.get('/api/app/version', async (ctx) => { 后端直接读取本地文件: ```typescript -import fs from 'fs' +import fs from "node:fs"; + +app.get("/api/app/version", async (ctx) => { + const versionFile = fs.readFileSync("/app/frontend-version.json", "utf-8"); + const { version } = JSON.parse(versionFile); -app.get('/api/app/version', async (ctx) => { - const versionFile = fs.readFileSync('/app/frontend-version.json', 'utf-8') - const { version } = JSON.parse(versionFile) - return { version, forceUpdate: false, // ... - } -}) + }; +}); ``` --- @@ -112,7 +112,7 @@ app.get('/api/app/version', async (ctx) => { ```json { - "version": "1.2.3" // 只改这里 + "version": "1.2.3" // 只改这里 } ``` @@ -162,27 +162,27 @@ versionName "1.2.3" // 与 package.json 保持一致 ```javascript #!/usr/bin/env node -import fs from 'fs' -import { execSync } from 'child_process' +import { execSync } from "node:child_process"; +import fs from "node:fs"; -const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf-8')) -const version = packageJson.version +const packageJson = JSON.parse(fs.readFileSync("./package.json", "utf-8")); +const version = packageJson.version; -console.log(`Syncing version ${version} to native projects...`) +console.log(`Syncing version ${version} to native projects...`); // iOS -const infoPlist = './ios/App/App/Info.plist' -let plistContent = fs.readFileSync(infoPlist, 'utf-8') +const infoPlist = "./ios/App/App/Info.plist"; +let plistContent = fs.readFileSync(infoPlist, "utf-8"); plistContent = plistContent.replace( /CFBundleShortVersionString<\/key>\s*.*?<\/string>/, `CFBundleShortVersionString\n\t${version}` -) -fs.writeFileSync(infoPlist, plistContent) +); +fs.writeFileSync(infoPlist, plistContent); // Android (需要安装 gradle 解析库或手动更新) -console.log('Please manually update android/app/build.gradle versionName') +console.log("Please manually update android/app/build.gradle versionName"); -console.log('✓ Version synced!') +console.log("✓ Version synced!"); ``` 在 `package.json` 中添加脚本: @@ -237,24 +237,24 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - + - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: '18' - + node-version: "18" + - name: Install dependencies run: pnpm install - + - name: Build run: pnpm run build env: NODE_ENV: production - + - name: Deploy to server run: | rsync -avz dist/ user@server:/var/www/html/ - + - name: Notify backend run: | curl -X POST https://your-backend.com/api/webhook/frontend-deployed \ @@ -284,107 +284,108 @@ jobs: **一次配置,永久受益!** 🎉 - ## 后端 API 实现示例 - 版本检查接口 ```ts -import { Elysia, t } from 'elysia' +// 方案二:从本地文件读取(适用于前后端部署在同一服务器) +import fs from "node:fs"; + +import { Elysia, t } from "elysia"; // 版本比较工具函数 function compareVersion(version1: string, version2: string): number { - const v1Parts = version1.split('.').map(Number) - const v2Parts = version2.split('.').map(Number) - const maxLength = Math.max(v1Parts.length, v2Parts.length) + const v1Parts = version1.split(".").map(Number); + const v2Parts = version2.split(".").map(Number); + const maxLength = Math.max(v1Parts.length, v2Parts.length); for (let i = 0; i < maxLength; i++) { - const v1Part = v1Parts[i] || 0 - const v2Part = v2Parts[i] || 0 + const v1Part = v1Parts[i] || 0; + const v2Part = v2Parts[i] || 0; - if (v1Part < v2Part) return -1 - if (v1Part > v2Part) return 1 + if (v1Part < v2Part) + return -1; + if (v1Part > v2Part) + return 1; } - return 0 + return 0; } // 方案一:从前端静态资源读取版本(推荐) async function getFrontendVersionFromURL(): Promise<{ - version: string - buildTime: string - gitCommit: string - environment: string + version: string; + buildTime: string; + gitCommit: string; + environment: string; }> { - const response = await fetch('https://your-frontend-domain.com/version.json') - return await response.json() + const response = await fetch("https://your-frontend-domain.com/version.json"); + return await response.json(); } -// 方案二:从本地文件读取(适用于前后端部署在同一服务器) -import fs from 'fs' - function getFrontendVersionFromFile(): { - version: string - buildTime: string - gitCommit: string - environment: string + version: string; + buildTime: string; + gitCommit: string; + environment: string; } { - const content = fs.readFileSync('/app/frontend-version.json', 'utf-8') - return JSON.parse(content) + const content = fs.readFileSync("/app/frontend-version.json", "utf-8"); + return JSON.parse(content); } // 应用商店链接配置 const APP_STORE_URLS = { - ios: 'https://apps.apple.com/app/id123456789', - android: 'https://play.google.com/store/apps/details?id=riwa.ionic.app', -} + ios: "https://apps.apple.com/app/id123456789", + android: "https://play.google.com/store/apps/details?id=riwa.ionic.app", +}; // 版本策略配置(可存储在数据库) const VERSION_POLICIES = { - minSupportVersion: '0.0.1', // 最低支持版本 - forceUpdateVersions: ['0.0.1'], // 需要强制更新的版本列表 + minSupportVersion: "0.0.1", // 最低支持版本 + forceUpdateVersions: ["0.0.1"], // 需要强制更新的版本列表 updateMessages: { - 'zh-CN': '修复了一些问题并优化了性能', - 'en-US': 'Bug fixes and performance improvements', + "zh-CN": "修复了一些问题并优化了性能", + "en-US": "Bug fixes and performance improvements", }, -} +}; // Elysia 路由定义 const app = new Elysia() .get( - '/api/app/version', + "/api/app/version", async ({ query, headers }) => { - const { platform, currentVersion } = query - const lang = headers['accept-language']?.split(',')[0] || 'en-US' + const { platform, currentVersion } = query; + const lang = headers["accept-language"]?.split(",")[0] || "en-US"; try { // 获取前端版本信息 - const frontendVersion = await getFrontendVersionFromURL() + const frontendVersion = await getFrontendVersionFromURL(); // 或使用本地文件: const frontendVersion = getFrontendVersionFromFile() // 判断是否需要更新 - const hasUpdate = compareVersion(currentVersion, frontendVersion.version) < 0 + const hasUpdate = compareVersion(currentVersion, frontendVersion.version) < 0; // 判断是否强制更新 - let forceUpdate = VERSION_POLICIES.forceUpdateVersions.includes(currentVersion) - + let forceUpdate = VERSION_POLICIES.forceUpdateVersions.includes(currentVersion); + // 检查是否低于最低支持版本 if (compareVersion(currentVersion, VERSION_POLICIES.minSupportVersion) < 0) { - forceUpdate = true + forceUpdate = true; } // 获取更新链接 - const updateUrl = platform === 'ios' - ? APP_STORE_URLS.ios - : platform === 'android' + const updateUrl = platform === "ios" + ? APP_STORE_URLS.ios + : platform === "android" ? APP_STORE_URLS.android - : '' + : ""; // 获取更新说明(多语言) - const updateMessage = VERSION_POLICIES.updateMessages[lang] - || VERSION_POLICIES.updateMessages['en-US'] + const updateMessage = VERSION_POLICIES.updateMessages[lang] + || VERSION_POLICIES.updateMessages["en-US"]; return { version: frontendVersion.version, - buildNumber: parseInt(frontendVersion.version.replace(/\./g, '')), + buildNumber: Number.parseInt(frontendVersion.version.replace(/\./g, "")), buildTime: frontendVersion.buildTime, gitCommit: frontendVersion.gitCommit, forceUpdate, @@ -392,26 +393,27 @@ const app = new Elysia() updateUrl, minSupportVersion: VERSION_POLICIES.minSupportVersion, releaseNotes: [ - '修复了已知问题', - '优化了应用性能', - '改进了用户界面', + "修复了已知问题", + "优化了应用性能", + "改进了用户界面", ], - } - } catch (error) { - console.error('Failed to get frontend version:', error) - + }; + } + catch (error) { + console.error("Failed to get frontend version:", error); + // 降级处理:返回当前版本,不强制更新 return { version: currentVersion, forceUpdate: false, - updateMessage: '', - updateUrl: '', - } + updateMessage: "", + updateUrl: "", + }; } }, { query: t.Object({ - platform: t.Union([t.Literal('ios'), t.Literal('android'), t.Literal('web')]), + platform: t.Union([t.Literal("ios"), t.Literal("android"), t.Literal("web")]), currentVersion: t.String(), }), response: t.Object({ @@ -426,13 +428,13 @@ const app = new Elysia() releaseNotes: t.Optional(t.Array(t.String())), }), } - ) + ); -export default app +export default app; /** * 使用示例: - * + * * 1. 启动后端服务 * 2. 前端请求:GET /api/app/version?platform=ios¤tVersion=0.0.1 * 3. 后端响应: @@ -448,9 +450,9 @@ export default app /** * 数据库存储方案(可选) - * + * * 如果需要更灵活的版本策略管理,可以将配置存储在数据库: - * + * * CREATE TABLE app_versions ( * id SERIAL PRIMARY KEY, * platform VARCHAR(20) NOT NULL, @@ -463,12 +465,11 @@ export default app * release_notes JSONB, * created_at TIMESTAMP DEFAULT NOW() * ); - * + * * 然后从数据库查询版本策略: * const policy = await db.query( * 'SELECT * FROM app_versions WHERE platform = $1 ORDER BY created_at DESC LIMIT 1', * [platform] * ) */ - -``` \ No newline at end of file +``` diff --git a/package.json b/package.json index c163565..68f146b 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@ionic/vue-router": "^8.7.11", "@riwa/api-types": "http://192.168.1.7:9527/api/riwa-eden-0.0.118.tgz", "@tailwindcss/vite": "^4.1.18", - "@vee-validate/yup": "^4.15.1", + "@vee-validate/zod": "^4.15.1", "@vueuse/core": "^14.1.0", "@vueuse/integrations": "^14.1.0", "@vueuse/router": "^14.1.0", @@ -55,7 +55,7 @@ "vue": "^3.5.25", "vue-i18n": "^11.2.2", "vue-router": "^4.6.3", - "yup": "^1.7.1" + "zod": "^3.24.1" }, "devDependencies": { "@antfu/eslint-config": "^6.6.1", diff --git a/packages/distribute/README.md b/packages/distribute/README.md index 00a1e4a..4c96899 100644 --- a/packages/distribute/README.md +++ b/packages/distribute/README.md @@ -50,19 +50,19 @@ pnpm generate ```typescript export const currentVersion: AppVersion = { - version: '1.0.0', - buildNumber: '100', - releaseDate: '2025-12-30', + version: "1.0.0", + buildNumber: "100", + releaseDate: "2025-12-30", releaseNotes: { - 'zh-CN': ['更新内容...'], - 'en-US': ['What\'s new...'], + "zh-CN": ["更新内容..."], + "en-US": ["What's new..."], }, downloads: { - ios: 'https://example.com/app.ipa', - android: 'https://example.com/app.apk', - h5: 'https://app.example.com', + ios: "https://example.com/app.ipa", + android: "https://example.com/app.apk", + h5: "https://app.example.com", }, -} +}; ``` ### 接入真实 API @@ -81,9 +81,9 @@ Nuxt UI 使用 TailwindCSS 4,可在 `nuxt.config.ts` 中配置: ```typescript export default defineNuxtConfig({ colorMode: { - preference: 'system', // 'light' | 'dark' | 'system' + preference: "system", // 'light' | 'dark' | 'system' }, -}) +}); ``` ## 目录结构 diff --git a/packages/distribute/app.config.ts b/packages/distribute/app.config.ts index 4b46711..cb02bbf 100644 --- a/packages/distribute/app.config.ts +++ b/packages/distribute/app.config.ts @@ -1,7 +1,7 @@ export default defineAppConfig({ ui: { colors: { - primary: 'blue', + primary: "blue", }, }, -}) +}); diff --git a/packages/distribute/app.vue b/packages/distribute/app.vue index ff1a8c8..7e669ce 100644 --- a/packages/distribute/app.vue +++ b/packages/distribute/app.vue @@ -6,4 +6,3 @@ - diff --git a/packages/distribute/assets/css/animations.css b/packages/distribute/assets/css/animations.css index a36388f..a9e1db5 100644 --- a/packages/distribute/assets/css/animations.css +++ b/packages/distribute/assets/css/animations.css @@ -64,7 +64,8 @@ /* 发光脉冲效果 */ @keyframes glow-pulse { - 0%, 100% { + 0%, + 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.3); } 50% { @@ -96,7 +97,8 @@ /* 悬浮动画 */ @keyframes float { - 0%, 100% { + 0%, + 100% { transform: translateY(0); } 50% { @@ -208,7 +210,8 @@ /* 脉冲缩放动画 */ @keyframes pulse-scale { - 0%, 100% { + 0%, + 100% { transform: scale(1); } 50% { @@ -250,7 +253,8 @@ /* 微妙的悬浮动画 */ @keyframes float-subtle { - 0%, 100% { + 0%, + 100% { transform: translateY(0) translateZ(0); } 50% { @@ -284,7 +288,8 @@ /* 发光脉冲动画 */ @keyframes pulse-glow { - 0%, 100% { + 0%, + 100% { opacity: 0.5; } 50% { @@ -298,7 +303,8 @@ /* 微妙的脉冲动画 */ @keyframes pulse-subtle { - 0%, 100% { + 0%, + 100% { opacity: 1; } 50% { @@ -362,7 +368,8 @@ /* 震动效果 */ @keyframes shake { - 0%, 100% { + 0%, + 100% { transform: translateX(0); } 25% { @@ -401,7 +408,8 @@ /* 呼吸灯效果 */ @keyframes breathe { - 0%, 100% { + 0%, + 100% { opacity: 0.6; transform: scale(1); } diff --git a/packages/distribute/components/PWAInstallBanner.vue b/packages/distribute/components/PWAInstallBanner.vue index a2cb516..681ddfb 100644 --- a/packages/distribute/components/PWAInstallBanner.vue +++ b/packages/distribute/components/PWAInstallBanner.vue @@ -1,59 +1,59 @@