diff --git a/packages/devui-vue/devui/skeleton/src/skeleton-types.ts b/packages/devui-vue/devui/skeleton/src/skeleton-types.ts index e5e0bae4944efe9a0708697d68f8541a20f17163..84b874ab89b63c7681e43f6a27141760caaa30f1 100644 --- a/packages/devui-vue/devui/skeleton/src/skeleton-types.ts +++ b/packages/devui-vue/devui/skeleton/src/skeleton-types.ts @@ -36,7 +36,7 @@ export const skeletonProps = { default: '40px' }, avatarShape: { - value: String as PropType<'round' | 'square'>, + type: String as PropType<'round' | 'square'>, default: 'round' }, titleWidth: { @@ -46,6 +46,18 @@ export const skeletonProps = { rowWidth: { type: [Number, String, Array] as PropType>, default: ['100%'] + }, + shape: { + type: String as PropType<'avatar' | 'image' | 'title' | 'paragraph' | 'button'>, + }, + width: { + type: [String, Number] as PropType + }, + height: { + type: [String, Number] as PropType + }, + style:{ + type: String } } as const diff --git a/packages/devui-vue/devui/skeleton/src/skeleton.scss b/packages/devui-vue/devui/skeleton/src/skeleton.scss index 39c952314389e0878e7d1f06226122f5a9068c7e..52ab86a08fa112f9ff5bcdbbe62ebd840ce2f424 100644 --- a/packages/devui-vue/devui/skeleton/src/skeleton.scss +++ b/packages/devui-vue/devui/skeleton/src/skeleton.scss @@ -1,10 +1,10 @@ -@import '../../styles-var/devui-var.scss'; +@import "../../styles-var/devui-var.scss"; .devui-skeleton { display: flex; justify-content: space-between; - .devui-skeleton__avatar { + &__avatar { display: flex; flex: 1; justify-content: center; @@ -17,41 +17,88 @@ } } - .devui-skeleton__item__group { + &__group { flex: 11; + } - .devui-skeleton__item, - .devui-skeleton__title { - width: 100%; - height: 16px; - background-color: #f2f2f2; - } + &__item, + &__title { + width: 100%; + height: 16px; + background-color: #f2f2f2; + } - .devui-skeleton__title { - margin-top: 24px; - } + &__title { + margin-top: 24px; + } - .devui-skeleton__paragraph { - margin-top: 12px; - } + &__paragraph { + margin-top: 12px; + } - .devui-skeleton__item:last-child { - width: 60%; - } + &__item:last-child { + width: 60%; + } + + &__avatar > .avatar, + &__group > div > &__item { + margin-top: 12px; } } -.devui-skeleton-animated > .devui-skeleton__item__group > .devui-skeleton__title, -.devui-skeleton-animated > .devui-skeleton__avatar > .avatar, -.devui-skeleton-animated > .devui-skeleton__item__group > div > .devui-skeleton__item { - @keyframes skeletonLoading { - to { - background-position-x: -20%; - } +.devui-skeleton__shape__avatar, +.devui-skeleton__shape__image, +.devui-skeleton__shape__title, +.devui-skeleton__shape__button { + background-color: #f2f2f2; +} +.devui-skeleton__shape__avatar { + width: 40px; + height: 40px; + background-color: #f2f2f2; +} +.devui-skeleton__shape__image { + width: 200px; + height: 150px; + border-radius: 4px; +} +.devui-skeleton__shape__title { + width: 40%; + height: 24px; +} +.devui-skeleton__shape__paragraph { + &__item { + background-color: #f2f2f2; + width: 100%; + height: 16px; + margin-bottom: 10px; } - background: - linear-gradient( + &__item:last-child { + width: 60%; + } +} +.devui-skeleton__shape__button { + width: 115px; + height: 32px; +} + +@keyframes skeletonLoading { + to { + background-position-x: -20%; + } +} + +.devui-skeleton__animated > .devui-skeleton__group > .devui-skeleton__title, +.devui-skeleton__animated > .devui-skeleton__group > div > .devui-skeleton__item, +.devui-skeleton__animated > .devui-skeleton__avatar > .avatar, +.devui-skeleton__animated .devui-skeleton__shape__avatar, +.devui-skeleton__animated.devui-skeleton__shape__avatar, +.devui-skeleton__animated.devui-skeleton__shape__image, +.devui-skeleton__animated.devui-skeleton__shape__title, +.devui-skeleton__animated.devui-skeleton__shape__paragraph > .devui-skeleton__shape__paragraph__item, +.devui-skeleton__animated.devui-skeleton__shape__button { + background: linear-gradient( 100deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.5) 50%, @@ -62,12 +109,3 @@ background-position-x: 180%; animation: 2s skeletonLoading ease-in-out infinite; } - -.devui-skeleton__avatar > .avatar, -.devui-skeleton__item__group > div > .devui-skeleton__item { - margin-top: 12px; -} - -.devui-skeleton-animated > .devui-skeleton__avatar > .avatar { - animation-delay: 0.1s; -} diff --git a/packages/devui-vue/devui/skeleton/src/skeleton.tsx b/packages/devui-vue/devui/skeleton/src/skeleton.tsx index 0aad34479eb64e9b13c315fe5f4fbffc53355115..64c810ff08670d43a1e3e928382454abf8fcf093 100644 --- a/packages/devui-vue/devui/skeleton/src/skeleton.tsx +++ b/packages/devui-vue/devui/skeleton/src/skeleton.tsx @@ -10,7 +10,7 @@ export default defineComponent({ const { slots } = ctx; function renderAnimate(isAnimated) { - return isAnimated ? 'devui-skeleton-animated' : '' + return isAnimated ? 'devui-skeleton__animated' : '' } function renderBorderRadius(isRound) { return isRound ? 'border-radius: 1em;' : '' @@ -56,7 +56,53 @@ export default defineComponent({ }) } } + + function renderShapeParagraph(rowNum, rowWidth, round) { + const arr = [] + + function pushIntoArray(type) { + for (let index = 0; index < rowNum; index++) { + arr.push({ width: type }) + } + } + (function handleRowWidth() { + if (rowWidth instanceof Array) { + for (let index = 0; index < rowNum; index++) { + if (rowWidth[index]) { + switch (typeof rowWidth[index]) { + case 'string': + arr.push({ width: rowWidth[index] }) + break + case 'number': + arr.push({ width: `${rowWidth[index]}px` }) + } + } else { + arr.push({ width: 1 }) + } + } + } else { + switch (typeof rowWidth) { + case 'string': + pushIntoArray(rowWidth) + break + case 'number': + pushIntoArray(`${rowWidth}px`) + break + } + } + })() + + return
{ + arr.map(item => { + return
+ }) + }
+ } + function renderAvatarStyle(avatarSize, avatarShape) { + function renderAvatarShape(avatarShape) { + return avatarShape === 'square' ? '' : 'border-radius:50%;' + } function renderAvatarSize(avatarSize) { switch (typeof avatarSize) { case 'string': @@ -65,9 +111,6 @@ export default defineComponent({ return `width:${avatarSize}px;height:${avatarSize}px;` } } - function renderAvatarShape(avatarShape) { - return avatarShape === 'square' ? '' : 'border-radius:50%;' - } return (renderAvatarSize(avatarSize) + renderAvatarShape(avatarShape)) } @@ -86,26 +129,42 @@ export default defineComponent({ return (renderTitleWidth(titleWidth) + renderBorderRadius(isRound) + renderTitleVisibility(isVisible)) } - function renderSkeleton(isLoading) { - if (isLoading) { - return <> -
-
-
-
-
- {renderParagraph(props.paragraph, props.row, props.rowWidth, props.round)} -
- - } - - return <>{slots.default?.()} + function renderDefaultSkeleton() { + return <> +
+
+
+
+
+ {renderParagraph(props.paragraph, props.row, props.rowWidth, props.round)} +
+ } return () => { - return
- {renderSkeleton(props.loading)} -
+ if (props.loading) { + if (props.shape) { + switch (props.shape) { + case 'avatar': + return <> +
+ + case 'paragraph': + return <> + {renderShapeParagraph(props.row, props.rowWidth, props.round)} + + default: + return <> +
+ + } + } else { + return
+ {renderDefaultSkeleton()} +
+ } + } + return <>{slots.default?.()} } } }) diff --git a/packages/devui-vue/devui/table/src/body/body.scss b/packages/devui-vue/devui/table/src/body/body.scss deleted file mode 100644 index ea2657279dfcdaccd4f275b9704afe884975afe3..0000000000000000000000000000000000000000 --- a/packages/devui-vue/devui/table/src/body/body.scss +++ /dev/null @@ -1,20 +0,0 @@ -@import '../../../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/packages/devui-vue/devui/table/src/body/body.tsx b/packages/devui-vue/devui/table/src/body/body.tsx deleted file mode 100644 index 9b02b8679897d1deb3d3347c868d72b6e5ad4934..0000000000000000000000000000000000000000 --- a/packages/devui-vue/devui/table/src/body/body.tsx +++ /dev/null @@ -1,33 +0,0 @@ -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/packages/devui-vue/devui/table/src/body/body.type.ts b/packages/devui-vue/devui/table/src/body/body.type.ts index c1febde00fb051c2b818c58fde03ba5004b0a021..bfe63e3c0614f948dcf0200c21c2d242fae8804a 100644 --- a/packages/devui-vue/devui/table/src/body/body.type.ts +++ b/packages/devui-vue/devui/table/src/body/body.type.ts @@ -1,8 +1,9 @@ -import { ExtractPropTypes } from 'vue'; +import { ExtractPropTypes, PropType } from 'vue'; +import { TableStore } from '../store'; export const TableBodyProps = { store: { - type: Object, + type: Object as PropType, default: {}, }, }; diff --git a/packages/devui-vue/devui/table/src/body/use-body.ts b/packages/devui-vue/devui/table/src/body/use-body.ts index 1657a06b50646cc594957432254062c6035cd1a2..34f68c25c3bac87e888411916f5010cd53ea96ce 100644 --- a/packages/devui-vue/devui/table/src/body/use-body.ts +++ b/packages/devui-vue/devui/table/src/body/use-body.ts @@ -1,13 +1,19 @@ -import { computed } from 'vue'; +import { computed, ComputedRef } from 'vue'; +import { Column } from '../column/column.type'; import { TableBodyPropsTypes } from './body.type' -export function useTableBody(props: TableBodyPropsTypes): any { - const storeData = props.store.states; +interface Data { + rowColumns: ComputedRef<(Record & { columns: Column[]; })[]> +} + +export const useTableBody = (props: TableBodyPropsTypes): Data => { + const states = 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 states._data.value.map((row) => { + return { + ...row, + columns: states._columns.value + }; }); }); diff --git a/packages/devui-vue/devui/table/src/colgroup/colgroup.tsx b/packages/devui-vue/devui/table/src/colgroup/colgroup.tsx index 93e7197d8466045978538007fbb68379a8a8992b..336258629197b333a859835292ea7f6def718252 100644 --- a/packages/devui-vue/devui/table/src/colgroup/colgroup.tsx +++ b/packages/devui-vue/devui/table/src/colgroup/colgroup.tsx @@ -1,23 +1,20 @@ -import { inject, defineComponent } from 'vue'; -import { Table } from '../table.type'; +import { inject, defineComponent, Ref } from 'vue'; +import { Table, TABLE_TOKEN } 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 ( + const parent = inject(TABLE_TOKEN); + const columns = parent.store.states._columns; + return () => ( - {columns.map((column, index) => { - return ; - })} + {parent.props.tableLayout === 'fixed' ? ( + columns.value.map((column, index) => { + return ; + }) + ) : null} ); - }, + } }); \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/column/column.tsx b/packages/devui-vue/devui/table/src/column/column.tsx index 60ef5e04cf8e85ae459d43673fd1ce60219978ab..0dddad33b61a273fe940ac418950373ca6653b5e 100644 --- a/packages/devui-vue/devui/table/src/column/column.tsx +++ b/packages/devui-vue/devui/table/src/column/column.tsx @@ -1,31 +1,32 @@ -import { inject, defineComponent, onBeforeMount, onMounted } from 'vue'; +import { inject, defineComponent, onBeforeUnmount, onMounted, toRefs } from 'vue'; import { Column, TableColumnProps, TableColumnPropsTypes, } from './column.type' -import { Table } from '../table.type'; -import { useRender } from './use-column'; +import { TABLE_TOKEN } from '../table.type'; +import { createColumn } 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); + setup(props: TableColumnPropsTypes, ctx) { + /* + ctx.slots : { + customFilterTemplate: Slot + } + */ + const column = createColumn(toRefs(props), ctx.slots); - onBeforeMount(() => { - setColumnWidth(column); - setColumnRender(column); - }); + const parent = inject(TABLE_TOKEN); onMounted(() => { parent.store.insertColumn(column); }); + + onBeforeUnmount(() => { + parent.store.removeColumn(column); + }); }, render() { return null; diff --git a/packages/devui-vue/devui/table/src/column/column.type.ts b/packages/devui-vue/devui/table/src/column/column.type.ts index cdab04098202602701a11228536e6d9339695253..e95fa6732f82f7d37d1335241f6467ab5793b9db 100644 --- a/packages/devui-vue/devui/table/src/column/column.type.ts +++ b/packages/devui-vue/devui/table/src/column/column.type.ts @@ -18,21 +18,44 @@ export const TableColumnProps = { default: 80, }, formatter: { - type: Function as PropType< - (row: any, column: Column, cellValue, index: number) => VNode - >, + type: Function as PropType, }, + sortable: { + type: Boolean, + default: false + }, + compareFn: { + type: Function as PropType, + default: (field: string, a: any, b: any) => a[field] < b[field] + } }; export type TableColumnPropsTypes = ExtractPropTypes; -export interface Column { +export type Formatter = (row: T, cellValue: R, index: number) => VNode[]; + +export type CompareFn = (field: string, a: T, b: T) => boolean; + +export type FilterList = (string | number)[]; + +export interface CustomFilterProps { + value: FilterList; + onChange: (value: FilterList) => void; +} + +export type CustomFilterSlot = (props: CustomFilterProps) => VNode[]; + +export interface Column = any> { field?: string width?: number minWidth?: number realWidth?: number header?: string + sortable?: boolean + filterable?: boolean renderHeader?: () => void - renderCell?: (data: any) => void - formatter?: (row: any, column: Column, cellValue, index: number) => VNode + renderCell?: (row: T, index: number) => void + formatter?: Formatter + compareFn?: CompareFn + customFilterTemplate?: CustomFilterSlot; } \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/column/use-column.ts b/packages/devui-vue/devui/table/src/column/use-column.ts index 7c5fc4ce546f06d6694a7e4f318cb9b0309565bb..5aca87b75e4f5c4403a7d8c71c3b3db9d5d8ec10 100644 --- a/packages/devui-vue/devui/table/src/column/use-column.ts +++ b/packages/devui-vue/devui/table/src/column/use-column.ts @@ -1,45 +1,56 @@ -import { ref } from 'vue'; +import { ref, watch, reactive, onBeforeMount, computed, ToRefs, Slots } 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); - }; - }; +export function createColumn = any>( + props: ToRefs, + templates: Slots +): Column { + const { + field, + header, + sortable, + width, + minWidth, + formatter, + compareFn + } = props; + const column: Column = reactive({}); + + watch( + [field, header, sortable], + ([field, header, sortable]) => { + column.field = field; + column.header = header; + column.sortable = sortable; + column.filterable = true; + }, + { immediate: true } + ); + + onBeforeMount(() => { + column.width = formatWidth(width.value); + column.minWidth = formatMinWidth(minWidth.value); + column.realWidth = column.width || column.minWidth; + column.renderHeader = defaultRenderHeader; + column.renderCell = defaultRenderCell; + column.formatter = formatter.value; + column.compareFn = compareFn.value; + column.customFilterTemplate = templates.customFilterTemplate; + }); - return { setColumnWidth, setColumnRender }; + return column; } -function defaultRenderHeader(column: Column) { - return column.header; +function defaultRenderHeader(this: Column) { + return this.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); +function defaultRenderCell>(this: Column, rowData: T, index: number) { + const value = rowData[this.field]; + if (this.formatter) { + return this.formatter(rowData, value, index); } - return value?.toString?.() || ''; + return value?.toString?.() ?? ''; } \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/header/filter/filter.scss b/packages/devui-vue/devui/table/src/header/filter/filter.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/devui-vue/devui/table/src/header/filter/filter.tsx b/packages/devui-vue/devui/table/src/header/filter/filter.tsx new file mode 100644 index 0000000000000000000000000000000000000000..05ef087de90d4dea87adf5984f76187bd2161593 --- /dev/null +++ b/packages/devui-vue/devui/table/src/header/filter/filter.tsx @@ -0,0 +1,73 @@ +import { defineComponent, PropType, ref, Slot } from "vue"; +import { CustomFilterSlot, FilterList } from "../../column/column.type"; + + +export const Filter = defineComponent({ + props: { + modelValue: { + type: Array as PropType, + default: [] + }, + 'onUpdate:modelValue': { + type: Function as PropType<(v: FilterList) => void> + }, + customTemplate: { + type: Function as PropType + }, + }, + emits: ['update:modelValue'], + setup(props) { + + const showDropdown = ref(true); + + const handleFilter = () => { + showDropdown.value = true; + } + + return () => ( + <> + + + + + + + + + + + + + {props.customTemplate?.({ value: props.modelValue, onChange: props["onUpdate:modelValue"] }) ?? ( +
+ )} +
+ + ) + } +}); + + +// TODO: 缺少 dropdown 组件 +const Dropdown = defineComponent({ + props: { + visible: { type: Boolean }, + 'onUpdate:visible': { type: Function }, + }, + setup(props) { + return () => ( +
test
+ ); + } +}); \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/header/filter/index.ts b/packages/devui-vue/devui/table/src/header/filter/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..998668a1df5fa7e132f27c1ca1efae4effb292f5 --- /dev/null +++ b/packages/devui-vue/devui/table/src/header/filter/index.ts @@ -0,0 +1 @@ +export * from './filter'; \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/header/header.tsx b/packages/devui-vue/devui/table/src/header/header.tsx index a05a80b7ae6f8189134368ea1d71f4a2cb582f9f..9ddd3a60329f69a4c04cb7128052031150c59899 100644 --- a/packages/devui-vue/devui/table/src/header/header.tsx +++ b/packages/devui-vue/devui/table/src/header/header.tsx @@ -1,28 +1,71 @@ -import { defineComponent, toRefs } from 'vue'; +import { defineComponent, inject, computed, ref, shallowRef, PropType, watch } from 'vue'; import { TableHeaderProps, TableHeaderPropsTypes } from './header.type' +import { SortDirection, TABLE_TOKEN } from '../table.type'; +import { Column, FilterList } from '../column/column.type'; + +import { Checkbox } from '../../../checkbox'; +import { Sort } from './sort'; +import { Filter } from './filter'; + import './header.scss'; + export default defineComponent({ name: 'DTableHeader', props: TableHeaderProps, setup(props: TableHeaderPropsTypes) { - const { store } = toRefs(props) - const columns = store.value.states._columns.value; + const table = inject(TABLE_TOKEN); + const { _checkAll: checkAll, _halfChecked: halfChecked } = table.store.states; - return { - columns, + const checkbox = computed(() => { + return table.props.checkable ? ( + + + + ) : null + }); + + return () => { + const columns = props.store.states._columns; + return ( + + + {checkbox.value} + {columns.value.map((column, index) => ( + + ))} + + + ) + } + } +}); + +const Th = defineComponent({ + props: { + column: { + type: Object as PropType, + required: true } }, - render() { - const { columns } = this - return ( - - - {columns.map((column, index) => { - return {column.renderHeader()}; - })} - - + setup(props: { column: Column; }) { + const directionRef = ref('DESC'); + const { sortData } = inject(TABLE_TOKEN).store; + watch([directionRef, () => props.column], ([direction, column]) => { + if (props.column.sortable) { + sortData(column.field, direction, column.compareFn); + } + }, { immediate: true }); + + // 过滤器 + const filteredRef = shallowRef([]); + + return () => ( + + {props.column.renderHeader()} + {props.column.sortable && } + {props.column.filterable && } + ) } }); \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/header/header.type.ts b/packages/devui-vue/devui/table/src/header/header.type.ts index 3369aa9d76063355dcfc33c108438a50279b2226..2beda703c89f4f30d68a9c075cfd594aae009db5 100644 --- a/packages/devui-vue/devui/table/src/header/header.type.ts +++ b/packages/devui-vue/devui/table/src/header/header.type.ts @@ -1,8 +1,9 @@ -import { ExtractPropTypes } from 'vue'; +import { ExtractPropTypes, PropType } from 'vue'; +import { TableStore } from '../store'; export const TableHeaderProps = { store: { - type: Object, + type: Object as PropType, default: {}, }, }; diff --git a/packages/devui-vue/devui/table/src/header/sort/index.ts b/packages/devui-vue/devui/table/src/header/sort/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..72099fcbdd2abb2025a317a5ed5e26e0995e4983 --- /dev/null +++ b/packages/devui-vue/devui/table/src/header/sort/index.ts @@ -0,0 +1 @@ +export { Sort } from './sort'; \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/header/sort/sort.scss b/packages/devui-vue/devui/table/src/header/sort/sort.scss new file mode 100644 index 0000000000000000000000000000000000000000..203b367d286d39a1daa534ae0d600ce1bc8d7708 --- /dev/null +++ b/packages/devui-vue/devui/table/src/header/sort/sort.scss @@ -0,0 +1,56 @@ +@import '../../../../styles-var/devui-var.scss'; + +.sort-clickable { + cursor: pointer; + position: absolute; + top: -16px; + left: 50%; + transform: translateX(-50%); + padding: 8px; + line-height: 20px; +} + +.sort-icon-default { + > svg g use { + fill: $devui-dividing-line; + } + + > svg g polygon { + fill: $devui-icon-bg; + } + + &:hover { + > svg g use { + fill: $devui-icon-fill-active-hover; + } + } +} + +.sort-icon-asc, +.sort-icon-desc { + > svg g use { + fill: $devui-icon-fill-active; + } + + > svg g polygon { + fill: $devui-icon-bg; + } + + &:hover { + > svg g use { + fill: $devui-icon-fill-active-hover; + } + } +} + +.sort-icon-asc { + > svg g polygon:last-of-type { + opacity: 0.3; + } +} + +.sort-icon-desc { + > svg g polygon:first-of-type { + opacity: 0.3; + } +} diff --git a/packages/devui-vue/devui/table/src/header/sort/sort.tsx b/packages/devui-vue/devui/table/src/header/sort/sort.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5ceadb53cd4a16356032d97addc0bf1b0fb5ae73 --- /dev/null +++ b/packages/devui-vue/devui/table/src/header/sort/sort.tsx @@ -0,0 +1,69 @@ +import { defineComponent, PropType } from 'vue'; +import { SortDirection } from '../../table.type'; +import './sort.scss'; + + +export const Sort = defineComponent({ + props: { + modelValue: { + type: String as PropType, + default: '' + }, + 'onUpdate:modelValue': { + type: Function as PropType<(v: SortDirection) => void> + } + }, + emits: ['update:modelValue'], + setup(props, ctx) { + const changeDirection = () => { + let direction = ''; + if (props.modelValue === 'ASC') { + direction = 'DESC'; + } else if (props.modelValue === 'DESC') { + direction = ''; + } else { + direction = 'ASC'; + } + ctx.emit('update:modelValue', direction); + } + + return () => ( + + + + + + + + + + + + + + + + + + + + ); + } +}) \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/store/index.ts b/packages/devui-vue/devui/table/src/store/index.ts index b7138064c7b394b6ba8e68e79d6a4c5c1cd194c2..86de8a1473bb5fe29de0b07d3920f270af9426cc 100644 --- a/packages/devui-vue/devui/table/src/store/index.ts +++ b/packages/devui-vue/devui/table/src/store/index.ts @@ -1,30 +1,119 @@ -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); - }); +import { watch, Ref, ref, computed } from 'vue'; +import { Column, CompareFn } from '../column/column.type'; +import { SortDirection } from '../table.type'; +export interface TableStore> { + states: { + _data: Ref + _columns: Ref + _checkList: Ref + _checkAll: Ref + _halfChecked: Ref } + insertColumn(column: Column): void + removeColumn(column: Column): void + getCheckedRows(): T[] + sortData(field: string, direction: SortDirection, compareFn: CompareFn): void +} +export function createStore(dataSource: Ref): TableStore { + const _data: Ref = ref([]); + const _columns: Ref = ref([]); + const _checkList: Ref = ref([]); + const _checkAllRecord: Ref = ref(false); + const _checkAll: Ref = computed({ + get: () => _checkAllRecord.value, + set: (val: boolean) => { + _checkAllRecord.value = val; + // 只有在 set 的时候变更 _checkList 的数据 + for (let i = 0; i < _checkList.value.length; i++) { + _checkList.value[i] = val; + } + } + }); + const _halfChecked = ref(false); + + watch(dataSource, (value: T[]) => { + _data.value = [...value]; + _checkList.value = new Array(value.length).fill(false); + }, { deep: true, immediate: true }); + + // checkList 只有全为true的时候 + watch(_checkList, (list) => { + if (list.length === 0) { + return; + } + let allTrue = true; + let allFalse = true; + for (let i = 0; i < list.length; i++) { + allTrue &&= list[i]; + allFalse &&= !list[i]; + } + + _checkAllRecord.value = allTrue; + _halfChecked.value = !(allFalse || allTrue); + + }, { immediate: true, deep: true }); + + /** + * 插入当前列 + * @param {Column} column + */ const insertColumn = (column: Column) => { _columns.value.push(column); }; + /** + * 移除当前列 + * @param {Column} column + * @returns + */ + const removeColumn = (column: Column) => { + const i = _columns.value.findIndex((v) => v === column); + if (i === -1) { + return; + } + _columns.value.splice(i, 1); + } + + /** + * 获取当前已选数据 + * @returns {T[]} + */ + const getCheckedRows = (): T[] => { + return _data.value.filter((_, index) => _checkList.value[index]); + } + + /** + * 对数据进行排序 + * @param {string} field + * @param {SortDirection} direction + * @param {CompareFn} compareFn + */ + const sortData = ( + field: string, + direction: SortDirection, + compareFn: CompareFn = (field: string, a: T, b: T) => a[field] > b[field] + ) => { + if (direction === 'ASC') { + _data.value = _data.value.sort((a, b) => compareFn(field, a, b) ? 1 : -1); + } else if (direction === 'DESC') { + _data.value = _data.value.sort((a, b) => !compareFn(field, a, b) ? 1 : -1); + } else { + _data.value = [...dataSource.value]; + } + } + return { - insertColumn, states: { _data, _columns, + _checkList, + _checkAll, + _halfChecked }, + insertColumn, + removeColumn, + getCheckedRows, + sortData }; } \ No newline at end of file diff --git a/packages/devui-vue/devui/table/src/table.scss b/packages/devui-vue/devui/table/src/table.scss index 48d9e7cf3180dea7646f67175ebe7ba3268b574f..c985ed0b2c74c8a0fcc834e85c3a23bfc6abbc7a 100644 --- a/packages/devui-vue/devui/table/src/table.scss +++ b/packages/devui-vue/devui/table/src/table.scss @@ -6,10 +6,13 @@ width: 100%; border-spacing: 0; border: none; + margin: 0; + padding: 0; &-wrapper { width: 100%; overflow-x: auto; + padding-top: 8px; } &-striped { @@ -23,6 +26,22 @@ font-size: $devui-font-size; text-align: center; } + + &-view { + display: flex; + flex-direction: column; + position: relative; + height: 100%; + + & .scroll-view { + flex: 1; + overflow-y: scroll; + } + } +} + +.table-layout-auto { + table-layout: auto; } .table-layout-auto { diff --git a/packages/devui-vue/devui/table/src/table.tsx b/packages/devui-vue/devui/table/src/table.tsx index 83146778b7d6595233b33ac52a0b792113a2ab9f..598a1144696465a1f7f70500964b40c95bb02f08 100644 --- a/packages/devui-vue/devui/table/src/table.tsx +++ b/packages/devui-vue/devui/table/src/table.tsx @@ -1,36 +1,69 @@ -import { provide, defineComponent, getCurrentInstance } from 'vue'; -import { Table, TableProps, TablePropsTypes } from './table.type'; +import { provide, defineComponent, getCurrentInstance, computed, toRef, defineExpose } from 'vue'; +import { Table, TableProps, TablePropsTypes, TABLE_TOKEN } 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) { + setup(props: TablePropsTypes, ctx) { const table = getCurrentInstance() as Table; - const store = createStore(props); + const store = createStore(toRef(props, 'data')); table.store = store; - const { classes } = useTable(props); - provide('table', table); - - return { classes, store }; - }, - render() { - const { classes, data, store, $slots } = this; - return ( -
- {$slots.default()} - + provide(TABLE_TOKEN, table); + + const { classes, style } = useTable(props); + + const isEmpty = computed(() => props.data.length === 0); + + const fixHeaderCompo = computed(() => { + return ( +
+
+
+ + +
+
+
+ + + {!isEmpty.value && } +
+
+
+ ); + }); + + const normalHeaderCompo = computed(() => { + return ( + - - {!!data.length && } + + {!isEmpty.value && }
- {!data.length &&
No Data
} + ) + }); + + ctx.expose({ + getCheckedRows() { + return store.getCheckedRows(); + } + }); + + return () => ( +
+ + {ctx.slots.default()} + {props.fixHeader ? fixHeaderCompo.value : normalHeaderCompo.value} + {isEmpty.value &&
No Data
}
); - }, + } }); diff --git a/packages/devui-vue/devui/table/src/table.type.ts b/packages/devui-vue/devui/table/src/table.type.ts index b9132f45ec9a57251db60d09b475cab66b56bf1c..369441f95e8bcf231ba9b4cba75163371a9697b5 100644 --- a/packages/devui-vue/devui/table/src/table.type.ts +++ b/packages/devui-vue/devui/table/src/table.type.ts @@ -1,26 +1,94 @@ -import { PropType, ExtractPropTypes, ComponentInternalInstance } from 'vue'; +import { PropType, ExtractPropTypes, ComponentInternalInstance, InjectionKey } from 'vue'; +import { TableStore } from './store'; + +export type TableSize = 'sm' | 'md' | 'lg'; export const TableProps = { data: { - type: Array as PropType, + type: Array as PropType[]>, default: [], }, striped: { type: Boolean, default: false, }, - headerBg:{ + scrollable: { type: Boolean, default: false }, - tableLayout:{ + maxWidth: { + type: String, + }, + maxHeight: { + type: String, + }, + tableWidth: { + type: String, + }, + tableHeight: { + type: String, + }, + size: { + type: String as PropType, + validator(value: string): boolean { + return value === 'sm' || value === 'md' || value === 'lg'; + } + }, + rowHoveredHighlight: { + type: Boolean, + default: true + }, + fixHeader: { + type: Boolean, + default: false + }, + checkable: { + type: Boolean, + default: true + }, + tableLayout: { type: String as PropType<'fixed' | 'auto'>, - default: 'fixed' + default: 'auto', + validator(v: string) { + return v === 'fixed' || v === 'auto'; + } + }, + showLoading: { + type: Boolean, + default: false + }, + headerBg: { + type: Boolean, + default: false } }; export type TablePropsTypes = ExtractPropTypes; -export interface Table extends ComponentInternalInstance { - store: any +export interface Table> extends ComponentInternalInstance { + store: TableStore + props: TablePropsTypes } + +// export interface TableCheckStatusArg { +// pageAllChecked?: boolean; // 全选 +// pageHalfChecked?: boolean; // 半选 +// } + +// export interface RowToggleStatusEventArg { +// rowItem: any; // 行数据 +// open: boolean; // 子表格是否展开 +// } + +export interface TableMethods> { + getCheckedRows(): T[] + // setRowCheckStatus(arg: TableCheckStatusArg): void + // setTableCheckStatus(arg: RowToggleStatusEventArg): void + // setRowChildToggleStatus(): void + // setTableChildrenToggleStatus(): void + // cancelEditingStatus(): void +} + +export const TABLE_TOKEN: InjectionKey = Symbol(); + +export type SortDirection = 'ASC' | 'DESC' | ''; diff --git a/packages/devui-vue/devui/table/src/use-table.ts b/packages/devui-vue/devui/table/src/use-table.ts index e375cbbb4ca06fb68921d87c421407cb381883f2..06dc72a4043d85e04225ada1c7057c8380bbbcdb 100644 --- a/packages/devui-vue/devui/table/src/use-table.ts +++ b/packages/devui-vue/devui/table/src/use-table.ts @@ -1,13 +1,23 @@ -import { computed } from 'vue'; +import { computed, ComputedRef, CSSProperties } from 'vue'; import { TablePropsTypes } from './table.type'; -export function useTable(props: TablePropsTypes): any { +interface TableConfig { + classes: ComputedRef> + style: ComputedRef +} + +export function useTable(props: TablePropsTypes): TableConfig { const classes = computed(() => ({ 'devui-table': true, 'devui-table-striped': props.striped, 'header-bg': props.headerBg, 'table-layout-auto': props.tableLayout === 'auto' })); - - return { classes }; + const style: ComputedRef = computed(() => ({ + maxHeight: props.maxHeight, + maxWidth: props.maxWidth, + height: props.tableHeight, + width: props.tableWidth + })); + return {classes, style}; } \ No newline at end of file diff --git a/packages/devui-vue/docs/components/skeleton/index.md b/packages/devui-vue/docs/components/skeleton/index.md index 2aae393d3cfc9df4cbac4aa5723e37cfbd7525b2..d8cc8601518d142a3f576b2d3b0501c819fe2389 100644 --- a/packages/devui-vue/docs/components/skeleton/index.md +++ b/packages/devui-vue/docs/components/skeleton/index.md @@ -99,9 +99,20 @@ export default defineComponent({ ``` ::: - -### API -d-skeleton +### 拼接模式 +提供细粒度的骨架屏元素,给予开发者更灵活的定制能力。注意样式需通过 +:::demo +```vue + +``` +::: +### d-skeleton Props | 参数 | 类型 | 默认 | 说明 | | :-----: | :-------: | :-----: | :-------------------------------------------- | | loading | `boolean` | `true` | 是否显示骨架屏,传 `false` 时会展示子组件内容 | @@ -110,24 +121,25 @@ d-skeleton | title | `boolean` | `true` | 是否显示标题占位图 | | paragraph | `boolean` | `true` | 是否显示段落占位图 | | round | `boolean` | `false` | 是否将标题和段落显示为圆角风格 | - -d-skeleton-avatar-props +### d-skeleton__avatar Props | 参数 | 类型 | 默认 | 说明 | | :-----: | :-------: | :-----: | :-------------------------------------------- | | avatar-size | `number \| string` | `40px` | 头像占位图大小 | | avatar-shape | `string` | `round` | 头像占位图形状,可选值为`square` | - -d-skeleton-title-props +### d-skeleton__title Props | 参数 | 类型 | 默认 | 说明 | | :-----: | :-------: | :-----: | :-------------------------------------------- | | title-width | `number \| string` | `40%` | 设置标题占位图的宽度 | - - -d-skeleton-paragraph-props +### d-skeleton__paragraph Props | 参数 | 类型 | 默认 | 说明 | | :-----: | :-------: | :-----: | :-------------------------------------------- | | row | `number` | `0` | 段落占位图行数 | | row-width | `number \| string \| (number \| string)[]` | `["100%"]` | 段落占位图宽度,可传数组来设置每一行的宽度 | - - +### 拼接模式 +| 参数 | 类型 | 默认 | 说明 | +| :-----: | :-------: | :-----: | :-------------------------------------------- | +| shape | `string` | - | 可选值为`avatar`,`image`,`title`,`paragraph`,`button`。 | +| animate | `boolean` | `true` | 是否开启动画 | +| style | `string` | - | 将 css 样式以字符串的形式传入 | +> avatar 与 paragraph 的 API 与默认模式相同; \ No newline at end of file diff --git a/packages/devui-vue/docs/components/table/index.md b/packages/devui-vue/docs/components/table/index.md index fb305b27c2e127afe91f737d63027c7a10b902aa..4cc693fd7e3475efd8e6929a2cf328e203c43b18 100644 --- a/packages/devui-vue/docs/components/table/index.md +++ b/packages/devui-vue/docs/components/table/index.md @@ -14,7 +14,7 @@ ```vue