- 设计并实现了开发者中心与企业控制台两大模块 - 按用户角色区分开发者和企业客户,支持多项目类型及成员管理 - 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面 - 实现应用版本发布、消息通知中心、权限审批与开发者申请流程 - 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能 - 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块 - 优化后端API,支持已登录和未注册用户不同加入应用流程 - 前端按钮统一采用微信手机号授权,完善用户授权体验 - 修复多个页面的JSX语法错误及依赖导入问题,替换部分组件库 - 增加详细的类型定义文件,提升项目类型安全 - 新增超过55个页面及60个API接口,扩展应用功能和服务体系 - 完成全面的样式设计,实现一致的视觉风格和交互体验
365 lines
12 KiB
TypeScript
365 lines
12 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { View, Text, Button, Input } from '@tarojs/components'
|
||
import Taro, { useRouter } from '@tarojs/taro'
|
||
import { usePullDownRefresh } from '@tarojs/taro'
|
||
import { pageVersion, createVersion } from '@/api/developer/developer'
|
||
import type { Version, VersionParam, VersionStatus, PublishEnv } from '@/types/developer'
|
||
import './version.scss'
|
||
|
||
// 状态配置
|
||
const STATUS_CONFIG: Record<VersionStatus, { label: string; color: string; bgColor: string }> = {
|
||
0: { label: '构建中', color: '#faad14', bgColor: '#fffbe6' },
|
||
1: { label: '已发布', color: '#52c41a', bgColor: '#f6ffed' },
|
||
2: { label: '已回滚', color: '#ff4d4f', bgColor: '#fff1f0' },
|
||
3: { label: '构建失败', color: '#f5222d', bgColor: '#fff1f0' },
|
||
}
|
||
|
||
// 环境配置
|
||
const ENV_CONFIG: Record<PublishEnv, { label: string; color: string }> = {
|
||
development: { label: '开发环境', color: '#722ed1' },
|
||
staging: { label: '预发布环境', color: '#1890ff' },
|
||
production: { label: '生产环境', color: '#52c41a' },
|
||
}
|
||
|
||
const VersionPage: React.FC = () => {
|
||
const router = useRouter()
|
||
const appId = Number(router.params.id)
|
||
|
||
const [loading, setLoading] = useState(false)
|
||
const [refreshing, setRefreshing] = useState(false)
|
||
const [list, setList] = useState<Version[]>([])
|
||
const [page, setPage] = useState(1)
|
||
const [hasMore, setHasMore] = useState(true)
|
||
const [showCreate, setShowCreate] = useState(false)
|
||
const [createForm, setCreateForm] = useState({
|
||
versionName: '',
|
||
versionNo: '',
|
||
changelog: '',
|
||
env: 'staging' as PublishEnv,
|
||
})
|
||
const [creating, setCreating] = useState(false)
|
||
const [currentTab, setCurrentTab] = useState<PublishEnv | 'all'>('all')
|
||
|
||
// 加载数据
|
||
const loadData = async (pageNum: number = 1, isRefresh = false) => {
|
||
if (loading) return
|
||
setLoading(true)
|
||
if (isRefresh) setRefreshing(true)
|
||
|
||
try {
|
||
const params: VersionParam = {
|
||
page: pageNum,
|
||
limit: 20,
|
||
websiteId: appId,
|
||
env: currentTab === 'all' ? undefined : currentTab,
|
||
}
|
||
const data = await pageVersion(params)
|
||
|
||
if (pageNum === 1) {
|
||
setList(data?.records || [])
|
||
} else {
|
||
setList(prev => [...prev, ...(data?.records || [])])
|
||
}
|
||
|
||
const total = data?.total || 0
|
||
const records = data?.records || []
|
||
setHasMore(records.length > 0 && (pageNum * 20) < total)
|
||
setPage(pageNum)
|
||
} catch (err) {
|
||
console.error('加载失败', err)
|
||
Taro.showToast({ title: '加载失败', icon: 'none' })
|
||
} finally {
|
||
setLoading(false)
|
||
setRefreshing(false)
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
loadData(1)
|
||
}, [appId, currentTab])
|
||
|
||
// 下拉刷新
|
||
usePullDownRefresh(() => {
|
||
loadData(1, true)
|
||
})
|
||
|
||
// 加载更多
|
||
const onReachBottom = () => {
|
||
if (hasMore && !loading) {
|
||
loadData(page + 1)
|
||
}
|
||
}
|
||
|
||
// 创建版本
|
||
const handleCreate = async () => {
|
||
if (!createForm.versionName.trim()) {
|
||
Taro.showToast({ title: '请输入版本名称', icon: 'none' })
|
||
return
|
||
}
|
||
if (!createForm.versionNo.trim()) {
|
||
Taro.showToast({ title: '请输入版本号', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
setCreating(true)
|
||
try {
|
||
await createVersion({
|
||
websiteId: appId,
|
||
versionName: createForm.versionName,
|
||
versionNo: createForm.versionNo,
|
||
changelog: createForm.changelog,
|
||
env: createForm.env,
|
||
} as Partial<Version>)
|
||
Taro.showToast({ title: '发布成功', icon: 'success' })
|
||
setShowCreate(false)
|
||
setCreateForm({ versionName: '', versionNo: '', changelog: '', env: 'staging' })
|
||
loadData(1)
|
||
} catch (err) {
|
||
console.error('发布失败', err)
|
||
Taro.showToast({ title: '发布失败', icon: 'none' })
|
||
} finally {
|
||
setCreating(false)
|
||
}
|
||
}
|
||
|
||
// 发布新版本
|
||
const handlePublish = () => {
|
||
if (list.some(v => v.status === 0)) {
|
||
Taro.showToast({ title: '有版本正在构建中', icon: 'none' })
|
||
return
|
||
}
|
||
setShowCreate(true)
|
||
}
|
||
|
||
// 格式化日期
|
||
const formatDate = (dateStr?: string) => {
|
||
if (!dateStr) return '-'
|
||
const date = new Date(dateStr)
|
||
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')}`
|
||
}
|
||
|
||
// 格式化文件大小
|
||
const formatSize = (size?: string) => {
|
||
if (!size) return '-'
|
||
if (size.length < 4) return size
|
||
const num = parseFloat(size)
|
||
if (num >= 1024 * 1024) {
|
||
return (num / (1024 * 1024)).toFixed(2) + ' MB'
|
||
} else if (num >= 1024) {
|
||
return (num / 1024).toFixed(2) + ' KB'
|
||
}
|
||
return size + ' B'
|
||
}
|
||
|
||
// Tab 配置
|
||
const tabs: { key: PublishEnv | 'all'; label: string }[] = [
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'development', label: '开发' },
|
||
{ key: 'staging', label: '预发布' },
|
||
{ key: 'production', label: '生产' },
|
||
]
|
||
|
||
return (
|
||
<View className="version-page">
|
||
{/* 头部 */}
|
||
<View className="version-page__header">
|
||
<Text className="version-page__title">📦 版本管理</Text>
|
||
<Button
|
||
className="version-page__publish-btn"
|
||
size="mini"
|
||
onClick={handlePublish}
|
||
>
|
||
+ 发布版本
|
||
</Button>
|
||
</View>
|
||
|
||
{/* 环境 Tab */}
|
||
<View className="version-page__tabs">
|
||
{tabs.map((tab) => (
|
||
<View
|
||
key={tab.key}
|
||
className={`version-page__tab ${currentTab === tab.key ? 'active' : ''}`}
|
||
onClick={() => setCurrentTab(tab.key)}
|
||
>
|
||
{tab.label}
|
||
</View>
|
||
))}
|
||
</View>
|
||
|
||
{/* 列表 */}
|
||
<View className="version-page__list">
|
||
{list.length === 0 && !loading ? (
|
||
<View className="version-page__empty">
|
||
<Text>暂无版本记录</Text>
|
||
<Text className="version-page__empty-hint">点击「发布版本」创建新版本</Text>
|
||
</View>
|
||
) : (
|
||
list.map((item) => (
|
||
<View key={item.id} className="version-card">
|
||
<View className="version-card__header">
|
||
<View className="version-card__info">
|
||
<Text className="version-card__name">
|
||
{item.versionName || `v${item.versionNo}`}
|
||
</Text>
|
||
<View
|
||
className="version-card__status"
|
||
style={{
|
||
color: STATUS_CONFIG[item.status as VersionStatus]?.color,
|
||
background: STATUS_CONFIG[item.status as VersionStatus]?.bgColor,
|
||
}}
|
||
>
|
||
{STATUS_CONFIG[item.status as VersionStatus]?.label}
|
||
</View>
|
||
</View>
|
||
<View
|
||
className="version-card__env"
|
||
style={{ color: ENV_CONFIG[item.env as PublishEnv]?.color }}
|
||
>
|
||
{ENV_CONFIG[item.env as PublishEnv]?.label}
|
||
</View>
|
||
</View>
|
||
|
||
{item.isCurrent && (
|
||
<View className="version-card__current">
|
||
<Text>当前版本</Text>
|
||
</View>
|
||
)}
|
||
|
||
<View className="version-card__body">
|
||
{item.versionNo && (
|
||
<View className="version-card__row">
|
||
<Text className="version-card__label">版本号:</Text>
|
||
<Text className="version-card__value">{item.versionNo}</Text>
|
||
</View>
|
||
)}
|
||
{item.packageSize && (
|
||
<View className="version-card__row">
|
||
<Text className="version-card__label">包大小:</Text>
|
||
<Text className="version-card__value">{formatSize(item.packageSize)}</Text>
|
||
</View>
|
||
)}
|
||
{item.publishBy && (
|
||
<View className="version-card__row">
|
||
<Text className="version-card__label">发布人:</Text>
|
||
<Text className="version-card__value">{item.publishBy}</Text>
|
||
</View>
|
||
)}
|
||
{item.publishTime && (
|
||
<View className="version-card__row">
|
||
<Text className="version-card__label">发布时间:</Text>
|
||
<Text className="version-card__value">{formatDate(item.publishTime)}</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
{item.changelog && (
|
||
<View className="version-card__changelog">
|
||
<Text className="version-card__changelog-title">更新日志:</Text>
|
||
<Text className="version-card__changelog-content">{item.changelog}</Text>
|
||
</View>
|
||
)}
|
||
|
||
<View className="version-card__footer">
|
||
<Text className="version-card__time">
|
||
创建于 {formatDate(item.createTime)}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
))
|
||
)}
|
||
|
||
{/* 加载状态 */}
|
||
{loading && list.length > 0 && (
|
||
<View className="version-page__loading">
|
||
<Text>加载中...</Text>
|
||
</View>
|
||
)}
|
||
|
||
{!hasMore && list.length > 0 && (
|
||
<View className="version-page__no-more">
|
||
<Text>没有更多了</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
{/* 发布弹窗 */}
|
||
{showCreate && (
|
||
<View className="version-page__modal">
|
||
<View className="version-page__modal-mask" onClick={() => setShowCreate(false)} />
|
||
<View className="version-page__modal-content">
|
||
<Text className="version-page__modal-title">发布新版本</Text>
|
||
|
||
<View className="version-page__form">
|
||
<View className="version-page__form-item">
|
||
<Text className="version-page__form-label">版本名称 *</Text>
|
||
<Input
|
||
className="version-page__form-input"
|
||
placeholder="如:正式版 v1.0.0"
|
||
value={createForm.versionName}
|
||
onInput={(e) => setCreateForm(prev => ({ ...prev, versionName: e.detail.value }))}
|
||
/>
|
||
</View>
|
||
|
||
<View className="version-page__form-item">
|
||
<Text className="version-page__form-label">版本号 *</Text>
|
||
<Input
|
||
className="version-page__form-input"
|
||
placeholder="如:1.0.0"
|
||
value={createForm.versionNo}
|
||
onInput={(e) => setCreateForm(prev => ({ ...prev, versionNo: e.detail.value }))}
|
||
/>
|
||
</View>
|
||
|
||
<View className="version-page__form-item">
|
||
<Text className="version-page__form-label">发布环境</Text>
|
||
<View className="version-page__form-radio">
|
||
{(['staging', 'production'] as const).map((env) => (
|
||
<View
|
||
key={env}
|
||
className={`version-page__form-radio-item ${createForm.env === env ? 'active' : ''}`}
|
||
onClick={() => setCreateForm(prev => ({ ...prev, env }))}
|
||
>
|
||
{ENV_CONFIG[env].label}
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
|
||
<View className="version-page__form-item">
|
||
<Text className="version-page__form-label">更新日志</Text>
|
||
<View className="version-page__form-textarea">
|
||
<textarea
|
||
className="version-page__textarea"
|
||
placeholder="请输入版本更新内容..."
|
||
value={createForm.changelog}
|
||
onInput={(e: any) => setCreateForm(prev => ({ ...prev, changelog: e.detail.value }))}
|
||
rows={4}
|
||
/>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
<View className="version-page__modal-actions">
|
||
<Button
|
||
className="version-page__modal-btn version-page__modal-btn--cancel"
|
||
onClick={() => setShowCreate(false)}
|
||
>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
className="version-page__modal-btn version-page__modal-btn--confirm"
|
||
loading={creating}
|
||
onClick={handleCreate}
|
||
>
|
||
发布
|
||
</Button>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default VersionPage
|