/** * 部署列表页面 */ import { useState, useEffect } from 'react' import { View, Text, ScrollView } from '@tarojs/components' import Taro from '@tarojs/taro' import { AtButton, AtTabs, AtTabsPane, AtTag, AtActivityIndicator, AtModal } from 'taro-ui' import { pageDeploy, triggerDeploy, rollbackDeploy } from '../../../../api/cicd' import { pageBuild } from '../../../../api/cicd' import type { Deploy, DeployStatus, DeployEnv, Build } from '../../../../types/cicd' import './deploys.scss' // 状态映射 const STATUS_MAP: Record = { pending: { text: '等待中', color: '#FFC107' }, deploying: { text: '部署中', color: '#2196F3' }, success: { text: '部署成功', color: '#4CAF50' }, failed: { text: '部署失败', color: '#F44336' }, rollback: { text: '回滚中', color: '#FF9800' }, } // 环境映射 const ENV_MAP: Record = { development: { text: '开发', color: '#9E9E9E' }, staging: { text: '预发布', color: '#FF9800' }, production: { text: '生产', color: '#4CAF50' }, } export default function Deploys() { const [loading, setLoading] = useState(true) const [deploys, setDeploys] = useState([]) const [total, setTotal] = useState(0) const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(true) const [env, setEnv] = useState('') const [appId, setAppId] = useState('') const [buildId, setBuildId] = useState('') const [showDeployModal, setShowDeployModal] = useState(false) const [selectBuild, setSelectBuild] = useState(null) const [selectEnv, setSelectEnv] = useState('staging') const [builds, setBuilds] = useState([]) // 获取部署列表 const fetchDeploys = async (pageNum = 1, reset = false) => { try { const res = await pageDeploy({ page: pageNum, limit: 20, websiteId: Number(appId), env: env || undefined, }) if (res.data) { if (reset) { setDeploys(res.data.list || []) } else { setDeploys(prev => [...prev, ...(res.data?.list || [])]) } setTotal(res.data.total || 0) setHasMore((res.data.list || []).length === 20) setPage(pageNum) } } catch (err) { console.error('获取部署列表失败', err) } finally { setLoading(false) } } // 获取成功构建列表(用于部署选择) const fetchBuilds = async () => { try { const res = await pageBuild({ page: 1, limit: 20, websiteId: Number(appId), status: 'success', }) if (res.data) { setBuilds(res.data.list || []) } } catch (err) { console.error('获取构建列表失败', err) } } useEffect(() => { const pages = Taro.getCurrentPages() const current = pages[pages.length - 1] if (current?.options?.appId) { setAppId(current.options.appId) } if (current?.options?.buildId) { setBuildId(current.options.buildId) // 如果有 buildId,自动打开部署弹窗 fetchBuilds().then(() => { if (current.options?.buildId && builds.length > 0) { const build = builds.find(b => b.id === Number(current.options.buildId)) if (build) { setSelectBuild(build) setShowDeployModal(true) } } }) } }, []) useEffect(() => { if (appId) { fetchDeploys(1, true) } }, [appId, env]) // 触发部署 const handleTriggerDeploy = async () => { if (!selectBuild || !appId) return try { Taro.showLoading({ title: '部署中...' }) await triggerDeploy({ websiteId: Number(appId), buildId: selectBuild.id!, env: selectEnv, }) Taro.showToast({ title: '部署已触发', icon: 'success' }) setShowDeployModal(false) fetchDeploys(1, true) } catch (err) { Taro.showToast({ title: '部署失败', icon: 'none' }) } finally { Taro.hideLoading() } } // 回滚部署 const handleRollback = async (deployId: number) => { Taro.showModal({ title: '确认回滚', content: '确定要回滚到上一个版本吗?', success: async (res) => { if (res.confirm) { try { Taro.showLoading({ title: '回滚中...' }) await rollbackDeploy(deployId) Taro.showToast({ title: '回滚成功', icon: 'success' }) fetchDeploys(page, true) } catch (err) { Taro.showToast({ title: '回滚失败', icon: 'none' }) } finally { Taro.hideLoading() } } }, }) } // 查看部署详情 const goToDeployDetail = (deployId: number) => { Taro.navigateTo({ url: `/developer/app/${appId}/deploy/${deployId}`, }) } // 加载更多 const handleLoadMore = () => { if (hasMore && !loading) { fetchDeploys(page + 1) } } // 格式化时间 const formatTime = (time?: string) => { if (!time) return '-' const date = new Date(time) return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}` } // 格式化时长 const formatDuration = (seconds?: number) => { if (!seconds) return '-' if (seconds < 60) return `${seconds}s` const mins = Math.floor(seconds / 60) const secs = seconds % 60 return `${mins}m ${secs}s` } const tabs = [ { title: '全部' }, { title: '开发' }, { title: '预发布' }, { title: '生产' }, ] const envList: (DeployEnv | '')[] = ['', 'development', 'staging', 'production'] return ( {/* 顶部统计 */} {total} 总部署 {deploys.filter(d => d.status === 'success').length} 成功 {deploys.filter(d => d.status === 'failed').length} 失败 {/* 环境筛选 */} setEnv(envList[index])} > {loading && deploys.length === 0 ? ( 加载中... ) : deploys.length === 0 ? ( 暂无部署记录 { fetchBuilds() setShowDeployModal(true) }}> 发起部署 ) : ( deploys.map((deploy) => ( deploy.id && goToDeployDetail(deploy.id)} > v{deploy.version} {ENV_MAP[deploy.env || 'staging'].text} {STATUS_MAP[deploy.status || 'pending'].text} #{deploy.buildNo} {deploy.previousVersion && ( 从 v{deploy.previousVersion} 回滚 )} {deploy.deployer} {formatTime(deploy.startTime)} {formatDuration(deploy.duration)} {deploy.status === 'success' && ( { e.stopPropagation() deploy.id && handleRollback(deploy.id) }} > 回滚 )} )) )} {loading && deploys.length > 0 && ( )} {!hasMore && deploys.length > 0 && ( 没有更多了 )} {/* 部署弹窗 */} setShowDeployModal(false)} onCancel={() => setShowDeployModal(false)} onConfirm={handleTriggerDeploy} content={ {/* 环境选择 */} 部署环境 {(['development', 'staging', 'production'] as DeployEnv[]).map((e) => ( setSelectEnv(e)} > {ENV_MAP[e].text} ))} {/* 构建选择 */} 选择构建版本 {builds.map((build) => ( setSelectBuild(build)} > #{build.buildNo} {build.branch} {build.commitId?.slice(0, 7)} {formatTime(build.endTime)} ))} } /> ) }