Browse Source

wip: Table component developing

陈凯龙 3 years ago
parent
commit
b271e13227

+ 10 - 9
package.json

@@ -26,13 +26,13 @@
   },
   "dependencies": {
     "@iconify/iconify": "^2.1.2",
-    "@vueuse/core": "^7.5.5",
+    "@vueuse/core": "^7.6.0",
     "@zxcvbn-ts/core": "^1.2.0",
     "animate.css": "^4.1.1",
     "axios": "^0.25.0",
     "echarts": "^5.3.0",
     "echarts-wordcloud": "^2.0.0",
-    "element-plus": "2.0.0",
+    "element-plus": "2.0.1",
     "intro.js": "^5.0.0",
     "lodash-es": "^4.17.21",
     "mockjs": "^1.1.0",
@@ -54,16 +54,17 @@
     "@purge-icons/generated": "^0.7.0",
     "@types/intro.js": "^3.0.2",
     "@types/lodash-es": "^4.17.6",
-    "@types/node": "^17.0.15",
+    "@types/node": "^17.0.16",
     "@types/nprogress": "^0.2.0",
     "@types/qrcode": "^1.4.2",
     "@types/qs": "^6.9.7",
-    "@typescript-eslint/eslint-plugin": "^5.10.2",
-    "@typescript-eslint/parser": "^5.10.2",
+    "@typescript-eslint/eslint-plugin": "^5.11.0",
+    "@typescript-eslint/parser": "^5.11.0",
     "@vitejs/plugin-vue": "^2.1.0",
     "@vitejs/plugin-vue-jsx": "^1.3.3",
     "autoprefixer": "^10.4.2",
     "commitizen": "^4.2.4",
+    "consola": "^2.15.3",
     "eslint": "^8.8.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-define-config": "^1.2.4",
@@ -78,20 +79,20 @@
     "prettier": "^2.5.1",
     "pretty-quick": "^3.1.3",
     "rimraf": "^3.0.2",
-    "stylelint": "^14.3.0",
+    "stylelint": "^14.4.0",
     "stylelint-config-html": "^1.0.0",
     "stylelint-config-prettier": "^9.0.3",
-    "stylelint-config-standard": "^24.0.0",
+    "stylelint-config-standard": "^25.0.0",
     "stylelint-order": "^5.0.0",
     "typescript": "4.5.5",
     "vite": "2.7.13",
     "vite-plugin-eslint": "^1.3.0",
     "vite-plugin-mock": "^2.9.6",
     "vite-plugin-purge-icons": "^0.7.0",
-    "vite-plugin-style-import": "^1.4.1",
+    "vite-plugin-style-import": "^2.0.0",
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-vue-setup-extend": "^0.4.0",
-    "vite-plugin-windicss": "^1.6.3",
+    "vite-plugin-windicss": "^1.7.0",
     "vue-tsc": "^0.31.2",
     "windicss": "^3.4.3",
     "windicss-analysis": "^0.3.5"

File diff suppressed because it is too large
+ 71 - 544
pnpm-lock.yaml


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

@@ -1,3 +1,7 @@
 import Table from './src/Table.vue'
 
+export interface TableExpose {
+  setProps: (props: Recordable) => void
+}
+
 export { Table }

+ 116 - 19
src/components/Table/src/Table.vue

@@ -1,13 +1,16 @@
 <script lang="tsx">
-import { ElTable, ElTableColumn } from 'element-plus'
-import { defineComponent, PropType, ref, computed, unref } from 'vue'
+import { ElTable, ElTableColumn, ElPagination } from 'element-plus'
+import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
 import { propTypes } from '@/utils/propTypes'
 import { setIndex } from './helper'
 import { getSlot } from '@/utils/tsxHelper'
+import type { TableProps } from './types'
 
 export default defineComponent({
   name: 'Table',
   props: {
+    pageSize: propTypes.number.def(10),
+    currentPage: propTypes.number.def(1),
     // 是否多选
     selection: propTypes.bool.def(true),
     // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
@@ -18,33 +21,105 @@ export default defineComponent({
       default: () => []
     },
     // 是否展示分页
-    // pagination: {
-    //   type: [Boolean, Object] as PropType<boolean | IObj>,
-    //   default: false
-    // },
+    pagination: {
+      type: Object as PropType<Pagination>,
+      default: (): Pagination | undefined => undefined
+    },
     // 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
     reserveSelection: propTypes.bool.def(false),
     // 加载状态
     loading: propTypes.bool.def(false),
     // 是否叠加索引
-    reserveIndex: propTypes.bool.def(true),
+    reserveIndex: propTypes.bool.def(false),
     // 对齐方式
     align: propTypes.string
       .validate((v: string) => ['left', 'center', 'right'].includes(v))
       .def('left'),
+    // 表头对齐方式
     headerAlign: propTypes.string
       .validate((v: string) => ['left', 'center', 'right'].includes(v))
       .def('left'),
-    // 表头对齐方式
     data: {
       type: Array as PropType<Recordable[]>,
       default: () => []
     }
   },
-  setup(props, { attrs, slots }) {
-    const tableRef = ref<ComponentRef<typeof ElTable>>()
+  emits: ['update:pageSize', 'update:currentPage', 'register'],
+  setup(props, { attrs, slots, emit, expose }) {
+    const elTableRef = ref<ComponentRef<typeof ElTable>>()
+
+    // 注册
+    onMounted(() => {
+      emit('register', unref(elTableRef)?.$parent, unref(elTableRef))
+    })
+
+    const pageSizeRef = ref(props.pageSize)
+
+    const currentPageRef = ref(props.currentPage)
+
+    // useTable传入的props
+    const outsideProps = ref<TableProps>({})
+
+    const mergeProps = ref<TableProps>({})
+
+    const getProps = computed(() => {
+      const propsObj = { ...props }
+      Object.assign(propsObj, unref(mergeProps))
+      return propsObj
+    })
+
+    const setProps = (props: TableProps = {}) => {
+      mergeProps.value = Object.assign(unref(mergeProps), props)
+      outsideProps.value = props
+    }
 
-    const getProps = computed(() => props)
+    expose({
+      setProps
+    })
+
+    const pagination = computed(() => {
+      return Object.assign(
+        {
+          small: false,
+          background: false,
+          pagerCount: 7,
+          layout: 'sizes, prev, pager, next, jumper, ->, total',
+          pageSizes: [10, 20, 30, 40, 50, 100],
+          disabled: false,
+          hideOnSinglePage: false,
+          total: 10
+        },
+        unref(getProps).pagination
+      )
+    })
+
+    watch(
+      () => unref(getProps).pageSize,
+      (val: number) => {
+        pageSizeRef.value = val
+      }
+    )
+
+    watch(
+      () => unref(getProps).currentPage,
+      (val: number) => {
+        currentPageRef.value = val
+      }
+    )
+
+    watch(
+      () => pageSizeRef.value,
+      (val: number) => {
+        emit('update:pageSize', val)
+      }
+    )
+
+    watch(
+      () => currentPageRef.value,
+      (val: number) => {
+        emit('update:currentPage', val)
+      }
+    )
 
     const getBindValue = computed(() => {
       const bindValue: Recordable = { ...attrs, ...props }
@@ -54,25 +129,37 @@ export default defineComponent({
     })
 
     const renderTableSelection = () => {
-      return (
+      // 渲染多选
+      return unref(getProps).selection ? (
         <ElTableColumn
           type="selection"
-          reserveSelection={props.reserveSelection}
+          reserveSelection={unref(getProps).reserveSelection}
           align={unref(getProps).align}
           headerAlign={unref(getProps).headerAlign}
           width="50"
         ></ElTableColumn>
-      )
+      ) : undefined
     }
 
     const rnderTableColumn = (columns: TableColumn[]) => {
-      return (props.selection ? [renderTableSelection()] : []).concat(
+      return [renderTableSelection()].concat(
         columns.map((v) => {
+          // 自定生成序号
           if (v.type === 'index') {
             return (
               <ElTableColumn
                 type="index"
-                index={v.index ? v.index : setIndex()}
+                index={
+                  v.index
+                    ? v.index
+                    : (index) =>
+                        setIndex(
+                          unref(getProps).reserveIndex,
+                          index,
+                          unref(getProps).pageSize,
+                          unref(getProps).currentPage
+                        )
+                }
                 align={v.align || unref(getProps).align}
                 headerAlign={v.headerAlign || unref(getProps).headerAlign}
                 label={v.label}
@@ -91,7 +178,9 @@ export default defineComponent({
                 {{
                   default: (data: TableSlotDefault) =>
                     // @ts-ignore
-                    getSlot(slots, v.field, data) || v?.formatter?.() || data.row[v.field],
+                    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`)
                 }}
@@ -106,17 +195,25 @@ export default defineComponent({
       <>
         <ElTable
           // @ts-ignore
-          ref={tableRef}
+          ref={elTableRef}
           data={unref(getProps).data}
           {...getBindValue}
           v-loading={unref(getProps).loading}
         >
           {{
-            default: () => rnderTableColumn(props.columns),
+            default: () => rnderTableColumn(unref(getProps).columns),
             // @ts-ignore
             append: () => getSlot(slots, 'append')
           }}
         </ElTable>
+        {unref(getProps).pagination ? (
+          <ElPagination
+            v-model:pageSize={pageSizeRef.value}
+            v-model:currentPage={currentPageRef.value}
+            class="mt-10px"
+            {...unref(pagination)}
+          ></ElPagination>
+        ) : undefined}
       </>
     )
   }

+ 7 - 2
src/components/Table/src/helper.ts

@@ -1,3 +1,8 @@
-export const setIndex = () => {
-  return 1
+export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => {
+  const newIndex = index + 1
+  if (reserveIndex) {
+    return size * (current - 1) + newIndex
+  } else {
+    return newIndex
+  }
 }

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

@@ -0,0 +1,23 @@
+export type TableProps = {
+  pageSize?: number
+  currentPage?: number
+  // 是否多选
+  selection?: boolean
+  // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
+  showOverflowTooltip?: boolean
+  // 表头
+  columns?: TableColumn[]
+  // 是否展示分页
+  pagination?: Pagination | undefined
+  // 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
+  reserveSelection?: boolean
+  // 加载状态
+  loading?: boolean
+  // 是否叠加索引
+  reserveIndex?: boolean
+  // 对齐方式
+  align?: 'left' | 'center' | 'right'
+  // 表头对齐方式
+  headerAlign?: 'left' | 'center' | 'right'
+  data?: Recordable
+} & Recordable

+ 116 - 0
src/hooks/web/useTable.ts

@@ -0,0 +1,116 @@
+import { Table, TableExpose } from '@/components/Table'
+import { ElTable } from 'element-plus'
+import { ref, reactive, watch, computed, unref, nextTick } from 'vue'
+import { AxiosPromise } from 'axios'
+import { get, assign } from 'lodash-es'
+import type { TableProps } from '@/components/Table/src/types'
+
+interface UseTableConfig<T, L> {
+  getListApi: (option: L) => AxiosPromise<T>
+  // 返回数据格式配置
+  response: {
+    list: string
+    total?: string
+  }
+}
+
+interface TableObject<K, L> {
+  pageSize: number
+  currentPage: number
+  total: number
+  tableList: K[]
+  parmasObj: L
+  loading: boolean
+}
+
+export const useTable = <T, K, L extends AxiosConfig = AxiosConfig>(
+  config?: UseTableConfig<T, L>
+) => {
+  const tableObject = reactive<TableObject<K, L>>({
+    // 页数
+    pageSize: 10,
+    // 当前页
+    currentPage: 1,
+    // 总条数
+    total: 10,
+    // 表格数据
+    tableList: [],
+    // AxiosConfig 配置
+    parmasObj: {} as L,
+    // 加载中
+    loading: true
+  })
+
+  const parmasObj = computed(() => {
+    return assign(
+      {
+        params: {
+          pageSize: tableObject.pageSize,
+          pageIndex: tableObject.currentPage
+        }
+      },
+      tableObject.parmasObj
+    )
+  })
+
+  watch(
+    () => tableObject.currentPage,
+    () => {
+      methods.getList()
+    }
+  )
+
+  watch(
+    () => tableObject.pageSize,
+    () => {
+      tableObject.currentPage = 1
+      methods.getList()
+    }
+  )
+
+  // Table实例
+  const tableRef = ref<typeof Table & TableExpose>()
+
+  // ElTable实例
+  const elTableRef = ref<ComponentRef<typeof ElTable>>()
+
+  const register = (ref: typeof Table & TableExpose, elRef: ComponentRef<typeof ElTable>) => {
+    tableRef.value = ref
+    elTableRef.value = elRef
+  }
+
+  const getTable = async () => {
+    await nextTick()
+    const table = unref(tableRef)
+    if (!table) {
+      console.error('The table is not registered. Please use the register method to register')
+    }
+    return table
+  }
+
+  const methods = {
+    getList: async () => {
+      tableObject.loading = true
+      const res = await config
+        ?.getListApi(unref(parmasObj) as L)
+        .catch(() => {})
+        .finally(() => {
+          tableObject.loading = false
+        })
+      if (res) {
+        tableObject.tableList = get(res.data || {}, config?.response.list as string)
+        tableObject.total = get(res.data || {}, config?.response?.total as string) || 0
+      }
+    },
+    setProps: async (props: TableProps = {}) => {
+      const table = await getTable()
+      table?.setProps(props)
+    }
+  }
+
+  return {
+    register,
+    tableObject,
+    methods
+  }
+}

+ 12 - 1
src/locales/en.ts

@@ -316,6 +316,17 @@ export default {
     action: 'Action',
     important: 'Important',
     good: 'Good',
-    commonly: 'Commonly'
+    commonly: 'Commonly',
+    operate: 'operate',
+    example: 'example',
+    show: 'Show',
+    hidden: 'Hidden',
+    pagination: 'pagination',
+    reserveIndex: 'Reserve index',
+    restoreIndex: 'Restore index',
+    showSelections: 'show selections',
+    hiddenSelections: 'restore selections',
+    showExpandedRows: 'show expanded rows',
+    hiddenExpandedRows: 'hidden expanded rows'
   }
 }

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

@@ -313,6 +313,17 @@ export default {
     action: '操作',
     important: '重要',
     good: '良好',
-    commonly: '一般'
+    commonly: '一般',
+    operate: '操作',
+    example: '示例',
+    show: '显示',
+    hidden: '隐藏',
+    pagination: '分页',
+    reserveIndex: '叠加序号',
+    restoreIndex: '还原序号',
+    showSelections: '显示多选',
+    hiddenSelections: '隐藏多选',
+    showExpandedRows: '显示展开行',
+    hiddenExpandedRows: '隐藏展开行'
   }
 }

+ 8 - 0
src/router/index.ts

@@ -147,6 +147,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
             meta: {
               title: t('router.defaultTable')
             }
+          },
+          {
+            path: 'use-table',
+            component: () => import('@/views/Components/Table/UseTableDemo.vue'),
+            name: 'UseTable',
+            meta: {
+              title: 'UseTable'
+            }
           }
         ]
       },

+ 10 - 5
src/views/Components/Table/DefaultTable.vue

@@ -7,6 +7,11 @@ import { TableData } from '@/api/table/types'
 import { ref, h } from 'vue'
 import { ElTag, ElButton } from 'element-plus'
 
+interface Params {
+  pageIndex?: number
+  pageSize?: number
+}
+
 const { t } = useI18n()
 
 const columns: TableColumn[] = [
@@ -34,12 +39,12 @@ const columns: TableColumn[] = [
       return h(
         ElTag,
         {
-          type: cellValue === 1 ? 'success' : cellValue === 1 ? 'warning' : 'danger'
+          type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
         },
         () =>
           cellValue === 1
             ? t('tableDemo.important')
-            : cellValue === 1
+            : cellValue === 2
             ? t('tableDemo.good')
             : t('tableDemo.commonly')
       )
@@ -59,11 +64,11 @@ const loading = ref(true)
 
 let tableDataList = ref<TableData[]>([])
 
-const getTableList = async () => {
+const getTableList = async (params?: Params) => {
   const res = await getTableListApi({
-    params: {
+    params: params || {
       pageIndex: 1,
-      pageSize: 20
+      pageSize: 10
     }
   })
     .catch(() => {})

+ 143 - 0
src/views/Components/Table/UseTableDemo.vue

@@ -0,0 +1,143 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+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 { ElTag, ElButton } from 'element-plus'
+import { useTable } from '@/hooks/web/useTable'
+
+const { register, tableObject, methods } = useTable<
+  {
+    total: number
+    list: TableData[]
+  },
+  TableData
+>({
+  getListApi: getTableListApi,
+  response: {
+    list: 'list',
+    total: 'total'
+  }
+})
+
+const { getList } = methods
+
+getList()
+
+const { t } = useI18n()
+
+const columns: 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: 'action',
+    label: t('tableDemo.action')
+  }
+]
+
+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) => {
+  const { setProps } = methods
+  setProps({
+    reserveIndex: custom
+  })
+}
+
+const showSelections = (show: boolean) => {
+  const { setProps } = methods
+  setProps({
+    selection: show
+  })
+}
+</script>
+
+<template>
+  <ContentWrap :title="`UseTable ${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="showSelections(true)">{{ t('tableDemo.showExpandedRows') }}</ElButton>
+    <ElButton @click="showSelections(false)">{{ t('tableDemo.hiddenExpandedRows') }}</ElButton>
+  </ContentWrap>
+  <ContentWrap :title="`UseTable ${t('tableDemo.example')}`">
+    <Table
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+      :columns="columns"
+      :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>
+    </Table>
+  </ContentWrap>
+</template>

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

@@ -8,3 +8,22 @@ declare type TableSlotDefault = {
   column: TableColumn
   $index: number
 } & Recordable
+
+declare interface Pagination {
+  small?: boolean
+  background?: boolean
+  pageSize?: number
+  defaultPageSize?: number
+  total?: number
+  pageCount?: number
+  pagerCount?: number
+  currentPage?: number
+  defaultCurrentPage?: number
+  layout?: string
+  pageSizes?: number[]
+  popperClass?: string
+  prevText?: string
+  nextText?: string
+  disabled?: boolean
+  hideOnSinglePage?: boolean
+}

+ 2 - 2
vite.config.ts

@@ -6,7 +6,7 @@ import WindiCSS from 'vite-plugin-windicss'
 import VueJsx from '@vitejs/plugin-vue-jsx'
 import EslintPlugin from 'vite-plugin-eslint'
 import VueI18n from '@intlify/vite-plugin-vue-i18n'
-import StyleImport, { ElementPlusResolve } from 'vite-plugin-style-import'
+import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
 import PurgeIcons from 'vite-plugin-purge-icons'
 import { viteMockServe } from 'vite-plugin-mock'
@@ -34,7 +34,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       Vue(),
       VueJsx(),
       WindiCSS(),
-      StyleImport({
+      createStyleImportPlugin({
         resolves: [ElementPlusResolve()],
         libs: [{
           libraryName: 'element-plus',

Some files were not shown because too many files changed in this diff