You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

T3栈饮酒游戏应用中卡牌随机加载后不定期停止渲染问题求助

T3栈饮酒游戏应用中卡牌随机加载后不定期停止渲染问题求助

大家好,我是一名初级开发者,最近基于T3 Create App + TypeScript + Drizzle + PostgreSQL开发了一款“真心话大冒险”类的饮酒游戏。

目前遇到一个非常诡异的问题:应用可以正常获取卡牌数据,但会不定期停止渲染新卡牌——有时候抽1-2张就卡住,有时候能连续抽到8张,之后就完全无法加载新卡牌了,必须刷新页面才能恢复正常。

更让人困惑的是:

  • 控制台没有任何错误日志输出
  • 数据库查询的控制台日志显示执行成功
  • 但界面就是不会渲染新的卡牌

下面是我的相关核心代码:


1. Game页面组件代码

'use client'

import '@/styles/gameStyle.css'
import React, { useState, useEffect, useCallback } from "react"
import { api } from "@/trpc/react"
import { v4 as uuidv4 } from 'uuid'
import { useMotionValue, useDragControls, motion, AnimatePresence } from "framer-motion"
import Card from "../components/card/card"
import DialogInfoModal from "../components/game/dialogInfoModal"
import PlayButton from "../components/game/PlayButton"
import PlayerForm from "../components/game/PlayerForm"
import DeckChoose from "../components/game/DeckChoose"
import { Button } from "@/components/ui/button"
import { useToast } from "@/hooks/use-toast"
import { Loader2 } from "lucide-react"

export default function Game() {
  const [selectedDeckIds, setSelectedDeckIds] = useState<string[]>([])
  const [gameStage, setGameStage] = useState<'initial' | 'deckChoose' | 'playerForm' | 'gamePlay'>('initial')
  const [currentMatchId, setCurrentMatchId] = useState<string | null>(null)
  const [playersList, setPlayersList] = useState<{ id: string; name: string }[]>([])
  const [currentPlayerIndex, setCurrentPlayerIndex] = useState(0)
  const [exitX, setExitX] = useState(0)

  const x = useMotionValue(0)
  const dragControls = useDragControls()

  const { toast } = useToast()

  const { data: cardData, refetch, isLoading: isCardLoading, error: cardError } = api.card.getRandomCardByDeckId.useQuery(
    { deckIds: selectedDeckIds },
    {
      enabled: selectedDeckIds.length > 0 && gameStage === 'gamePlay',
      retry: false,
      onError: (error) => {
        toast({ description: 'Error fetching card: ' + error.message, variant: "destructive" })
      }
    }
  )

  const { mutate: playerMutate } = api.players.create.useMutation()
  const { mutate: matchesMutate, isLoading: isMatchMutating } = api.match.create.useMutation({
    onSuccess: (matchData) => {
      setCurrentMatchId(matchData.id)
      console.log('Match created with ID:', matchData.id)
    },
    onError: (error) => {
      toast({ description: 'Erro ao criar partida: ' + error.message, variant: "destructive" })
    }
  })

  const { mutate: matchPlayersMutate } = api.matchPlayers.create.useMutation()

  useEffect(() => {
    if (gameStage === 'gamePlay' && selectedDeckIds.length > 0) {
      refetch()
    }
  }, [gameStage, selectedDeckIds, refetch])

  const handlePlayButtonClick = () => {
    setGameStage('deckChoose')
    createMatch()
  }

  const handlePlayerForm = () => {
    setGameStage('gamePlay')
  }

  const createMatch = () => {
    const date = new Date()
    const status = 'Started'
    matchesMutate({ status, date })
  }

  const handlePlayerSubmit = (name: string) => {
    const id = uuidv4()

    playerMutate({ id, name }, {
      onSuccess: () => {
        toast({ description: 'Jogador Adicionado com Sucesso!' })
        setPlayersList((prev) => [...prev, { id, name }])
        if (currentMatchId) {
          matchPlayersMutate({
            matchId: currentMatchId,
            playerId: id,
          })
        }
      }
    })
  }

  const handleDeckSelection = useCallback((chosenDeckIds: string[]) => {
    setSelectedDeckIds(chosenDeckIds)
    setGameStage('playerForm')
  }, [])

  const handleDragEnd = (
    _: MouseEvent | TouchEvent | PointerEvent,
    info: { offset: { x: number } }
  ) => {
    if (info.offset.x < -150 || info.offset.x > 150) {
      setExitX(info.offset.x < -150 ? -400 : 400)
      console.log(info.offset.x < -150 ? 'esquerda' : 'direita')

      setCurrentPlayerIndex((prevIndex) => (prevIndex + 1) % playersList.length)
      refetch()
    }
  }

  return (
    <main className="relative main flex h-screen flex-col items-center justify-center bg-gradient-to-b from-[#15042e] to-[#070012]">
      <AnimatePresence mode="wait">
        {gameStage === 'initial' && (
          <motion.div
            key="playButton"
            initial={{ opacity: 0, scale: 0 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0 }}
            transition={{ duration: 0.5 }}
          >
            <PlayButton onClick={handlePlayButtonClick} />
          </motion.div>
        )}

        {gameStage === 'deckChoose' && (
          <motion.div
            key="deckChoose"
            initial={{ opacity: 0, scale: 0 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0 }}
            transition={{ duration: 0.5 }}
          >
            <DeckChoose onDeckSelection={handleDeckSelection} />
          </motion.div>
        )}

        {gameStage === 'playerForm' && (
          <motion.div
            key="playerForm"
            initial={{ opacity: 0, scale: 0 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0 }}
            transition={{ duration: 0.5 }}
          >
            <PlayerForm setPlayerFormVisible={handlePlayerForm} onSubmit={handlePlayerSubmit} playersList={playersList} />
          </motion.div>
        )}

        {gameStage === 'gamePlay' && (
          <motion.div
            key="gamePlay"
            initial={{ opacity: 0, scale: 0 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0 }}
            transition={{ duration: 0.5 }}
            className="w-full h-full flex flex-col items-center justify-center"
          >
            <h2 className="text-white mb-4 absolute top-20 text-xl font-semibold">Turno de: {playersList[currentPlayerIndex]?.name}</h2>

            <div className="flex justify-center items-center w-full h-full">
              {isCardLoading ? (
                <Loader2 className="h-8 w-8 animate-spin" />
              ) : cardError ? (
                <div className="text-center">
                  <p className="text-red-500 mb-4">Error: {cardError.message}</p>
                  <Button onClick={() => refetch()}>Tente Novamente</Button>
                </div>
              ) : cardData && cardData.length > 0 ? (
                <AnimatePresence>
                  {cardData.map((data) => (
                    <Card
                      key={data.id ?? uuidv4()}
                      drag="x"
                      cardDescription={data.description ?? ''}
                      cardNumber={data.level ?? 0}
                      cardTheme={data.theme ?? ''}
                      cardName={data.name ?? ''}
                      cardSubtitle={data.subtitle ?? ''}
                      x={x}
                      dragControls={dragControls}
                      onDragEnd={handleDragEnd}
                    />
                  ))}
                </AnimatePresence>
              ) : (
                <p className="text-white">Sem Cartas Nesse Deck</p>
              )}
            </div>
          </motion.div>
        )}
      </AnimatePresence>

      <div className="absolute bottom-8 right-8">
        <DialogInfoModal />
      </div>
    </main>
  )
}

