feat(user): 合并切换开发者菜单项状态并更新开发者中心UI

- 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心”
- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式
- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构
- 重新设计开发者中心页面UI,统一为门店中心风格
- 顶部展示开发者头像及昵称手机号信息
- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标
- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片
- 未登录或非开发者状态显示相应空态提示及操作按钮
- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示
This commit is contained in:
2026-07-23 18:35:53 +08:00
parent dc72e530c7
commit 226e223d96
3 changed files with 216 additions and 90 deletions

View File

@@ -1,26 +1,65 @@
import React, { useState, useEffect, useCallback } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import { View, Text, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import NavBar from '@/components/NavBar'
import StatCard from '@/components/common/StatCard'
import EntryGrid from '@/components/common/EntryGrid'
import Loading from '@/components/common/Loading'
import Badge from '@/components/common/Badge'
import { useUser } from '@/hooks/useUser'
import { getMyApps } from '@/api/app/appProduct'
import { getAppApiKeyStats } from '@/api/app/apikey'
import { getMyTickets } from '@/api/app/ticket'
import { getPageTotal } from '@/utils/devcenter'
definePageConfig({ navigationBarTitleText: '开发者中心' })
definePageConfig({
navigationBarTitleText: '开发者中心',
})
const DeveloperCenterPage: React.FC = () => {
const { user, isLoggedIn } = useUser()
// 功能卡片定义(未来新增功能只需在这里加一项)
const FEATURE_CARDS = [
{
key: 'apps',
icon: '📦',
title: '我的应用',
desc: '管理你开发并上架的应用',
url: '/pages/developer/apps/index',
color: '#3b82f6',
bgColor: '#dbeafe',
showBadge: true,
},
{
key: 'apikeys',
icon: '🔑',
title: 'API Key',
desc: '查看与管理你的接口密钥',
url: '/pages/developer/apikeys/index',
color: '#10b981',
bgColor: '#dcfce7',
showBadge: true,
},
{
key: 'tickets',
icon: '🎫',
title: '我的工单',
desc: '提交与跟踪你的工单',
url: '/pages/developer/tickets/index',
color: '#f59e0b',
bgColor: '#fef3c7',
showBadge: true,
},
]
export default function DeveloperCenterPage() {
const { user, isLoggedIn, loading: userLoading } = useUser()
const isDeveloper = !!(user as any)?.isDeveloper
const [loading, setLoading] = useState(true)
const [stats, setStats] = useState({ apps: 0, keys: 0, tickets: 0 })
useEffect(() => {
if (isLoggedIn && isDeveloper) {
load()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoggedIn, isDeveloper])
/** 加载我的应用 / API Key / 工单数量 */
const load = useCallback(async () => {
setLoading(true)
try {
const [appsR, keysR, ticketsR] = await Promise.allSettled([
getMyApps({ page: 1, limit: 1 }),
@@ -31,84 +70,159 @@ const DeveloperCenterPage: React.FC = () => {
const keys = keysR.status === 'fulfilled' ? getPageTotal(keysR.value) : 0
const tickets = ticketsR.status === 'fulfilled' ? getPageTotal(ticketsR.value) : 0
setStats({ apps, keys, tickets })
} catch (e) {
} catch {
// ignore
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
if (isLoggedIn) load()
}, [isLoggedIn, load])
/** 获取卡片角标数字 */
const getBadgeCount = (key: string) => {
if (key === 'apps') return stats.apps
if (key === 'apikeys') return stats.keys
if (key === 'tickets') return stats.tickets
return 0
}
if (userLoading) {
return (
<View className='min-h-full bg-gray-50 flex items-center justify-center'>
<Text className='text-gray-400'>...</Text>
</View>
)
}
if (!isLoggedIn) {
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='开发者中心' />
<View className='flex flex-col items-center justify-center py-24'>
<Text className='text-gray-400 text-sm'></Text>
<View
className='mt-4 px-6 py-2 rounded-full'
style={{ background: '#0e932e' }}
onClick={() => Taro.navigateTo({ url: '/passport/login' })}
>
<Text className='text-white text-sm'></Text>
</View>
</View>
<View className='min-h-full bg-gray-50 flex flex-col items-center justify-center py-24'>
<Text className='text-5xl mb-3'>🔐</Text>
<Text className='text-base text-gray-700 mb-2'></Text>
<Text className='text-xs text-gray-400 text-center'></Text>
</View>
)
}
if (!isDeveloper) {
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='开发者中心' />
<View className='flex flex-col items-center justify-center py-20 px-10'>
<Text className='text-5xl mb-3'>🛠</Text>
<Text className='text-base text-gray-700 mb-2'></Text>
<Text className='text-xs text-gray-400 text-center mb-5 leading-relaxed'>
API Key
</Text>
<View
className='px-6 py-2 rounded-full'
style={{ background: '#2563eb' }}
onClick={() => Taro.navigateTo({ url: '/pages/user/vip-upgrade/index' })}
>
<Text className='text-white text-sm'></Text>
</View>
<View className='min-h-full bg-gray-50 flex flex-col items-center justify-center py-20 px-10'>
<Text className='text-5xl mb-3'>🛠</Text>
<Text className='text-base text-gray-700 mb-2'></Text>
<Text className='text-xs text-gray-400 text-center mb-5 leading-relaxed'>
API Key
</Text>
<View
className='px-6 py-2 rounded-full'
style={{ background: '#2563eb' }}
onClick={() => Taro.navigateTo({ url: '/pages/user/vip-upgrade/index' })}
>
<Text className='text-white text-sm'></Text>
</View>
</View>
)
}
const entries = [
{ icon: '📦', label: '我的应用', url: '/pages/developer/apps/index' },
{ icon: '🔑', label: 'API Key', url: '/pages/developer/apikeys/index' },
{ icon: '🎫', label: '我的工单', url: '/pages/developer/tickets/index' },
]
// 头部展示信息
const header = {
avatar: (user as any)?.avatar,
title: '开发者中心',
subtitle: `${((user as any)?.nickname || (user as any)?.username || '')}${((user as any)?.phone) ? ` · ${(user as any).phone}` : ''}`,
}
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='开发者中心' />
<ScrollView scrollY>
{loading ? (
<Loading />
) : (
<View className='p-4'>
<View className='grid grid-cols-3 gap-3 mb-3'>
<StatCard label='我的应用' value={stats.apps} color='#3b82f6' />
<StatCard label='API Key' value={stats.keys} color='#10b981' />
<StatCard label='我的工单' value={stats.tickets} color='#f59e0b' />
</View>
<Text className='text-base font-medium mb-2 block'></Text>
<EntryGrid items={entries} columns={3} />
<View className='h-6' />
<View className='min-h-full bg-gray-50'>
{/* 顶部信息区:头像 + 名称 */}
<View
className='pt-8 pb-12 px-5 rounded-b-3xl relative overflow-hidden'
style={{ background: 'linear-gradient(135deg, #15803d 0%, #22c55e 60%, #4ade80 100%)' }}
>
<View className='absolute -top-10 -right-10 w-40 h-40 rounded-full opacity-10'
style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
<View className='absolute -bottom-6 -left-6 w-24 h-24 rounded-full opacity-15'
style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
<View className='relative z-10 flex items-center gap-4'>
{/* 头像 */}
<View className='w-16 h-16 rounded-full bg-white/20 border-2 border-white/40 overflow-hidden flex-shrink-0'>
{header.avatar ? (
<Image
className='w-full h-full'
src={header.avatar}
mode='aspectFill'
/>
) : (
<View className='w-full h-full flex items-center justify-center'>
<Text className='text-2xl'>👤</Text>
</View>
)}
</View>
)}
</ScrollView>
{/* 信息 */}
<View className='flex-1 min-w-0'>
<Text className='text-white text-lg font-bold block truncate'>
{header.title}
</Text>
<Text className='text-white text-opacity-80 text-sm mt-1 block truncate'>
{header.subtitle}
</Text>
</View>
</View>
</View>
{/* 功能卡片区 */}
<View className='mx-3 -mt-6 relative z-20'>
<View className='flex flex-col gap-3'>
{FEATURE_CARDS.map(card => (
<View
key={card.key}
className='bg-white rounded-xl p-4 flex items-center gap-4 active:opacity-80 relative'
onClick={() => Taro.navigateTo({ url: card.url })}
>
{/* 图标 */}
<View
className='w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0'
style={{ background: card.bgColor }}
>
<Text className='text-2xl'>{card.icon}</Text>
</View>
{/* 文字 */}
<View className='flex-1 min-w-0'>
<Text className='text-gray-800 text-base font-medium block'>{card.title}</Text>
<Text className='text-gray-400 text-xs mt-0.5 block'>{card.desc}</Text>
</View>
{/* 数量角标 */}
{card.showBadge && (
<Badge count={getBadgeCount(card.key)} className='absolute -top-1 -right-1' size={20} fontSize={11} fontWeight='bold' />
)}
{/* 箭头 */}
<Text className='text-gray-300 text-lg flex-shrink-0'></Text>
</View>
))}
</View>
</View>
{/* 底部提示 */}
<View className='mx-3 mt-6 mb-6'>
{/* 开发者帮助 */}
<View
className='bg-white rounded-xl p-4 flex items-center gap-3 active:opacity-80'
onClick={() => Taro.navigateTo({ url: '/pages/user/help-center/index' })}
>
<View className='w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0'
style={{ background: '#dbeafe' }}>
<Text className='text-xl'>💡</Text>
</View>
<View className='flex-1 min-w-0'>
<Text className='text-gray-800 text-sm font-medium block'></Text>
<Text className='text-gray-400 text-xs mt-0.5 block'>
SDK
</Text>
</View>
<Text className='text-blue-500 text-sm flex-shrink-0'></Text>
</View>
</View>
</View>
)
}
export default DeveloperCenterPage