feat: 新增 Table 表格组件

This commit is contained in:
xuqingkai 2023-10-25 21:49:47 +08:00
parent 89d27f4d0f
commit e77872fb56
14 changed files with 919 additions and 0 deletions

3
docs/component/table.md Normal file
View File

@ -0,0 +1,3 @@
<frame/>
# Table 表格

View File

@ -621,6 +621,16 @@
"navigationBarTitleText": "Navbar 导航栏",
"navigationStyle": "custom"
}
},
{
"path": "pages/table/Index",
"name": "table",
"style": {
"mp-alipay": {
"allowsBounceVertical": "NO"
},
"navigationBarTitleText": "Table 表格"
}
}
],
// "tabBar": {

279
src/pages/table/Index.vue Normal file
View File

@ -0,0 +1,279 @@
<template>
<page-wraper>
<wd-table :data="dataList" :stripe="true" @sort-method="doSort" @row-click="handleRowClick">
<wd-table-col prop="name" label="姓名" fixed="true" width="320rpx" :sortable="true"></wd-table-col>
<wd-table-col prop="grade" label="分数" width="220rpx" :sortable="true">
<!-- <template #default="scope: any">
<view class="custom-class">
<text>{{ scope.row.grade }}</text>
<text>同比{{ scope.row.compare }}</text>
</view>
</template> -->
</wd-table-col>
<wd-table-col prop="hobby" label="一言以蔽之" :sortable="true"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
<wd-table-col prop="gender" label="性别"></wd-table-col>
<wd-table-col prop="graduation" label="学成时间"></wd-table-col>
</wd-table>
</page-wraper>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const dataList = ref<Record<string, any>[]>([
{
name: '张飞',
school: '武汉市阳逻杀猪学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 56,
compare: '10%',
hobby: '燕人张飞在此!'
},
{
name: '关羽',
school: '武汉市阳逻绿豆学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 66,
compare: '11%',
hobby: '颜良文丑,以吾观之,如土鸡瓦犬耳。'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 45,
compare: '1%',
hobby: '我得空明,如鱼得水也'
},
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 69,
compare: '14%',
hobby: '子龙,子龙,世无双'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '21%',
hobby: '兴汉讨贼,克复中原'
},
{
name: '姜维',
school: '武汉市阳逻停水停电技术学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 87,
compare: '32%',
hobby: '我计不成,乃天命也!'
},
{
name: '张飞',
school: '武汉市阳逻杀猪学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 76,
compare: '44%',
hobby: '燕人张飞在此!'
},
{
name: '关羽',
school: '武汉市阳逻绿豆学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '14%',
hobby: '颜良文丑,以吾观之,如土鸡瓦犬耳。'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 71,
compare: '38%',
hobby: '我得空明,如鱼得水也'
},
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 96,
compare: '8%',
hobby: '子龙,子龙,世无双'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 86,
compare: '84%',
hobby: '兴汉讨贼,克复中原'
},
{
name: '姜维',
school: '武汉市阳逻停水停电技术学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 26,
compare: '10%',
hobby: '我计不成,乃天命也!'
},
{
name: '张飞',
school: '武汉市阳逻杀猪学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 45,
compare: '16%',
hobby: '燕人张飞在此!'
},
{
name: '关羽',
school: '武汉市阳逻绿豆学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '6%',
hobby: '颜良文丑,以吾观之,如土鸡瓦犬耳。'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 76,
compare: '38%',
hobby: '我得空明,如鱼得水也'
},
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 36,
compare: '48%',
hobby: '子龙,子龙,世无双'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 99,
compare: '1%',
hobby: '兴汉讨贼,克复中原'
},
{
name: '姜维',
school: '武汉市阳逻停水停电技术学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '21%',
hobby: '我计不成,乃天命也!'
},
{
name: '张飞',
school: '武汉市阳逻杀猪学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 77,
compare: '33%',
hobby: '燕人张飞在此!'
},
{
name: '关羽',
school: '武汉市阳逻绿豆学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 66,
compare: '48%',
hobby: '颜良文丑,以吾观之,如土鸡瓦犬耳。'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 68,
compare: '21%',
hobby: '我得空明,如鱼得水也'
},
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 91,
compare: '12%',
hobby: '子龙,子龙,世无双'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 99,
compare: '18%',
hobby: '兴汉讨贼,克复中原'
},
{
name: '姜维',
school: '武汉市阳逻停水停电技术学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '18%',
hobby: '我计不成,乃天命也!'
}
])
/**
* 排序
* @param e
*/
function doSort(e) {
dataList.value = dataList.value.reverse()
}
function handleRowClick({ rowIndex }) {
console.log(rowIndex)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,18 @@
# hd-table-column
Table-Column 表格列
## Props
<!-- @vuese:hd-table-column:props:start -->
|Name|Description|Type|Required|Default|
|---|---|---|---|---|
|prop|列对应字段|`String`|`false`|-|
|label|列标题|`String`|`false`|-|
|width|列宽度|`String`|`false`|-|
|fixed|列是否固定|`Boolean`|`false`|false|
|sortable|是否开启列排序|`Boolean`|`false`|false|
<!-- @vuese:hd-table-column:props:end -->

View File

@ -0,0 +1,36 @@
## 代码演示
### 基础用法
通过`prop`设置表格该列的对应字段,并通过`label`设置表格该列的标题。
``` html
<hd-table-column prop="name" label="姓名"></hd-table-column>
<hd-table-column prop="school" label="求学之所"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
```
### 列宽度
通过`width`设置表格该列的列宽度。
``` html
<hd-table-column prop="name" label="姓名" :width="90"></hd-table-column>
```
### 是否固定列
通过`fixed`设置表格该列是否固定,默认为`false`(不固定),且只支持钉钉固定在左侧。
``` html
<hd-table-column prop="name" label="姓名" :fixed="true"></hd-table-column>
<hd-table-column prop="school" label="求学之所"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
```
### 列排序
通过`sortable`设置表格该列是否参与排序,默认为`false`(不排序)。
``` html
<hd-table-column prop="name" label="姓名" :sortable="true"></hd-table-column>
<hd-table-column prop="school" label="求学之所" :sortable="true"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
```

View File

@ -0,0 +1,4 @@
<frame url="pages/table/Table"/>
# Table-Column 表格列
表格组件的列元素。

View File

@ -0,0 +1,13 @@
@import '../common/abstracts/variable';
@import '../common/abstracts/mixin';
@include b(table-col) {
flex: 0 0 auto;
@include e(cell){
}
@include e(row){
}
}

View File

@ -0,0 +1,126 @@
<template>
<view :class="`wd-table-col ${fixed ? 'is-fixed' : ''}`" :style="rootStyle">
<view
:class="`wd-table__cell ${stripe ? 'is-stripe' : ''}`"
v-for="(row, index) in column"
:key="index"
:style="rowStyle"
@click="handleRowClick(index)"
>
<slot v-if="$slots.default" :row="scope(index)"></slot>
<view class="cell" v-else>{{ row }}</view>
</view>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-table-col',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { Ref, computed, inject, onMounted, ref } from 'vue'
import { addUnit, isDef, objToStyle } from '../common/util'
interface Props {
//
prop: string
//
label: string
//
width?: string
// true, left, right
fixed?: string | boolean
//
sortable?: boolean
//
lightHigh?: boolean
}
const props = withDefaults(defineProps<Props>(), {
fixed: false, // true, left, right
sortable: false, //
lightHigh: false //
})
// eslint-disable-next-line @typescript-eslint/ban-types
const setColumns: Function = inject('setColumns') as Function // .
// eslint-disable-next-line @typescript-eslint/ban-types
const setRowClick: Function = inject('setRowClick') as Function
const $props = inject<Ref>('$props') || ref({}) // table
//
const stripe = computed(() => {
return $props.value.stripe || false
})
/**
* 根节点样式
*/
const rootStyle = computed(() => {
const style: Record<string, string | number> = {}
if (isDef(props.width)) {
// widthpxpx
style['width'] = addUnit(props.width)
}
return objToStyle(style)
})
const rowStyle = computed(() => {
const rowHeight: string | number = $props.value.rowHeight || '80rpx' //
const style: Record<string, string | number> = {}
if (isDef(props.width)) {
// widthpxpx
style['width'] = addUnit(props.width)
}
if (isDef(rowHeight)) {
// widthpxpx
style['height'] = addUnit(rowHeight)
}
return objToStyle(style)
})
//
const scope = computed(() => {
return (index: number) => {
return $props.value.data[index] || {}
}
})
//
const column = computed(() => {
let column: any[] = $props.value.data.map((item) => {
return item[props.prop]
})
return column
})
onMounted(() => {
setColumns(
{
prop: props.prop,
label: props.label,
width: props.width,
fixed: props.fixed,
sortable: props.sortable,
sortDirection: '',
lightHigh: props.lightHigh
} // sortDirection
)
})
function handleRowClick(index: number) {
setRowClick(index)
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,40 @@
# hd-table
Table 表格
## Props
<!-- @vuese:hd-table:props:start -->
|Name|Description|Type|Required|Default|
|---|---|---|---|---|
|dataSource|table数据源|`Array`|`false`|[]|
|stripe|是否为斑马纹|`Boolean`|`false`|false|
|height|表格高度|`String`|`false`|默认值:'80vh'|
|rowHeight|行高|`String`/`Number`|`false`|默认值:'80rpx'|
<!-- @vuese:hd-table:props:end -->
## Events
<!-- @vuese:hd-table:events:start -->
|Event Name|Description|Parameters|
|---|---|---|
|sort-method|点击排序时触发|value:Object 当前列表头|
|row-click|点击行时触发|{rowIndex:number} 点击行的下标|
<!-- @vuese:hd-table:events:end -->
## Slots
<!-- @vuese:hd-table:slots:start -->
|Name|Description|Default Slot Content|
|---|---|---|
|default|-|-|
<!-- @vuese:hd-table:slots:end -->

View File

@ -0,0 +1,187 @@
## 代码演示
### 基础用法
通过`dataSource`设置表格数据。
```html
<hd-table :dataSource="dataList">
<hd-table-column prop="name" label="姓名"></hd-table-column>
<hd-table-column prop="school" label="求学之所"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
</hd-table>
```
```ts
const dataList = ref<Record<string,string>> ([
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业'
}
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业'
}
])
```
### 斑马纹
通过`stripe`设置表格是否展示斑马纹,默认`false`(不展示)。
```html
<hd-table :dataSource="dataList" :stripe="true">
<hd-table-column prop="name" label="姓名"></hd-table-column>
<hd-table-column prop="school" label="求学之所"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
</hd-table>
```
### 表格高度
通过`height`设置表格高度,默认为`80vh`
```html
<hd-table :dataSource="dataList" height="90vh">
<hd-table-column prop="name" label="姓名"></hd-table-column>
<hd-table-column prop="school" label="求学之所"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
</hd-table>
```
### 排序事件
当存在列参与排序时,点击会触发`sort-method`排序事件。
```html
<hd-table :dataSource="dataList" @sort-method="doSort">
<hd-table-column prop="name" label="姓名"></hd-table-column>
<hd-table-column prop="school" label="求学之所" :sortable="true"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
</hd-table>
```
```ts
function doSort(e) {
console.log('这里是排序事件')
}
```
### 自定义列模板
自定义列的显示内容,可组合其他组件使用。
通过 `Scoped slot` 可以获取到 `row` 的数据,用法参考 demo。
```html
<hd-table :dataSource="dataList" :stripe="true" @sort-method="doSort">
<hd-table-column prop="name" label="姓名" fixed="true" width="320rpx" :sortable="true"></hd-table-column>
<hd-table-column prop="grade" label="分数" width="220rpx" :sortable="true">
<template #default="scope: any">
<view class="custom-class">
<text>{{ scope.row.grade }}</text>
<text>同比{{ scope.row.compare }}</text>
</view>
</template>
</hd-table-column>
<hd-table-column prop="hobby" label="一言以蔽之" :sortable="true"></hd-table-column>
<hd-table-column prop="school" label="求学之所"></hd-table-column>
<hd-table-column prop="major" label="专业"></hd-table-column>
<hd-table-column prop="gender" label="性别"></hd-table-column>
<hd-table-column prop="graduation" label="学成时间"></hd-table-column>
</hd-table>
```
```ts
import { ref } from 'vue'
const dataList = ref<Record<string, any>[]>([
{
name: '张飞',
school: '武汉市阳逻杀猪学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 56,
compare: '10%',
hobby: '燕人张飞在此!'
},
{
name: '关羽',
school: '武汉市阳逻绿豆学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 66,
compare: '11%',
hobby: '颜良文丑,以吾观之,如土鸡瓦犬耳。'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 45,
compare: '1%',
hobby: '我得空明,如鱼得水也'
},
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 69,
compare: '14%',
hobby: '子龙,子龙,世无双'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '21%',
hobby: '兴汉讨贼,克复中原'
},
{
name: '姜维',
school: '武汉市阳逻停水停电技术学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 87,
compare: '32%',
hobby: '我计不成,乃天命也!'
}
])
/**
* 排序
* @param e
*/
function doSort(e) {
dataList.value = dataList.value.reverse()
}
```
```css
.custom-class {
height: 80rpx;
width: 220rpx;
display: flex;
flex-direction: column;
align-items: center;
}
```

View File

@ -0,0 +1,4 @@
<frame url="pages/table/Table"/>
# Table 表格
表格组件,支持表格内容上下左右滚动。

View File

@ -0,0 +1,36 @@
@import '../common/abstracts/variable';
@import '../common/abstracts/mixin';
@include b(table) {
position: relative;
width: 100%;
overflow: auto;
background: #ffffff;
@include e(header){
position: sticky;
top: 0;
width: 100%;
height: 80rpx;
display: flex;
pointer-events: none;
overflow-x: auto;
white-space: nowrap;
}
@include e(body){
display: flex;
overflow: auto;
white-space: nowrap !important;
width: 100%;
}
@include e(cell){
display: inline-block;
flex: 0 0;
width: 220rpx;
}
@include e(row){
}
}

View File

@ -0,0 +1,161 @@
<template>
<view class="wd-table" :style="rootStyle">
<scroll-view class="wd-table__header" :scroll-left="left" enable-flex="true" scroll-x>
<view :class="`wd-table__cell ${column.fixed ? 'is-fixed' : ''}`" v-for="(column, index) in columns" :key="index">
<text>{{ column.label }}</text>
</view>
</scroll-view>
<scroll-view class="wd-table__body" scroll-x="true" scroll-y="true" enable-flex="true" @scroll="doScroll">
<slot></slot>
</scroll-view>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-table',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance, nextTick, onMounted, provide, ref, watch } from 'vue'
import { addUnit, debounce, deepClone, getRect, isDef, objToStyle } from '../common/util'
interface Props {
//
data: Array<any>
//
bordered?: boolean
// table
stripe?: boolean
// Table
height?: string
//
rowHeight?: number | string
}
const props = withDefaults(defineProps<Props>(), {
//
data: () => [],
// table
stripe: false,
bordered: true,
// table
height: '80vh',
//
rowHeight: '80rpx'
})
// privide$dataSource
watch(
props,
() => {
$props.value = props
},
{ deep: true }
)
const left = ref<number>(0) // scroll-view
const scrollWidth = ref<number | string>('auto') // scroll-viewsticky
const columns = ref<Array<Record<string, any>>>([]) //
const $props = ref<Props>(props)
const emit = defineEmits(['click', 'sort-method', 'row-click'])
const scroll = debounce(doScroll, 100, false) //
/**
* 根节点样式
*/
const rootStyle = computed(() => {
const style: Record<string, string | number> = {}
if (isDef(props.height)) {
style['height'] = addUnit(props.height)
}
return objToStyle(style)
})
/**
* 根节点样式
*/
const headerStyle = computed(() => {
return (width: string | number) => {
const style: Record<string, string | number> = {}
if (isDef(width)) {
style['width'] = addUnit(width)
}
return objToStyle(style)
}
})
const { proxy } = getCurrentInstance() as any
onMounted(() => {
nextTick(() => {
getRect('.header-container', false, proxy).then((data: any) => {
if (data && data.width) {
scrollWidth.value = addUnit(data.width)
}
})
})
})
/**
* 设置列
* @param column
*/
function setColumns(column: Record<string, any>) {
columns.value = deepClone([...columns.value, column])
}
/**
* 排序事件
*/
function doSort(column, index) {
if (column.sortDirection === 'asc') {
columns.value[index].sortDirection = 'desc'
} else if (columns.value[index].sortDirection === 'desc') {
columns.value[index].sortDirection = ''
} else {
columns.value[index].sortDirection = 'asc'
}
columns.value.forEach((col, i) => {
if (index != i) {
col.sortDirection = ''
}
})
emit('sort-method', columns.value[index])
}
/**
* 滚动事件
*/
function doScroll(event) {
left.value = event.detail.scrollLeft
if (scrollWidth.value !== event.detail.scrollWidth) {
scrollWidth.value = addUnit(event.detail.scrollWidth)
}
}
function setRowClick(index: number) {
emit('row-click', { rowIndex: index })
}
provide('$props', $props) // provide
provide('setRowClick', setRowClick)
provide('setColumns', setColumns)
</script>
<style lang="scss" scoped>
@import './index.scss';
scroll-view::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
border-radius: 0;
background-color: transparent;
color: transparent;
}
</style>

View File

@ -85,6 +85,8 @@ declare module '@vue/runtime-core' {
WdTabbarItem: typeof import('./components/wd-tabbar-item/wd-tabbar-item.vue')['default']
WdNavbar: typeof import('./components/wd-navbar/wd-navbar.vue')['default']
WdNavbarCapsule: typeof import('./components/wd-navbar-capsule/wd-navbar-capsule.vue')['default']
WdTable: typeof import('./components/wd-table/wd-table.vue')['default']
WdTableCol: typeof import('./components/wd-table-col/wd-table-col.vue')['default']
}
}