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

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>

解决方案

错误核心原因

  1. 通道格式不匹配:Laravel事件定义的通道是task.{taskId}user.{userId}(无点分隔),但Vue中监听的是task.{taskId}.user.{userId}(有点分隔),导致Echo无法收到推送。
  2. 实时消息缺少discussion_id:初始加载的消息包含discussion_id,但推送和提交后的消息未携带该字段,模板渲染时触发读取错误。
  3. 控制器返回数据不完整:store方法仅返回状态,未返回新创建的消息对象,导致提交后添加的是undefined。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:55:54