02020 / vite-kit

0 stars 0 forks source link

schemaRender #9

Open 02020 opened 3 years ago

02020 commented 3 years ago

schemaRender

根据数据结构 schema,执行渲染


import kit from './utils';

const preprocessOptionsC = (context, isFunctional) => (key) => {
  return isFunctional ? context[key] || context.data[key] : context['$' + key];
};

// 求值x
const preX = (f, props, schema) => {
  let x = typeof f === 'function' ? f(props || {}) : schema;
  x = typeof x === 'function' ? x(schema) : x;
  return x;
};

// 增加事件函数
const handlerOn = (on, xKey) =>
  kit.fR((item) => {
    const _key = kit.toKeys(item, 2);
    return [_key[0], (...args) => on && on(_key[1], xKey, ...args)];
  });

/**
 * 处理插槽-测试未通过
 * @param {*} renderG
 * @param {*} x
 */
const scopedSlots = (renderG, x) => {
  const slotsTemplateC = kit.fRo(
    kit.fxC((key) => preprocessOptions('scopedSlots')[key])
  );

  const slotsC = kit.fRo(kit.fxC(() => renderG));

  const _scopedSlots = {
    ...slotsTemplateC(x.slotsT || {}),
    ...slotsC(x.slots || {}),
  };
  // console.log(x.component, _scopedSlots);
  // console.log('preprocessOptions', preprocessOptions('scopedSlots'));
  // console.log(x.component, x.slots);
  return _scopedSlots;
};

// render 函数封装-层级渲染
const fRender = function (h, __context) {
  const context = __context ? __context : this;
  const preprocessOptions = preprocessOptionsC(context, !!__context);
  const { f, props, on, schema } = preprocessOptions('props');

  const x = preX(f, props, schema);
  // console.log('x', x);
  // 递归渲染
  const renderG = (x) => {
    if (!x || typeof x === 'string') {
      return x;
    }

    let child = kit.fMap(renderG)(x.child);
    // o => []
    child = kit.of(child, !Array.isArray(child));

    // if (x.component === 'Panel') {
    //   debugger;
    // }

    const resp = h(
      x.component || 'div',
      {
        class: x.class,
        style: x.style,
        props: x.props,
        slot: x.slot, // 子组件名称
        attrs: x.attrs,
        on: !x.on ? {} : handlerOn(on, x.key)(x.on),
        // scopedSlots: scopedSlots(renderG,x), // 作用域插槽
      },
      child
    );
    // console.log('___执行了渲染___' + (x.component || 'div'));
    return resp;
  };

  return renderG(x);
};

/**
 * toRender
 * 带生命周期的组件
 * * 底层组件不能采用函数式组件
 */
export const toRender = (name = 'toRender') => ({
  name,
  props: {
    f: {
      type: Function,
      default: () => () => {},
    },
    on: Function,
    props: {
      type: Object,
      default: () => {},
    },
    schema: {
      type: [Array, Object],
      default: () => [],
    },
  },
  render: fRender,
});

/**
 * .vue
 *  components: { 'e-render': toComponent(toBuilder.toTable) }
  },
 * @param {*} f
 */
export const toComponent = (f, name) => ({
  functional: true,
  name: name || 'toComponent',
  props: ['props', 'on', 'schema'],
  render: function (h, context) {
    // console.log(context.props);
    const { props, on, schema } = context.props;
    return h(toRender(name || 'toComponent'), {
      props: { f, props, on, schema },
    });
  },
});
02020 commented 3 years ago

builder-构建组件,常用组件配置


/**
 * iview-卡片标题
 * @param {*} title
 * @param {*} extra
 */
export const Card = ({ title, extra, child }) => ({
  component: 'Card',
  child: [
    title && toSlot('title', title, [['click', 'click-title']]),
    extra && toSlot('extra', extra, [['click', 'click-extra']]),
    child,
  ],
});

/**
 * iview-折叠面板
 * @param {*} value
 */
export const Collapse = (value = []) => (child) => ({
  key: 'temp',
  component: 'Collapse',
  props: {
    value,
  },
  on: ['on-change', ['input', 'input-collapse']],
  child: child,
});

