首页 > 解决方案 > 如何在表格上显示这些数据?

问题描述

我正在构建一个 vue js 应用程序,我正在尝试用这样的数据构建一个 html 表。

{Header}: 
 {
   {Subheader}: {
       2021-05-26 00:09: [1, 2, 3]
       2021-05-26 00:13: [10]
       2021-05-26 00:16: [6]
{Header}
   {Subheader}: {
       2021-05-26 00:09: [2, 6, 1]
       2021-05-26 00:13: [50]
       2021-05-26 00:16: [10]
{Header}
   {Subheader}: {
       2021-05-26 00:09: [4]
       2021-05-26 00:13: [5, 5, 8]
       2021-05-26 00:16: [4]

   ...

现在标题和子标题可以是任何东西,这就是我将其放在括号中的原因,诀窍是显示标题然后将其合并,以便它可以匹配其自己的子标题的长度,最后在每一行上放置来自 Subheader 值的相应项目所以根据上面的示例,它将是这样的

|      Header       | Header  |
|Subheader|Subheader|Subheader|
|    1    |    2    |    4    |
|    2    |    6    |    5    |
|    3    |    1    |    5    |
|   10    |   50    |    8    |
|    6    |   10    |    4    |

到目前为止,这是我的 vue.js 代码

<template>
    <div ref="main" class="relative h-full pt-10 analytics">
        <table class="w-full" v-if="fullCycle">
            <thead>
                <tr>
                    <th class="border" :colspan="Object.values(header).length" v-for="(header, i) in fullCycle" :key="i">
                        {{ i }}
                    </th>
                </tr>
                <tr>
                    <th class="border" v-for="(item, index) in getSubValues(fullCycle)" :key="index">
                        {{ item }}
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(item, i) of getThirdLevel()" :key="i">
                    <td class="border" v-for="(val, index) in item" :key="index">{{ val }}</td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

<script>

export default {
    data() {
        return {
            fullCycle: null,
        };
    },
    mounted() {
        this.init();
    },
    methods: {
        async init() {
            await this.fetchData();
        },
        async fetchData() {
            try {
                const res = await this.$axios.get('/redacted/cool-api');
                this.fullCycle = res.data;
            } catch (err) {
                console.log(err);
            }
        },
        getSubValues(obj) {
            const values = [];

            for (const item of Object.keys(obj)) {
                for (const index in obj[item]) {
                    values.push(index);
                }
            }

            return values;
        },
        getThirdLevel() {
            const obj = this.fullCycle;
            const levelTwoLength = this.getSubValues(obj).length;
            const values = [];

            for (const item of Object.keys(obj)) {
                for (const index of Object.keys(obj[item])) {
                    values.push(Object.values(obj[item][index]));
                }
            }

            let max = 0;
            for (const item of values) {
                if (max < item.length) max = item.length;
            }
            let iterator = 0;

            let finalArray = [];
            for (const item of values) {
                for (let i = 0; i < max; i++) {
                    console.log(item[i]);
                    if (item[i]) {
                        for (let val of item[i]) {
                            if (!finalArray[iterator]) finalArray[iterator] = [];
                            finalArray[iterator].push(val ? val : '');
                            if (finalArray[iterator].length === levelTwoLength) iterator++;
                        }
                    } else {
                        if (!finalArray[iterator]) finalArray[iterator] = [];
                        finalArray[iterator].push('');
                        if (finalArray[iterator].length === levelTwoLength) iterator++;
                    }
                }
            }

            return finalArray;
        },
    },
};
</script>

现在上面的代码有点工作,但问题是因为每天可能有很多项目,它把它们并排而不是从上到下。这finalArray是一个新创建的多维数组,它是通过遍历每个子标题的每个值并通过总子标题长度限制它来构建的,基本上将其推入一个新数组。同样,每个标题、子标题和日期都可以不同。每个标题可以有多个子标题

谢谢

标签: javascriptvue.js

解决方案


我不完全明白你的例子,但我认为是这样的:

 const data = {
  "header_1_1": {
    "subheader_1_1_1": {
      "2021-05-26 00:09": [1, 2, 3],
      "2021-05-26 00:13": [10],
      "2021-05-26 00:16": [6],
    },
    "subheader_1_1_2": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
  },
  "header_1_2": {
    "subheader_1_2_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  }
}

我认为创建表将具有的行更容易,所以让我们从它开始:

// sample data
const data = {
  "header_1_1": {
    "subheader_1_1_1": {
      "2021-05-26 00:09": [1, 2, 3],
      "2021-05-26 00:13": [10],
      "2021-05-26 00:16": [6],
    },
    "subheader_1_1_2": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
  },
  "header_1_2": {
    "subheader_1_2_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  },
}

// reading the tree structure
const readTree = (arr) => {
  let ret = []
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      ret = [...ret, ...arr[i]]
    } else {
      ret = [...ret, readTree(Object.values(arr[i]))]
    }
  }
  return ret
}

// creating rows from columns
const createRows = (columns) => {
  let ret = []
  for (let i = 0; i < columns[0].length; i++) {
    if (typeof ret[i] === "undefined") ret[i] = []
    for (let j = 0; j < columns.length; j++) {
      ret[i].push(columns[j][i])
    }
  }
  return ret
}

const columns = readTree(Object.values(data)).flat()
const rows = createRows(columns)

console.log("result of readTree:", columns)
console.log("result of createRows:", rows)

// creating & adding row HTML
const tbody = document.getElementById("tbody")
const createRowHtml = (data) => {
  let html = ''
  data.forEach(row => {
    html += '<tr>'
    row.forEach(cell => {
      html += `<td>${cell}</td>`
    })
    html += '</tr>'
  })
  return html
}

tbody.innerHTML = createRowHtml(rows)
<table id="table">
  <tbody id="tbody">
  </tbody>
</table>

现在,我们有了行,让我们处理标题。

标题

// sample data
const data = {
  "header_1_1": {
    "subheader_1_1_1": {
      "2021-05-26 00:09": [1, 2, 3],
      "2021-05-26 00:13": [10],
      "2021-05-26 00:16": [6],
    },
    "subheader_1_1_2": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
  },
  "header_1_2": {
    "subheader_1_2_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  },
}

// reading the tree structure for headers
const readHeaders = (obj) => {
  let ret = []
  ret = [...ret, ...Object.entries(obj).map(([key, val]) => {
    const a = readHeaders(val)
    return {
      text: key,
      items: a.filter(({
        items
      }) => !items.length),
      colspan: a.length || 0,
    }
  })]
  return ret
}

const reduceHeaders = (a, c, i) => {
  const {
    text,
    colspan
  } = c
  if (typeof a[i] === "undefined") a[i] = []
  if (c.items.length) {
    a[i + 1] = [...(a[i + 1] ||  []), ...c.items.reduce((a, c) => reduceHeaders(a, c, i + 1), []).flat()]
  }
  a[i].push({
    text,
    colspan
  })
  return a
}

const headers = readHeaders(data)
const reducedHeaders = headers.reduce((a, c) => {
  return reduceHeaders(a, c, 0)
}, [])

console.log("headers:", headers)
console.log("reducedHeaders:", reducedHeaders)

// creating & adding header HTML
const thead = document.getElementById("thead")
const createHeaderRowHtml = (data) => {
  let html = ''
  data.forEach((row, i) => {
    html += '<tr>'
    row.forEach(cell => {
      html += `<th colspan="${ i < data.length - 1 ? cell.colspan : 1 }">${cell.text}</th>`
    })
    html += '</tr>'
  })
  return html
}

thead.innerHTML = createHeaderRowHtml(reducedHeaders)
table {
  border-collapse: collapse;
}

table,
tr,
th,
td {
  border: 1px solid black;
}
<table id="table">
  <thead id="thead">
  </thead>
</table>

所以,有标题。

把它放在一起

(使用修改后的数据集来展示灵活性):

// sample data
const data = {
  "header_1_1": {
    "subheader_1_1_1": {
      "2021-05-26 00:09": [1, 2, 3],
      "2021-05-26 00:13": [10],
      "2021-05-26 00:16": [6],
    },
    "subheader_1_1_2": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
    "subheader_1_1_3": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
  },
  "header_1_2": {
    "subheader_1_2_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  },
  "header_1_3": {
    "subheader_1_3_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
    "subheader_1_3_2": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  },
}

// reading the tree structure
const readTree = (arr) => {
  let ret = []
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      ret = [...ret, ...arr[i]]
    } else {
      ret = [...ret, readTree(Object.values(arr[i]))]
    }
  }
  return ret
}

// creating rows from columns
const createRows = (columns) => {
  let ret = []
  for (let i = 0; i < columns[0].length; i++) {
    if (typeof ret[i] === "undefined") ret[i] = []
    for (let j = 0; j < columns.length; j++) {
      ret[i].push(columns[j][i])
    }
  }
  return ret
}

const columns = readTree(Object.values(data)).flat()
const rows = createRows(columns)

// creating & adding row HTML
const tbody = document.getElementById("tbody")
const createRowHtml = (data) => {
  let html = ''
  data.forEach(row => {
    html += '<tr>'
    row.forEach(cell => {
      html += `<td>${cell}</td>`
    })
    html += '</tr>'
  })
  return html
}

tbody.innerHTML = createRowHtml(rows)

// reading the tree structure for headers
const readHeaders = (obj) => {
  let ret = []
  ret = [...ret, ...Object.entries(obj).map(([key, val]) => {
    const a = readHeaders(val)
    return {
      text: key,
      items: a.filter(({
        items
      }) => !items.length),
      colspan: a.length || 0,
    }
  })]
  return ret
}

const reduceHeaders = (a, c, i) => {
  const {
    text,
    colspan
  } = c
  if (typeof a[i] === "undefined") a[i] = []
  if (c.items.length) {
    a[i + 1] = [...(a[i + 1] ||  []), ...c.items.reduce((a, c) => reduceHeaders(a, c, i + 1), []).flat()]
  }
  a[i].push({
    text,
    colspan
  })
  return a
}

const headers = readHeaders(data)
const reducedHeaders = headers.reduce((a, c) => {
  return reduceHeaders(a, c, 0)
}, [])

// creating & adding header HTML
const thead = document.getElementById("thead")
const createHeaderRowHtml = (data) => {
  let html = ''
  data.forEach((row, i) => {
    html += '<tr>'
    row.forEach(cell => {
      html += `<th colspan="${ i < data.length - 1 ? cell.colspan : 1 }">${cell.text}</th>`
    })
    html += '</tr>'
  })
  return html
}

thead.innerHTML = createHeaderRowHtml(reducedHeaders)
table {
  border-collapse: collapse;
}

table,
tr,
th,
td {
  border: 1px solid black;
}
<table id="table">
  <thead id="thead">
  </thead>
  <tbody id="tbody">
  </tbody>
</table>

结论

如您所见,这是一个相当复杂的解决方案。它可以通过统一标题和行 HTML 创建功能来简化,如果标题和项目的所有功能都可以一次性完成,也可以提高效率。如果子标题下的数据集长度不同,它并不完全灵活(不处理任意深度的源数据)并且容易出错。但是整体概念非常好——值得展开(在这个级别),即使你已经设法解决了这个问题。:)

解决方案

// mock dataset large
const initData = () => ({
  "header_1_1": {
    "subheader_1_1_1": {
      "2021-05-26 00:09": [1, 2, 3],
      "2021-05-26 00:13": [10],
      "2021-05-26 00:16": [6],
    },
    "subheader_1_1_2": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
    "subheader_1_1_3": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
  },
  "header_1_2": {
    "subheader_1_2_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  },
  "header_1_3": {
    "subheader_1_3_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
    "subheader_1_3_2": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  },
})

// mock dataset short
const initDataShort = () => ({
  "header_1_1": {
    "subheader_1_1_1": {
      "2021-05-26 00:09": [1, 2, 3],
      "2021-05-26 00:13": [10],
      "2021-05-26 00:16": [6],
    },
    "subheader_1_1_2": {
      "2021-05-26 00:09": [2, 6, 1],
      "2021-05-26 00:13": [50],
      "2021-05-26 00:16": [10],
    },
  },
  "header_1_2": {
    "subheader_1_2_1": {
      "2021-05-26 00:09": [4],
      "2021-05-26 00:13": [5, 5, 8],
      "2021-05-26 00:16": [4],
    },
  },
})

// utility function: transpose
const transpose = (arr) => {
  return arr[0].map((_, colIndex) => arr.map(row => row[colIndex]));
}

// rendering the cell: using a render
// function, so the "th" or "td" can
// be passed down dynamically from the
// parent component
Vue.component("TableCell", {
  props: ["colname", "tag"],
  render(h) {
    return h(this.tag, this.colname)
  },
})

// header row
Vue.component("HeaderRow", {
  props: ["rowData"],
  template: `
    <tr>
      <table-cell
        v-for="(cell, i) in rowData"
        :key="i"
        :colname="cell.colname"
        :colspan="cell.colspan"
        :tag="'th'"
      />
    </tr>
  `
})

// body row
Vue.component("BodyRow", {
  props: ["rowData"],
  template: `
    <tr>
      <table-cell
        v-for="(cell, i) in rowData"
        :key="i"
        :colname="cell"
        :tag="'td'"
      />
    </tr>
  `
})

new Vue({
  el: "#app",
  data() {
    return {
      tableData: initData(),
      dataSource: "long",
    }
  },
  computed: {
    // mapped data
    mapped() {
      return Object.entries(this.tableData).map(([key, val]) => this.mapLeafs(val, key))
    },
    // header rows of the table
    headers() {
      return this.createHeaders(this.mapped)
    },
    // body rows of the table
    items() {
      return transpose(this.createColumns(this.mapped))
    },
  },
  methods: {
    // just to help feel the flexibility
    onChangeDataSource() {
      if (this.dataSource === "short") {
        this.tableData = initData()
        this.dataSource = "long"
      } else {
        this.tableData = initDataShort()
        this.dataSource = "short"
      }
    },
    // processing/mapping array of values to subheaders
    mapLeafs(obj, key) {
      let acc = []
      const mapped = Object.entries(obj).map(([key, val]) => {
        if (Array.isArray(val)) acc = [...acc, ...val]
        return this.mapLeafs(val, key)
      })
      return {
        colname: key,
        colspan: acc.length ? 1 : mapped.length,
        subheaders: acc.length ? [] : mapped,
        values: acc,
      }
    },
    // creating headers rows from the processed (mapped) data
    createHeaders(obj, d = 0, ret = []) {
      obj.forEach(({
        colname,
        colspan,
        subheaders
      }) => {
        if (!ret[d]) ret[d] = []
        ret[d].push({
          colname,
          colspan,
        })
        if (subheaders.length) {
          this.createHeaders(subheaders, d + 1, ret)
        }
      })
      return ret
    },
    // creating body rows from the processed (mapped) data
    createColumns(obj, d = 0, ret = []) {
      obj.forEach(({
        values,
        subheaders
      }) => {
        if (!values.length) {
          this.createColumns(subheaders, d + 1, ret)
        } else {
          ret.push(values)
        }
      })
      return ret
    }
  },
  template: `
    <div>
      <div>
        <button
          @click="onChangeDataSource"
        >
          SWITCH DATA SOURCE
        </button>
      </div>
      <div
        class="table-container"
      >
      <table>
        <thead>
          <header-row
            v-for="(row, i) in headers"
            :key="i"
            :row-data="row"
          />
        </thead>
        <tbody>
          <body-row
            v-for="(row, i) in items"
            :key="i"
            :row-data="row"
          />
        </tbody>
      </table>
      </div>
    </div>
  `
})
html,
body {
  margin: 0;
  body: 0;
}

table {
  border-collapse: collapse;
}

table,
tr,
th,
td {
  border: 1px solid black;
}

.table-container {
  padding: 8px 16px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>


推荐阅读