Vue3+Axios+Laravel Echo聊天应用:discussion_id读取未定义错误
问题
开发基于Vue 3、Axios和Laravel Echo(搭配Pusher)的实时聊天应用时,出现以下错误:
ChatArtisanClient.vue:68 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'discussion_id') at ChatArtisanClient.vue:68:52 at renderList (chunk-U6BEPC57.js?v=670d87f9:4352:16) at ChatArtisanClient.vue:1:1
数据库中已存在的消息可正常显示,但实时监听的window.Echo部分无日志输出,且触发上述错误。相关代码如下:
Laravel 事件代码
class MessageNotification implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $message; public $user; public $taskId; public $userId; public function __construct($message, $user, $taskId, $userId) { $this->message = $message; $this->user = $user; $this->taskId = $taskId; $this->userId = $userId; } public function broadcastOn() { return new PrivateChannel('task.' . $this->taskId . 'user.' . $this->userId); } }
Laravel 广播通道代码
Broadcast::channel('App.Models.User.{id}', function ($user, $id) { return (int) $user->id === (int) $id; }); use Illuminate\Support\Facades\Log; Broadcast::channel('task.{taskId}.user.{userId}', function ($user, $taskId, $userId) { Log::info('Tentative de connexion au canal privé', ['user_id' => $user->id, 'task_id' => $taskId, 'userId' => $userId]); $discussion = Discussion::where('task_id', $taskId) ->where('artisan_user_id', $user->id) ->first(); if (!$discussion) { Log::warning('Aucune discussion trouvée', ['user_id' => $user->id, 'task_id' => $taskId]); return false; } Log::info('Discussion trouvée', ['discussion_id' => $discussion->id, 'task_id' => $taskId, 'artisan_user_id' => $discussion->artisan_user_id, 'client_user_id' => $discussion->user_id]); $isParticipant = $discussion->user_id === $userId || $discussion->artisan_user_id === $user->id; Log::info('Vérification des participants', ['is_participant' => $isParticipant]); return $isParticipant; });
Laravel 控制器代码
use App\Events\MessageNotification; use App\Notifications\NewMessageNotification; class ChatsController extends Controller { public function index() { $messages = Message::with('user')->get(); return Inertia::render('Chat', compact('messages')); } public function fetchMessages() { return Message::with('user')->get(); } public function store(Request $request) { // Récupérer l'utilisateur authentifié (ici l'artisan) $artisan = Auth::user(); $taskId = $request->taskId; // Récupérer l'utilisateur (le client) associé à la tâche $task = Task::find($taskId); $userId = $task->user_id; // Identifiant du client // Vérifier si une discussion existe déjà entre l'artisan et le client pour la tâche $discussion = Discussion::where('task_id', $taskId) ->where('artisan_user_id', $artisan->id) ->first(); // Si aucune discussion n'existe, en créer une nouvelle if (!$discussion) { $discussion = Discussion::create([ 'task_id' => $taskId, 'user_id' => $userId, // Identifiant du client 'artisan_user_id' => $artisan->id // Identifiant de l'artisan ]); } // Créer un nouveau message dans la discussion $message = $discussion->messages()->create([ 'user_id' => $artisan->id, // Identifiant de l'utilisateur envoyant le message (l'artisan ici) 'message' => $request->message, ]); // Diffuser l'événement sur le canal privé basé sur les IDs des utilisateurs broadcast(new MessageNotification($message, Auth::user(), $taskId, $artisan->id))->toOthers(); return ['status' => 'Message envoyé']; } }
Vue3 组件代码
<script setup> const props = defineProps({ taskId: Number, userId: Number }); const msg = ref([]); const form = useForm({ message: "", taskId: props.taskId, }); onMounted(async () => { try { const response = await axios.get('/messages'); msg.value = response.data; console.log("MessageNotification"); // Placez ici le reste du code qui dépend de msg.value console.log(msg.value); } catch (error) { console.error('Erreur lors de la récupération des messages:', error); } }); window.Echo.private(`task.${props.taskId}.user.${props.userId}`).listen('MessageNotification', (response) => { console.log("MessageNotification"); console.log(response); msg.value.push({ message: response.message.message, user: response.user }); //alert('Show without refresh!'); }); const submit = () => { axios.post("/chat", form).then((response) => { msg.value.push(response.data.message); form.message = null; }); }; </script> <template> <AppLayout title="Tâche"> <Head title="Chat" /> <template> <h2 class="font-semibold text-xl text-gray-800 leading-tight">Chat</h2> </template> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="font-bold text-xl mb-4 p-3">Messages</div> <hr/> <div class="overflow-y-auto max-h"> <ul class="chat list-none p-6"> <li v-for="(message, i) in msg" :key="i" class="left clearfix"> <div class="clearfix"> <div class="header"> <strong class="text-blue-500"> {{ message.discussion_id }} : </strong> {{ message.message }} </div> </div> </li> </ul> </div> <hr> <div class="mt-4 p-4"> <div class="flex items-center"> <input id="btn-input" type="text" name="message" class="border border-gray-200 rounded w-full" v-model="form.message" @keyup.enter="submit" placeholder="Type your message here..." /> <span class="ml-2"> <button class="bg-blue-500 text-white px-4 py-2 rounded" id="btn-chat" @click="submit"> Send </button> </span> </div> </div> </div> </div> </div> </AppLayout> </template>
解决方案
错误核心原因
- 通道格式不匹配:Laravel事件定义的通道是
task.{taskId}user.{userId}(无点分隔),但Vue中监听的是task.{taskId}.user.{userId}(有点分隔),导致Echo无法收到推送。 - 实时消息缺少
discussion_id:初始加载的消息包含discussion_id,但推送和提交后的消息未携带该字段,模板渲染时触发读取错误。 - 控制器返回数据不完整:
store方法仅返回状态,未返回新创建的消息对象,导致提交后添加的是undefined。 - Echo监听时机错误:监听代码在
onMounted外执行,可能在props未初始化时就创建通道,导致参数错误。
修复步骤
1. 修正Laravel事件的通道格式
在broadcastOn方法中补充分隔点:
public function broadcastOn() { return new PrivateChannel('task.' . $this->taskId . '.user.' . $this->userId); }
2. 让控制器返回完整消息对象
修改store方法的返回值,包含新创建的消息:
return [ 'status' => 'Message envoyé', 'message' => $message // 返回带discussion_id的完整消息模型 ];
3. 完善Vue中推送消息的结构
确保推送的消息包含discussion_id:
window.Echo.private(`task.${props.taskId}.user.${props.userId}`).listen('MessageNotification', (response) => { console.log("MessageNotification received"); console.log(response); msg.value.push({ discussion_id: response.message.discussion_id, message: response.message.message, user: response.user }); });
4. 调整Echo监听位置到onMounted内部
确保props初始化后再创建监听:
onMounted(async () => { try { const response = await axios.get('/messages'); msg.value = response.data; console.log("Initial messages loaded"); console.log(msg.value); } catch (error) { console.error('Erreur lors de la récupération des messages:', error); } // 将监听代码移到此处 window.Echo.private(`task.${props.taskId}.user.${props.userId}`).listen('MessageNotification', (response) => { console.log("MessageNotification received"); console.log(response); msg.value.push({ discussion_id: response.message.discussion_id, message: response.message.message, user: response.user }); }); });
5. 模板添加空值保护(可选)
避免因数据缺失触发报错:
<strong class="text-blue-500"> {{ message.discussion_id || 'N/A' }} : </strong>
内容的提问来源于stack exchange,提问作者ahmadou gaye
相关产品推荐
相关产品推荐

