环境准备
amis-editor-demo
本次组件开发是基于amis-editor-demo这个项目来开展的,首先需要搭建该工程,项目地址:amis-editor-demo
toast-ui-calendar
因为amis的组件扩展需要React来开发,所以日历组件采用toast-ui-calendar的react版本,在amis-editor-demo项目开发环境下安装:
npm install --save @toast-ui/react-calendar
WorkCalendar渲染组件开发
类和接口设计
在render目录下新建WorkCalendar.tsx文件:
首先设计整个渲染组件的类和接口
@Renderer({
test: /\bwork-calendar$/,
name: 'work-calendar'
})
export default class WorkCalendar extends React.Component<WorkCalendarProps, WorkCalendarState>
@Renderer是amis扩展组件的注解写法,WorkCalendar继承React.Component, 其中泛型接口代表了支持的属性和状态,其定义如下:
/**
* 工作日历状态信息定义
*/
export interface WorkCalendarState {
calendars: WorkCalendarSchema[] //日历分类
schedules: WorkScheduleSchema[] // 任务信息
currentView: string //日历视图
dateRange: string //时间范围
}
/**
* 工作日历属性信息定义
*/
export interface WorkCalendarProps extends RendererProps {
schedulesApi: Api, //任务获取接口
calendarsApi: Api, //日历类型获取接口
name: string //组件名称,方便与其他组件通信
}
WorkCalendarProps中的属性跟后面开发amis Editor配置组件息息相关,是配置信息的主要入口。
WorkCalendarSchema和WorkScheduleSchema分别是日历分类和工作任务属性接口信息,是calendarsApi和schedulesApi接口返回数据的数据结构定义。
/**
* 日历分类属性定义
*/
export interface WorkCalendarSchema {
id: string,
name: string,
color: string,
bgColor: string,
dragBgColor: string,
borderColor: string,
checked?: boolean
}
/**
* 任务信息属性定义
*/
export interface WorkScheduleSchema {
id?: string,
calendarId: string,
title?: string,
body?: string,
start?: string,
end?: string,
isAllDay?: boolean,
category: string,//milesstons,task,allday,time
location?: string,
attendees?: string[],
isPending?: boolean,
isFocused?: boolean,
isVisible?: boolean,
isReadOnly?: boolean,
isPrivate?: boolean,
customStyle?: string,
state?: string //busy,free
}
交互设计与实现
有了上述属性和状态信息,我们接下来可以设计页面交互。本次主要实现以下交互:
日历类型展现 日历类型筛选
首先将页面分成左右两部分,左边是日历类型展现和筛选,右边是任务显示页面
我们将左边的部分设计为CalendarListCheckboxes组件,这个组件接收calendars状态信息进行显示,并通过回调函数处理工作日历选择操作:
创建CalendarListCheckboxes.tsx文件,渲染内容如下:
render() {
const {
calendars
} = this.props;
let body: Array<React.ReactNode> = [];
if (Array.isArray(calendars) && calendars.length) {
body = calendars.map((option, key) => this.renderOption(option, key));
}
return (
<div>
{body && body.length ? (
body
) : (
<div >
无数据
</div>
)}
</div>
)
}
body = calendars.map((option, key) => this.renderOption(option, key));
这个代码是重点,通过遍历calendars进行日历分类展现,根据日历类型是否已选控制span的颜色,在checkbox发生变化以后调用onChange函数控制工作任务的显隐。
renderOption(option: Option, index: number) {
return (
<div key={option.id}>
<label>
<input type="checkbox" className="tui-full-calendar-checkbox-round" id="checklist" name="checklist" value={option.id} checked={option.checked} onChange={this.editChecked.bind(this)}/>
<span style={{borderColor:option.borderColor,backgroundColor:option.checked ? option.borderColor : 'transparent'}}>
</span>
<span>{option.name}</span>
</label>
</div>
);
}
onChange={this.editChecked.bind(this)}, 显式调用bind(this)可以给事件函数传递this对象。
editChecked = (event:any) => {
const checked = event.target.checked;
const id = event.target.value;
const cs = this.props.calendars;
const newcheckedCalendars = cs.map((item)=>{
if(item.id === id){
item.checked = checked;
}
return item;
})
this.setState({checkedCalendars:newcheckedCalendars},()=>{
this.props.handleScheduleVisible();
})
}
this.props.handleScheduleVisible(); 通过props传递父组件的handleScheduleVisible函数,在日历分类状态发生变化设置完后回调处理工作任务的显隐操作。
日、周、月视图切换
回到WorkCalendar程序中,我们来实现 日、周、月视图切换操作。
使用amis的Select组件进行视图渲染:
<Select
options={this.viewOptions}
value={this.state.currentView}
onChange={this.handleSelectView}
clearable={false}
>
</Select>
this.viewOptions:
private viewOptions = [
{
"value": "day",
"label": "日"
},
{
"value": "week",
"label": "周"
},
{
"value": "month",
"label": "月"
},
]
默认值value是当前的日历视图currentView。
onChange={this.handleSelectView},用来实现视图切换:
/**
* 日历视图切换操作
* @param event
*/
handleSelectView = (event: any) => {
this.setState({ currentView: event.value }, () => {
this.setState({ dateRange: this.handleDateRange() })
});
}
这里使用了箭头函数,因此可以不用显式调用bind(this)传递this对象
日历视图切换以后,要根设置当前视图状态,然后设置日期范围状态:this.setState({ dateRange: this.handleDateRange() })。
handleDateRange = () => {
const cal = this.calendarRef.current.getInstance();
const options = cal.getOptions();
const viewName = cal.getViewName();
var html = [];
if (viewName === 'day') {
html.push(this.currentCalendarDate('YYYY.MM.DD', cal));
} else if (viewName === 'month' &&
(!options.month.visibleWeeksCount || options.month.visibleWeeksCount > 4)) {
html.push(this.currentCalendarDate('YYYY.MM', cal));
} else {
html.push(moment(cal.getDateRangeStart().getTime()).format('YYYY.MM.DD'));
html.push(' ~ ');
html.push(moment(cal.getDateRangeEnd().getTime()).format('YYYY.MM.DD'));
}
const queryConfig = {
viewName: viewName,
dataRange: html.join('')
}
this.querySchedulesByDateRange(queryConfig);
return html.join('');
}
const cal = this.calendarRef.current.getInstance();
calendarRef是日历组件的引用,创建方式如下:
calendarRef: any = React.createRef();
创建以后,要在对应的组件上进行引用,如:
<Calendar
ref={this.calendarRef}
height="50"
calendars={this.state.calendars}
disableDblClick={false}
这样就可以获取组件实例,调用其实例方法了,例如:
const cal = this.calendarRef.current.getInstance();
const options = cal.getOptions();
const viewName = cal.getViewName();
日期范围显示信息返回前,还要根据时间范围重新查询工作任务信息:
const queryConfig = {
viewName: viewName,
dataRange: html.join('')
}
this.querySchedulesByDateRange(queryConfig);
this.querySchedulesByDateRange(queryConfig),这里的参数是用来让amis的requestAdaptor来进行获取并传递给api的,包括日历的视图模式和时间范围,具体调用方式采用了amis提供的fetcher方法(this.props.env.fetcher),设置完schedules状态后,还要根据选择的日历分类对工作任务的显隐进行控制,完整方法如下:
private querySchedulesByDateRange(queryConfig: any) {
const schedulesApi = this.props.schedulesApi;
let loadCancel: Function | null = null;
this.props.env.fetcher(schedulesApi, this.props.data, {
autoAppend: false,
cancelExecutor: (executor: Function) => (loadCancel = executor),
queryConfig,
...this.props.config
}).then(result => {
this.setState({ schedules: result.data.schedules }, () => {
this.handleScheduleVisible();
});
});
}
上一页操作 下一页操作 回到今天操作
有了前面获取日历实例的方法,这三个操作实现起来很简单,直接上代码:
/**
* 下一页操作
*/
handleClickNextButton = () => {
const calendarInstance = this.calendarRef.current.getInstance();
calendarInstance.next();
this.setState({ dateRange: this.handleDateRange() })
};
/**
* 上一页操作
*/
handleClickPreButton = () => {
const calendarInstance = this.calendarRef.current.getInstance();
calendarInstance.prev();
this.setState({ dateRange: this.handleDateRange() })
};
/**
* 回到今天操作
*/
handleClickTodayButton = () => {
const calendarInstance = this.calendarRef.current.getInstance();
calendarInstance.today();
this.setState({ dateRange: this.handleDateRange() })
};
WorkCalendar配置组件开发
这部分根据渲染组件需要的配置属性进行开发即可,主要是两个接口的配置,具体代码如下:
import React from 'react';
import {RendererEditor, BasicEditor, getSchemaTpl} from 'amis-editor';
@RendererEditor('work-calendar', {
name: '工作日历',
description: '工作日历',
// docLink: '/docs/renderers/Nav',
type: 'work-calendar',
previewSchema: {
// 用来生成预览图的
type: 'work-calendar'
},
scaffold: {
// 拖入组件里面时的初始数据
type: 'work-calendar'
}
})
export default class WorkCalendarEditor extends BasicEditor {
tipName = '工作日历';
settingsSchema = {
title: '工作日历配置',
body:[
{
type: 'tabs',
tabsMode: 'line',
className: 'm-t-n-xs',
contentClassName: 'no-border p-l-none p-r-none',
tabs: [
{
title: '常规',
controls: [
getSchemaTpl("source",{label:"日历类型获取接口",name:"calendarsApi"}),
getSchemaTpl("source",{label:"工作任务获取接口",name:"schedulesApi"})
]
},
{
title: '其他',
controls: [
getSchemaTpl("name")
]
}
]
}
]
};
}
getSchemaTpl(“source”),这个方式可以快速完成接口配置模板获取。
getSchemaTpl(“name”) ,可以获取组件名字配置模板,
配置效果如下:
一个示例amis配置信息如下:
{
"type": "page",
"title": "Hello world",
"body": [
{
"type": "tpl",
"tpl": "初始页面",
"inline": false
},
{
"type": "work-calendar",
"calendarsApi": {
"method": "get",
"url": "http://api.k780.com/?app=weather.today&weaId=1&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
"adaptor": "payload.calendars = [\n {\n id: \"1\",\n name: \"调令\",\n color: \"#ffffff\",\n bgColor: \"#9e5fff\",\n dragBgColor: \"#9e5fff\",\n borderColor: \"#9e5fff\"\n },\n {\n id: \"2\",\n name: \"督办\",\n color: \"#ffffff\",\n bgColor: \"#00a9ff\",\n dragBgColor: \"#00a9ff\",\n borderColor: \"#00a9ff\"\n }\n];\nreturn payload;",
"requestAdaptor": "debugger;"
},
"schedulesApi": {
"method": "get",
"url": "http://api.k780.com/?app=weather.today&weaId=1&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
"adaptor": "const start = new Date();\nconst end = new Date(new Date().setMinutes(start.getMinutes() + 30));\npayload.schedules = [\n {\n calendarId: \"1\",\n category: \"allday\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"1\",\n category: \"allday\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"1\",\n category: \"allday\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"1\",\n category: \"allday\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"1\",\n category: \"time\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n }, {\n calendarId: \"1\",\n category: \"time\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"1\",\n category: \"time\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"1\",\n category: \"time\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"1\",\n category: \"time\",\n isVisible: true,\n title: \"Study\",\n id: \"1\",\n body: \"Test\",\n start,\n end\n },\n {\n calendarId: \"2\",\n category: \"time\",\n isVisible: true,\n title: \"Meeting\",\n id: \"2\",\n body: \"Description\",\n start: new Date(new Date().setHours(start.getHours() + 1)),\n end: new Date(new Date().setHours(start.getHours() + 2))\n }\n];\nreturn payload;",
"requestAdaptor": "debugger;"
}
}
]
}
总结
大致开发过程已经完成,这个版本的配置信息还不够丰富,可以根据toastui-calendar的api,添加更多的配置属性,未来还需要增加以下特性:
- 工作任务添加
- 工作日历类型添加
- 工作任务检索
- 工作任务链接跳转