- 设计并实现了开发者中心与企业控制台两大模块 - 按用户角色区分开发者和企业客户,支持多项目类型及成员管理 - 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面 - 实现应用版本发布、消息通知中心、权限审批与开发者申请流程 - 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能 - 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块 - 优化后端API,支持已登录和未注册用户不同加入应用流程 - 前端按钮统一采用微信手机号授权,完善用户授权体验 - 修复多个页面的JSX语法错误及依赖导入问题,替换部分组件库 - 增加详细的类型定义文件,提升项目类型安全 - 新增超过55个页面及60个API接口,扩展应用功能和服务体系 - 完成全面的样式设计,实现一致的视觉风格和交互体验
305 lines
10 KiB
TypeScript
305 lines
10 KiB
TypeScript
/**
|
|
* 部署详情页面
|
|
*/
|
|
import { useState, useEffect } from 'react'
|
|
import { View, Text, ScrollView } from '@tarojs/components'
|
|
import Taro from '@tarojs/taro'
|
|
import { AtButton, AtTag, AtActivityIndicator } from 'taro-ui'
|
|
import { getDeploy, getDeployLogs, rollbackDeploy } from '../../../../../api/cicd'
|
|
import type { Deploy, DeployStatus, DeployEnv } from '../../../../../types/cicd'
|
|
import './deploy-detail.scss'
|
|
|
|
// 状态映射
|
|
const STATUS_MAP: Record<DeployStatus, { text: string; color: string }> = {
|
|
pending: { text: '等待中', color: '#FFC107' },
|
|
deploying: { text: '部署中', color: '#2196F3' },
|
|
success: { text: '部署成功', color: '#4CAF50' },
|
|
failed: { text: '部署失败', color: '#F44336' },
|
|
rollback: { text: '回滚中', color: '#FF9800' },
|
|
}
|
|
|
|
// 环境映射
|
|
const ENV_MAP: Record<DeployEnv, { text: string; color: string }> = {
|
|
development: { text: '开发环境', color: '#9E9E9E' },
|
|
staging: { text: '预发布环境', color: '#FF9800' },
|
|
production: { text: '生产环境', color: '#4CAF50' },
|
|
}
|
|
|
|
export default function DeployDetail() {
|
|
const [loading, setLoading] = useState(true)
|
|
const [deploy, setDeploy] = useState<Deploy>({})
|
|
const [logs, setLogs] = useState('')
|
|
const [activeTab, setActiveTab] = useState('info')
|
|
const [deployId, setDeployId] = useState('')
|
|
|
|
useEffect(() => {
|
|
const pages = Taro.getCurrentPages()
|
|
const current = pages[pages.length - 1]
|
|
if (current?.options?.id) {
|
|
setDeployId(current.options.id)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (deployId) {
|
|
fetchDeployDetail()
|
|
}
|
|
}, [deployId])
|
|
|
|
// 获取部署详情
|
|
const fetchDeployDetail = async () => {
|
|
try {
|
|
const res = await getDeploy(Number(deployId))
|
|
if (res.data) {
|
|
setDeploy(res.data)
|
|
}
|
|
} catch (err) {
|
|
console.error('获取部署详情失败', err)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
// 获取部署日志
|
|
const fetchDeployLogs = async () => {
|
|
try {
|
|
const res = await getDeployLogs(Number(deployId))
|
|
if (res.data) {
|
|
setLogs(res.data.logs || '')
|
|
}
|
|
} catch (err) {
|
|
console.error('获取部署日志失败', err)
|
|
}
|
|
}
|
|
|
|
// 切换 Tab
|
|
useEffect(() => {
|
|
if (activeTab === 'logs' && !logs) {
|
|
fetchDeployLogs()
|
|
}
|
|
}, [activeTab])
|
|
|
|
// 回滚部署
|
|
const handleRollback = async () => {
|
|
Taro.showModal({
|
|
title: '确认回滚',
|
|
content: `确定要回滚到版本 ${deploy.previousVersion || '上一个版本'} 吗?`,
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
try {
|
|
Taro.showLoading({ title: '回滚中...' })
|
|
await rollbackDeploy(Number(deployId))
|
|
Taro.showToast({ title: '回滚成功', icon: 'success' })
|
|
fetchDeployDetail()
|
|
} catch (err) {
|
|
Taro.showToast({ title: '回滚失败', icon: 'none' })
|
|
} finally {
|
|
Taro.hideLoading()
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
// 格式化时间
|
|
const formatTime = (time?: string) => {
|
|
if (!time) return '-'
|
|
const date = new Date(time)
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
|
}
|
|
|
|
// 格式化时长
|
|
const formatDuration = (seconds?: number) => {
|
|
if (!seconds) return '-'
|
|
if (seconds < 60) return `${seconds} 秒`
|
|
const mins = Math.floor(seconds / 60)
|
|
const secs = seconds % 60
|
|
return `${mins} 分 ${secs} 秒`
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<View className="deploy-detail loading-wrap">
|
|
<AtActivityIndicator size={32} />
|
|
<Text className="loading-text">加载中...</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const statusInfo = STATUS_MAP[deploy.status || 'pending']
|
|
const envInfo = ENV_MAP[deploy.env || 'staging']
|
|
|
|
return (
|
|
<View className="deploy-detail">
|
|
{/* 部署状态卡片 */}
|
|
<View className="status-card">
|
|
<View className="status-header">
|
|
<View className="version-info">
|
|
<Text className="version">v{deploy.version}</Text>
|
|
<AtTag type={envInfo.color.includes('4CAF50') ? 'success' : 'primary'}>
|
|
{envInfo.text}
|
|
</AtTag>
|
|
</View>
|
|
<AtTag
|
|
type={statusInfo.color.includes('F44336') ? 'error' : statusInfo.color.includes('4CAF50') ? 'success' : 'primary'}
|
|
>
|
|
{statusInfo.text}
|
|
</AtTag>
|
|
</View>
|
|
|
|
<View className="status-progress">
|
|
<View className="progress-step">
|
|
<View className={`step-dot ${deploy.status !== 'pending' ? 'active' : ''}`} />
|
|
<Text className="step-label">等待</Text>
|
|
</View>
|
|
<View className={`progress-line ${deploy.status === 'deploying' || deploy.status === 'success' ? 'active' : ''}`} />
|
|
<View className="progress-step">
|
|
<View className={`step-dot ${deploy.status === 'deploying' || deploy.status === 'success' ? 'active' : ''}`} />
|
|
<Text className="step-label">部署</Text>
|
|
</View>
|
|
<View className={`progress-line ${deploy.status === 'success' ? 'active' : ''}`} />
|
|
<View className="progress-step">
|
|
<View className={`step-dot ${deploy.status === 'success' ? 'active' : ''}`} />
|
|
<Text className="step-label">完成</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View className="deploy-meta">
|
|
<View className="meta-item">
|
|
<Text className="label">构建版本</Text>
|
|
<Text className="value">#{deploy.buildNo}</Text>
|
|
</View>
|
|
<View className="meta-item">
|
|
<Text className="label">部署人</Text>
|
|
<Text className="value">{deploy.deployer}</Text>
|
|
</View>
|
|
<View className="meta-item">
|
|
<Text className="label">开始时间</Text>
|
|
<Text className="value">{formatTime(deploy.startTime)}</Text>
|
|
</View>
|
|
<View className="meta-item">
|
|
<Text className="label">总耗时</Text>
|
|
<Text className="value">{formatDuration(deploy.duration)}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{deploy.previousVersion && (
|
|
<View className="rollback-info">
|
|
<Text className="iconfont icon-rollback" />
|
|
<Text>从 v{deploy.previousVersion} 回滚</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
{/* 操作按钮 */}
|
|
<View className="actions">
|
|
{deploy.status === 'success' && (
|
|
<AtButton type="primary" onClick={handleRollback}>
|
|
回滚到此版本
|
|
</AtButton>
|
|
)}
|
|
<AtButton
|
|
type="secondary"
|
|
onClick={() => {
|
|
Taro.navigateBack()
|
|
}}
|
|
>
|
|
返回列表
|
|
</AtButton>
|
|
</View>
|
|
|
|
{/* Tab 切换 */}
|
|
<View className="tabs">
|
|
<View
|
|
className={`tab ${activeTab === 'info' ? 'active' : ''}`}
|
|
onClick={() => setActiveTab('info')}
|
|
>
|
|
部署信息
|
|
</View>
|
|
<View
|
|
className={`tab ${activeTab === 'logs' ? 'active' : ''}`}
|
|
onClick={() => setActiveTab('logs')}
|
|
>
|
|
部署日志
|
|
</View>
|
|
</View>
|
|
|
|
{/* Tab 内容 */}
|
|
<ScrollView scrollY className="tab-content">
|
|
{activeTab === 'info' && (
|
|
<View className="info-panel">
|
|
<View className="info-section">
|
|
<Text className="section-title">基本信息</Text>
|
|
<View className="info-row">
|
|
<Text className="row-label">部署 ID</Text>
|
|
<Text className="row-value">{deploy.id}</Text>
|
|
</View>
|
|
<View className="info-row">
|
|
<Text className="row-label">项目 ID</Text>
|
|
<Text className="row-value">{deploy.websiteId}</Text>
|
|
</View>
|
|
<View className="info-row">
|
|
<Text className="row-label">构建 ID</Text>
|
|
<Text className="row-value">{deploy.buildId}</Text>
|
|
</View>
|
|
<View className="info-row">
|
|
<Text className="row-label">版本号</Text>
|
|
<Text className="row-value">v{deploy.version}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View className="info-section">
|
|
<Text className="section-title">时间信息</Text>
|
|
<View className="info-row">
|
|
<Text className="row-label">开始时间</Text>
|
|
<Text className="row-value">{formatTime(deploy.startTime)}</Text>
|
|
</View>
|
|
<View className="info-row">
|
|
<Text className="row-label">结束时间</Text>
|
|
<Text className="row-value">{formatTime(deploy.endTime)}</Text>
|
|
</View>
|
|
<View className="info-row">
|
|
<Text className="row-label">总耗时</Text>
|
|
<Text className="row-value">{formatDuration(deploy.duration)}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{deploy.previousVersion && (
|
|
<View className="info-section">
|
|
<Text className="section-title">回滚信息</Text>
|
|
<View className="info-row">
|
|
<Text className="row-label">回滚源版本</Text>
|
|
<Text className="row-value">v{deploy.previousVersion}</Text>
|
|
</View>
|
|
<View className="info-row">
|
|
<Text className="row-label">回滚至版本</Text>
|
|
<Text className="row-value">v{deploy.version}</Text>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
{activeTab === 'logs' && (
|
|
<View className="logs-panel">
|
|
{logs ? (
|
|
<View className="logs-content">
|
|
{logs.split('\n').map((line, index) => (
|
|
<Text key={index} className="log-line">
|
|
{line}
|
|
</Text>
|
|
))}
|
|
</View>
|
|
) : (
|
|
<View className="loading-logs">
|
|
<AtActivityIndicator size={24} />
|
|
<Text>加载日志中...</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
</View>
|
|
)
|
|
}
|