diff --git a/mock/role/index.ts b/mock/role/index.ts index 72022c6..477806b 100644 --- a/mock/role/index.ts +++ b/mock/role/index.ts @@ -142,6 +142,14 @@ const adminList = [ title: 'UseTable' } }, + { + path: 'tree-table', + component: 'views/Components/Table/TreeTable', + name: 'TreeTable', + meta: { + title: 'TreeTable' + } + }, { path: 'ref-table', component: 'views/Components/Table/RefTable', @@ -481,6 +489,7 @@ const testList: string[] = [ '/components/table', '/components/table/default-table', '/components/table/use-table', + '/components/table/tree-table', '/components/table/ref-table', '/components/editor-demo', '/components/editor-demo/editor', diff --git a/mock/table/index.ts b/mock/table/index.ts index ca8512d..267b835 100644 --- a/mock/table/index.ts +++ b/mock/table/index.ts @@ -12,7 +12,7 @@ const count = 100 const baseContent = '

I am testing data, I am testing data.

' -let List: { +interface ListProps { id: string author: string title: string @@ -20,7 +20,20 @@ let List: { importance: number display_time: string pageviews: number -}[] = [] +} + +interface TreeListProps { + id: string + author: string + title: string + content: string + importance: number + display_time: string + pageviews: number + children: TreeListProps[] +} + +let List: ListProps[] = [] for (let i = 0; i < count; i++) { List.push( @@ -38,7 +51,114 @@ for (let i = 0; i < count; i++) { ) } +const treeList: TreeListProps[] = [] + +for (let i = 0; i < count; i++) { + treeList.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)', + children: [ + { + 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)', + children: [ + { + 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)' + }, + { + 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)' + } + ] + }, + { + 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)' + }, + { + 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)' + }, + { + 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/treeList', + method: 'get', + timeout, + response: ({ query }) => { + const { title, pageIndex, pageSize } = query + const mockList = treeList.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 { + data: { + code: code, + data: { + total: mockList.length, + list: pageList + } + } + } + } + }, // 列表接口 { url: '/example/list', diff --git a/src/api/table/index.ts b/src/api/table/index.ts index 36b3af9..c3b6d06 100644 --- a/src/api/table/index.ts +++ b/src/api/table/index.ts @@ -5,6 +5,10 @@ export const getTableListApi = (params: any) => { return request.get({ url: '/example/list', params }) } +export const getTreeTableListApi = (params: any) => { + return request.get({ url: '/example/treeList', params }) +} + export const saveTableApi = (data: Partial): Promise => { return request.post({ url: '/example/save', data }) } diff --git a/src/components/Table/index.ts b/src/components/Table/index.ts index 224d32d..1799596 100644 --- a/src/components/Table/index.ts +++ b/src/components/Table/index.ts @@ -1,6 +1,6 @@ import Table from './src/Table.vue' import { ElTable } from 'element-plus' -import { TableSetProps } from './src/types' +import { TableColumn, TableSetProps } from './src/types' export type { TableColumn, @@ -13,6 +13,8 @@ export type { export interface TableExpose { setProps: (props: Recordable) => void setColumn: (columnProps: TableSetProps[]) => void + addColumn: (column: TableColumn, index?: number) => void + delColumn: (field: string) => void selections: Recordable[] elTableRef: ComponentRef } diff --git a/src/components/Table/src/Table.vue b/src/components/Table/src/Table.vue index bfa0ae9..757fe9e 100644 --- a/src/components/Table/src/Table.vue +++ b/src/components/Table/src/Table.vue @@ -6,14 +6,13 @@ import { setIndex } from './helper' import type { TableProps, TableColumn, Pagination, TableSetProps } from './types' import { set } from 'lodash-es' import { CSSProperties } from 'vue' +import { getSlot } from '@/utils/tsxHelper' export default defineComponent({ name: 'Table', props: { pageSize: propTypes.number.def(10), currentPage: propTypes.number.def(1), - // 是否多选 - selection: propTypes.bool.def(true), // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip, showOverflowTooltip: propTypes.bool.def(true), // 表头 @@ -49,7 +48,7 @@ export default defineComponent({ height: propTypes.oneOfType([Number, String]), maxHeight: propTypes.oneOfType([Number, String]), stripe: propTypes.bool.def(false), - border: propTypes.bool.def(false), + border: propTypes.bool.def(true), size: { type: String as PropType, validator: (v: ComponentSize) => ['medium', 'small', 'mini'].includes(v) @@ -103,10 +102,7 @@ export default defineComponent({ >, default: () => undefined }, - rowKey: { - type: [Function, String] as PropType<(row: Recordable) => string | string>, - default: () => 'id' - }, + rowKey: propTypes.string.def('id'), emptyText: propTypes.string.def('No Data'), defaultExpandAll: propTypes.bool.def(false), expandRowKeys: { @@ -164,7 +160,7 @@ export default defineComponent({ default: () => undefined }, treeProps: { - type: Object as PropType<{ hasChildren: string; children: string; label: string }>, + type: Object as PropType<{ hasChildren?: string; children?: string; label?: string }>, default: () => ({ hasChildren: 'hasChildren', children: 'children', label: 'label' }) }, tableLayout: { @@ -175,7 +171,7 @@ export default defineComponent({ flexible: propTypes.bool.def(false) }, emits: ['update:pageSize', 'update:currentPage', 'register'], - setup(props, { attrs, emit, expose }) { + setup(props, { attrs, emit, slots, expose }) { const elTableRef = ref>() // 注册 @@ -217,6 +213,23 @@ export default defineComponent({ } } + const addColumn = (column: TableColumn, index?: number) => { + const { columns } = unref(getProps) + if (index) { + columns.splice(index, 0, column) + } else { + columns.push(column) + } + } + + const delColumn = (field: string) => { + const { columns } = unref(getProps) + const index = columns.findIndex((item) => item.field === field) + if (index > -1) { + columns.splice(index, 1) + } + } + const selections = ref([]) const selectionChange = (selection: Recordable[]) => { @@ -226,6 +239,8 @@ export default defineComponent({ expose({ setProps, setColumn, + delColumn, + addColumn, selections, elTableRef }) @@ -275,42 +290,17 @@ export default defineComponent({ ) const getBindValue = computed(() => { - const bindValue: Recordable = { ...attrs, ...props } + const bindValue: Recordable = { ...attrs, ...unref(getProps) } delete bindValue.columns delete bindValue.data + console.log(bindValue) return bindValue }) - const renderTableSelection = () => { - const { selection, reserveSelection, align, headerAlign } = unref(getProps) - // 渲染多选 - return selection ? ( - - ) : undefined - } - - // const renderTableExpand = () => { - // const { align, headerAlign, expand } = unref(getProps) - // // 渲染展开行 - // return expand ? ( - // - // {{ - // // @ts-ignore - // default: (data: TableSlotDefault) => getSlot(slots, 'expand', data) - // }} - // - // ) : undefined - // } - const renderTreeTableColumn = (columnsChildren: TableColumn[]) => { const { align, headerAlign, showOverflowTooltip } = unref(getProps) return columnsChildren.map((v) => { + if (v.hidden) return null const props = { ...v } as any if (props.children) delete props.children @@ -318,11 +308,13 @@ export default defineComponent({ const slots = { default: (...args: any[]) => { - if (props?.slots?.default) { - return slots.default(args) - } else if (children && children.length) { - return renderTreeTableColumn(children) - } + const data = args[0] + return children && children.length + ? renderTreeTableColumn(children) + : props?.slots?.default + ? props.slots.default(args) + : v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) || + data.row[v.field] } } if (props?.slots?.header) { @@ -351,59 +343,69 @@ export default defineComponent({ currentPage, align, headerAlign, - showOverflowTooltip + showOverflowTooltip, + reserveSelection } = unref(getProps) - return [renderTableSelection()].concat( - (columnsChildren || columns).map((v) => { - // 自定生成序号 - if (v.type === 'index') { - return ( - setIndex(reserveIndex, index, pageSize, currentPage) - } - align={v.align || align} - headerAlign={v.headerAlign || headerAlign} - label={v.label} - width="65px" - > - ) - } else { - const props = { ...v } as any - if (props.children) delete props.children - const children = v.children - - const slots = { - default: (...args: any[]) => { - if (props?.slots?.default) { - return slots.default(args) - } else if (children && children.length) { - return renderTreeTableColumn(children) - } + return (columnsChildren || columns).map((v) => { + if (v.hidden) return null + if (v.type === 'index') { + return ( + setIndex(reserveIndex, index, pageSize, currentPage) } - } - if (props?.slots?.header) { - slots['header'] = (...args: any[]) => props.slots.header(args) - } + align={v.align || align} + headerAlign={v.headerAlign || headerAlign} + label={v.label} + width="65px" + > + ) + } else if (v.type === 'selection') { + return ( + + ) + } else { + const props = { ...v } as any + if (props.children) delete props.children - return ( - - {slots} - - ) + const children = v.children + + const slots = { + default: (...args: any[]) => { + const data = args[0] + return children && children.length + ? renderTreeTableColumn(children) + : props?.slots?.default + ? props.slots.default(args) + : v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) || + data.row[v.field] + } } - }) - ) + if (props?.slots?.header) { + slots['header'] = (...args: any[]) => props.slots.header(args) + } + + return ( + + {slots} + + ) + } + }) } return () => ( @@ -415,7 +417,9 @@ export default defineComponent({ {...unref(getBindValue)} > {{ - default: () => renderTableColumn() + default: () => renderTableColumn(), + empty: () => getSlot(slots, 'empty') || props.emptyText, + append: () => getSlot(slots, 'append') }} {unref(getProps).pagination ? ( diff --git a/src/components/Table/src/types/index.ts b/src/components/Table/src/types/index.ts index 0799cff..173eb86 100644 --- a/src/components/Table/src/types/index.ts +++ b/src/components/Table/src/types/index.ts @@ -2,6 +2,11 @@ import { TableProps as ElTableProps } from 'element-plus' export interface TableColumn { field: string label?: string + type?: string + /** + * 是否隐藏 + */ + hidden?: boolean children?: TableColumn[] slots?: { default?: (...args: any[]) => JSX.Element | JSX.Element[] | null @@ -69,8 +74,6 @@ export interface TableSetProps { export interface TableProps extends Omit>, 'data'> { pageSize?: number currentPage?: number - // 是否多选 - selection?: boolean // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip, showOverflowTooltip?: boolean // 表头 diff --git a/src/hooks/web/useForm.ts b/src/hooks/web/useForm.ts index ef5a906..3846210 100644 --- a/src/hooks/web/useForm.ts +++ b/src/hooks/web/useForm.ts @@ -32,7 +32,7 @@ export const useForm = () => { const methods = { /** * @description 设置form组件的props - * @param field FormItem的field + * @param props form组件的props */ setProps: async (props: FormProps = {}) => { const form = await getForm() diff --git a/src/hooks/web/useTable.ts b/src/hooks/web/useTable.ts index b81f45a..e1fa3f9 100644 --- a/src/hooks/web/useTable.ts +++ b/src/hooks/web/useTable.ts @@ -1,107 +1,46 @@ -import { Table, TableExpose, TableProps, TableSetProps } from '@/components/Table' -import { ElTable, ElMessageBox, ElMessage } from 'element-plus' -import { ref, reactive, watch, computed, unref, nextTick, onMounted } from 'vue' -import { get } from 'lodash-es' -import { useI18n } from '@/hooks/web/useI18n' - -const { t } = useI18n() - -interface TableResponse { - total: number - list: T[] - pageNumber: number - pageSize: number -} +import { Table, TableExpose, TableProps, TableSetProps, TableColumn } from '@/components/Table' +import { ElTable } from 'element-plus' +import { ref, watch, unref, nextTick, onMounted } from 'vue' interface UseTableConfig { - // 是否初始化请求一次 + /** + * 是否初始化的时候请求一次 + */ immediate?: boolean - // 获取数据字段映射 - props?: { - list?: string - total?: string - } fetchDataApi: () => Promise<{ list: any[] total: number }> - // getListApi: (option: any) => Promise>> - // delListApi?: (option: any) => Promise - // 返回数据格式配置 - // response: { - // list: string - // total?: string - // } - // 默认传递的参数 - // defaultParams?: Recordable - // props?: TableProps -} - -interface TableObject { - pageSize: number - currentPage: number - total: number - list: T[] - params: any - loading: boolean - currentRow: Nullable } export const useTable = (config: UseTableConfig) => { const { immediate = true } = config const loading = ref(false) - const pageIndex = ref(1) + const currentPage = ref(1) const pageSize = ref(10) const total = ref(0) const dataList = ref([]) - const tableObject = reactive({ - // 页数 - pageSize: 10, - // 当前页 - currentPage: 1, - // 总条数 - total: 10, - // 表格数据 - list: [], - // AxiosConfig 配置 - params: { - // ...(config?.defaultParams || {}) - }, - // 加载中 - loading: true, - // 当前行的数据 - currentRow: null - }) - - const paramsObj = computed(() => { - return { - ...tableObject.params, - pageSize: tableObject.pageSize, - pageIndex: tableObject.currentPage + watch( + () => currentPage.value, + () => { + methods.getList() } - }) + ) - // watch( - // () => tableObject.currentPage, - // () => { - // methods.getList() - // } - // ) - - // watch( - // () => tableObject.pageSize, - // () => { - // // 当前页不为1时,修改页数后会导致多次调用getList方法 - // if (tableObject.currentPage === 1) { - // methods.getList() - // } else { - // tableObject.currentPage = 1 - // methods.getList() - // } - // } - // ) + watch( + () => pageSize.value, + () => { + // 当前页不为1时,修改页数后会导致多次调用getList方法 + if (unref(currentPage) === 1) { + methods.getList() + } else { + currentPage.value = 1 + methods.getList() + } + } + ) onMounted(() => { if (immediate) { @@ -148,6 +87,9 @@ export const useTable = (config: UseTableConfig) => { // } const methods = { + /** + * 获取表单数据 + */ getList: async () => { loading.value = true try { @@ -162,36 +104,62 @@ export const useTable = (config: UseTableConfig) => { } finally { loading.value = false } - // const res = await config?.getListApi(unref(paramsObj)).finally(() => { - // tableObject.loading = false - // }) - // if (res) { - // tableObject.list = get(res.data || {}, config?.response.list as string) - // tableObject.total = get(res.data || {}, config?.response?.total as string) || 0 - // } + }, + + /** + * @description 设置table组件的props + * @param props table组件的props + */ + setProps: async (props: TableProps = {}) => { + const table = await getTable() + table?.setProps(props) + }, + + /** + * @description 设置column + * @param columnProps 需要设置的列 + */ + setColumn: async (columnProps: TableSetProps[]) => { + const table = await getTable() + table?.setColumn(columnProps) + }, + + /** + * @description 新增column + * @param tableColumn 需要新增数据 + * @param index 在哪里新增 + */ + addColumn: async (tableColumn: TableColumn, index?: number) => { + const table = await getTable() + table?.addColumn(tableColumn, index) + }, + + /** + * @description 删除column + * @param field 删除哪个数据 + */ + delColumn: async (field: string) => { + const table = await getTable() + table?.delColumn(field) + }, + + /** + * @description 获取全选数据 + * @returns + */ + getSelections: async () => { + const table = await getTable() + return table?.selections || [] + }, + + /** + * @description 获取ElTable组件的实例 + * @returns ElTable instance + */ + getElTableExpose: async () => { + await getTable() + return unref(elTableRef) } - // setProps: async (props: TableProps = {}) => { - // const table = await getTable() - // table?.setProps(props) - // }, - // setColumn: async (columnProps: TableSetProps[]) => { - // const table = await getTable() - // table?.setColumn(columnProps) - // }, - // getSelections: async () => { - // const table = await getTable() - // return (table?.selections || []) as T[] - // }, - // // 与Search组件结合 - // setSearchParams: (data: Recordable) => { - // tableObject.currentPage = 1 - // tableObject.params = Object.assign(tableObject.params, { - // pageSize: tableObject.pageSize, - // pageIndex: tableObject.currentPage, - // ...data - // }) - // methods.getList() - // }, // // 删除数据 // delList: async (ids: string[] | number[], multiple: boolean, message = true) => { // const tableRef = await getTable() @@ -222,11 +190,9 @@ export const useTable = (config: UseTableConfig) => { return { tableRegister: register, - elTableRef, - tableObject, - methods, + tableMethods: methods, tableState: { - pageIndex, + currentPage, pageSize, total, dataList, diff --git a/src/locales/en.ts b/src/locales/en.ts index 4f2b13f..51eb882 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -417,7 +417,11 @@ export default { hiddenExpandedRows: 'Hidden expanded rows', changeTitle: 'Change title', header: 'Header', - selectAllNone: 'Select all / none' + selectAllNone: 'Select all / none', + delOrAddAction: 'Delete or add action', + showOrHiddenStripe: 'Show or hidden stripe', + showOrHiddenBorder: 'Show or hidden border', + fixedHeaderOrAuto: 'Fixed header or auto' }, richText: { richText: 'Rich text', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index e8f4fb0..494da9f 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -412,7 +412,11 @@ export default { hiddenExpandedRows: '隐藏展开行', changeTitle: '修改标题', header: '头部', - selectAllNone: '全选/全不选' + selectAllNone: '全选/全不选', + delOrAddAction: '删除/添加操作列', + showOrHiddenStripe: '显示/隐藏斑马纹', + showOrHiddenBorder: '显示/隐藏边框', + fixedHeaderOrAuto: '固定头部/自动' }, richText: { richText: '富文本', diff --git a/src/router/index.ts b/src/router/index.ts index 2c998df..7f55b3f 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -184,6 +184,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [ meta: { title: 'UseTable' } + }, + { + path: 'tree-table', + component: () => import('@/views/Components/Table/TreeTable.vue'), + name: 'TreeTable', + meta: { + title: 'TreeTable' + } } ] }, diff --git a/src/views/Components/Table/TreeTable.vue b/src/views/Components/Table/TreeTable.vue new file mode 100644 index 0000000..dfb1293 --- /dev/null +++ b/src/views/Components/Table/TreeTable.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/views/Components/Table/UseTableDemo.vue b/src/views/Components/Table/UseTableDemo.vue index 42d67d6..7575d21 100644 --- a/src/views/Components/Table/UseTableDemo.vue +++ b/src/views/Components/Table/UseTableDemo.vue @@ -1,18 +1,17 @@ + +