wip: Table component developing

This commit is contained in:
陈凯龙 2022-02-09 11:34:45 +08:00
parent 7ef216c87e
commit b271e13227
14 changed files with 1091 additions and 728 deletions

View File

@ -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 Load Diff

View File

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

View File

@ -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),
// schemashowOverflowTooltip,
@ -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>>()
const getProps = computed(() => props)
//
onMounted(() => {
emit('register', unref(elTableRef)?.$parent, unref(elTableRef))
})
const pageSizeRef = ref(props.pageSize)
const currentPageRef = ref(props.currentPage)
// useTableprops
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
}
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}
</>
)
}

View File

@ -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
}
}

View File

@ -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
src/hooks/web/useTable.ts Normal file
View File

@ -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
}
}

View File

@ -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'
}
}

View File

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

View File

@ -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'
}
}
]
},

View File

@ -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(() => {})

View File

@ -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>

View File

@ -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
}

View File

@ -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',