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,364 @@
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