Bläddra i källkod

feat: Add Table component and add useTable hook

陈凯龙 3 år sedan
förälder
incheckning
17e8e7cda9

+ 1 - 0
src/components/Table/index.ts

@@ -2,6 +2,7 @@ import Table from './src/Table.vue'
 
 export interface TableExpose {
   setProps: (props: Recordable) => void
+  setColumn: (columnProps: TableSetPropsType[]) => void
 }
 
 export { Table }

+ 93 - 26
src/components/Table/src/Table.vue

@@ -5,6 +5,7 @@ import { propTypes } from '@/utils/propTypes'
 import { setIndex } from './helper'
 import { getSlot } from '@/utils/tsxHelper'
 import type { TableProps } from './types'
+import { set } from 'lodash-es'
 
 export default defineComponent({
   name: 'Table',
@@ -20,6 +21,8 @@ export default defineComponent({
       type: Array as PropType<TableColumn[]>,
       default: () => []
     },
+    // 展开行
+    expand: propTypes.bool.def(false),
     // 是否展示分页
     pagination: {
       type: Object as PropType<Pagination>,
@@ -73,8 +76,22 @@ export default defineComponent({
       outsideProps.value = props
     }
 
+    const setColumn = (columnProps: TableSetPropsType[], columnsChildren?: TableColumn[]) => {
+      const { columns } = unref(getProps)
+      for (const v of columnsChildren || columns) {
+        for (const item of columnProps) {
+          if (v.field === item.field) {
+            set(v, item.path, item.value)
+          } else if (v.children?.length) {
+            setColumn(columnProps, v.children)
+          }
+        }
+      }
+    }
+
     expose({
-      setProps
+      setProps,
+      setColumn
     })
 
     const pagination = computed(() => {
@@ -129,21 +146,73 @@ export default defineComponent({
     })
 
     const renderTableSelection = () => {
+      const { selection, reserveSelection, align, headerAlign } = unref(getProps)
       // 渲染多选
-      return unref(getProps).selection ? (
+      return selection ? (
         <ElTableColumn
           type="selection"
-          reserveSelection={unref(getProps).reserveSelection}
-          align={unref(getProps).align}
-          headerAlign={unref(getProps).headerAlign}
+          reserveSelection={reserveSelection}
+          align={align}
+          headerAlign={headerAlign}
           width="50"
         ></ElTableColumn>
       ) : undefined
     }
 
-    const rnderTableColumn = (columns: TableColumn[]) => {
-      return [renderTableSelection()].concat(
-        columns.map((v) => {
+    const renderTableExpand = () => {
+      const { align, headerAlign } = unref(getProps)
+      // 渲染展开行
+      return unref(getProps).expand ? (
+        <ElTableColumn type="expand" align={align} headerAlign={headerAlign}>
+          {{
+            // @ts-ignore
+            default: (data: TableSlotDefault) => getSlot(slots, 'expand', data)
+          }}
+        </ElTableColumn>
+      ) : undefined
+    }
+
+    const rnderTreeTableColumn = (columnsChildren: TableColumn[]) => {
+      const { align, headerAlign, showOverflowTooltip } = unref(getProps)
+      return columnsChildren.map((v) => {
+        const props = { ...v }
+        if (props.children) delete props.children
+        return (
+          <ElTableColumn
+            showOverflowTooltip={showOverflowTooltip}
+            align={align}
+            headerAlign={headerAlign}
+            {...props}
+            prop={v.field}
+          >
+            {{
+              default: (data: TableSlotDefault) =>
+                v.children && v.children.length
+                  ? rnderTableColumn(v.children)
+                  : // @ts-ignore
+                    getSlot(slots, v.field, data) ||
+                    v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
+                    data.row[v.field],
+              // @ts-ignore
+              header: getSlot(slots, `${v.field}-header`)
+            }}
+          </ElTableColumn>
+        )
+      })
+    }
+
+    const rnderTableColumn = (columnsChildren?: TableColumn[]) => {
+      const {
+        columns,
+        reserveIndex,
+        pageSize,
+        currentPage,
+        align,
+        headerAlign,
+        showOverflowTooltip
+      } = unref(getProps)
+      return [...[renderTableExpand()], ...[renderTableSelection()]].concat(
+        (columnsChildren || columns).map((v) => {
           // 自定生成序号
           if (v.type === 'index') {
             return (
@@ -152,35 +221,33 @@ export default defineComponent({
                 index={
                   v.index
                     ? v.index
-                    : (index) =>
-                        setIndex(
-                          unref(getProps).reserveIndex,
-                          index,
-                          unref(getProps).pageSize,
-                          unref(getProps).currentPage
-                        )
+                    : (index) => setIndex(reserveIndex, index, pageSize, currentPage)
                 }
-                align={v.align || unref(getProps).align}
-                headerAlign={v.headerAlign || unref(getProps).headerAlign}
+                align={v.align || align}
+                headerAlign={v.headerAlign || headerAlign}
                 label={v.label}
                 width="100px"
               ></ElTableColumn>
             )
           } else {
+            const props = { ...v }
+            if (props.children) delete props.children
             return (
               <ElTableColumn
-                showOverflowTooltip={unref(getProps).showOverflowTooltip}
-                align={unref(getProps).align}
-                headerAlign={unref(getProps).headerAlign}
-                {...v}
+                showOverflowTooltip={showOverflowTooltip}
+                align={align}
+                headerAlign={headerAlign}
+                {...props}
                 prop={v.field}
               >
                 {{
                   default: (data: TableSlotDefault) =>
-                    // @ts-ignore
-                    getSlot(slots, v.field, data) ||
-                    v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
-                    data.row[v.field],
+                    v.children && v.children.length
+                      ? rnderTreeTableColumn(v.children)
+                      : // @ts-ignore
+                        getSlot(slots, v.field, data) ||
+                        v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
+                        data.row[v.field],
                   // @ts-ignore
                   header: getSlot(slots, `${v.field}-header`)
                 }}
@@ -201,7 +268,7 @@ export default defineComponent({
           v-loading={unref(getProps).loading}
         >
           {{
-            default: () => rnderTableColumn(unref(getProps).columns),
+            default: () => rnderTableColumn(),
             // @ts-ignore
             append: () => getSlot(slots, 'append')
           }}

+ 1 - 0
src/components/Table/src/types.ts

@@ -20,4 +20,5 @@ export type TableProps = {
   // 表头对齐方式
   headerAlign?: 'left' | 'center' | 'right'
   data?: Recordable
+  expand?: boolean
 } & Recordable

+ 13 - 1
src/hooks/web/useTable.ts

@@ -12,6 +12,7 @@ interface UseTableConfig<T, L> {
     list: string
     total?: string
   }
+  props?: TableProps
 }
 
 interface TableObject<K, L> {
@@ -88,7 +89,11 @@ export const useTable = <T, K, L extends AxiosConfig = AxiosConfig>(
     return table
   }
 
-  const methods = {
+  const methods: {
+    setProps: (props: Recordable) => void
+    getList: () => Promise<void>
+    setColumn: (columnProps: TableSetPropsType[]) => void
+  } = {
     getList: async () => {
       tableObject.loading = true
       const res = await config
@@ -105,11 +110,18 @@ export const useTable = <T, K, L extends AxiosConfig = AxiosConfig>(
     setProps: async (props: TableProps = {}) => {
       const table = await getTable()
       table?.setProps(props)
+    },
+    setColumn: async (columnProps: TableSetPropsType[]) => {
+      const table = await getTable()
+      table?.setColumn(columnProps)
     }
   }
 
+  config?.props && methods.setProps(config.props)
+
   return {
     register,
+    elTableRef,
     tableObject,
     methods
   }

+ 6 - 4
src/locales/en.ts

@@ -324,9 +324,11 @@ export default {
     pagination: 'pagination',
     reserveIndex: 'Reserve index',
     restoreIndex: 'Restore index',
-    showSelections: 'show selections',
-    hiddenSelections: 'restore selections',
-    showExpandedRows: 'show expanded rows',
-    hiddenExpandedRows: 'hidden expanded rows'
+    showSelections: 'Show selections',
+    hiddenSelections: 'Restore selections',
+    showExpandedRows: 'Show expanded rows',
+    hiddenExpandedRows: 'Hidden expanded rows',
+    changeTitle: 'Change title',
+    header: 'Header'
   }
 }

+ 3 - 1
src/locales/zh-CN.ts

@@ -324,6 +324,8 @@ export default {
     showSelections: '显示多选',
     hiddenSelections: '隐藏多选',
     showExpandedRows: '显示展开行',
-    hiddenExpandedRows: '隐藏展开行'
+    hiddenExpandedRows: '隐藏展开行',
+    changeTitle: '修改标题',
+    header: '头部'
   }
 }

+ 8 - 0
src/router/index.ts

@@ -155,6 +155,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
             meta: {
               title: 'UseTable'
             }
+          },
+          {
+            path: 'ref-table',
+            component: () => import('@/views/Components/Table/RefTable.vue'),
+            name: 'RefTable',
+            meta: {
+              title: 'RefTable'
+            }
           }
         ]
       },

+ 181 - 0
src/views/Components/Table/RefTable.vue

@@ -0,0 +1,181 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableExpose } from '@/components/Table'
+import { getTableListApi } from '@/api/table'
+import { TableData } from '@/api/table/types'
+import { ref, h, reactive, unref } from 'vue'
+import { ElTag, ElButton } from 'element-plus'
+import { useTable } from '@/hooks/web/useTable'
+
+const { t } = useI18n()
+
+const columns = reactive<TableColumn[]>([
+  {
+    field: 'index',
+    label: t('tableDemo.index'),
+    type: 'index'
+  },
+  {
+    field: 'content',
+    label: t('tableDemo.header'),
+    children: [
+      {
+        field: 'title',
+        label: t('tableDemo.title')
+      },
+      {
+        field: 'author',
+        label: t('tableDemo.author')
+      },
+      {
+        field: 'display_time',
+        label: t('tableDemo.displayTime')
+      },
+      {
+        field: 'importance',
+        label: t('tableDemo.importance'),
+        formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
+          return h(
+            ElTag,
+            {
+              type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
+            },
+            () =>
+              cellValue === 1
+                ? t('tableDemo.important')
+                : cellValue === 2
+                ? t('tableDemo.good')
+                : t('tableDemo.commonly')
+          )
+        }
+      },
+      {
+        field: 'pageviews',
+        label: t('tableDemo.pageviews')
+      }
+    ]
+  },
+  {
+    field: 'action',
+    label: t('tableDemo.action')
+  }
+])
+
+const { register, tableObject, methods } = useTable<
+  {
+    total: number
+    list: TableData[]
+  },
+  TableData
+>({
+  getListApi: getTableListApi,
+  response: {
+    list: 'list',
+    total: 'total'
+  },
+  props: {
+    columns
+  }
+})
+
+const { getList } = methods
+
+getList()
+
+const tableRef = ref<TableExpose>()
+
+const acitonFn = (data: TableSlotDefault) => {
+  console.log(data)
+}
+
+const paginationObj = ref<Pagination>()
+
+const showPagination = (show: boolean) => {
+  if (show) {
+    paginationObj.value = {
+      total: tableObject.total
+    }
+  } else {
+    paginationObj.value = undefined
+  }
+}
+
+const reserveIndex = (custom: boolean) => {
+  unref(tableRef)?.setProps({
+    reserveIndex: custom
+  })
+}
+
+const showSelections = (show: boolean) => {
+  unref(tableRef)?.setProps({
+    selection: show
+  })
+}
+
+const index = ref(1)
+
+const changeTitle = () => {
+  unref(tableRef)?.setColumn([
+    {
+      field: 'title',
+      path: 'label',
+      value: `${t('tableDemo.title')}${unref(index)}`
+    }
+  ])
+  index.value++
+}
+
+const showExpandedRows = (show: boolean) => {
+  unref(tableRef)?.setProps({
+    expand: show
+  })
+}
+</script>
+
+<template>
+  <ContentWrap :title="`RefTable ${t('tableDemo.operate')}`">
+    <ElButton @click="showPagination(true)">
+      {{ t('tableDemo.show') }} {{ t('tableDemo.pagination') }}
+    </ElButton>
+    <ElButton @click="showPagination(false)">
+      {{ t('tableDemo.hidden') }} {{ t('tableDemo.pagination') }}
+    </ElButton>
+
+    <ElButton @click="reserveIndex(true)">{{ t('tableDemo.reserveIndex') }}</ElButton>
+    <ElButton @click="reserveIndex(false)">{{ t('tableDemo.restoreIndex') }}</ElButton>
+
+    <ElButton @click="showSelections(true)">{{ t('tableDemo.showSelections') }}</ElButton>
+    <ElButton @click="showSelections(false)">{{ t('tableDemo.hiddenSelections') }}</ElButton>
+
+    <ElButton @click="changeTitle">{{ t('tableDemo.changeTitle') }}</ElButton>
+
+    <ElButton @click="showExpandedRows(true)">{{ t('tableDemo.showExpandedRows') }}</ElButton>
+    <ElButton @click="showExpandedRows(false)">{{ t('tableDemo.hiddenExpandedRows') }}</ElButton>
+  </ContentWrap>
+  <ContentWrap :title="`RefTable ${t('tableDemo.example')}`">
+    <Table
+      ref="tableRef"
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+      :data="tableObject.tableList"
+      :loading="tableObject.loading"
+      :pagination="paginationObj"
+      @register="register"
+    >
+      <template #action="data">
+        <ElButton type="primary" @click="acitonFn(data as TableSlotDefault)">
+          {{ t('tableDemo.action') }}
+        </ElButton>
+      </template>
+
+      <template #expand="data">
+        <div class="ml-30px">
+          <div>{{ t('tableDemo.title') }}:{{ data.row.title }}</div>
+          <div>{{ t('tableDemo.author') }}:{{ data.row.author }}</div>
+          <div>{{ t('tableDemo.displayTime') }}:{{ data.row.display_time }}</div>
+        </div>
+      </template>
+    </Table>
+  </ContentWrap>
+</template>

+ 74 - 37
src/views/Components/Table/UseTableDemo.vue

@@ -4,7 +4,7 @@ import { useI18n } from '@/hooks/web/useI18n'
 import { Table } from '@/components/Table'
 import { getTableListApi } from '@/api/table'
 import { TableData } from '@/api/table/types'
-import { ref, h } from 'vue'
+import { ref, h, reactive, unref } from 'vue'
 import { ElTag, ElButton } from 'element-plus'
 import { useTable } from '@/hooks/web/useTable'
 
@@ -28,51 +28,57 @@ getList()
 
 const { t } = useI18n()
 
-const columns: TableColumn[] = [
+const columns = reactive<TableColumn[]>([
   {
     field: 'index',
     label: t('tableDemo.index'),
     type: 'index'
   },
   {
-    field: 'title',
-    label: t('tableDemo.title')
-  },
-  {
-    field: 'author',
-    label: t('tableDemo.author')
-  },
-  {
-    field: 'display_time',
-    label: t('tableDemo.displayTime')
-  },
-  {
-    field: 'importance',
-    label: t('tableDemo.importance'),
-    formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
-      return h(
-        ElTag,
-        {
-          type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
-        },
-        () =>
-          cellValue === 1
-            ? t('tableDemo.important')
-            : cellValue === 2
-            ? t('tableDemo.good')
-            : t('tableDemo.commonly')
-      )
-    }
-  },
-  {
-    field: 'pageviews',
-    label: t('tableDemo.pageviews')
+    field: 'content',
+    label: t('tableDemo.header'),
+    children: [
+      {
+        field: 'title',
+        label: t('tableDemo.title')
+      },
+      {
+        field: 'author',
+        label: t('tableDemo.author')
+      },
+      {
+        field: 'display_time',
+        label: t('tableDemo.displayTime')
+      },
+      {
+        field: 'importance',
+        label: t('tableDemo.importance'),
+        formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
+          return h(
+            ElTag,
+            {
+              type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
+            },
+            () =>
+              cellValue === 1
+                ? t('tableDemo.important')
+                : cellValue === 2
+                ? t('tableDemo.good')
+                : t('tableDemo.commonly')
+          )
+        }
+      },
+      {
+        field: 'pageviews',
+        label: t('tableDemo.pageviews')
+      }
+    ]
   },
   {
     field: 'action',
     label: t('tableDemo.action')
   }
-]
+])
 
 const acitonFn = (data: TableSlotDefault) => {
   console.log(data)
@@ -103,6 +109,27 @@ const showSelections = (show: boolean) => {
     selection: show
   })
 }
+
+const index = ref(1)
+
+const changeTitle = () => {
+  const { setColumn } = methods
+  setColumn([
+    {
+      field: 'title',
+      path: 'label',
+      value: `${t('tableDemo.title')}${unref(index)}`
+    }
+  ])
+  index.value++
+}
+
+const showExpandedRows = (show: boolean) => {
+  const { setProps } = methods
+  setProps({
+    expand: show
+  })
+}
 </script>
 
 <template>
@@ -120,8 +147,10 @@ const showSelections = (show: boolean) => {
     <ElButton @click="showSelections(true)">{{ t('tableDemo.showSelections') }}</ElButton>
     <ElButton @click="showSelections(false)">{{ t('tableDemo.hiddenSelections') }}</ElButton>
 
-    <ElButton @click="showSelections(true)">{{ t('tableDemo.showExpandedRows') }}</ElButton>
-    <ElButton @click="showSelections(false)">{{ t('tableDemo.hiddenExpandedRows') }}</ElButton>
+    <ElButton @click="changeTitle">{{ t('tableDemo.changeTitle') }}</ElButton>
+
+    <ElButton @click="showExpandedRows(true)">{{ t('tableDemo.showExpandedRows') }}</ElButton>
+    <ElButton @click="showExpandedRows(false)">{{ t('tableDemo.hiddenExpandedRows') }}</ElButton>
   </ContentWrap>
   <ContentWrap :title="`UseTable ${t('tableDemo.example')}`">
     <Table
@@ -138,6 +167,14 @@ const showSelections = (show: boolean) => {
           {{ t('tableDemo.action') }}
         </ElButton>
       </template>
+
+      <template #expand="data">
+        <div class="ml-30px">
+          <div>{{ t('tableDemo.title') }}:{{ data.row.title }}</div>
+          <div>{{ t('tableDemo.author') }}:{{ data.row.author }}</div>
+          <div>{{ t('tableDemo.displayTime') }}:{{ data.row.display_time }}</div>
+        </div>
+      </template>
     </Table>
   </ContentWrap>
 </template>

+ 7 - 0
types/componentType/table.d.ts

@@ -1,6 +1,7 @@
 declare type TableColumn = {
   field: string
   label?: string
+  children?: TableColumn[]
 } & Recordable
 
 declare type TableSlotDefault = {
@@ -27,3 +28,9 @@ declare interface Pagination {
   disabled?: boolean
   hideOnSinglePage?: boolean
 }
+
+declare interface TableSetPropsType {
+  field: string
+  path: string
+  value: any
+}