(table): add sub-table tree-table

This commit is contained in:
就眠儀式 2022-05-28 23:50:41 +08:00
parent 1f5313fd94
commit 3e68cdf169
6 changed files with 345 additions and 111 deletions

View File

@ -0,0 +1,183 @@
<script lang="ts">
export default {
name: "TableRow",
};
</script>
<script lang="ts" setup>
import { computed, ref, useSlots, WritableComputedRef } from "vue";
import { Recordable } from "../../types";
export interface LayTableProps {
selectedKeys: Recordable[];
tableColumnKeys: Recordable[];
columns: Recordable[];
checkbox?: boolean;
id: string;
data: any;
}
const slot = useSlots();
const emit = defineEmits([
"row",
"row-double",
"contextmenu",
"update:selectedKeys",
]);
const props = withDefaults(defineProps<LayTableProps>(), {
checkbox: false,
});
const tableSelectedKeys: WritableComputedRef<Recordable[]> = computed({
get() {
return [...props.selectedKeys];
},
set(val) {
emit("update:selectedKeys", val);
},
});
const isExpand = ref(false);
const slotsData = ref<string[]>([]);
props.columns.map((value: any) => {
if (value.customSlot) {
slotsData.value.push(value.customSlot);
}
});
const rowClick = function (data: any, evt: MouseEvent) {
emit("row", data, evt);
};
const rowDoubleClick = function (data: any, evt: MouseEvent) {
emit("row-double", data, evt);
};
const contextmenu = function (data: any, evt: MouseEvent) {
emit("contextmenu", data, evt);
};
const expandIconType = computed(() => {
return isExpand.value ? "layui-icon-subtraction" : "layui-icon-addition";
});
const handleExpand = () => {
isExpand.value = !isExpand.value;
}
</script>
<template>
<tr
@click.stop="rowClick(data, $event)"
@dblclick.stop="rowDoubleClick(data, $event)"
@contextmenu.stop="contextmenu(data, $event)"
>
<!-- 复选框 -->
<td v-if="checkbox" class="layui-table-col-special">
<div class="layui-table-cell laytable-cell-checkbox">
<lay-checkbox
v-model="tableSelectedKeys"
:label="data[id]"
skin="primary"
/>
</div>
</td>
<!-- 数据列 -->
<template v-for="(column, index) in columns" :key="column">
<!-- 展示否 -->
<template v-if="tableColumnKeys.includes(column.key)">
<!-- 插槽列 -->
<template v-if="column.customSlot">
<td
class="layui-table-cell"
:style="{
textAlign: column.align,
width: column.width ? column.width : '0',
minWidth: column.minWidth ? column.minWidth : '47px',
whiteSpace: column.ellipsisTooltip ? 'nowrap' : 'normal',
}"
>
<lay-icon
v-if="(slot.expand || data.children) && index === 0"
class="layui-table-cell-expand-icon"
:type="expandIconType"
@click="handleExpand"
></lay-icon>
<lay-tooltip
v-if="column.ellipsisTooltip"
:content="data[column.key]"
:isAutoShow="true"
>
<slot :name="column.customSlot" :data="data"></slot>
</lay-tooltip>
<slot v-else :name="column.customSlot" :data="data"></slot>
</td>
</template>
<!-- Column -->
<template v-else>
<template v-if="column.key in data">
<td
class="layui-table-cell"
:style="{
textAlign: column.align,
width: column.width ? column.width : '0',
minWidth: column.minWidth ? column.minWidth : '47px',
whiteSpace: column.ellipsisTooltip ? 'nowrap' : 'normal',
}"
>
<lay-icon
v-if="(slot.expand || data.children) && index === 0"
class="layui-table-cell-expand-icon"
:type="expandIconType"
@click="handleExpand"
></lay-icon>
<lay-tooltip
v-if="column.ellipsisTooltip"
:content="data[column.key]"
:isAutoShow="true"
>
{{ data[column.key] }}
</lay-tooltip>
<span v-else> {{ data[column.key] }} </span>
</td>
</template>
</template>
</template>
</template>
</tr>
<!-- 嵌套表单 -->
<tr class="layui-table-cell-expand" v-if="slot.expand && isExpand">
<slot name="expand"></slot>
</tr>
<!-- 树形结构 -->
<template v-if="data.children && isExpand">
<template v-for="(children, index) in data.children" :key="index">
<table-row
:id="id"
:data="children"
:columns="columns"
:checkbox="checkbox"
:tableColumnKeys="tableColumnKeys"
@row="rowClick"
@row-double="rowDoubleClick"
@contextmenu="contextmenu"
v-model:selectedKeys="tableSelectedKeys"
>
<template v-for="name in slotsData" #[name]>
<slot :name="name" :data="data"></slot>
</template>
<template v-if="slot.expand" #expand>
<slot name="expand" :data="data"></slot>
</template>
</table-row>
</template>
</template>
</template>

