02020 / vite-kit

0 stars 0 forks source link

Grade-Render-1.0 #6

Open 02020 opened 3 years ago

02020 commented 3 years ago

动态组件-for的封装

二级嵌套

<template>
  <component :is="config.component" 
  v-bind="config.props"
  :style="config.style"
  :class="config.class"
  >
    <component
      :is="group.component"
      v-for="(group, pid) in config.items"
      :key="group._key"
      :class="group.class"
      v-bind="group.props"
    >
      <component
        v-for="(comp, index) in group.child"
        @input="onCommand(comp.name, $event)"
        @click="onClick(pid, index)"
        v-bind="comp.props"
        :class="comp.class"
        :is="comp.component"
        :key="comp.name"
      >
        <slot :pid="pid" :id="index" :data="comp" :value="comp.value"> </slot>
      </component>
    </component>
  </component>
</template>

<script>
//  两级
export default {
  name: 'grade-builder',
  components: {}, // 由外部传入
  props: {
    config: {
      type: Object,
      required: true,
    },
  },

  data() {
    return {};
  },

  computed: {},

  created() {
    this.prepare();
  },

  methods: {
    prepare() {
      this.config.items.forEach((group, index) => {
        group._key = `group_${index}`;
        group.child.forEach((child, i) => {
          child.component = child.component || 'div';
          child.name = `child_${i}`;
        });
      });
    },
    onCommand(name, args) {
      this.$emit('cmd', name, args);
    },
    onClick(r, c) {
      this.$emit('cmd', r, c);
    },
  },
};
</script>
02020 commented 3 years ago

config 配置构建

1.0 版本

const toConfig = (list) => {
  // 第2级
  const f2 = (x) => {
    return {
      component: 'div',
      class: 'text-sm font-semibold text-center py-2 m-px ' + x.class,
      title: x.title,
    };
  };

  // 第1级
  const f1 = (f) => (x) => {
    return {
      component: 'div',
      class: 'grid grid-cols-6',
      // slot 中的数据 ==> card
      child: x.map(f),
    };
  };
 // 第0级
  const f0 = (list, f1, f2) => {
    const f = f1(f2);
    // 外包为圈的配置
    return {
      component: 'div',
      style: {
        width: '700px',
      },
      class: 'border border-blue-500',
      items: list.map(f),
    };
  };

  return f0(list, f1, f2);
};

2.0 版本


const toConfig = (f, option) => (list) =>
  list.map((x) => {
    return {
      ...option,
      child: x.map(f),
    };
  });

// 给组件使用
const toBuild = (option0, option1, f) => (list) => {
  const fn = toConfig(f, option1);
  return { ...option0, items: fn(list) };
};

// 组件中的配置
const option0 = {
  component: 'div',
  style: { width: '700px' },
  class: 'border border-blue-500',
};

const option1 = {
  component: 'div',
  class: 'grid grid-cols-6',
};

// 第2级
const f2 = (x) => {
  return {
    component: 'div',
    class: 'text-sm font-semibold text-center py-2 m-px ' + x.class,
    title: x.title,
  };
};
const build = toBuild(option0, option1, f2);
// build(list);
02020 commented 3 years ago

grade-builder 无限极嵌套 - 核心代码


import { renderC } from '.';
export default {
  name: 'grade-builder',
  props: {
    config: {
      type: Object,
    },
  },
  created() {
    this.prepare();
  },

  methods: {
    prepare() {
      this.config.child &&
        this.config.child.forEach((group, index) => {
          group._key = `group_${index}`;
          group.child &&
            group.child.forEach((child, i) => {
              child.component = child.component || 'div';
              child.name = `child_${i}`;
            });
        });
    },
    onCommand(name, args) {
      this.$emit('cmd', name, args);
    },
    onClick(r, c) {
      this.$emit('cmd', r, c);
    },
  },
  render(h) {
    const scopedSlots = this.$scopedSlots;
    const context = this;
    return renderC(h, { context, scopedSlots }, this.config);
  },
};

utils

// 通过 template 来渲染
export const slotsTemplate = (slots, scopedSlots) => {
  if (!slots) return;
  return Object.keys(slots).reduce((acc, key) => {
    acc[key] = (props) => {
      if (Array.isArray(slots[key])) {
        return slots[key].map(scopedSlots[key]);
      } else {
        return scopedSlots[key](slots[key]);
      }
    };
    return acc;
  }, {});
};

