功能
- 支持横向分布和纵向分布
- 可自定义内容(通过插槽)
- 可修改基本样式(如宽度、高度、文本排列)
效果
写的很简陋哈,只实现了基本功能,还有许多地方需要完善。下面分别为常用的三种模式:
- 纵向表格(正常情况)
- 横向表格
- 可操作表格
使用
<template>
<s-table :dataSource="state.dataSource" layout="column">
<s-table-column prop="id" label="ID"/>
<s-table-column prop="name" label="名字"/>
<s-table-column prop="age" label="年龄"/>
<s-table-column prop="phone" label="电话号码" />
<s-table-column prop="hobby" label="爱好"/>
</s-table>
<br/>
<s-table :dataSource="state.dataSource" :header-style="{textAlign:'center',width:'100px'}" align="center">
<s-table-column prop="name" label="名字"/>
<s-table-column prop="age" label="年龄"/>
<s-table-column prop="phone" label="电话号码"/>
<s-table-column prop="hobby" label="爱好"/>
</s-table>
<s-table :dataSource="state.dataSource" layout="column">
<s-table-column prop="id" label="ID"/>
<s-table-column prop="name" label="名字"/>
<s-table-column prop="age" label="年龄"/>
<s-table-column prop="phone" label="电话号码">
<template #default="defaultProps">
<s-input
:value="defaultProps.value"
name="phone"
:rules="[
{ type: 'required', message: '电话号码不能为空' },
]"
@update="update($event, defaultProps.data.id)"
/>
</template>
</s-table-column>
<s-table-column prop="hobby" label="爱好"/>
</s-table>
</template>
<script lang="ts" setup>
import { reactive } from "vue"
import { STable, STableColumn, SInput } from "@/components"
const state = reactive({
dataSource: [
{
id: 1,
name: "小明",
age: 98,
hobby: "游泳",
phone: "123456"
},
{
id: 2,
name: "小红",
age: 55,
hobby: "跑步",
phone: "954623"
},
{
id: 3,
name: "小黑",
age: 32,
hobby: "打羽毛球",
phone: "654513"
}
]
})
const update = (params:BaseForm, id:number) => {
state.dataSource = state.dataSource.map((item) => {
if (item.id === id) {
return {
...item,
[params.name]: params.value
}
} else {
return item
}
})
}
</script>
Types
// Table 方向
type TableLayout = "horizon" | "column";
// Table 支持的头部样式
interface HeaderStyle {
width?: string; // 头部宽度
height?: string; // 头部高度
textAlign?: TableAlign; // 头部文本排列方式
}
// 真实运用到dom的头部样式
interface RealHeaderStyle extends HeaderStyle {
minWidth?: string;
minHeight?: string;
lineHeight?: string;
flex?: number;
display?: string;
alignItems?: string;
justifyContent?: string;
[index: string]: string | number | undefined;
}
// Table 文本方向
type TableAlign = "left" | "center" | "right";
// 每一条数据的格式(根据需要自定义)
type TableCell = {
id: number | string;
[index: string]: any;
};
封装
写的有点烂,当时想着只改个layout属性就能改变表格方向,于是在样式的使用上花了很多时间,也写了许多样式的逻辑判断,挺绕。如果有其他更优雅的方案欢迎评论区提出。
STable
<script lang="ts">
import { h, PropType, reactive, watchEffect } from "vue"
import { TableCell, HeaderStyle, TableAlign } from "@types"
export default {
name: "STable",
props: {
// 数据源
dataSource: {
type: Array as PropType<TableCell[]>,
default: () => []
},
// 头部样式
headerStyle: Object as PropType<HeaderStyle>,
// 表格展示方向
layout: {
type: String,
default: "horizon"
},
// 表格体文本排列方式
align: {
type: String as PropType<TableAlign>,
default: "left"
}
},
setup (props, context) {
if (!context.slots || !context.slots.default) return null
let slots = reactive<any[]>([])
// 监听父组件传来的属性变化,让子组件跟着变化
watchEffect(() => {
if (!context.slots || !context.slots.default) return null
slots = context.slots.default().map((slot) => ({
...slot,
props: {
...props,
...slot.props
}
}))
})
// 根据不同的方向应用不同的样式
if (props.layout === "column") {
return () =>
h(
"div",
{
className: "s-table table-layout-column"
},
slots
)
} else {
return () =>
h(
"div",
{
className: "s-table-wrap"
},
[h("div", { className: "s-table table-layout-horizon" }, slots)]
)
}
}
}
</script>
<style lang="less">
// 一开始用flex布局来处理正常情况下的表格样式(column)
// 后面发现用在flex在横向时不管用,改用了grid布局,所以写的不是很统一,根据需求改哈~
.s-table-wrap {
overflow: auto;
.s-table {
border-radius: @bigRadius;
&.table-layout-horizon {
display: grid;
.table-columns {
display: flex;
.table-cell {
flex: 1;
}
// 从第二行开始
&:nth-child(n + 2) {
.table-title,
.table-cell {
border-right: 1px dotted @borderColor;
border-bottom: 1px dotted @borderColor;
}
.table-title {
background-color: @default;
}
}
&:first-child {
.table-title {
color: @white;
}
.table-cell {
background-color: @default;
border-bottom: 1px dotted @borderColor;
&:nth-child(n + 3) {
border-left: 1px dotted @borderColor;
}
}
}
}
}
}
}
.s-table {
overflow-x: auto;
border-radius: @bigRadius;
&.table-layout-column {
display: flex;
.table-columns {
display: flex;
flex-direction: column;
// flex-basis: 100px;
&:nth-child(n + 3) {
.table-cell {
border-top: 1px dotted @borderColor;
}
}
.table-title {
white-space: nowrap;
border-bottom: 1px dotted @borderColor;
background-color: @default;
position: relative;
}
.table-cell {
flex: 1;
}
}
}
}
</style>
STableColumn
<template>
<ul class="table-columns" :key="prop" :style="{...culumnStyle}">
<li :class="['table-title',textAlign]" :key="prop" :style="{...titleStyle}">{{label}}</li>
<li :class="['table-cell',textAlign]" v-for="data in dataSource" :key="data.id">
<slot name="default" :data="data" :value="data[prop]">
{{data[prop]}}
</slot>
</li>
</ul>
</template>
<script lang='ts'>
import { PropType, ref } from "vue"
import { TableCell, HeaderStyle, RealHeaderStyle, TableLayout, TableAlign } from "@types"
export default {
props: {
prop: {
type: String,
default: ""
},
label: {
type: String,
require: true
},
width: {
type: String,
default: ""
}, // 列的宽度
align: {
type: String as PropType<TableAlign>,
default: "left"
}
//继承父组件表格传来的
dataSource: {
type: Array as PropType<TableCell[]>,
default: () => []
},
headerStyle: Object as PropType<HeaderStyle>,
layout: {
type: String as PropType<TableLayout>,
default: "horizon"
},
},
setup (props, context) {
const titleStyle = ref<RealHeaderStyle>({})
const culumnStyle = ref({})
const textAlign = ref(props.align)
const colWidth = ref(props.width)
// 头部的默认样式与自定义样式
const HS = props.headerStyle || undefined
if (props.layout === "horizon") {
// 【头左体右】表格
titleStyle.value.minWidth = "180px" // 默认样式
if (HS) {
titleStyle.value = {
...titleStyle.value,
...HS
}
if (HS.width) {
titleStyle.value.minWidth = HS.width
}
}
} else {
// 正常方向表格 【头上体下】表格
if (HS) {
titleStyle.value = { ...HS }
if (HS.width) {
titleStyle.value.minWidth = HS.width // 避免头部的宽度小于内容宽度
} else {
// 如果表格没有传头部的宽度,cell自动扩展
culumnStyle.value.flex = 1
}
if (HS.height) {
titleStyle.value.minHeight = HS.height
titleStyle.value.lineHeight = HS.height
}
} else {
// 如果表格没有传头部的样式,cell自动扩展
culumnStyle.value.flex = 1
}
// 判断列是否有传宽度
if (colWidth.value) {
titleStyle.value.minWidth = colWidth.value // 列的样式覆盖表格的样式
}
}
return {
titleStyle,
textAlign,
culumnStyle
}
}
}
</script>
<style lang="less">
.table-cell,.table-title{
padding: @itemSpace;
}
.table-columns{
.center{
text-align: center;
}
.left{
text-align: left;
}
.right{
text-align: right;
}
}
</style>