Skip to content

Commit

Permalink
feat: 二次elTable封装 meTable
Browse files Browse the repository at this point in the history
支持自定义列
支持打印
支持导出
自定义工具栏
  • Loading branch information
yuntian001 committed Sep 14, 2022
1 parent c2537ff commit 75af535
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 130 deletions.
68 changes: 68 additions & 0 deletions mock/apiDemo/table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { success, fail } from '../helper';
export default [
{
url: '/api/table/list',
method: 'get',
timeout: 500 + Math.floor(Math.random() * 1000),
response: () => {
return success([
{
date: '2016-05-03',
name: '张三',
province: '北京',
city: '北京',
address: '开发大街1350号',
zip: '100000',
},
{
date: '2016-05-02',
name: '李四',
province: '上海',
city: '上海',
address: '测试大街2350号',
zip: '200000',
},
{
date: '2016-05-04',
name: '王五',
province: '山东',
city: '青岛',
address: '产品大街0875号',
zip: '266000',
},
{
date: '2016-05-01',
name: '刘六',
province: '山东',
city: '济南',
address: '幸福大街4590号',
zip: '250000',
},
{
date: '2016-05-08',
name: '张三',
province: '北京',
city: '北京',
address: '开发大街1350号',
zip: '100000',
},
{
date: '2016-05-06',
name: '王五',
province: '山东',
city: '青岛',
address: '产品大街0875号',
zip: '266000',
},
{
date: '2016-05-07',
name: '王五',
province: '山东',
city: '青岛',
address: '产品大街0875号',
zip: '266000',
},
]);
},
},
];
22 changes: 22 additions & 0 deletions src/api/table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import request from '@/utils/request';