/**
 * iview-折叠面板-项
 * @param {*} name
 * @param {*} title
 */
export const Panel = ({ name = '', child, title = '标题' }, index) => ({
  component: 'Panel',
  props: { name: name + '_' + index },
  child: [title, toSlot('content', child)],
});

/**
 * iview-折叠面板-项
 * @param {*} name
 * @param {*} title
 */
export const PanelC = (name, title, index) => (child) =>
  Panel({ name, child, title }, index);

/**
 * iview-列表
 * @param {*} props
 */
export const List = (props) => (child) => ({
  component: 'List',
  style: props.style,
  class: props.class,
  props: {
    itemLayout: 'vertical',
    ...props,
  },
  child: child,
});
/**
 * iview-列表-列表项
 * @param {*} param0
 */
export const ListItem = ({ action, extra, avatar, title, description }) => {
  return {
    component: 'ListItem',
    child: [
      { slot: 'action', child: action }, // 只能这样配置
      { slot: 'extra', child: extra, on: [['click', 'click-extra']] }, // 只能这样配置
      {
        component: 'ListItemMeta',
        child: [
          {
            slot: 'avatar',
            child: avatar,
          },
          { slot: 'title', child: title },
          { slot: 'description', child: description },
        ],
      },
    ],
  };
};

/**
 *
 * @param {*} cb
 * @param {*} option 默认配置
 * @param {*} f 对默认配置option进行处理
 * @returns {Array} [option, option]
 * @demo 渲染本级
 */
export const fMap = (f, cb = (x) => x) => (list) => cb(list.map(f));

/**
 * iview-图标
 * @param {*} type
 * @param {*} size
 * @param {*} color
 */
export const Icon = (props) => {
  const { type, size = 30, color = '#2d8cf0' } = props;
  return {
    key: props.key || props.name,
    class: props.class,
    attrs: {
      title: props.title,
    },
    component: 'Icon',
    on: [['click', 'icon-click']],
    props: {
      type,
      size,
      color,
    },
  };
};

/**
 * iview-图标-主要供map函数使用
 * @param {*} size
 * @param {*} color
 */
export const IconC = (size = 30, color = '#2d8cf0') => ({ title, type }) =>
  Icon({ title, type, size, color });

/**
 * 带卡片样式的折叠面板
 * @param {*} props
 */
export const toCollapseCard = (props) =>
  fMap(
    (x, index) => fMap(Card, PanelC(x.name, x.title, index))(x.child),
    Collapse(props.value)
  );

/**
 * 折叠面板
 * @param {*} props
 */
export const toCollapse = (props) => fMap(Panel, Collapse(props.value));

/**
 * 列表
 * @param {*} fStyle
 */
export const toList = (props, fStyle) =>
  fMap((x) => ListItem(fStyle(x)), List(props));

// 组件的配置清单,供Render使用
export const toSlot = (name, child, on = []) => ({
  slot: name,
  child: child,
  on,
});

/**
 *
 * @param {*} item
 * @param {*} _class
 */
const toStyle = (item, _class = '') => (child) => {
  return {
    class: (item.class || item) + _class,
    style: item.style,
    child: child,
  };
};

/**
 * 1-表格
 * @param {*} param0
 */
export const toETable = ({ table, row, col }) =>
  fMap(toRow({ row, col }), toStyle(table));

/**
 * 2-表格-行
 * @param {*} param0
 */
export const toRow = ({ row, col }) => fMap(toCol(col), toStyle(row));

/**
 * 3-表格-列
 * 待完善与优化
 * @param {*} col
 */
export const toCol = (col) => (cell) => {
  // 数据格式转换, x为数组
  const [key, _class, child] = cell;
  return {
    on: ['click'],
    key,
    style: col.style,
    class: col.class || col + _class,
    child,
  };
};
02020 commented 3 years ago

demo

toRender


<template>
  <div>
    <to-render :f="null" :schema="schema" @cmd="onCMD" />
    <hr style="margin: 20px 0" />
    <to-render
      :f="dataOne.f"
      :props="dataOne.props"
      :schema="dataOne.schema"
      @cmd="onCMD"
    />
  </div>
