npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@medlinker/mall-template

v0.8.12

Published

模板编辑工具

Downloads

11

Readme

模板编辑工具

背景目的

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

现阶段目标:

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

概念

控件-Control

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

现阶段控件会铺满容器。

结构

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

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

配置文件

下面是配置的属性

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

配置 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 外还有一些通用的属性

| 属性 | 类型 | 说明 | 必填 | 默认值 | | -------- | ------ | --------------- | ---- | ------ | | default | any | schema 的默认值 | 否 | - | | showName | string | schema 的标签 | 否 | - | | tips | string | 控件的提示文案 | 否 | - |

控件组件

控件组件实际上就是一个 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 组件会被传入一下属性

| 属性 | 类型 | 说明 | 必填 | 默认值 | | ---------------- | ----------------- | --------------- | ---- | ------ | | schemaDefinition | any | schema 的默认值 | 否 | - | | value | any | 值 | 否 | - | | 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)。