结合JSX实现自定义内容的类表格组件

175 阅读1分钟

image-20221101092248954

最近又遇到类似表格的界面,在很久以前我就做过了,其中的自定义内容,是使用render函数实现的。使用时的代码如下,总归看起来不够优雅。

column.render = (h, {value: {row}}) => { // 使用render函数自定义单元格内容
            let vNode = [h("span", {}, row.mobile)];
            if (!row.isShowMobile) {
              vNode.push(h("i",
                  {
                    class: {
                      'iconfont-md': true,
                      'icon-xianshiyanjing-01': true
                    },
                    style: {
                      "margin-left": '8px'
                    },
                    on: {
                      click: () => { // 实现绑定数据
                        this.showPassword(1, row)
                      }
                    },
                  },
              ),)
            }
            return vNode;
          }

结合JSX之后,直接写一个JSX即可。

// 这个代码片段是在computed里的
[{
  label: "纳税人识别号",
  key: "socialCreditCode",
  jsxRight: { // 本来想传更多的参数进去,所以采用对象的形式
    // 使用匿名函数保证this指向问题
    jsx: () => {
      return <f-button vOn:click={this.click}>{this.message}</f-button>
    },
  },
}]

那么现在我们看一下是怎么实现的。

配置环境

1.安装 JSX-babel

npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props

2.增加配置babel.config.js

module.exports = {
  presets: ['@vue/babel-preset-jsx'],
}

那么我们其他学习参考jsx-vue2(响应式,指令,事件等)

JSX转换器

在这里使用了render函数对JSX进行处理

<script>
export default {
  name: "render-jsx",
  props: {
    jsx:{
      type: Function
    }
  },
  render(h) {
    // 获取props里的jsx执行
    return this.$props.jsx();
  }
}
</script><style lang="less" scoped></style>

需要注意的是,我们在这里使用函数传递jsx,为什么这样做呢?因为可以使用匿名函数,控制变量的作用域。

使用一下

render-jsx.vue

<script>
export default {
  name: "render-jsx",
  props: {
    jsx:{
      type: Function
    }
  },
  render(h) {
    // 获取props里的jsx执行
    return this.$props.jsx();
  }
}
</script><style lang="less" scoped></style>

FButton.vue 用来模拟点击事件和判断是否可以使用单文件组件(SFC)

<template>
  <div class="f-button" @click="click">
    <slot></slot>
  </div>
</template><script>
export default {
  name: "FButton",
  methods:{
    click(){
      this.$emit('click')
    }
  }
}
</script><style lang="less" scoped>
.f-button{
  display: flex;
  align-items: center;
  color: skyblue;
  border: 1px solid skyblue;
  padding: 0 8px;
  border-radius: 2px;
}
</style>

infoTable.vue

<template>
  <div class="info-table" :class="{'in-table-border':isBorder}">
    <div class="item__row" v-for="(item,index) in options" :key="index">
      <div class="item__col" v-for="item2 in item" :key="item2.key">
        <template v-if="item2">
          <div class="item__col__init item__col__left">
            <template v-if="item.jsxLeft">
              <render-jsx :render="item.jsxLeft.jsx"></render-jsx>
            </template>
            <template v-else>
              {{ item2.label }}
            </template>
          </div>
          <div class="item__col__init item__col__right">
            <template v-if="item2.jsxRight">
              <render-jsx :jsx="item2.jsxRight.jsx"></render-jsx>
            </template>
            <template v-else>
              {{ data[item2.key] }}
            </template>
          </div>
        </template>
      </div>
    </div>
  </div>
</template><script>
import RenderJsx from "./render-jsx";
​
export default {
  name: "info-table",
  components: {RenderJsx},
  props: {
    options: {
      type: Array,
      default: () => {
        return []
      }
    },
    isBorder: {
      type: Boolean,
      default: true
    },
    data: {
      type: Object,
      default() {
        return {}
      },
    }
  },
  methods: {
    click() {
      console.log(this.options, this.data);
    }
  }
}
</script><style lang="less" scoped>
.item__row {
  display: flex;
  min-height: 36px;
  box-sizing: border-box;
  border-bottom: 1px var(--blue) solid;
  flex-shrink: 0;
​
  .item__col {
    display: flex;
    width: 100%;
    word-wrap: break-word;
    border: 1px solid darkslategray;
​
    .item__col__init {
      display: flex;
      align-items: center;
      padding: 12px;
      overflow: hidden;
    }
​
    .item__col__left {
      width: 125px;
      flex-shrink: 0;
​
      .item__col__left__label {
        font-weight: bold;
      }
    }
​
    .item__col__right {
      flex-grow: 1;
      flex-shrink: 1;
    }
  }
​
  .item__col:last-child {
    flex-grow: 1;
​
    .item__col__right {
      border-right: 0;
    }
  }
}
​
.item__row:nth-child(2n) {
  background: var(--blue-3);
}
​
.item__row:last-child {
  border-bottom: 0;
}
</style>

App.Vue

<script>
import infoTable from "./components/info-table";
import FButton from "../FButton";
​
export default {
  components: {
    infoTable,
    FButton
  },
  data() {
    return {
      message: 'hello',
      data: {
        socialCreditCode: '1233',
        companyName: '陈**',
        taxTypes: '有',
        registrationType: '个人',
        registerTime: '2022-10',
        businessScope: '个人经营',
        industry: '计算机',
        creditLevel: '1级',
        legalPersonName: '陈'
      },
​
    };
  },
  render() {
    return <info-table data={this.data} options={this.options}></info-table>;
  },
  methods: {
    click() {
      return console.log('hello');
    }
  },
  computed: {
    options() {
      return [
        [{
          label: "纳税人识别号",
          key: "socialCreditCode",
          jsxRight: {
            jsx: () => {
              return <f-button vOn:click={this.click}>
                <div>{this.message}</div>
              </f-button>
            },
          },
        }, {
          label: "纳税人名称",
          key: "companyName",
        },
          {}
        ],
        [{
          label: "纳税人资格",
          key: "taxTypes",
        }, {
          label: "登记注册类型",
          key: "registrationType",
        }, {
          label: "法人名称",
          key: "legalPersonName",
        }],
        [{
          label: "税务登记日期",
          key: "registerTime",
        }, {
          label: "纳税信用等级",
          key: "creditLevel",
        }, {
          label: "税务行业归属",
          key: "industry",
        }],
        [{
          label: "工商经营范围",
          key: "businessScope",
        }]
      ]
    }
  }
};
</script>

参考文档

jsx-vue2