</template>

<script>
import { toRender, toFunctional, toETable } from '../';

// 构造demo数据
const rowData = [
  ['a', 'bg-green-500', '01室'],
  ['b', 'bg-red-500', '02室'],
  ['c', 'bg-blue-500', '03室'],
];
const floor = (r) => (x) => {
  const xx = [...x];
  xx[2] = r + xx[2];
  return xx;
};
const dataC = (r) => rowData.map(floor(r));

// 配置样式
const style = {
  table: {
    class: 'border border-blue-500',
    style: { width: '500px' },
  },
  row: 'grid grid-cols-6',
  col: 'text-sm font-semibold text-center py-2 m-px text-white ',
};

export default {
  components: { toRender: toRender() },
  data() {
    return {
      schema: toETable(style)([dataC(6), dataC(5)]),
      dataOne: {
        f: toETable,
        on: this.onCMD,
        props: style,
        schema: [dataC(6), dataC(5)],
      },
    };
  },
  methods: {
    onCMD(name, ...args) {
      console.log('onCMD:' + name, ...args);
    },
  },
};
</script>

toComponent


<template>
  <div>
    <e-render :props="dataOne.props" :on="dataOne.on" :schema="dataOne.schema">
    </e-render>
    <hr style="margin: 20px 0" />
    <e-table
      :props="dataTable.props"
      :on="dataTable.on"
      :schema="dataTable.schema"
    >
    </e-table>
    <Button @click="onClick">点击</Button>
  </div>
</template>

<script>
import { toRender, toFunctional, toComponent, toCollapse, toETable } from '../';

// 构造demo数据
const rowData = [
  ['a', 'bg-green-500', '01室'],
  ['b', 'bg-red-500', '02室'],
  ['c', 'bg-blue-500', '03室'],
];
const floor = (r) => (x) => {
  const xx = [...x];
  xx[2] = r + xx[2];
  return xx;
};
const dataC = (r) => rowData.map(floor(r));

// 配置样式
const style = {
  table: {
    class: 'border border-blue-500',
    style: { width: '500px' },
  },
  row: 'grid grid-cols-6',
  col: 'text-sm font-semibold text-center py-2 m-px text-white ',
};

export default {
  components: {
    'e-render': toComponent(toCollapse),
    'e-table': toComponent(toETable),
  },
  data() {
    return {
      dataOne: {
        f: toCollapse,
        on: this.onCMD,
        props: {
          key: 'ddd',
        },
        schema: [{ name: 'dd', child: 'ss', title: '我是标题-1' }],
      },
      dataTable: {
        on: this.onCMD,
        props: style,
        schema: [dataC(6), dataC(5)],
      },
    };
  },
  methods: {
    onCMD(name, ...args) {
      console.log('onCMD:' + name, ...args);
    },
    onClick() {
      // this.dataOne.schema[0].title = '我是标题-' + +new Date();
      this.dataTable.schema = [dataC(4)]
    },
  },
};
</script>
02020 commented 3 years ago

demo

toCollapse

<template>
  <div>
    <to-render :f="null" :schema="schema" :on="onCMD" @cmd="onCMD" />
    <Button @click="onClickSchema">schema</Button>
    <hr style="margin: 20px 0" />
    <to-render
      :f="dataOne.f"
      :props="dataOne.props"
      :on="dataOne.on"
      :schema="dataOne.schema"
      @cmd="onCMD"
    />

    <Button @click="onClick">点击</Button>
    <hr style="margin: 20px 0" />
  </div>
</template>

<script>
import { toRender, toFunctional, toComponent, toCollapse } from '../';

const schema = {
  component: 'Collapse',
  on: ['on-change'],
  child: [
    {
      component: 'Panel',
      child: [
        '我是标题-1',
        {
          slot: 'content',
          child: 'slot="content"是组件[Panel-slot]中的组件名 ',
        },
      ],
    },
    {
      component: 'Panel',
      child: [
        '我是标题-2',
        {
          slot: 'content',
          child: '这里是内容, 可以放组件',
        },
      ],
    },
  ],
};

