From 9d71152d33008b09070b7b68a42dc10b8a795c4a Mon Sep 17 00:00:00 2001 From: Junhui Chen Date: Mon, 14 Jul 2025 14:00:31 +0800 Subject: [PATCH 1/6] feat: prod deploy (#6) Reviewed-on: https://git.fairclip.cn/FairClip/memowake-front/pulls/6 Co-authored-by: Junhui Chen Co-committed-by: Junhui Chen --- .gitea/workflows/prod.yaml | 37 +++++++++++++++++++++++++++++++++++++ scripts/prod_deploy.sh | 12 ++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .gitea/workflows/prod.yaml create mode 100644 scripts/prod_deploy.sh diff --git a/.gitea/workflows/prod.yaml b/.gitea/workflows/prod.yaml new file mode 100644 index 0000000..94ba759 --- /dev/null +++ b/.gitea/workflows/prod.yaml @@ -0,0 +1,37 @@ +name: Prod Deploy +run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 +on: + push: + tags: + - 'releases/*' + branches: + - 'releases/*' + +jobs: + Explore-Gitea-Actions: + runs-on: self_hosted + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" + - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." + - name: Check out repository code + uses: https://git.fairclip.cn/mirrors/actions-checkout@v4 + - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ gitea.workspace }} + - name: Build Docker Image + run: | + echo "Building the Docker image..." + # tag_name=${{ gitea.ref_name }} + tag_name=0.5.0.1 + docker build -t hub.fairclip.cn/memowake/memowake-front:${tag_name} . + docker push hub.fairclip.cn/memowake/memowake-front:${tag_name} + echo "Docker image built successfully!" + - name: Deploy + run: | + echo "Deploying the project..." + tag_name=0.5.0.1 + bash ./scripts/prod_deploy.sh ${tag_name} + echo "Deploy successful!" \ No newline at end of file diff --git a/scripts/prod_deploy.sh b/scripts/prod_deploy.sh new file mode 100644 index 0000000..4ebfc4a --- /dev/null +++ b/scripts/prod_deploy.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -x + +TAG=${1:-latest} + +CONTROLPANEL_HOST="101.132.185.243" +CONTROLPANEL_USER="ecs-user" +CONTROLPANEL_SSH_KEY="~/.ssh/id_ed25519" + +# SSH到CONTROLPANEL_HOST执行以下命令 +# helm install memowake-front . -n memowake +ssh ${CONTROLPANEL_USER}@${CONTROLPANEL_HOST} "cd /home/ecs-user/infra-deploy/memowake-front && helm upgrade memowake-front . -n memowake --set image.tag=${TAG}" \ No newline at end of file From c4b0dc14324740647b4736955f4f6acf1715de88 Mon Sep 17 00:00:00 2001 From: Junhui Chen Date: Mon, 14 Jul 2025 16:30:06 +0800 Subject: [PATCH 2/6] fix --- .gitea/workflows/prod.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/prod.yaml b/.gitea/workflows/prod.yaml index 94ba759..d9b8a6b 100644 --- a/.gitea/workflows/prod.yaml +++ b/.gitea/workflows/prod.yaml @@ -4,8 +4,6 @@ on: push: tags: - 'releases/*' - branches: - - 'releases/*' jobs: Explore-Gitea-Actions: @@ -24,14 +22,13 @@ jobs: - name: Build Docker Image run: | echo "Building the Docker image..." - # tag_name=${{ gitea.ref_name }} - tag_name=0.5.0.1 + tag_name=${{ gitea.ref_name }} docker build -t hub.fairclip.cn/memowake/memowake-front:${tag_name} . docker push hub.fairclip.cn/memowake/memowake-front:${tag_name} echo "Docker image built successfully!" - name: Deploy run: | echo "Deploying the project..." - tag_name=0.5.0.1 + tag_name=${{ gitea.ref_name }} bash ./scripts/prod_deploy.sh ${tag_name} echo "Deploy successful!" \ No newline at end of file From 4913e96586def3c032cceeed0cce9eeb748eb511 Mon Sep 17 00:00:00 2001 From: Junhui Chen Date: Mon, 14 Jul 2025 21:02:10 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E6=8E=A5=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E7=BA=BF=E4=B8=8Aapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 1 + .env.production | 1 + .gitea/workflows/prod.yaml | 4 +++- app.config.js | 9 +++++++++ dockerfiles/prod.Dockerfile | 17 +++++++++++++++++ lib/server-api-util.ts | 9 +++++---- 6 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 .env create mode 100644 .env.production create mode 100644 app.config.js create mode 100644 dockerfiles/prod.Dockerfile diff --git a/.env b/.env new file mode 100644 index 0000000..53a6945 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +API_ENDPOINT=http://192.168.31.115:18080/api \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..a102646 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +API_ENDPOINT=https://api.memorywake.com/api \ No newline at end of file diff --git a/.gitea/workflows/prod.yaml b/.gitea/workflows/prod.yaml index d9b8a6b..957e503 100644 --- a/.gitea/workflows/prod.yaml +++ b/.gitea/workflows/prod.yaml @@ -23,12 +23,14 @@ jobs: run: | echo "Building the Docker image..." tag_name=${{ gitea.ref_name }} - docker build -t hub.fairclip.cn/memowake/memowake-front:${tag_name} . + tag_name=$(echo "${{ gitea.ref_name }}" | tr '/' '-') + docker build -t hub.fairclip.cn/memowake/memowake-front:${tag_name} -f dockerfiles/prod.Dockerfile . docker push hub.fairclip.cn/memowake/memowake-front:${tag_name} echo "Docker image built successfully!" - name: Deploy run: | echo "Deploying the project..." tag_name=${{ gitea.ref_name }} + tag_name=$(echo "${{ gitea.ref_name }}" | tr '/' '-') bash ./scripts/prod_deploy.sh ${tag_name} echo "Deploy successful!" \ No newline at end of file diff --git a/app.config.js b/app.config.js new file mode 100644 index 0000000..5c730f6 --- /dev/null +++ b/app.config.js @@ -0,0 +1,9 @@ +// app.config.js +console.log("API_ENDPOINT from process.env:", process.env.API_ENDPOINT); + +export default ({ config }) => ({ + ...config, + extra: { + API_ENDPOINT: process.env.API_ENDPOINT || "http://192.168.31.115:18080/api" + } +}); \ No newline at end of file diff --git a/dockerfiles/prod.Dockerfile b/dockerfiles/prod.Dockerfile new file mode 100644 index 0000000..9210a8f --- /dev/null +++ b/dockerfiles/prod.Dockerfile @@ -0,0 +1,17 @@ +# 第一阶段:构建 Expo Web 静态文件 +FROM docker.fairclip.cn/node:22 AS builder +WORKDIR /app +COPY package.json . +# 设置npm源 +RUN npm config set registry http://192.168.31.115:8081/repository/npm/ +RUN npm install -g expo-cli && npm install +COPY . . +RUN cp .env.production .env +RUN npx expo export -p web + +# 第二阶段:使用 nginx 作为 Web 服务器 +FROM docker.fairclip.cn/nginx:1.29-alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/lib/server-api-util.ts b/lib/server-api-util.ts index 18d045c..7793c1c 100644 --- a/lib/server-api-util.ts +++ b/lib/server-api-util.ts @@ -23,13 +23,14 @@ export interface PagedResult { } // 获取.env文件中的变量 -export const API_ENDPOINT = "http://192.168.31.115:18080/api" + +export const API_ENDPOINT = () => process.env.API_ENDPOINT || "http://192.168.31.115:18080/api"; // 更新 access_token 的逻辑 - 用于React组件中 export const useAuthToken = async(message: string | null) => { try { const { login } = useAuth(); - const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`); + const response = await fetch(`${API_ENDPOINT()}/v1/iam/access-token-refresh`); const apiResponse: ApiResponse = await response.json(); // 如果接口报错,页面弹出来错误信息 @@ -71,7 +72,7 @@ export const refreshAuthToken = async(message: string | null): Promise // 退出刷新会重新填充数据 let response; - response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`, { + response = await fetch(`${API_ENDPOINT()}/v1/iam/access-token-refresh`, { method: "POST", body: JSON.stringify({ "refresh_token": cookie, @@ -139,7 +140,7 @@ export const fetchApi = async ( headers.set('Authorization', `Bearer ${token}`); } - const url = `${API_ENDPOINT}/v1${path}`; + const url = `${API_ENDPOINT()}/v1${path}`; const response = await fetch(url, { ...options, headers, From 0d7eb8f132c482ba70ed9936d5a1e17b1a81d487 Mon Sep 17 00:00:00 2001 From: Junhui Chen Date: Mon, 14 Jul 2025 21:09:19 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.config.js | 2 -- dockerfiles/prod.Dockerfile | 1 + lib/server-api-util.ts | 5 +++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app.config.js b/app.config.js index 5c730f6..aee3564 100644 --- a/app.config.js +++ b/app.config.js @@ -1,6 +1,4 @@ // app.config.js -console.log("API_ENDPOINT from process.env:", process.env.API_ENDPOINT); - export default ({ config }) => ({ ...config, extra: { diff --git a/dockerfiles/prod.Dockerfile b/dockerfiles/prod.Dockerfile index 9210a8f..84f7180 100644 --- a/dockerfiles/prod.Dockerfile +++ b/dockerfiles/prod.Dockerfile @@ -7,6 +7,7 @@ RUN npm config set registry http://192.168.31.115:8081/repository/npm/ RUN npm install -g expo-cli && npm install COPY . . RUN cp .env.production .env +ENV API_ENDPOINT=https://api.memorywake.com/api RUN npx expo export -p web # 第二阶段:使用 nginx 作为 Web 服务器 diff --git a/lib/server-api-util.ts b/lib/server-api-util.ts index 7793c1c..5f9d009 100644 --- a/lib/server-api-util.ts +++ b/lib/server-api-util.ts @@ -1,4 +1,5 @@ import { setCredentials } from '@/features/auth/authSlice'; +import Constants from 'expo-constants'; import * as SecureStore from 'expo-secure-store'; import { Platform } from 'react-native'; import Toast from 'react-native-toast-message'; @@ -24,7 +25,7 @@ export interface PagedResult { // 获取.env文件中的变量 -export const API_ENDPOINT = () => process.env.API_ENDPOINT || "http://192.168.31.115:18080/api"; +export const API_ENDPOINT = () => Constants.expoConfig?.extra?.API_ENDPOINT || "http://192.168.31.115:18080/api"; // 更新 access_token 的逻辑 - 用于React组件中 export const useAuthToken = async(message: string | null) => { @@ -140,7 +141,7 @@ export const fetchApi = async ( headers.set('Authorization', `Bearer ${token}`); } - const url = `${API_ENDPOINT()}/v1${path}`; + const url = `${API_ENDPOINT}/v1${path}`; const response = await fetch(url, { ...options, headers, From edf03a04eec43e3c97f5945cad09a67adcef42bc Mon Sep 17 00:00:00 2001 From: Junhui Chen Date: Mon, 14 Jul 2025 21:16:42 +0800 Subject: [PATCH 5/6] fix: server api util --- lib/server-api-util.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/server-api-util.ts b/lib/server-api-util.ts index 5f9d009..b122f7f 100644 --- a/lib/server-api-util.ts +++ b/lib/server-api-util.ts @@ -25,13 +25,13 @@ export interface PagedResult { // 获取.env文件中的变量 -export const API_ENDPOINT = () => Constants.expoConfig?.extra?.API_ENDPOINT || "http://192.168.31.115:18080/api"; +export const API_ENDPOINT = Constants.expoConfig?.extra?.API_ENDPOINT || "http://192.168.31.115:18080/api"; // 更新 access_token 的逻辑 - 用于React组件中 export const useAuthToken = async(message: string | null) => { try { const { login } = useAuth(); - const response = await fetch(`${API_ENDPOINT()}/v1/iam/access-token-refresh`); + const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`); const apiResponse: ApiResponse = await response.json(); // 如果接口报错,页面弹出来错误信息 @@ -73,7 +73,7 @@ export const refreshAuthToken = async(message: string | null): Promise // 退出刷新会重新填充数据 let response; - response = await fetch(`${API_ENDPOINT()}/v1/iam/access-token-refresh`, { + response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`, { method: "POST", body: JSON.stringify({ "refresh_token": cookie, From 04693b805e976a537e857c9fe35149048898e8ab Mon Sep 17 00:00:00 2001 From: Junhui Chen Date: Wed, 16 Jul 2025 14:28:24 +0800 Subject: [PATCH 6/6] feat: download page --- app/(tabs)/_layout.tsx | 10 ++++++ app/(tabs)/download.tsx | 63 ++++++++++++++++++++++++++++++++++ assets/icons/svg/android.svg | 1 + assets/icons/svg/apple.svg | 1 + i18n/index.ts | 2 +- i18n/locales/en/download.json | 6 ++++ i18n/locales/zh/download.json | 6 ++++ i18n/translations-generated.ts | 12 ++++--- package-lock.json | 12 +++++++ package.json | 1 + 10 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 app/(tabs)/download.tsx create mode 100644 assets/icons/svg/android.svg create mode 100644 assets/icons/svg/apple.svg create mode 100644 i18n/locales/en/download.json create mode 100644 i18n/locales/zh/download.json diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 9db1790..fc5c8d1 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -120,6 +120,16 @@ export default function TabLayout() { tabBarStyle: { display: 'none' } // 确保在标签栏中不显示 }} /> + {/* 下载 */} + null, // 隐藏底部标签栏 + headerShown: false, // 隐藏导航栏 + tabBarStyle: { display: 'none' } // 确保在标签栏中不显示 + }} + /> ); } diff --git a/app/(tabs)/download.tsx b/app/(tabs)/download.tsx new file mode 100644 index 0000000..35cd9d9 --- /dev/null +++ b/app/(tabs)/download.tsx @@ -0,0 +1,63 @@ +import AndroidLogo from '@/assets/icons/svg/android.svg'; +import AppleLogo from '@/assets/icons/svg/apple.svg'; +import MemoIP from '@/assets/icons/svg/memo-ip.svg'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useTranslation } from 'react-i18next'; +import { Linking, Text, TouchableOpacity, View } from 'react-native'; + +const IOS_APP_STORE_URL = 'https://apps.apple.com/cn/app/id6748205761'; +const ANDROID_APK_URL = 'https://cdn.memorywake.com/apks/application-f086a38c-dac1-43f1-9d24-e4378c2ce121.apk'; +export default function DownloadScreen() { + const handleIOSDownload = () => { + Linking.openURL(IOS_APP_STORE_URL); + }; + + const handleAndroidDownload = () => { + Linking.openURL(ANDROID_APK_URL); + }; + + const { t } = useTranslation(); + + return ( + + + + + + + MemoWake + + + {t('desc', { ns: 'download' })} + + + + + + + + {t('ios', { ns: 'download' })} + + + + + + + {t('android', { ns: 'download' })} + + + + + ); +} diff --git a/assets/icons/svg/android.svg b/assets/icons/svg/android.svg new file mode 100644 index 0000000..59f4ee2 --- /dev/null +++ b/assets/icons/svg/android.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg/apple.svg b/assets/icons/svg/apple.svg new file mode 100644 index 0000000..b590be7 --- /dev/null +++ b/assets/icons/svg/apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/i18n/index.ts b/i18n/index.ts index 8b5ff26..02374a2 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -17,7 +17,7 @@ i18n resources: translations, // 支持命名空间 - ns: ['common', 'example'], + ns: ['common', 'example', 'download'], defaultNS: 'common', // 设置默认语言为中文 diff --git a/i18n/locales/en/download.json b/i18n/locales/en/download.json new file mode 100644 index 0000000..faaec14 --- /dev/null +++ b/i18n/locales/en/download.json @@ -0,0 +1,6 @@ +{ + "title": "Download Our App", + "desc": "Get the full experience by downloading our app on your favorite platform.", + "ios": "Download for iOS", + "android": "Download for Android" +} \ No newline at end of file diff --git a/i18n/locales/zh/download.json b/i18n/locales/zh/download.json new file mode 100644 index 0000000..3b1a5d0 --- /dev/null +++ b/i18n/locales/zh/download.json @@ -0,0 +1,6 @@ +{ + "title": "下载我们的应用", + "desc": "在您喜欢的平台上下载我们的应用,以获得完整的体验。", + "ios": "下载 iOS 版", + "android": "下载 Android 版" +} \ No newline at end of file diff --git a/i18n/translations-generated.ts b/i18n/translations-generated.ts index be54939..4edacca 100644 --- a/i18n/translations-generated.ts +++ b/i18n/translations-generated.ts @@ -3,6 +3,7 @@ import enAdmin from './locales/en/admin.json'; import enAsk from './locales/en/ask.json'; import enCommon from './locales/en/common.json'; +import enDownload from './locales/en/download.json'; import enExample from './locales/en/example.json'; import enFairclip from './locales/en/fairclip.json'; import enLanding from './locales/en/landing.json'; @@ -12,6 +13,7 @@ import enUpload from './locales/en/upload.json'; import zhAdmin from './locales/zh/admin.json'; import zhAsk from './locales/zh/ask.json'; import zhCommon from './locales/zh/common.json'; +import zhDownload from './locales/zh/download.json'; import zhExample from './locales/zh/example.json'; import zhFairclip from './locales/zh/fairclip.json'; import zhLanding from './locales/zh/landing.json'; @@ -22,25 +24,27 @@ import zhUpload from './locales/zh/upload.json'; const translations = { en: { admin: enAdmin, + ask: enAsk, common: enCommon, + download: enDownload, example: enExample, fairclip: enFairclip, landing: enLanding, login: enLogin, personal: enPersonal, - upload: enUpload, - ask: enAsk + upload: enUpload }, zh: { admin: zhAdmin, + ask: zhAsk, common: zhCommon, + download: zhDownload, example: zhExample, fairclip: zhFairclip, landing: zhLanding, login: zhLogin, personal: zhPersonal, - upload: zhUpload, - ask: zhAsk + upload: zhUpload }, }; diff --git a/package-lock.json b/package-lock.json index 32c4a54..e5f8264 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "expo-image": "~2.3.2", "expo-image-manipulator": "~13.1.7", "expo-image-picker": "~16.1.4", + "expo-linear-gradient": "^14.1.5", "expo-linking": "~7.1.5", "expo-localization": "^16.1.5", "expo-location": "~18.1.5", @@ -8033,6 +8034,17 @@ "react": "*" } }, + "node_modules/expo-linear-gradient": { + "version": "14.1.5", + "resolved": "http://192.168.31.115:8081/repository/npm/expo-linear-gradient/-/expo-linear-gradient-14.1.5.tgz", + "integrity": "sha512-BSN3MkSGLZoHMduEnAgfhoj3xqcDWaoICgIr4cIYEx1GcHfKMhzA/O4mpZJ/WC27BP1rnAqoKfbclk1eA70ndQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-linking": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.6.tgz", diff --git a/package.json b/package.json index 6236ffb..8c846b5 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "expo-image": "~2.3.2", "expo-image-manipulator": "~13.1.7", "expo-image-picker": "~16.1.4", + "expo-linear-gradient": "^14.1.5", "expo-linking": "~7.1.5", "expo-localization": "^16.1.5", "expo-location": "~18.1.5",