feat(routes): 添加新频道和令牌的受保护路由

feat(channels): 支持创建模式,优化表单处理逻辑
feat(tokens): 支持创建模式,优化表单处理逻辑
fix(sidebar): 修复导航链接的激活状态判断
This commit is contained in:
Jeff Gu committed 2026-04-10 14:54:39 +08:00
1 parent 7ff59e153a
commit d5f23fe9ed
4 files changed
+77 -21

No files matched your search

+16
View File
@@ -77,6 +77,14 @@ function App() {
</ProtectedRoute>
}
/>
<Route
path="/channels/new"
element={
<ProtectedRoute>
<Channels createMode />
</ProtectedRoute>
}
/>
<Route
path="/tokens"
element={
@@ -85,6 +93,14 @@ function App() {
</ProtectedRoute>
}
/>
<Route
path="/tokens/new"
element={
<ProtectedRoute>
<Tokens createMode />
</ProtectedRoute>
}
/>
<Route
path="/pricing"
element={
+1 -1
View File
@@ -80,7 +80,7 @@ export function Sidebar({
};
const NavLink = ({ item }: { item: NavItem }) => {
const isActive = location.pathname === item.href;
const isActive = location.pathname === item.href || location.pathname.startsWith(`${item.href}/`);
return (
<Link
to={item.href}
+29 -9
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
import { apiClient } from '@/api/client'
import { Channel, ChannelConfig, ChannelModelMapping } from '@/types'
import { Card, CardContent } from '@/components/ui/card'
@@ -365,8 +366,9 @@ const getChannelEndpointPreview = (type: string | undefined, endpoint: string):
return buildFallbackEndpointPreview(trimmedEndpoint, requestPath)
}
export function Channels() {
const [view, setView] = useState<'list' | 'form'>('list')
export function Channels({ createMode = false }: { createMode?: boolean }) {
const navigate = useNavigate()
const [view, setView] = useState<'list' | 'form'>(createMode ? 'form' : 'list')
const [editMode, setEditMode] = useState<EditMode>('form')
const [modelEditorMode, setModelEditorMode] = useState<ModelEditorMode>('visual')
const [editingKey, setEditingKey] = useState<string | null>(null)
@@ -426,8 +428,7 @@ export function Channels() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['channels'] })
addToast(editingKey ? '渠道更新成功' : '渠道添加成功', 'success')
resetForm()
setView('list')
closeForm()
},
onError: (error: Error) => {
addToast('保存失败:' + error.message, 'error')
@@ -464,7 +465,7 @@ export function Channels() {
},
})
const resetForm = () => {
const resetForm = useCallback(() => {
setFormData(createDefaultChannelFormData())
setChannelKey('')
setJsonValue('')
@@ -474,11 +475,30 @@ export function Channels() {
setModelEditorMode('visual')
setEditingKey(null)
setEditMode('form')
}, [])
useEffect(() => {
if (createMode) {
resetForm()
setView('form')
return
}
setView('list')
}, [createMode, resetForm])
const closeForm = () => {
resetForm()
setView('list')
if (createMode) {
navigate('/channels', { replace: true })
}
}
const handleAdd = () => {
resetForm()
setView('form')
navigate('/channels/new')
}
const handleEdit = (channel: Channel) => {
@@ -827,7 +847,7 @@ export function Channels() {
<div className="p-4 md:p-6 lg:p-8 animate-in">
<div className="max-w-4xl mx-auto">
<div className="mb-6">
<Button variant="ghost" size="sm" className="mb-3 -ml-2 text-muted-foreground" onClick={() => setView('list')}>
<Button variant="ghost" size="sm" className="mb-3 -ml-2 text-muted-foreground" onClick={closeForm}>
<ArrowLeft className="h-4 w-4 mr-1" />
返回列表
</Button>
@@ -1051,7 +1071,7 @@ export function Channels() {
)}
<div className="flex items-center justify-end gap-3 pt-2">
<Button variant="outline" onClick={() => setView('list')}>
<Button variant="outline" onClick={closeForm}>
取消
</Button>
<Button onClick={handleSave} disabled={saveMutation.isPending}>
+31 -11
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
import { apiClient } from '@/api/client'
import { Token, TokenConfig, Channel } from '@/types'
import { Card, CardContent } from '@/components/ui/card'
@@ -32,8 +33,9 @@ import { PageContainer } from '@/components/ui/page-container'
type EditMode = 'form' | 'json'
export function Tokens() {
const [view, setView] = useState<'list' | 'form'>('list')
export function Tokens({ createMode = false }: { createMode?: boolean }) {
const navigate = useNavigate()
const [view, setView] = useState<'list' | 'form'>(createMode ? 'form' : 'list')
const [editMode, setEditMode] = useState<EditMode>('form')
const [editingKey, setEditingKey] = useState<string | null>(null)
const [formData, setFormData] = useState<TokenConfig>({
@@ -41,7 +43,7 @@ export function Tokens() {
channel_keys: [],
total_quota: 0,
})
const [tokenKey, setTokenKey] = useState('')
const [tokenKey, setTokenKey] = useState(() => (createMode ? generateTokenKey() : ''))
const [jsonValue, setJsonValue] = useState('')
const [availableChannels, setAvailableChannels] = useState<string[]>([])
const [selectedChannels, setSelectedChannels] = useState<string[]>([])
@@ -86,8 +88,7 @@ export function Tokens() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tokens'] })
addToast(editingKey ? '令牌更新成功' : '令牌添加成功', 'success')
resetForm()
setView('list')
closeForm()
},
onError: (error: any) => {
addToast('保存失败:' + error.message, 'error')
@@ -120,19 +121,38 @@ export function Tokens() {
},
})
const resetForm = () => {
const resetForm = useCallback(() => {
setFormData({ name: '', channel_keys: [], total_quota: 0 })
setTokenKey('')
setJsonValue('')
setSelectedChannels([])
setEditingKey(null)
setEditMode('form')
}, [])
useEffect(() => {
if (createMode) {
resetForm()
setTokenKey(generateTokenKey())
setView('form')
return
}
setView('list')
}, [createMode, resetForm])
const closeForm = () => {
resetForm()
setView('list')
if (createMode) {
navigate('/tokens', { replace: true })
}
}
const handleAdd = () => {
resetForm()
setTokenKey(generateTokenKey())
setView('form')
navigate('/tokens/new')
}
const handleEdit = (token: Token) => {
@@ -448,7 +468,7 @@ export function Tokens() {
<div className="max-w-2xl mx-auto">
{/* Header */}
<div className="mb-6">
<Button variant="ghost" size="sm" className="mb-3 -ml-2 text-muted-foreground" onClick={() => setView('list')}>
<Button variant="ghost" size="sm" className="mb-3 -ml-2 text-muted-foreground" onClick={closeForm}>
<ArrowLeft className="h-4 w-4 mr-1" />
返回列表
</Button>
@@ -597,7 +617,7 @@ export function Tokens() {
{/* Actions */}
<div className="flex items-center justify-end gap-3 pt-2">
<Button variant="outline" onClick={() => setView('list')}>
<Button variant="outline" onClick={closeForm}>
取消
</Button>
<Button onClick={handleSave} disabled={saveMutation.isPending}>