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