const enum Api {
LIST = '/api/table/list/',
}
export type ListResult = {
date: string;
name: string;
province: string;
city: string;
address: string;
zip: string;
}[];
export function listApi() {
return request<ListResult>(
() => ({
url: Api.LIST,
method: 'get',
}),
{ noLoading: true },
);
}
81 changes: 55 additions & 26 deletions src/components/meTable/hooks/customColumn.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,92 @@
import { debounce } from 'lodash-es';
import { VNode, Component, watch, reactive, shallowRef, ref, withCtx, renderSlot } from 'vue';
import { VNode, Component, watch, reactive, ref } from 'vue';
import { useGlobalStore } from '@/store';
const { i18n } = useGlobalStore();
interface Label {
value: string;
label?: string;
children: Label[];
}
const defaultMap = new Map<string, () => VNode[]>();
const origionDefault = new Map<string, () => VNode[]>();
/* 用于判断 vnode 是否是 el-table-column 组件 */
function isElTableColumn(vnode: VNode) {
return (vnode.type as Component)?.name === 'ElTableColumn';
}
export default (slot: () => VNode[]) => {
const labels = [] as Label[];
const labels = reactive([]) as Label[];
let needScreen = false;
const checkedLabels = reactive(new Set<string>());
const key = ref(Symbol());
const children = shallowRef();
const checkedLabelsRaw = new Set<string>();
let changeLabel = false;
const getVNodes = (vNodes: VNode[], labels: Label[], parentId = '') => {
const components = [] as VNode[];
vNodes.forEach((vNode, index) => {
if (!isElTableColumn(vNode)) {
components.push(vNode);
return;
}
if (changeLabel) {
const label = labels.find((item) => item.value === parentId + '_' + index)!;
label.label = vNode.props?.label;
origionDefault.has(parentId + '_' + index) &&
getVNodes(origionDefault.get(parentId + '_' + index)!(), label.children, parentId + '_' + index);
return;
}
//checkedLabelsRaw不能是动态reactive,否则属性值变换时会触发default渲染造成抖动
if (needScreen) {
if (!checkedLabels.has(parentId + '_' + index)) {
if (!checkedLabelsRaw.has(parentId + '_' + index)) {
return;
}
} else {
if (isElTableColumn(vNode)) {
labels.push({
value: parentId + '_' + index,
label: vNode.props?.label,
children: [],
});
}
checkedLabels.add(parentId + '_' + index);
labels.push({
value: parentId + '_' + index,
label: vNode.props?.label,
children: [],
});
checkedLabelsRaw.add(parentId + '_' + index);
}
if (vNode.children && (vNode.children as Record<string, () => VNode[]>).default) {
if (!needScreen) {
defaultMap.set(parentId + '_' + index, (vNode.children as Record<string, () => VNode[]>)?.default);
origionDefault.set(parentId + '_' + index, (vNode.children as Record<string, () => VNode[]>)?.default);
getVNodes(
defaultMap.get(parentId + '_' + index)!(),
origionDefault.get(parentId + '_' + index)!(),
labels[labels.length - 1].children,
parentId + '_' + index,
);
}
const children = computed(() => defaultMap.get(parentId + '_' + index)!());
(vNode.children as Record<string, () => VNode[]>).default = () => {
return getVNodes(children.value, labels[labels.length - 1].children, parentId + '_' + index)();
};
//必须使用函数方式包含 origion default 否则动态渲染会失效
(vNode.children as Record<string, () => VNode[]>).default = () =>
getVNodes(origionDefault.get(parentId + '_' + index)!(), [], parentId + '_' + index)();
}
components.push(vNode);
});
return () => components;
};
children.value = getVNodes(slot(), labels);
watch(
const children = ref(() => getVNodes(slot(), labels)());
children.value(); //初始化
needScreen = true;
const checkedLabels = reactive(checkedLabelsRaw);
const localeWacth = watch(
//语言变更时更新label
i18n.locale,
() => {
changeLabel = true;
getVNodes(slot(), labels);
changeLabel = false;
},
);
const checkedLabelsWatch = watch(
checkedLabels,
debounce(() => {
needScreen = true;
key.value = Symbol();
children.value = () => getVNodes(slot(), labels)();
}, 500),
);
return { children, key, labels, checkedLabels };
return {
children,
labels,
checkedLabels,
clean: (() => {
localeWacth();
checkedLabelsWatch;
}) as (() => void) | undefined,
};
};
72 changes: 50 additions & 22 deletions src/components/meTable/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
<el-input
v-if="_.vnode.props.onQuickSearch"
v-model="searchText"
placeholder="快捷搜索"
:placeholder="$t('快捷搜索')"
prefix-icon="mel-icon-search"
@change="$emit('quickSearch', searchText)"
/>
<el-button-group>
<el-popover v-if="customColumn" placement="bottom" trigger="click" width="auto">
<template #reference>
<el-button icon="mel-icon-grid" title="自定义列" />
<el-button icon="mel-icon-grid" :title="$t('自定义列')" />
</template>
<template #default>
<el-tree
node-key="value"
:data="labels"
:data="customColumnProps.labels"
default-expand-all
:default-checked-keys="checkedLabels"
:props="{ label: 'label', children: 'children' }"
Expand All @@ -43,39 +43,45 @@
popper-class="me-exportmenu-popover el-dropdown__popper"
>
<template #reference>
<el-button icon="mel-icon-download" title="导出" />
<el-button icon="mel-icon-download" :title="$t('导出')" />
</template>
<template #default>
<ul class="el-dropdown-menu">
<li
v-for="item in exportMenu"
:key="item.label"
class="el-dropdown-menu__item"
@click="handleExport(elTable, item.filename ?? name!)"
@click="handleExport(item.handle, item.filename ?? name!)"
>
{{ item.label }}
</li>
</ul>
</template>
</el-popover>
<el-button v-if="print" icon="mel-icon-printer" title="打印" @click="printTable" />
<el-button v-if="print" icon="mel-icon-printer" :title="$t('打印')" @click="printTable(elTable, name)" />
<slot name="tools"></slot>
</el-button-group>
<el-button v-if="$slots.search" @click="showSearch = !showSearch">
<el-button v-if="$slots.search" :title="$t('更多筛选')" @click="showSearch = !showSearch">
<mel-icon-search></mel-icon-search>
</el-button>
</div>
</div>
</div>
<el-table v-bind="$attrs" ref="elTable" v-loading="loading" v-show="showTable">
<component :is="children" :key="key"></component>
<el-table v-bind="$attrs" ref="elTable" v-loading="loading">
<component :is="customColumnProps.children"></component>
<template #append>
<slot name="append"></slot>
</template>
<template #empty>
<slot name="empty"></slot>
</template>
</el-table>
</div>
</template>
<script lang="ts">
import { children } from 'dom7';
import { ElTable } from 'element-plus';
import { fa } from 'element-plus/es/locale';
import { VNode } from 'snabbdom';
import { ComponentOptionsMixin, ExtractPropTypes, PropType } from 'vue';
import customColumn from './hooks/customColumn';
import exportTable from './hooks/exportTable';
Expand Down Expand Up @@ -148,28 +154,50 @@ export default defineComponent<
setup(props, { slots }) {
const showSearch = ref(props.defaultShowSearch);
const searchText = ref('');
const { children, labels, checkedLabels, key } = customColumn(slots.default!);
const customColumnProps = ref<ReturnType<typeof customColumn>>();
const checkedLabels = shallowRef([] as string[]);
watch(
() => props.customColumn,
(value) => {
if (value) {
customColumnProps.value = customColumn(slots.default!);
checkedLabels.value = [...customColumnProps.value.checkedLabels];
} else {
customColumnProps.value?.clean?.();
customColumnProps.value = {
children: slots.default! as any,
labels: [],
checkedLabels: new Set(),
clean: undefined,
};
}
},
{ immediate: true },
);
const checkChange = (data: { value: string }, is: boolean, childrenIs: boolean) => {
if (is || childrenIs) {
checkedLabels.add(data.value);
customColumnProps.value!.checkedLabels.add(data.value);
} else {
checkedLabels.delete(data.value);
customColumnProps.value!.checkedLabels.delete(data.value);
}
};
const elTable = ref<ELTable>();
const showTable = ref(true);
watch(key, () => {
showTable.value = false;
setTimeout(() => (showTable.value = true), 500);
onMounted(() => {
elTable.value!.getSelectionIndexs = function () {
const index = [] as number[];
if (this.data) {
this.getSelectionRows()?.forEach((item: unknown) => {
index.push(this.data.indexOf(toRaw(item)));
});
}
return index;
};
});
return {
showTable,
key,
showSearch,
searchText,
children,
labels,
checkedLabels: [...checkedLabels],
customColumnProps,
checkedLabels,
checkChange,
elTable,
exportTable,
Expand Down
7 changes: 7 additions & 0 deletions src/locales/lang/en/meTable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"自定义列": "Custom Columns",
"导出": "Export",
"打印": "Print",
"快捷搜索": "Quick Search",
"更多筛选": "Filter By"
}
Loading

0 comments on commit 75af535

Please sign in to comment.