View File

@ -12,10 +12,6 @@
table-layout: fixed;
}
.layui-table tr {
// display: flex;
}
.layui-table th {
text-align: left;
font-weight: 400;
@ -112,7 +108,7 @@
.layui-table-view .layui-table {
position: relative;
margin: 0;
border-collapse: separate;
border-collapse: collapse;
}
.layui-table-view .layui-table[lay-skin="line"] {
@ -240,8 +236,8 @@
.layui-table-tool-panel li {
overflow: hidden;
text-overflow: ellipsis;
// white-space: nowrap;
}
.layui-table-call-ellipsis{
width: 100%;
overflow: hidden;
@ -360,6 +356,12 @@
-webkit-box-pack: center;
}
.layui-table-cell-expand-icon {
border: 1px solid #eee;
margin-right: 8px;
border-radius: 2px;
}
.layui-table-body {
position: relative;
overflow: auto;
@ -370,7 +372,7 @@
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.4);
background-color: rgba(0, 0, 0, 0.2);
background-clip: padding-box;
border: 3px solid transparent;
border-radius: 7px;
@ -635,3 +637,7 @@ body .layui-table-tips .layui-layer-content {
.layui-table-view {
margin: 10px 0;
}
.layui-table-cell-expand{
border-bottom: 1px solid #eee;
}

View File

@ -6,7 +6,7 @@ export default {
<script setup lang="ts">
import "./index.less";
import { ref, watch, useSlots, withDefaults, onMounted } from "vue";
import { ref, watch, useSlots, withDefaults, onMounted, Ref } from "vue";
import { v4 as uuidv4 } from "../../utils/guidUtil";
import { Recordable } from "../../types";
import LayCheckbox from "../checkbox/index.vue";
@ -14,6 +14,7 @@ import LayDropdown from "../dropdown/index.vue";
import LayTooltip from "../tooltip/index.vue";
import { LayIcon } from "@layui/icons-vue";
import LayPage from "../page/index.vue";
import TableRow from "./TableRow.vue";
export interface LayTableProps {
id?: string;
@ -49,7 +50,7 @@ const slots = slot.default && slot.default();
const allChecked = ref(false);
const tableDataSource = ref([...props.dataSource]);
const tableSelectedKeys = ref([...props.selectedKeys]);
const tableSelectedKeys = ref<Recordable[]>([...props.selectedKeys]);
const tableColumns = ref([...props.columns]);
const tableColumnKeys = ref(
props.columns.map((item: any) => {
@ -81,7 +82,7 @@ const changeAll = function (checked: any) {
watch(
tableSelectedKeys,
function () {
() => {
if (tableSelectedKeys.value.length === props.dataSource.length) {
allChecked.value = true;
} else {
@ -108,7 +109,6 @@ const contextmenu = function (data: any, evt: MouseEvent) {
emit("contextmenu", data, evt);
};
// table
const print = function () {
let subOutputRankPrint = document.getElementById(tableId) as HTMLElement;
let newContent = subOutputRankPrint.innerHTML;
@ -163,16 +163,12 @@ function exportToExcel(headerList: any, bodyList: any) {
}
const sortTable = (e: any, key: string, sort: string) => {
//
let currentSort = e.target.parentNode.getAttribute("lay-sort");
//
if (sort === "desc") {
if (currentSort === sort) {
//
e.target.parentNode.setAttribute("lay-sort", "");
tableDataSource.value = [...props.dataSource];
} else {
// desc
e.target.parentNode.setAttribute("lay-sort", "desc");
tableDataSource.value.sort((x, y) => {
if (x[key] < y[key]) return 1;
@ -182,11 +178,9 @@ const sortTable = (e: any, key: string, sort: string) => {
}
} else {
if (currentSort === sort) {
//
e.target.parentNode.setAttribute("lay-sort", "");
tableDataSource.value = [...props.dataSource];
} else {
// asc
e.target.parentNode.setAttribute("lay-sort", "asc");
tableDataSource.value.sort((x, y) => {
if (x[key] < y[key]) return -1;
@ -200,12 +194,19 @@ const sortTable = (e: any, key: string, sort: string) => {
let tableHeader = ref<HTMLElement | null>(null);
let tableBody = ref<HTMLElement | null>(null);
//
onMounted(() => {
tableBody.value?.addEventListener("scroll", () => {
tableHeader.value!.scrollLeft = tableBody.value?.scrollLeft || 0;
});
});
const slotsData = ref<string[]>([]);
props.columns.map((value: any) => {
if (value.customSlot) {
slotsData.value.push(value.customSlot);
}
});
</script>
<template>
@ -315,86 +316,26 @@ onMounted(() => {
<div class="layui-table-body layui-table-main" ref="tableBody">
<table class="layui-table" :lay-size="size">
<tbody>
<!-- 渲染 -->
<template v-for="data in tableDataSource" :key="data">
<tr
@click.stop="rowClick(data, $event)"
@dblclick.stop="rowDoubleClick(data, $event)"
@contextmenu.stop="contextmenu(data, $event)"
>
<!-- 复选框 -->
<td v-if="checkbox" class="layui-table-col-special">
<div class="layui-table-cell laytable-cell-checkbox">
<lay-checkbox
v-model="tableSelectedKeys"
skin="primary"
:label="data[id]"
/>
</div>
</td>
<!-- 数据列 -->
<template v-for="column in columns" :key="column">
<!-- 展示否 -->
<template v-if="tableColumnKeys.includes(column.key)">
<!-- 插槽列 -->
<template v-if="column.customSlot">
<td
class="layui-table-cell"
:style="{
textAlign: column.align,
width: column.width ? column.width : '0',
minWidth: column.minWidth
? column.minWidth
: '47px',
whiteSpace: column.ellipsisTooltip
? 'nowrap'
: 'normal',
}"
>
<lay-tooltip
v-if="column.ellipsisTooltip"
:content="data[column.key]"
:isAutoShow="true"
>
<slot :name="column.customSlot" :data="data"></slot>
</lay-tooltip>
<slot
v-else
:name="column.customSlot"
<table-row
:id="id"
:data="data"
></slot>
</td>
</template>
<!-- Column -->
<template v-else>
<template v-if="column.key in data">
<td
class="layui-table-cell"
:style="{
textAlign: column.align,
width: column.width ? column.width : '0',
minWidth: column.minWidth
? column.minWidth
: '47px',
whiteSpace: column.ellipsisTooltip
? 'nowrap'
: 'normal',
}"
:columns="columns"
:checkbox="checkbox"
:tableColumnKeys="tableColumnKeys"
@row="rowClick"
@row-double="rowDoubleClick"
@contextmenu="contextmenu"
v-model:selectedKeys="tableSelectedKeys"
>
<lay-tooltip
v-if="column.ellipsisTooltip"
:content="data[column.key]"
:isAutoShow="true"
>
{{ data[column.key] }}
</lay-tooltip>
<span v-else> {{ data[column.key] }} </span>
</td>
<template v-for="name in slotsData" #[name]>
<slot :name="name" :data="data"></slot>
</template>
<template v-if="slot.expand" #expand>
<slot name="expand" :data="data"></slot>
</template>
</template>
</template>
</tr>
</table-row>
</template>
</tbody>
</table>
@ -410,8 +351,12 @@ onMounted(() => {
show-skip
@jump="change"
>
<template #prev><lay-icon type="layui-icon-left" /></template>
<template #next><lay-icon type="layui-icon-right" /></template>
<template #prev>
<lay-icon type="layui-icon-left" />
</template>
<template #next>
<lay-icon type="layui-icon-right" />
</template>
</lay-page>
</div>
</div>

View File

@ -5,7 +5,7 @@ export default {
</script>
<script setup lang="ts">
import { StringOrNumber } from "./tree.type";
import { StringOrNumber, CustomKey, CustomString } from "./tree.type";
import { LayIcon } from "@layui/icons-vue";
import LayCheckbox from "../checkbox/index.vue";
import { Ref, useSlots } from "vue";
@ -13,10 +13,7 @@ import { Tree } from "./tree";
import { Nullable } from "../../types";
import LayTransition from "../transition/index.vue";
type CustomKey = string | number;
type CustomString = (() => string) | string;
interface TreeData {
export interface TreeData {
id: CustomKey;
title: CustomString;
children: TreeData[];
@ -29,7 +26,7 @@ interface TreeData {
parentNode: Nullable<TreeData>;
}
interface TreeNodeProps {
export interface TreeNodeProps {
tree: Tree;
nodeList: TreeData[];
showCheckbox: boolean;
@ -113,9 +110,9 @@ function handleTitleClick(node: TreeData) {
{ 'layui-tree-iconClick': true },
]"
>
<LayIcon :type="nodeIconType(node)" @click="handleIconClick(node)" />
<lay-icon :type="nodeIconType(node)" @click="handleIconClick(node)" />
</span>
<LayCheckbox
<lay-checkbox
v-if="showCheckbox"
:modelValue="node.isChecked.value"
:disabled="node.isDisabled.value"
@ -138,13 +135,13 @@ function handleTitleClick(node: TreeData) {
</span>
</div>
</div>
<LayTransition :enable="collapseTransition">
<lay-transition :enable="collapseTransition">
<div
v-if="node.isLeaf.value"
class="layui-tree-pack layui-tree-showLine"
style="display: block"
>
<TreeNode
<tree-node
:node-list="node.children"
:show-checkbox="showCheckbox"
:show-line="showLine"
@ -154,8 +151,6 @@ function handleTitleClick(node: TreeData) {
@node-click="recursiveNodeClick"
/>
</div>
</LayTransition>
</lay-transition>
</div>
</template>
<style scoped></style>

View File

@ -32,3 +32,6 @@ export interface TreeEmits {
(e: "update:expandKeys", keys: KeysType): void;
(e: "node-click", node: OriginalTreeData, event: Event): void;
}
export type CustomKey = string | number;
export type CustomString = (() => string) | string;

View File

@ -237,6 +237,11 @@ export default {
<lay-button size="xs">修改</lay-button>
<lay-button size="xs" type="primary">删除</lay-button>
</template>
<template v-slot:expand="{ data }">
<div style="height:100px;">
内容
</div>
</template>
</lay-table>
</template>
@ -318,6 +323,103 @@ export default {
:::
::: title 开启子表
:::
::: demo 当表格内容较多不能一次性完全展示时。
<template>
<lay-table :columns="columns6" :dataSource="dataSource6">
<template v-slot:expand="{ data }">
{{ data }}
</template>
</lay-table>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const columns6 = [
{
title:"姓名",
width:"200px",
key:"name"
},{
title:"成绩",
width: "180px",
key:"score"
}
]
const dataSource6 = [
{name:"张三", score:100},
{name:"李四", score:80},
{name:"王二", score:99},
{name:"麻子", score:92},
{name:"无名", score:60},
{name:"有名", score:70},
]
return {
columns6,
dataSource6
}
}
}
</script>
:::
::: title 树形表格
:::
::: demo 树形数据的展示,当数据中有 children 字段时会自动展示为树形表格
<template>
<lay-table :columns="columns7" :dataSource="dataSource7">
</lay-table>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const columns7 = [
{
title:"姓名",
width:"200px",
key:"name"
},{
title:"成绩",
width: "180px",
key:"score"
},
]
const dataSource7 = [
{name:"张三", score:100, children: [{name:"张三", score:100},{name:"张三", score:100}]},
{name:"李四", score:80, children: [{name:"张三", score:100},{name:"张三", score:100}]},
{name:"王二", score:99, children: [{name:"张三", score:100},{name:"张三", score:100}]},
{name:"麻子", score:92, children: [{name:"张三", score:100},{name:"张三", score:100}]},
{name:"无名", score:60, children: [{name:"张三", score:100},{name:"张三", score:100}]},
{name:"有名", score:70, children: [{name:"张三", score:100},{name:"张三", score:100}]},
]
return {
columns7,
dataSource7
}
}
}
</script>
:::
::: title Table 属性
:::