Browse Source

Merge branch 'feat/增补test'

0es 2 months ago
parent
commit
0e16448046

+ 0 - 2
app/api/playmate.ts

@@ -49,5 +49,3 @@ export const playmateApi = {
     return http.post<unknown>('/playmate/submit/refundVoucher', data)
   },
 }
-
-

+ 0 - 1
app/api/skill.ts

@@ -12,7 +12,6 @@ import type {
   SkillDetailVo,
   SkillOrderDetailRequest,
   SkillOrderInfoDetailVo,
-  SkillOrderInfoVo,
   SkillOrderListDTO,
   SkillSearchDTO,
   SkillQrCodeCreateDto,

+ 1 - 1
app/app.vue

@@ -12,7 +12,7 @@ useHead({
     {
       name: 'viewport',
       content:
-        'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no',
+        'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover',
     },
   ],
 })

+ 7 - 9
app/composables/useAboutPopup.ts

@@ -1,12 +1,12 @@
 import { reactive, readonly, ref } from 'vue'
 
-export type AboutItemKey =
-  | 'privacyPolicy'
-  | 'termsOfService'
-  | 'communityGuideline'
-  | 'broadcasterAgreement'
-  | 'aboutUs'
-  | 'contactUs'
+export type AboutItemKey
+  = | 'privacyPolicy'
+    | 'termsOfService'
+    | 'communityGuideline'
+    | 'broadcasterAgreement'
+    | 'aboutUs'
+    | 'contactUs'
 
 export interface AboutPopupOptions {
   /**
@@ -50,5 +50,3 @@ export const useAboutPopup = () => {
     select,
   }
 }
-
-

+ 1 - 1
app/pages/mine/share.vue

@@ -176,7 +176,7 @@ onMounted(async () => {
     const origin = window.location.origin
     const id = userProfile.value?.userNo
     if (id)
-      shareUrl.value = `${origin}/user/profile?id=${id}`
+      shareUrl.value = `${origin}/user/profile?id=${id}&share=web`
   }
 
   await refreshPlaymateInfo()

+ 2 - 0
app/pages/user/category.vue

@@ -84,6 +84,8 @@ const handleQrCodeOrder = async () => {
   if (!code)
     return
 
+  callDeepLink(`gami://app/qrcode/order?qrcode=${code}`)
+
   const data = await request(() => skillApi.viewOrderQrcode({ id: code }))
 
   if (!data)

+ 6 - 0
app/pages/user/profile.vue

@@ -91,6 +91,12 @@ const loadPlaymateInfo = async () => {
 
 onMounted(() => {
   loadPlaymateInfo()
+
+  const share = route.query.share as string | undefined
+  const id = route.query.id as string | undefined
+  if (share && id) {
+    callDeepLink(`gami://app/profile?uid=${id}`)
+  }
 })
 
 const stopFavorDragListeners = () => {

+ 20 - 0
app/plugins/blurScrollTop.client.ts

@@ -0,0 +1,20 @@
+export default defineNuxtPlugin(() => {
+  if (!import.meta.client) return
+
+  const onBlur = (e: FocusEvent) => {
+    const target = e.target as HTMLElement | null
+    if (!target) return
+
+    const tag = target.tagName
+    if (tag !== 'INPUT' && tag !== 'TEXTAREA') return
+
+    window.setTimeout(() => {
+      window.scrollTo(0, 0)
+      document.body.scrollTop = 0
+      document.documentElement.scrollTop = 0
+    }, 100)
+  }
+
+  // `blur` does not bubble; use capture to listen globally.
+  document.addEventListener('blur', onBlur, true)
+})

+ 0 - 1
app/types/api/index.ts

@@ -4,4 +4,3 @@ export * from './category'
 export * from './skill'
 export * from './upload'
 export * from './wallet'
-

+ 0 - 2
app/types/api/upload.ts

@@ -31,5 +31,3 @@ export interface BaseOssS3VO {
    */
   fileUrl: string
 }
-
-

+ 7 - 0
app/utils/helpers.ts

@@ -1,4 +1,5 @@
 import { toPng } from 'html-to-image'
+import { isIOS } from '~/utils/ua'
 
 export const formatNumber = (value: number) =>
   new Intl.NumberFormat().format(value)
@@ -50,3 +51,9 @@ export const callNativeScheme = (scheme: string) => {
     window.location.href = scheme
   }
 }
+
+export const callDeepLink = (url: string) => {
+  if (import.meta.client && !isIOS()) {
+    window.location.href = url
+  }
+}

+ 12 - 9
app/utils/request.ts

