refactor(user): 将VIP身份统一改为开发者标记
- 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记 - 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper - UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save - 前端api新增setUserDeveloper调用新的审核接口 - vip-review页面移除角色相关代码,改为调用setUserDeveloper方法 - 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录 - app.config.ts追加开发者中心与平台管理相关页面路由 - 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等) - 添加公共工具方法和复用组件用于开发者中心及平台管理功能 - 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性
This commit is contained in:
175
src/pages/developer/apikeys/index.tsx
Normal file
175
src/pages/developer/apikeys/index.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { View, Text, ScrollView, Input, Textarea } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import NavBar from '@/components/NavBar'
|
||||
import Loading from '@/components/common/Loading'
|
||||
import { useUser } from '@/hooks/useUser'
|
||||
import { pageAppApiKey, createAppApiKey, updateAppApiKeyStatus, removeAppApiKey } from '@/api/app/apikey'
|
||||
import { getPageList, maskKey } from '@/utils/devcenter'
|
||||
|
||||
definePageConfig({ navigationBarTitleText: 'API Key 管理' })
|
||||
|
||||
const ApiKeysPage: React.FC = () => {
|
||||
const { isLoggedIn } = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [list, setList] = useState<any[]>([])
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [form, setForm] = useState({ name: '', remark: '' })
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await pageAppApiKey({ page: 1, limit: 50 })
|
||||
setList(getPageList(data))
|
||||
} catch (e) {
|
||||
// ignore
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn) load()
|
||||
}, [isLoggedIn, load])
|
||||
|
||||
const toggle = async (item: any) => {
|
||||
try {
|
||||
await updateAppApiKeyStatus(item.id, item.status === 0 ? 1 : 0)
|
||||
Taro.showToast({ title: '已更新', icon: 'none' })
|
||||
load()
|
||||
} catch (e: any) {
|
||||
Taro.showToast({ title: e?.message || '操作失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const remove = (item: any) => {
|
||||
Taro.showModal({
|
||||
title: '删除密钥',
|
||||
content: '删除后无法恢复,确定删除?',
|
||||
success: async (r) => {
|
||||
if (r.confirm) {
|
||||
try {
|
||||
await removeAppApiKey(item.id)
|
||||
Taro.showToast({ title: '已删除', icon: 'success' })
|
||||
load()
|
||||
} catch (e: any) {
|
||||
Taro.showToast({ title: e?.message || '删除失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const copy = (item: any) => {
|
||||
Taro.setClipboardData({ data: item.apiKey || '' })
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!form.name.trim()) {
|
||||
Taro.showToast({ title: '请填写名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await createAppApiKey({ name: form.name, scopes: 'read,write', remark: form.remark })
|
||||
Taro.showToast({ title: '创建成功', icon: 'success' })
|
||||
setShowModal(false)
|
||||
load()
|
||||
} catch (e: any) {
|
||||
Taro.showToast({ title: e?.message || '创建失败', icon: 'none' })
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='min-h-screen bg-gray-100'>
|
||||
<NavBar
|
||||
title='API Key 管理'
|
||||
rightText='创建'
|
||||
onRightClick={() => {
|
||||
setForm({ name: '', remark: '' })
|
||||
setShowModal(true)
|
||||
}}
|
||||
/>
|
||||
<ScrollView scrollY>
|
||||
{loading ? (
|
||||
<Loading />
|
||||
) : list.length === 0 ? (
|
||||
<View className='flex flex-col items-center justify-center py-24'>
|
||||
<Text className='text-gray-400 text-sm'>暂无 API Key</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='mt-3'>
|
||||
{list.map((item) => (
|
||||
<View key={item.id} className='bg-white px-4 py-3 border-b border-gray-50'>
|
||||
<View className='flex items-center justify-between'>
|
||||
<Text className='text-sm text-gray-800'>{item.name}</Text>
|
||||
<Text
|
||||
className='text-xs px-1.5 py-0.5 rounded-full'
|
||||
style={{
|
||||
color: item.status === 0 ? '#16a34a' : '#6b7280',
|
||||
background: (item.status === 0 ? '#16a34a' : '#6b7280') + '22',
|
||||
}}
|
||||
>
|
||||
{item.status === 0 ? '正常' : '禁用'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className='text-xs text-gray-400 mt-1 block'>{maskKey(item.apiKey)}</Text>
|
||||
<View className='flex gap-4 mt-2'>
|
||||
<Text className='text-xs' style={{ color: '#2563eb' }} onClick={() => copy(item)}>
|
||||
复制
|
||||
</Text>
|
||||
<Text className='text-xs' style={{ color: '#d97706' }} onClick={() => toggle(item)}>
|
||||
{item.status === 0 ? '禁用' : '启用'}
|
||||
</Text>
|
||||
<Text className='text-xs' style={{ color: '#dc2626' }} onClick={() => remove(item)}>
|
||||
删除
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{showModal && (
|
||||
<View style={{ position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.4)', zIndex: 999 }}>
|
||||
<View className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4'>
|
||||
<Text className='text-base font-medium block mb-3'>创建 API Key</Text>
|
||||
<View className='mb-3'>
|
||||
<Text className='text-xs text-gray-500 block mb-1'>名称</Text>
|
||||
<Input
|
||||
className='bg-gray-100 rounded-lg px-3 py-2 text-sm'
|
||||
placeholder='如:生产环境'
|
||||
value={form.name}
|
||||
onInput={(e) => setForm({ ...form, name: e.detail.value })}
|
||||
/>
|
||||
</View>
|
||||
<View className='mb-4'>
|
||||
<Text className='text-xs text-gray-500 block mb-1'>备注</Text>
|
||||
<Textarea
|
||||
className='bg-gray-100 rounded-lg px-3 py-2 text-sm w-full'
|
||||
placeholder='可选'
|
||||
value={form.remark}
|
||||
onInput={(e) => setForm({ ...form, remark: e.detail.value })}
|
||||
style={{ height: '70px' }}
|
||||
/>
|
||||
</View>
|
||||
<View className='flex gap-3'>
|
||||
<View className='flex-1 py-2.5 rounded-lg bg-gray-100 text-center' onClick={() => setShowModal(false)}>
|
||||
<Text className='text-sm text-gray-600'>取消</Text>
|
||||
</View>
|
||||
<View className='flex-1 py-2.5 rounded-lg text-center' style={{ background: '#2563eb' }} onClick={submit}>
|
||||
<Text className='text-sm text-white'>{submitting ? '提交中...' : '创建'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default ApiKeysPage
|
||||
Reference in New Issue
Block a user