- 设计并实现了开发者中心与企业控制台两大模块 - 按用户角色区分开发者和企业客户,支持多项目类型及成员管理 - 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面 - 实现应用版本发布、消息通知中心、权限审批与开发者申请流程 - 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能 - 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块 - 优化后端API,支持已登录和未注册用户不同加入应用流程 - 前端按钮统一采用微信手机号授权,完善用户授权体验 - 修复多个页面的JSX语法错误及依赖导入问题,替换部分组件库 - 增加详细的类型定义文件,提升项目类型安全 - 新增超过55个页面及60个API接口,扩展应用功能和服务体系 - 完成全面的样式设计,实现一致的视觉风格和交互体验
270 lines
8.4 KiB
TypeScript
270 lines
8.4 KiB
TypeScript
/**
|
|
* 构建列表页面
|
|
*/
|
|
import { useState, useEffect } from 'react'
|
|
import { View, Text, ScrollView, Input } from '@tarojs/components'
|
|
import Taro from '@tarojs/taro'
|
|
import { AtButton, AtTabs, AtTabsPane, AtTag, AtActivityIndicator } from 'taro-ui'
|
|
import { pageBuild, triggerBuild, cancelBuild } from '../../../../api/cicd'
|
|
import type { Build, BuildStatus } from '../../../../types/cicd'
|
|
import './builds.scss'
|
|
|
|
// 状态映射
|
|
const STATUS_MAP: Record<BuildStatus, { text: string; color: string }> = {
|
|
pending: { text: '等待中', color: '#FFC107' },
|
|
running: { text: '构建中', color: '#2196F3' },
|
|
success: { text: '构建成功', color: '#4CAF50' },
|
|
failed: { text: '构建失败', color: '#F44336' },
|
|
cancelled: { text: '已取消', color: '#9E9E9E' },
|
|
}
|
|
|
|
export default function Builds() {
|
|
const [loading, setLoading] = useState(true)
|
|
const [builds, setBuilds] = useState<Build[]>([])
|
|
const [total, setTotal] = useState(0)
|
|
const [page, setPage] = useState(1)
|
|
const [hasMore, setHasMore] = useState(true)
|
|
const [status, setStatus] = useState<BuildStatus | ''>('')
|
|
const [keyword, setKeyword] = useState('')
|
|
const [appId, setAppId] = useState('')
|
|
|
|
// 获取构建列表
|
|
const fetchBuilds = async (pageNum = 1, reset = false) => {
|
|
try {
|
|
const res = await pageBuild({
|
|
page: pageNum,
|
|
limit: 20,
|
|
websiteId: Number(appId),
|
|
status: status || undefined,
|
|
})
|
|
|
|
if (res.data) {
|
|
if (reset) {
|
|
setBuilds(res.data.list || [])
|
|
} else {
|
|
setBuilds(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)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
const pages = Taro.getCurrentPages()
|
|
const current = pages[pages.length - 1]
|
|
if (current?.options?.appId) {
|
|
setAppId(current.options.appId)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (appId) {
|
|
fetchBuilds(1, true)
|
|
}
|
|
}, [appId, status])
|
|
|
|
// 触发构建
|
|
const handleTriggerBuild = async () => {
|
|
if (!appId) return
|
|
|
|
Taro.showModal({
|
|
title: '提示',
|
|
content: '确定要触发新的构建吗?',
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
try {
|
|
Taro.showLoading({ title: '触发中...' })
|
|
await triggerBuild({ websiteId: Number(appId) })
|
|
Taro.showToast({ title: '构建已触发', icon: 'success' })
|
|
fetchBuilds(1, true)
|
|
} catch (err) {
|
|
Taro.showToast({ title: '触发失败', icon: 'none' })
|
|
} finally {
|
|
Taro.hideLoading()
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
// 取消构建
|
|
const handleCancelBuild = async (buildId: number) => {
|
|
Taro.showModal({
|
|
title: '提示',
|
|
content: '确定要取消该构建吗?',
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
try {
|
|
Taro.showLoading({ title: '取消中...' })
|
|
await cancelBuild(buildId)
|
|
Taro.showToast({ title: '已取消', icon: 'success' })
|
|
fetchBuilds(page, true)
|
|
} catch (err) {
|
|
Taro.showToast({ title: '取消失败', icon: 'none' })
|
|
} finally {
|
|
Taro.hideLoading()
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
// 查看构建详情
|
|
const goToBuildDetail = (buildId: number) => {
|
|
Taro.navigateTo({
|
|
url: `/developer/app/${appId}/build/${buildId}`,
|
|
})
|
|
}
|
|
|
|
// 加载更多
|
|
const handleLoadMore = () => {
|
|
if (hasMore && !loading) {
|
|
fetchBuilds(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: '成功' },
|
|
{ title: '失败' },
|
|
]
|
|
|
|
return (
|
|
<View className="builds-page">
|
|
{/* 搜索栏 */}
|
|
<View className="search-bar">
|
|
<View className="search-input-wrap">
|
|
<Text className="iconfont icon-search" />
|
|
<Input
|
|
className="search-input"
|
|
placeholder="搜索构建号/分支/提交信息"
|
|
value={keyword}
|
|
onInput={(e) => setKeyword(e.detail.value)}
|
|
/>
|
|
</View>
|
|
<AtButton size="small" type="primary" onClick={handleTriggerBuild}>
|
|
触发构建
|
|
</AtButton>
|
|
</View>
|
|
|
|
{/* 状态筛选 */}
|
|
<AtTabs
|
|
current={tabs.findIndex(t => {
|
|
if (status === '') return t.title === '全部'
|
|
return STATUS_MAP[status]?.text === t.title
|
|
})}
|
|
tabList={tabs}
|
|
scroll
|
|
onClick={(index) => {
|
|
const statusMap: (BuildStatus | '')[] = ['', 'pending', 'running', 'success', 'failed']
|
|
setStatus(statusMap[index])
|
|
}}
|
|
>
|
|
<AtTabsPane current={0} index={0}>
|
|
<ScrollView
|
|
scrollY
|
|
className="build-list"
|
|
onScrollToLower={handleLoadMore}
|
|
>
|
|
{loading && builds.length === 0 ? (
|
|
<View className="loading-wrap">
|
|
<AtActivityIndicator size={32} />
|
|
<Text className="loading-text">加载中...</Text>
|
|
</View>
|
|
) : builds.length === 0 ? (
|
|
<View className="empty-wrap">
|
|
<Text className="iconfont icon-empty" />
|
|
<Text className="empty-text">暂无构建记录</Text>
|
|
</View>
|
|
) : (
|
|
builds.map((build) => (
|
|
<View
|
|
className="build-item"
|
|
key={build.id}
|
|
onClick={() => build.id && goToBuildDetail(build.id)}
|
|
>
|
|
<View className="build-header">
|
|
<Text className="build-no">#{build.buildNo || build.id}</Text>
|
|
<AtTag
|
|
type={STATUS_MAP[build.status || 'pending'].color.includes('F44336') ? 'error' : 'primary'}
|
|
size="small"
|
|
>
|
|
{STATUS_MAP[build.status || 'pending'].text}
|
|
</AtTag>
|
|
</View>
|
|
|
|
<View className="build-info">
|
|
<View className="build-branch">
|
|
<Text className="iconfont icon-git-branch" />
|
|
<Text>{build.branch || 'main'}</Text>
|
|
</View>
|
|
<View className="build-commit">
|
|
<Text className="iconfont icon-commit" />
|
|
<Text className="commit-id">{build.commitId?.slice(0, 7)}</Text>
|
|
<Text className="commit-msg">{build.commitMessage}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View className="build-footer">
|
|
<View className="build-meta">
|
|
<Text className="author">{build.author}</Text>
|
|
<Text className="time">{formatTime(build.createTime)}</Text>
|
|
<Text className="duration">{formatDuration(build.duration)}</Text>
|
|
</View>
|
|
{build.status === 'pending' || build.status === 'running' ? (
|
|
<AtButton
|
|
size="small"
|
|
type="secondary"
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
build.id && handleCancelBuild(build.id)
|
|
}}
|
|
>
|
|
取消
|
|
</AtButton>
|
|
) : null}
|
|
</View>
|
|
</View>
|
|
))
|
|
)}
|
|
|
|
{loading && builds.length > 0 && (
|
|
<View className="loading-more">
|
|
<AtActivityIndicator size={24} />
|
|
</View>
|
|
)}
|
|
|
|
{!hasMore && builds.length > 0 && (
|
|
<View className="no-more">没有更多了</View>
|
|
)}
|
|
</ScrollView>
|
|
</AtTabsPane>
|
|
</AtTabs>
|
|
</View>
|
|
)
|
|
}
|