@@ -85,6 +85,9 @@ const generateSign = (id: string, time: number, token: string, secret: string, b
 export const createRequest = () => {
   const runtimeConfig = useRuntimeConfig()
   const { token, logout } = useAuth()
+  const nuxtApp = useNuxtApp()
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  const t = ((nuxtApp as any)?.$i18n?.t?.bind((nuxtApp as any)?.$i18n) as ((key: string) => string) | undefined) || ((key: string) => key)
 
   // API base configuration (read within a valid Nuxt context)
   const API_CONFIG = {
@@ -185,7 +188,7 @@ export const createRequest = () => {
 
       // Handle business errors
       handleApiError(data.code, data.msg)
-      throw new Error(data.msg || 'Request failed')
+      throw new Error(data.msg || t('common.errors.requestFailed'))
     }
   }
 
@@ -206,29 +209,29 @@ export const createRequest = () => {
           handleUnauthorized()
           break
         case 403:
-          showError('No permission to access this resource')
+          showError(t('common.errors.noPermission'))
           break
         case 404:
-          showError('Resource not found')
+          showError(t('common.errors.notFound'))
           break
         case 500:
-          showError('Server error, please try again later')
+          showError(t('common.errors.serverError'))
           break
         default:
-          showError(data?.msg || error?.message || 'Request failed')
+          showError(data?.msg || error?.message || t('common.errors.requestFailed'))
       }
     }
     else {
       // Network error or timeout
       const message = error?.message || ''
       if (message.includes('timeout')) {
-        showError('Request timeout, please try again')
+        showError(t('common.errors.timeout'))
       }
       else if (message.includes('Network Error')) {
-        showError('Network error, please check your connection')
+        showError(t('common.errors.network'))
       }
       else {
-        showError(message || 'Unknown error occurred')
+        showError(message || t('common.errors.unknownError'))
       }
     }
 
@@ -261,7 +264,7 @@ export const createRequest = () => {
       const route = useRoute()
       const redirect = route.fullPath
 
-      showError('Login expired, please login again')
+      showError(t('common.errors.loginExpired'))
       navigateTo({
         path: '/login',
         query: redirect && redirect !== '/login' ? { redirect } : undefined,

+ 13 - 10
app/utils/requestNative.ts

@@ -91,6 +91,9 @@ const generateSign = (id: string, time: number, token: string, secret: string, b
 export const createRequestNative = () => {
   const runtimeConfig = useRuntimeConfig()
   const { token, logout } = useAuth()
+  const nuxtApp = useNuxtApp()
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  const t = ((nuxtApp as any)?.$i18n?.t?.bind((nuxtApp as any)?.$i18n) as ((key: string) => string) | undefined) || ((key: string) => key)
 
   const API_CONFIG = {
     baseURL: runtimeConfig.public.apiBase || '/api',
@@ -108,7 +111,7 @@ export const createRequestNative = () => {
     const raw = window.GAMI_BRIDGE?.requestHeader
     if (!raw || typeof raw !== 'object') {
       console.error('Must in native context')
-      showError('Must in native context')
+      showError(t('common.errors.nativeOnly'))
 
       return {}
     }
@@ -194,7 +197,7 @@ export const createRequestNative = () => {
       }
 
       handleApiError(data.code, data.msg)
-      throw new Error(data.msg || 'Request failed')
+      throw new Error(data.msg || t('common.errors.requestFailed'))
     }
   }
 
@@ -211,23 +214,23 @@ export const createRequestNative = () => {
           handleUnauthorized()
           break
         case 403:
-          showError('No permission to access this resource')
+          showError(t('common.errors.noPermission'))
           break
         case 404:
-          showError('Resource not found')
+          showError(t('common.errors.notFound'))
           break
         case 500:
-          showError('Server error, please try again later')
+          showError(t('common.errors.serverError'))
           break
         default:
-          showError(data?.msg || error?.message || 'Request failed')
+          showError(data?.msg || error?.message || t('common.errors.requestFailed'))
       }
     }
     else {
       const message = error?.message || ''
-      if (message.includes('timeout')) showError('Request timeout, please try again')
-      else if (message.includes('Network Error')) showError('Network error, please check your connection')
-      else showError(message || 'Unknown error occurred')
+      if (message.includes('timeout')) showError(t('common.errors.timeout'))
+      else if (message.includes('Network Error')) showError(t('common.errors.network'))
+      else showError(message || t('common.errors.unknownError'))
     }
 
     return Promise.reject(error)
@@ -249,7 +252,7 @@ export const createRequestNative = () => {
       const route = useRoute()
       const redirect = route.fullPath
 
-      showError('Login expired, please login again')
+      showError(t('common.errors.loginExpired'))
       navigateTo({
         path: '/login',
         query: redirect && redirect !== '/login' ? { redirect } : undefined,

+ 21 - 0
app/utils/ua.ts

@@ -0,0 +1,21 @@
+export const isIOS = () => {
+  // Client-only: SSR should always return false.
+  if (!import.meta.client) return false
+
+  try {
+    const ua = (navigator.userAgent || '').toLowerCase()
+    if (/iphone|ipad|ipod/.test(ua)) return true
+
+    // iPadOS 13+ may report itself as "Macintosh" in desktop mode.
+    // Detect it by combining platform + touch capability.
+    const nav = navigator as Navigator & { maxTouchPoints?: number }
+    const platform = (nav.platform || '').toLowerCase()
+    const touchPoints = Number(nav.maxTouchPoints || 0)
+    if (platform === 'macintel' && touchPoints > 1) return true
+  }
+  catch {
+    // ignore
+  }
+
+  return false
+}

+ 12 - 1
i18n/locales/en.json

@@ -5,7 +5,18 @@
     "copied": "Copied to clipboard",
     "copyFailed": "Copy failed",
     "logOut": "Log out",
-    "unknown": "Unknown"
+    "unknown": "Unknown",
+    "errors": {
+      "loginExpired": "Login expired, please login again",
+      "nativeOnly": "Please open in the app",
+      "noPermission": "No permission",
+      "notFound": "Not found",
+      "serverError": "Server error, try again later",
+      "requestFailed": "Request failed",
+      "timeout": "Request timeout, try again",
+      "network": "Network error, check your connection",
+      "unknownError": "Unknown error"
+    }
   },
   "order": {
     "common": {

+ 12 - 1
i18n/locales/id.json

@@ -5,7 +5,18 @@
     "copied": "Disalin ke papan klip",
     "copyFailed": "Gagal menyalin",
     "logOut": "Keluar",
-    "unknown": "Tidak diketahui"
+    "unknown": "Tidak diketahui",
+    "errors": {
+      "loginExpired": "Sesi habis, login lagi",
+      "nativeOnly": "Buka di aplikasi",
+      "noPermission": "Tidak ada izin",
+      "notFound": "Tidak ditemukan",
+      "serverError": "Server bermasalah, coba lagi",
+      "requestFailed": "Permintaan gagal",
+      "timeout": "Waktu habis, coba lagi",
+      "network": "Gangguan jaringan",
+      "unknownError": "Terjadi kesalahan"
+    }
   },
   "order": {
     "common": {

+ 12 - 1
i18n/locales/zh.json

@@ -5,7 +5,18 @@
     "copied": "已复制到剪贴板",
     "copyFailed": "复制失败",
     "logOut": "退出登录",
-    "unknown": "未知"
+    "unknown": "未知",
+    "errors": {
+      "loginExpired": "登录已过期,请重新登录",
+      "nativeOnly": "请在App内打开",
+      "noPermission": "暂无权限",
+      "notFound": "资源不存在",
+      "serverError": "服务器异常,请稍后再试",
+      "requestFailed": "请求失败",
+      "timeout": "请求超时,请重试",
+      "network": "网络异常,请检查网络",
+      "unknownError": "未知错误"
+    }
   },
   "order": {
     "common": {

+ 16 - 0
nuxt.config.ts

@@ -29,6 +29,22 @@ export default defineNuxtConfig({
     client: 'hidden',
   },
   compatibilityDate: '2025-07-15',
+  nitro: {
+    routeRules: {
+      '/.well-known/apple-app-site-association': {
+        headers: {
+          'Content-Type': 'application/json',
+          'Cache-Control': 'no-cache',
+        },
+      },
+      '/.well-known/assetlinks.json': {
+        headers: {
+          'Content-Type': 'application/json',
+          'Cache-Control': 'no-cache',
+        },
+      },
+    },
+  },
   vite: {
     plugins: [
       tailwindcss(),

+ 11 - 0
public/.well-known/apple-app-site-association

@@ -0,0 +1,11 @@
+{
+  "applinks": {
+    "apps": [],
+    "details": [
+      {
+        "appID": "5H8D98R72W.com.jiehe.gami",
+        "paths": ["/user/profile", "/user/category"]
+      }
+    ]
+  }
+}

+ 13 - 0
public/.well-known/assetlinks.json

@@ -0,0 +1,13 @@
+[
+  {
+    "relation": ["delegate_permission/common.handle_all_urls"],
+    "target": {
+      "namespace": "android_app",
+      "package_name": "com.jiehe.gami",
+      "sha256_cert_fingerprints": [
+        "E3:17:41:41:CC:6C:B5:DD:E7:36:47:84:8D:B4:AF:5F:F4:E9:E0:76:72:D1:DC:EB:4F:22:0F:78:32:BD:18:29",
+        "3D:F0:29:85:7C:9A:2D:7D:87:52:CE:68:F9:C7:77:75:9F:B7:AC:A1:B5:E3:68:1B:D8:11:5F:12:1B:77:F7:F4"
+      ]
+    }
+  }
+]