0.8.4 • Published 4 years ago

med-mt v0.8.4

Weekly downloads
234
License
-
Repository
-
Last release
4 years ago

模板编辑工具

背景目的

互联网医院商城移动端需要运营人员动态配置展示内容,从展示哪些内容,内容的展示顺序,内容的数据几个维度上都需要是灵活可变的。当然运营不可能 coding 页面,所以需要一套动态的可视化编辑器来帮助他们完成这些工作。

现阶段目标:

  • 图形化编辑,拖拽排序等功能
  • 可扩展控件
  • 可扩展数据类型
  • 效果预览
  • JSON 格式数据,支持 SSR
  • 撤销,前进功能
  • ...

概念

控件-Control

控件是客户端可见的界面,一个控件包含一套完成的页面和内部的功能逻辑。比如轮播推荐商品等都是控件。控件表面和组件类似,但控件是组件和一系列配置的集合,因为控件除了展示页面,还需要告诉编辑器 我是谁(组件的身份)我有哪些数据类型和可编辑页面和编辑的数据是如何关联的等问题。

现阶段控件会铺满容器。

结构

一个基础的控件文件夹包含以下文件

├── ControlName.tsx // 组件实现
├── config.json // 配置文件
├── index.ts // 入口文件
└── 其他依赖文件

配置文件

下面是配置的属性

属性类型说明必填默认值
namestring控件的唯一名称,相当于 id-
showNamestring控件的显示名称,不传则取 name-
groupstring控件所在的分类,相同分类的控件会归纳在同一个分组默认
thumbstring控件展示的缩略图-
propsarray定义控件的依赖的属性,是控件配置的核心-
配置 props

props声明了控件 UI 依赖的属性值,声明的类型的转化成值会以 props 的形式传递给 React UI 组件,也就是说,这里定义了组件的每一个 prop 的类型。

每一个 prop 至少包含keyschema两个属性。

  • key 属性的键,比如 key 是url,那么 UI 组件会接受到一个url的 prop
  • schema 属性的类型,比如SchemaTypes.Text声明了这个值是一个字符串。schema 不仅决定了值的类型,还决定了在编辑这个值时页面上是如何展示的,如文本框,选择器等等。此外我们还可以自定义 schema,这个后面会提到。

基本的配置

{
  props: [
    {
      key: 'url',
      schema: SchemaTypes.Text,
    },
  ]
}

当类型有扩展属性时,可以将 schema 以 type 字段声明,其他字段作为扩展属性,如定义inputProps传递给输入框控件,内部有maxLength为最大输入长度。

{
  props: [
    {
      key: 'url',
      schema: {
        type: SchemaTypes.Text,
        inputProps: {
          maxLength: 4, // 最大长度为4
        },
      },
    },
  ]
}

schema还支持嵌套声明,不过由于多层嵌套在 UI 层面不好展示层级关系,因此目前只支持一层嵌套。如分类 1namelink2 个属性。

{
  props: [
    {
      key: 'imageLink',
      schema: {
        name: {
          type: SchemaTypes.Text,
          inputProps: {
            maxLength: 4, // 最大长度为4
          },
        },
        link: {
          type: SchemaTypes.Link,
        },
      },
    },
  ]
}

我们默认支持了数组类型的schema,当类型为数组时,会有个额外的item属性,item的配置和schema配置完全一致。此外还可以通过max属性来限制最大长度。

如一个可编辑长度的分类控件配置如下

{
  props: [
    {
      key: 'categories',
      schema: {
        type: SchemaTypes.Array,
        max: 8,
        item: {
          name: {
            type: SchemaTypes.Text,
            inputProps: {
              maxLength: 4, // 最大长度为4
            },
          },
          link: {
            type: SchemaTypes.Link,
          },
        },
      },
    },
  ]
}

当数组类型在界面上初始化时,编辑器会生成一个默认项,当长度为 1 时最后一项将无法删除,也就是说数组值的长度永远不会为 0,除非删除整个控件。

除了 type 外还有一些通用的属性

属性类型说明必填默认值
defaultanyschema 的默认值-
showNamestringschema 的标签-
tipsstring控件的提示文案-

控件组件

控件组件实际上就是一个 React 组件,上面已经提到组件的props就是 config 中配置的props属性。

例如上面的链接图片控件的组件实现大概是这样的

import React from 'react'
import './style.css'

interface Props {
  url: string
  link: string
}

export default (props: Props) => {
  const { url, link } = props

  return (
    <Link href={link} className="mt-full-image-box">
      <img src={url} className="mt-full-image" alt="" />
    </Link>
  )
}

当编辑者选中控件到视窗面板(vision panel)后,页面会渲染这个组件的内容。如果点击控件,编辑视图(editor panel)会出现控件定义的 props 的编辑控件用于编辑。

Interactive

默认支持控件的整体选中进行编辑,但是有时候我们的控件上可定义的元素会比较多,这时让使用者判断当前编辑的哪一部分内容会比较困难。为了解决这个问题,我们提供了一个Interactive组件来解决这个问题。它有以下功能

  • 被包裹的组件会成为一个可交互的元素,当点击时会定位到元素对应的编辑控件
  • 当元素对应的编辑控件获取焦点时,元素也会高亮
  • 元素属性更改时,高亮部分也会随属性变化而变化

Interactive组件并不会对组件本身的渲染造成影响,在实际运行时相当于直接返回了被包裹的组件本身。

它的使用也非常的简单,下面以分类控件来演示用法。

