| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- import React, { useLayoutEffect, useRef, useState } from 'react';
- import { isVoidField, Field } from '@formily/core';
- import { useField, observer } from '@formily/react';
- import { Popover } from 'antd';
- import { EditOutlined, CloseOutlined, MessageOutlined } from '@ant-design/icons';
- import { BaseItem, IFormItemProps } from '@formily/antd/lib/form-item';
- import { PopoverProps } from 'antd/lib/popover';
- import { useClickAway, usePrefixCls } from '@formily/antd/lib/__builtins__';
- import cls from 'classnames';
- import { get } from 'lodash';
- import { Ellipsis } from '@/components';
- /**
- * 默认Inline展示
- */
- type IPopoverProps = PopoverProps;
- type ComposedEditable = React.FC<React.PropsWithChildren<IFormItemProps>> & {
- Popover?: React.FC<React.PropsWithChildren<IPopoverProps & { title?: React.ReactNode }>>;
- };
- const useParentPattern = () => {
- const field = useField<Field>();
- return field?.parent?.pattern || field?.form?.pattern;
- };
- const useEditable = (): [boolean, (payload: boolean) => void] => {
- const pattern = useParentPattern();
- const field = useField<Field>();
- useLayoutEffect(() => {
- if (pattern === 'editable') {
- return field.setPattern('readPretty');
- }
- }, [pattern]);
- return [
- field.pattern === 'editable',
- (payload: boolean) => {
- if (pattern !== 'editable') return;
- field.setPattern(payload ? 'editable' : 'readPretty');
- },
- ];
- };
- const useFormItemProps = (): IFormItemProps => {
- const field = useField();
- if (isVoidField(field)) return {};
- if (!field) return {};
- const takeMessage = () => {
- if (field.selfErrors.length) return field.selfErrors;
- if (field.selfWarnings.length) return field.selfWarnings;
- if (field.selfSuccesses.length) return field.selfSuccesses;
- };
- return {
- feedbackStatus: field.validateStatus === 'validating' ? 'pending' : field.validateStatus,
- feedbackText: takeMessage(),
- extra: field.description,
- };
- };
- export const Editable: ComposedEditable = observer((props) => {
- const [editable, setEditable] = useEditable();
- const pattern = useParentPattern();
- const itemProps = useFormItemProps();
- const field = useField<Field>();
- const basePrefixCls = usePrefixCls();
- const prefixCls = usePrefixCls('formily-editable');
- const ref = useRef<boolean>();
- const innerRef = useRef<HTMLDivElement | any>();
- const recover = () => {
- if (ref.current && !field?.errors?.length) {
- setEditable(false);
- }
- };
- const renderEditHelper = () => {
- if (editable) return;
- return (
- <BaseItem {...props} {...itemProps}>
- {pattern === 'editable' && <EditOutlined className={`${prefixCls}-edit-btn`} />}
- {pattern !== 'editable' && <MessageOutlined className={`${prefixCls}-edit-btn`} />}
- </BaseItem>
- );
- };
- const renderCloseHelper = () => {
- if (!editable) return;
- return (
- <BaseItem {...props}>
- <CloseOutlined className={`${prefixCls}-close-btn`} />
- </BaseItem>
- );
- };
- useClickAway((e) => {
- const target = e.target as HTMLElement;
- if (target?.closest(`.${basePrefixCls}-select-dropdown`)) return;
- if (target?.closest(`.${basePrefixCls}-picker-dropdown`)) return;
- if (target?.closest(`.${basePrefixCls}-cascader-menus`)) return;
- recover();
- }, innerRef);
- const onClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
- const target = e.target as HTMLElement;
- const close = innerRef.current?.querySelector(`.${prefixCls}-close-btn`);
- if (target?.contains(close) || close?.contains(target)) {
- recover();
- } else if (!ref.current) {
- setTimeout(() => {
- setEditable(true);
- setTimeout(() => {
- innerRef.current?.querySelector('input')?.focus();
- });
- });
- }
- };
- ref.current = editable;
- return (
- <div className={prefixCls} ref={innerRef} onClick={onClick}>
- <div className={`${prefixCls}-content`}>
- <BaseItem {...props} {...itemProps}>
- {props.children}
- </BaseItem>
- {renderEditHelper()}
- {renderCloseHelper()}
- </div>
- </div>
- );
- });
- Editable.Popover = observer((props) => {
- const field = useField<Field>();
- // console.log(field.path.segments)
- // console.log(field.form.query(field.path).pattern.segments)
- const pattern = useParentPattern();
- let title = props.title || field.title;
- const [visible, setVisible] = useState(false);
- const prefixCls = usePrefixCls('formily-editable');
- const closePopover = async () => {
- try {
- await field.form.validate(`${field.address}.*`);
- } finally {
- const errors = field.form.queryFeedbacks({
- type: 'error',
- address: `${field.address}.*`,
- });
- if (errors?.length) return;
- setVisible(false);
- }
- };
- const openPopover = () => {
- setVisible(true);
- };
- if ((field.title === '配置参数' || field.title === '指标数据') && !props.title) {
- const filterKeys = ['config', 'edit'];
- const path = field.path.segments.filter((key: any) => !filterKeys.includes(key));
- // console.log('EditTable', path, field.form.values);
- const value = get(field.form.values, path)?.name;
- title = value || '配置参数';
- }
- const headTitle = () => {
- return (
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
- <div style={{ width: 150 }}>
- <Ellipsis title={props.title || field.title} />
- </div>
- <CloseOutlined
- onClick={() => {
- setVisible(false);
- }}
- />
- </div>
- );
- };
- return (
- <Popover
- {...props}
- title={headTitle()}
- visible={visible}
- className={cls(prefixCls, props.className)}
- content={props.children}
- trigger="click"
- destroyTooltipOnHide
- onVisibleChange={(param) => {
- if (param) {
- openPopover();
- } else {
- closePopover();
- }
- }}
- >
- <div>
- <BaseItem className={`${prefixCls}-trigger`}>
- <div className={`${prefixCls}-content`}>
- <span className={`${prefixCls}-preview`}>{title}</span>
- {pattern === 'editable' && <EditOutlined className={`${prefixCls}-edit-btn`} />}
- {pattern !== 'editable' && <MessageOutlined className={`${prefixCls}-edit-btn`} />}
- </div>
- </BaseItem>
- </div>
- </Popover>
- );
- });
- export default Editable;
|