1
0
forked from boos/BoosAPI

fix: address code review findings

- fix(api): use effectiveTokenId in route channel insert to match duplicate detection logic
- fix(css): rename keyframes to kebab-case (filter-bar-slide-down/up)
- fix(web): wrap "保存中..." text with tr() in ManualRoutePanel
- fix(web): use ref for fresh cache check and add error handling in useRouteChannels
This commit is contained in:
Hureru
2026-03-11 15:51:10 +08:00
parent 8128d6e036
commit f5fcf5d707
4 changed files with 14 additions and 9 deletions
+1 -1
View File
@@ -539,7 +539,7 @@ export async function tokensRoutes(app: FastifyInstance) {
await db.insert(schema.routeChannels).values({ await db.insert(schema.routeChannels).values({
routeId, routeId,
accountId: item.accountId, accountId: item.accountId,
tokenId: item.tokenId, tokenId: effectiveTokenId,
sourceModel: sourceModel || null, sourceModel: sourceModel || null,
priority: 0, priority: 0,
weight: 10, weight: 10,
+4 -4
View File
@@ -4101,19 +4101,19 @@ textarea {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
animation: filterBarSlideDown 0.22s cubic-bezier(0.22, 1, 0.36, 1); animation: filter-bar-slide-down 0.22s cubic-bezier(0.22, 1, 0.36, 1);
} }
.route-filter-bar-expanded.is-closing { .route-filter-bar-expanded.is-closing {
animation: filterBarSlideUp 0.18s ease forwards; animation: filter-bar-slide-up 0.18s ease forwards;
} }
@keyframes filterBarSlideDown { @keyframes filter-bar-slide-down {
from { opacity: 0; max-height: 0; transform: translateY(-4px); } from { opacity: 0; max-height: 0; transform: translateY(-4px); }
to { opacity: 1; max-height: 600px; transform: translateY(0); } to { opacity: 1; max-height: 600px; transform: translateY(0); }
} }
@keyframes filterBarSlideUp { @keyframes filter-bar-slide-up {
from { opacity: 1; max-height: 600px; } from { opacity: 1; max-height: 600px; }
to { opacity: 0; max-height: 0; overflow: hidden; } to { opacity: 0; max-height: 0; overflow: hidden; }
} }
@@ -174,7 +174,7 @@ export default function ManualRoutePanel({
className="spinner spinner-sm" className="spinner spinner-sm"
style={{ borderTopColor: 'white', borderColor: 'rgba(255,255,255,0.3)' }} style={{ borderTopColor: 'white', borderColor: 'rgba(255,255,255,0.3)' }}
/>{' '} />{' '}
保存中... {tr('保存中...')}
</> </>
) : ( ) : (
tr(editingRouteId ? '保存群组' : '创建群组') tr(editingRouteId ? '保存群组' : '创建群组')
@@ -1,4 +1,4 @@
import { useState, useCallback } from 'react'; import { useState, useCallback, useRef } from 'react';
import { api } from '../../api.js'; import { api } from '../../api.js';
import { normalizeChannels } from './utils.js'; import { normalizeChannels } from './utils.js';
import type { RouteChannel } from './types.js'; import type { RouteChannel } from './types.js';
@@ -6,19 +6,24 @@ import type { RouteChannel } from './types.js';
export function useRouteChannels() { export function useRouteChannels() {
const [channelsByRouteId, setChannelsByRouteId] = useState<Record<number, RouteChannel[]>>({}); const [channelsByRouteId, setChannelsByRouteId] = useState<Record<number, RouteChannel[]>>({});
const [loadingChannelsByRouteId, setLoadingChannelsByRouteId] = useState<Record<number, boolean>>({}); const [loadingChannelsByRouteId, setLoadingChannelsByRouteId] = useState<Record<number, boolean>>({});
const channelsByRouteIdRef = useRef(channelsByRouteId);
channelsByRouteIdRef.current = channelsByRouteId;
const loadChannels = useCallback(async (routeId: number, force = false) => { const loadChannels = useCallback(async (routeId: number, force = false) => {
if (!force && channelsByRouteId[routeId]) return channelsByRouteId[routeId]; if (!force && channelsByRouteIdRef.current[routeId]) return channelsByRouteIdRef.current[routeId];
setLoadingChannelsByRouteId((prev) => ({ ...prev, [routeId]: true })); setLoadingChannelsByRouteId((prev) => ({ ...prev, [routeId]: true }));
try { try {
const channels = await api.getRouteChannels(routeId); const channels = await api.getRouteChannels(routeId);
const sorted = normalizeChannels(channels || []); const sorted = normalizeChannels(channels || []);
setChannelsByRouteId((prev) => ({ ...prev, [routeId]: sorted })); setChannelsByRouteId((prev) => ({ ...prev, [routeId]: sorted }));
return sorted; return sorted;
} catch (error) {
console.error(`Failed to load channels for route ${routeId}:`, error);
throw error;
} finally { } finally {
setLoadingChannelsByRouteId((prev) => ({ ...prev, [routeId]: false })); setLoadingChannelsByRouteId((prev) => ({ ...prev, [routeId]: false }));
} }
}, [channelsByRouteId]); }, []);
const invalidateChannels = useCallback((routeId: number) => { const invalidateChannels = useCallback((routeId: number) => {
setChannelsByRouteId((prev) => { setChannelsByRouteId((prev) => {