// render 函数封装-层级渲染
export const renderC = (h, { context, scopedSlots }, x) => {
  // 通过 Render 来渲染
  const slotsC = (slots) => {
    if (!slots) return;
    return Object.keys(slots).reduce((acc, key) => {
      acc[key] = (props) => {
        if (Array.isArray(slots[key])) {
          return slots[key].map(renderG);
        } else {
          // console.log(slots[key]);
          return renderG(slots[key]);
        }
      };
      return acc;
    }, {});
  };

  // 递归渲染
  const renderG = (x) => {
    if (!x || typeof x === 'string') {
      return x;
    }

    let child = !x.child ? [renderG(x.content)] : x.child.map(renderG);

    const _scopedSlots = {
      ...slotsTemplate(x.slotsT, scopedSlots),
      ...slotsC(x.slots),
    };

    const on = !x.on
      ? {}
      : x.on.reduce((acc, key) => {
          const _key = Array.isArray(key)
            ? key
            : [key, x.key ? key + '-' + x.key : key];
          acc[_key[0]] = (...args) => {
            context.$emit('cmd', _key[1], x, ...args);
          };
          return acc;
        }, {});

    // console.log(x.component, _scopedSlots);
    const resp = h(
      x.component || 'div',
      {
        class: x.class,
        style: x.style,
        props: x.props,
        slot: x.slot,
        on,
        scopedSlots: _scopedSlots,
      },
      child
    );
    return resp;
  };
  return renderG(x);
};
02020 commented 3 years ago

无限层级 demo

1. 简单板

//   <builder :config="config"> </builder>

import builder, { toConfigGrade } from './grade-builder';
const classCell = 'text-sm font-semibold text-center py-2 m-px text-white';

// 组件中的配置
const option0 = {
  component: 'div',
  class: 'border border-blue-500',
  style: { width: '500px' },
};

const option1 = { component: 'div', class: 'grid grid-cols-4' };

// 第2级
const f2 = (x) => {
  return {
    component: 'div',
    class: classCell + x.class,
    content: x.content,
  };
};

const build = toConfigGrade(option0, option1, f2);

const list = [
  [
    { class: classCell + ' bg-green-500 ', content: '601室' },
    { class: classCell + ' bg-red-500', content: '602室' },
    { class: classCell + ' bg-blue-500', content: '603室' },
    { class: classCell + ' bg-pink-500', content: '604室' },
  ],
  [
    { class: classCell + ' bg-pink-500', content: '501室' },
    { class: classCell + ' bg-green-500 ', content: '502室' },
    { class: classCell + ' bg-red-500', content: '503室' },
    { class: classCell + ' bg-blue-500', content: '504室' },
  ],
  [
    { class: classCell + ' bg-blue-500', content: '401室' },
    { class: classCell + ' bg-pink-500', content: '402室' },
    { class: classCell + ' bg-green-500 ', content: '403室' },
    { class: classCell + ' bg-red-500', content: '404室' },
  ],
];

export default {
  components: { builder },
  data() {    return {      config: build(list)    };  },
};

2. template渲染方式

<template>
  <builder :config="config">
    <!-- 方式一: 使用template渲染 -->
    <template v-slot:content="data">
      <div :class="[data.class]">
        <div :key="ci" v-for="(col, ci) in data.child" :class="[col.class]">
          {{ col.content }}
        </div>
      </div>
    </template>
  </builder>
</template>

<script>
import builder from './grade-builder';
const classCell = 'text-sm font-semibold text-center py-2 m-px text-white';
const table = [  {
    class: 'grid grid-cols-6',
    on: ['click'],
    child: [{ class: classCell + ' bg-green-500 ', content: '601室' }],
  }];

const config = {
  component: 'Collapse',
  on: ['on-change'],
  child: [ {
      component: 'Panel',
      content: '我是标题',
      // 方式二: 使用render来渲染
      slots: { content: table },
    }, {
      component: 'Panel',    content: 'Panel-child',
      // 方式一: 使用template渲染
      slotsT: {        content: table      },  
    },
  ],
};

export default {
  components: { builder },
  data() {    return { config };  }
};
</script>

3. slots 子组件 事件处理

//  <builder :config="config" @cmd="onCMD"> </builder>

import builder from './grade-builder';

const classCell = 'text-sm font-semibold text-center py-2 m-px text-white ';

const table = [
  {
    class: 'grid grid-cols-6',
    child: [
      { class: classCell + ' bg-green-500 ', content: '601室' },
      { class: classCell + ' bg-red-500', key: 'b', content: '602室' },
      { class: classCell + ' bg-blue-500', key: 'c', content: '603室' },
      { class: classCell + ' bg-pink-500', key: 'd', content: '604室' },
    ],
  },
];

