Error: Please import the top-level fullcalendar lib before attempting to import a plugin.下面是我的代码,并且使用的版本一致<template>
<!-- 办公-我的日程页面 -->
<div class="divBox">
<el-card body-style="padding:0 0 0 0; " class="normal-page">
<div class="header">
<span class="title">我的日程</span>
<el-button type="primary" size="small" :icon="Plus" @click="addSchedule">
{{ $t('calendar.newschedule') }}
</el-button>
</div>
<el-row :gutter="14">
<el-col :span="7">
<calendar-bar ref="calendarBar" @handle-date="handleDate" />
</el-col>
<el-col :span="17" class="line" v-loading="loading" element-loading-text="数据正在加载中">
<div class="plan-tabs-content"></div>
<!-- 我的日历 -->
<div class="needToBeDealt">
<div class="add-btn">
<div class="content-right"></div>
<div class="content-button">
<el-radio-group v-model="time" size="small" @change="selectChange">
<el-radio-button
v-for="(itemn, indexn) in fromList"
:key="indexn"
:label="itemn.val"
>
{{ itemn.text }}
</el-radio-button>
</el-radio-group>
</div>
</div>
<FullCalendar ref="calendar" id="calendar" style="height: calc(100vh - 150px)" :options="calendarOptions">
<template v-slot:slotLabelContent="arg">
<div class="calendar-timeline">
<!-- <span v-if="moment(arg.date).format('HH:mm') !== '00:00'">-->
<!-- {{ moment(arg.date).format('HH:mm') }}-->
<!-- </span>-->
</div>
</template>
<template v-slot:dayHeaderContent="arg">
<div class="header-box">
<div class="day-header" :class="period == 1 ? 'ml30' : ''">
<!-- <span class="week">{{ getWeek(arg.date) }}</span>-->
<!-- <span class="date" v-if="period !== 3">{{ moment(arg.date).format('D') }}</span>-->
</div>
</div>
</template>
<template v-slot:eventContent="arg">
<div
class="item"
:style="{
background: period == 3 ? getColorFn(arg.event.textColor, '0.1') : arg.event.color
}"
>
<div class="flex over-title" @click="handleEventClick(arg)">
<img
v-if="period !== 2"
:src="arg.event.extendedProps.avatar"
class="img"
alt=""
/>
<div
class="over-title"
:style="{ color: period == 3 ? arg.event.textColor : arg.event.textColor }"
>
{{ arg.event.title }}
</div>
</div>
<template v-if="arg.event.extendedProps.show > -1">
<span
v-if="arg.event.extendedProps.finish == 2"
class="el-icon-error"
:style="{ color: arg.event.textColor }"
></span>
<span
v-else-if="arg.event.extendedProps.finish == 3"
@click="putStatus('取消完成', 1, arg.event)"
class="el-icon-success"
:style="{ color: arg.event.textColor }"
></span>
<span
v-else
class="yuan"
:style="{ 'border-color': arg.event.textColor }"
@click="putStatus('完成', 3, arg.event, arg)"
></span>
</template>
</div>
</template>
</FullCalendar>
</div>
</el-col>
</el-row>
</el-card>
<!-- 通用弹窗表单 -->
<add-todo ref="addTodo" @get-list="getList" :left-time="leftTime"></add-todo>
<calendar-details
ref="calendarDetails"
@delete-fn="getList"
@edit-fn="editFn"
:date-info="dateInfo"
></calendar-details>
<contract-dialog ref="contractDialog" :config="configContract" @is-ok="getList"></contract-dialog>
<!-- 跟进弹窗 -->
<el-dialog title="添加跟进记录" class="record" v-model="dialogVisible" width="40%">
<record-upload :form-info="formInfo" @change="recordChange"></record-upload>
</el-dialog>
<edit-examine
ref="editExamine"
:parameter-data="parameterData"
:ids="formInfo.data.id"
@schedule-record="scheduleRecord"
></edit-examine>
</div>
</template>
<script>
import FullCalendar from '@fullcalendar/vue3' // 核心库必须第一个导入
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'
import listPlugin from '@fullcalendar/list'
import {
defineComponent,
defineAsyncComponent,
ref,
reactive,
onMounted,
computed,
toRefs,
nextTick
} from 'vue';
import { Plus } from '@element-plus/icons-vue';
//import { ElMessage } from 'element-plus';
import moment from 'moment';
// 正确顺序:先核心库再插件
// 样式导入(必须在插件之后)
// import '@fullcalendar/common/main.css'
// import '@fullcalendar/daygrid/main.css'
// import '@fullcalendar/timegrid/main.css'
// import '@fullcalendar/list/main.css'
console.log(window.FullCalendar); // 检查全局对象
console.log(window.FullCalendarCore); // 如果你手动挂载了核心库
// 根据实际需要取消注释这些API导入
// import {
// clientRemindDetailApi,
// scheduleListApi,
// scheduleStatusApi
// } from '@/api/enterprise';
// import { configRuleApproveApi } from '@/api/config';
// import { toGetWeek, getColor } from '@/utils/format';
export default defineComponent({
name: 'WorkDealt',
components: {
CalendarBar: defineAsyncComponent(() => import('./components/calendarBar.vue')),
AddTodo: defineAsyncComponent(() => import('./components/addTodo')),
FullCalendar,
CalendarDetails: defineAsyncComponent(() => import('./components/calendarDetails'))
// ContractDialog: defineAsyncComponent(() => import('@/views/customer/contract/components/contractDialog')),
// RecordUpload: defineAsyncComponent(() => import('@/views/customer/list/components/recordUpload')),
// EditExamine: defineAsyncComponent(() => import('@/views/user/examine/components/editExamine'))
},
setup() {
const state = reactive({
loading: false,
calendarApi: null,
title: '',
time: '1',
cid: null,
dialogVisible: false,
configContract: {},
formInfo: {
avatar: '',
type: 'add',
show: 1,
data: {},
follow_id: 0
},
fromList: computed(() => [
{text: '日', val: '1'},
{text: '周', val: '2'},
{text: '月', val: '3'}
]),
start_time: '',
end_time: '',
dateInfo: {},
type: [],
period: 1,
calendarOptions: {
height: 'calc(100vh - 232px)',
eventColor: '',
initialDate: moment().format('YYYY-MM-DD HH:mm:ss'),
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin],
handleWindowResize: true,
displayEventTime: false,
slotDuration: '00:60:00',
scrollTime: '05:30:00',
slotMinTime: '00:00:00',
slotMaxTime: '24:00:00',
headerToolbar: {
left: '',
center: 'prev title next',
right: ''
},
buttonText: {
month: '月',
week: '周',
day: '天'
},
allDaySlot: true,
dayMaxEventRows: 6,
allDayText: '',
moreLinkContent: state => `还有${state.num}个日程`,
weekends: true,
nowIndicator: true,
weekNumbers: false,
slotLabelFormat: {
hour: '2-digit',
minute: '2-digit',
meridiem: false,
hour12: false
},
selectable: true,
displayEventEnd: false,
initialView: 'timeGridDay',
dateClick: (e) => handleDateClick(e),
customButtons: {
next: {
text: 'PREV',
click: () => next()
},
prev: {
text: 'PREV',
click: () => prev()
}
},
events: [],
slotEventOverlap: false,
eventOverlap: false,
locale: 'zh-cn',
weekNumberCalculation: 'ISO'
},
detailedData: {},
parameterData: {
contract_id: '',
customer_id: '',
invoice_id: '',
bill_id: ''
},
leftTime: '',
buildData: [],
item: {},
status: 0
})
const calendarBar = ref(null);
const addTodo = ref(null);
const calendarDetails = ref(null);
const contractDialog = ref(null);
const editExamine = ref(null);
const userId = ref(null);
// 日期更新函数
const updateCalendarDates = () => {
if (!state.calendarApi) return;
state.title = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD');
state.start_time = state.title + ' 00:00:00';
state.end_time = state.title + ' 23:59:59';
};
onMounted(() => {
const userInfo = JSON.parse(localStorage.getItem('userInfo'));
if (userInfo) {
userId.value = userInfo.userId;
state.formInfo.avatar = userInfo.avatar;
}
// 使用 nextTick 确保在 DOM 更新后执行
nextTick(() => {
if (calendarBar.value && calendarBar.value.getApi) {
state.calendarApi = calendarBar.value.getApi();
dayRender();
updateCalendarDates();
}
});
});
// 原始方法开始(保持所有方法逻辑不变) //
const handleEventClick = e => {
calendarDetails.value.open(e.event.extendedProps);
}
const recordChange = () => {
state.dialogVisible = false;
getList();
}
const getConfigApprove = async () => {
// 实际项目中取消注释
// const result = await configRuleApproveApi(0);
// state.buildData = result.data;
}
const putStatus = async (text, status, item) => {
const {extendedProps} = item;
state.item = extendedProps;
const bill_id = extendedProps.bill_id;
switch (extendedProps.cid_value) {
case 3:
case 4:
const id = bill_id.bill_id !== 0 ? bill_id.bill_id : bill_id.remind_id;
if (status == 3) {
// 实际项目中取消注释
// clientRemindDetailApi(id).then((res) => {
// res.data.id = bill_id.remind_id;
// state.parameterData.customer_id = res.data.eid;
// state.parameterData.contract_id = res.data.cid;
//
// const switchType = res.data.types === 0 ? 'contract_refund_switch' : 'contract_renew_switch';
// const data = {id: state.buildData[switchType]};
// editExamine.value.open(data, res.data.cid, switchType, item, status);
// })
}
break;
case 2:
if (extendedProps.finish === 3) return false;
state.formInfo.follow_id = bill_id ? bill_id.follow_id : 0;
state.formInfo.data.eid = extendedProps.link_id;
state.dialogVisible = true;
break;
default:
scheduleRecord(extendedProps, status);
}
}
const scheduleRecord = async (data = state.item, status = state.status) => {
const {start_time: start, end_time: end, itemId: id} = data;
const info = {status, start, end};
// 实际项目中取消注释
// await scheduleStatusApi(id, info);
await getList();
}
const handleDateClick = e => {
state.detailedData = {
startDate: moment(e.date).format('YYYY-MM-DD'),
startTime: moment(e.date).format('HH:mm:ss'),
endDate: moment(e.date).format('YYYY-MM-DD'),
time: moment(e.date).format('YYYY-MM-DD HH:mm:ss')
}
addTodo.value.open(state.detailedData);
}
const dayRender = () => {
document.documentElement.style.setProperty(`--fc-today-bg-color`, '#fff');
let dayTime = document.querySelectorAll('.fc-timegrid-slot-label-cushion');
dayTime[0]?.classList.add('fcTime');
document.querySelectorAll('.fc-button-primary').forEach(li => li.setAttribute('title', ''));
}
const day = () => {
state.period = 1;
state.calendarApi.changeView('timeGridDay');
getList();
removeTitle();
dayRender();
}
const month = () => {
state.period = 3;
state.calendarApi.changeView('dayGridMonth');
getList();
removeTitle();
}
const week = () => {
state.period = 2;
state.calendarApi.changeView('timeGridWeek');
getList();
removeTitle();
dayRender();
}
const prev = () => {
state.calendarApi.prev();
updateCalendarDates();
getList();
if (state.period === 2) {
const date = calendarBar.value.value;
calendarBar.value.value = moment(date).subtract(7, 'd').format('YYYY-MM-DD');
} else {
calendarBar.value.value = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD');
}
}
const next = () => {
state.calendarApi.next();
updateCalendarDates();
getList();
if (state.period == 2) {
let date = calendarBar.value.value;
calendarBar.value.value = moment(date).add(7, 'd').format('YYYY-MM-DD');
} else {
calendarBar.value.value = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD');
}
}
const getList = () => {
// 实际项目中取消注释
// scheduleListApi().then(res => {
// let newArr = []
// res.data.forEach(item => {
// // ...处理逻辑...
// })
// state.calendarOptions.events = newArr
// })
calendarBar.value?.getList();
}
const findItem = (arr, key, val) => {
for (var i = 0; i < arr.length; i++) {
if (arr[i].id === val || arr[i].id == userId.value) {
return 2;
}
}
return -1;
}
// const getWeek = date => toGetWeek(date);
const editFn = (id, type, date) => {
let data = {
id,
type,
edit: true,
date
}
addTodo.value.openBox(data);
}
const handleDate = (data, val) => {
state.cid = data.type;
state.leftTime = data.time;
if (val) {
state.calendarApi.gotoDate(data.time);
}
setTimeout(() => {
state.calendarApi = calendarBar.value.getApi();
state.time = 1;
day();
getList();
}, 300);
}
const selectChange = e => {
state.time = e;
if (state.time == 1) {
day();
} else if (state.time == 2) {
week();
} else {
month();
}
}
const addSchedule = () => addTodo.value.open();
// 临时 getColorFn 实现 - 实际项目中应该使用真实实现
const getColorFn = (thisColor, thisOpacity) => {
// 如果是透明背景
if (state.period === 3) {
// RGBA 格式
const r = parseInt(thisColor.slice(1, 3), 16);
const g = parseInt(thisColor.slice(3, 5), 16);
const b = parseInt(thisColor.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${thisOpacity})`;
}
return thisColor;
}
const removeTitle = () => {
setTimeout(() => {
document.querySelectorAll('.fc-daygrid-day-bottom a').forEach(li => li.title = '');
}, 300);
}
return {
...toRefs(state),
Plus,
calendarBar,
addTodo,
calendarDetails,
contractDialog,
editExamine,
handleEventClick,
recordChange,
putStatus,
scheduleRecord,
handleDateClick,
dayRender,
day,
month,
week,
prev,
next,
getList,
findItem,
// getWeek,
editFn,
handleDate,
selectChange,
addSchedule,
getColorFn,
removeTitle
}
}
})
</script>
<style lang="scss" scoped>
.divBox {
:deep(.fc-header-toolbar) {
position: absolute;
top: 0;
left: 0;
}
.header {
padding: 16px 20px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #dcdfe6;
.title {
font-weight: 500;
font-size: 18px;
color: #303133;
}
}
.day-header {
display: flex;
flex-direction: column;
justify-content: center;
height: 52px;
font-family: PingFang SC-Regular, PingFang SC;
line-height: 20px;
.week {
font-size: 13px;
color: #909399;
}
.date {
font-size: 18px;
color: #606266;
font-weight: 800;
}
}
.ml30 {
margin-left: -30px;
}
:deep(.fc-timegrid-slot-label-cushion) {
color: #909399;
font-size: 12px;
}
.line {
border-left: 1px solid #f0f2f5;
}
.item {
width: 100%;
display: flex;
font-size: 14px;
font-family: PingFang SC-Regular, PingFang SC;
justify-content: space-between;
align-items: center;
padding-right: 4px;
border-radius: 13px;
.img {
flex-shrink: 0;
width: 18px;
height: 18px;
border-radius: 50%;
margin-right: 5px;
object-fit: cover;
}
.yuan {
width: 12px;
height: 12px;
border-radius: 50px;
border: 1px solid #fff;
}
}
.over-title {
width: 98% !important;
overflow: hidden !important;
white-space: nowrap;
text-overflow: ellipsis !important;
}
}
</style>