feat(developer): 完成小程序开发者中心和企业控制台改造

- 设计并实现了开发者中心与企业控制台两大模块
- 按用户角色区分开发者和企业客户,支持多项目类型及成员管理
- 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面
- 实现应用版本发布、消息通知中心、权限审批与开发者申请流程
- 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能
- 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块
- 优化后端API,支持已登录和未注册用户不同加入应用流程
- 前端按钮统一采用微信手机号授权,完善用户授权体验
- 修复多个页面的JSX语法错误及依赖导入问题,替换部分组件库
- 增加详细的类型定义文件,提升项目类型安全
- 新增超过55个页面及60个API接口,扩展应用功能和服务体系
- 完成全面的样式设计,实现一致的视觉风格和交互体验
This commit is contained in:
2026-04-13 02:26:46 +08:00
parent 2ae30ac692
commit ffab0ec25c
199 changed files with 20017 additions and 508 deletions

View File

@@ -0,0 +1,269 @@
/**
* 构建列表页面
*/
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>
)
}