Browse Source

wip: Table component developing

kailong321200875 3 years ago
parent
commit
7b7fcfef59

+ 65 - 0
mock/table/index.ts

@@ -0,0 +1,65 @@
+import { config } from '@/config/axios/config'
+import { MockMethod } from 'vite-plugin-mock'
+import { toAnyString } from '@/utils'
+import Mock from 'mockjs'
+
+const { result_code } = config
+
+const timeout = 1000
+
+const count = 100
+
+const baseContent =
+  '<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
+
+const List: {
+  id: string
+  author: string
+  title: string
+  content: string
+  importance: number
+  display_time: string
+  pageviews: number
+}[] = []
+
+for (let i = 0; i < count; i++) {
+  List.push(
+    Mock.mock({
+      id: toAnyString(),
+      // timestamp: +Mock.Random.date('T'),
+      author: '@first',
+      title: '@title(5, 10)',
+      content: baseContent,
+      importance: '@integer(1, 3)',
+      display_time: '@datetime',
+      pageviews: '@integer(300, 5000)'
+      // image_uri
+    })
+  )
+}
+
+export default [
+  // 登录接口
+  {
+    url: '/example/list',
+    method: 'get',
+    timeout,
+    response: ({ query }) => {
+      const { title, pageIndex, pageSize } = query
+      const mockList = List.filter((item) => {
+        if (title && item.title.indexOf(title) < 0) return false
+        return true
+      })
+      const pageList = mockList.filter(
+        (_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
+      )
+      return {
+        code: result_code,
+        data: {
+          total: mockList.length,
+          list: pageList
+        }
+      }
+    }
+  }
+] as MockMethod[]

+ 15 - 15
package.json

@@ -32,15 +32,15 @@
     "axios": "^0.25.0",
     "echarts": "^5.3.0",
     "echarts-wordcloud": "^2.0.0",
-    "element-plus": "1.3.0-beta.9",
-    "intro.js": "^4.3.0",
+    "element-plus": "2.0.0",
+    "intro.js": "^5.0.0",
     "lodash-es": "^4.17.21",
     "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
-    "pinia": "^2.0.10",
+    "pinia": "^2.0.11",
     "qrcode": "^1.5.0",
     "qs": "^6.10.3",
-    "vue": "3.2.26",
+    "vue": "3.2.30",
     "vue-i18n": "9.1.9",
     "vue-router": "^4.0.12",
     "vue-types": "^4.1.1",
@@ -49,30 +49,30 @@
   "devDependencies": {
     "@commitlint/cli": "^16.1.0",
     "@commitlint/config-conventional": "^16.0.0",
-    "@iconify/json": "^2.0.30",
-    "@intlify/vite-plugin-vue-i18n": "^3.2.1",
+    "@iconify/json": "^2.0.34",
+    "@intlify/vite-plugin-vue-i18n": "^3.2.2",
     "@purge-icons/generated": "^0.7.0",
     "@types/intro.js": "^3.0.2",
-    "@types/lodash-es": "^4.17.5",
-    "@types/node": "^17.0.13",
+    "@types/lodash-es": "^4.17.6",
+    "@types/node": "^17.0.15",
     "@types/nprogress": "^0.2.0",
     "@types/qrcode": "^1.4.2",
     "@types/qs": "^6.9.7",
-    "@typescript-eslint/eslint-plugin": "^5.10.1",
-    "@typescript-eslint/parser": "^5.10.1",
+    "@typescript-eslint/eslint-plugin": "^5.10.2",
+    "@typescript-eslint/parser": "^5.10.2",
     "@vitejs/plugin-vue": "^2.1.0",
     "@vitejs/plugin-vue-jsx": "^1.3.3",
     "autoprefixer": "^10.4.2",
     "commitizen": "^4.2.4",
     "eslint": "^8.8.0",
     "eslint-config-prettier": "^8.3.0",
-    "eslint-define-config": "^1.2.3",
+    "eslint-define-config": "^1.2.4",
     "eslint-plugin-prettier": "^4.0.0",
-    "eslint-plugin-vue": "^8.4.0",
+    "eslint-plugin-vue": "^8.4.1",
     "husky": "^7.0.4",
     "less": "^4.1.2",
-    "lint-staged": "^12.3.2",
-    "postcss": "^8.4.5",
+    "lint-staged": "^12.3.3",
+    "postcss": "^8.4.6",
     "postcss-html": "^1.3.0",
     "postcss-less": "^6.0.0",
     "prettier": "^2.5.1",
@@ -92,7 +92,7 @@
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-vue-setup-extend": "^0.4.0",
     "vite-plugin-windicss": "^1.6.3",
-    "vue-tsc": "^0.31.1",
+    "vue-tsc": "^0.31.2",
     "windicss": "^3.4.3",
     "windicss-analysis": "^0.3.5"
   },

File diff suppressed because it is too large
+ 65 - 662
pnpm-lock.yaml


+ 11 - 0
src/api/table/index.ts

@@ -0,0 +1,11 @@
+import { useAxios } from '@/hooks/web/useAxios'
+import type { TableData } from './types'
+
+const { request } = useAxios()
+
+export const getTableListApi = ({ params }: AxiosConfig) => {
+  return request<{
+    total: number
+    list: TableData[]
+  }>({ url: '/example/list', method: 'get', params })
+}

+ 9 - 0
src/api/table/types.ts

@@ -0,0 +1,9 @@
+export type TableData = {
+  id: string
+  author: string
+  title: string
+  content: string
+  importance: number
+  display_time: string
+  pageviews: number
+}

+ 16 - 12
src/components/Search/src/Search.vue

@@ -39,9 +39,16 @@ const emit = defineEmits(['search', 'reset'])
 const visible = ref(true)
 
 const newSchema = computed(() => {
-  let schema: FormSchema[] = []
+  let schema: FormSchema[] = cloneDeep(props.schema)
+  if (props.expand && props.expandField && !unref(visible)) {
+    const index = findIndex(schema, (v: FormSchema) => v.field === props.expandField)
+    if (index > -1) {
+      const length = schema.length
+      schema.splice(index + 1, length)
+    }
+  }
   if (props.layout === 'inline') {
-    schema = cloneDeep(props.schema).concat([
+    schema = schema.concat([
       {
         field: 'action',
         formItemProps: {
@@ -49,14 +56,6 @@ const newSchema = computed(() => {
         }
       }
     ])
-  } else {
-    schema = cloneDeep(props.schema)
-  }
-  if (props.expand && props.expandField && !unref(visible)) {
-    const index = findIndex(schema, (v: FormSchema) => v.field === props.expandField)
-    if (index > -1) {
-      schema.splice(0, index + 1)
-    }
   }
   return schema
 })
@@ -86,6 +85,11 @@ const bottonButtonStyle = computed(() => {
     textAlign: props.buttomPosition
   }
 }) as CSSProperties
+
+const setVisible = () => {
+  unref(elFormRef)?.resetFields()
+  visible.value = !unref(visible)
+}
 </script>
 
 <template>
@@ -108,7 +112,7 @@ const bottonButtonStyle = computed(() => {
           <Icon icon="ep:refresh-right" class="mr-5px" />
           {{ t('common.reset') }}
         </ElButton>
-        <ElButton v-if="showReset" type="text" @click="visible = !visible">
+        <ElButton v-if="showReset" type="text" @click="setVisible">
           {{ t(visible ? 'common.shrink' : 'common.expand') }}
           <Icon :icon="visible ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
         </ElButton>
@@ -126,7 +130,7 @@ const bottonButtonStyle = computed(() => {
         <Icon icon="ep:refresh-right" class="mr-5px" />
         {{ t('common.reset') }}
       </ElButton>
-      <ElButton v-if="showReset" type="text" @click="visible = !visible">
+      <ElButton v-if="showReset" type="text" @click="setVisible">
         {{ t(visible ? 'common.shrink' : 'common.expand') }}
         <Icon :icon="visible ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
       </ElButton>

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

@@ -0,0 +1,3 @@
+import Table from './src/Table.vue'
+
+export { Table }

+ 126 - 0
src/components/Table/src/Table.vue

@@ -0,0 +1,126 @@
+<script lang="tsx">
+import { ElTable, ElTableColumn } from 'element-plus'
+import { defineComponent, PropType, ref, computed, unref } from 'vue'
+import { propTypes } from '@/utils/propTypes'
+import { setIndex } from './helper'
+import { getSlot } from '@/utils/tsxHelper'
+
+export default defineComponent({
+  name: 'Table',
+  props: {
+    // 是否多选
+    selection: propTypes.bool.def(true),
+    // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
+    showOverflowTooltip: propTypes.bool.def(true),
+    // 表头
+    columns: {
+      type: Array as PropType<TableColumn[]>,
+      default: () => []
+    },
+    // 是否展示分页
+    // pagination: {
+    //   type: [Boolean, Object] as PropType<boolean | IObj>,
+    //   default: false
+    // },
+    // 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
+    reserveSelection: propTypes.bool.def(false),
+    // 加载状态
+    loading: propTypes.bool.def(false),
+    // 是否叠加索引
+    reserveIndex: propTypes.bool.def(true),
+    // 对齐方式
+    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>>()
+
+    const getProps = computed(() => props)
+
+    const getBindValue = computed(() => {
+      const bindValue: Recordable = { ...attrs, ...props }
+      delete bindValue.columns
+      delete bindValue.data
+      return bindValue
+    })
+
+    const renderTableSelection = () => {
+      return (
+        <ElTableColumn
+          type="selection"
+          reserveSelection={props.reserveSelection}
+          align={unref(getProps).align}
+          headerAlign={unref(getProps).headerAlign}
+          width="50"
+        ></ElTableColumn>
+      )
+    }
+
+    const rnderTableColumn = (columns: TableColumn[]) => {
+      return (props.selection ? [renderTableSelection()] : []).concat(
+        columns.map((v, i) => {
+          if (v.type === 'index') {
+            return (
+              <ElTableColumn
+                type="index"
+                index={v.index ? v.index : setIndex()}
+                align={v.align || unref(getProps).align}
+                headerAlign={v.headerAlign || unref(getProps).headerAlign}
+                label={v.label}
+                width="100px"
+              ></ElTableColumn>
+            )
+          } else {
+            return (
+              <ElTableColumn
+                showOverflowTooltip={unref(getProps).showOverflowTooltip}
+                align={unref(getProps).align}
+                headerAlign={unref(getProps).headerAlign}
+                {...v}
+                prop={v.field}
+              >
+                {{
+                  default: () =>
+                    // @ts-ignore
+                    getSlot(slots, v.field, { row: props.data[i], field: v.field, index: i }) ||
+                    v?.formatter?.() ||
+                    props.data[i][v.field],
+                  // @ts-ignore
+                  header: getSlot(slots, `${v.field}-header`)
+                }}
+              </ElTableColumn>
+            )
+          }
+        })
+      )
+    }
+
+    return () => (
+      <>
+        <ElTable
+          // @ts-ignore
+          ref={tableRef}
+          data={unref(getProps).data}
+          {...getBindValue}
+          v-loading={unref(getProps).loading}
+        >
+          {{
+            default: () => rnderTableColumn(props.columns),
+            // @ts-ignore
+            append: () => getSlot(slots, 'append')
+          }}
+        </ElTable>
+      </>
+    )
+  }
+})
+</script>

+ 3 - 0
src/components/Table/src/helper.ts

@@ -0,0 +1,3 @@
+export const setIndex = () => {
+  return 1
+}

+ 17 - 1
src/locales/en.ts

@@ -100,7 +100,9 @@ export default {
     infotip: 'Infotip',
     form: 'Form',
     defaultForm: 'All examples',
-    search: 'Search'
+    search: 'Search',
+    table: 'Table',
+    defaultTable: 'Basic example'
   },
   analysis: {
     newUser: 'New user',
@@ -301,5 +303,19 @@ export default {
     left: 'left',
     center: 'center',
     right: 'right'
+  },
+  tableDemo: {
+    table: 'Table',
+    tableDes: 'Secondary packaging of Table components based on ElementPlus',
+    index: 'Index',
+    title: 'Title',
+    author: 'Author',
+    displayTime: 'Display time',
+    importance: 'Importance',
+    pageviews: 'Pageviews',
+    action: 'Action',
+    important: 'Important',
+    good: 'Good',
+    commonly: 'Commonly'
   }
 }

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

@@ -100,7 +100,9 @@ export default {
     infotip: '信息提示',
     form: '表单',
     defaultForm: '全部示例',
-    search: '查询'
+    search: '查询',
+    table: '表格',
+    defaultTable: '基础示例'
   },
   analysis: {
     newUser: '新增用户',
@@ -298,5 +300,19 @@ export default {
     left: '左',
     center: '中',
     right: '右'
+  },
+  tableDemo: {
+    table: '表格',
+    tableDes: '基于 ElementPlus 的 Table 组件二次封装',
+    index: '序号',
+    title: '标题',
+    author: '作者',
+    displayTime: '创建时间',
+    importance: '重要性',
+    pageviews: '阅读数',
+    action: '操作',
+    important: '重要',
+    good: '良好',
+    commonly: '一般'
   }
 }

+ 19 - 0
src/router/index.ts

@@ -131,6 +131,25 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           }
         ]
       },
+      {
+        path: 'table',
+        component: getParentLayout(),
+        name: 'TableDemo',
+        meta: {
+          title: t('router.table'),
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'default-table',
+            component: () => import('@/views/Components/Table/DefaultTable.vue'),
+            name: 'DefaultTable',
+            meta: {
+              title: t('router.defaultTable')
+            }
+          }
+        ]
+      },
       {
         path: 'search',
         component: () => import('@/views/Components/Search.vue'),

+ 12 - 0
src/utils/index.ts

@@ -95,3 +95,15 @@ export function formatTime(time: Date | number | string, fmt: string) {
     return fmt
   }
 }
+
+/**
+ * 生成随机字符串
+ */
+export function toAnyString() {
+  const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
+    const r: number = (Math.random() * 16) | 0
+    const v: number = c === 'x' ? r : (r & 0x3) | 0x8
+    return v.toString()
+  })
+  return str
+}

+ 1 - 1
src/views/Components/Qrcode.vue

@@ -100,7 +100,7 @@ const disabledClick = () => {
       <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
         <ElCard shadow="hover" class="mb-10px text-center">
           <div class="font-bold">{{ t('qrcodeDemo.size') }}</div>
-          <Qrcode :text="title" :width="250" />
+          <Qrcode :text="title" :width="100" />
         </ElCard>
       </ElCol>
     </ElRow>

+ 95 - 0
src/views/Components/Table/DefaultTable.vue

@@ -0,0 +1,95 @@
+<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'
+
+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 === 1 ? 'warning' : 'danger'
+        },
+        () =>
+          cellValue === 1
+            ? t('tableDemo.important')
+            : cellValue === 1
+            ? t('tableDemo.good')
+            : t('tableDemo.commonly')
+      )
+    }
+  },
+  {
+    field: 'pageviews',
+    label: t('tableDemo.pageviews')
+  },
+  {
+    field: 'action',
+    label: t('tableDemo.action')
+  }
+]
+
+const loading = ref(true)
+
+let tableDataList = ref<TableData[]>([])
+
+const getTableList = async () => {
+  const res = await getTableListApi({
+    params: {
+      pageIndex: 1,
+      pageSize: 20
+    }
+  })
+    .catch(() => {})
+    .finally(() => {
+      loading.value = false
+    })
+  if (res) {
+    tableDataList.value = res.data.list
+  }
+}
+
+getTableList()
+
+const acitonFn = (data: TableColumnDefault) => {
+  console.log(data)
+}
+</script>
+
+<template>
+  <ContentWrap :title="t('tableDemo.table')" :message="t('tableDemo.tableDes')">
+    <Table :columns="columns" :data="tableDataList" :loading="loading">
+      <template #action="data">
+        <ElButton @click="acitonFn(data as TableColumnDefault)">{{
+          t('tableDemo.action')
+        }}</ElButton>
+      </template>
+    </Table>
+  </ContentWrap>
+</template>

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

@@ -0,0 +1,10 @@
+declare type TableColumn = {
+  field: string
+  label?: string
+} & Recordable
+
+declare type TableColumnDefault = {
+  row: Recordable
+  field: string
+  index: number
+}

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