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

365 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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