export default {
  components: {
    toRender: toRender(),
  },
  data() {
    return {
      schema: schema,
      dataOne: {
        f: toCollapse,
        on: this.onCMD,
        props: {
          key: 'ddd',
        },
        schema: [
          { name: 'dd', child: 'ss', title: '我是标题-1' },
          { name: 'dd', child: 'ss', title: '我是标题-2' },
          { name: 'dd', child: 'ss', title: '我是标题-3' },
        ],
      },
    };
  },
  methods: {
    onCMD(name, ...args) {
      console.log('onCMD:' + name, ...args);
    },
    onClickSchema() {
      this.schema.child[0].child[0] = '我是标题-' + +new Date();
    },
    onClick() {
      this.dataOne.schema[0].title = '我是标题-' + +new Date();
    },
  },
};
</script>

toCollapseCard


<template>
  <div>
    <to-render v-bind="dataOne"> </to-render>
    <hr style="margin: 20px 0" />
    <to-render v-bind="dataSchema"> </to-render>

    <Button @click="onClick">点击</Button>
  </div>
</template>

<script>
import { toRender, toCollapseCard, toETable, toRow } from '../';

// 构造demo数据
const rowData = [
  ['a', 'bg-green-500', '01室'],
  ['b', 'bg-red-500', '02室'],
  ['c', 'bg-blue-500', '03室'],
];
const floor = (r) => (x) => {
  const xx = [...x];
  xx[2] = r + xx[2];
  return xx;
};
const dataC = (r) => rowData.map(floor(r));

// 配置样式
const style = {
  table: {
    class: 'border border-blue-500',
    style: { width: '500px' },
  },
  row: 'grid grid-cols-6',
  col: 'text-sm font-semibold text-center py-2 m-px text-white ',
};

// 数据组装
const schema = [
  {
    name: 'a',
    title: '分组-标题1',
    child: [
      {
        title: '标题1',
        child: toETable(style)([dataC(6), dataC(5)]),
      },
      {
        title: '标题2',
        extra: '离开',
        child: toETable(style)([dataC(6), dataC(5)]),
      },
    ],
  },
  {
    name: 'b',
    title: '分组-标题2',
    child: [{ title: '标题1', extra: '离开', child: toRow(style)(dataC(8)) }],
  },
];

const Icon = {
  component: 'Icon',
  props: {
    type: 'md-cart',
    size: '22',
  },
};

const schemaFull = {
  component: 'Collapse',
  on: ['on-change'],
  props: {
    value: 'c',
  },
  child: [
    {
      component: 'Panel',
      props: { name: 'c' },
      child: [
        '我是标题',
        {
          slot: 'content',
          child: [
            {
              component: 'Card',
              child: [
                { slot: 'title', child: toRow(style)(dataC(1)) },
                {
                  slot: 'extra',
                  on: [['click', 'click-title']],
                  child: Icon,
                },
                toETable(style)([dataC(6), dataC(5)]),
                '---',
                toETable(style)([dataC(8), dataC(7)]),
              ],
            },
            '---',
            {
              component: 'Card',
              child: [
                { slot: 'title', child: toRow(style)(dataC(2)) },
                {
                  slot: 'extra',
                  child: Icon,
                  on: [['click', 'click-title']],
                },
                toETable(style)([dataC(5), dataC(4)]),
              ],
            },
          ],
        },
      ],
    },
  ],
};

export default {
  components: { toRender: toRender() },
  data() {
    return {
      dataOne: {
        f: toCollapseCard,
        on: this.onCMD,
        props: {
          value: 'a_0',
        },
        schema: schema,
      },
      dataSchema: {
        f: null,
        on: this.onCMD,
        props: {
          key: 'ddd',
        },
        schema: schemaFull,
      },
    };
  },
  methods: {
    onCMD(name, ...args) {
      console.log('onCMD:' + name, ...args);
    },
    onClick() {
      this.flag = !this.flag;
      this.dataOne.props = {
        value: this.flag ? 'b_1' : 'a_0',
      };
    },
  },
};
</script>