From b101760092b05a82988f296fec14d2c28ed672f1 Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 25 Mar 2021 08:14:19 +0800 Subject: [PATCH] feat: add calendar view --- .../pages/AdminLoader/View/Calendar/index.tsx | 225 +++++++ .../AdminLoader/View/Calendar/style.less | 634 ++++++++++++++++++ .../pages/AdminLoader/View/index.tsx | 2 + 3 files changed, 861 insertions(+) create mode 100644 packages/app/src/components/pages/AdminLoader/View/Calendar/index.tsx create mode 100644 packages/app/src/components/pages/AdminLoader/View/Calendar/style.less diff --git a/packages/app/src/components/pages/AdminLoader/View/Calendar/index.tsx b/packages/app/src/components/pages/AdminLoader/View/Calendar/index.tsx new file mode 100644 index 000000000..6fa022856 --- /dev/null +++ b/packages/app/src/components/pages/AdminLoader/View/Calendar/index.tsx @@ -0,0 +1,225 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Table as AntdTable, Card, Pagination } from 'antd'; +import { useRequest, useHistory } from 'umi'; +import api from '@/api-client'; +import { LoadingOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons'; +import './style.less'; +import { Calendar as BigCalendar, momentLocalizer } from 'react-big-calendar' +import * as dates from 'react-big-calendar/lib/utils/dates'; +import moment from 'moment'; +const localizer = momentLocalizer(moment) // or globalizeLocalizer +import Drawer from '@/components/pages/AdminLoader/Drawer'; +import { Details, DetailsPage } from '../Table'; + +export const icon = ; + +export interface CalendarProps { + schema?: any; + activeTab?: any; + resourceName: string; + associatedName?: string; + associatedKey?: string; + [key: string]: any; +} + +// let weekRangeFormat = ({ start, end }, culture, local) => +// local.format(start, 'MMMM DD', culture) + +// ' – ' + +// local.format(end, dates.eq(start, end, 'month') ? 'DD' : 'MMMM DD', culture) + +function toEvents(data, options: any = {}) { + const { startDateField, endDateField, labelField, idField = 'id' } = options; + if (!Array.isArray(data)) { + return []; + } + return data.map(item => ({ + id: item[idField], + title: item[labelField], + allDay: true, + start: moment(item[startDateField||'createdAt'] || item.created_at).toDate(), + end: moment(item[endDateField || startDateField || 'createdAt'] || item.created_at).toDate(), + })); +} + +export function Calendar(props: CalendarProps) { + const { + schema = {}, + associatedKey, + defaultFilter, + } = props; + + const paginated = false; + + const { rowKey = 'id', + filter: schemaFilter, + sort, + appends, + resourceName, + labelField, + startDateField, + endDateField, + detailsOpenMode, + details = [], + } = schema; + + console.log({schema}) + + const [calendarView, setCalendarView] = useState('month'); + + const history = useHistory(); + + const { data, loading, mutate, refresh, run, params } = useRequest((params = {}, ...args) => { + const { current, pageSize, sorter, filter, ...restParams } = params; + console.log('paramsparamsparamsparamsparams', params, args); + return api.resource(resourceName).list({ + associatedKey, + page: paginated ? current : 1, + perPage: paginated ? pageSize : -1, + sorter, + sort, + 'fields[appends]': appends, + // filter, + // ...actionDefaultParams, + filter: { + and: [ + defaultFilter, + schemaFilter, + filter, + // __parent ? { + // collection_name: __parent, + // } : null, + ].filter(obj => obj && Object.keys(obj).length) + } + // ...args2, + }) + .then(({data = [], meta = {}}) => { + return { + data: { + list: data, + total: meta.count||data.length, + }, + }; + }); + }, { + paginated, + }); + + const events = toEvents(data?.list, { + idField: rowKey, + labelField, + startDateField, + endDateField, + }); + + const messages = { + allDay: '', + previous:
, + next:
, + today: '今天', + month: '月', + week: '周', + day: '天', + agenda: '列表', + date: '日期', + time: '时间', + event: '事件', + noEventsInRange: '无', + showMore: (count) => `还有 ${count} 项` + } + + + console.log('events', data) + return ( + + { + if (dates.eq(start, end, 'month')) { + return local.format(start, 'Y年M月', culture); + } + return `${local.format(start, 'Y年M月', culture)} - ${local.format(end, 'Y年M月', culture)}`; + } + }} + events={events} + onSelectSlot={(slotInfo) => { + // setFormMode('create'); + // drawerRef.current.setVisible(true); + // console.log('onSelectSlot', slotInfo) + }} + onView={(view) => { + setCalendarView(view); + console.log(view) + }} + onSelectEvent={(data) => { + console.log({data}); + if (!detailsOpenMode || !details.length) { + return; + } + if (detailsOpenMode === 'window') { + const paths = history.location.pathname.split('/'); + history.push(`/admin/${paths[2]}/${data[rowKey]}/0`); + } else { + Drawer.open({ + headerStyle: details.length > 1 ? { + paddingBottom: 0, + borderBottom: 0, + // paddingTop: 16, + // marginBottom: -4, + } : {}, + // title: details.length > 1 ? undefined : data[labelField], + title: data.title, + bodyStyle: { + // padding: 0, + }, + content: ({resolve, closeWithConfirm}) => ( +
+
{ + await refresh(); + resolve(); + }} + onValueChange={() => { + closeWithConfirm && closeWithConfirm(true); + }} + onDraft={async () => { + await refresh(); + resolve(); + }} + onReset={resolve} + onDataChange={async () => { + await refresh(); + }} + data={data} + resolve={resolve} + items={details} + /> +
+ ), + }); + } + }} + onRangeChange={(range) => { + console.log({range}) + }} + views={['month', 'week', 'day']} + // step={120} + // showMultiDayTimes + // max={dates.add(dates.endOf(new Date(2015, 17, 1), 'day'), -1, 'hours')} + // defaultDate={new Date(2015, 3, 1)} + components={{ + timeGutterHeader: () =>
全天
, + }} + localizer={localizer} + /> +
+ ); +} diff --git a/packages/app/src/components/pages/AdminLoader/View/Calendar/style.less b/packages/app/src/components/pages/AdminLoader/View/Calendar/style.less new file mode 100644 index 000000000..d9ade5112 --- /dev/null +++ b/packages/app/src/components/pages/AdminLoader/View/Calendar/style.less @@ -0,0 +1,634 @@ +@charset "UTF-8"; +.rbc-btn { + color: inherit; + font: inherit; + margin: 0; } + +button.rbc-btn { + overflow: visible; + text-transform: none; + -webkit-appearance: button; + cursor: pointer; } + +button[disabled].rbc-btn { + cursor: not-allowed; } + +button.rbc-input::-moz-focus-inner { + border: 0; + padding: 0; } + +.rbc-calendar { + box-sizing: border-box; + // height: 75vh; + display: flex; + flex-direction: column; + align-items: stretch; } + +.rbc-calendar *, +.rbc-calendar *:before, +.rbc-calendar *:after { + box-sizing: inherit; } + +.rbc-abs-full, .rbc-row-bg { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; } + +.rbc-ellipsis, .rbc-event-label, .rbc-row-segment .rbc-event-content, .rbc-show-more { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + +.rbc-rtl { + direction: rtl; } + +.rbc-off-range { + color: rgba(0,0,0,.25); } + +.rbc-off-range-bg { + // background: #e6e6e6; +} + +.rbc-header { + // font-size: 16px; + overflow: hidden; + flex: 1 0 0%; + text-overflow: ellipsis; + white-space: nowrap; + padding: 4px 12px; + text-align: right; + vertical-align: middle; + // font-weight: 500; + min-height: 32px; + color: rgba(0,0,0,.85); + margin: 0 4px; + border-bottom: 2px solid #f0f0f0; +} + .rbc-header + .rbc-header { + // border-left: 1px solid #f0f0f0; + } + .rbc-rtl .rbc-header + .rbc-header { + border-left-width: 0; + border-right: 1px solid #f0f0f0; } + .rbc-header > a, .rbc-header > a:active, .rbc-header > a:visited { + color: inherit; + text-decoration: none; } + +.rbc-row-content { + position: relative; + user-select: none; + -webkit-user-select: none; + z-index: 4; } + +.rbc-row-content-scrollable { + display: flex; + flex-direction: column; + height: 100%; } + .rbc-row-content-scrollable .rbc-row-content-scroll-container { + height: 100%; + overflow-y: scroll; + /* Hide scrollbar for Chrome, Safari and Opera */ + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ } + .rbc-row-content-scrollable .rbc-row-content-scroll-container::-webkit-scrollbar { + display: none; } + + +.rbc-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + margin-bottom: 10px; + font-size: 16px; } + .rbc-toolbar .rbc-toolbar-label { + flex-grow: 1; + padding: 0 10px; + text-align: center; } + .rbc-toolbar button { + outline: none; + font-size: 14px; + line-height: 1.5715; + height: 32px; + color: #373a3c; + display: inline-block; + margin: 0; + position: relative; + z-index: 1; + text-align: center; + vertical-align: middle; + background: none; + background-image: none; + border: 1px solid #d9d9d9; + padding: 4px 15px; + border-radius: 2px; + line-height: normal; + white-space: nowrap; } + .rbc-toolbar button:active, .rbc-toolbar button.rbc-active { + // background-image: none; + // box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + // background-color: #e6e6e6; + z-index: 2; + color: #1890ff; + border-color: #1890ff; } + .rbc-toolbar button:active:hover, .rbc-toolbar button:active:focus, .rbc-toolbar button.rbc-active:hover, .rbc-toolbar button.rbc-active:focus { + // color: #373a3c; + // background-color: #d4d4d4; + // border-color: #8c8c8c; + z-index: 2; + color: #40a9ff; + border-color: #40a9ff; + } + // .rbc-toolbar button:focus { + // color: #373a3c; + // background-color: #e6e6e6; + // border-color: #adadad; } + .rbc-toolbar button:hover { + // color: #373a3c; + // background-color: #e6e6e6; + z-index: 2; + color: #40a9ff; + border-color: #40a9ff; + } + +.rbc-btn-group { + display: inline-block; + white-space: nowrap; } + .rbc-btn-group > button:first-child:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .rbc-btn-group > button:last-child:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .rbc-rtl .rbc-btn-group > button:first-child:not(:last-child) { + border-radius: 4px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .rbc-rtl .rbc-btn-group > button:last-child:not(:first-child) { + border-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .rbc-btn-group > button:not(:first-child):not(:last-child) { + border-radius: 0; } + .rbc-btn-group button + button { + margin-left: -1px; } + .rbc-rtl .rbc-btn-group button + button { + margin-left: 0; + margin-right: -1px; } + .rbc-btn-group + .rbc-btn-group, + .rbc-btn-group + button { + margin-left: 10px; } + +.rbc-event { + border: none; + box-sizing: border-box; + box-shadow: none; + margin: 0; + padding: 2px 5px; + background-color: #f0f0f0; + border-radius: 2px; + // color: #1890ff; + cursor: pointer; + font-size: 12px; + width: 100%; + text-align: left; + &:hover { + background-color: #e6f7ff; + color: #1890ff; + } +} + .rbc-slot-selecting .rbc-event { + cursor: inherit; + pointer-events: none; } + .rbc-event.rbc-selected { + background-color: #e6f7ff; + color: #1890ff; + } + .rbc-event:focus { + // outline: 5px auto #3b99fc; + } + +.rbc-event-label { + font-size: 80%; } + +.rbc-event-overlaps { + box-shadow: -1px 1px 5px 0px rgba(51, 51, 51, 0.5); } + +.rbc-event-continues-prior { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.rbc-event-continues-after { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + +.rbc-event-continues-earlier { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.rbc-event-continues-later { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.rbc-row { + display: flex; + flex-direction: row; } + +.rbc-row-segment { + padding: 0 4px 1px 4px; } + +.rbc-selected-cell { + background-color: rgba(0, 0, 0, 0.1); } + +.rbc-show-more { + z-index: 4; + font-weight: bold; + font-size: 85%; + height: auto; + line-height: normal; + color: inherit; + padding: 2px 5px; +} + +.rbc-month-view { + position: relative; + // border: 1px solid #f0f0f0; + display: flex; + flex-direction: column; + flex: 1 0 0; + width: 100%; + user-select: none; + -webkit-user-select: none; + height: 68vh; } + +.rbc-month-header { + display: flex; + flex-direction: row; } + +.rbc-month-row { + display: flex; + position: relative; + flex-direction: column; + flex: 1 0 0; + flex-basis: 0px; + overflow: hidden; + height: 100%; } + .rbc-month-row + .rbc-month-row { + // border-top: 1px solid #f0f0f0; + } + +.rbc-date-cell { + // cursor: pointer; + flex: 1 1 0; + min-width: 0; + padding-right: 12px; + padding-top: 4px; + text-align: right; } + .rbc-date-cell.rbc-now a { + // font-weight: bold; + color: #1890ff; + } + .rbc-date-cell > a, .rbc-date-cell > a:active, .rbc-date-cell > a:visited { + color: inherit; + text-decoration: none; + } + .rbc-date-cell > a:hover { + color: #1890ff; + } + +.rbc-row-bg { + display: flex; + flex-direction: row; + flex: 1 0 0; + overflow: hidden; } + + + +.rbc-day-bg { + flex: 1 0 0%; + // border-top: 2px solid #f0f0f0; + margin: 0 4px; + &:hover { + background: #f5f5f5; + } + // cursor: pointer; +} + +.rbc-month-view { + .rbc-day-bg { + border-top: 2px solid #f0f0f0; + } + .rbc-today { + border-color: #1890ff !important; + background-color: #e6f7ff !important; + } + .rbc-header { + border-bottom: 0 !important; + } +} + + .rbc-day-bg + .rbc-day-bg { + // margin-left: 8px; + // border-left: 1px solid #f0f0f0; + } + .rbc-rtl .rbc-day-bg + .rbc-day-bg { + // border-left-width: 0; + // border-right: 1px solid #f0f0f0; + } + +.rbc-overlay { + position: absolute; + z-index: 5; + margin-top: 5px; + border-radius: 2px; + // border: 1px solid #e5e5e5; + background-color: #fff; + box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08), 0 9px 28px 8px rgba(0,0,0,.05); + padding: 12px 16px; } + .rbc-overlay > * + * { + margin-top: 1px; } + +.rbc-overlay-header { + font-weight: 500; + min-height: 32px; + border-bottom: 1px solid #f0f0f0; + margin: -12px -16px 12px -16px; + padding: 5px 16px 4px; } + +.rbc-agenda-view { + display: flex; + flex-direction: column; + flex: 1 0 0; + overflow: auto; } + .rbc-agenda-view table.rbc-agenda-table { + width: 100%; + border: 1px solid #f0f0f0; + border-spacing: 0; + border-collapse: collapse; } + .rbc-agenda-view table.rbc-agenda-table tbody > tr > td { + padding: 5px 10px; + vertical-align: top; } + .rbc-agenda-view table.rbc-agenda-table .rbc-agenda-time-cell { + padding-left: 15px; + padding-right: 15px; + text-transform: lowercase; } + .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { + border-left: 1px solid #f0f0f0; } + .rbc-rtl .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { + border-left-width: 0; + border-right: 1px solid #f0f0f0; } + .rbc-agenda-view table.rbc-agenda-table tbody > tr + tr { + border-top: 1px solid #f0f0f0; } + .rbc-agenda-view table.rbc-agenda-table thead > tr > th { + padding: 3px 5px; + text-align: left; + border-bottom: 1px solid #f0f0f0; } + .rbc-rtl .rbc-agenda-view table.rbc-agenda-table thead > tr > th { + text-align: right; } + +.rbc-agenda-time-cell { + text-transform: lowercase; } + .rbc-agenda-time-cell .rbc-continues-after:after { + content: ' »'; } + .rbc-agenda-time-cell .rbc-continues-prior:before { + content: '« '; } + +.rbc-agenda-date-cell, +.rbc-agenda-time-cell { + white-space: nowrap; } + +.rbc-agenda-event-cell { + width: 100%; } + +.rbc-time-column { + display: flex; + flex-direction: column; + min-height: 100%; } + .rbc-time-column .rbc-timeslot-group { + flex: 1; } + +.rbc-timeslot-group { + border-bottom: 1px solid #f0f0f0; + min-height: 40px; + line-height: 39px; + display: flex; + flex-flow: column nowrap; + // cursor: pointer; + &:hover { + background: #f0f0f0; + } +} + +.rbc-time-gutter, +.rbc-header-gutter { + flex: none; } + +.rbc-label { + padding: 0 5px; } + +.rbc-day-slot { + position: relative; } + .rbc-day-slot .rbc-events-container { + bottom: 0; + left: 0; + position: absolute; + right: 0; + margin-right: 10px; + top: 0; } + .rbc-day-slot .rbc-events-container.rbc-rtl { + left: 10px; + right: 0; } + .rbc-day-slot .rbc-event { + border: 1px solid #265985; + display: flex; + max-height: 100%; + min-height: 20px; + flex-flow: column wrap; + align-items: flex-start; + overflow: hidden; + position: absolute; } + .rbc-day-slot .rbc-event-label { + flex: none; + padding-right: 5px; + width: auto; } + .rbc-day-slot .rbc-event-content { + width: 100%; + flex: 1 1 0; + word-wrap: break-word; + line-height: 1; + height: 100%; + min-height: 1em; } + .rbc-day-slot .rbc-time-slot { + // border-top: 1px solid #f7f7f7; + } +.rbc-time-header-gutter { + line-height: 40px; +} +.rbc-time-header-cell { + min-height: 32px !important; +} +.rbc-calendar.view-week { + .rbc-time-header-cell { + margin-top: -32px; + margin-bottom: 4px; + } + .rbc-time-view { + padding-top: 32px; + border-top: 0; + } + .rbc-header { + padding: 4px 8px; + } + .rbc-time-header-content { + padding: 4px 0; + } + .rbc-time-header-gutter { + padding: 0; + padding-top: 2px; + > div { + border-top: 2px solid #f0f0f0; + padding: 0 5px; + } + } +} +.rbc-time-view-resources .rbc-time-gutter, +.rbc-time-view-resources .rbc-time-header-gutter { + position: sticky; + left: 0; + background-color: white; + border-right: 1px solid #f0f0f0; + z-index: 10; + margin-right: -1px; } + +.rbc-time-view-resources .rbc-time-header { + overflow: hidden; } + +.rbc-time-view-resources .rbc-time-header-content { + min-width: auto; + flex: 1 0 0; + flex-basis: 0px; } + +.rbc-time-view-resources .rbc-time-header-cell-single-day { + display: none; } + +.rbc-time-view-resources .rbc-day-slot { + min-width: 140px; } + +.rbc-time-view-resources .rbc-header, +.rbc-time-view-resources .rbc-day-bg { + width: 140px; + flex: 1 1 0; + flex-basis: 0 px; } + +.rbc-time-header-content + .rbc-time-header-content { + margin-left: -1px; } + +.rbc-time-slot { + flex: 1 0 0; } + .rbc-time-slot.rbc-now { + font-weight: bold; } + +.rbc-day-header { + text-align: center; } + +.rbc-slot-selection { + z-index: 10; + position: absolute; + background-color: rgba(0, 0, 0, 0.5); + color: white; + font-size: 75%; + width: 100%; + padding: 3px; } + +.rbc-slot-selecting { + cursor: move; } + +.rbc-time-view { + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + border-top: 1px solid #f0f0f0; + min-height: 0; } + .rbc-time-view .rbc-time-gutter { + white-space: nowrap; } + .rbc-time-view .rbc-allday-cell { + box-sizing: content-box; + width: 100%; + height: 100%; + position: relative; } + .rbc-time-view .rbc-allday-cell + .rbc-allday-cell { + border-left: 1px solid #f0f0f0; } + .rbc-time-view .rbc-allday-events { + position: relative; + z-index: 4; } + .rbc-time-view .rbc-row { + box-sizing: border-box; + min-height: 20px; } + +.rbc-time-header { + display: flex; + flex: 0 0 auto; + flex-direction: row; } + .rbc-time-header.rbc-overflowing { + border-right: 1px solid #f0f0f0; } + .rbc-rtl .rbc-time-header.rbc-overflowing { + border-right-width: 0; + border-left: 1px solid #f0f0f0; } + .rbc-time-header > .rbc-row:first-child { + border-bottom: 1px solid #f0f0f0; } + .rbc-time-header > .rbc-row.rbc-row-resource { + border-bottom: 1px solid #f0f0f0; } + +.rbc-time-header-cell-single-day { + display: none; } + +.rbc-time-header-content { + flex: 1; + display: flex; + min-width: 0; + flex-direction: column; + // border-left: 1px solid #f0f0f0; +} + .rbc-rtl .rbc-time-header-content { + border-left-width: 0; + border-right: 1px solid #f0f0f0; } + .rbc-time-header-content > .rbc-row.rbc-row-resource { + border-bottom: 1px solid #f0f0f0; + flex-shrink: 0; } + +.rbc-time-content { + display: flex; + flex: 1 0 0%; + align-items: flex-start; + width: 100%; + border-top: 1px solid #f0f0f0; + overflow-y: auto; + position: relative; } + .rbc-time-content > .rbc-time-gutter { + flex: none; } + .rbc-time-content > * + * > * { + // border-left: 1px solid #f0f0f0; + } + .rbc-rtl .rbc-time-content > * + * > * { + border-left-width: 0; + border-right: 1px solid #f0f0f0; } + .rbc-time-content > .rbc-day-slot { + width: 100%; + user-select: none; + -webkit-user-select: none; } + +.rbc-current-time-indicator { + position: absolute; + z-index: 3; + left: 0; + right: 0; + height: 1px; + background-color: #74ad31; + pointer-events: none; } diff --git a/packages/app/src/components/pages/AdminLoader/View/index.tsx b/packages/app/src/components/pages/AdminLoader/View/index.tsx index 30ba712dd..c0073ff18 100644 --- a/packages/app/src/components/pages/AdminLoader/View/index.tsx +++ b/packages/app/src/components/pages/AdminLoader/View/index.tsx @@ -20,6 +20,7 @@ import { Descriptions } from './Descriptions'; import { FilterForm } from './FilterForm'; import { SubTable } from './SubTable'; import { Wysiwyg } from './Wysiwyg'; +import { Calendar } from './Calendar'; const VIEWS = new Map(); @@ -64,5 +65,6 @@ registerView('filterForm', FilterForm); registerView('descriptions', Descriptions); registerView('association', Association); registerView('wysiwyg', Wysiwyg); +registerView('calendar', Calendar); export default View;