table.forEach((x) => {
  x.child.forEach((element) => { 
    element.on = ['click'];
  });
});

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

const config = {
  component: 'Collapse',
  on: ['on-change'],
  child: [
    {
      component: 'Panel',
      content: '我是标题',
      slots: {
        content: {
          component: 'Card',
          child: [
            { slot: 'title', child: table },
            { slot: 'extra', content: Icon, on: [['click', 'click-title']] },
            ...table, ...table],
        },
      },
    },
  ],
};

export default {
  components: { builder },
  data() {    return { config };  },
  methods: {
    onCMD(name, ...args) {
      console.log('onCMD:' + name, ...args);
    },
  },
};
02020 commented 3 years ago

过程代码

utils.js

const isObject = (val) => val !== null && typeof val === 'object';

/**
 * 递归查找关键字所在位置
 * @param {*} key
 * @param {*} value
 * @param {*} src
 * @returns {Object}
 */
const __toConfigKey = (src, key, value) => {
  return Object.keys(src).reduce((acc, k) => {
    acc[k] = src[k];

    if (isObject(acc[k]) && !Array.isArray(acc[k])) {
      acc[k] = __toConfigKey(acc[k], key, value);
    } else if (k === key && acc[k] === null) {
      if (key === 'child' && !Array.isArray(value)) {
        console.error(`value值必须为[Array]:${value}`);
      }
      acc[k] = value;
    }
    return acc;
  }, {});
};

export const toConfigKey = (src, key) => (value) =>
  __toConfigKey(src, key, value);

/**
 * 渲染子级 查找 child 所在的层级
 * @param {*} option 父级的配置
 * @param {*} f
 * @returns {Object} option
 */
export const toChild = (option, f) => (list = []) =>
  toConfigKey(option, 'child')(list.map(f));

// 以下部分需要升级
// 数据格式转换 双层数组
// const list  = [ [],[],[] ]
const toConfigEach = (f, option) => (list) =>
  list.map((x) => {
    return {
      ...option,
      child: x.map(f),
    };
  });

// 数据格式转换-给组件使用
export const toConfigGradeTwo = (option0, option1, f) => (list) => {
  const fn = toConfigEach(f, option1);
  return { ...option0, child: fn(list) };
};

collapse-builder.js

export default {
  name: 'collapse-builder',
  props: {
    value: {}, // model
    config: {
      type: Array,
      required: true,
    },
    accordion: Boolean,
    simple: Boolean,
  },

  methods: {
    onCommand(...args) {
      this.$emit('cmd', ...args);
    },
    __input(v) {
      this.$emit('input', v);
    },
  },

  render(h) {
    this.config.forEach((group, index) => {
      group.name = group.name || `panel_${index}`;
    });
    const panel = ({ label, name, content }) =>
      h(
        'Panel',
        {
          props: { name },
          scopedSlots: {
            content: () => this.$scopedSlots.default(content),
          },
        },
        label //  1212 增加按钮及图标
      );

    return h(
      'Collapse',
      {
        on: {
          'on-change': this.onCommand,
        },
        model: {
          value: this.value,
          callback: this.__input,
          expression: 'value',
        },
        props: {
          accordion: this.accordion,
          simple: this.simple,
        },
      },
      this.config.map((x) => panel(x))
    );
  },
};
02020 commented 3 years ago

grade-builder 无限极嵌套 - 核心代码 1.1

01.core-render-渲染部分

// 通过 template 来渲染
export const slotsTemplate = (slots, scopedSlots) => {
  if (!slots) return;
  return Object.keys(slots).reduce((acc, key) => {
    acc[key] = (props) => {
      if (Array.isArray(slots[key])) {
        return slots[key].map(scopedSlots[key]);
      } else {
        return scopedSlots[key](slots[key]);
      }
    };
    return acc;
  }, {});
};

