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:
parent
2b2e1efc7a
commit
33ae08c90f
|
|
@ -2,15 +2,16 @@
|
||||||
* 移动端版本管理首页
|
* 移动端版本管理首页
|
||||||
*
|
*
|
||||||
* 功能概述:
|
* 功能概述:
|
||||||
* 统一管理榴莲 App 和股行 App 的版本发布。
|
* 统一管理榴莲 App、股行 App 和 IT0 App 的版本发布。
|
||||||
* 支持在同一界面切换管理不同应用的版本。
|
* 支持在同一界面切换管理不同应用的版本。
|
||||||
*
|
*
|
||||||
* 支持的应用:
|
* 支持的应用:
|
||||||
* - 榴莲 App (mobile): 连接 admin-service 后端
|
* - 榴莲 App (mobile): 连接 admin-service 后端
|
||||||
* - 股行 App (mining): 连接 mining-admin-service 后端
|
* - 股行 App (mining): 连接 mining-admin-service 后端
|
||||||
|
* - IT0 App (it0): 连接 IT0 version-service 后端
|
||||||
*
|
*
|
||||||
* 主要功能:
|
* 主要功能:
|
||||||
* 1. 应用切换 - 在两个应用间切换版本管理
|
* 1. 应用切换 - 在多个应用间切换版本管理
|
||||||
* 2. 平台筛选 - 按 Android/iOS 筛选版本
|
* 2. 平台筛选 - 按 Android/iOS 筛选版本
|
||||||
* 3. 版本上传 - 上传新版本 APK/IPA
|
* 3. 版本上传 - 上传新版本 APK/IPA
|
||||||
* 4. 版本管理 - 编辑、删除、启用/禁用版本
|
* 4. 版本管理 - 编辑、删除、启用/禁用版本
|
||||||
|
|
@ -33,6 +34,7 @@ import toast from 'react-hot-toast'
|
||||||
const APP_LABELS: Record<AppType, { name: string; description: string }> = {
|
const APP_LABELS: Record<AppType, { name: string; description: string }> = {
|
||||||
mobile: { name: '榴莲 App', description: '管理榴莲 App 的版本更新' },
|
mobile: { name: '榴莲 App', description: '管理榴莲 App 的版本更新' },
|
||||||
mining: { name: '股行 App', description: '管理股行 App 的版本更新' },
|
mining: { name: '股行 App', description: '管理股行 App 的版本更新' },
|
||||||
|
it0: { name: 'IT0 App', description: '管理 IT0 App 的版本更新' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
|
@ -120,6 +122,16 @@ export default function HomePage() {
|
||||||
>
|
>
|
||||||
股行 App
|
股行 App
|
||||||
</button>
|
</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>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,16 @@
|
||||||
*
|
*
|
||||||
* 功能概述:
|
* 功能概述:
|
||||||
* 为前端提供统一的HTTP客户端,支持连接多个后端服务。
|
* 为前端提供统一的HTTP客户端,支持连接多个后端服务。
|
||||||
* 当前支持两个应用的版本管理:
|
* 当前支持三个应用的版本管理:
|
||||||
* - mobile (榴莲 App): 连接 admin-service 后端
|
* - mobile (榴莲 App): 连接 admin-service 后端
|
||||||
* - mining (股行 App): 连接 mining-admin-service 后端
|
* - mining (股行 App): 连接 mining-admin-service 后端
|
||||||
|
* - it0 (IT0 App): 连接 IT0 version-service 后端
|
||||||
*
|
*
|
||||||
* 配置方式:
|
* 配置方式:
|
||||||
* 通过环境变量配置后端地址:
|
* 通过环境变量配置后端地址:
|
||||||
* - NEXT_PUBLIC_API_URL: 榴莲App后端地址
|
* - NEXT_PUBLIC_API_URL: 榴莲App后端地址
|
||||||
* - NEXT_PUBLIC_MINING_API_URL: 股行App后端地址
|
* - NEXT_PUBLIC_MINING_API_URL: 股行App后端地址
|
||||||
|
* - NEXT_PUBLIC_IT0_API_URL: IT0 App后端地址
|
||||||
*
|
*
|
||||||
* 使用方式:
|
* 使用方式:
|
||||||
* ```typescript
|
* ```typescript
|
||||||
|
|
@ -38,8 +40,9 @@ export class ApiError extends Error {
|
||||||
* 应用类型
|
* 应用类型
|
||||||
* - mobile: 榴莲 App (使用 admin-service 后端)
|
* - mobile: 榴莲 App (使用 admin-service 后端)
|
||||||
* - mining: 股行 App (使用 mining-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 {
|
export interface ApiConfig {
|
||||||
baseURL: string
|
baseURL: string
|
||||||
|
|
@ -52,7 +55,8 @@ export interface ApiConfig {
|
||||||
* 注意:
|
* 注意:
|
||||||
* - mobile 使用 /api/v1/versions 前缀 (admin-service)
|
* - mobile 使用 /api/v1/versions 前缀 (admin-service)
|
||||||
* - mining 使用 /api/v2/upgrade-versions 前缀 (mining-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> = {
|
const APP_CONFIGS: Record<AppType, ApiConfig> = {
|
||||||
// 榴莲 App - 连接原有的 admin-service 后端
|
// 榴莲 App - 连接原有的 admin-service 后端
|
||||||
|
|
@ -65,6 +69,11 @@ const APP_CONFIGS: Record<AppType, ApiConfig> = {
|
||||||
baseURL: process.env.NEXT_PUBLIC_MINING_API_URL || 'http://localhost:3023',
|
baseURL: process.env.NEXT_PUBLIC_MINING_API_URL || 'http://localhost:3023',
|
||||||
apiPrefix: '/api/v2/upgrade-versions',
|
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 {
|
export function createApiClient(appType: AppType = 'mobile'): AxiosInstance {
|
||||||
|
|
@ -111,3 +120,4 @@ export function getApiPrefix(appType: AppType): string {
|
||||||
|
|
||||||
export const apiClient = createApiClient('mobile')
|
export const apiClient = createApiClient('mobile')
|
||||||
export const miningApiClient = createApiClient('mining')
|
export const miningApiClient = createApiClient('mining')
|
||||||
|
export const it0ApiClient = createApiClient('it0')
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
ParsedPackageInfo,
|
ParsedPackageInfo,
|
||||||
Platform,
|
Platform,
|
||||||
} from '@/domain'
|
} 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 {
|
export class VersionRepositoryImpl implements IVersionRepository {
|
||||||
private client: AxiosInstance
|
private client: AxiosInstance
|
||||||
|
|
@ -20,7 +20,9 @@ export class VersionRepositoryImpl implements IVersionRepository {
|
||||||
this.client = client
|
this.client = client
|
||||||
this.apiPrefix = '/api/v1/versions'
|
this.apiPrefix = '/api/v1/versions'
|
||||||
} else {
|
} else {
|
||||||
this.client = appType === 'mining' ? miningApiClient : apiClient
|
this.client = appType === 'mining' ? miningApiClient
|
||||||
|
: appType === 'it0' ? it0ApiClient
|
||||||
|
: apiClient
|
||||||
this.apiPrefix = getApiPrefix(appType)
|
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 versionRepository = new VersionRepositoryImpl('mobile')
|
||||||
export const miningVersionRepository = new VersionRepositoryImpl('mining')
|
export const miningVersionRepository = new VersionRepositoryImpl('mining')
|
||||||
|
export const it0VersionRepository = new VersionRepositoryImpl('it0')
|
||||||
|
|
||||||
// Factory function to get repository by app type
|
// Factory function to get repository by app type
|
||||||
export function getVersionRepository(appType: AppType): VersionRepositoryImpl {
|
export function getVersionRepository(appType: AppType): VersionRepositoryImpl {
|
||||||
return appType === 'mining' ? miningVersionRepository : versionRepository
|
switch (appType) {
|
||||||
|
case 'mining': return miningVersionRepository
|
||||||
|
case 'it0': return it0VersionRepository
|
||||||
|
default: return versionRepository
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
import { useState, useRef } from 'react'
|
import { useState, useRef } from 'react'
|
||||||
import { Platform } from '@/domain'
|
import { Platform } from '@/domain'
|
||||||
import { useVersionActions } from '@/application'
|
import { useVersionActions, useAppType } from '@/application'
|
||||||
import { versionRepository } from '@/infrastructure/repositories/version-repository-impl'
|
import { getVersionRepository } from '@/infrastructure/repositories/version-repository-impl'
|
||||||
|
|
||||||
interface UploadModalProps {
|
interface UploadModalProps {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
|
|
@ -12,6 +12,7 @@ interface UploadModalProps {
|
||||||
|
|
||||||
export function UploadModal({ onClose, onSuccess }: UploadModalProps) {
|
export function UploadModal({ onClose, onSuccess }: UploadModalProps) {
|
||||||
const { uploadVersion } = useVersionActions()
|
const { uploadVersion } = useVersionActions()
|
||||||
|
const { appType } = useAppType()
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
@ -48,7 +49,7 @@ export function UploadModal({ onClose, onSuccess }: UploadModalProps) {
|
||||||
// Auto-parse package info
|
// Auto-parse package info
|
||||||
setIsParsing(true)
|
setIsParsing(true)
|
||||||
try {
|
try {
|
||||||
const parsed = await versionRepository.parsePackage(selectedFile, detectedPlatform)
|
const parsed = await getVersionRepository(appType).parsePackage(selectedFile, detectedPlatform)
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
platform: detectedPlatform,
|
platform: detectedPlatform,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue