Fullcalendar这个插件挺好,就是很多方法感觉官方文档也没怎么说,导致上手难度大,而且有些默认事件真的不太友好...废话不多说,先上效果图!
1、效果GIF
1.1 基本按钮功能
1.2 事件hover显示
1.3 事件添加、编辑、状态修改
1.4 日历事件搜索
2、 代码实现
2.1 Fullcalendar日历、el-popover弹窗
Fullcalendar@5.11.3引入后,要设置一大堆参数calendarOptions,包括显示时间区域、默认视图、是否显示全天类型、中文界面、事件的操作函数等,具体的一些设置内容,见下面代码的注释。
<template> <FullCalendar class="calendar" ref="fullCalendar" :options="calendarOptions" ><template v-slot:eventContent="arg"> <el-popover :append-to-body="true" ref="popover1" placement="top-start" width="220" :visible-arrow="true" trigger="hover" :teleported="false" popper-class="popover" :open-delay="100" @show="showPic(arg)" @hide="popoverPicReset(arg)" > <el-row class="popover_title"> <el-col :span="12" :style="{ color: arg.event.extendedProps.isDone == false ? 'red' : 'green', }" > <span style="padding-right: 2px" :style="{ 'border-left': arg.event.extendedProps.isDone == false ? '5px solid red' : '5px solid green', }" ></span >{{ arg.event.extendedProps.isDone == false ? "未开始" : "已完成" }}</el-col > <el-col :span="12" style=" display: flex; flex-direction: row-reverse; font-size: 14; color: #000; " >{{ arg.event.allDay == true ? "全天" : formatTimer(arg.event.start) }}</el-col > </el-row> <el-row> <el-col :span="24" style="text-align: center"> <el-image v-if="popoverimg.length != 0" :src="popoverimg[0]" @click="PreviewPic(popoverimg)" fit="fill" class="popoverShowImg" ><div slot="placeholder" class="image-slot"> 加载中<span class="dot">...</span> </div></el-image > <div class="block"></div> </el-col> </el-row> <el-row class="popover_content"> <el-col :span="24" style="max-height: 150px; overflow: auto"> <span class="click">{{ arg.event.title }}</span> </el-col> <el-col :span="24"> <el-link v-if=" arg.event.extendedProps.address != null && arg.event.extendedProps.address != '' " :href="undefined" :underline="false" @click="fileDownload(arg.event.extendedProps.address)" class="link" >{{ arg.event.extendedProps.address == null ? "" : arg.event.extendedProps.address.replace( "D:\\flask\\upload\\", "" ) }}</el-link ></el-col > </el-row> <el-row style="margin-top: 5px" ><el-col style="width: 15%" ><div> <el-button class="hvr-icon-pulse-grow" :popperAppendToBody="false" size="mini" icon="el-icon-edit hvr-icon" type="primary" circle @click="handleEventClick(arg)" > </el-button> </div> </el-col> <el-col style="width: 15%" ><el-button class="hvr-icon-bounce" size="mini" type="success" icon="el-icon-document-checked hvr-icon" circle @click="onCheckBtnClicked(arg)" > </el-button> </el-col> <el-col style="width: 15%" ><el-popconfirm confirm-button-text="好" cancel-button-text="否" icon="el-icon-info" icon-color="red" title="确定删除这个事项吗?" @confirm="onRemoveBtnClicked(arg)" ><el-button class="hvr-icon-buzz-out" slot="reference" size="mini" type="danger" icon="el-icon-delete hvr-icon" circle > </el-button ></el-popconfirm> </el-col ></el-row> <div slot="reference"> <span class="tree_span_text">{{ arg.timeText }}</span> <span>{{ arg.event.title }}</span> </div> </el-popover> </template> </FullCalendar> </template> <script> import FullCalendar from "@fullcalendar/vue"; import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin from "@fullcalendar/interaction"; import listPlugin from "@fullcalendar/list"; export default { // 注册局部组件 components: { FullCalendar, Treeselect, "el-image-viewer": () => import("element-ui/packages/image/src/image-viewer"), }, data() { return { // Fullcalendar版本@5.11.3日历控件设置项,官网文档地址:https://fullcalendar.io/ calendarOptions: { visibleRange: { start: "2000-01-01", end: "2100-12-31" }, // 可视化区间,必须设置,否则查询列表不会显示事件 // validRange: { start: "2021-09-01", end: "2021-09-01" }, // 可展示区间 // 引入的插件,比如fullcalendar/daygrid,fullcalendar/timegrid引入后才可显示月,周,日 plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin, listPlugin], initialView: "dayGridMonth", // 默认为那个视图(月:dayGridMonth,周:timeGridWeek,日:timeGridDay) firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推 locale: "zh-cn", // 切换语言,当前为中文 allDaySlot: true, // 不显示all-day businessHours: true, // handleWindowResize: true, // 是否随浏览器窗口大小变化而自动变化。 allDayText: "全天", // 设置all-Day显示的文字,不设置的话默认显示"all-Day" themeSystem: "bootstrap", // 主题色(本地测试未能生效) // loading: this.loadingEvent, // 视图数据加载中、加载完成触发(用于配合显示/隐藏加载指示器。) // selectAllow: this.selectAllow, //编程控制用户可以选择的地方,返回true则表示可选择,false表示不可选择 // eventMouseEnter: this.eventMouseEnter, // 鼠标滑过 allowContextMenu: false, editable: true, // 是否可以进行(拖动、缩放)修改 eventStartEditable: true, // Event日程开始时间可以改变,默认true,如果是false其实就是指日程块不能随意拖动,只能上下拉伸改变他的endTime eventDurationEditable: true, // Event日程的开始结束时间距离是否可以改变,默认true,如果是false则表示开始结束时间范围不能拉伸,只能拖拽 selectable: true, // 是否可以选中日历格 selectMirror: true, selectMinDistance: 0, // 选中日历格的最小距离 // eventLimit: true, //数据条数太多时,限制各自里显示的数据条数(多余的以“+2more”格式显示),默认false不限制,支持输入数字设定固定的显示条数 moreLinkContent: "+ 更多", //当一块区域内容太多以"+2 more"格式显示时,这个more的名称自定义 // dayPopoverFormat: "YYYY-M-d", dayMaxEventRows: true, // 日历显示事件最大条数,for all non-TimeGrid views weekends: true, // navLinks: true, // 天链接 selectHelper: false, slotEventOverlap: false, // 相同时间段的多个日程视觉上是否允许重叠,默认true允许 aspectRatio: 1.35, //设置日历单元格宽度与高度的比例。 expandRows: true, height: auto, contentHeight: 100, nowIndicator: true, //周/日视图中显示今天当前时间点(以红线标记),默认false不显示 weekMode: "fixed", //在月视图里显示周的模式,因为每月周数可能不同,所以月视图高度不一定。fixed:固定显示6周高,日历高度保持不变liquid:不固定周数,高度随周数变化variable:不固定周数,但高度固定 weekNumbers: true, //是否在日历中显示周次(一年中的第几周),如果设置为true,则会在月视图的左侧、周视图和日视图的左上角显示周数。 weekText: "周", customButtons: { //自定义按钮 getToday: { text: "今天", click: this.getToday, }, getNext: { text: ">", click: this.getNext, }, getPrev: { text: "<", click: this.getPrev, }, getPrevYear: { text: "<<", click: this.getPrevYear, }, getNextYear: { text: ">>", click: this.getNextYear, }, customButton: { text: "今日标记全部已完成", click: this.customButton, }, customButton1: { text: "显示未完成", click: this.customButton, }, }, // 日历头部按钮,即Fullcalendar表头显示区域 // headerToolbar: { // left: "getPrevYear,getPrev,getToday,getNext,getNextYear", //"getPrevYear,getPrev,getToday,getNext,getNextYear customButton,customButton1", // center: "title", // right: // "customButton customButton1 dayGridMonth,timeGridWeek,timeGridDay,listMonth", //dayGridWeek,listMonth // }, headerToolbar: false, // Fullcalendar表头显示区域不显示,显示自己自定义的html头部 // 使用内置按钮的显示文本 buttonText: { today: "今天", month: "月", week: "周", day: "日", list: "日程", }, // 设置日历显示事件时间头 slotLabelFormat: { hour: "2-digit", minute: "2-digit", meridiem: false, hour12: false, // 设置时间为24小时 }, // 视图的一些基本设置 views: { // 月视图阳历转农历 dayGridMonth: { height: 500, displayEventTime: true, //是否显示时间 dayMaxEventRows: 4, // adjust to 6 only for timeGridWeek/timeGridDay // titleFormat: { year: "numeric", month: "2-digit", day: "2-digit" }, //控制日历显示的标题 // moreLinkContent: "+ 更多", //可放在这里单独对每个视图控制显示更多的文字 moreLinkClick: "popover", eventTimeFormat: { hour: "numeric", minute: "2-digit", meridiem: false, }, dayPopoverFormat: { month: "long", day: "numeric", year: "numeric", }, // 显示农历 // dayCellContent(item) { // let _date = new Date(item.date).toLocaleDateString().split("/"); // let _dateF = calendar.solarToLunar(_date[0], _date[1], _date[2]); // // 以二十四节气覆盖农历日期 // if (calendar.getLunar24Days(_date[0], _date[1], _date[2])) { // _dateF.dayStr = calendar.getLunar24Days( // _date[0], // _date[1], // _date[2] // ); // } // return { html: `<p>${item.dayNumberText}(${_dateF.dayStr})</p>` }; // }, }, timeGridWeek: {}, timeGridDay: {}, listMonth: {}, }, // 设置过往时间无法点击 // selectAllow: function (clickInfo) { // if (clickInfo.end < new Date()) { // return false; // } // return true; // }, weekends: true, //是否显示周末,设为false则不显示周六和周日 selectable: true, //是否可以选中日历格 editable: false, //是否可以进行(拖动、缩放)修改 navLinks: true, //天链接 select: this.selectDate, //选中日历格事件 eventClick: this.handleEventClick, //选中备忘录事件 eventsSet: this.handleEvents, events: this.getCalendarList, //获取数据源 eventMouseEnter: this.eventMouseEnter, //鼠标悬浮事件 slotEventOverlap: true, //相同时间段的多个日程视觉上是否允许重叠,默认true允许 eventResize: this.onEventResize, // 事件时间区间调整 eventDrop: this.onEventResize, // 事件Drag-Drop事件 eventMouseLeave: this.eventMouseLeave, // 鼠标移出事件发生的事件 }, }; }, } </script>
复制
在这里,鼠标在事件上面经过时,会显示一个弹出窗,如下图。可见,弹出框有:是否已完成,开始时间,图列说明(可以是图片、GIF等),文字说明、链接或是附件。以上的这些都是用el-popover实现,用了v-slot:eventContent="arg",将日历的数据进行处理。图片的显示需要修改源码才能显示,不然有bug显示不出来,修改的源码见此文章第3节内容。
2.2 Fullcalendar日历自定义头部
在calendarOptions设置里,修改headerToolbar,设置为false。
然后写好自己的html代码,并调整好css样式。
绑定自定义按钮的函数功能,主要是利用了calendarApi自带的函数功能,包括视图切换、月日视图切换、往前和往后功能等,当然搜索功能是自己定义的。
2.3 搜索功能
这里是onSearch函数功能,主要是在前端对events的过滤,然后再设置视图为list视图,注意这个视图在日历头部的功能区是没有的,但是是Fullcalendar内置的。当搜索框为空或者清空搜索字符后,需要重新请求后端数据。
3、Fullcalendar源码修改
3.1 修改源码main.js的地址:
3.2 添加的show_pic函数/方法:
CalendarApi.prototype.show_pic = function (arg) { var state = this.getCurrentData(); this.unselect(); // 出现图片的关键 this.dispatch({ type: 'CHANGE_DATE', dateMarker: state.dateEnv.createMarker(arg.view.currentStart), }); };
复制
4、Vue源码
<template> <!-- el-mian是个人右侧容器的设置组件 --> <el-main> <div> <!-- 日历头部div --> <div class="fc-toolbar" style="display: flex; margin-bottom: 2px"> <!-- 日历头部左侧显示区域 --> <div class="fc-left" style="flex: 1; justify-content: flex-start"> <div style="vertical-align: middle"> <el-input placeholder="请输入查询内容" v-model="search_input" clearable class="input" size="medium" @keyup.enter.native="onSearch" @clear="getToday()" > <i slot="suffix" class="el-icon-search" @click="onSearch"></i> </el-input> </div> </div> <!-- 日历头部中间显示区域 --> <div class="fc-center" style="display: flex; flex: 3; justify-content: center" > <el-button-group> <el-button icon="el-icon-d-arrow-left" @click="getPrevYear" class="fc_btns" ></el-button> <el-button icon="el-icon-arrow-left" @click="getPrev" class="fc_btns" ></el-button> </el-button-group> <h2 class="title"> {{ title }} </h2> <el-button-group> <el-button icon="el-icon-arrow-right" @click="getNext" class="fc_btns" ></el-button> <el-button icon="el-icon-d-arrow-right" @click="getNextYear" class="fc_btns" ></el-button> </el-button-group> </div> <!-- 显示图标注释栏 --> <!-- <div class="tips" style="display: flex"> <div style=" height: 14px; width: 14px; background: green; text-align: center; position: relative; top: 27%; " ></div> <span class="tip-content">已完成</span> <div style=" height: 14px; width: 14px; background: #fe9b02; text-align: center; position: relative; top: 27%; " ></div> <span class="tip-content">未开始</span> </div> --> <!-- 日历头部右侧显示区域 --> <div class="fc-right"> <el-button-group> <el-button @click="today" type="success" plain size="medium" class="fc_btns" >今天</el-button > <el-button @click="month" type="primary" plain size="medium" class="fc_btns" >月</el-button > <el-button @click="week" type="primary" plain size="medium" class="fc_btns" >周</el-button > <el-button @click="day" type="primary" plain size="medium" class="fc_btns" >日</el-button > <el-button @click="list" type="primary" plain size="medium" class="fc_btns" >列表</el-button > </el-button-group> </div> </div> </div> <!-- 日历本体 --> <el-row> <el-col :md="24" :xs="24"> <div style="margin-top: 0px"> <FullCalendar class="calendar" ref="fullCalendar" :options="calendarOptions" ><template v-slot:eventContent="arg"> <el-popover :append-to-body="true" ref="popover1" placement="top-start" width="220" :visible-arrow="true" trigger="hover" :teleported="false" popper-class="popover" :open-delay="100" @show="showPic(arg)" @hide="popoverPicReset(arg)" > <el-row class="popover_title"> <el-col :span="12" :style="{ color: arg.event.extendedProps.isDone == false ? 'red' : 'green', }" > <span style="padding-right: 2px" :style="{ 'border-left': arg.event.extendedProps.isDone == false ? '5px solid red' : '5px solid green', }" ></span >{{ arg.event.extendedProps.isDone == false ? "未开始" : "已完成" }}</el-col > <el-col :span="12" style=" display: flex; flex-direction: row-reverse; font-size: 14; color: #000; " >{{ arg.event.allDay == true ? "全天" : formatTimer(arg.event.start) }}</el-col > </el-row> <el-row> <el-col :span="24" style="text-align: center"> <el-image v-if="popoverimg.length != 0" :src="popoverimg[0]" @click="PreviewPic(popoverimg)" fit="fill" class="popoverShowImg" ><div slot="placeholder" class="image-slot"> 加载中<span class="dot">...</span> </div></el-image > <div class="block"></div> </el-col> </el-row> <el-row class="popover_content"> <el-col :span="24" style="max-height: 150px; overflow: auto"> <span class="click">{{ arg.event.title }}</span> </el-col> <el-col :span="24"> <el-link v-if=" arg.event.extendedProps.address != null && arg.event.extendedProps.address != '' " :href="undefined" :underline="false" @click="fileDownload(arg.event.extendedProps.address)" class="link" >{{ arg.event.extendedProps.address == null ? "" : arg.event.extendedProps.address.replace( "D:\\flask\\upload\\", "" ) }}</el-link ></el-col > </el-row> <el-row style="margin-top: 5px" ><el-col style="width: 15%" ><div> <el-button class="hvr-icon-pulse-grow" :popperAppendToBody="false" size="mini" icon="el-icon-edit hvr-icon" type="primary" circle @click="handleEventClick(arg)" > </el-button> </div> </el-col> <el-col style="width: 15%" ><el-button class="hvr-icon-bounce" size="mini" type="success" icon="el-icon-document-checked hvr-icon" circle @click="onCheckBtnClicked(arg)" > </el-button> </el-col> <el-col style="width: 15%" ><el-popconfirm confirm-button-text="好" cancel-button-text="否" icon="el-icon-info" icon-color="red" title="确定删除这个事项吗?" @confirm="onRemoveBtnClicked(arg)" ><el-button class="hvr-icon-buzz-out" slot="reference" size="mini" type="danger" icon="el-icon-delete hvr-icon" circle > </el-button ></el-popconfirm> </el-col ></el-row> <div slot="reference"> <span class="tree_span_text">{{ arg.timeText }}</span> <span>{{ arg.event.title }}</span> </div> </el-popover> </template> </FullCalendar> </div> <!-- 事件添加或修改对话框 --> <el-dialog :visible.sync="dialogVisible" :popperAppendToBody="false" @close="cancel" v-dialogDrag :close-on-click-modal="false" class="calendar_matters" > <div slot="title" class="header-title" :style="{ color: 'black' }"> <i class="el-icon-edit"></i><span> 事件</span> </div> <el-form ref="form" :model="form" label-width="80px"> <el-row> <el-col :span="12" :xs="24"> <el-form-item label="事件时间"> <div class="dateRange"> <el-date-picker v-model="dateRange" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" > </el-date-picker> </div> </el-form-item> </el-col> <el-col :span="12"> </el-col> </el-row> <el-row> <el-col :span="24" ><el-form-item label="具体事项"> <el-input v-model="form.remark" type="textarea" class="calendar_details" ></el-input> </el-form-item ></el-col> </el-row> <el-row> <el-col :span="12" :xs="24" ><el-form-item label="提醒类别" prop="deptId"> <treeselect v-model="form.category" :options="Options" :props="defaultProps" :show-count="true" placeholder="请选择类型" @select="categorySelected" /> </el-form-item ></el-col> <el-col :span="12" :xs="24" ><el-form-item v-if="form.userId == undefined" label="记录人" prop="userName" > <el-input v-model="form.userName" maxlength="30" disabled class="userName" /> </el-form-item ></el-col> </el-row> <el-row> <el-col :span="12"> </el-col> <el-col :span="12" :xs="24"> <el-form-item label="状态"> <el-radio-group v-model="form.isDone"> <el-radio label="0">已完成</el-radio> <el-radio label="1">未确认</el-radio> </el-radio-group> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="18" :xs="24"> <el-form-item label="附件" class="attachment" prop="address"> <el-upload action="#" :show-file-list="false" :auto-upload="false" :on-change="address_beforeupload" > <div> <el-button type="primary" icon="el-icon-folder-opened" ></el-button> </div> </el-upload> <el-input v-model="form.address" clearable ></el-input></el-form-item ></el-col> </el-row> <el-row> <el-col :span="24" :xs="24"> <el-form-item label="相关图片" prop="address"> <el-upload ref="uploadFile" class="upload-demo" action="#" :auto-upload="false" :show-file-list="true" :on-change="beforeupload" list-type="picture-card" :file-list="filelist" multiple > <i slot="default" class="el-icon-plus"></i> <div slot="file" slot-scope="{ file }"> <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" /> <span class="el-upload-list__item-actions"> <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)" > <i class="el-icon-zoom-in"></i> </span> <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)" > <i class="el-icon-download"></i> </span> <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)" > <i class="el-icon-delete"></i> </span> </span> </div> </el-upload> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </el-dialog> <!-- 图片预览对话框 --> <el-image-viewer v-if="img_dialogVisible" :initial-index="0" :on-close="onClose" :url-list="dialogImageUrl" style="z-index: 3000" ></el-image-viewer> </el-col> </el-row> </el-main> </template> <script> // import { getCalendarList } from "@/api/calendar.js"; import FullCalendar from "@fullcalendar/vue"; import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin from "@fullcalendar/interaction"; import listPlugin from "@fullcalendar/list"; // import tippy from "tippy.js"; // import "../../assets/tippy.css"; // import calendar from "../../utils/calendar.js"; // import { INITIAL_EVENTS, createEventId } from "./event-utils"; import { auto } from "@popperjs/core"; import "../../directives.js"; // v-dialogDrag: 弹窗可拖拽属性 import Treeselect from "@riophae/vue-treeselect"; // Treeselect插件 import "@riophae/vue-treeselect/dist/vue-treeselect.css"; // 若依css设置 export default { // 注册局部组件 components: { FullCalendar, Treeselect, "el-image-viewer": () => import("element-ui/packages/image/src/image-viewer"), }, data() { return { // 搜索框输入的文本 search_input: "", // treeselect插件默认配置 defaultProps: { children: "children", label: "label", }, // 提醒类别设置 Options: [ { id: "工作类别", pid: 0, label: "工作类别", children: [], }, { id: "生活类别", pid: 0, label: "生活类别", children: [], }, { id: "其他类别", pid: 0, label: "其他类别", children: [], }, { id: "", pid: 0, label: "无", children: [], }, ], // 表单是否显示 dialogVisible: false, // 表单标题栏 title: "事件", // 表单当前编辑模式设置,add或amend form_edited_state: "", // 表单内容设置项 form: { remark: undefined, isDone: undefined, img: "", address: "", }, // 表单日期设置 dateRange: [], // 图片预览的操作按钮是否展示 disabled: false, // 图片是否显示 img_dialogVisible: false, // 预览图片地址 dialogImageUrl: "", // el-popover弹出框标题内容 popover_title: "事项", // el-popover图片地址存放 popoverimg: [], // 附件地址存放 filelist: [], // ------------------ // Fullcalendar版本@5.11.3日历控件设置项,官网文档地址:https://fullcalendar.io/ calendarOptions: { // visibleRange: { start: "2021-09-01", end: "2022-10-01" }, // 可视化区间 // validRange: { start: "2021-09-01", end: "2021-09-01" }, // 可展示区间 // 引入的插件,比如fullcalendar/daygrid,fullcalendar/timegrid引入后才可显示月,周,日 plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin, listPlugin], initialView: "dayGridMonth", // 默认为那个视图(月:dayGridMonth,周:timeGridWeek,日:timeGridDay) firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推 locale: "zh-cn", // 切换语言,当前为中文 allDaySlot: true, // 不显示all-day businessHours: true, // handleWindowResize: true, // 是否随浏览器窗口大小变化而自动变化。 allDayText: "全天", // 设置all-Day显示的文字,不设置的话默认显示"all-Day" themeSystem: "bootstrap", // 主题色(本地测试未能生效) // loading: this.loadingEvent, // 视图数据加载中、加载完成触发(用于配合显示/隐藏加载指示器。) // selectAllow: this.selectAllow, //编程控制用户可以选择的地方,返回true则表示可选择,false表示不可选择 // eventMouseEnter: this.eventMouseEnter, // 鼠标滑过 allowContextMenu: false, editable: true, // 是否可以进行(拖动、缩放)修改 eventStartEditable: true, // Event日程开始时间可以改变,默认true,如果是false其实就是指日程块不能随意拖动,只能上下拉伸改变他的endTime eventDurationEditable: true, // Event日程的开始结束时间距离是否可以改变,默认true,如果是false则表示开始结束时间范围不能拉伸,只能拖拽 selectable: true, // 是否可以选中日历格 selectMirror: true, selectMinDistance: 0, // 选中日历格的最小距离 // eventLimit: true, //数据条数太多时,限制各自里显示的数据条数(多余的以“+2more”格式显示),默认false不限制,支持输入数字设定固定的显示条数 moreLinkContent: "+ 更多", //当一块区域内容太多以"+2 more"格式显示时,这个more的名称自定义 // dayPopoverFormat: "YYYY-M-d", dayMaxEventRows: true, // 日历显示事件最大条数,for all non-TimeGrid views weekends: true, // navLinks: true, // 天链接 selectHelper: false, slotEventOverlap: false, // 相同时间段的多个日程视觉上是否允许重叠,默认true允许 aspectRatio: 1.35, //设置日历单元格宽度与高度的比例。 expandRows: true, height: auto, contentHeight: 100, nowIndicator: true, //周/日视图中显示今天当前时间点(以红线标记),默认false不显示 weekMode: "fixed", //在月视图里显示周的模式,因为每月周数可能不同,所以月视图高度不一定。fixed:固定显示6周高,日历高度保持不变liquid:不固定周数,高度随周数变化variable:不固定周数,但高度固定 weekNumbers: true, //是否在日历中显示周次(一年中的第几周),如果设置为true,则会在月视图的左侧、周视图和日视图的左上角显示周数。 weekText: "周", customButtons: { //自定义按钮 getToday: { text: "今天", click: this.getToday, }, getNext: { text: ">", click: this.getNext, }, getPrev: { text: "<", click: this.getPrev, }, getPrevYear: { text: "<<", click: this.getPrevYear, }, getNextYear: { text: ">>", click: this.getNextYear, }, customButton: { text: "今日标记全部已完成", click: this.customButton, }, customButton1: { text: "显示未完成", click: this.customButton, }, }, // 日历头部按钮,即Fullcalendar表头显示区域 // headerToolbar: { // left: "getPrevYear,getPrev,getToday,getNext,getNextYear", //"getPrevYear,getPrev,getToday,getNext,getNextYear customButton,customButton1", // center: "title", // right: // "customButton customButton1 dayGridMonth,timeGridWeek,timeGridDay,listMonth", //dayGridWeek,listMonth // }, headerToolbar: false, // Fullcalendar表头显示区域不显示,显示自己自定义的html头部 // 使用内置按钮的显示文本 buttonText: { today: "今天", month: "月", week: "周", day: "日", list: "日程", }, // 设置日历显示事件时间头 slotLabelFormat: { hour: "2-digit", minute: "2-digit", meridiem: false, hour12: false, // 设置时间为24小时 }, // 视图的一些基本设置 views: { // 月视图阳历转农历 dayGridMonth: { height: 500, displayEventTime: true, //是否显示时间 dayMaxEventRows: 4, // adjust to 6 only for timeGridWeek/timeGridDay // titleFormat: { year: "numeric", month: "2-digit", day: "2-digit" }, //控制日历显示的标题 // moreLinkContent: "+ 更多", //可放在这里单独对每个视图控制显示更多的文字 moreLinkClick: "popover", eventTimeFormat: { hour: "numeric", minute: "2-digit", meridiem: false, }, dayPopoverFormat: { month: "long", day: "numeric", year: "numeric", }, // 显示农历 // dayCellContent(item) { // let _date = new Date(item.date).toLocaleDateString().split("/"); // let _dateF = calendar.solarToLunar(_date[0], _date[1], _date[2]); // // 以二十四节气覆盖农历日期 // if (calendar.getLunar24Days(_date[0], _date[1], _date[2])) { // _dateF.dayStr = calendar.getLunar24Days( // _date[0], // _date[1], // _date[2] // ); // } // return { html: `<p>${item.dayNumberText}(${_dateF.dayStr})</p>` }; // }, }, timeGridWeek: {}, timeGridDay: {}, listMonth: {}, }, // 设置过往时间无法点击 // selectAllow: function (clickInfo) { // if (clickInfo.end < new Date()) { // return false; // } // return true; // }, weekends: true, //是否显示周末,设为false则不显示周六和周日 selectable: true, //是否可以选中日历格 editable: false, //是否可以进行(拖动、缩放)修改 navLinks: true, //天链接 select: this.selectDate, //选中日历格事件 eventClick: this.handleEventClick, //选中备忘录事件 eventsSet: this.handleEvents, events: this.getCalendarList, //获取数据源 eventMouseEnter: this.eventMouseEnter, //鼠标悬浮事件 slotEventOverlap: true, //相同时间段的多个日程视觉上是否允许重叠,默认true允许 eventResize: this.onEventResize, // 事件时间区间调整 eventDrop: this.onEventResize, // 事件Drag-Drop事件 eventMouseLeave: this.eventMouseLeave, // 鼠标移出事件发生的事件 }, }; }, mounted() { this.calendarApi = this.$refs.fullCalendar.getApi(); this.title = this.calendarApi.view.title; }, watch: { search_input: { handler: function (newData, oldData) { if (newData == "") { this.month(); this.getToday(); } }, deep: true, }, }, methods: { // 将当前时间移至今日事件 today() { this.getToday(); }, // 月视图 month() { this.calendarApi.changeView("dayGridMonth"); this.title = this.calendarApi.view.title; }, // 周视图 week() { this.calendarApi.changeView("timeGridWeek"); this.title = this.calendarApi.view.title; }, // 日视图 day() { this.calendarApi.changeView("timeGridDay"); this.handleTime(this.calendarApi.currentData.dateProfile.activeRange); this.title = this.calendarApi.view.title; }, // 列表视图 list() { this.calendarApi.changeView("listMonth"); this.title = this.calendarApi.view.title; }, // 鼠标划过,使用tippy插件显示tooltip eventMouseEnter(info) { // 非周列表的情况下显示悬浮提示; // if (info.view.type != "listWeek") { // tippy(info.el, { // content: info.event.title, // placement: "top-start", // }); // } }, // 鼠标离开 eventMouseLeave(arg) { // console.log("mouseleave"); // arg.jsEvent.preventDefault(); }, // 事项调整时间区间事件 onEventResize(arg) { let newTimeStart = this.dateFormat( "YYYY-mm-dd HH:MM:SS", arg.event.startStr ); let newTimeEnd = this.dateFormat("YYYY-mm-dd HH:MM:SS", arg.event.endStr); this.get("/calendar/updateTime", { id: arg.event.id, Start: newTimeStart, End: newTimeEnd, }).then((res) => { this.$message.success(res.data.info); }); // 必须加这句,不然切换视图会有显示事件数目的bug let calendarApi = arg.view.calendar; calendarApi.today(); }, // el-popover图片点击预览放大事件 PreviewPic(arg) { this.dialogImageUrl = arg; this.img_dialogVisible = true; this.stopMove(); }, // el-popover图片点击预览后关闭事件 onClose() { this.img_dialogVisible = false; this.move(); }, // el-popover隐藏时触发,将图片地址修改为空 async popoverPicReset(arg) { this.popoverimg = await []; let calendarApi = arg.view.calendar; calendarApi.show_pic(arg); }, // el-popover显示图片功能 async showPic(arg) { let calendarApi = arg.view.calendar; this.popoverimg = []; await this.post("/get_img_url", arg.event.extendedProps.img, "blob").then( (res) => { // console.log(res.data.imgs); res.data.imgs.forEach((item, index) => { const img = "data:image/png;base64," + item; this.file = this.base64ImgtoFile(img); // 得到File对象 const url = window.webkitURL.createObjectURL(this.file) || window.URL.createObjectURL(this.file); this.popoverimg.push(url); }); } ); calendarApi.show_pic(arg); }, // 提醒类别选择 categorySelected(node) { this.form.categoryName = node.label; }, // 自定义按钮 customButton() { console.log("点击了自定义按钮"); }, // 今天 getToday() { let calendarApi = this.$refs.fullCalendar.getApi(); calendarApi.today(); this.handleTime(calendarApi.currentData.dateProfile.activeRange); this.title = this.calendarApi.view.title; this.search_input = ""; }, // 上一年 getPrevYear() { let calendarApi = this.$refs.fullCalendar.getApi(); calendarApi.prevYear(); // this.handleTime(calendarApi.currentData.dateProfile.activeRange); this.title = this.calendarApi.view.title; }, // 下一年 getNextYear() { let calendarApi = this.$refs.fullCalendar.getApi(); calendarApi.nextYear(); // this.handleTime(calendarApi.currentData.dateProfile.activeRange); this.title = this.calendarApi.view.title; }, // 上一月 getPrev() { let calendarApi = this.$refs.fullCalendar.getApi(); calendarApi.prev(); // this.handleTime(calendarApi.currentData.dateProfile.activeRange); this.title = this.calendarApi.view.title; }, // 下一月 getNext() { let calendarApi = this.$refs.fullCalendar.getApi(); calendarApi.next(); // this.handleTime(calendarApi.currentData.dateProfile.activeRange); this.title = this.calendarApi.view.title; }, // 处理时间格式 handleTime(activeRange) { let result = { startStr: activeRange.start, endStr: activeRange.end, }; this.getCalendarList(); }, // 获取列表信息 getCalendarList(result) { // 以当前时间插入数据 let _this = this; // 注意,请求的数据是数据库所有数据即可,不用考虑当前显示的时间范围,Fullcalendar会自动只显示当前日期范围的事件 _this .get("/calendar/getCalendarList", "") .then((res) => { _this.calendarOptions.events = []; res.data.data.forEach((item) => { var data = { id: item[0], title: item[1], start: item[2], end: item[3], allDay: item[4], className: item[5] == true ? "borderGreen" : "borderOrange", // 非标准字段:除上述字段外,您还可以在每个事件对象中包含自己的非标准字段。FullCalendar不会修改或删除这些字段。例如,开发人员通常包括描述在回调中使用的字段,如事件呈现挂钩. 任何非标准属性都将移动到extendedProps哈希期间事件解析. extendedProps: { isDone: item[5], img: item[6], address: item[7], type: item[8], }, others: "该字段值会被自动归类到extendedProps里面", backgroundColor: (item[4] == true ? "all_Day" : "other") != "all_Day" ? item[5] == true ? "#c2fccd" : "#FFECDC" : "#66b1ff", editable: true, // 是否可以进行(拖动、缩放)修改 }; _this.calendarOptions.events.push(data); }); }) .catch((error) => { this.$message.error(error); }); }, // 选择日期,填写事件 selectDate: function (arg) { let startTime = this.dateFormat("YYYY-mm-dd HH:MM", arg.start); let endTime = this.dateFormat("YYYY-mm-dd HH:MM", arg.end); this.dialogVisible = true; this.form_edited_state = "add"; this.dateRange = [startTime, endTime]; // 设置当前记录事件的选择的时间段 let info = JSON.parse(localStorage.getItem("userInfo")); // 获取当前记录人信息 this.form.userName = info.username; // 设置记录人 this.form.isDone = "1"; // 默认设置为事件未完成状态 this.form.isAllDay = arg.allDay; let calendarApi = arg.view.calendar; calendarApi.unselect(); // 清除当前日期选择 }, // 表单确定按钮,提交事件 submitForm() { let calendarApi = this.$refs.fullCalendar.getApi(); var startTime = this.dateFormat("YYYY-mm-dd HH:MM:SS", this.dateRange[0]); var endTime = this.dateFormat("YYYY-mm-dd HH:MM:SS", this.dateRange[1]); if (this.form_edited_state == "add") { // 添加事件的后端数据请求 this.get( "/calendar/eventRecord", { isAllDay: this.form.isAllDay, dateRange: JSON.stringify([startTime, endTime]), remark: this.form.remark, type: this.form.type == undefined ? "" : this.form.type, isDone: this.form.isDone == "1" ? false : true, userName: this.form.userName, address: this.form.address == undefined ? "" : this.form.address, img: this.form.img == null ? "" : this.form.img, }, "" ).then((res) => { this.$message.success("事件添加成功!"); this.handleTime(calendarApi.currentData.dateProfile.activeRange); }); } else if (this.form_edited_state == "amend") { // 修改事件的后端数据请求 this.get( "/calendar/submit", { id: this.form.id, dateRange: JSON.stringify([startTime, endTime]), remark: this.form.remark, type: this.form.type == undefined ? "" : this.form.type, isDone: this.form.isDone == "1" ? false : true, userName: this.form.userName, address: this.form.address == undefined ? "" : this.form.address, img: this.form.img == null ? "" : this.form.img, // type: this.form.category, }, "" ).then((res) => { this.$message.success("修改事项成功!"); if (this.search_input == "") { this.handleTime(calendarApi.currentData.dateProfile.activeRange); } }); } this.dialogVisible = false; }, // 表单取消按钮 cancel() { this.form = { id: "", dateRange: "", remark: "", type: "", isDone: false, userName: "", address: "", img: "", }; this.filelist = []; this.dialogVisible = false; this.form_edited_state = ""; }, // 点击事项事件 handleEventClick(clickInfo) { this.dialogVisible = true; this.form_edited_state = "amend"; //修改状态 var startTime = this.dateFormat( "YYYY-mm-dd HH:MM:SS", clickInfo.event.startStr ); var endTime = this.dateFormat( "YYYY-mm-dd HH:MM:SS", clickInfo.event.endStr ); // 设置打开对话框各部分的显示值 this.form.id = clickInfo.event.id; this.dateRange = [clickInfo.event.start, clickInfo.event.end]; this.form.remark = clickInfo.event.title; // 获取当前登录用户的名字 let info = JSON.parse(localStorage.getItem("userInfo")); this.form.userName = info.username; this.form.isDone = clickInfo.event.extendedProps.isDone == true ? "0" : "1"; this.form.img = clickInfo.event.extendedProps.img; this.form.address = clickInfo.event.extendedProps.address; this.form.category = clickInfo.event.extendedProps.type == undefined ? "" : clickInfo.event.extendedProps.type; // 请求后端图片URL方法 this.post("/get_img_url", clickInfo.event.extendedProps.img).then( (res) => { res.data.imgs.forEach((item, index) => { const img = "data:image/png;base64," + item; this.file = this.base64ImgtoFile(img); // 得到File对象 const url = window.webkitURL.createObjectURL(this.file) || window.URL.createObjectURL(this.file); this.filelist.push({ name: res.data.origin_url[index], url: url, }); }); } ); }, // 事项标记已完成或改为未完成的事件 onCheckBtnClicked(arg) { this.get( "/calendar/checked", { id: arg.event.id, status: arg.event.extendedProps.isDone }, "" ).then((res) => { this.calendarOptions.events.filter((item) => { if (item.id == arg.event.id) { item.extendedProps.isDone = !arg.event.extendedProps.isDone; arg.event.extendedProps.isDone == true ? (item.className = "borderOrange") : (item.className = "borderGreen"); item.backgroundColor = (arg.event.allDay == true ? "all_Day" : "other") != "all_Day" ? arg.event.extendedProps.isDone == true ? "#FFECDC" : "#c2fccd" : "#66b1ff"; } return item; }); this.$message.success("事件状态修改成功!"); }); }, // 事项删除事件 onRemoveBtnClicked(arg) { this.get("/calendar/remove", arg.event.id).then((res) => { this.calendarOptions.events = this.calendarOptions.events.filter( (item) => { return item.id != arg.event.id; } ); }); }, // 当前事件绑定,此段代码可删掉 handleEvents(events) { this.currentEvents = events; }, // 提交上传文件 submitFileForm() { /* 这里为啥会先发一个Option请求再发Post请求:这是浏览器处理跨域做的逻辑。 CORS跨域请求会先发option请求,如果server返回access-control-allow-origin头为*或者和当前域名一致的话, 才会进入第二段的真正请求。不然就会报 cross origin request is forbidden错误。*/ this.$refs.upload.submit(); }, // 对话框图片预览放大 handlePictureCardPreview(file) { this.dialogImageUrl = [file.url]; this.img_dialogVisible = true; }, // 预览图片下载到本地 handleDownload(file) { // console.log(file, "@@@@"); this.get("/bbx_img_download", file.name, "blob").then((res) => { var blob = new Blob([res.data], { type: "application/octet-stream;chartset=UTF-8", }); var url = window.URL.createObjectURL(blob); var a = document.createElement("a"); a.href = url; //文件名 let name = res.config.params.replace("F:\\flask\\upload\\", ""); a.download = name; a.click(); window.URL.revokeObjectURL(url); // 释放掉blob对象 }); }, // 下载文件 fileDownload(file) { // console.log(this.urlToLink(file)); // 判断是否为网页 let bool = this.urlToLink(file); if (bool) { var b = document.createElement("a"); b.setAttribute("href", file); b.setAttribute("target", "_blank"); document.body.appendChild(b); b.click(); } else { if (file != null || file != "") { this.get("/get_file_download", file, "blob").then((res) => { var blob = new Blob([res.data], { type: "application/octet-stream;chartset=UTF-8", }); var url = window.URL.createObjectURL(blob); var a = document.createElement("a"); a.href = url; //文件名 let name = res.config.params.replace("F:\\flask\\upload\\", ""); a.download = name; a.click(); window.URL.revokeObjectURL(url); // 释放掉blob对象 }); } else { } } }, // 移除对话框预览图片 handleRemove(file) { let address = this.form.img.split(","); address = address.filter((item) => { return item != file.name; }); address = address.join(","); this.form.img = address; const newArray = this.filelist.filter((item, index) => { return item.uid != file.uid; }); this.filelist = newArray; }, // 判断字符串是否为网页 urlToLink(str) { var reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g; if (reg.test(str)) { return true; } else { return false; } }, // 停止页面滚动 stopMove() { const m = (e) => { e.preventDefault(); }; document.body.style.overflow = "hidden"; document.addEventListener("touchmove", m, false); // 禁止页面滑动 }, // 开启页面滚动 move() { const m = (e) => { e.preventDefault(); }; document.body.style.overflow = "auto"; document.removeEventListener("touchmove", m, true); }, // 查询功能 onSearch() { let search_text = this.search_input; let curr_Events = this.calendarOptions.events; if (search_text != "") { this.calendarApi.changeView("list"); let result = this.searchStr(search_text, curr_Events); this.calendarOptions.events = result; this.title = "查询结果"; } else { // this.today(); } }, // 数组中匹配单个字符串的方法,传入数组支持格式[{},{}], searchStr(str, arr) { let newList = []; // 要匹配字符串的首个字符 let startChar = str.charAt(0); // 要匹配字符串的字符长度 let strLength = str.length; for (let i = 0; i < arr.length; i++) { // 默认数组arr中对象arr[i]不存在str let isExist = false; let obj = arr[i]; for (let key in obj) { if (typeof obj[key] === "function") { obj[key](); } else { let keyValue = ""; // 获取arr[i][key]的值 if (obj[key] !== null && typeof obj[key] === "string") { keyValue = obj[key]; } else if (obj[key] !== null && typeof obj[key] !== "string") { keyValue = JSON.stringify(obj[key]); } // arr[i][key]中的各个位置的字符与str的0位置字符startChar对比如果相等, // 在arr[i][key]中从j位置截取与str长度相同的字符,对比是否相等 for (let j = 0; j < keyValue.length; j++) { // 把原有数据转化为小写,输入数据也转化为纯小写,实现模糊匹配,如区分大小写,可删除toLowerCase() if ( keyValue.charAt(j).toLowerCase() === startChar.toLowerCase() ) { if ( keyValue .substring(j) .substring(0, strLength) .toLowerCase() === str.toLowerCase() ) { // 模糊匹配到的字符存在表示arr[i]中存在str isExist = true; break; } } } } } // 当arr[i]中存在str时,把arr[i]放入一个新数组 if (isExist === true) { newList.push(obj); } } // 最后返回这个新数组 return newList; }, // 格式化时间 fmt是所需格式化的格式,如"YYYY-mm-dd HH:MM:SS",date是所需格式化的日期 dateFormat(fmt, date) { let ret = ""; date = new Date(date); const opt = { "Y+": date.getFullYear().toString(), // 年 "m+": (date.getMonth() + 1).toString(), // 月 "d+": date.getDate().toString(), // 日 "H+": date.getHours().toString(), // 时 "M+": date.getMinutes().toString(), // 分 "S+": date.getSeconds().toString(), // 秒 }; for (let k in opt) { ret = new RegExp("(" + k + ")").exec(fmt); if (ret) { fmt = fmt.replace( ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0") ); } } return fmt; }, //日期格式转换 formatTimer: function (value) { let date = new Date(value); let y = date.getFullYear(); let MM = date.getMonth() + 1; MM = MM < 10 ? "0" + MM : MM; let d = date.getDate(); d = d < 10 ? "0" + d : d; let h = date.getHours(); h = h < 10 ? "0" + h : h; let m = date.getMinutes(); m = m < 10 ? "0" + m : m; let s = date.getSeconds(); s = s < 10 ? "0" + s : s; return h + ":" + m; }, // 获取当前时间戳 getCurrentTime() { let date = new Date(); let year = date.getFullYear(); let month = date.getMonth() + 1; let day = date.getDate(); let hour = date.getHours(); let minute = date.getMinutes(); let second = date.getSeconds(); month = month < 10 ? "0" + month : month; // 可注释掉 day = day < 10 ? "0" + day : day; hour = hour < 10 ? "0" + hour : hour; // 可注释掉 minute = minute < 10 ? "0" + minute : minute; second = second < 10 ? "0" + second : second; return `${year}/${month}/${day} ${hour}:${minute}:${second}`; }, // 地址上传地址 address_beforeupload(file, filelist) { let formData = new FormData(); formData.append("file", file.raw); this.upload("/get_file_url", formData, "").then((res) => { this.form.address = res.data.path; }); }, /** 图片上传功能 */ beforeupload(file, filelist) { this.filelist = filelist; let formData = new FormData(); formData.append("file", file.raw); // 请求数据 this.upload("/get_file_url", formData, "").then((res) => { if (this.form.img == null) { this.form.img = res.data.path; } else { this.form.img = this.form.img + "," + res.data.path; } }); }, // base64字段变成blob二进制数据,关于图片的 base64ImgtoFile(dataurl, filename = "file") { const arr = dataurl.split(","); const mime = arr[0].match(/:(.*?);/)[1]; const suffix = mime.split("/")[1]; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], `${filename}.${suffix}`, { type: mime, }); }, }, }; </script> <!-- 样式1,本地样式 --> <style lang="scss" scoped> .calendar_matters >>> .el-dialog__body { height: 450px; overflow: auto; } .calendar_details >>> .el-textarea__inner { font-weight: bold; font-family: Arial, Helvetica, sans-serif; color: #000; height: 120px; } .calendar_matters >>> .el-dialog__header { border-radius: 5px; background-color: #cae1f7; align-content: center; padding: 15px; font-weight: bold; border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: aliceblue; } .fc-daygrid-day-top p { font-size: 13px; color: #606266; margin-right: 10px; } .fc .fc-toolbar.fc-header-toolbar { margin-bottom: 10px; } .el-main { padding: 8px 10px 8px 10px; } .userName >>> .el-input__inner { font-weight: bold; color: #000000ab; } .attachment >>> .el-form-item__content { display: flex; } .upload-demo >>> .el-upload-list--picture-card .el-upload-list__item { height: 120px; width: 120px; } .upload-demo >>> .el-upload--picture-card { height: 120px; width: 120px; line-height: 120px; } .dateRange >>> .el-range-input { font-weight: bold; color: #080808; } .calendar >>> .fc-header-toolbar { margin-bottom: 5px; } .calendar >>> .borderGreen { border-left: 5px solid #44bb08 !important; border-radius: 0; border: none; white-space: normal; overflow: hidden; text-overflow: ellipsis; span { color: #000; font-weight: bold; } } .calendar >>> .borderOrange { border-left: 5px solid #fe9b02 !important; border-radius: 0; border: none; white-space: normal; overflow: hidden; span { color: #000; font-weight: bold; } } .calendar >>> .borderOrigin { border-radius: 0; border: none; white-space: normal; overflow: hidden; text-overflow: ellipsis; max-height: 150px; span { color: #000; font-weight: bold; } } .calendar >>> .fc-event-title { font-weight: bold; color: #000; overflow: hidden; } .calendar >>> .fc-event-time { font-weight: bold; color: #000; } .calendar >>> .fc-daygrid-event-dot { border: none; } .popover { .el-popover { max-height: 350px; } } .popover_title { font-weight: bold; margin-bottom: 3px; } .popover_content { color: #000; font-size: 13px; } .popoverShowImg { width: auto; cursor: pointer; >>> .el-image__inner { max-height: 200px; } } .link { color: #000; font-size: 12px; font-weight: bold; margin-top: 2px; & :hove { color: #66b1ff; } } .fc_btns { padding: 10px 12px; } .el-icon-search { line-height: 2.5; margin-right: 8px; } .fc-right { display: flex; flex: 1.5; justify-content: flex-end; } .tip-content { line-height: 2.2; margin-right: 4px; font-weight: 600; } .title { margin: 0px 5px; line-height: 1.6; } .calendar_matters { width: 100%; } .calendar >>> .el-popover__reference { display: grid; max-height: 150px; overflow: auto; } </style> <!-- 样式2 --> <style> .el-popover__reference::-webkit-scrollbar { /*滚动条整体样式*/ width: 8px; /*高宽分别对应横竖滚动条的尺寸*/ height: 1px; } .el-popover__reference::-webkit-scrollbar-thumb { /*滚动条里面小方块*/ border-radius: 10px; -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); background: #535353; } .el-popover__reference::-webkit-scrollbar-track { /*滚动条里面轨道*/ -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); border-radius: 2px; background: #ededed; } .popover .el-popover__title { font-weight: bold; margin-bottom: 5px; border-bottom: solid 1px; padding: 2px; } @media only screen and (max-width: 767px) { .fc-toolbar { flex-direction: column; } .fc-left { flex: 1 !important; } .tips { flex: 1 !important; justify-content: center; margin-bottom: 5px; } .fc-center { flex: 1 !important; margin: 5px 0px; } .fc-right { flex: 1; justify-content: center !important; margin-bottom: 5px; } .tip-content { line-height: 1 !important; margin-right: 4px; font-weight: 600; } .title { font-size: 20px; font-weight: 700; line-height: 1.9 !important; } .fc-list-table { word-break: break-all; overflow: auto; } .fc-list-event-title { overflow: auto; } .el-dialog { width: 90%; } .dateRange { overflow: auto; } } </style>
复制
5、 后端Flask源码
from flask import Flask, render_template,\ request, jsonify, make_response, Response, send_file,session,send_from_directory from flask_cors import CORS import datetime import json import pypyodbc from collections import deque import os import base64 import hashlib import xlwt import openpyxl import xlrd app = Flask(__name__, static_folder='./templates/static', # 设置静态文件夹目录 template_folder="./templates") # 设置vue编译输出目录dist文件夹,为Flask模板文件目录 # 解决后端跨域问题,不然会在前端网页控制台显示“ccess to XMLHttpRequest at 'http://localhost:8080/api/login' from origin 'null' has been blocked” CORS(app, supports_credentials=True) session = {} @app.route('/') def index(): return render_template('index.html',name='index') #使用模板插件,引入index.html。此处会自动Flask模板文件目录寻找index.html文件。 @app.route('/calendar/eventRecord', methods=["GET", "POST"]) def calendar_evnetRecord(): params = request.values date = json.loads(params["dateRange"]) startTime = datetime.datetime.strptime(date[0],"%Y-%m-%d %H:%M:%S") endTime = datetime.datetime.strptime(date[1],"%Y-%m-%d %H:%M:%S") # print(params) conn = get_conn() cur = conn.cursor() sql = "insert into 记事本(allDay, startStr,endStr, title, type, isDone, userName,img,address) values(%s, '%s','%s','%s','%s',%s,'%s','%s','%s')" \ %(params["isAllDay"],startTime,endTime,params["remark"],params["type"],params["isDone"],params["userName"],params["img"],params["address"]) cur.execute(sql) cur.commit() cur.close() conn.close() return jsonify({"code":200}) @app.route('/calendar/getCalendarList', methods=["GET", "POST"]) def calendar_getCalendarList(): params = request.values conn = get_conn() cur = conn.cursor() sql = "select ID,title,Format(startStr, 'yyyy-MM-dd HH:mm:ss'),Format(endStr, 'yyyy-MM-dd HH:mm:ss'),allDay,isDone,img,address,type " \ "from 记事本 " cur.execute(sql) data = cur.fetchall() cur.close() conn.close() return jsonify({"code":200,"data":data}) @app.route('/calendar/submit', methods=["GET", "POST"]) def calendar_submit(): params = request.values date = json.loads(params["dateRange"]) startTime = datetime.datetime.strptime(date[0],"%Y-%m-%d %H:%M:%S") endTime = datetime.datetime.strptime(date[1],"%Y-%m-%d %H:%M:%S") conn = get_conn() cur = conn.cursor() sql="update 记事本 set startStr='%s',endStr='%s',title='%s',type='%s',isDone=%s,userName='%s',img='%s',address='%s' where ID=%s"\ %(startTime,endTime,params["remark"],params["type"],params["isDone"],params["userName"],params["img"],params["address"] ,params["id"]) cur.execute(sql) cur.commit() cur.close() conn.close() return jsonify({"code":200}) @app.route('/calendar/checked', methods=["GET", "POST"]) def calendar_checked(): params = request.values status = True if params["status"] == "false" else False conn = get_conn() cur = conn.cursor() sql = "update 记事本 set isDone=%s where ID=%s" %(status,params["id"]) cur.execute(sql) cur.commit() cur.close() conn.close() return jsonify({"code": 200, "info":"状态修改成功!"}) @app.route('/calendar/remove', methods=["GET", "POST"]) def calendar_remove(): matters_id = request.values.get("0") conn = get_conn() cur = conn.cursor() sql = "delete from 记事本 where ID=%s" % (matters_id) try: cur.execute(sql) cur.commit() return jsonify({"code": 200, "info":"删除成功!"}) except Exception as e: return jsonify({"code": 200, "info":"发生错误!错误代码:"+str(e)}) finally: cur.close() conn.close() @app.route('/calendar/updateTime', methods=["GET", "POST"]) def calendar_updateTime(): params = request.values conn = get_conn() cur = conn.cursor() sql = "update 记事本 set startStr='%s',endStr='%s' where ID=%s" %(params["Start"],params["End"],params["id"]) cur.execute(sql) cur.commit() cur.close() conn.close() return jsonify({"code": 200, "info": "事件时间修改成功!"}) @app.route('/get_file_download', methods=["GET", "POST"]) def get_file_download(): file_url = request.values.get("0") with open(file_url, 'rb') as file_f: res = make_response(file_f.read()) # 用flask提供的make_response 方法来自定义自己的response对象 # res.headers['Content-Type'] = 'image/jpg' # 设置response对象的请求头属性'Content-Type'为图片格式 return res if __name__ == '__main__': # 0.0.0.0 表示同一个局域网均可访问,也可以替换成本机地址:通过命令行命令:ipcofig 获取 app.run(host='0.0.0.0', port='5000', debug=True)
复制