// render 函数封装-层级渲染
export const renderC = (h, { context, scopedSlots }, x) => {
  // 通过 Render 来渲染
  const slotsC = (slots) => {
    if (!slots) return;
    return Object.keys(slots).reduce((acc, key) => {
      acc[key] = (props) => {
        if (Array.isArray(slots[key])) {
          return slots[key].map(renderG);
        } else {
          // console.log(slots[key]);
          return renderG(slots[key]);
        }
      };
      return acc;
    }, {});
  };

  // 递归渲染
  const renderG = (x) => {
    if (!x || typeof x === 'string') {
      return x;
    }

    let child = Array.isArray(x.child)
      ? x.child.map(renderG)
      : [renderG(x.child)];

    const _scopedSlots = {
      ...slotsTemplate(x.slotsT, scopedSlots),
      ...slotsC(x.slots),
    };

    // 事件采用分发的方式
    const on = !x.on
      ? {}
      : x.on.reduce((acc, key) => {
          const _key = Array.isArray(key)
            ? key
            : [key, x.key ? key + '-' + x.key : key];
          acc[_key[0]] = (...args) => {
            context.$emit('cmd', _key[1], x, ...args);
          };
          return acc;
        }, {});

    // console.log(x.component, _scopedSlots);
    const resp = h(
      x.component || 'div',
      {
        class: x.class,
        style: x.style,
        props: x.props,
        slot: x.slot, // 子组件渲染
        on,
        scopedSlots: _scopedSlots,
      },
      child
    );
    return resp;
  };
  return renderG(x);
};

export default {
  name: 'grade-builder',
  props: {
    config: {
      type: Object,
    },
  },
  render(h) {
    const scopedSlots = this.$scopedSlots;
    const context = this;
    return renderC(h, { context, scopedSlots }, this.config);
  },
};

02.builder-组件构建

import { Icon, Collapse, Panel, PanelCard, CardTitle, List, ListItem } from '.';

export const RR = {
  /**
   *
   */
  o: (f) => (g) => (x) => f(g(x)),

  of: (x) => [x],
};

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

/**
 * 带卡片样式的折叠面板
 * @param {*} props
 */
export const toCollapseCard = (props, cb = (x) => x) =>
  fMap((x, index) => {
    const fc = PanelCard(x.name || '' + index, x.group);
    const title = CardTitle(x.title, x.extra);
    title.push(x);
    return cb(fc(title), index);
  }, Collapse(props.value));

/**
 * 折叠面板
 * @param {*} props
 */
export const toCollapse = (props, cb = (x) => x) =>
  fMap((x, index) => {
    const fc = Panel(x.name || '' + index, x.group || x.title);
    return cb(fc(x), index);
  }, Collapse(props.value));

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

/**
 *
 * @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 toTable = ({ 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,
  };
};

03.config-组件的配置

// 组件的配置清单,供Render使用

/**
 * iview-图标
 * @param {*} type
 * @param {*} size
 * @param {*} color
 */
export const Icon = (type, size = 30, color = '#2d8cf0') => {
  return {
    component: 'Icon',
    props: {
      type,
      size,
      color,
    },
  };
};

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

/**
 * iview-卡片标题
 * @param {*} title
 * @param {*} extra
 */
export const CardTitle = (title, extra) => {
  title = title || Icon('ios-attach');
  extra = extra || Icon('md-add');
  return [
    {
      slot: 'title',
      child: title,
      // 原事件名称->新事件名称
      on: [['click', 'click-extra']],
    },
    {
      slot: 'extra',
      child: extra,
      on: [['click', 'click-extra']],
    },
  ];
};
/**
 * iview-折叠面板
 * @param {*} value
 */
export const Collapse = (value = []) => (child) => {
  return {
    component: 'Collapse',
    props: {
      value,
    },
    on: ['on-change', ['input', 'input-collapse']],
    child: child,
  };
};

/**
 * iview-折叠面板-项
 * @param {*} name
 * @param {*} title
 */
export const Panel = (name = '', title = '标题') => (child) => {
  return {
    component: 'Panel',
    props: {
      name: name,
    },
    child: title,
    slots: {
      content: child,
    },
  };
};
/**
 * iview-带卡片样式的折叠面板
 * @param {*} name
 * @param {*} title
 */
export const PanelCard = (name = '', title = '标题') => (child) => {
  return {
    component: 'Panel',
    props: {
      name,
    },
    child: title,
    slots: {
      content: {
        component: 'Card',
        child: child,
      },
    },
  };
};
/**
 * iview-列表
 * @param {*} props
 */
export const List = (props) => (child) => {
  return {
    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',
    slots: {
      action: {
        child: action,
      },
      extra: {
        child: extra,
        on: [['click', 'click-extra']],
      },
    },
    child: [
      { slot: 'action' }, // 只能这样配置
      { slot: 'extra' }, // 只能这样配置
      {
        component: 'ListItemMeta',
        child: [
          {
            slot: 'avatar',
            child: avatar,
          },
          { slot: 'title', child: title },
          { slot: 'description', child: description },
        ],
      },
    ],
  };
};

const index = {   Icon,  Collapse,  Panel,  PanelCard};

export default index;