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

179 lines
6.1 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, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { Button, Empty, Tabs, PullToRefresh } from '@nutui/nutui-react-taro'
import './index.scss'
const ProjectList: React.FC = () => {
const [activeTab, setActiveTab] = useState('all')
const [projects, setProjects] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
// 模拟数据
const mockProjects = [
{
id: 1,
name: '我的企业官网',
type: 'pro',
description: '企业品牌展示官网',
appCount: 2,
memberCount: 3,
apiCallCount: 12580,
status: 'active',
updatedAt: '2026-04-10',
},
{
id: 2,
name: '电商小程序',
type: 'enterprise',
description: '多端电商解决方案',
appCount: 5,
memberCount: 8,
apiCallCount: 98650,
status: 'active',
updatedAt: '2026-04-12',
},
{
id: 3,
name: '内部管理系统',
type: 'basic',
description: 'OA办公系统',
appCount: 1,
memberCount: 5,
apiCallCount: 3200,
status: 'active',
updatedAt: '2026-04-08',
},
]
// 加载数据
const loadData = async () => {
try {
setLoading(true)
// TODO: 替换为真实 API 调用
// const result = await pageMyProject({ type: activeTab === 'all' ? undefined : activeTab })
await new Promise((resolve) => setTimeout(resolve, 500))
setProjects(mockProjects)
} catch (error) {
console.error('加载失败', error)
Taro.showToast({ title: '加载失败', icon: 'none' })
} finally {
setLoading(false)
}
}
// 下拉刷新
const onRefresh = async () => {
setRefreshing(true)
await loadData()
setRefreshing(false)
}
useEffect(() => {
loadData()
}, [activeTab])
// 获取项目类型标签
const getTypeBadge = (type: string) => {
const badges: Record<string, { text: string; color: string }> = {
basic: { text: '基础', color: '#6b7280' },
pro: { text: '专业', color: '#3b82f6' },
enterprise: { text: '企业', color: '#f59e0b' },
}
return badges[type] || badges.basic
}
// 跳转创建页面
const handleCreate = () => {
Taro.navigateTo({ url: '/developer/project/create' })
}
// 跳转项目详情
const handleProjectClick = (id: number) => {
Taro.navigateTo({ url: `/developer/project/${id}` })
}
return (
<View className="project-list-page">
<PullToRefresh refreshing={refreshing} onRefresh={onRefresh}>
{/* 标签页 */}
<View className="tabs-wrapper">
<Tabs value={activeTab} onChange={(v) => setActiveTab(v as string)}>
<Tabs.TabPane title="全部项目" value="all" />
<Tabs.TabPane title="基础版" value="basic" />
<Tabs.TabPane title="专业版" value="pro" />
<Tabs.TabPane title="企业版" value="enterprise" />
</Tabs>
</View>
<ScrollView scrollY className="project-list-page__scroll">
{/* 项目列表 */}
<View className="project-list">
{loading ? (
<View className="project-list__loading">
<Text>...</Text>
</View>
) : projects.length === 0 ? (
<Empty description="暂无项目" />
) : (
<View className="project-list__content">
{projects.map((project) => {
const badge = getTypeBadge(project.type)
return (
<View key={project.id} className="project-card" onClick={() => handleProjectClick(project.id)}>
<View className="project-card__header">
<View className="project-card__title-row">
<Text className="project-card__name">{project.name}</Text>
<View className="project-card__badge" style={{ backgroundColor: `${badge.color}15`, color: badge.color }}>
<Text className="project-card__badgeText">{badge.text}</Text>
</View>
</View>
<Text className="project-card__desc">{project.description}</Text>
</View>
<View className="project-card__stats">
<View className="project-card__stat">
<Text className="project-card__statValue">{project.appCount}</Text>
<Text className="project-card__statLabel"></Text>
</View>
<View className="project-card__stat">
<Text className="project-card__statValue">{project.memberCount}</Text>
<Text className="project-card__statLabel"></Text>
</View>
<View className="project-card__stat">
<Text className="project-card__statValue">{project.apiCallCount}</Text>
<Text className="project-card__statLabel">API调用</Text>
</View>
</View>
<View className="project-card__footer">
<Text className="project-card__time"> {project.updatedAt}</Text>
<View className="project-card__action">
<Text className="project-card__actionText"> </Text>
</View>
</View>
</View>
)
})}
</View>
)}
</View>
{/* 底部安全区域 */}
<View className="safe-area-bottom" />
</ScrollView>
{/* 创建按钮 */}
<View className="create-btn-wrapper">
<Button type="primary" block onClick={handleCreate}>
</Button>
</View>
</PullToRefresh>
</View>
)
}
export default ProjectList