Open 02020 opened 3 years ago
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);
};
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);
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);
},
};
// 通过 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);
};
// <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) }; },
};
<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>
// <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);
},
},
};
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) };
};
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))
);
},
};
// 通过 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);
},
};
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,
};
};
// 组件的配置清单,供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;
动态组件-for的封装
二级嵌套