我正在开发一个使用 fullcalendar 进行约会的项目。我想要完成的任务是使日历中的事件保持最新。
我决定使用 Livewire,这让事情变得简单。 一切正常。但因为我在 livewire 组件中使用了wire:poll,所以组件的内容会被刷新以保持内容始终更新。
问题是,组件一刷新,fullcalendar就从页面上完全消失了。
当然,页面上的其他数据会毫无问题地更新。 我该如何解决这个问题。我究竟做错了什么。或者有更好的方法来处理这个问题吗?
这是我的代码:
Livewire 组件类:
<?php
namespace Modules\Visitor\Livewire;
use Livewire\Component;
use Illuminate\Support\Carbon;
use Modules\Visitor\app\Models\Appointment;
class FrontendCalendar extends Component
{
public $events;
public function render()
{
$today_appointments = Appointment::where('status', 0)->whereDate('created_at','>=', Carbon::today())->latest()->get();
$pending_appointments = Appointment::where('status', 0)->whereDate('created_at','<', Carbon::today())->latest()->get();
$appointments_events = Appointment::where('status', 0)->latest()->get()->map(function($item){
return [
'id' => $item->id,
'title' => $item->name,
'start' => format_date($item->start_date),
'end' => format_date($item->end_date),
'extendedProps' => [
'calendar' => $item->color ?? 'primary',
'fullname' => $item->name,
'phone' => $item->phone,
'email' => $item->email,
'address' => $item->address,
'gender' => $item->gender,
'purpose' => $item->purpose,
'status' => $item->status,
'start_date' => $item->start_date,
'end_date' => $item->end_date,
'color' => $item->color?? 'primary',
]
];
});
$this->dispatch('refreshCalender', ($appointments_events));
return view('visitor::livewire.frontend-calendar',compact(
'today_appointments','pending_appointments','appointments_events'
));
}
}
Livewire 视图: livewire.frontend-calendar
<div wire:poll.keep-alive>
<div class="card app-calendar-wrapper">
<div class="row g-0">
<!-- Calendar -->
<div class="col app-calendar-content">
<div class="card shadow-none border-0 border-start rounded-0">
<div class="card-body pb-0">
<!-- FullCalendar -->
<div id="calendar"></div>
</div>
</div>
<div class="app-overlay"></div>
<!-- FullCalendar Offcanvas -->
@include('visitor::offcanvas')
</div>
<!-- /Calendar -->
<!-- Calendar Sidebar -->
<div class="col app-calendar-sidebar pt-1" id="app-calendar-sidebar">
{{-- <div class="p-3 pb-2 my-sm-0 mb-3">
<div class="d-grid">
<button class="btn btn-primary btn-toggle-sidebar" data-bs-toggle="offcanvas" data-bs-target="#addEventSidebar" aria-controls="addEventSidebar">
<i class="mdi mdi-plus me-1"></i>
<span class="align-middle">Add Appointment</span>
</button>
</div>
</div> --}}
<div class="p-4">
@if (!empty($today_appointments) && ($today_appointments->count() > 0))
<h4>Today</h4>
<hr class="container-m-nx my-4">
<ul style="overflow-y: scroll;height: 200px;" class="p-0 m-0">
@foreach ($today_appointments as $item)
<li class="d-flex shadow-sm p-1 rounded mb-4">
<div class="avatar flex-shrink-0 me-3">
@if (substr($item->gender,0,1) == 'M')
<img src="{{asset('assets/img/avatars/1.png')}}" alt="avatar" class="rounded">
@else
<img src="{{asset('assets/img/avatars/8.png')}}" alt="avatar" class="rounded">
@endif
</div>
<div class="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div class="me-2">
<h6 class="mb-0 fw-semibold text-nowrap"><small><span class="badge bg-label-success rounded-pill"><i class="tf-icons mdi mdi-circle mdi-14px"></i></span> <span>{{$item->name}}</span></small></h6>
<small class="text-muted">
<i class="mdi mdi-calendar-blank-outline mdi-14px"></i>
<span>{{$item->start_date}} |{{$item->end_date}}</span>
</small>
<p>{{$item->purpose}}</p>
</div>
</div>
</li>
<hr>
@endforeach
</ul>
<hr class="container-m-nx my-4">
@endif
@if (!empty($pending_appointments) && ($pending_appointments->count() > 0))
<h4>Upcoming</h4>
<hr class="container-m-nx my-4">
<ul style="overflow-y: scroll;height: 300px; overflow-x:hidden" class="p-0 m-0">
@foreach ($pending_appointments as $item)
<li class="d-flex shadow-sm p-1 rounded mb-4">
<div class="avatar flex-shrink-0 me-3">
@if (substr($item->gender,0,1) == 'M')
<img src="{{asset('assets/img/avatars/1.png')}}" alt="avatar" class="rounded">
@else
<img src="{{asset('assets/img/avatars/8.png')}}" alt="avatar" class="rounded">
@endif
</div>
<div class="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div class="me-2">
<h6 class="mb-0 fw-semibold text-nowrap"><small><span class="badge bg-label-success rounded-pill"><i class="tf-icons mdi mdi-circle mdi-14px"></i></span> <span>{{$item->name}}</span></small></h6>
<small class="text-muted">
<i class="mdi mdi-calendar-blank-outline mdi-14px"></i>
<span>{{$item->start_date}} |{{$item->end_date}}</span>
</small>
<p>{{$item->purpose}}</p>
</div>
</div>
</li>
<hr>
@endforeach
</ul>
@endif
</div>
</div>
<!-- /Calendar Sidebar -->
</div>
</div>
</div>
@push('page-script')
<script>
'use strict';
document.addEventListener('DOMContentLoaded', function () {
(function () {
let calendarEl = document.getElementById('calendar'),
appCalendarSidebar = document.querySelector('.app-calendar-sidebar'),
offcanvasAddAppointment = document.getElementById('addEventSidebar'),
appOverlay = document.querySelector('.app-overlay'),
offcanvasTitle = document.querySelector('.offcanvas-title'),
btnToggleSidebar = document.querySelector('.btn-toggle-sidebar'),
btnSubmit = document.querySelector('button[type="submit"]'),
btnCancel = document.querySelector('.btn-cancel'),
selectAll = document.querySelector('.select-all'),
inlineCalendar = document.querySelector('.inline-calendar');
let eventToUpdate, inlineCalInstance;
const bsoffcanvasAddAppointment = new bootstrap.Offcanvas(offcanvasAddAppointment);
// Inline sidebar calendar (flatpicker)
if (inlineCalendar) {
inlineCalInstance = inlineCalendar.flatpickr({
monthSelectorType: 'static',
inline: true
});
}
function eventClick(info) {
eventToUpdate = info.event;
var appointment_id = appointment_id;
bsoffcanvasAddAppointment.show();
if (offcanvasTitle) {
offcanvasTitle.innerHTML = 'Update Appointment';
}
btnSubmit.innerHTML = 'Update';
btnSubmit.classList.add('btn-update-event');
btnSubmit.classList.remove('btn-add-event');
var data = eventToUpdate.extendedProps;
$('#appointment_id').val(eventToUpdate.id);
$('#fullname').val(data.fullname);
$('#phone').val(data.phone);
$('#email').val(data.email);
$('#address').val(data.address);
$('#gender').val(data.gender);
$('#purpose').val(data.purpose);
$('#startDate').val(data.start_date);
$('#endDate').val(data.end_date);
$('#status').val(data.status).trigger('change');
}
function modifyToggler() {
const fcSidebarToggleButton = document.querySelector('.fc-sidebarToggle-button');
if (fcSidebarToggleButton) {
fcSidebarToggleButton.classList.remove('fc-button-primary');
fcSidebarToggleButton.classList.add('d-lg-none', 'd-inline-block', 'ps-0');
while (fcSidebarToggleButton.firstChild) {
fcSidebarToggleButton.firstChild.remove();
}
fcSidebarToggleButton.setAttribute('data-bs-toggle', 'sidebar');
fcSidebarToggleButton.setAttribute('data-overlay', '');
fcSidebarToggleButton.setAttribute('data-target', '#app-calendar-sidebar');
fcSidebarToggleButton.insertAdjacentHTML('beforeend', '<i class="mdi mdi-menu mdi-24px text-body"></i>');
}
}
let eventsData = @json($appointments_events)
Livewire.on('refreshCalender', (e) => {
eventsData = { ...e }[0]
console.log(eventsData);
})
let calendar = new Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: eventsData,
plugins: [dayGridPlugin, interactionPlugin, listPlugin, timegridPlugin],
editable: true,
dragScroll: true,
eventResizableFromStart: true,
customButtons: {
sidebarToggle: {
text: 'Sidebar'
}
},
headerToolbar: {
start: 'sidebarToggle, prev,next, title',
end: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
loading: function(isLoading) {
if (!isLoading) {
this.getEvents().forEach(function(e){
if (e.source === null) {
e.remove();
}
});
}
},
initialDate: new Date(),
navLinks: true, // can click day/week names to navigate views
eventClassNames: function ({ event: calendarEvent }) {
const colorName = calendarEvent._def.extendedProps.color;
// Background Color
return ['fc-event-' + colorName];
},
dateClick: function (info) {
let date = moment(info.date).format('YYYY-MM-DD');
bsoffcanvasAddAppointment.show();
if (offcanvasTitle) {
offcanvasTitle.innerHTML = 'Add Appointment';
}
btnSubmit.innerHTML = 'Add';
btnSubmit.classList.remove('btn-update-event');
btnSubmit.classList.add('btn-add-event');
},
eventClick: function (info) {
eventClick(info);
},
datesSet: function () {
modifyToggler();
},
viewDidMount: function () {
modifyToggler();
}
});
calendar.render();
// Modify sidebar toggler
modifyToggler();
// Hide left sidebar if the right sidebar is open
if (btnToggleSidebar) {
btnToggleSidebar.addEventListener('click', e => {
if (offcanvasTitle) {
offcanvasTitle.innerHTML = 'Add Appointment';
}
btnSubmit.innerHTML = 'Add';
btnSubmit.classList.remove('btn-update-event');
btnSubmit.classList.add('btn-add-event');
appCalendarSidebar.classList.remove('show');
appOverlay.classList.remove('show');
});
}
if(inlineCalInstance){
inlineCalInstance.config.onChange.push(function (date) {
calendar.changeView(calendar.view.type, moment(date[0]).format('YYYY-MM-DD'));
modifyToggler();
appCalendarSidebar.classList.remove('show');
appOverlay.classList.remove('show');
});
}
})();
});
</script>
@endpush
显示日历的主页
@extends('layouts.layoutMaster')
@section('title', 'Appointments - Calendar View')
@section('vendor-style')
<link rel="stylesheet" href="{{asset('assets/vendor/libs/fullcalendar/fullcalendar.css')}}" />
<link rel="stylesheet" href="{{asset('assets/vendor/libs/flatpickr/flatpickr.css')}}" />
@endsection
@section('page-style')
<link rel="stylesheet" href="{{asset('assets/vendor/css/pages/app-calendar.css')}}" />
@endsection
@section('content')
<livewire:visitor::frontend-calendar />
@endsection
@section('vendor-script')
<script src="{{asset('assets/vendor/libs/fullcalendar/fullcalendar.js')}}"></script>
<script src="{{asset('assets/vendor/libs/flatpickr/flatpickr.js')}}"></script>
<script src="{{asset('assets/vendor/libs/moment/moment.js')}}"></script>
@endsection
我应该提到的是, @push('page-script') 在页面中的 @section('vendor-script') 下呈现。 预先感谢
我希望日历能够随着新事件一起呈现,而不会在我身上消失。
我也尝试过使用here 提到的答案,但没有成功。
这个问题似乎是双重的。首先,您的脚本被多次推送到 'page-script' 堆栈中。这意味着每次刷新组件时,相同的脚本都会被推入堆栈。其次,如果您无法使用 wire:ignore,则每当刷新组件时,您可能需要重新初始化 FullCalendar。您可以借助 Livewire Events 来实现这一目标。