浏览代码

feat(table): add table componeng

suzigang 4 年之前
父节点
当前提交
eb4d49ec4f

+ 11 - 0
src/config.json

@@ -973,6 +973,17 @@
           "sort": 1,
           "show": true,
           "author": "Drjingfubo"
+        },
+        {
+          "version": "3.0.0",
+          "taro": true,
+          "name": "Table",
+          "type": "component",
+          "cName": "表格组件",
+          "desc": "用于展示多条结构类似的数据,并具有一定的操作功能",
+          "sort": 9,
+          "show": true,
+          "author": "szg2008"
         }
       ]
     }

+ 285 - 0
src/packages/__VUE/table/demo.vue

@@ -0,0 +1,285 @@
+<template>
+  <div class="demo">
+    <h2>基础用法</h2>
+    <nut-table :columns="columns1" :data="data1"></nut-table>
+    <h2>是否显示边框,文字对齐</h2>
+    <nut-table :columns="columns2" :data="data1" :bordered="bordered1"></nut-table>
+    <h2>显示总结栏</h2>
+    <nut-table :columns="columns3" :data="data2" :summary="summary"></nut-table>
+    <h2>条纹、明暗交替</h2>
+    <nut-table :columns="columns3" :data="data2" :striped="striped"></nut-table>
+    <h2>无数据默认展示,支持自定义</h2>
+    <nut-table :columns="columns3" :data="data3"> </nut-table>
+    <br />
+    <nut-table :columns="columns3" :data="data3">
+      <template #nodata>
+        <div class="no-data"> 这里是自定义展示 </div>
+      </template>
+    </nut-table>
+    <h2>自定义单元格</h2>
+    <nut-table :columns="columns4" :data="data4"> </nut-table>
+    <h2>支持异步渲染(5s之后看效果)</h2>
+    <nut-table :columns="columns3" :data="data5"> </nut-table>
+    <h2>支持排序</h2>
+    <nut-table :columns="columns6" :data="data6" @sorter="handleSorter"> </nut-table>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, getCurrentInstance, onMounted, onUnmounted, h } from 'vue';
+import { createComponent } from '../../utils/create';
+import { TableColumnProps } from './types';
+import { Toast } from '@/packages/nutui.vue';
+import Button from '@/packages/__VUE/button/index.vue';
+import Icon from '@/packages/__VUE/icon/index.vue';
+const { createDemo } = createComponent('table');
+export default createDemo({
+  components: {
+    Button,
+    Icon
+  },
+  props: {},
+  setup(props, { emit, slot }) {
+    const state = reactive({
+      bordered1: false,
+      striped: true,
+      columns1: [
+        {
+          title: '姓名',
+          key: 'name'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        }
+      ],
+      columns2: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        }
+      ],
+      columns3: [
+        {
+          title: '姓名',
+          key: 'name'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '年龄',
+          key: 'age'
+        },
+        {
+          title: '地址',
+          key: 'address'
+        }
+      ],
+      columns4: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '操作',
+          key: 'render'
+        }
+      ],
+      columns6: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center',
+          sorter: true
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '年龄',
+          key: 'age',
+          sorter: (row1: any, row2: any) => {
+            return row1.age - row2.age;
+          }
+        }
+      ],
+      data1: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中'
+        }
+      ],
+      data2: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          age: 13,
+          address: '北京'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          age: 34,
+          address: '上海'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          age: 4,
+          address: '杭州'
+        }
+      ],
+      data3: [],
+      data4: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          render: () => {
+            return h(
+              Button,
+              {
+                onClick: () => {
+                  (Toast as any).text('hello');
+                },
+                size: 'small',
+                type: 'primary'
+              },
+              'Hello'
+            );
+          }
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          render: () => {
+            return h(Icon, { name: 'dongdong', size: '14px' });
+          }
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          render: () => {
+            return h(
+              Button,
+              {
+                type: 'success',
+                size: 'small',
+                onClick: () => {
+                  window.open('https://www.jd.com');
+                }
+              },
+              '跳转到京东'
+            );
+          }
+        }
+      ],
+      data5: [],
+      data6: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          age: 10
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          age: 30
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          age: 4
+        }
+      ],
+      timer: null as number | null,
+      summary: () => {
+        return {
+          value: '这是总结栏',
+          colspan: 5
+        };
+      }
+    });
+
+    const handleSorter = (item: TableColumnProps) => {
+      (Toast as any).text(`${JSON.stringify(item)}`);
+    };
+
+    onMounted(() => {
+      state.timer = setTimeout(() => {
+        state.data5 = state.data2.slice() as any;
+      }, 5000);
+    });
+
+    onUnmounted(() => {
+      state.timer = null;
+    });
+
+    return {
+      ...toRefs(state),
+      handleSorter
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.demo {
+  padding-bottom: 20px !important;
+}
+::v-deep(.nut-table) {
+  background-color: #fff;
+}
+</style>

+ 435 - 0
src/packages/__VUE/table/doc.md

@@ -0,0 +1,435 @@
+# Table 表格
+
+### 介绍
+
+用于展示基础表格
+
+### 安装
+
+``` javascript
+import { createApp } from 'vue';
+// vue
+import { Table } from '@nutui/nutui';
+// taro
+import { Table } from '@nutui/nutui-taro';
+
+const app = createApp();
+app.use(Table);
+```
+
+## 代码演示
+
+### 基础使用
+
+
+```html
+<nut-table :columns="columns" :data="data"></nut-table>
+```
+```ts
+setup() {
+    const state = reactive({
+        columns: [
+        {
+          title: '姓名',
+          key: 'name'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        }
+      ],
+      data: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中'
+        }
+      ],
+    });
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+### 是否显示边框,文字对齐
+
+
+```html
+<nut-table :columns="columns" :data="data" :bordered="bordered"></nut-table>
+```
+```ts
+setup() {
+    const state = reactive({
+        bordered: false,
+        columns: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        }
+      ],
+      data: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中'
+        }
+      ],
+    });
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+### 显示总结栏
+
+
+```html
+<nut-table :columns="columns" :data="data" :summary="summary"></nut-table>
+```
+```ts
+setup() {
+    const state = reactive({
+        columns: [
+        {
+          title: '姓名',
+          key: 'name'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '年龄',
+          key: 'age'
+        },
+        {
+          title: '地址',
+          key: 'address'
+        }
+      ],
+      data: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          age: 13,
+          address: '北京'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          age: 34,
+          address: '上海'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          age: 4,
+          address: '杭州'
+        }
+      ],
+      summary: () => {
+        return {
+          value: '这是总结栏',
+          colspan: 5
+        }
+      }
+    });
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+### 条纹、明暗交替
+
+
+```html
+<nut-table :columns="columns3" :data="data2" :striped="striped"></nut-table>
+```
+```ts
+setup() {
+    const state = reactive({
+      ...,
+      striped: true,
+    });
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+### 无数据默认展示,支持自定义
+
+```html
+<nut-table :columns="columns" :data="data"></nut-table>
+
+<nut-table :columns="columns3" :data="data3">
+    <template #nodata>
+    <div class="no-data">
+        这里是自定义展示
+    </div>
+    </template>
+</nut-table>
+```
+```ts
+setup() {
+    const state = reactive({
+      columns: [
+        {
+          title: '姓名',
+          key: 'name'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '年龄',
+          key: 'age'
+        },
+        {
+          title: '地址',
+          key: 'address'
+        }
+      ],
+      data: [],
+      striped: true,
+    });
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+### 自定义单元格
+
+```html
+<nut-table :columns="columns" :data="data">
+```
+```ts
+setup() {
+    const state = reactive({
+      columns: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '操作',
+          key: 'action'
+        }
+      ],
+      data: [
+          {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          action: "<button style='color: #fff; font-size: 14px; background: linear-gradient(135deg,#fa2c19 0%,#fa6419 100%);border: 0;border-radius: 5px;'>删除</button>"
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          action: "<a href='https://www.jd.com' style='text-decoration: none; color: #498ff2;'>跳转至京东</a>"
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          action:  "<div style='color: #498ff2;'>编辑</div>"
+        }
+      ]
+    });
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+### 支持异步渲染
+
+```html
+<nut-table :columns="columns" :data="data">
+```
+```ts
+setup() {
+    const state = reactive({
+      data: [],
+      data1: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          age: 13,
+          address: '北京'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          age: 34,
+          address: '上海'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          age: 4,
+          address: '杭州'
+        }
+      ]
+    });
+    onMounted(() => {
+        setTimeout(() => {
+            state.data = state.data1.slice();
+        }, 5000);
+    });
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+### 支持排序
+
+```html
+<nut-table :columns="columns" :data="data" @sorter="handleSorter">
+```
+```ts
+setup() {
+    const state = reactive({
+      columns: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center',
+          sorter: true
+        },
+        {
+          title: '性别',
+          key: 'sex',
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '年龄',
+          key: 'age',
+          sorter: (row1: any, row2: any) => { return row1.age - row2.age }
+        }
+      ],
+      data: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          age: 10
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          age: 30
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          age: 4
+        }
+      ]
+    });
+    
+    const handleSorter = (item: TableColumnProps) => {
+      (Toast as any).text(`${JSON.stringify(item)}`);
+    }
+
+    return {
+        ...toRefs(state),
+    };
+}
+```
+
+## API
+
+### Props
+
+| 参数         | 说明                             | 类型   | 默认值           |
+|--------------|----------------------------------|--------|------------------|
+| bordered         | 是否显示边框 | Boolean |`true`         |
+| columns         | 表头数据 | TableColumnProps[] |`[]`         |
+| data         | 表格数据 | Object[] |`[]`         |
+| summary         | 是否显示简介 | Function | -         |
+| striped         | 条纹是否明暗交替 | Boolean |`false` |
+
+### TableColumnProps
+
+| 参数         | 说明                             | 类型   | 默认值           |
+|--------------|----------------------------------|--------|------------------|
+| key         | 列的唯一标识 | String |``  |
+| title         | 表头标题 | String |``         |
+| align         | 列的对齐方式,可选值`left`,`center`,`right` | String |`left`         |
+| sorter         | 排序,可选值有 `true`,`function`, `default`, 其中 `default`表示点击之后可能会依赖接口, `function`可以返回具体的排序函数, `default`表示采用默认的排序算法 | Boolean、Function、String |-|
+
+### Events
+
+| 事件名 | 说明           | 回调参数     |
+|--------|----------------|--------------|
+| sorter  | 点击排序按钮触发 | item: 当前点击的表头的数据 |
+

+ 81 - 0
src/packages/__VUE/table/index.scss

@@ -0,0 +1,81 @@
+.nut-table {
+  display: flex;
+  width: 100%;
+  flex-direction: column;
+  font-size: $font-size-2;
+  &__main {
+    display: table;
+    width: 100%;
+    border-collapse: collapse;
+    overflow-x: hidden;
+    &--striped {
+      .nut-table__main__head {
+        &__tr {
+          background-color: $table-tr-even-bg-color;
+        }
+      }
+      .nut-table__main__body {
+        &__tr:nth-child(odd) {
+          background-color: $table-tr-odd-bg-color;
+        }
+      }
+      .nut-table__main__body {
+        &__tr:nth-child(even) {
+          background-color: $table-tr-even-bg-color;
+        }
+      }
+    }
+    &__head,
+    &__body {
+      &__tr {
+        display: table-row;
+        &__th {
+          display: table-cell;
+          padding: $table-cols-padding;
+        }
+        &__td {
+          display: table-cell;
+          padding: $table-cols-padding;
+          &__nodata {
+            display: flex;
+            height: 50px;
+            align-items: center;
+            justify-content: center;
+          }
+        }
+        &--border {
+          border: 1px solid $table-border-color;
+        }
+        &--alignleft,
+        &--align {
+          text-align: left;
+        }
+        &--aligncenter {
+          text-align: center;
+        }
+        &--alignright {
+          text-align: right;
+        }
+      }
+    }
+    &__head {
+      display: table-header-group;
+    }
+    &__body {
+      display: table-row-group;
+    }
+  }
+  &__summary {
+    display: flex;
+    align-items: center;
+    height: 30px;
+    padding: $table-cols-padding;
+  }
+  &__nodata {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 30px;
+    padding: $table-cols-padding;
+  }
+}

+ 132 - 0
src/packages/__VUE/table/index.taro.vue

@@ -0,0 +1,132 @@
+<template>
+  <view :class="classes">
+    <view class="nut-table__main" :class="{ 'nut-table__main--striped': striped }">
+      <view class="nut-table__main__head">
+        <view class="nut-table__main__head__tr">
+          <span
+            class="nut-table__main__head__tr__th"
+            :class="cellClasses(item)"
+            v-for="item in columns"
+            :key="item.key"
+            @click="handleSorterClick(item)"
+          >
+            {{ item.title }}
+            <slot name="icon"></slot>
+            <nut-icon v-if="!$slots.icon && item.sorter" name="down-arrow" size="12px"></nut-icon>
+          </span>
+        </view>
+      </view>
+      <view class="nut-table__main__body">
+        <view class="nut-table__main__body__tr" v-for="item in curData" :key="item">
+          <span
+            class="nut-table__main__body__tr__td"
+            :class="cellClasses(getColumnItem(value))"
+            v-for="value in Object.keys(item)"
+            :key="value"
+          >
+            {{ typeof item[value] !== 'function' ? item[value] : '' }}
+            <RenderColumn :slots="item[value]" v-if="typeof item[value] === 'function'"></RenderColumn>
+          </span>
+        </view>
+      </view>
+    </view>
+    <view class="nut-table__summary" v-if="summary">
+      <span class="nut-table__summary__text" v-html="summary().value"></span>
+    </view>
+    <view class="nut-table__nodata" v-if="!curData.length">
+      <div class="nut-table__nodata" :class="{ 'nut-table__nodata--border': bordered }">
+        <slot name="nodata"></slot>
+        <div v-if="!$slots.nodata" class="nut-table__nodata__text"> 暂无数据 </div>
+      </div>
+    </view>
+  </view>
+</template>
+
+<script lang="ts">
+import { computed, PropType, reactive, toRefs, watch } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('table');
+import RenderColumn from './renderColumn';
+import { TableColumnProps } from './types';
+export default create({
+  components: {
+    RenderColumn
+  },
+  props: {
+    bordered: {
+      type: Boolean,
+      default: true
+    },
+    columns: {
+      type: Array as PropType<TableColumnProps[]>,
+      default: () => {
+        return [];
+      }
+    },
+    data: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    summary: {
+      type: Function,
+      default: null
+    },
+    striped: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['sorter'],
+  setup(props, { emit, slots }) {
+    const state = reactive({
+      curData: props.data
+    });
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const cellClasses = (item: TableColumnProps) => {
+      return {
+        'nut-table__main__head__tr--border': props.bordered,
+        [`nut-table__main__head__tr--align${item.align ? item.align : ''}`]: true
+      };
+    };
+
+    const getColumnItem = (value: string): TableColumnProps => {
+      return props.columns.filter((item: TableColumnProps) => item.key === value)[0];
+    };
+
+    const handleSorterClick = (item: TableColumnProps) => {
+      if (item.sorter) {
+        emit('sorter', item);
+        state.curData =
+          typeof item.sorter === 'function'
+            ? state.curData.sort(item.sorter)
+            : item.sorter === 'default'
+            ? state.curData.sort()
+            : state.curData;
+      }
+    };
+
+    watch(
+      () => props.data,
+      (val) => {
+        state.curData = val.slice();
+      }
+    );
+
+    return {
+      ...toRefs(state),
+      classes,
+      cellClasses,
+      getColumnItem,
+      handleSorterClick
+    };
+  }
+});
+</script>

+ 132 - 0
src/packages/__VUE/table/index.vue

@@ -0,0 +1,132 @@
+<template>
+  <view :class="classes">
+    <view class="nut-table__main" :class="{ 'nut-table__main--striped': striped }">
+      <view class="nut-table__main__head">
+        <view class="nut-table__main__head__tr">
+          <span
+            class="nut-table__main__head__tr__th"
+            :class="cellClasses(item)"
+            v-for="item in columns"
+            :key="item.key"
+            @click="handleSorterClick(item)"
+          >
+            {{ item.title }}
+            <slot name="icon"></slot>
+            <nut-icon v-if="!$slots.icon && item.sorter" name="down-arrow" size="12px"></nut-icon>
+          </span>
+        </view>
+      </view>
+      <view class="nut-table__main__body">
+        <view class="nut-table__main__body__tr" v-for="item in curData" :key="item">
+          <span
+            class="nut-table__main__body__tr__td"
+            :class="cellClasses(getColumnItem(value))"
+            v-for="value in Object.keys(item)"
+            :key="value"
+          >
+            {{ typeof item[value] !== 'function' ? item[value] : '' }}
+            <RenderColumn :slots="item[value]" v-if="typeof item[value] === 'function'"></RenderColumn>
+          </span>
+        </view>
+      </view>
+    </view>
+    <view class="nut-table__summary" v-if="summary">
+      <span class="nut-table__summary__text" v-html="summary().value"></span>
+    </view>
+    <view class="nut-table__nodata" v-if="!curData.length">
+      <div class="nut-table__nodata" :class="{ 'nut-table__nodata--border': bordered }">
+        <slot name="nodata"></slot>
+        <div v-if="!$slots.nodata" class="nut-table__nodata__text"> 暂无数据 </div>
+      </div>
+    </view>
+  </view>
+</template>
+
+<script lang="ts">
+import { computed, PropType, reactive, toRefs, watch } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('table');
+import RenderColumn from './renderColumn';
+import { TableColumnProps } from './types';
+export default create({
+  components: {
+    RenderColumn
+  },
+  props: {
+    bordered: {
+      type: Boolean,
+      default: true
+    },
+    columns: {
+      type: Array as PropType<TableColumnProps[]>,
+      default: () => {
+        return [];
+      }
+    },
+    data: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    summary: {
+      type: Function,
+      default: null
+    },
+    striped: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['sorter'],
+  setup(props, { emit, slots }) {
+    const state = reactive({
+      curData: props.data
+    });
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const cellClasses = (item: TableColumnProps) => {
+      return {
+        'nut-table__main__head__tr--border': props.bordered,
+        [`nut-table__main__head__tr--align${item.align ? item.align : ''}`]: true
+      };
+    };
+
+    const getColumnItem = (value: string): TableColumnProps => {
+      return props.columns.filter((item: TableColumnProps) => item.key === value)[0];
+    };
+
+    const handleSorterClick = (item: TableColumnProps) => {
+      if (item.sorter) {
+        emit('sorter', item);
+        state.curData =
+          typeof item.sorter === 'function'
+            ? state.curData.sort(item.sorter)
+            : item.sorter === 'default'
+            ? state.curData.sort()
+            : state.curData;
+      }
+    };
+
+    watch(
+      () => props.data,
+      (val) => {
+        state.curData = val.slice();
+      }
+    );
+
+    return {
+      ...toRefs(state),
+      classes,
+      cellClasses,
+      getColumnItem,
+      handleSorterClick
+    };
+  }
+});
+</script>

+ 9 - 0
src/packages/__VUE/table/renderColumn.ts

@@ -0,0 +1,9 @@
+import { h } from 'vue';
+export default {
+  setup(props: any) {
+    return () => h(`view`, {}, props.slots());
+  },
+  props: {
+    slots: Object
+  }
+};

+ 16 - 0
src/packages/__VUE/table/types.ts

@@ -0,0 +1,16 @@
+import { VNodeChild } from 'vue';
+export interface TableColumnProps {
+  key?: string;
+  title?: string;
+  align?: string;
+  sorter?: Function;
+  render?: (rowData: object, rowIndex: number) => VNodeChild | string;
+}
+
+export interface TableProps {
+  bordered: true;
+  columns: TableColumnProps[];
+  data: object[];
+  summary: string;
+  striped: boolean;
+}

+ 6 - 0
src/packages/styles/variables.scss

@@ -434,5 +434,11 @@ $grid-item-text-margin: 8px !default;
 $grid-item-text-color: $title-color2 !default;
 $grid-item-text-font-size: $font-size-1 !default;
 
+// table
+$table-border-color: #ececec;
+$table-cols-padding: 10px;
+$table-tr-even-bg-color: #f3f3f3;
+$table-tr-odd-bg-color: $white;
+
 @import './mixins/index';
 @import './animation/index';

+ 2 - 1
src/sites/mobile-taro/vue/src/app.config.ts

@@ -85,7 +85,8 @@ export default {
         'pages/barrage/index',
         'pages/timeselect/index',
         'pages/sku/index',
-        'pages/card/index'
+        'pages/card/index',
+        'pages/table/index'
       ]
     }
   ],

+ 4 - 0
src/sites/mobile-taro/vue/src/business/pages/table/index.config.js

@@ -0,0 +1,4 @@
+export default {
+  navigationBarTitleText: 'Table',
+  disableScroll: true
+};

+ 262 - 0
src/sites/mobile-taro/vue/src/business/pages/table/index.vue

@@ -0,0 +1,262 @@
+<template>
+  <div class="demo">
+    <h2>基础用法</h2>
+    <nut-table :columns="columns1" :data="data1"></nut-table>
+    <h2>是否显示边框,文字对齐</h2>
+    <nut-table :columns="columns2" :data="data1" :bordered="bordered1"></nut-table>
+    <h2>显示总结栏</h2>
+    <nut-table :columns="columns3" :data="data2" :summary="summary"></nut-table>
+    <h2>条纹、明暗交替</h2>
+    <nut-table :columns="columns3" :data="data2" :striped="striped"></nut-table>
+    <h2>无数据默认展示,支持自定义</h2>
+    <nut-table :columns="columns3" :data="data3"> </nut-table>
+    <br />
+    <nut-table :columns="columns3" :data="data3">
+      <template #nodata>
+        <div class="no-data"> 这里是自定义展示 </div>
+      </template>
+    </nut-table>
+    <h2>自定义单元格</h2>
+    <nut-table :columns="columns4" :data="data4"> </nut-table>
+    <h2>支持异步渲染(5s之后看效果)</h2>
+    <nut-table :columns="columns3" :data="data5"> </nut-table>
+    <h2>支持排序</h2>
+    <nut-table :columns="columns6" :data="data6" @sorter="handleSorter"> </nut-table>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, onMounted, onUnmounted, h, defineComponent } from 'vue';
+import { TableColumnProps } from '@/packages/__VUE/table/types';
+import Button from './../../../../../../../packages/__VUE/button/index.taro.vue';
+import Icon from './../../../../../../../packages/__VUE/icon/index.taro.vue';
+export default defineComponent({
+  components: {
+    Button,
+    Icon
+  },
+  props: {},
+  setup(props, { emit, slot }) {
+    const state = reactive({
+      bordered1: false,
+      striped: true,
+      columns1: [
+        {
+          title: '姓名',
+          key: 'name'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        }
+      ],
+      columns2: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        }
+      ],
+      columns3: [
+        {
+          title: '姓名',
+          key: 'name'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '年龄',
+          key: 'age'
+        },
+        {
+          title: '地址',
+          key: 'address'
+        }
+      ],
+      columns4: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center'
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '操作',
+          key: 'render'
+        }
+      ],
+      columns6: [
+        {
+          title: '姓名',
+          key: 'name',
+          align: 'center',
+          sorter: true
+        },
+        {
+          title: '性别',
+          key: 'sex'
+        },
+        {
+          title: '学历',
+          key: 'record'
+        },
+        {
+          title: '年龄',
+          key: 'age',
+          sorter: (row1: any, row2: any) => {
+            return row1.age - row2.age;
+          }
+        }
+      ],
+      data1: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中'
+        }
+      ],
+      data2: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          age: 13,
+          address: '北京'
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          age: 34,
+          address: '上海'
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          age: 4,
+          address: '杭州'
+        }
+      ],
+      data3: [],
+      data4: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          render: () => {
+            return h(Button, { size: 'small', type: 'primary' }, 'Hello');
+          }
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          render: () => {
+            return h(Icon, { name: 'dongdong', size: '14px' });
+          }
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          render: () => {
+            return h(Button, { type: 'success', size: 'small' }, '编辑按钮');
+          }
+        }
+      ],
+      data5: [],
+      data6: [
+        {
+          name: 'Tom',
+          sex: '男',
+          record: '小学',
+          age: 10
+        },
+        {
+          name: 'Lucy',
+          sex: '女',
+          record: '本科',
+          age: 30
+        },
+        {
+          name: 'Jack',
+          sex: '男',
+          record: '高中',
+          age: 4
+        }
+      ],
+      timer: null as number | null,
+      summary: () => {
+        return {
+          value: '这是总结栏',
+          colspan: 5
+        };
+      }
+    });
+
+    const handleSorter = (item: TableColumnProps) => {
+      console.log(JSON.stringify(item));
+    };
+
+    onMounted(() => {
+      state.timer = setTimeout(() => {
+        state.data5 = state.data2.slice() as any;
+      }, 5000);
+    });
+
+    onUnmounted(() => {
+      state.timer = null;
+    });
+
+    return {
+      ...toRefs(state),
+      handleSorter
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+.demo {
+  padding-bottom: 20px !important;
+}
+.nut-table {
+  background-color: #fff;
+}
+</style>