Add tui.calendar

This commit is contained in:
HarlanLuo 2018-12-19 00:36:51 +08:00
parent 999ea3a443
commit cda5c1ff19
6 changed files with 749 additions and 0 deletions

View File

@ -38,6 +38,7 @@
"clipboard": "1.7.1",
"codemirror": "5.39.2",
"connect": "3.6.6",
"dayjs": "^1.7.8",
"driver.js": "0.8.1",
"dropzone": "5.2.0",
"echarts": "4.1.0",
@ -52,6 +53,7 @@
"screenfull": "3.3.3",
"showdown": "1.8.6",
"sortablejs": "1.7.0",
"tui-calendar": "^1.9.0",
"tui-editor": "1.2.7",
"vue": "2.5.17",
"vue-count-to": "1.0.13",

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="36.449px" height="36.448px" viewBox="0 0 36.449 36.448" style="enable-background:new 0 0 36.449 36.448;"
xml:space="preserve">
<g>
<g>
<rect x="12.858" y="14.626" width="4.596" height="4.089"/>
<rect x="18.996" y="14.626" width="4.595" height="4.089"/>
<rect x="25.128" y="14.626" width="4.596" height="4.089"/>
<rect x="6.724" y="20.084" width="4.595" height="4.086"/>
<rect x="12.858" y="20.084" width="4.596" height="4.086"/>
<rect x="18.996" y="20.084" width="4.595" height="4.086"/>
<rect x="25.128" y="20.084" width="4.596" height="4.086"/>
<rect x="6.724" y="25.54" width="4.595" height="4.086"/>
<rect x="12.858" y="25.54" width="4.596" height="4.086"/>
<rect x="18.996" y="25.54" width="4.595" height="4.086"/>
<rect x="25.128" y="25.54" width="4.596" height="4.086"/>
<path d="M31.974,32.198c0,0.965-0.785,1.75-1.75,1.75h-24c-0.965,0-1.75-0.785-1.75-1.75V12.099h-2.5v20.099
c0,2.343,1.907,4.25,4.25,4.25h24c2.344,0,4.25-1.907,4.25-4.25V12.099h-2.5V32.198z"/>
<path d="M30.224,3.948h-1.098V2.75c0-1.517-1.197-2.75-2.67-2.75c-1.474,0-2.67,1.233-2.67,2.75v1.197h-2.74V2.75
c0-1.517-1.197-2.75-2.67-2.75c-1.473,0-2.67,1.233-2.67,2.75v1.197h-2.74V2.75c0-1.517-1.197-2.75-2.67-2.75
c-1.473,0-2.67,1.233-2.67,2.75v1.197H6.224c-2.343,0-4.25,1.907-4.25,4.25v2h2.5h27.5h2.5v-2
C34.474,5.855,32.568,3.948,30.224,3.948z M11.466,7.646c0,0.689-0.525,1.25-1.17,1.25s-1.17-0.561-1.17-1.25V2.75
c0-0.689,0.525-1.25,1.17-1.25s1.17,0.561,1.17,1.25V7.646z M19.546,7.646c0,0.689-0.525,1.25-1.17,1.25s-1.17-0.561-1.17-1.25
V2.75c0-0.689,0.525-1.25,1.17-1.25s1.17,0.561,1.17,1.25V7.646z M27.626,7.646c0,0.689-0.525,1.25-1.17,1.25
c-0.646,0-1.17-0.561-1.17-1.25V2.75c0-0.689,0.524-1.25,1.17-1.25c0.645,0,1.17,0.561,1.17,1.25V7.646z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -333,6 +333,19 @@ export const asyncRouterMap = [
]
},
{
path: '/calendar',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/calendar/index'),
name: 'Calendar',
meta: { title: 'Calendar', icon: 'calendar' }
}
]
},
{
path: 'external-link',
component: Layout,

View File

@ -0,0 +1,290 @@
<template>
<div :style="{height}"/>
</template>
<script>
import TuiCalendar from 'tui-calendar'
const calendarEvents = [
'afterRenderSchedule',
'clickDayname',
'clickMore',
'clickSchedule',
'clickTimezonesCollapseBtn'
]
const scheduleNeedProps = ['start', 'category']
function cloneObj(obj) {
return JSON.parse(JSON.stringify(obj))
}
export default {
name: 'ToastUICalendar',
props: {
tag: {
type: String,
default: 'div'
},
calendars: {
type: Array,
default() { return [] }
},
defaultSchedules: {
type: Array,
default() { return [] },
validator(val) {
if (val) {
return val.every(schedule => scheduleNeedProps.every(prop => schedule.hasOwnProperty(prop)))
}
return true
}
},
view: {
type: String,
default: 'month'
},
taskView: {
type: [Boolean, Array],
default: true
},
scheduleView: {
type: [Boolean, Array],
default: true
},
theme: {
type: Object,
default() { return {} }
},
template: {
type: Object,
default() { return {} }
},
week: {
type: Object,
default() { return {} }
},
month: {
type: Object,
default() { return {} }
},
timezones: {
type: Array,
default() { return [] }
},
useCreationPopup: {
type: Boolean,
default: true
},
useDetailPopup: {
type: Boolean,
default: true
},
disableDblClick: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
onUpdateSchedule: {
type: Function,
default: () => { return true }
},
onCreateSchedule: {
type: Function,
default: () => { return true }
},
onDeleteSchedule: {
type: Function,
default: () => { return true }
},
height: {
type: String,
default: '800px'
}
},
data() {
return {
calendar: null,
scheduleIds: []
}
},
watch: {
calendars(val) {
this.calendar.setCalendars(val)
},
defaultSchedules() {
this.reflectSchedules()
},
view(val) {
this.calendar.changeView(val, true)
},
taskView(val) {
this.calendar.setOptions({ taskView: val })
},
scheduleView(val) {
this.calendar.setOptions({ scheduleView: val })
},
theme: {
deep: true,
handler(val) {
this.calendar.setTheme(cloneObj(val))
}
},
week: {
deep: true,
handler(val) {
const silent = this.view !== 'week' && this.view !== 'day'
this.calendar.setOptions({ week: cloneObj(val) }, silent)
}
},
month: {
deep: true,
handler(val) {
const silent = this.view !== 'month'
this.calendar.setOptions({ month: cloneObj(val) }, silent)
}
},
timezones(val) {
this.calendar.setOptions({ timezones: val })
},
disableDblClick(val) {
this.calendar.setOptions({ disableDblClick: val })
},
readonly(val) {
this.calendar.setOptions({ isReadOnly: val })
}
},
mounted() {
this.calendar = new TuiCalendar(this.$el, {
defaultView: this.view,
taskView: this.taskView,
scheduleView: this.scheduleView,
theme: this.theme,
template: this.template,
week: this.week,
month: this.month,
calendars: this.calendars,
timezones: this.timezones,
useCreationPopup: this.useCreationPopup,
useDetailPopup: this.useDetailPopup,
disableDblClick: this.disableDblClick,
isReadOnly: this.readonly
})
this.registerEvents()
this.reflectSchedules()
},
beforeDestroy() {
calendarEvents.forEach(event => {
this.calendar.off(event)
})
this.calendar.destroy()
},
methods: {
registerEvents() {
calendarEvents.forEach(event => {
this.calendar.on(event, (...args) => {
this.$emit(event, ...args)
return true
})
})
this.calendar.on('beforeCreateSchedule', this.beforeCreateSchedule)
this.calendar.on('beforeUpdateSchedule', this.beforeUpdateSchedule)
this.calendar.on('beforeDeleteSchedule', this.beforeDeleteSchedule)
},
reflectSchedules() {
this.calendar.clear(true)
if (this.defaultSchedules) {
this.calendar.createSchedules(this.defaultSchedules, true)
this.scheduleIds = this.defaultSchedules.map(t => ({
scheduleId: t.id,
calendarId: t.calendarId
}))
}
this.calendar.render()
},
invoke(methodName, ...args) {
if (this.calendar[methodName]) {
return this.calendar[methodName](...args)
}
console.error(`method ${methodName} not found in tui-calendar instance`)
},
beforeCreateSchedule(payload) {
const result = this.onCreateSchedule(payload)
if (result === false) {
return
}
const schedule = this.saveNewSchedule(payload)
this.scheduleIds.push({
scheduleId: schedule.id,
calendarId: schedule.calendarId
})
this.$emit('change', { type: 'create', schedule })
},
beforeUpdateSchedule(payload) {
const result = this.onUpdateSchedule(payload)
if (result === false) {
return
}
const { schedule, start, end } = payload
schedule.start = start
schedule.end = end
this.calendar.updateSchedule(schedule.id, schedule.calendarId, schedule)
this.$emit('change', { type: 'update', schedule })
},
beforeDeleteSchedule(payload) {
const result = this.onDeleteSchedule(payload)
if (result === false) {
return
}
const { schedule } = payload
this.calendar.deleteSchedule(schedule.id, schedule.calendarId)
const index = this.scheduleIds.findIndex(t => t.scheduleId === schedule.id && t.calendarId === schedule.calendarId)
this.scheduleIds.splice(index, 1)
this.$emit('change', { type: 'delete', schedule })
},
saveNewSchedule(scheduleData) {
const calendar = scheduleData.calendar || this.findCalendar(scheduleData.calendarId)
const schedule = {
id: scheduleData.id || String(Date.now()),
title: scheduleData.title,
isAllDay: scheduleData.isAllDay,
start: scheduleData.start,
end: scheduleData.end,
category: scheduleData.isAllDay ? 'allday' : 'time',
dueDateClass: '',
location: scheduleData.location,
raw: {
class: scheduleData.raw['class']
},
state: scheduleData.state
}
if (calendar) {
schedule.calendarId = calendar.id
schedule.color = calendar.color
schedule.bgColor = calendar.bgColor
schedule.dragBgColor = calendar.bgColor
schedule.borderColor = calendar.borderColor
}
this.calendar.createSchedules([schedule])
return schedule
},
findCalendar(id) {
if (this.calendars) {
return this.calendars.find(t => t.id === id)
}
return null
},
getAllSchedules() {
return this.scheduleIds.map(item => this.calendar.getSchedule(item.scheduleId, item.calendarId))
}
}
}
</script>

View File

@ -0,0 +1,117 @@
export default {
'common.border': '1px solid #e5e5e5',
'common.backgroundColor': 'white',
'common.holiday.color': '#ff4040',
'common.saturday.color': '#135de6',
'common.dayname.color': '#333',
'common.today.color': '#135de6',
// creation guide style
'common.creationGuide.backgroundColor': 'rgba(81, 92, 230, 0.05)',
'common.creationGuide.border': '1px solid #515ce6',
// month header 'dayname'
'month.dayname.height': '31px',
'month.dayname.borderLeft': '1px solid #e5e5e5',
'month.dayname.paddingLeft': '10px',
'month.dayname.paddingRight': '10px',
'month.dayname.backgroundColor': 'inherit',
'month.dayname.fontSize': '12px',
'month.dayname.fontWeight': 'normal',
'month.dayname.textAlign': 'left',
// month day grid cell 'day'
'month.holidayExceptThisMonth.color': 'rgba(255, 64, 64, 0.4)',
'month.dayExceptThisMonth.color': 'rgba(51, 51, 51, 0.4)',
'month.weekend.backgroundColor': 'inherit',
'month.day.fontSize': '14px',
// month schedule style
'month.schedule.borderRadius': '2px',
'month.schedule.height': '24px',
'month.schedule.marginTop': '2px',
'month.schedule.marginLeft': '8px',
'month.schedule.marginRight': '8px',
// month more view
'month.moreView.border': '1px solid #d5d5d5',
'month.moreView.boxShadow': '0 2px 6px 0 rgba(0, 0, 0, 0.1)',
'month.moreView.backgroundColor': 'white',
'month.moreView.paddingBottom': '17px',
'month.moreViewTitle.height': '44px',
'month.moreViewTitle.marginBottom': '12px',
'month.moreViewTitle.backgroundColor': 'white',
'month.moreViewTitle.borderBottom': 'none',
'month.moreViewTitle.padding': '12px 17px 0 17px',
'month.moreViewList.padding': '0 17px 17px 17px',
// week header 'dayname'
'week.dayname.height': '42px',
'week.dayname.borderTop': '1px solid #e5e5e5',
'week.dayname.borderBottom': '1px solid #e5e5e5',
'week.dayname.borderLeft': 'inherit',
'week.dayname.paddingLeft': '0',
'week.dayname.backgroundColor': 'inherit',
'week.dayname.textAlign': 'left',
'week.today.color': '#333',
// week vertical panel 'vpanel'
'week.vpanelSplitter.border': '1px solid #e5e5e5',
'week.vpanelSplitter.height': '3px',
// week daygrid 'daygrid'
'week.daygrid.borderRight': '1px solid #e5e5e5',
'week.daygrid.backgroundColor': 'inherit',
'week.daygridLeft.width': '72px',
'week.daygridLeft.backgroundColor': 'inherit',
'week.daygridLeft.paddingRight': '8px',
'week.daygridLeft.borderRight': '1px solid #e5e5e5',
'week.today.backgroundColor': 'rgba(81, 92, 230, 0.05)',
'week.weekend.backgroundColor': 'inherit',
// week timegrid 'timegrid'
'week.timegridLeft.width': '72px',
'week.timegridLeft.backgroundColor': 'inherit',
'week.timegridLeft.borderRight': '1px solid #e5e5e5',
'week.timegridLeft.fontSize': '11px',
'week.timegridLeftTimezoneLabel.height': '20px',
'week.timegridOneHour.height': '52px',
'week.timegridHalfHour.height': '26px',
'week.timegridHalfHour.borderBottom': 'none',
'week.timegridHorizontalLine.borderBottom': '1px solid #e5e5e5',
'week.timegrid.paddingRight': '8px',
'week.timegrid.borderRight': '1px solid #e5e5e5',
'week.timegridSchedule.borderRadius': '2px',
'week.timegridSchedule.paddingLeft': '2px',
'week.currentTime.color': '#515ce6',
'week.currentTime.fontSize': '11px',
'week.currentTime.fontWeight': 'normal',
'week.pastTime.color': '#333',
'week.pastTime.fontWeight': 'normal',
'week.futureTime.color': '#333',
'week.futureTime.fontWeight': 'normal',
'week.currentTimeLinePast.border': '1px dashed #515ce6',
'week.currentTimeLineBullet.backgroundColor': '#515ce6',
'week.currentTimeLineToday.border': '1px solid #515ce6',
'week.currentTimeLineFuture.border': 'none',
// week creation guide style
'week.creationGuide.color': '#515ce6',
'week.creationGuide.fontSize': '11px',
'week.creationGuide.fontWeight': 'bold',
// week daygrid schedule style
'week.dayGridSchedule.borderRadius': '2px',
'week.dayGridSchedule.height': '24px',
'week.dayGridSchedule.marginTop': '2px',
'week.dayGridSchedule.marginLeft': '8px',
'week.dayGridSchedule.marginRight': '8px'
}

View File

@ -0,0 +1,266 @@
<template>
<div class="app-container">
<div>
<el-button @click="getAllSchedules">Get All Schedules</el-button>
<el-select v-model="selectedView">
<el-option v-for="option in viewModeOptions" :key="option" :value="option">
{{ option }}
</el-option>
</el-select>
<el-button @click="moveDate('prev')">prev</el-button>
<el-button @click="moveDate('today')">today</el-button>
<el-button @click="moveDate('next')">next</el-button>
<span>{{ dateRange }}</span>
</div>
<div class="calendar">
<tui-calendar
ref="tuiCal"
:view="selectedView"
:default-schedules="scheduleList"
:calendars="calendarList"
:task-view="taskView"
:schedule-view="scheduleView"
:use-detail-popup="useDetailPopup"
:use-creation-popup="useCreationPopup"
:disable-dbl-click="disableDblClick"
:readonly="readonly"
:month="month"
:week="week"
:timezones="timezones"
:template="templates"
:theme="theme"
:on-create-schedule="onCreateSchedule"
:on-update-schedule="onUpdateSchedule"
:on-delete-schedule="onDeleteSchedule"
@clickSchedule="onClickSchedule"
@afterRenderSchedule="onAfterRenderSchedule"
@clickTimezonesCollapseBtn="onClickTimezonesCollapseBtn"
@clickDayname="onClickDayname"
@clickMore="onClickMore"
/>
</div>
</div>
</template>
<script>
import TuiCalendar from './components/TuiCalendar'
import dayjs from 'dayjs'
import theme from './components/theme'
import 'tui-time-picker/dist/tui-time-picker.css'
import 'tui-date-picker/dist/tui-date-picker.css'
import 'tui-calendar/dist/tui-calendar.css'
export default {
name: 'CalendarDemo',
components: {
TuiCalendar
},
data() {
const scheduleList = [
{
id: '1',
calendarId: '0',
title: 'TOAST UI Calendar',
category: 'time',
dueDateClass: '',
start: dayjs().toDate(),
end: dayjs().add(3, 'hour').toDate()
},
{
id: '2',
calendarId: '1',
title: 'Practice',
category: 'milestone',
dueDateClass: '',
start: dayjs().add(1, 'day').toDate(),
end: dayjs().add(1, 'day').toDate(),
isReadOnly: true
},
{
id: '3',
calendarId: '1',
title: 'FE Workshop',
category: 'allday',
dueDateClass: '',
start: dayjs().subtract(2, 'day').toDate(),
end: dayjs().subtract(1, 'day').toDate(),
isReadOnly: true
},
{
id: '4',
calendarId: '1',
title: 'Report',
category: 'time',
dueDateClass: '',
start: dayjs().toDate(),
end: dayjs().add(1, 'hour').toDate()
}
]
const calendarList = [
{
id: '0',
name: 'Private',
bgColor: '#9e5fff',
borderColor: '#9e5fff'
},
{
id: '1',
name: 'Company',
bgColor: '#00a9ff',
borderColor: '#00a9ff'
}
]
const templates = {
milestone(schedule) {
return `<span style="color:#fff;background-color: ${schedule.bgColor};">${schedule.title}</span>`
},
milestoneTitle() {
return 'Milestone'
},
allday(schedule) {
return `${schedule.title}<i class="fa fa-refresh"></i>`
},
alldayTitle() {
return 'All Day'
}
}
const timezones = [{
timezoneOffset: 480,
displayLabel: 'GMT+08:00',
tooltip: 'China'
}, {
timezoneOffset: -420,
displayLabel: 'GMT-08:00',
tooltip: 'Los Angeles'
}]
return {
dateRange: '',
selectedView: 'month',
viewModeOptions: ['month', 'week', 'day'],
month: {
startDayOfWeek: 0
},
week: {
showTimezoneCollapseButton: true,
timezonesCollapsed: true
},
timezones,
scheduleList,
calendarList,
templates,
taskView: true,
scheduleView: true,
useDetailPopup: true,
useCreationPopup: true,
disableDblClick: true,
readonly: false,
theme
}
},
watch: {
selectedView() {
this.refreshRangeText()
}
},
created() {
document.querySelector('.app-main').style.overflow = 'visible'
},
mounted() {
this.refreshRangeText()
},
beforeDestroy() {
document.querySelector('.app-main').style.overflow = null
},
methods: {
getAllSchedules() {
console.log(this.$refs.tuiCal.getAllSchedules())
},
refreshRangeText() {
this.$nextTick(() => {
const { invoke } = this.$refs.tuiCal
const view = invoke('getViewName')
if (view === 'month') {
const calDate = invoke('getDate')
this.dateRange = dayjs(calDate).format('YYYY-MM')
} else if (view === 'week') {
const rangeStart = invoke('getDateRangeStart')
const rangeEnd = invoke('getDateRangeEnd')
const start = dayjs(rangeStart).format('YYYY-MM-DD')
const end = dayjs(rangeEnd).format('YYYY-MM-DD')
this.dateRange = `${start} ~ ${end}`
} else {
const calDate = invoke('getDate')
this.dateRange = dayjs(calDate).format('YYYY-MM-DD')
}
})
},
onAfterRenderSchedule(payload) {
console.group('onAfterRenderSchedule')
console.log('Schedule Info: ', payload.schedule)
console.groupEnd()
},
onClickSchedule(payload) {
console.group('onClickSchedule')
console.log('MouseEvent: ', payload.event)
console.log('Calendar Info: ', payload.calendar)
console.log('Schedule Info: ', payload.schedule)
console.groupEnd()
},
onClickDayname(payload) {
console.group('onClickSchedule')
console.log('Date: ', payload.date)
console.groupEnd()
},
onClickMore(payload) {
console.group('onClickSchedule')
console.log('target: ', payload.target)
console.log('date: ', payload.date)
console.groupEnd()
},
onClickTimezonesCollapseBtn(timezonesCollapsed) {
console.group('onClickTimezonesCollapseBtn')
console.log('Is Collapsed Timezone? ', timezonesCollapsed)
console.groupEnd()
if (timezonesCollapsed) {
this.theme['week.timegridLeft.width'] = '100px'
this.theme['week.daygridLeft.width'] = '100px'
} else {
this.theme['week.timegridLeft.width'] = '50px'
this.theme['week.daygridLeft.width'] = '50px'
}
},
onCreateSchedule(schedule) {
console.group('onCreateSchedule')
console.log('schedule', schedule)
console.groupEnd()
schedule.id = String(Date.now()).slice(-3)
},
onUpdateSchedule(payload) {
console.group('onUpdateSchedule')
console.log('calendar', payload.calendar)
console.log('schedule', payload.schedule)
console.groupEnd()
},
onDeleteSchedule({ schedule }) {
console.group('onDeleteSchedule')
console.log('schedule', schedule)
console.groupEnd()
},
moveDate(action) {
this.$refs.tuiCal.invoke(action)
this.refreshRangeText()
}
}
}
</script>
<style scoped>
.calendar {
margin-top: 20px;
}
</style>