feat(mobile-upgrade): add IT0 App version management support

Add IT0 App as the third application in the mobile-upgrade frontend,
connecting to IT0's new version-service backend.

Changes:
- api-client.ts: added 'it0' to AppType union, APP_CONFIGS entry
  pointing to NEXT_PUBLIC_IT0_API_URL (default: it0api.szaiai.com),
  exported it0ApiClient instance
- version-repository-impl.ts: added it0VersionRepository instance,
  updated constructor client selection for it0, updated
  getVersionRepository() factory with switch/case
- page.tsx: added IT0 App to APP_LABELS, added emerald green toggle
  button in app selector
- upload-modal.tsx: fixed existing bug where parsePackage() was
  hardcoded to use mobile repository regardless of selected app;
  now uses getVersionRepository(appType) dynamically

Backend: IT0 version-service at it0api.szaiai.com/api/v1/versions
Env var: NEXT_PUBLIC_IT0_API_URL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-03 07:49:37 -08:00
parent 2b2e1efc7a
commit 33ae08c90f
4 changed files with 42 additions and 12 deletions

View File

@ -2,15 +2,16 @@
*
*
* :
* App App
* App App IT0 App
*
*
* :
* - App (mobile): admin-service
* - App (mining): mining-admin-service
* - IT0 App (it0): IT0 version-service
*
* :
* 1. -
* 1. -
* 2. - Android/iOS
* 3. - APK/IPA
* 4. - /
@ -33,6 +34,7 @@ import toast from 'react-hot-toast'
const APP_LABELS: Record<AppType, { name: string; description: string }> = {
mobile: { name: '榴莲 App', description: '管理榴莲 App 的版本更新' },
mining: { name: '股行 App', description: '管理股行 App 的版本更新' },
it0: { name: 'IT0 App', description: '管理 IT0 App 的版本更新' },
}
export default function HomePage() {
@ -120,6 +122,16 @@ export default function HomePage() {
>
App
</button>
<button
onClick={() => handleAppTypeChange('it0')}
className={`px-5 py-2.5 rounded-lg text-sm font-medium transition-all shadow-sm ${
appType === 'it0'
? 'bg-emerald-600 text-white shadow-emerald-200'
: 'bg-white text-gray-700 hover:bg-gray-50 border border-gray-200'
}`}
>
IT0 App
</button>
</div>
</div>
<div className="text-sm text-gray-500">

View File

@ -3,14 +3,16 @@
*
* :
* HTTP客户端
* :
* :
* - mobile ( App): admin-service
* - mining ( App): mining-admin-service
* - it0 (IT0 App): IT0 version-service
*
* :
* :
* - NEXT_PUBLIC_API_URL: 榴莲App后端地址
* - NEXT_PUBLIC_MINING_API_URL: 股行App后端地址
* - NEXT_PUBLIC_IT0_API_URL: IT0 App后端地址
*
* 使:
* ```typescript
@ -38,8 +40,9 @@ export class ApiError extends Error {
*
* - mobile: 榴莲 App (使 admin-service )
* - mining: 股行 App (使 mining-admin-service )
* - it0: IT0 App (使 IT0 version-service )
*/
export type AppType = 'mobile' | 'mining'
export type AppType = 'mobile' | 'mining' | 'it0'
export interface ApiConfig {
baseURL: string
@ -52,7 +55,8 @@ export interface ApiConfig {
* :
* - mobile 使 /api/v1/versions (admin-service)
* - mining 使 /api/v2/upgrade-versions (mining-admin-service )
* - API前缀不同是因为两个后端是独立的服务
* - it0 使 /api/v1/versions (IT0 version-service)
* - API前缀不同是因为各后端是独立的服务
*/
const APP_CONFIGS: Record<AppType, ApiConfig> = {
// 榴莲 App - 连接原有的 admin-service 后端
@ -65,6 +69,11 @@ const APP_CONFIGS: Record<AppType, ApiConfig> = {
baseURL: process.env.NEXT_PUBLIC_MINING_API_URL || 'http://localhost:3023',
apiPrefix: '/api/v2/upgrade-versions',
},
// IT0 App - 连接 IT0 version-service 后端
it0: {
baseURL: process.env.NEXT_PUBLIC_IT0_API_URL || 'https://it0api.szaiai.com',
apiPrefix: '/api/v1/versions',
},
}
export function createApiClient(appType: AppType = 'mobile'): AxiosInstance {
@ -111,3 +120,4 @@ export function getApiPrefix(appType: AppType): string {
export const apiClient = createApiClient('mobile')
export const miningApiClient = createApiClient('mining')
export const it0ApiClient = createApiClient('it0')

View File

@ -9,7 +9,7 @@ import {
ParsedPackageInfo,
Platform,
} from '@/domain'
import { apiClient, miningApiClient, AppType, getApiPrefix } from '../http/api-client'
import { apiClient, miningApiClient, it0ApiClient, AppType, getApiPrefix } from '../http/api-client'
export class VersionRepositoryImpl implements IVersionRepository {
private client: AxiosInstance
@ -20,7 +20,9 @@ export class VersionRepositoryImpl implements IVersionRepository {
this.client = client
this.apiPrefix = '/api/v1/versions'
} else {
this.client = appType === 'mining' ? miningApiClient : apiClient
this.client = appType === 'mining' ? miningApiClient
: appType === 'it0' ? it0ApiClient
: apiClient
this.apiPrefix = getApiPrefix(appType)
}
}
@ -118,11 +120,16 @@ export class VersionRepositoryImpl implements IVersionRepository {
}
}
// Pre-created repository instances for both apps
// Pre-created repository instances for all apps
export const versionRepository = new VersionRepositoryImpl('mobile')
export const miningVersionRepository = new VersionRepositoryImpl('mining')
export const it0VersionRepository = new VersionRepositoryImpl('it0')
// Factory function to get repository by app type
export function getVersionRepository(appType: AppType): VersionRepositoryImpl {
return appType === 'mining' ? miningVersionRepository : versionRepository
switch (appType) {
case 'mining': return miningVersionRepository
case 'it0': return it0VersionRepository
default: return versionRepository
}
}

View File

@ -2,8 +2,8 @@
import { useState, useRef } from 'react'
import { Platform } from '@/domain'
import { useVersionActions } from '@/application'
import { versionRepository } from '@/infrastructure/repositories/version-repository-impl'
import { useVersionActions, useAppType } from '@/application'
import { getVersionRepository } from '@/infrastructure/repositories/version-repository-impl'
interface UploadModalProps {
onClose: () => void
@ -12,6 +12,7 @@ interface UploadModalProps {
export function UploadModal({ onClose, onSuccess }: UploadModalProps) {
const { uploadVersion } = useVersionActions()
const { appType } = useAppType()
const fileInputRef = useRef<HTMLInputElement>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
@ -48,7 +49,7 @@ export function UploadModal({ onClose, onSuccess }: UploadModalProps) {
// Auto-parse package info
setIsParsing(true)
try {
const parsed = await versionRepository.parsePackage(selectedFile, detectedPlatform)
const parsed = await getVersionRepository(appType).parsePackage(selectedFile, detectedPlatform)
setFormData((prev) => ({
...prev,
platform: detectedPlatform,