2. Card Router(TRPC后端路由)代码

import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "@/server/api/trpc";
import { cards } from "@/server/db/schema";
import { db } from "@/server/db/index"
import { sql, inArray } from "drizzle-orm";

export async function getRandomCardByDeckIds(deckIds: string[]) {
    if (deckIds.length === 0) {
        throw new Error("No deck IDs provided");
    }

    const query = sql`
        SELECT * FROM "pinga-party_card"
        WHERE ${inArray(cards.deckId, deckIds)}
        ORDER BY random()
        LIMIT 1
    `;

    return await db.execute(query);
}

export async function getRandomCard() {
    return await db.execute(sql`SELECT * FROM "pinga-party_card" ORDER BY random() LIMIT 1`);
}

export const cardRouter = createTRPCRouter({
    getRandomCardByDeckId: publicProcedure
        .input(z.object({
            deckIds: z.array(z.string())
        }))
        .query(async ({ input }) => {
            return await getRandomCardByDeckIds(input.deckIds);
        }),

    getRandomCard: publicProcedure.query(async () => {
        return await getRandomCard();
    }),

    get: publicProcedure.query(async () => {
        return await db.query.cards.findMany({
            with: {
                deck: true,
            },
        });
    }),

    create: publicProcedure
        .input(
            z.object({
                name: z.string(),
                description: z.string(),
                theme: z.string(),
                level: z.number().int(),
                deckId: z.string(),
                subtitle: z.string(),
            })
        )
        .mutation(async (opts) => {
            const { input } = opts;
            await db.insert(cards).values({
                name: input.name,
                description: input.description,
                theme: input.theme,
                level: input.level,
                deckId: input.deckId,
                subtitle: input.subtitle,
            })
        }),
})

我已经检查过数据库中的卡牌数据完整性,也尝试过添加调试日志、用AI工具分析代码,但完全找不到问题根源。有没有大佬能帮我分析下,可能是哪里的逻辑导致了这个问题?非常感谢!


备注:内容来源于stack exchange,提问作者Pedro Paitax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 09:48:03