一个面试题,根据json结构生成html表格

我的输入是

{
    A1: {
        B1: {
            C1: {
                D1: 1233,
                D2: 11
            },
            C2: {
                D1: 10,
                D2: 10
            }
        },
        B2: {
            C1: {
                D1: 10,
                D2: 11
            },
            C2: {
                D1: 10,
                D2: 10
            },
            C3: {
                D1: 10,
                D2: 10
            }
        }
    }
}

用什么框架都可以,只要求输出以下table, json的最后一个节点就是table的最后一个column,并且只能占据一行,也就是rowspan要为1.然后难点在于怎么判断前面的column占用的rowspan,我一点思路的都没有囧...

image

更新:
如果有代码演示最好了,底下评论的朋友谢谢了,我看了文章还是想不出来改怎么写代码...

阅读 2.5k
3 个回答

树的递归,根据后代叶子节点数量决定当前节点所跨的行,深度优先适合此场景。
代码实现:http://jsrun.net/AXaKp/edit

function getRows (data) {
    const result = []
    const values = Object.values(data)
    if (values.some(v => typeof v !== 'object' || !v || !Object.keys(v).length)) {
        result.push([{
            text: Object.keys(data).join(', '),
            rowspan: 1
        }])
        return result
    }
    for (let k in data) {
        const pathList = getRows(data[k])
        if (pathList.length) {
            const first = pathList[0]
            first.unshift({
                text: k,
                rowspan: pathList.length
            })
        }
        result.push(...pathList)
    }
    return result
}

function generateHtml (rows, head) {
    return `
    <table border="1">
        <tr>
          ${ head.map(h => `<th>${h}</th>`).join('') }
        </tr>
        ${rows.map(r => {
            return `
            <tr>
                ${r.map(({ text, rowspan }) => `<td rowspan="${rowspan}">${text}</td>`).join('')}
            </tr>
            `
        }).join('\n')}
    </table>
    `
}

function render (data) {
    const rows = getRows(data)
    const head = (rows[0] || []).map(r => `Column-${r.text[0]}`)
    const html = generateHtml(rows, head)
    const div = document.createElement('div')
    div.innerHTML = html
    document.body.appendChild(div)
}

测试:

render(data)

效果:

image.png

这是一个树形结构,遍历这棵树,通过递归把每个节占据的行数算出来(就是每个子节点占据的行数之后)。然后你在渲染表格的时候就知道 rowspan 该是多少了(注意:如果为 1 可以省略哦)

给个提示

function calcRows(node) {
    const keys = Object.keys(node);
    const rowspan = node.rowspan = keys.reduce((sum, key) => sum + calcRows(node[key]), 0);
    return rowspan;
}

把JSON对象视为树,表格的单元格就是节点,每一行就是节点的路径。这样每个单元格的rowSpan就是其路径的数量。

<table id="table"></table>
const data = {
    A1: {
        B1: {
            C1: {
                D1: 1233,
                D2: 11
            },
            C2: {
                D1: 10,
                D2: 10
            }
        },
        B2: {
            C1: {
                D1: 10,
                D2: 11
            },
            C2: {
                D1: 10,
                D2: 10
            },
            C3: {
                D1: 10,
                D2: 10
            }
        }
    }
}

// 利用递归方式的深度优先遍历算法计算每个节点的路径数量,并保持路径列表
function getPath(node, key) {    
  const pathList = Object.keys(node).reduce((pathList, key) => {
    return pathList.concat(getPath(node[key], key));
  }, [])
  const pathCount = pathList.length;
  pathList.map((path, index) => {
    // 这里特殊处理下:只在第一条路径里插入当前节点key,其他路径不插入,方便生成`tr`
    path.unshift(!!index ? null : { key, pathCount })
    return path;
  });

  return pathList.length ? pathList : [[{ key, pathCount: 1 }]]
}

// 路径列表转成`tr`列表
function toTableRows(data) {
  const pathList = getPath(data, 'root');
  const rows = pathList.map((row, index) => {
    const tr = ['<tr>'];
    for(let i = 1; i < row.length; ++i) {
        if(row[i]) {
            const { key, pathCount } = row[i];        
            tr.push(`<td rowSpan="${pathCount}">${key}</td>`)
        }        
     }
     return tr.join('')
  })
  return rows.join('')
}

document.getElementById('table').innerHTML = toTableRows(data)
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题