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

Vue中FullCalendar按日期动态切换资源的问题求助

问题描述

我在Vue项目中使用FullCalendar的resourceTimeGridDay视图,需要根据日期动态切换资源——仅显示当日可用的员工(通过接口获取当日非休假的员工数据)。
使用eventDidMount钩子时,仅当日存在事件时能正确切换资源,无事件则不生效;尝试使用datesSet钩子时,出现错误:Pickup.vue:97 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'calendarOptions')。


后端代码(PickupController.php)
public function getResources(Request $request){
     
    /**
     * TODO
     * 
     * SELECT
     *   employees.id 
     *    FROM employees
     *    left JOIN appointments
     *    ON appointments.employee_id = employees.id
     *    WHERE employees.id NOT IN (
     *    SELECT appointments.employee_id WHERE (date(appointments.start_time) <= CURDATE() AND date(appointments.finish_time) >= CURDATE()))
     *    AND employees.department_id = 3
     *    GROUP BY employees.id
     */
    $employees = Employee::select('employees.id','employees.name','employees.business_start_time','employees.business_finish_time')
                ->leftjoin('appointments','employees.id','=','appointments.employee_id')
                ->whereNotIn('employees.id',
                DB::table('appointments')->select('appointments.employee_id')->whereDate(('appointments.start_time'),'<=',$request->date)->whereDate(('appointments.finish_time'),'>=',$request->date))
                ->where('employees.department_id','=',3)
                ->groupBy('employees.id')
                ->get();
   

    $resources = [];

    foreach($employees as $employee) {
        $resource = [];
        $resource['id'] = $employee->id;
        $resource['title'] = $employee->name;
        $resource['businessHours']['daysOfWeek'] = [ 1, 2, 3, 4, 5];
        $resource['businessHours']['startTime'] = $employee->business_start_time;
        $resource['businessHours']['endTime'] = $employee->business_finish_time;

        $resources[] = $resource;
    }

    return response()->json($resources);
}

前端代码(Pickup.vue)
<script>
import FullCalendar from '@fullcalendar/vue3'
import timeGridPlugin from "@fullcalendar/timegrid"
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin from '@fullcalendar/interaction'
import resourceTimeGridPlugin from '@fullcalendar/resource-timegrid'
import scrollGridPlugin from '@fullcalendar/scrollgrid'
import listPlugin from '@fullcalendar/list'
import esLocale from '@fullcalendar/core/locales/es'
import adaptivePlugin from '@fullcalendar/adaptive'
import PickupModal from '../../Components/Modals/PickupModal.vue'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'
import axios from 'axios'
import { router  } from "@inertiajs/vue3"
import moment from 'moment'
import { reactive } from "vue";
  
export default {
    name: 'Pickup',
    components: {
        FullCalendar,
        PickupModal,
        AuthenticatedLayout
    },
    emits: {
        'deleteEventResource':{
            id:'',
        },
        'editEventResource':{
            id:'',
        },
        'closeModal':true,
        'saveEventResource':{id:'',},
    },
    props: {
        errors: Object,
        events: Array,
        resources: Array,
    },
    data() {
        return {
            showModal: false,
            editing : false,
            newEventResource: {
                title: '',
                content: '',
                date_at: '',
                end_at: '',
                status: '',
                color: '',
                content: ''
            },
            calendarOptions : {
                schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
                plugins: [ dayGridPlugin, timeGridPlugin, listPlugin,interactionPlugin, adaptivePlugin, resourceTimeGridPlugin, scrollGridPlugin ],
                headerToolbar: {
                    left: "prev,next today",
                    center: "title",
                    right: "dayGridMonth,timeGridWeek,resourceTimeGridDay,listMonth",
                },
                initialView: 'resourceTimeGridDay',
                locale: esLocale,
                dayMaxEvents: false,
                weekends: true,
                navLinks: true,
                nowIndicator: true,
                firstDay: 1,
                defaultAllDay: false,
                timeZone: 'Europe/Madrid',
                resources: [],
                dateClick: this.handleDateClick,
                eventClick: this.handleEventClick,
                events: [],
                dayMinWidth: 150,
                stickyFooterScrollbar: true,
                businessHours: [],
                slotDuration: '00:15:00',
                eventContent: function(eventInfo) {
                    return { html: eventInfo.timeText + "<br>" + eventInfo.event.title + "<br>" + eventInfo.event.extendedProps.content }
                },
                
               
                eventDidMount: this.dayDrivers, //仅当日有事件时生效,资源能切换
               
                eventMouseEnter: function(info) {info.el.title = info.event.extendedProps.content;},
                datesSet:function (dateInfo){
                    let date = dateInfo.startStr.split("T")[0];
                    axios.get(route('pickups.index',{date:date})).then(({data}) => {
                        this.$data.calendarOptions.resources = data; //报错:Cannot read properties of undefined (reading 'calendarOptions')
                    });
                },
                //lazyFetching: false,
                //rerenderDelay: 500,
            }
        }
    },
    beforeMount(){
        const start = new Date().toISOString().split("T")[0];
        this.$data.calendarOptions.resources = {
            url: route('pickups.index',{date:start}),
            method: 'GET',
            failure: error => {
                console.log('tenemos este error: ', error.message);
            }
        },
        this.$data.calendarOptions.events = {
            url: route('pickupsEvents'),
            method: 'GET',
            failure: error => {
                console.log('tenemos este error: ', error.message);
            }
        }
    },
    methods: {
        dateClick(arg){
            this.$data.showModal = true;
            this.setModalOpen(arg);
        },
        handleDateClick(clickInfo){
            this.resetModal();
            this.$data.showModal = true;
            this.$emit('dateClick',clickInfo);
        },
        dayDrivers(date){
                
            let calendarApi = this.$refs.fullCalendar.getApi()
            const start = calendarApi.getDate().toISOString().split("T")[0];
            
            axios.get(route('pickups.index',{date:start})).then(({data}) => {
            
                //calendarApi.addResource(data);
                this.$data.calendarOptions.resources = data;
                //this.$refs.fullCalendar.getApi().refetchResources();
            });
            
        },
</script>

解决方案

1. 修复datesSet钩子的this指向问题

datesSet中使用普通函数时,this的指向不是Vue组件实例,导致无法访问this.$data.calendarOptions。改成箭头函数即可解决:

datesSet: (dateInfo) => {
    let date = dateInfo.startStr.split("T")[0];
    axios.get(route('pickups.index',{date:date})).then(({data}) => {
        this.$data.calendarOptions.resources = data;
    });
}

2. 推荐使用FullCalendar API更新资源(更可靠)

直接修改calendarOptions.resources可能不会触发日历的重新渲染,建议通过FullCalendar提供的API来操作资源:

datesSet: (dateInfo) => {
    const calendarApi = dateInfo.view.calendar; // 获取日历实例
    const date = dateInfo.startStr.split("T")[0];
    
    axios.get(route('pickups.index',{date:date})).then(({data}) => {
        calendarApi.removeAllResources(); // 清空现有资源
        data.forEach(resource => {
            calendarApi.addResource(resource); // 添加新资源
        });
    });
}

3. 替换eventDidMount为datesSet(覆盖所有日期场景)

eventDidMount只有在当天存在事件时才会触发,无法覆盖无事件的日期。使用datesSet钩子,只要日期切换就会触发,完美适配所有场景。


内容的提问来源于stack exchange,提问作者vuenew

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 22:17:04