A, analysis,

1. test.json

{" keys ": {" result_label" : {" label ":" product category "}, "round1_label" : {" label ":" double tenth one ", "children" : {" round1_remark ": "Sales (a)", "round1" : "inventory (a)"}}, "round2_label" : {" label ":" 12-12 ", "children" : {" round2_remark ":" (a) sales ", "round2" : }}} "inventory (a)", "project_results" : [{" result_label ":" jacket "and" round1 ":" ", "round1_remark" : ""," round2 ": ""," round2_remark ":" ", "children" : [{" result_label ":" coat ", "round1" : "20000", "round1_remark" : "5000", "round2" : "30000", "round2_remark" : "2000"}, {" result_label ":" jacket "and" round1 ":" 27476 ", "round1_remark" : "1239", "round2" : "32760", "round2_remark" : "800"}, {" result_label ":" fleece ", "round1" : "72008", "round1_remark" : "2909", "round2" : "74579", "round2_remark" : "2132"}, {" result_label ":" shirt "and" round1 ":" 83790 ", "round1_remark" : "2009", "round2" : "71323", "round2_remark" : "3323"}, {" result_label ":" T-shirt ", "round1" : "290890", "round1_remark" : "21221", "round2" : "878878", "round2_remark" : "4343"}}, {" result_label ":" bottoms ", "round1" : ""," round1_remark ":" ", "round2" : ""," round2_remark ":" ", "children" : [{" result_label ":" shorts, "" round1" : "1350706", "round1_remark" : "50706", "round2" : "1954425", "round2_label ": "32342"}, {"result_label":" casual trousers ", "round1": "1575289", "round1_remark": "11223", "round2": "1631917", "round2_remark": "67887"}, {"result_label": "cargo ", "round1": "129797", "round1_remark": "8787", "round2": "734011", "round2_remark": "54454"}, {"result_label": "blue ", "round1": "1801629", "round1_remark": "34242", "round2" : "734011", "round2_remark" : "4321"}}, {" result_label ":" accessories ", "round1" : ""," round1_remark ": ""," round2 ":" ", "round2_remark" : ""," children ": [{" result_label" : "hat", "round1" : "2158", "round1_remark" : "233", "round2" : "6759", "round2_remark" : "100"}, {" result_label ":" bag ", "round1" : "1465", "round1_remark" : "32", "round2": "497", "round2_remark": "55" } ] } ] }Copy the code

2. Demand

  • “Commodity category”, “Double 11”, “Double 12” as the first table.

  • The first table “Double 11”, “Double 12” contain the second table “inventory (pieces)” and “sales (pieces)”.

  • Unfurl and fold subitems of tops, bottoms and accessories.

  • Merge the top, bottom, and accessories lines.

  • Realize the sales cell can be edited, other cells can not be edited.

3. Knowledge

  • The multilevel header functionality of Element UI.

  • Tree data and lazy loading capabilities of Element UI.

  • Element UI’s ability to merge rows or columns.

  • Custom editable component EditableCell.

  • Custom styles.

Effect of 4.

Second, the implementation

1. Custom table editable component EditableCell

<template> <div class="edit-cell" @dblclick="onFieldClick"> <! <el-tooltip v-if="! editMode && ! showInput" :placement="toolTipPlacement" :open-delay="toolTipDelay" :content="toolTipContent" > <div tabindex="0" class="cell-content" :class="{'edit-enabled-cell': canEdit}" @keyup.enter="onFieldClick" > <slot name="content" /> </div> </el-tooltip> <! -- Vue framework custom tag, which is used to dynamically bind our components --> <! - through property is the value of the can render different components - > < component: is = "editableComponent" v - if = "editMode | | showInput" ref = "input" v - model = "model" v-bind="$attrs" @focus="onFieldClick" @keyup.enter.native="onInputExit" v-on="listeners" > <slot name="edit-component-slot" /> </component> </div> </template> <script> export default { name: 'EditableCell', // The child component does not inherit the inheritAttrs: false, // The child component uses props to declare data that needs to be accepted from the parent component: {value: {type: String, default: "}, toolTipContent: {type: String, default: 'Double click to edit'}, toolTipDelay: {type: Number, default: 500 }, toolTipPlacement: { type: String, default: 'top-start' }, showInput: { type: Boolean, default: false }, editableComponent: { type: String, default: 'el-input' }, closeEvent: { type: String, default: 'blur' }, canEdit: { type: Boolean, default: false } }, data() { return { editMode: False}}, /** * 1.computed is used to monitor self-defined variables that are not declared in data but defined directly in computed, * and then can be two-way data binding on the page showing the results or used for other processing * * 2. com puted suits to deal with multiple variables or objects after return a result value, * who is one of several multiple variables value has changed, we monitor this value will change too, *, for example: The relationship between the list of items in the shopping cart and the total amount should change whenever the number of items in the list changes, either by decreasing or increasing or deleting items. The total here is best calculated using the computed property */ computed: {model: {get() {return this.value}, set(val) {this.$emit('input', val)}, listeners() { return { [this.closeEvent]: this.onInputExit, ... this.$listeners } } }, methods: {onFieldClick() {if (this.canedit) {this.editMode = true // this.$nextTick This this.$nextTick(() => { const inputRef = this.$refs.input if (inputRef && inputRef.focus) { inputRef.focus() } }) } }, onInputExit() { this.editMode = false }, onInputChange(val) { this.$emit('input', val) } } } </script> <style> .cell-content { min-height: 30px; line-height: 30px; /* padding-left: 5px; padding-top: 5px; */ border: 1px solid transparent; } .edit-enabled-cell { /* border: 1px dashed #409eff; */ border:0 } </style>Copy the code

2. Define the parameters required for page rendering

Data () {return {disabled: false, // Table can be edited abLED: true, // table can not be edited labelList: {}, // used to store table header tableData_show: {} // page table render data}},Copy the code

3. Obtain the local JSON data, perform operations, and assign values

Import goodsData from '/static/test.json'// get local JSON data created() {this.tableData_show = goodsData// Assign For (var key in goodsData['keys']) {this.labelList[key] = goodsData['keys'][key]['label']}}Copy the code

4. Register the EditableCell component

import EditableCell from '@/views/task/components/EditableCell'

components: {
   EditableCell
},
Copy the code

5. Page rendering

<template> <div class="app-container progStyle"> <! < p style="text-align: center; margin: 0 0 10px 0; font-size:18px;" < p style = "max-width: 100%; clear: both; min-height: 1em; <el-table :data="tableData_show['project_results']" style="width: 100%; border :cell-class-name="addClass" row-key="result_label" default-expand-all :tree-props="{children: 'children', hasChildren: 'hasChildren'}" :span-method="arraySpanMethod" > <! -- First table header: <el-table-column v-for="(value1,key1,index1) in tableData_show['keys']" :key="index1" :label="value1['label']" align="center" > <! <template slot-scope="scope"> <span>{{scope. Row [key1]}}</span> </template> <! <div v-if="key1! =='result_label'"> <el-table-column v-for="(value2,key2,index2) in value1['children']" :key="index2" :label="value2" align="center" > <! --> <editable cell v-model="row[key2]" slot-scope="{row}" :can-edit="(key2.indexof ('remark')===-1)? disabled : abled" > <span slot="content">{{ row[key2] }}</span> </editable-cell> </el-table-column> </div> </el-table-column> </el-table> </div> </template>Copy the code

6. Combine the cell method arraySpanMethod with the custom cell style method addClass

methods: ArraySpanMethod ({row}) {var keys = object.keys (row) if (key.indexof ('children') > -1) {return [1, Keys (this.labellist).length * 2 + 1]}}, // custom cell style addClass({row, columnIndex }) { if (columnIndex === 0 && Object.keys(row).indexOf('children') > -1) { return 'fatherColStyle'// Else if (columnIndex === 0 && object.keys (row).indexof ('children') === -1) {return 'sonStyle'// columnIndex == 0 && Object.keys(row).indexof ('children') === -1)} }Copy the code

7. Custom table styles

El-table --border,. El-table --group{border: 1px solid # 979fB1; } .progStyle .el-table--border th, .progStyle .el-table__fixed-right-patch, .progStyle .el-table td, .progStyle .el-table th.is-leaf{ border-bottom: 1px solid #979fb1; } .progStyle .el-table--border td, .progStyle .el-table--border th, .progStyle .el-table__body-wrapper .el-table--border.is-scrolling-left~.el-table__fixed{ border-right: 1px solid #979fb1; El-table [class*=el-table__row--level]. El-table__expand -icon{height: 32px; line-height: 32px; float: left; El-table tr:nth-child(0) {background-color: #cbcfd9; background-color: #cbcfd9; } .progStyle .el-table th>.cell{ color: black; font-size: 18px; FatherColStyle {font-weight: bold; font-size: 16px; background-color: #f5f7fa; color: black; } // Text style. FatherColStyle span{float: left; } // sonStyle{font-weight: bold; background-color: #f0f9eb; font-size: 14px; color: #27a2ff; } </style>Copy the code

8. Complete code

<template> <div class="app-container progStyle"> <! < p style="text-align: center; margin: 0 0 10px 0; font-size:18px;" < p style = "max-width: 100%; clear: both; min-height: 1em; <el-table :data="tableData_show['project_results']" style="width: 100%; border :cell-class-name="addClass" row-key="result_label" default-expand-all :tree-props="{children: 'children', hasChildren: 'hasChildren'}" :span-method="arraySpanMethod" > <! -- First table header: <el-table-column v-for="(value1,key1,index1) in tableData_show['keys']" :key="index1" :label="value1['label']" align="center" > <! <template slot-scope="scope"> <span>{{scope. Row [key1]}}</span> </template> <! <div v-if="key1! =='result_label'"> <el-table-column v-for="(value2,key2,index2) in value1['children']" :key="index2" :label="value2" align="center" > <! --> <editable cell v-model="row[key2]" slot-scope="{row}" :can-edit="(key2.indexof ('remark')===-1)? disabled : abled" > <span slot="content">{{ row[key2] }}</span> </editable-cell> </el-table-column> </div> </el-table-column> </el-table> </div> </template> <script> import goodsData from '/static/test.json'// local json import EditableCell from '@/views/task/components/EditableCell' export default { components: { EditableCell }, data() { return { disabled: False, // table can be edited abLED: true, // table can not be edited labelList: {}, // used to store table header tableData_show: {} // page table render data}}, Created () {this.tableData_show = goodsData for (var key in goodsData['keys']) {this.labellist [key] = ['key ']['key ']['label'] ArraySpanMethod ({row}) {var keys = object.keys (row) if (key.indexof ('children') > -1) {return [1, Keys (this.labellist).length * 2 + 1]}}, // custom cell style addClass({row, columnIndex }) { if (columnIndex === 0 && Object.keys(row).indexOf('children') > -1) { return 'fatherColStyle'// Else if (columnIndex === 0 && object.keys (row).indexof ('children') === -1) {return 'sonStyle'// columnIndex == 0 && Object.keys(row).indexof ('children') === -1)} El-table --border,. El-table --group{border: 1px solid # 979fB1;}} </script> <style lang=" SCSS "> } .progStyle .el-table--border th, .progStyle .el-table__fixed-right-patch, .progStyle .el-table td, .progStyle .el-table th.is-leaf{ border-bottom: 1px solid #979fb1; } .progStyle .el-table--border td, .progStyle .el-table--border th, .progStyle .el-table__body-wrapper .el-table--border.is-scrolling-left~.el-table__fixed{ border-right: 1px solid #979fb1; El-table [class*=el-table__row--level]. El-table__expand -icon{height: 32px; line-height: 32px; float: left; El-table tr:nth-child(0) {background-color: #cbcfd9; background-color: #cbcfd9; } .progStyle .el-table th>.cell{ color: black; font-size: 18px; FatherColStyle {font-weight: bold; font-size: 16px; background-color: #f5f7fa; color: black; } // Text style. FatherColStyle span{float: left; } // sonStyle{font-weight: bold; background-color: #f0f9eb; font-size: 14px; color: #27a2ff; } </style>Copy the code