categories.map((item, index) => {
  return (
    <Interactive path={`categories[${index}].link`} key={index}>
      <div className="mt-category-auto-item">
        <Interactive path={`categories[${index}].icon`}>
          <img className="mt-category-auto-item-icon" src={item.icon} alt="" />
        </Interactive>
        <Interactive path={`categories[${index}].name`}>
          <div className="mt-category-auto-item-text">{item.name}</div>
        </Interactive>
      </div>
    </Interactive>
  )
})

将需要显示的组件用 Interactive 进行包裹,并传入path属性,path是当前组件依赖的属性路径。如第三个分类的图标的pathcategories[2].icon。因为链接是针对于整个数组有效,所以在最外层包裹并设置pathcategories[2].link

导出和注册

index.ts导出控件的组件和配置

import Component from './Component'
import config from './config'

export default {
  Component,
  config,
}

当在页面入口通过regsitControls注册组件后,在控件面板(control panel便能看到所有控件。

// App.tsx

mt.regsitControls([fullImage])

数据定义-Schema

Schema 定义控件某个属性的类型,它确定了编辑时的 UI 和产生的数据。

Schema 组件

Schema 通过一个 React 组件实现功能,下面是文本输入框的 schema 实现

import React, { useCallback, useRef } from 'react'
import './style.css'
import { useSchemaFocus, SchemaTypes } from '../../core/schema'

export default (props: SchemaCompPropsWithConfig) => {
  const { schemaDefinition, value, onChange } = props
  const { inputProps, tips, showName } = schemaDefinition
  const inputRef = useRef<HTMLInputElement>(null)
  const handleFocus = useCallback(() => {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  const IS_NUMBER = schemaDefinition.type === SchemaTypes.Number

  const { focus, blur } = useSchemaFocus(handleFocus)

  return (
    <div className="mt-form-el">
      <label>
        {showName && <span className="mt-form-el-label">{showName}: </span>}
        <input
          ref={inputRef}
          value={value === undefined || value === null ? '' : value}
          className="mt-input-text"
          type="text"
          placeholder={`请输入${schemaDefinition.showName || ''}`}
          {...inputProps}
          onChange={e => {
            const value = Number(e.target.value)
            if (IS_NUMBER) {
              console.log(value)
              onChange(isNaN(value) ? 0 : value)
            } else {
              onChange(e.target.value)
            }
          }}
          onBlur={blur}
          onFocus={focus}
        />
        {tips && <div className="mt-form-el-tips">* {tips}</div>}
      </label>
    </div>
  )
}

Schema 的实现内容没有强制要求,每一个 Schema 组件会被传入一下属性

属性类型说明必填默认值
schemaDefinitionanyschema 的默认值-
valueany-
onChange(value:any)=>void值更改的回调-

Schema 组件会有 2 种控制的状态

  1. 获取焦点 - 当编辑视窗的控件中的某个可交互的(Interactive)控件元素进行点击时,schema 组件会获取焦点
  2. 失去焦点 - 当其他 schema 控件获取焦点或者对应控件元素失去焦点时,当前 schema 组件会失去焦点。

我们通过useSchemaFocus hook 来控制 Schema 的获焦和失焦行为。它接受一个回调函数,当控件元素获取焦点时会触发。同时返回focusblur方法,如果 schema 组件在操作时获取和失去焦点时(如输入框获取了焦点),执行对应的方法能时让编辑视窗的控件获取焦点。

建议使用浏览器原生获取焦点的能力来避免怪异行为,例如tabindex等。。

Schema 注册

schema 需要注册后才能在控件中使用,也就是说必须在控件注册之前注册 schema。

注册后的 schema 可以在SchemaTypes获取。

registSchemaComp('DrugList', DrugList)
registSchemaComp('DrugCategory', DrugCategory)

// 使用
{
   type: SchemaTypes.DrugList,
}

其他组件

Link

Link 组件用于解决编辑过程时 a 标签点击问题

useEnv

获取当前的环境上下文,如是编辑模式(edit)还是渲染模式(render)。

0.8.4

4 years ago

0.8.1

4 years ago

0.8.3

4 years ago

0.8.8

4 years ago

0.8.0

4 years ago

0.8.0-7

4 years ago

0.8.0-6

4 years ago

0.8.0-5

4 years ago

0.8.0-4

4 years ago

0.8.0-3

4 years ago

0.8.0-2

4 years ago

0.8.0-1

4 years ago

0.7.7-1.1.4.0

4 years ago

0.7.7-1.1.3.0

4 years ago

0.7.7-1.1.2.0

4 years ago

0.7.7-1.1.0

4 years ago

0.7.7-1.1.1.0

4 years ago

0.7.7-1.0

4 years ago

0.8.0-0

4 years ago

0.7.6

4 years ago

0.7.5

4 years ago

0.7.4

4 years ago

0.7.3

4 years ago

0.7.2

4 years ago

0.7.1

4 years ago

0.7.0

4 years ago

0.6.5

4 years ago

0.6.3

4 years ago

0.6.2

4 years ago

0.6.1

4 years ago

0.5.7

4 years ago

0.6.0

4 years ago

0.5.6

4 years ago

0.5.5

4 years ago

0.5.4

4 years ago

0.5.3

4 years ago

0.5.2

4 years ago

0.5.1

4 years ago

0.5.0

4 years ago

0.4.14

4 years ago

0.4.13

4 years ago

0.4.12

4 years ago

0.4.11

4 years ago

0.4.10

4 years ago

0.4.9

4 years ago

0.4.8

4 years ago

0.4.7

4 years ago

0.4.6

4 years ago

0.4.5

4 years ago

0.4.4

4 years ago

0.4.0

4 years ago

0.3.0

4 years ago

0.2.0

4 years ago

0.1.0

4 years ago