diff --git a/docs/zh-CN/components/form/input-date.md b/docs/zh-CN/components/form/input-date.md index 5f779c159..eff911189 100755 --- a/docs/zh-CN/components/form/input-date.md +++ b/docs/zh-CN/components/form/input-date.md @@ -312,6 +312,185 @@ order: 13 } ``` +## 日历日程 + +```schema: scope="body" +{ + "type": "input-date", + "embed": true, + "schedules": [ + { + "startTime": "2021-12-11 05:14:00", + "endTime": "2021-12-11 06:14:00", + "content": "这是一个日程1" + }, + { + "startTime": "2021-12-21 05:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程2" + } + ] +} +``` + +## 日历日程-自定义颜色 + +```schema: scope="body" +{ + "type": "input-date", + "embed": true, + "schedules": [ + { + "startTime": "2021-12-11 05:14:00", + "endTime": "2021-12-11 06:14:00", + "content": "这是一个日程1", + "className": "bg-success" + }, + { + "startTime": "2021-12-21 05:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程2", + "className": "bg-info" + } + ] +} +``` + +```schema: scope="body" +{ + "type": "input-date", + "embed": true, + "scheduleClassNames": ["bg-success", "bg-info"], + "schedules": [ + { + "startTime": "2021-12-11 05:14:00", + "endTime": "2021-12-11 06:14:00", + "content": "这是一个日程1" + }, + { + "startTime": "2021-12-21 05:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程2" + } + ] +} +``` + +## 日历日程-自定义日程展示 +```schema: scope="body" +{ + "type": "input-date", + "embed": true, + "schedules": [ + { + "startTime": "2021-12-11 05:14:00", + "endTime": "2021-12-11 06:14:00", + "content": "这是一个日程1" + }, + { + "startTime": "2021-12-21 05:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程2" + } + ], + "scheduleAction": { + "actionType": "drawer", + "drawer": { + "title": "日程", + "body": { + "type": "table", + "columns": [ + { + "name": "time", + "label": "时间" + }, + { + "name": "content", + "label": "内容" + } + ], + "data": "${scheduleData}" + } + } + } +} +``` + +## 日历日程-支持从数据源中获取日程 +```schema +{ + "type": "page", + "data": { + "schedules": [ + { + "startTime": "2021-12-11 05:14:00", + "endTime": "2021-12-11 06:14:00", + "content": "这是一个日程1" + }, + { + "startTime": "2021-12-21 05:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程2" + } + ] + }, + "body": [ + { + "type": "input-date", + "embed": true, + "schedules": "${schedules}" + } + ] +} +``` + +## 放大模式 + +```schema: scope="body" +{ + "type": "input-date", + "embed": true, + "largeMode": true, + "schedules": [ + { + "startTime": "2021-12-11 05:14:00", + "endTime": "2021-12-11 06:14:00", + "content": "这是一个日程1" + }, + { + "startTime": "2021-12-12 02:14:00", + "endTime": "2021-12-13 05:14:00", + "content": "这是一个日程2" + }, + { + "startTime": "2021-12-20 05:14:00", + "endTime": "2021-12-21 05:14:00", + "content": "这是一个日程3" + }, + { + "startTime": "2021-12-21 05:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程4" + }, + { + "startTime": "2021-12-22 02:14:00", + "endTime": "2021-12-23 05:14:00", + "content": "这是一个日程5" + }, + { + "startTime": "2021-12-22 02:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程6" + }, + { + "startTime": "2021-12-22 02:14:00", + "endTime": "2021-12-22 05:14:00", + "content": "这是一个日程7" + } + ] +} +``` + ## 原生日期组件 原生数字日期将直接使用浏览器的实现,最终展现效果和浏览器有关,而且只支持 `min`、`max`、`step` 这几个属性设置。 @@ -348,3 +527,7 @@ order: 13 | clearable | `boolean` | `true` | 是否可清除 | | embed | `boolean` | `false` | 是否内联模式 | | timeConstraints | `object` | `true` | 请参考: [react-datetime](https://github.com/YouCanBookMe/react-datetime) | +| schedules | `Array<{startTime: Date, endTime: Date, content: any, className?: string}> \| string` | | 日历中展示日程,可设置静态数据或从上下文中取数据,className参考[背景色](https://baidu.gitee.io/amis/zh-CN/style/background/background-color) | +| scheduleClassNames | `Array` | `['bg-warning', 'bg-danger', 'bg-success', 'bg-info', 'bg-secondary']` | 日历中展示日程的颜色,参考[背景色](https://baidu.gitee.io/amis/zh-CN/style/background/background-color) | +| scheduleAction | `SchemaNode` | | 自定义日程展示 | +| largeMode | `boolean` | `false` | 放大模式 | diff --git a/scss/components/_calendar.scss b/scss/components/_calendar.scss new file mode 100644 index 000000000..2d9c420fa --- /dev/null +++ b/scss/components/_calendar.scss @@ -0,0 +1,81 @@ + +.#{$ns}ScheduleCalendar { + &-icon { + position: absolute; + bottom: var(--Calendar-icon-bottom); + left: 50%; + transform: translateX(-50%); + display: block; + width: var(--Calendar-icon-width); + height: var(--Calendar-icon-height); + border-radius: 50%; + z-index: 10; + } + &-action { + display: block; + padding: 0; + width: 100%; + height: 100%; + border: none; + background: transparent; + color: inherit; + &:not(:disabled):not(.is-disabled):hover { + color: inherit; + background: transparent; + border-color: transparent; + } + } + .rdtDay { + position: relative; + } + + &-text-overflow { + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + position: absolute; + width: 100%; + } +} + +.#{$ns}ScheduleCalendar-large { + width: 100%; + + .rdtPicker { + width: 100%; + table { + border-collapse: collapse; + border-spacing: 0; + td { + border: var(--Calendar-borderWidth) solid var(--borderColor); + } + } + } + + .rdtDay { + height: var(--Calendar-rdt-day); + vertical-align: top; + } + + .#{$ns}ScheduleCalendar-large-day-wrap { + position: absolute; + top: 0; + left: 0; + min-width: 100%; + height: 100%; + .#{$ns}ScheduleCalendar-large-schedule-content { + position: relative; + z-index: 10; + border-radius: var(--borderRadius); + text-align: left; + padding: var(--Calendar-schedule-content-padding); + height: var(--Calendar-schedule-content-height); + color: var(--Calendar-schedule-content-color); + } + } + + .#{$ns}ScheduleCalendar-action { + z-index: 20; + position: relative; + } +} diff --git a/scss/themes/_common.scss b/scss/themes/_common.scss index 56473d2aa..dedc88333 100644 --- a/scss/themes/_common.scss +++ b/scss/themes/_common.scss @@ -37,6 +37,7 @@ @import '../components/button-group'; @import '../components/dropdown'; @import '../components/each'; +@import '../components/calendar'; @import '../components/collapse'; @import '../components/collapse-group'; @import '../components/color'; diff --git a/scss/themes/_cxd-variables.scss b/scss/themes/_cxd-variables.scss index 13cc07d6d..e13e02ec8 100644 --- a/scss/themes/_cxd-variables.scss +++ b/scss/themes/_cxd-variables.scss @@ -221,6 +221,15 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06), --Switch-onDisabled-color: #{$G11}; // --Switch-onDisabled-circle-BackgroundColor: #fff; + --Calendar-icon-bottom: #{px2rem(-4px)}; + --Calendar-icon-width: #{px2rem(10px)}; + --Calendar-icon-height: #{px2rem(10px)}; + --Calendar-borderWidth: #{px2rem(1px)}; + --Calendar-rdt-day: #{px2rem(100px)}; + --Calendar-schedule-content-padding: 0 #{px2rem(4px)}; + --Calendar-schedule-content-height: #{px2rem(20px)}; + --Calendar-schedule-content-color: #{$white}; + --ColorPicker-borderWidth: #{px2rem(1px)}; --ColorPicker-borderRadius: #{$R3}; --ColorPicker-bg: var(--white); diff --git a/src/components/DatePicker.tsx b/src/components/DatePicker.tsx index 387e9418f..7e12d1e9f 100644 --- a/src/components/DatePicker.tsx +++ b/src/components/DatePicker.tsx @@ -278,6 +278,15 @@ export interface DateProps extends LocaleProps, ThemeProps { borderMode?: 'full' | 'half' | 'none'; // 是否为内嵌模式,如果开启就不是 picker 了,直接页面点选。 embed?: boolean; + schedules?: Array<{ + startTime: Date, + endTime: Date, + content: any, + className?: string + }>; + scheduleClassNames?: Array; + largeMode?: boolean; + onScheduleClick?: (scheduleData: any) => void; // 下面那个千万不要写,写了就会导致 keyof DateProps 得到的结果是 string | number; // [propName: string]: any; @@ -302,7 +311,8 @@ export class DatePicker extends React.Component { viewMode: 'days' as 'years' | 'months' | 'days' | 'time', shortcuts: '', closeOnSelect: true, - overlayPlacement: 'auto' + overlayPlacement: 'auto', + scheduleClassNames: ['bg-warning', 'bg-danger', 'bg-success', 'bg-info', 'bg-secondary'] }; state: DatePickerState = { isOpened: false, @@ -546,7 +556,11 @@ export class DatePicker extends React.Component { format, borderMode, embed, - minDate + minDate, + schedules, + largeMode, + scheduleClassNames, + onScheduleClick } = this.props; const __ = this.props.translate; @@ -554,12 +568,33 @@ export class DatePicker extends React.Component { let date: moment.Moment | undefined = this.state.value; if (embed) { + let schedulesData: DateProps['schedules'] = undefined; + if (schedules && Array.isArray(schedules)) { + // 设置日程颜色 + let index = 0; + schedulesData = schedules.map((schedule: any) => { + let className = schedule.className; + if (!className && scheduleClassNames) { + className = scheduleClassNames[index]; + index++; + if (index >= scheduleClassNames.length) { + index = 0; + } + } + return { + ...schedule, + className + }; + }); + } return (
{ locale={locale} minDate={minDate} // utc={utc} + schedules={schedulesData} + largeMode={largeMode} + onScheduleClick={onScheduleClick} />
); diff --git a/src/components/calendar/Calendar.tsx b/src/components/calendar/Calendar.tsx index b60186c0e..dedabdb72 100644 --- a/src/components/calendar/Calendar.tsx +++ b/src/components/calendar/Calendar.tsx @@ -28,6 +28,14 @@ interface BaseDatePickerProps year?: number, date?: moment.Moment ) => JSX.Element; + schedules?: Array<{ + startTime: Date; + endTime: Date; + content: string | React.ReactElement; + color?: string; + }>; + largeMode?: boolean; + onScheduleClick?: (scheduleData: any) => void; } class BaseDatePicker extends ReactDatePicker { @@ -82,7 +90,10 @@ class BaseDatePicker extends ReactDatePicker { 'nextIcon', 'isEndDate', 'classnames', - 'minDate' + 'minDate', + 'schedules', + 'largeMode', + 'onScheduleClick' ].forEach(key => (props[key] = (this.props as any)[key])); return props; diff --git a/src/components/calendar/DaysView.tsx b/src/components/calendar/DaysView.tsx index 47a3a3c65..ae1bc4f1b 100644 --- a/src/components/calendar/DaysView.tsx +++ b/src/components/calendar/DaysView.tsx @@ -5,6 +5,7 @@ import React from 'react'; import Downshift from 'downshift'; import {LocaleProps, localeable} from '../../locale'; import {ClassNamesFn} from '../../theme'; +import find from 'lodash/find'; interface CustomDaysViewProps extends LocaleProps { classPrefix?: string; @@ -39,6 +40,14 @@ interface CustomDaysViewProps extends LocaleProps { updateSelectedDate: (event: React.MouseEvent, close?: boolean) => void; handleClickOutside: () => void; classnames: ClassNamesFn; + schedules?: Array<{ + startTime: Date, + endTime: Date, + content: any, + className?: string + }>; + largeMode?: boolean; + onScheduleClick?: (scheduleData: any) => void; } export class CustomDaysView extends DaysView { @@ -117,6 +126,86 @@ export class CustomDaysView extends DaysView { }; renderDay = (props: any, currentDate: moment.Moment) => { + if (this.props.schedules) { + let schedule: any[] = []; + this.props.schedules.forEach((item: any) => { + if (currentDate.isSameOrAfter(moment(item.startTime).subtract(1, 'days')) && currentDate.isSameOrBefore(item.endTime)) { + schedule.push(item); + } + }); + if (schedule.length > 0) { + const cx = this.props.classnames; + const __ = this.props.translate; + // 日程数据 + const scheduleData = { + scheduleData: schedule.map((item: any) => { + return { + ...item, + time: moment(item.startTime).format('YYYY-MM-DD HH:mm:ss') + ' - ' + moment(item.endTime).format('YYYY-MM-DD HH:mm:ss'), + } + }), + currentDate + }; + + // 放大模式 + if (this.props.largeMode) { + let showSchedule: any[] = []; + for (let i = 0; i < schedule.length; i++) { + if (showSchedule.length > 3) { + break; + } + if (moment(schedule[i].startTime).isSame(currentDate, 'day')) { + showSchedule.push(schedule[i]); + } + else if (currentDate.weekday() === 0) { + // 周一重新设置日程 + showSchedule.push({ + ...schedule[i], + width: moment(schedule[i].endTime).date() - currentDate.date() + }); + } + } + [0, 1, 2].forEach((i: number) => { + const findSchedule = find(schedule, (item: any) => item.height === i); + if (findSchedule && findSchedule !== showSchedule[i] && currentDate.weekday() !== 0) { + // 生成一个空白格占位 + showSchedule.splice(i, 0, { + width: 1, + className: 'bg-transparent', + content: '' + }); + } + else { + showSchedule[i] && (showSchedule[i].height = i); + } + }); + // 最多展示3个 + showSchedule = showSchedule.slice(0, 3); + const scheduleDiv = showSchedule.map((item: any, index: number) => { + const width = item.width || Math.min(moment(item.endTime).diff(moment(item.startTime), 'days') + 1, 7 - moment(item.startTime).weekday()); + return
+
{item.content}
+
; + }); + return this.props.onScheduleClick && this.props.onScheduleClick(scheduleData)}> +
+
{currentDate.date()}
+ {scheduleDiv} + {schedule.length > 3 &&
{schedule.length - 3} {__('more')}
} +
+ + } + + // 正常模式 + const ScheduleIcon = ; + return this.props.onScheduleClick && this.props.onScheduleClick(scheduleData)}> + {currentDate.date()} + {ScheduleIcon} + ; + } + } return {currentDate.date()}; }; diff --git a/src/renderers/Form/InputDate.tsx b/src/renderers/Form/InputDate.tsx index e58bd8079..6cd4e50c9 100644 --- a/src/renderers/Form/InputDate.tsx +++ b/src/renderers/Form/InputDate.tsx @@ -1,10 +1,12 @@ import React from 'react'; import {FormItem, FormControlProps, FormBaseControl} from './Item'; import cx from 'classnames'; -import {filterDate} from '../../utils/tpl-builtin'; +import {filterDate, isPureVariable, resolveVariableAndFilter} from '../../utils/tpl-builtin'; import moment from 'moment'; import 'moment/locale/zh-cn'; import DatePicker from '../../components/DatePicker'; +import {SchemaObject} from '../../Schema'; +import {createObject, anyChanged} from '../../utils/helper'; export interface InputDateBaseControlSchema extends FormBaseControl { /** @@ -85,6 +87,24 @@ export interface DateControlSchema extends InputDateBaseControlSchema { * 限制最大日期 */ maxDate?: string; + + /** + * 日程 + */ + schedules?: Array<{ + startTime: Date, + endTime: Date, + content: any, + className?: string + }> | string; + /** + * 日程显示颜色自定义 + */ + scheduleClassNames?: Array; + /** + * 日程点击展示 + */ + scheduleAction?: SchemaObject; } /** @@ -267,6 +287,12 @@ export interface DateProps extends FormControlProps { interface DateControlState { minDate?: moment.Moment; maxDate?: moment.Moment; + schedules?: Array<{ + startTime: Date, + endTime: Date, + content: any, + className?: string + }>; } export default class DateControl extends React.PureComponent< @@ -304,9 +330,18 @@ export default class DateControl extends React.PureComponent< setPrinstineValue((utc ? moment.utc(date) : date).format(format)); } + let schedulesData = props.schedules; + if (typeof schedulesData === 'string') { + const resolved = resolveVariableAndFilter(schedulesData, data, '| raw'); + if (Array.isArray(resolved)) { + schedulesData = resolved; + } + } + this.state = { minDate: minDate ? filterDate(minDate, data, format) : undefined, - maxDate: maxDate ? filterDate(maxDate, data, format) : undefined + maxDate: maxDate ? filterDate(maxDate, data, format) : undefined, + schedules: schedulesData }; } @@ -334,6 +369,47 @@ export default class DateControl extends React.PureComponent< : undefined }); } + + if (anyChanged(['schedules', 'data'], prevProps, props) + && (typeof props.schedules === 'string' && isPureVariable(props.schedules)) + ) { + const schedulesData = resolveVariableAndFilter(props.schedules, props.data, '| raw'); + const preSchedulesData = resolveVariableAndFilter(prevProps.schedules, prevProps.data, '| raw'); + if (Array.isArray(schedulesData) && preSchedulesData !== schedulesData) { + this.setState({ + schedules: schedulesData + }) + } + } + } + + // 日程点击事件 + onScheduleClick(scheduleData: any) { + const {scheduleAction, onAction, data, translate: __} = this.props; + const defaultscheduleAction = { + actionType: 'dialog', + dialog: { + title: __('Schedule'), + actions: [], + body: { + type: 'table', + columns: [ + { + name: 'time', + label: __('Time') + }, + { + name: 'content', + label: __('Content') + } + ], + data: '${scheduleData}' + } + } + }; + + onAction && onAction(null, scheduleAction || defaultscheduleAction, createObject(data, scheduleData)); + } render() { @@ -348,6 +424,8 @@ export default class DateControl extends React.PureComponent< format, timeFormat, valueFormat, + largeMode, + render, ...rest } = this.props; @@ -363,6 +441,9 @@ export default class DateControl extends React.PureComponent< format={valueFormat || format} {...this.state} classnames={cx} + schedules={this.state.schedules} + largeMode={largeMode} + onScheduleClick={this.onScheduleClick.bind(this)} /> );