import React from 'react'; import {findDOMNode} from 'react-dom'; import {Renderer, RendererProps} from '../factory'; import {SchemaNode, Schema, Action} from '../types'; import {filter, evalExpression} from '../utils/tpl'; import cx from 'classnames'; import Checkbox from '../components/Checkbox'; import {IItem} from '../store/list'; import {padArr, isVisible, isDisabled, noop} from '../utils/helper'; import {resolveVariable} from '../utils/tpl-builtin'; import QuickEdit, {SchemaQuickEdit} from './QuickEdit'; import PopOver, {SchemaPopOver} from './PopOver'; import {TableCell} from './Table'; import Copyable, {SchemaCopyable} from './Copyable'; import {Icon} from '../components/icons'; import { BaseSchema, SchemaClassName, SchemaCollection, SchemaExpression, SchemaObject, SchemaTpl, SchemaUrlPath } from '../Schema'; import {ActionSchema} from './Action'; export type CardBodyField = SchemaObject & { /** * 列标题 */ label: string; /** * label 类名 */ labelClassName?: SchemaClassName; /** * 绑定字段名 */ name?: string; /** * 配置查看详情功能 */ popOver?: SchemaPopOver; /** * 配置快速编辑功能 */ quickEdit?: SchemaQuickEdit; /** * 配置点击复制功能 */ copyable?: SchemaCopyable; }; /** * Card 卡片渲染器。 * 文档:https://baidu.gitee.io/amis/docs/components/card */ export interface CardSchema extends BaseSchema { /** * 指定为 card 类型 */ type: 'card'; /** * 头部配置 */ header?: { className?: SchemaClassName; /** * 标题 */ title?: SchemaTpl; titleClassName?: string; /** * 副标题 */ subTitle?: SchemaTpl; subTitleClassName?: SchemaClassName; subTitlePlaceholder?: string; /** * 描述 */ description?: SchemaTpl; /** * 描述占位内容 */ descriptionPlaceholder?: string; /** * 描述占位类名 */ descriptionClassName?: string; /** * @deprecated 建议用 description */ desc?: SchemaTpl; /** * @deprecated 建议用 descriptionPlaceholder */ descPlaceholder?: SchemaTpl; /** * @deprecated 建议用 descriptionClassName */ descClassName?: SchemaClassName; /** * 图片地址 */ avatar?: SchemaUrlPath; avatarText?: SchemaTpl; avatarTextClassName?: SchemaClassName; /** * 图片包括层类名 */ avatarClassName?: SchemaClassName; /** * 图片类名。 */ imageClassName?: SchemaClassName; /** * 是否点亮 */ highlight?: SchemaExpression; highlightClassName?: SchemaClassName; }; /** * 内容区域 */ body?: Array; /** * 底部按钮集合。 */ actions?: Array; } export interface CardProps extends RendererProps, Omit { onCheck: (item: IItem) => void; itemIndex?: number; multiple?: boolean; highlightClassName?: string; hideCheckToggler?: boolean; item: IItem; checkOnItemClick?: boolean; } export class Card extends React.Component { static defaultProps: Partial = { className: '', avatarClassName: '', bodyClassName: '', actionsCount: 4, titleClassName: '', highlightClassName: '', subTitleClassName: '', descClassName: '' }; static propsList: Array = [ 'avatarClassName', 'bodyClassName', 'actionsCount', 'titleClassName', 'highlightClassName', 'subTitleClassName', 'descClassName', 'hideCheckToggler' ]; constructor(props: CardProps) { super(props); this.getPopOverContainer = this.getPopOverContainer.bind(this); this.itemRender = this.itemRender.bind(this); this.handleAction = this.handleAction.bind(this); this.handleQuickChange = this.handleQuickChange.bind(this); this.handleClick = this.handleClick.bind(this); this.handleCheck = this.handleCheck.bind(this); } handleClick(e: React.MouseEvent) { const target: HTMLElement = e.target as HTMLElement; const ns = this.props.classPrefix; let formItem; if ( !e.currentTarget.contains(target) || ~['INPUT', 'TEXTAREA'].indexOf(target.tagName) || ((formItem = target.closest(`button, a, .${ns}Form-item`)) && e.currentTarget.contains(formItem)) ) { return; } const item = this.props.item; this.props.onCheck && this.props.onCheck(item); } handleCheck() { const item = this.props.item; this.props.onCheck && this.props.onCheck(item); } handleAction(e: React.UIEvent, action: Action, ctx: object) { const {onAction, item} = this.props; onAction && onAction(e, action, ctx || item.data); } handleQuickChange( values: object, saveImmediately?: boolean, savePristine?: boolean, resetOnFailed?: boolean ) { const {onQuickChange, item} = this.props; onQuickChange && onQuickChange(item, values, saveImmediately, savePristine, resetOnFailed); } getPopOverContainer() { return findDOMNode(this); } renderToolbar() { const { dragging, selectable, checkable, selected, onSelect, checkOnItemClick, multiple, hideCheckToggler, classnames: cx, classPrefix: ns } = this.props; if (dragging) { return (
); } else if (selectable && !hideCheckToggler) { return (
); } return null; } renderActions() { const { actions, render, dragging, actionsCount, data, classnames: cx } = this.props; if (Array.isArray(actions)) { const group = padArr( actions.filter(item => isVisible(item, data)), actionsCount ); return group.map((actions, groupIndex) => (
{actions.map((action, index) => { const size = action.size || 'sm'; return render( `action/${index}`, { level: 'link', type: 'button', ...action, size }, { isMenuItem: true, key: index, index, disabled: dragging || isDisabled(action, data), className: cx( 'Card-action', action.className || `${size ? `Card-action--${size}` : ''}` ), componentClass: 'a', onAction: this.handleAction } ); })}
)); } return null; } renderChild( node: SchemaNode, region: string = 'body', key: any = 0 ): React.ReactNode { const {render} = this.props; if (typeof node === 'string' || typeof node === 'number') { return render(region, node, {key}) as JSX.Element; } const childNode: Schema = node as Schema; if (childNode.type === 'hbox' || childNode.type === 'grid') { return render(region, node, { key, itemRender: this.itemRender }) as JSX.Element; } return this.renderFeild(region, childNode, key, this.props); } itemRender(field: any, index: number, props: any) { return this.renderFeild(`column/${index}`, field, index, props); } renderFeild(region: string, field: any, key: any, props: any) { const {render, classnames: cx, itemIndex} = props; const data = this.props.data; if (!isVisible(field, data)) { return; } const $$id = field.$$id ? `${field.$$id}-field` : ''; return (
{field && field.label ? ( ) : null} { render( region, { ...field, field: field, $$id, type: 'card-item-field' }, { className: cx('Card-fieldValue', field.className), rowIndex: itemIndex, colIndex: key, value: field.name ? resolveVariable(field.name, data) : undefined, popOverContainer: this.getPopOverContainer, onAction: this.handleAction, onQuickChange: this.handleQuickChange } ) as JSX.Element }
); } renderBody() { const {body} = this.props; if (!body) { return null; } if (Array.isArray(body)) { return body.map((child, index) => this.renderChild(child, `body/${index}`, index) ); } return this.renderChild(body, 'body'); } render() { const { className, data, header, render, bodyClassName, highlightClassName, titleClassName, subTitleClassName, descClassName, checkOnItemClick, avatarClassName, checkable, classnames: cx, classPrefix: ns, imageClassName, avatarTextClassName } = this.props; let heading = null; if (header) { const { highlight: highlightTpl, avatar: avatarTpl, avatarText: avatarTextTpl, title: titleTpl, subTitle: subTitleTpl, subTitlePlaceholder, desc: descTpl } = header; const descPlaceholder = header.descriptionPlaceholder || header.descPlaceholder; const highlight = !!evalExpression(highlightTpl!, data as object); const avatar = filter(avatarTpl, data, '| raw'); const avatarText = filter(avatarTextTpl, data); const title = filter(titleTpl, data); const subTitle = filter(subTitleTpl, data); const desc = filter(header.description || descTpl, data); heading = (
{avatar ? ( ) : avatarText ? ( {avatarText} ) : null}
{highlight ? ( ) : null} {title ? (
{render('title', title)}
) : null} {subTitle || subTitlePlaceholder ? (
{render('sub-title', subTitle || subTitlePlaceholder!, { className: cx(!subTitle ? 'Card-placeholder' : undefined) })}
) : null} {desc || descPlaceholder ? (
{render('desc', desc || descPlaceholder!, { className: !desc ? 'text-muted' : undefined })}
) : null}
); } const body = this.renderBody(); return (
{this.renderToolbar()} {heading} {body ? (
{body}
) : null} {this.renderActions()}
); } } @Renderer({ type: 'card', name: 'card' }) export class CardRenderer extends Card { static propsList = ['multiple', ...Card.propsList]; } @Renderer({ type: 'card-item-field', name: 'card-item' }) @QuickEdit() @PopOver() @Copyable() export class CardItemFieldRenderer extends TableCell { static defaultProps = { ...TableCell.defaultProps, wrapperComponent: 'div' }; static propsList = [ 'quickEdit', 'quickEditEnabledOn', 'popOver', 'copyable', 'inline', ...TableCell.propsList ]; render() { let { type, className, render, style, wrapperComponent: Component, labelClassName, value, data, children, width, innerClassName, label, tabIndex, onKeyUp, field, ...rest } = this.props; const schema = { ...field, className: innerClassName, type: (field && field.type) || 'plain' }; let body = children ? children : render('field', schema, { ...rest, value, data }); if (width) { style = style || {}; style.width = style.width || width; body = (
{body}
); } if (!Component) { return body as JSX.Element; } return ( {body} ); } }