diff --git a/devui/table/__tests__/table.spec.ts b/devui/table/__tests__/table.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..35e37e75c5b4de5a4f51b7262d1b64e5eef3a8dc
--- /dev/null
+++ b/devui/table/__tests__/table.spec.ts
@@ -0,0 +1,8 @@
+import { mount } from '@vue/test-utils';
+import { Table } from '../index';
+
+describe('table test', () => {
+ it('table init render', async () => {
+ // todo
+ })
+})
diff --git a/devui/table/index.ts b/devui/table/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c40ee5ced62faca537baca02ee222613846a4a4f
--- /dev/null
+++ b/devui/table/index.ts
@@ -0,0 +1,19 @@
+import type { App } from 'vue'
+import Table from './src/table'
+import Column from './src/column/column'
+
+Table.install = function(app: App): void {
+ app.component(Table.name, Table)
+ app.component(Column.name, Column)
+}
+
+export { Table, Column }
+
+export default {
+ title: 'Table 表格',
+ category: '数据展示',
+ status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释
+ install(app: App): void {
+ app.use(Table as any)
+ }
+}
diff --git a/devui/table/src/body/body.scss b/devui/table/src/body/body.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6417799221226608b49e6c629169296611cb348f
--- /dev/null
+++ b/devui/table/src/body/body.scss
@@ -0,0 +1,20 @@
+@import '@devui/styles-var/devui-var.scss';
+
+.devui-tbody {
+ tr {
+ font-size: $devui-font-size-card-title;
+ color: $devui-text;
+ border: none;
+ border-bottom: 1px solid $devui-dividing-line;
+ background-color: $devui-global-bg-normal;
+
+ &:hover {
+ background-color: $devui-list-item-hover-bg;
+ }
+
+ td {
+ padding: 10px;
+ border: none;
+ }
+ }
+}
diff --git a/devui/table/src/body/body.tsx b/devui/table/src/body/body.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9b02b8679897d1deb3d3347c868d72b6e5ad4934
--- /dev/null
+++ b/devui/table/src/body/body.tsx
@@ -0,0 +1,33 @@
+import { defineComponent } from 'vue';
+import { TableBodyProps, TableBodyPropsTypes } from './body.type'
+import { useTableBody } from './use-body';
+import './body.scss';
+
+export default defineComponent({
+ name: 'DTableBody',
+ props: TableBodyProps,
+ setup(props: TableBodyPropsTypes) {
+ const { rowColumns } = useTableBody(props);
+
+ return { rowColumns };
+ },
+ render() {
+ const { rowColumns } = this;
+
+ return (
+
+ {rowColumns.map((row, rowIndex) => {
+ return (
+
+ {row.columns.map((column, index) => {
+ return (
+ {column.renderCell({ row, column, $index: index })} |
+ );
+ })}
+
+ );
+ })}
+
+ );
+ },
+});
\ No newline at end of file
diff --git a/devui/table/src/body/body.type.ts b/devui/table/src/body/body.type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c1febde00fb051c2b818c58fde03ba5004b0a021
--- /dev/null
+++ b/devui/table/src/body/body.type.ts
@@ -0,0 +1,10 @@
+import { ExtractPropTypes } from 'vue';
+
+export const TableBodyProps = {
+ store: {
+ type: Object,
+ default: {},
+ },
+};
+
+export type TableBodyPropsTypes = ExtractPropTypes;
diff --git a/devui/table/src/body/use-body.ts b/devui/table/src/body/use-body.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1657a06b50646cc594957432254062c6035cd1a2
--- /dev/null
+++ b/devui/table/src/body/use-body.ts
@@ -0,0 +1,15 @@
+import { computed } from 'vue';
+import { TableBodyPropsTypes } from './body.type'
+
+export function useTableBody(props: TableBodyPropsTypes): any {
+ const storeData = props.store.states;
+ const rowColumns = computed(() => {
+ return storeData._data.value.map((row) => {
+ const obj = Object.assign({}, row);
+ obj.columns = storeData._columns.value;
+ return obj;
+ });
+ });
+
+ return { rowColumns };
+}
\ No newline at end of file
diff --git a/devui/table/src/colgroup/colgroup.tsx b/devui/table/src/colgroup/colgroup.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..93e7197d8466045978538007fbb68379a8a8992b
--- /dev/null
+++ b/devui/table/src/colgroup/colgroup.tsx
@@ -0,0 +1,23 @@
+import { inject, defineComponent } from 'vue';
+import { Table } from '../table.type';
+import { Column } from '../column/column.type';
+
+export default defineComponent({
+ name: 'DColGroup',
+ setup() {
+ const parent: Table = inject('table');
+ const columns: Column[] = parent.store.states._columns;
+
+ return { columns };
+ },
+ render() {
+ const { columns } = this;
+ return (
+
+ {columns.map((column, index) => {
+ return ;
+ })}
+
+ );
+ },
+});
\ No newline at end of file
diff --git a/devui/table/src/column/column.tsx b/devui/table/src/column/column.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..60ef5e04cf8e85ae459d43673fd1ce60219978ab
--- /dev/null
+++ b/devui/table/src/column/column.tsx
@@ -0,0 +1,33 @@
+import { inject, defineComponent, onBeforeMount, onMounted } from 'vue';
+import {
+ Column,
+ TableColumnProps,
+ TableColumnPropsTypes,
+} from './column.type'
+import { Table } from '../table.type';
+import { useRender } from './use-column';
+
+export default defineComponent({
+ name: 'DColumn',
+ props: TableColumnProps,
+ setup(props: TableColumnPropsTypes) {
+ const column: Column = {
+ field: props.field,
+ header: props.header,
+ };
+ const parent: Table = inject('table');
+ const { setColumnWidth, setColumnRender } = useRender(props);
+
+ onBeforeMount(() => {
+ setColumnWidth(column);
+ setColumnRender(column);
+ });
+
+ onMounted(() => {
+ parent.store.insertColumn(column);
+ });
+ },
+ render() {
+ return null;
+ },
+});
\ No newline at end of file
diff --git a/devui/table/src/column/column.type.ts b/devui/table/src/column/column.type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cdab04098202602701a11228536e6d9339695253
--- /dev/null
+++ b/devui/table/src/column/column.type.ts
@@ -0,0 +1,38 @@
+import { PropType, ExtractPropTypes, VNode } from 'vue';
+
+export const TableColumnProps = {
+ header: {
+ type: String,
+ default: '',
+ },
+ field: {
+ type: String,
+ default: '',
+ },
+ width: {
+ type: [String, Number],
+ default: '',
+ },
+ minWidth: {
+ type: [String, Number],
+ default: 80,
+ },
+ formatter: {
+ type: Function as PropType<
+ (row: any, column: Column, cellValue, index: number) => VNode
+ >,
+ },
+};
+
+export type TableColumnPropsTypes = ExtractPropTypes;
+
+export interface Column {
+ field?: string
+ width?: number
+ minWidth?: number
+ realWidth?: number
+ header?: string
+ renderHeader?: () => void
+ renderCell?: (data: any) => void
+ formatter?: (row: any, column: Column, cellValue, index: number) => VNode
+}
\ No newline at end of file
diff --git a/devui/table/src/column/use-column.ts b/devui/table/src/column/use-column.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7c5fc4ce546f06d6694a7e4f318cb9b0309565bb
--- /dev/null
+++ b/devui/table/src/column/use-column.ts
@@ -0,0 +1,45 @@
+import { ref } from 'vue';
+import { Column, TableColumnPropsTypes } from './column.type'
+import { formatWidth, formatMinWidth } from '../utils';
+
+export function useRender(props: TableColumnPropsTypes): any {
+ const formatedWidth = ref(formatWidth(props.width));
+ const formatedMinWidth = ref(formatMinWidth(props.minWidth));
+ const setColumnWidth = (column: Column) => {
+ column.width = formatedWidth.value;
+ column.minWidth = formatedMinWidth.value;
+ column.realWidth = column.width || column.minWidth;
+ return column;
+ };
+
+ const setColumnRender = (column: Column) => {
+ column.renderHeader = () => {
+ return defaultRenderHeader(column);
+ };
+ column.renderCell = (data) => {
+ return defaultRenderCell(data);
+ };
+ };
+
+ return { setColumnWidth, setColumnRender };
+}
+
+function defaultRenderHeader(column: Column) {
+ return column.header;
+}
+
+function defaultRenderCell({
+ row,
+ column,
+ $index,
+}: {
+ row: any
+ column: Column
+ $index: number
+}) {
+ const value = row[column.field];
+ if (column.formatter) {
+ return column.formatter(row, column, value, $index);
+ }
+ return value?.toString?.() || '';
+}
\ No newline at end of file
diff --git a/devui/table/src/header/header.scss b/devui/table/src/header/header.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b2886eee13301a184943c62f14810f963c2ff2d9
--- /dev/null
+++ b/devui/table/src/header/header.scss
@@ -0,0 +1,18 @@
+@import '@devui/styles-var/devui-var.scss';
+
+.devui-thead {
+ tr {
+ font-size: $devui-font-size-card-title;
+ color: $devui-text;
+ font-weight: 700;
+ border: none;
+ border-bottom: 1px solid $devui-line;
+ background-color: $devui-global-bg-normal;
+
+ th {
+ text-align: left;
+ padding: 12px 10px;
+ border: none;
+ }
+ }
+}
diff --git a/devui/table/src/header/header.tsx b/devui/table/src/header/header.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..88c8001390e0de5c23bc5ccaa6e900cd2a3d8456
--- /dev/null
+++ b/devui/table/src/header/header.tsx
@@ -0,0 +1,22 @@
+import { defineComponent, toRefs } from 'vue';
+import { TableHeaderProps, TableHeaderPropsTypes } from './header.type'
+import './header.scss';
+
+export default defineComponent({
+ name: 'DTableHeader',
+ props: TableHeaderProps,
+ setup(props: TableHeaderPropsTypes) {
+ const { store } = toRefs(props)
+ const columns = store.value.states._columns.value;
+
+ return (
+
+
+ {columns.map((column, index) => {
+ return {column.renderHeader()} | ;
+ })}
+
+
+ )
+ }
+});
\ No newline at end of file
diff --git a/devui/table/src/header/header.type.ts b/devui/table/src/header/header.type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3369aa9d76063355dcfc33c108438a50279b2226
--- /dev/null
+++ b/devui/table/src/header/header.type.ts
@@ -0,0 +1,10 @@
+import { ExtractPropTypes } from 'vue';
+
+export const TableHeaderProps = {
+ store: {
+ type: Object,
+ default: {},
+ },
+};
+
+export type TableHeaderPropsTypes = ExtractPropTypes;
diff --git a/devui/table/src/store/index.ts b/devui/table/src/store/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7138064c7b394b6ba8e68e79d6a4c5c1cd194c2
--- /dev/null
+++ b/devui/table/src/store/index.ts
@@ -0,0 +1,30 @@
+import { ref, watch } from 'vue';
+import { TablePropsTypes } from '../table.type';
+import { Column } from '../column/column.type';
+
+export function createStore(props: TablePropsTypes): any {
+ const _data = ref([]);
+ const _columns = ref([]);
+ updateData();
+
+ watch(() => props.data, updateData, { deep: true });
+
+ function updateData() {
+ _data.value = [];
+ props.data.forEach((item) => {
+ _data.value.push(item);
+ });
+ }
+
+ const insertColumn = (column: Column) => {
+ _columns.value.push(column);
+ };
+
+ return {
+ insertColumn,
+ states: {
+ _data,
+ _columns,
+ },
+ };
+}
\ No newline at end of file
diff --git a/devui/table/src/table-types.ts b/devui/table/src/table-types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1f9cbcf673a594df6b5455b103d11e8783f27593
--- /dev/null
+++ b/devui/table/src/table-types.ts
@@ -0,0 +1,14 @@
+import type { PropType, ExtractPropTypes } from 'vue'
+
+type TableData = Array<{
+ [key: string]: any
+}>
+
+export const tableProps = {
+ data: {
+ type: Array as PropType,
+ required: true,
+ }
+} as const
+
+export type TableProps = ExtractPropTypes
diff --git a/devui/table/src/table.scss b/devui/table/src/table.scss
new file mode 100644
index 0000000000000000000000000000000000000000..9709244d058cbcb7bbdb2a00e8dcb53eee9aa9a8
--- /dev/null
+++ b/devui/table/src/table.scss
@@ -0,0 +1,26 @@
+@import '@devui/styles-var/devui-var.scss';
+
+.devui-table {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ border-spacing: 0;
+ border: none;
+
+ &-wrapper {
+ width: 100%;
+ overflow-x: auto;
+ }
+
+ &-striped {
+ tbody tr:nth-child(even) {
+ background-color: $devui-list-item-strip-bg;
+ }
+ }
+
+ &-empty {
+ width: 100%;
+ font-size: $devui-font-size;
+ text-align: center;
+ }
+}
diff --git a/devui/table/src/table.tsx b/devui/table/src/table.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..83146778b7d6595233b33ac52a0b792113a2ab9f
--- /dev/null
+++ b/devui/table/src/table.tsx
@@ -0,0 +1,36 @@
+import { provide, defineComponent, getCurrentInstance } from 'vue';
+import { Table, TableProps, TablePropsTypes } from './table.type';
+import { useTable } from './use-table';
+import { createStore } from './store';
+import ColGroup from './colgroup/colgroup';
+import TableHeader from './header/header';
+import TableBody from './body/body';
+import './table.scss';
+
+export default defineComponent({
+ name: 'DTable',
+ props: TableProps,
+ setup(props: TablePropsTypes) {
+ const table = getCurrentInstance() as Table;
+ const store = createStore(props);
+ table.store = store;
+ const { classes } = useTable(props);
+ provide('table', table);
+
+ return { classes, store };
+ },
+ render() {
+ const { classes, data, store, $slots } = this;
+ return (
+
+ {$slots.default()}
+
+
+
+ {!!data.length && }
+
+ {!data.length &&
No Data
}
+
+ );
+ },
+});
diff --git a/devui/table/src/table.type.ts b/devui/table/src/table.type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..563b9bb6a9cb2a2dfbf67c0a9e4b5c04754488bb
--- /dev/null
+++ b/devui/table/src/table.type.ts
@@ -0,0 +1,18 @@
+import { PropType, ExtractPropTypes, ComponentInternalInstance } from 'vue';
+
+export const TableProps = {
+ data: {
+ type: Array as PropType,
+ default: [],
+ },
+ striped: {
+ type: Boolean,
+ default: false,
+ },
+};
+
+export type TablePropsTypes = ExtractPropTypes;
+
+export interface Table extends ComponentInternalInstance {
+ store: any
+}
diff --git a/devui/table/src/use-table.ts b/devui/table/src/use-table.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c5e9f1e65d0bd212f4b92ab8de7ab32a374173f0
--- /dev/null
+++ b/devui/table/src/use-table.ts
@@ -0,0 +1,11 @@
+import { computed } from 'vue';
+import { TablePropsTypes } from './table.type';
+
+export function useTable(props: TablePropsTypes): any {
+ const classes = computed(() => ({
+ 'devui-table': true,
+ 'devui-table-striped': props.striped,
+ }));
+
+ return { classes };
+}
\ No newline at end of file
diff --git a/devui/table/src/utils.ts b/devui/table/src/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9043fe1a3f676a500a0ef664a033b131c1bb12a3
--- /dev/null
+++ b/devui/table/src/utils.ts
@@ -0,0 +1,11 @@
+export function formatWidth(width: number | string): number {
+ if (typeof width === 'number') {
+ return width;
+ }
+
+ return parseInt(width, 10) || 0;
+}
+
+export function formatMinWidth(minWidth: number | string): number {
+ return formatWidth(minWidth) || 80;
+}
\ No newline at end of file
diff --git a/docs/.vitepress/devui-theme/styles/demo-block.scss b/docs/.vitepress/devui-theme/styles/demo-block.scss
index 3c7b4b881d28f2ba93bac9cd5fe79fed557f128c..facd32f70602369c4a4a63da17179b36131600b1 100644
--- a/docs/.vitepress/devui-theme/styles/demo-block.scss
+++ b/docs/.vitepress/devui-theme/styles/demo-block.scss
@@ -23,4 +23,11 @@
.meta {
border-top: solid 1px $devui-dividing-line !important;
+ background-color: $devui-area !important;
+}
+
+.description {
+ border: solid 1px $devui-dividing-line !important;
+ color: $devui-text !important;
+ background-color: $devui-base-bg !important;
}
diff --git a/docs/.vitepress/devui-theme/styles/layout.scss b/docs/.vitepress/devui-theme/styles/layout.scss
index a25c86d6cdb49d5caeb7a24f28f21251e8021d16..8f0ef7a58e631aea3bf8bca270f8aa2745a379d9 100644
--- a/docs/.vitepress/devui-theme/styles/layout.scss
+++ b/docs/.vitepress/devui-theme/styles/layout.scss
@@ -159,6 +159,14 @@ ol {
padding-left: 1.25em;
}
+ul {
+ list-style: disc;
+}
+
+ol {
+ list-style: decimal;
+}
+
li > ul,
li > ol {
margin: 0;
diff --git a/docs/components/table/index.md b/docs/components/table/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..07cf4c4905d61fc443981556e7cda931463e3329
--- /dev/null
+++ b/docs/components/table/index.md
@@ -0,0 +1,210 @@
+# Table 表格
+
+展示行列数据。
+
+### 何时使用
+
+1. 当有大量结构化的数据需要展现时;
+2. 当需要对数据进行排序、过滤、自定义操作等复杂行为时。
+
+### 基本用法
+
+:::demo 简单表格,`d-table`组件上的`data`属性传入要展示的数据,`d-table-column`组件上通过`field`传入对应列内容的字段名,`header`传入对应列的标题。
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+:::
+
+### 斑马纹表格
+
+:::demo 通过`d-table`组件上的`striped`属性,可设置带斑马纹的表格,更容易区分不同行的数据。
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+:::
+
+### 空数据模板
+
+:::demo 当传入的数据为空时,默认展示空数据模板。
+
+```vue
+
+
+ 更新数据
+
+
+
+
+
+
+
+
+
+
+```
+
+:::
+
+
+### d-table Props
+
+| 参数 | 类型 | 默认值 | 说明 |
+| ------- | --------- | ------- | ------------------ |
+| data | `Array` | `[]` | 显示的数据 |
+| striped | `Boolean` | `false` | 是否显示斑马纹间隔 |
+
+### d-column Props
+
+| 参数 | 类型 | 默认值 | 说明 |
+| ------ | ------------------ | ------ | ---------------------- |
+| header | `String` | `-` | 对应列的标题 |
+| field | `String` | `-` | 对应列内容的字段名 |
+| width | `String \| Number` | `-` | 对应列的宽度,单位`px` |
+| min-width | `String \| Number` | `-` | 对应列的最小宽度,单位`px` |