From 5e0277d4515f92fc3903f7d2adaf0afa59582a80 Mon Sep 17 00:00:00 2001 From: chenos Date: Wed, 2 Jun 2021 11:09:01 +0800 Subject: [PATCH] updates --- packages/client/package.json | 4 + packages/client/src/blocks/block/index.md | 34 -- packages/client/src/blocks/block/index.tsx | 94 ----- packages/client/src/blocks/block/style.less | 20 -- packages/client/src/blocks/calendar/index.md | 7 + packages/client/src/blocks/chart/index.md | 6 + .../client/src/blocks/form/demos/demo1.tsx | 32 +- packages/client/src/blocks/grid/Drop.tsx | 150 ++++++++ packages/client/src/blocks/grid/Grid.tsx | 319 +++++++++++++++++ packages/client/src/blocks/grid/demo.less | 15 - .../client/src/blocks/grid/demos/demo2.tsx | 133 +++++++ .../client/src/blocks/grid/demos/demo3.tsx | 200 +++++++++++ packages/client/src/blocks/grid/index.md | 100 +++--- packages/client/src/blocks/grid/index.tsx | 49 --- packages/client/src/blocks/grid/style.less | 143 ++++++++ packages/client/src/blocks/grid/utils.tsx | 94 +++++ packages/client/src/blocks/index.tsx | 1 - packages/client/src/blocks/kanban/index.md | 6 + packages/client/src/blocks/table/index.md | 7 +- .../src/components/MenuEditor/index.tsx | 25 +- .../src/components/MenuEditor/style.less | 14 + .../src/components/RouteSwitch/index.tsx | 1 - packages/client/src/fields/block/index.tsx | 20 ++ packages/client/src/fields/cascader/index.md | 14 +- packages/client/src/fields/checkbox/index.tsx | 2 +- .../client/src/fields/date-picker/index.md | 14 + packages/client/src/fields/index.tsx | 338 +++++++++++++++++- packages/client/src/fields/input/index.md | 13 +- .../src/templates/admin-layout/index.tsx | 2 +- .../src/templates/page-template/index.tsx | 8 +- 30 files changed, 1553 insertions(+), 312 deletions(-) delete mode 100644 packages/client/src/blocks/block/index.md delete mode 100644 packages/client/src/blocks/block/index.tsx delete mode 100644 packages/client/src/blocks/block/style.less create mode 100644 packages/client/src/blocks/grid/Drop.tsx create mode 100644 packages/client/src/blocks/grid/Grid.tsx delete mode 100644 packages/client/src/blocks/grid/demo.less create mode 100644 packages/client/src/blocks/grid/demos/demo2.tsx create mode 100644 packages/client/src/blocks/grid/demos/demo3.tsx create mode 100644 packages/client/src/blocks/grid/style.less create mode 100644 packages/client/src/blocks/grid/utils.tsx create mode 100644 packages/client/src/fields/block/index.tsx diff --git a/packages/client/package.json b/packages/client/package.json index 98e605d66..bb6699f0d 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -24,6 +24,10 @@ "ahooks": "^2.10.2", "axios": "^0.21.1", "lodash": "^4.17.21", + "react-dnd": "^14.0.2", + "react-dnd-html5-backend": "^14.0.0", + "react-dnd-preview": "^6.0.2", + "react-dnd-touch-backend": "^14.0.0", "react-helmet": "^6.1.0", "react-hooks-global-state": "^1.0.1", "umi-request": "^1.3.5" diff --git a/packages/client/src/blocks/block/index.md b/packages/client/src/blocks/block/index.md deleted file mode 100644 index 151e0e057..000000000 --- a/packages/client/src/blocks/block/index.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Block - 区块 -nav: - title: 组件 - path: /client -group: - order: 2 - title: Blocks - path: /client/blocks ---- - -# Block - 区块 - -```tsx -import React from 'react'; -import BlockEditor from './'; - -const items = [ - { - type: 'page', - name: 'block1', - }, - { - type: 'page', - name: 'block2', - } -] - -export default () => { - return ( - - ) -} -``` diff --git a/packages/client/src/blocks/block/index.tsx b/packages/client/src/blocks/block/index.tsx deleted file mode 100644 index d281e1b5f..000000000 --- a/packages/client/src/blocks/block/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import { Dropdown, Menu, Card, Button, Popover } from 'antd'; -import { useDynamicList } from 'ahooks'; -import { MenuOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'; -import './style.less'; -import { FormDialog, FormLayout } from '@formily/antd'; -import { SchemaField } from '../../fields'; - -export function AddNewAction(props) { - const { insert, children } = props; - const menu = ( - - { - FormDialog(`新建区块`, () => { - return ( - - - - - - ); - }) - .open({}) - .then(insert); - }} - > - 新建区块 - - - ); - return {children || }; -} - -export function SettingAction(props) { - const { remove } = props; - const menu = ( - - remove()} key="delete"> - 删除区块 - - - ); - return ( - - - - ); -} - -export function BlockItem(props) { - const { remove, insert, replace } = props; - return ( -
-
- - -
-
- - 这是内容区 - -
-
- ); -} - -export default (props) => { - const { list, push, remove, replace, insert } = useDynamicList(props.items); - return ( -
- {list.map((item, index) => ( - insert(index + 1, data)} - replace={(data) => replace(index, data)} - remove={() => remove(index)} - key={index} - schema={item} - /> - ))} -
- push(data)}> - - -
- ); -}; diff --git a/packages/client/src/blocks/block/style.less b/packages/client/src/blocks/block/style.less deleted file mode 100644 index 7ab49e1d6..000000000 --- a/packages/client/src/blocks/block/style.less +++ /dev/null @@ -1,20 +0,0 @@ -.block-list { - background: #f0f2f5; - padding: 0 24px 24px; -} -.block-item { - position: relative; - padding-top: 24px; - .block-item-actions { - display: none; - position: absolute; - top: 0; - right: 0; - z-index: 1; - } - &:hover { - .block-item-actions { - display: inline-block; - } - } -} \ No newline at end of file diff --git a/packages/client/src/blocks/calendar/index.md b/packages/client/src/blocks/calendar/index.md index 29935be6c..d244d9ad1 100644 --- a/packages/client/src/blocks/calendar/index.md +++ b/packages/client/src/blocks/calendar/index.md @@ -10,3 +10,10 @@ group: --- # Calendar - 日历 + +## 代码演示 + +### 基本使用 + +### 设计器模式 + diff --git a/packages/client/src/blocks/chart/index.md b/packages/client/src/blocks/chart/index.md index e1bd37d1c..71ddeeaf2 100644 --- a/packages/client/src/blocks/chart/index.md +++ b/packages/client/src/blocks/chart/index.md @@ -11,3 +11,9 @@ group: # Chart - 图表 +## 代码演示 + +### 基本使用 + +### 设计器模式 + diff --git a/packages/client/src/blocks/form/demos/demo1.tsx b/packages/client/src/blocks/form/demos/demo1.tsx index 1b3ace2de..b1c7c5cd3 100644 --- a/packages/client/src/blocks/form/demos/demo1.tsx +++ b/packages/client/src/blocks/form/demos/demo1.tsx @@ -4,25 +4,27 @@ import React from 'react'; import { FormBlock } from '@nocobase/client'; +const fields = [ + { + interface: 'string', + type: 'string', + title: `单行文本`, + name: 'username', + required: true, + default: 'abcdefg', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: 'please enter', + }, + }, +]; + export default () => { return ( ({ + accept, + drop: () => ({ + gridType: 'first-row', + schema, + segments: field.address.segments, + }), + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + }), + [schema], + ); + + const active = canDrop && isOver; + + return ( +
+ ); +} + +export function DropRow() { + const { schema } = useSchema(); + const field = useField(); + const accept = useContext(AcceptContext); + const [{ canDrop, isOver }, drop] = useDrop( + () => ({ + accept, + drop: () => ({ + gridType: 'row', + schema, + segments: field.address.segments, + }), + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + }), + [schema], + ); + + const active = canDrop && isOver; + + return
; +} + +export function DropColumn() { + const field = useField(); + const { schema } = useSchema(); + const accept = useContext(AcceptContext); + const [{ canDrop, isOver }, drop] = useDrop( + () => ({ + accept, + drop: () => ({ + gridType: 'column', + schema, + segments: field.address.segments, + }), + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + }), + [schema], + ); + + const active = canDrop && isOver; + + return ( +
+ ); +} + +export function DropLastColumn() { + const field = useField(); + const { schema } = useSchema(); + const accept = useContext(AcceptContext); + const [{ canDrop, isOver }, drop] = useDrop( + () => ({ + accept, + drop: () => ({ + gridType: 'last-column', + schema, + segments: field.address.segments, + }), + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + }), + [schema], + ); + + const active = canDrop && isOver; + + return ( +
+ ); +} + +export function DropBlock({ canDrop }) { + const { schema } = useSchema(); + const accept = useContext(AcceptContext); + const [{ isOver }, drop] = useDrop( + () => ({ + accept, + drop: () => ({ schema }), + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + canDrop: () => canDrop, + }), + [canDrop], + ); + + console.log({ canDrop }); + + const active = canDrop && isOver; + + return ( +
+ ); +} + +export default { + DropRow, + DropColumn, + DropLastColumn, + DropBlock, +}; diff --git a/packages/client/src/blocks/grid/Grid.tsx b/packages/client/src/blocks/grid/Grid.tsx new file mode 100644 index 000000000..84cc50f76 --- /dev/null +++ b/packages/client/src/blocks/grid/Grid.tsx @@ -0,0 +1,319 @@ +import React, { createContext, useContext, useState } from 'react'; +import { Row, Col, Dropdown, Menu } from 'antd'; +import { DndProvider, useDrag, useDrop } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import { + DropFirstRow, + DropRow, + DropColumn, + DropBlock, + DropLastColumn, +} from './Drop'; +import { + CloseOutlined, + DeleteOutlined, + MenuOutlined, + PlusOutlined, + ArrowDownOutlined, + ArrowUpOutlined, +} from '@ant-design/icons'; +import { + getFullPaths, + SchemaDesignerContext, + useSchemaQuery, + useSchema, +} from '../../fields'; +import { TouchBackend } from 'react-dnd-touch-backend'; +import { usePreview } from 'react-dnd-preview'; +import classNames from 'classnames'; +import './style.less'; +import { useField, useFieldSchema } from '@formily/react'; + +const Preview = () => { + const accept = useContext(AcceptContext); + const { item, style, display, itemType } = usePreview(); + if (itemType !== accept) { + return null; + } + if (!display) { + return null; + } + if (!item.ref) { + return null; + } + if (!item.ref.current) { + return null; + } + const el = item.ref.current as HTMLDivElement; + console.log({ itemType }); + return ( +
+
+
+ ); +}; + +export interface GridPorps { + children?: React.ReactNode; +} + +export interface GridRowPorps { + children?: React.ReactNode; + rowOrder?: number; +} + +export interface GridColumnPorps { + children?: React.ReactNode; + span?: any; +} + +export interface GridBlockProps { + children?: React.ReactNode; + lastComponentType?: string; +} + +export type GridComponent = React.FC & { + Row?: React.FC; + Column?: React.FC; + Block?: React.FC; +}; + +export const AcceptContext = createContext(null); + +export const Grid: GridComponent = (props) => { + const { children } = props; + const schema = useFieldSchema(); + const field = useField(); + console.log({ accept: schema.name, schema: schema.toJSON() }); + return ( +
+ + + {children} + + + +
+ ); +}; + +Grid.Row = (props) => { + const { children, rowOrder } = props; + const { schema } = useSchema(); + const field = useField(); + const accept = useContext(AcceptContext); + console.log({ accept }); + const [{ canDrop, isOverCurrent }, drop] = useDrop(() => ({ + accept, + drop: (item, monitor) => { + const didDrop = monitor.didDrop(); + if (didDrop) { + return; + } + return { gridType: 'row', schema, segments: field.address.segments }; + }, + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + isOverCurrent: monitor.isOver({ shallow: true }), + }), + })); + + const active = canDrop && isOverCurrent; + + return ( +
+ {rowOrder === 0 && } + {children} + + {/* */} +
+ ); +}; + +Grid.Column = (props) => { + const { children, span } = props; + return ( + + + {children} + + ); +}; + +interface DropResult { + [key: string]: any; +} + +Grid.Block = (props) => { + const { children, lastComponentType } = props; + const { schema, refresh } = useSchema(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const accept = useContext(AcceptContext); + const context = useContext(SchemaDesignerContext); + const { + insertAfter, + insertAfterWithAddRow, + insertBeforeWithAddRow, + insertBeforeWithAddColumn, + appendToRowWithAddColumn, + } = useSchemaQuery(); + const ref = React.useRef(); + console.log({ accept }); + const [{ opacity, isDragging }, drag, preview] = useDrag( + () => ({ + type: accept, + item: { + ref, + preview, + schema, + }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + opacity: monitor.isDragging() ? 0.9 : 1, + }), + end: (item, monitor) => { + const dropResult = monitor.getDropResult(); + if (item && dropResult) { + if (dropResult.gridType === 'block') { + insertAfter(field.address.segments, dropResult.segments); + } else if (dropResult.gridType === 'row') { + insertAfterWithAddRow(field.address.segments, dropResult.segments); + } else if (dropResult.gridType === 'column') { + insertBeforeWithAddColumn( + field.address.segments, + dropResult.segments, + ); + } else if (dropResult.gridType === 'last-column') { + appendToRowWithAddColumn( + field.address.segments, + dropResult.segments, + ); + } else if (dropResult.gridType === 'first-row') { + insertBeforeWithAddRow( + field.address.segments, + dropResult.segments, + ); + } + refresh(); + } + }, + }), + [field, schema], + ); + + const segments = field.address.segments; + + const [{ canDrop, isOver }, drop] = useDrop( + () => ({ + accept, + drop: () => { + console.log('source.segments', segments); + return { gridType: 'block', segments, schema }; + }, + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + // hover: (item, monitor) => { + // console.log(monitor.getSourceClientOffset()); + // }, + canDrop: () => !isDragging, + }), + [isDragging, schema], + ); + + const active = canDrop && isOver; + + drop(ref); + + console.log({ isDragging }); + + return ( +
+ + {children} + {/* */} +
+ ); +}; + +const ActionBar = ({ dragRef }) => { + const { addBlock, removeBlock } = useSchemaQuery(); + const [active, setActive] = useState(false); + return ( +
+ + { + addBlock({}, true); + setActive(false); + }} + icon={} + > + 在上方插入区块 + + { + addBlock({}); + setActive(false); + }} + icon={} + > + 在下方插入区块 + + + { + removeBlock(); + setActive(false); + }} + icon={} + > + 删除区块 + + + } + > + + +
+ ); +}; + +export default Grid; diff --git a/packages/client/src/blocks/grid/demo.less b/packages/client/src/blocks/grid/demo.less deleted file mode 100644 index 604a1c107..000000000 --- a/packages/client/src/blocks/grid/demo.less +++ /dev/null @@ -1,15 +0,0 @@ -#components-grid-demo [class~='ant-row'] { - .ant-col { - // background: rgba(128, 128, 128, 0.08); - > div { - padding: 16px; - border-radius: 0; - color: #fff; - margin-bottom: 8px; - margin-top: 8px; - min-height: 30px; - text-align: center; - background: rgba(0,146,255,.75); - } - } -} \ No newline at end of file diff --git a/packages/client/src/blocks/grid/demos/demo2.tsx b/packages/client/src/blocks/grid/demos/demo2.tsx new file mode 100644 index 000000000..672d81bd0 --- /dev/null +++ b/packages/client/src/blocks/grid/demos/demo2.tsx @@ -0,0 +1,133 @@ +import React, { useMemo } from 'react'; +import { FormProvider, FormConsumer, useField, useFieldSchema } from '@formily/react'; +import { createForm } from '@formily/core'; +import { + SchemaFieldWithDesigner, + registerFieldComponents, + useSchema, +} from '../../../fields'; +import { grid, row, column, block } from '../utils'; +import { blocks2properties } from '../utils'; + +function Designer(props) { + const form = useMemo(() => createForm({}), []); + const { schema } = props; + return ( +
+ + + {/* + {(form) => { + return
{JSON.stringify(form.values, null, 2)}
; + }} +
*/} +
+
+ ); +} + +function Hello(props) { + const schema = useFieldSchema(); + return ( +
+ Hello {schema.title} +
+ ); +} + +registerFieldComponents({ Hello }); +const blocks = [ + { + type: 'string', + title: `Block 1`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 1, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 2`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 2, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 3`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 2, + columnOrder: 1, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 4`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 1, + blockOrder: 2, + }, + { + type: 'string', + title: `Block 5`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 3, + columnOrder: 1, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 6`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 3, + columnOrder: 2, + blockOrder: 1, + }, +]; +const schema = blocks2properties(blocks); + +export default () => { + console.log({schema}); + return ( +
+ + {/*
{JSON.stringify(schema, null, 2)}
*/} +
+ ); +}; diff --git a/packages/client/src/blocks/grid/demos/demo3.tsx b/packages/client/src/blocks/grid/demos/demo3.tsx new file mode 100644 index 000000000..f0ca50fdb --- /dev/null +++ b/packages/client/src/blocks/grid/demos/demo3.tsx @@ -0,0 +1,200 @@ +import React, { useMemo } from 'react'; +import { FormProvider, FormConsumer, useField, useFieldSchema } from '@formily/react'; +import { createForm } from '@formily/core'; +import { + SchemaFieldWithDesigner, + registerFieldComponents, +} from '../../../fields'; +import { grid, row, column, block } from '../utils'; +import { blocks2properties } from '../utils'; +import { Card } from 'antd'; + +function Designer(props) { + const form = useMemo(() => createForm({}), []); + const { schema } = props; + return ( +
+ + + {/* + {(form) => { + return
{JSON.stringify(form.values, null, 2)}
; + }} +
*/} +
+
+ ); +} + +function Hello(props) { + const schema = useFieldSchema(); + return ( +
+ Hello {schema.title} +
+ ); +} + +registerFieldComponents({ Hello, Designer, Card }); + +const nested = blocks2properties([ + { + type: 'string', + title: `Nested Block 1`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 1, + blockOrder: 1, + }, + { + type: 'string', + title: `Nested Block 2`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 2, + blockOrder: 1, + }, + { + type: 'string', + title: `Nested Block 3`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 2, + columnOrder: 1, + blockOrder: 1, + }, +]); + +const blocks = [ + { + type: 'string', + title: `Block 1`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 1, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 2`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 2, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 3`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 2, + columnOrder: 1, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 4`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 1, + columnOrder: 1, + blockOrder: 2, + }, + { + type: 'string', + title: `Block 5`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 3, + columnOrder: 1, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 6`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 3, + columnOrder: 2, + blockOrder: 1, + }, + { + type: 'string', + title: `Block 7`, + required: true, + 'x-read-pretty': false, + 'x-decorator': 'Card', + 'x-decorator-props': { + title: '内嵌区块(只能在当前区域内部拖拽)', + }, + 'x-component': 'Designer', + 'x-component-props': { + schema: { + type: 'object', + properties: { + layout: { + type: 'void', + 'x-component': 'FormLayout', + 'x-component-props': { + layout: 'vertical', + }, + properties: { + [nested.name]: nested, + }, + }, + }, + }, + }, + rowOrder: 4, + columnOrder: 1, + blockOrder: 1, + }, +]; +const schema = blocks2properties(blocks); +export default () => { + console.log({schema}); + return ( +
+ + {/*
{JSON.stringify(schema, null, 2)}
*/} +
+ ); +}; diff --git a/packages/client/src/blocks/grid/index.md b/packages/client/src/blocks/grid/index.md index 78d70549a..b1b070521 100644 --- a/packages/client/src/blocks/grid/index.md +++ b/packages/client/src/blocks/grid/index.md @@ -5,72 +5,56 @@ nav: path: /client group: order: 2 - title: Blocks + title: Blocks path: /client/blocks --- # Grid - 栅格 -## 示例 +基于行(Row)和列(Col)来定义区块(Block)的外部框架。 -```tsx -import React from 'react'; -import { BlockContext, GridBlock } from '@nocobase/client'; -import './demo.less'; +## 代码演示 -function Hello({ name }) { - return
Hello {name}
; -} +### 基本用法 -const blocks = [ - { - name: 'gb1', - 'x-component': 'Hello', - 'x-row': 0, - 'x-column': 0, - 'x-sort': 1, - }, - { - name: 'gb2', - 'x-component': 'Hello', - 'x-row': 0, - 'x-column': 0, - 'x-sort': 2, - }, - { - name: 'gb3', - 'x-component': 'Hello', - 'x-row': 0, - 'x-column': 1, - 'x-sort': 0, - }, - { - name: 'gb4', - 'x-component': 'Hello', - 'x-row': 1, - 'x-column': 0, - 'x-sort': 0, - }, - { - name: 'gb5', - 'x-component': 'Hello', - 'x-row': 2, - 'x-column': 0, - 'x-sort': 0, - }, -]; + -export default () => { - return ( - -
- -
-
- ) +### 内嵌区块 + + + +## API 说明 + +### Grid + +只能在同一个 Grid 里拖拽布局 + +### Grid.Row + +行 + +### Grid.Column + +列 + +### Grid.Block + +区块 + +### BlockOptions + +```ts +interface BlockOptions { + rowOrder: number; + columnOrder: number; + blockOrder: number; } ``` + +- rowOrder:第几行 +- columnOrder:第几列 +- blockOrder:某单元格内部区块排序 + +### blocks2properties + +原始 schema 需要至少 grid->row->col->block->custom 五层嵌套,写起来非常繁琐,`blocks2properties` 方法可以简化配置。 \ No newline at end of file diff --git a/packages/client/src/blocks/grid/index.tsx b/packages/client/src/blocks/grid/index.tsx index a07cd70ae..e69de29bb 100644 --- a/packages/client/src/blocks/grid/index.tsx +++ b/packages/client/src/blocks/grid/index.tsx @@ -1,49 +0,0 @@ -import React from 'react'; -import { Row, Col } from 'antd'; -import { useBlock } from '../'; -import set from 'lodash/set'; - -export function BlockItem(props: any) { - const { Component } = useBlock(props); - if (!Component) { - return null; - } - console.log('BlockItem') - return -} - -export interface GridBlockProps { - blocks: any[]; -} - -export function GridBlock(props: GridBlockProps) { - const { blocks = [] } = props; - const obj = { - rows: [], - }; - blocks.forEach(block => { - const path = ['rows', block['x-row'], block['x-column'], block['x-sort']]; - console.log(path); - set(obj, path, block); - }); - console.log({obj}) - return ( -
- {obj.rows.map((cols, rowIndex) => { - return ( - - {cols.map((items, colIndex) => { - return ( - - {items.map((item, key) => ) } - - ) - })} - - ); - })} -
- ); -} - -export default GridBlock; diff --git a/packages/client/src/blocks/grid/style.less b/packages/client/src/blocks/grid/style.less new file mode 100644 index 000000000..5a2b5c602 --- /dev/null +++ b/packages/client/src/blocks/grid/style.less @@ -0,0 +1,143 @@ +.drop-column { + position: absolute; + top: 0; + left: -12px; + height: calc(100% - 24px); + width: 24px; + // border: 1px dashed rgb(232, 232, 232); + &.active { + &::after { + content: ''; + display: block; + width: 12px; + height: 100%; + position: absolute; + left: 5px; + background: #e6f7ff; + } + } + &.last { + left: auto; + right: -24px; + } +} +.drop-block { + height: 12px; + position: absolute; + bottom: -12px; + width: 100%; + left: 0; + // background: #e6f7ff; + // pointer-events: none; + &.active { + background: #e6f7ff; + } +} +// .ant-formily-item + .drop-block { +// position: absolute; +// bottom: 12px; +// width: 100%; +// } +.grid-row { + position: relative; + + &.active { + &::after { + content: ''; + display: block; + height: 12px; + position: absolute; + bottom: 5px; + width: 100%; + background: #e6f7ff; + } + } +} + +.grid-block { + position: relative; + padding-bottom: 1px; + &.active { + &::after { + content: ''; + display: block; + height: 12px; + position: absolute; + bottom: 5px; + width: 100%; + background: #e6f7ff; + } + } + + &.grid-block--Input { + margin-bottom: 0; + .drop-block { + bottom: 0; + } + }rgb(172, 172, 172) + .grid-block-actions { + position: absolute; + top: 5px; + right: 5px; + z-index: 2; + display: none; + } + > .action-bar { + position: absolute; + right: 10px; + top: 5px; + z-index: 2; + display: none; + &.active { + display: inline-block; + } + } + &:hover, + &.hover { + > .action-bar { + display: inline-block; + } + } +} + +.grid-block .ant-card { + margin-bottom: 24px; +} + +.drop-row { + height: 12px; + // background: #e6f7ff; + position: absolute; + bottom: 5px; + width: 100%; + // background: #e6f7ff; + &.active { + background: #e6f7ff; + } + + &.first { + top: -17px; + z-index: 2; + bottom: auto; + } +} + +.draggable { + cursor: move !important; /* fallback: no `url()` support or images disabled */ + cursor: -webkit-grab !important; /* Chrome 1-21, Safari 4+ */ + cursor: -moz-grab !important; /* Firefox 1.5-26 */ + cursor: grab !important; /* W3C standards syntax, should come least */ +} + +.draggable:active { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} +tr.drop-over-downward td { + border-bottom: 1px dashed #1890ff; +} + +tr.drop-over-upward td { + border-top: 1px dashed #1890ff; +} \ No newline at end of file diff --git a/packages/client/src/blocks/grid/utils.tsx b/packages/client/src/blocks/grid/utils.tsx new file mode 100644 index 000000000..d2efc14db --- /dev/null +++ b/packages/client/src/blocks/grid/utils.tsx @@ -0,0 +1,94 @@ +import set from 'lodash/set'; +import { uid } from '@formily/shared'; + +export const grid = (...rows: any[]) => { + const rowProperties = {}; + rows.forEach((row, index) => { + set(row, 'x-component-props.rowOrder', index); + rowProperties[row.name] = row; + }); + const name = `g_${uid()}`; + return { + type: 'void', + name, + 'x-component': 'Grid', + 'x-component-props': {}, + properties: rowProperties, + }; +}; + +export const row = (...cols: any[]) => { + const rowName = `r_${uid()}`; + const colsProperties = {}; + cols.forEach((col, index) => { + set(col, 'x-component-props.columnOrder', index); + set(col, 'x-component-props.span', 24 / cols.length); + colsProperties[col.name] = col; + }); + return { + type: 'void', + name: rowName, + 'x-component': 'Grid.Row', + 'x-component-props': {}, + properties: colsProperties, + }; +}; + +export const column = (...blocks: any[]) => { + const colName = `c_${uid()}`; + const properties = {}; + blocks.forEach((item) => { + properties[item.name] = item; + }); + return { + name: colName, + type: 'void', + 'x-component': 'Grid.Column', + 'x-read-pretty': true, + 'x-component-props': { + labelCol: 6, + wrapperCol: 10, + span: 24, + }, + properties, + }; +}; + +export const block = (...fields: any[]) => { + const blockName = `b_${uid()}`; + const properties = {}; + fields.forEach((item) => { + const name = item.name || `f_${uid()}`; + properties[name] = item; + }); + const lastComponentType = fields[fields.length - 1]['x-component']; + return { + name: blockName, + type: 'void', + 'x-component': 'Grid.Block', + 'x-component-props': { + lastComponentType, + }, + 'x-read-pretty': false, + properties, + }; +}; + +export function blocks2properties(blocks: any[]) { + const obj = { + rows: [], + }; + blocks.forEach(block => { + const path = ['rows', block['rowOrder'], block['columnOrder'], block['blockOrder']]; + console.log(path); + set(obj, path, block); + }); + return grid(...obj.rows.filter(Boolean).map((cols) => { + return row( + ...cols.filter(Boolean).map((items: any[]) => { + console.log({items: items.filter(Boolean)}) + return column(...items.filter(Boolean).map(item => block(item))); + }), + ); + })); +} diff --git a/packages/client/src/blocks/index.tsx b/packages/client/src/blocks/index.tsx index ca3d11763..b101743e4 100644 --- a/packages/client/src/blocks/index.tsx +++ b/packages/client/src/blocks/index.tsx @@ -3,7 +3,6 @@ import React, { createContext, useContext } from 'react'; export * from './form'; export * from './descriptions'; export * from './table'; -export * from './grid'; export const BlockContext = createContext({}); diff --git a/packages/client/src/blocks/kanban/index.md b/packages/client/src/blocks/kanban/index.md index dce2a6756..12d06e768 100644 --- a/packages/client/src/blocks/kanban/index.md +++ b/packages/client/src/blocks/kanban/index.md @@ -11,3 +11,9 @@ group: # Kanban - 看板 +## 代码演示 + +### 基本使用 + +### 设计器模式 + diff --git a/packages/client/src/blocks/table/index.md b/packages/client/src/blocks/table/index.md index c0d9a03a2..4d8ab8abd 100644 --- a/packages/client/src/blocks/table/index.md +++ b/packages/client/src/blocks/table/index.md @@ -11,6 +11,11 @@ group: # Table - 表格 -## 示例 +## 代码演示 + +### 基本使用 + +### 设计器模式 + diff --git a/packages/client/src/components/MenuEditor/index.tsx b/packages/client/src/components/MenuEditor/index.tsx index 563c09977..31869be5d 100644 --- a/packages/client/src/components/MenuEditor/index.tsx +++ b/packages/client/src/components/MenuEditor/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Layout, Menu, Button, Dropdown } from 'antd'; +import { Layout, Menu, Button, Dropdown, Modal } from 'antd'; import { AppstoreOutlined, MenuOutlined, @@ -132,7 +132,12 @@ export function AddNewAction(props) { ); return ( - {props.children || } + + {props.children || } + ); } @@ -179,14 +184,26 @@ export function SettingAction(props) { 移动到 - remove()} key="delete"> + { + Modal.confirm({ + title: '确定菜单', + content: '确认删除此菜单项吗?', + onOk: remove, + }); + }} + key="delete" + > 删除菜单 ); return ( - + ); diff --git a/packages/client/src/components/MenuEditor/style.less b/packages/client/src/components/MenuEditor/style.less index 3f1c6220f..4a95d8451 100644 --- a/packages/client/src/components/MenuEditor/style.less +++ b/packages/client/src/components/MenuEditor/style.less @@ -41,4 +41,18 @@ display: inline-block; } } +} + +.ant-menu-horizontal { + .menu-icons { + position: absolute; + right: 0; + background: #1890ff; + padding-left: 10px; + height: auto; + line-height: 1rem; + top: 50%; + transform: translateY(-50%); + transition: all 0.3s ease-in; + } } \ No newline at end of file diff --git a/packages/client/src/components/RouteSwitch/index.tsx b/packages/client/src/components/RouteSwitch/index.tsx index ae56e9eb7..81807d69e 100644 --- a/packages/client/src/components/RouteSwitch/index.tsx +++ b/packages/client/src/components/RouteSwitch/index.tsx @@ -19,7 +19,6 @@ export function RouteSwitch(props: RouteSwitchProps) { {routes.map((route, key) => { if (route.type === 'redirect') { - console.log(route); return ( Block + ) +} + +export const Block = connect(Test, mapProps( + (props, field) => { + // console.log({ props, field }); + return { + ...props, + }; + }, +)); + +export default Block; diff --git a/packages/client/src/fields/cascader/index.md b/packages/client/src/fields/cascader/index.md index cbdbe9a1c..5382d1ad5 100644 --- a/packages/client/src/fields/cascader/index.md +++ b/packages/client/src/fields/cascader/index.md @@ -102,4 +102,16 @@ export default () => { ); }; -``` \ No newline at end of file +``` + +## 参数说明 + +### dataRequest vs dataSource + +- dataSource 静态选项,value 为 string 或 number。 +- dataRequest 动态选项,value 为 string 或者 number 时,需要异步获取 label;也可以是 object,无需异步获取数据。 + +### 常用配置项 + +- changeOnSelect:当此项为 true 时,点选每级菜单选项值都会发生变化 +- maxLevel:dataRequest 参数,省市区字段时可以用于配置最大选中级别 \ No newline at end of file diff --git a/packages/client/src/fields/checkbox/index.tsx b/packages/client/src/fields/checkbox/index.tsx index be0b8e4f7..43968c730 100644 --- a/packages/client/src/fields/checkbox/index.tsx +++ b/packages/client/src/fields/checkbox/index.tsx @@ -19,7 +19,7 @@ export const Checkbox: ComposedCheckbox = connect( onInput: 'onChange', }, (props, field) => { - console.log({ props, field }); + // console.log({ props, field }); return { ...props, }; diff --git a/packages/client/src/fields/date-picker/index.md b/packages/client/src/fields/date-picker/index.md index 12804b832..5bb04947b 100644 --- a/packages/client/src/fields/date-picker/index.md +++ b/packages/client/src/fields/date-picker/index.md @@ -112,3 +112,17 @@ export default () => { ); }; ``` + +## 参数说明 + +### dateFormat + +日期格式 + +### showTime + +是否显示时间 + +### timeFormat + +时间格式 diff --git a/packages/client/src/fields/index.tsx b/packages/client/src/fields/index.tsx index d7ad569a0..f37907126 100644 --- a/packages/client/src/fields/index.tsx +++ b/packages/client/src/fields/index.tsx @@ -1,6 +1,15 @@ -import React, { useMemo } from 'react'; +import React, { createContext, useMemo, useState, useContext } from 'react'; import { Form, FormItem, FormLayout, Space } from '@formily/antd'; -import { FormProvider, createSchemaField } from '@formily/react'; +import { + FormProvider, + createSchemaField, + useField, + useFieldSchema, + Schema, + useForm, +} from '@formily/react'; +import set from 'lodash/set'; +import { uid } from '@formily/shared'; import { createForm } from '@formily/core'; import * as core from '@formily/core'; import { Select } from './select'; @@ -17,6 +26,8 @@ import { Markdown } from './markdown'; import { Password } from './password'; import { Radio } from './radio'; import { TimePicker } from './time-picker'; +import { Block } from './block'; +import { Grid } from '../blocks/grid/Grid'; export function fields2properties(fields: any) { const items = Array.isArray(fields) ? fields : [fields]; @@ -50,23 +61,26 @@ const fieldComponents = { Radio, Space, TimePicker, + Block, + Grid, Column: ({ children }) => children, + Blank: ({ children }) => children, }; const fieldScope = {}; export function registerFieldComponents(components) { - Object.keys(components).forEach(displayName => { + Object.keys(components).forEach((displayName) => { fieldComponents[displayName] = components[displayName]; }); } export function getFieldComponent(displayName: string) { - return fieldComponents[displayName] + return fieldComponents[displayName]; } export function registerFieldScope(scopes) { - Object.keys(scopes).forEach(name => { + Object.keys(scopes).forEach((name) => { fieldScope[name] = scopes[name]; }); } @@ -76,6 +90,300 @@ export const SchemaField = createSchemaField({ scope: fieldScope, }); +export const SchemaDesignerContext = createContext(new Schema({})); +export const SchemaRefreshContext = createContext(null); + +export function SchemaFieldWithDesigner(props) { + function Container(props) { + const { schema } = props; + const [, refresh] = useState(0); + return ( + { + refresh(Math.random()); + }} + > + + + + + ); + } + return ; +} + +export const getFullPaths = (schema: Schema) => { + if (!schema) { + return []; + } + const paths = [schema.name]; + if (schema.parent && schema.parent.name) { + paths.unshift(...getFullPaths(schema.parent)); + } + return paths; +}; + +export function useSchema() { + const context = useContext(SchemaDesignerContext); + const refresh = useContext(SchemaRefreshContext); + const fieldSchema = useFieldSchema(); + const paths = getFullPaths(fieldSchema); + let schema: Schema = context; + const names = [...paths]; + while (names.length) { + schema = schema.properties[names.shift()]; + } + return { schema, fieldSchema, refresh }; +} + +export function getSchema(context) { + return (paths) => { + const fullPaths = Array.isArray(paths) ? paths : getFullPaths(paths); + let s: Schema = context; + const names = [...fullPaths]; + while (names.length) { + s = s.properties[names.shift()]; + } + return s; + }; +} + +export function addPropertyBefore(target, prop) { + Object.keys(target.parent.properties).forEach((name) => { + if (name === target.name) { + target.parent.addProperty(prop.name, prop); + } + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + }); +} + +export function addPropertyAfter(target, prop) { + Object.keys(target.parent.properties).forEach((name) => { + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + if (name === target.name) { + target.parent.addProperty(prop.name, prop); + } + }); +} + +export function useSchemaQuery() { + const context = useContext(SchemaDesignerContext); + const form = useForm(); + const { schema, refresh } = useSchema(); + return { + removeBlock() { + schema.parent.removeProperty(schema.name); + refresh(); + }, + addBlock: (data, up = false) => { + const blockSchema = block({ + type: 'string', + title: `Block ${uid()}`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 2, + columnOrder: 1, + blockOrder: 1, + }); + if ('Grid.Column' === schema.parent['x-component']) { + if (Object.keys(schema.parent.parent.properties).length > 1) { + up + ? addPropertyBefore(schema, blockSchema) + : addPropertyAfter(schema, blockSchema); + } else { + const rowSchema = row(column(blockSchema)); + up + ? addPropertyAfter(schema.parent.parent, rowSchema) + : addPropertyAfter(schema.parent.parent, rowSchema); + } + } + console.log('x-component', schema.parent['x-component']); + refresh(); + // schema.parent['x-component'] + }, + insertAfter: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + if (!source || !target) { + return; + } + console.log({ + sourcePath, + source, + target, + targetPath, + sourceParentproperties: source.parent.properties, + }); + const names = []; + Object.keys(target.parent.properties).forEach((name) => { + // if (names.includes(source.name)) { + // return; + // } + // names.push(name); + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + if (name === target.name) { + // names.push(source.name); + source.parent.removeProperty(source.name); + console.log('source.parent.properties', source.parent.properties); + target.parent.addProperty(source.name, source.toJSON()); + } + }); + }, + insertAfterWithAddRow: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const rowSchema = row(column(source.toJSON())); + source.parent.removeProperty(source.name); + Object.keys(target.parent.properties).forEach((name) => { + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + if (name === target.name) { + target.parent.addProperty(rowSchema.name, rowSchema); + } + }); + }, + insertBeforeWithAddRow: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const rowSchema = row(column(source.toJSON())); + source.parent.removeProperty(source.name); + Object.keys(target.parent.properties).forEach((name) => { + if (name === target.name) { + target.parent.addProperty(rowSchema.name, rowSchema); + } + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + }); + }, + appendToRowWithAddColumn: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const colSchema = column(source.toJSON()); + source.parent.removeProperty(source.name); + const len = Object.keys(target.properties).length + 1; + target.addProperty(colSchema.name, colSchema); + console.log('target.properties', target.properties); + Object.keys(target.properties).forEach((name) => { + const prop = target.properties[name]; + form.setFieldState(getFullPaths(prop).join('.'), (state) => { + state.componentProps = { + span: 24 / len, + }; + }); + }); + }, + insertBeforeWithAddColumn: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const colSchema = column(source.toJSON()); + source.parent.removeProperty(source.name); + const len = Object.keys(target.parent.properties).length + 1; + console.log('x-component-props.span', 24 / len); + Object.keys(target.parent.properties).forEach((name) => { + if (name === target.name) { + target.parent.addProperty(colSchema.name, colSchema); + } + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + const json = property.toJSON(); + target.parent.addProperty(property.name, json); + }); + Object.keys(target.parent.properties).forEach((name) => { + const prop = target.parent.properties[name]; + form.setFieldState(getFullPaths(prop).join('.'), (state) => { + state.componentProps = { + span: 24 / len, + }; + }); + }); + }, + }; +} + +export const grid = (...rows: any[]) => { + const rowProperties = {}; + rows.forEach((row, index) => { + set(row, 'x-component-props.rowOrder', index); + rowProperties[row.name] = row; + }); + const name = `g_${uid()}`; + return { + type: 'void', + name, + 'x-component': 'Grid', + 'x-component-props': {}, + properties: rowProperties, + }; +}; + +export const row = (...cols: any[]) => { + const rowName = `r_${uid()}`; + const colsProperties = {}; + cols.forEach((col, index) => { + set(col, 'x-component-props.columnOrder', index); + set(col, 'x-component-props.span', 24 / cols.length); + colsProperties[col.name] = col; + }); + return { + type: 'void', + name: rowName, + 'x-component': 'Grid.Row', + 'x-component-props': {}, + properties: colsProperties, + }; +}; + +export const column = (...blocks: any[]) => { + const colName = `c_${uid()}`; + const properties = {}; + blocks.forEach((item) => { + properties[item.name] = item; + }); + return { + name: colName, + type: 'void', + 'x-component': 'Grid.Column', + 'x-read-pretty': true, + 'x-component-props': { + labelCol: 6, + wrapperCol: 10, + span: 24, + }, + properties, + }; +}; + +export const block = (...fields: any[]) => { + const blockName = `b_${uid()}`; + const properties = {}; + fields.forEach((item) => { + const name = item.name || `f_${uid()}`; + // item.title = `${item.title} ${name}`; + properties[name] = item; + }); + const lastComponentType = fields[fields.length - 1]['x-component']; + return { + name: blockName, + type: 'void', + 'x-component': 'Grid.Block', + 'x-component-props': { + lastComponentType, + }, + 'x-read-pretty': false, + properties, + }; +}; + export function parseEffects(effects: any, form?: any) { if (!effects) { return; @@ -102,15 +410,17 @@ export interface FieldProps { export function Field(props: FieldProps) { const { schema, readPretty, data, effects } = props; - const form = props.form || useMemo( - () => - createForm({ - initialValues: data, - readPretty, - effects: (form) => parseEffects(effects, form), - }), - [], - ); + const form = + props.form || + useMemo( + () => + createForm({ + initialValues: data, + readPretty, + effects: (form) => parseEffects(effects, form), + }), + [], + ); return (
diff --git a/packages/client/src/fields/input/index.md b/packages/client/src/fields/input/index.md index e30c63e14..e7f62ef8b 100644 --- a/packages/client/src/fields/input/index.md +++ b/packages/client/src/fields/input/index.md @@ -280,4 +280,15 @@ export default () => { ## 参数说明 -输入框组件暂时还没有特殊的参数,其他通用参数参考 [Field 章节](fields)。 \ No newline at end of file +输入框组件暂时还没有特殊的参数,其他通用参数参考 [Field 章节](fields)。 + +常用配置参数: + +- type +- title +- name +- required +- default +- x-component-props.placeholder +- x-decorator-props.description +- x-validator diff --git a/packages/client/src/templates/admin-layout/index.tsx b/packages/client/src/templates/admin-layout/index.tsx index 9877eea1b..17401af96 100644 --- a/packages/client/src/templates/admin-layout/index.tsx +++ b/packages/client/src/templates/admin-layout/index.tsx @@ -38,7 +38,7 @@ function Menu() {
    {data.map((item, index) => (
  • - {item.title} + {item.title}
  • ))}
diff --git a/packages/client/src/templates/page-template/index.tsx b/packages/client/src/templates/page-template/index.tsx index 258454e60..6bd36a525 100644 --- a/packages/client/src/templates/page-template/index.tsx +++ b/packages/client/src/templates/page-template/index.tsx @@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { Spin } from 'antd'; import { Helmet } from 'react-helmet'; import { usePage, BlockContext } from '../../'; +import { useRequest } from 'ahooks'; export function PageTemplate({ route }) { const { GridBlock } = useContext(BlockContext); @@ -9,11 +10,14 @@ export function PageTemplate({ route }) { useEffect(() => { setPreName(route.name); }, [route.name]); - const { data = {}, loading } = usePage(route.name); + const { data = {}, loading } = useRequest(`/api/routes:getPage?name=${route.name}`, { + // refreshDeps: [route.name], + }); + console.log(data.title, { loading, preName, blocks: data.blocks }); if (loading || preName !== route.name) { return ; } - console.log(data.title, { preName, blocks: data.blocks }); + console.log(data.title, { loading, preName, blocks: data.blocks }); return (