feat(table): table (#407)

Co-authored-by: winerlu <winerlu@tencent.com>
Co-authored-by: zazzaz <izazzaz@hotmail.com>
This commit is contained in:
justwiner 2020-10-20 10:31:47 +08:00 committed by GitHub
parent f4f81af246
commit 2985a71751
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 9551 additions and 68 deletions

View File

@ -12,6 +12,7 @@
"build:esm": "rollup --config ./build/rollup.config.js",
"build:theme": "rimraf packages/theme-chalk/lib && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk && rimraf packages/theme-chalk/lib",
"lint": "eslint ./packages --ext .vue,.js,.ts",
"lint-fix": "eslint --fix ./packages --ext .vue,.js,.ts",
"website-build": "rimraf website-dist && cross-env NODE_ENV=production webpack --config ./website/webpack.config.js",
"website-dev": "webpack-dev-server --config ./website/webpack.config.js",
"website-dev:play": "cross-env PLAY_ENV=true yarn website-dev"
@ -106,7 +107,9 @@
"@popperjs/core": "^2.4.4",
"dayjs": "1.x",
"lodash": "^4.17.20",
"mitt": "^2.1.0"
"mitt": "^2.1.0",
"normalize-wheel": "^1.0.1",
"sinon": "^9.0.3"
},
"files": [
"lib",

View File

@ -1,3 +1,4 @@
export { default as ClickOutside } from './click-outside'
export { default as RepeatClick } from './repeat-click'
export { default as TrapFocus } from './trap-focus'
export { default as Mousewheel } from './mousewheel'

View File

@ -0,0 +1,28 @@
import normalizeWheel from 'normalize-wheel'
import type { ObjectDirective } from 'vue'
const isFirefox =
typeof navigator !== 'undefined' &&
navigator.userAgent.toLowerCase().indexOf('firefox') > -1
const mousewheel = function (element, callback) {
if (element && element.addEventListener) {
const fn = function (event) {
const normalized = normalizeWheel(event)
callback && callback.apply(this, [event, normalized])
}
if (isFirefox) {
element.addEventListener('DOMMouseScroll', fn)
} else {
element.onmousewheel = fn
}
}
}
const Mousewheel: ObjectDirective = {
beforeMount(el, binding) {
mousewheel(el, binding.value)
},
}
export default Mousewheel

View File

@ -40,6 +40,10 @@ import ElCalendar from '@element-plus/calendar'
import ElInfiniteScroll from '@element-plus/infinite-scroll'
import ElMessage from '@element-plus/message'
import ElDrawer from '@element-plus/drawer'
import ElTableInstall, {
Table as ElTable,
TableColumn as ElTableColumn,
} from '@element-plus/table'
import ElPopconfirm from '@element-plus/popconfirm'
import ElForm from '@element-plus/form'
import ElUpload from '@element-plus/upload'
@ -93,6 +97,8 @@ export {
ElInfiniteScroll,
ElMessage,
ElDrawer,
ElTable,
ElTableColumn,
ElPopconfirm,
ElForm,
ElUpload,
@ -149,6 +155,7 @@ const install = (app: App): void => {
ElMessage(app)
ElMessageBox(app)
ElDrawer(app)
ElTableInstall(app)
ElPopconfirm(app)
ElForm(app)
ElUpload(app)

View File

@ -3,3 +3,4 @@ export { default as useEvents } from './use-events'
export { default as useLockScreen } from './use-lockscreen'
export { default as useRestoreActive } from './use-restore-active'
export { default as useModal } from './use-modal'
export { default as useMigrating } from './use-migrating'

View File

@ -0,0 +1,33 @@
import { kebabCase } from '@element-plus/utils/util'
import { onMounted, getCurrentInstance } from 'vue'
const useMigrating = function () {
onMounted(() => {
const instance = getCurrentInstance()
if (process.env.NODE_ENV === 'production') return
if (!instance.vnode) return
const { props = {} } = getMigratingConfig()
const { data } = instance
const definedProps = data.attrs || {}
for (let propName in definedProps as any) {
propName = kebabCase(propName) // compatible with camel case
if (props[propName]) {
console.warn(
`[Element Migrating][${this.$options.name}][Attribute]: ${props[propName]}`,
)
}
}
})
const getMigratingConfig = function () {
return {
props: {},
events: {},
}
}
return {
getMigratingConfig,
}
}
export default useMigrating

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
<template>
<div class="block">
<h3>基础表格</h3>
<el-table :data="tableData" style="width: 100%">
<el-table-column label="序号" type="index" width="80" />
<el-table-column label="日期" prop="date" width="180" />
<el-table-column label="姓名" prop="name" width="180" />
<el-table-column label="地址" prop="address" />
</el-table>
</div>
</template>
<script lang='ts'>
export default {
setup() {
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
}
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,70 @@
<template>
<div class="block">
<h3>带边框表格</h3>
<el-table :data="tableData" border style="width: 100%">
<el-table-column
label="日期"
prop="date"
show-overflow-tooltip
width="180"
/>
<el-table-column
label="姓名"
prop="name"
show-overflow-tooltip
width="80"
/>
<el-table-column label="地址" prop="address" />
</el-table>
</div>
</template>
<script lang='ts'>
export default {
setup() {
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎王小虎王小虎王小虎王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
],
}
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,58 @@
<template>
<h3>自定义表头</h3>
<el-table :data="tableData.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase()))" style="width: 100%">
<el-table-column label="Date" prop="date" />
<el-table-column label="Name" prop="name" />
<el-table-column align="right">
<template #header>
<el-input v-model="search" placeholder="输入关键字搜索" size="mini" />
</template>
<template #default="scope">
<el-button size="mini" type @click="handleEdit(scope.$index, scope.row)">Edit</el-button>
<el-button size="mini" type @click="handleDelete(scope.$index, scope.row)">Delete</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const handleEdit = (index, row) => {
console.log(index, row)
}
const handleDelete = (index, row) => {
console.log(index, row)
}
const search = ref('')
return {
handleEdit,
handleDelete,
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
search,
}
},
}
</script>

View File

@ -0,0 +1,60 @@
<template>
<h3>自定义索引</h3>
<el-table :data="tableData" style="width: 100%">
<el-table-column :index="indexMethod" type="index" />
<el-table-column label="日期" prop="date" width="180" />
<el-table-column label="姓名" prop="name" width="180" />
<el-table-column label="地址" prop="address" />
</el-table>
</template>
<script>
export default {
setup() {
const indexMethod = index => {
return index * 2
}
return {
indexMethod,
tableData: [
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
tag: '家',
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333,
tag: '公司',
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333,
tag: '家',
},
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333,
tag: '公司',
},
],
}
},
}
</script>

View File

@ -0,0 +1,81 @@
<template>
<h3>自定义列模板</h3>
<el-table :data="tableData" style="width: 100%">
<el-table-column label="日期" width="180">
<template #default="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>
</el-table-column>
<el-table-column label="姓名" width="180">
<template #default="scope">
<el-popper effect="light" placement="top" trigger="hover">
<template #default>
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
</template>
<template #trigger>
<div class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</template>
</el-popper>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button size="mini" type @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { Popper as ElPopper } from '@element-plus/popper'
export default {
components: {
ElPopper,
},
setup() {
const handleEdit = (index, row) => {
console.log(index, row)
}
const handleDelete = (index, row) => {
console.log(index, row)
}
return {
handleEdit,
handleDelete,
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
}
},
}
</script>
<style scoped>
.name-wrapper {
display: inline-block;
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<h3>展开行</h3>
<el-button type @click="toggleRowExpansion">切换某一行的展开状态</el-button>
<el-table ref="table" :data="tableData" style="width: 100%">
<el-table-column type="expand">
<template #default="props">
<div class="expand-table-demo">
<div>
<label>商品名称</label>
<span>{{ props.row.name }}</span>
</div>
<div>
<label>所属店铺</label>
<span>{{ props.row.shop }}</span>
</div>
<div>
<label>商品 ID</label>
<span>{{ props.row.id }}</span>
</div>
<div>
<label>店铺 ID</label>
<span>{{ props.row.shopId }}</span>
</div>
<div>
<label>商品分类</label>
<span>{{ props.row.category }}</span>
</div>
<div>
<label>店铺地址</label>
<span>{{ props.row.address }}</span>
</div>
<div>
<label>商品描述</label>
<span>{{ props.row.desc }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="商品 ID" prop="id" />
<el-table-column label="商品名称" prop="name" />
<el-table-column label="描述" prop="desc" />
</el-table>
</template>
<script>
import { getCurrentInstance, ref } from 'vue'
export default {
setup() {
const instance = getCurrentInstance()
const tableData = ref([
{
id: '12987122',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333',
},
{
id: '12987123',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333',
},
{
id: '12987125',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333',
},
{
id: '12987126',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333',
},
])
const toggleRowExpansion = () => {
instance.refs.table.toggleRowExpansion(tableData.value[0])
}
return {
toggleRowExpansion,
tableData,
}
},
}
</script>
<style scoped>
.expand-table-demo > div > label {
width: 90px;
color: #99a9bf;
display: inline-block;
}
.expand-table-demo > div {
margin: 10px 0;
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<h3>过滤</h3>
<el-button type @click="resetDateFilter">清除日期过滤器</el-button>
<el-button type @click="clearFilter">清除所有过滤器</el-button>
<el-table ref="filterTable" :data="tableData" style="width: 100%">
<el-table-column
:filter-method="filterHandler"
:filter-multiple="false"
:filters="[{text: '2016-05-01', value: '2016-05-01'}, {text: '2016-05-02', value: '2016-05-02'}, {text: '2016-05-03', value: '2016-05-03'}, {text: '2016-05-04', value: '2016-05-04'}]"
column-key="date"
label="日期"
prop="date"
sortable
width="180"
/>
<el-table-column label="姓名" prop="name" width="180" />
<el-table-column :formatter="formatter" label="地址" prop="address" />
<el-table-column
:filter-method="filterTag"
:filters="[{ text: '家', value: '家' }, { text: '公司', value: '公司' }]"
filter-placement="bottom-start"
label="标签"
prop="tag"
width="100"
>
<template #default="scope">
<el-tag :type="scope.row.tag === '家' ? 'primary' : 'success'" disable-transitions>{{ scope.row.tag }}</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { getCurrentInstance } from 'vue'
export default {
setup() {
const instance = getCurrentInstance()
const resetDateFilter = () => {
instance.refs.filterTable.clearFilter('date')
}
const clearFilter = () => {
instance.refs.filterTable.clearFilter()
}
const formatter = row => {
return row.address + '---'
}
const filterTag = (value, row) => {
return row.tag === value
}
const filterHandler = (value, row, column) => {
const property = column['property']
return row[property] === value
}
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
tag: '家',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
tag: '公司',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
tag: '家',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
tag: '公司',
},
],
resetDateFilter,
clearFilter,
formatter,
filterTag,
filterHandler,
}
},
}
</script>

View File

@ -0,0 +1,71 @@
<template>
<div class="block">
<h3>固定列</h3>
<el-table :data="tableData" border style="width: 500px">
<el-table-column
fixed
label="日期"
prop="date"
width="150"
/>
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="省份" prop="province" width="120" />
<el-table-column label="市区" prop="city" width="120" />
<el-table-column label="地址" prop="address" width="300" />
<el-table-column label="邮编" prop="zip" width="120" />
<el-table-column fixed="right" label="操作" width="100">
<template #default="scope">
<el-button size="small" type="text" @click="handleClick(scope.row)">查看</el-button>
<el-button size="small" type="text">编辑</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
setup() {
const handleClick = row => {
console.log(row)
}
return {
handleClick,
tableData: [
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333,
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333,
},
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333,
},
],
}
},
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<div class="block" style="width: 500px">
<h3>固定列和表头</h3>
<el-table :data="tableData" height="250" style="width: 100%">
<el-table-column
fixed
label="日期"
prop="date"
width="150"
/>
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="省份" prop="province" width="120" />
<el-table-column label="市区" prop="city" width="120" />
<el-table-column label="地址" prop="address" width="300" />
<el-table-column label="邮编" prop="zip" width="120" />
</el-table>
</div>
</template>
<script>
export default {
setup() {
return {
tableData: [
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-08',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-06',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
],
}
},
}
</script>

View File

@ -0,0 +1,95 @@
<template>
<div class="block">
<h3>固定表头</h3>
<el-table
:data="tableData"
border
height="250"
style="width: 100%"
>
<el-table-column label="日期" prop="date" width="180" />
<el-table-column label="姓名" prop="name" width="180" />
<el-table-column label="地址" prop="address" />
</el-table>
</div>
</template>
<script lang='ts'>
export default {
setup() {
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
],
}
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,104 @@
<template>
<h3>表尾合计行</h3>
<el-table
:data="tableData"
border
show-summary
style="width: 100%"
>
<el-table-column label="ID" prop="id" width="180" />
<el-table-column label="姓名" prop="name" />
<el-table-column label="数值 1" prop="amount1" sortable />
<el-table-column label="数值 2" prop="amount2" sortable />
<el-table-column label="数值 3" prop="amount3" sortable />
</el-table>
<el-table
:data="tableData"
:summary-method="getSummaries"
border
height="200"
show-summary
style="width: 100%; margin-top: 20px"
sum-text="123"
>
<el-table-column label="ID" prop="id" width="180" />
<el-table-column label="姓名" prop="name" />
<el-table-column label="数值 1" prop="amount1" />
<el-table-column label="数值 2" prop="amount2" />
<el-table-column label="数值 3" prop="amount3" />
</el-table>
</template>
<script>
export default {
setup() {
const getSummaries = param => {
const { columns, data } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '总价'
return
}
const values = data.map(item => Number(item[column.property]))
if (!values.every(value => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr)
if (!isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)
sums[index] += ' 元'
} else {
sums[index] = 'N/A'
}
})
return sums
}
return {
getSummaries,
tableData: [
{
id: '12987122',
name: '王小虎',
amount1: '234',
amount2: '3.2',
amount3: 10,
},
{
id: '12987123',
name: '王小虎',
amount1: '165',
amount2: '4.43',
amount3: 12,
},
{
id: '12987124',
name: '王小虎',
amount1: '324',
amount2: '1.9',
amount3: 9,
},
{
id: '12987125',
name: '王小虎',
amount1: '621',
amount2: '2.2',
amount3: 17,
},
{
id: '12987126',
name: '王小虎',
amount1: '539',
amount2: '4.1',
amount3: 15,
},
],
}
},
}
</script>

View File

@ -0,0 +1,24 @@
export { default as BasicUsage } from './basic.vue'
export { default as Border } from './border.vue'
export { default as Stripe } from './stripe.vue'
export { default as RowClassName } from './rowClassName.vue'
export { default as FixHeader } from './fixedHeader.vue'
export { default as FixCol } from './fixedCol.vue'
export { default as FixColRow } from './fixedColRow.vue'
export { default as MaxHeight } from './maxHeight.vue'
export { default as MulHeader } from './mulHeader.vue'
export { default as SingleSelect } from './singleSelect.vue'
export { default as MulCheck } from './mulCheck.vue'
export { default as Sort } from './sort.vue'
export { default as Filter } from './filter.vue'
export { default as DiyTemplate } from './diyTemplate.vue'
export { default as ExpandRow } from './expandRow.vue'
export { default as TreeTable } from './treeTable.vue'
export { default as DiyHeader } from './diyHeader.vue'
export { default as FooterCount } from './footerCount.vue'
export { default as MergeRowCol } from './mergeRowCol.vue'
export { default as DiyIndex } from './diyIndex.vue'
export default {
title: 'Table',
}

View File

@ -0,0 +1,94 @@
<template>
<h3>流体高度</h3>
<el-table :data="tableData" max-height="250" style="width: 700px">
<el-table-column
fixed
label="日期"
prop="date"
width="150"
/>
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="省份" prop="province" width="120" />
<el-table-column label="市区" prop="city" width="120" />
<el-table-column label="地址" prop="address" width="300" />
<el-table-column label="邮编" prop="zip" width="120" />
<el-table-column fixed="right" label="操作" width="120">
<template #default="scope">
<el-button size="small" type="text" @click.prevent="deleteRow(scope.$index)">移除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const tableData = ref([
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-08',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-06',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
])
const deleteRow = index => {
tableData.value.splice(index, 1)
}
return {
deleteRow,
tableData,
}
},
}
</script>

View File

@ -0,0 +1,103 @@
<template>
<div>
<h3>合并行或列</h3>
<el-table
:data="tableData"
:span-method="arraySpanMethod"
border
style="width: 100%"
>
<el-table-column label="ID" prop="id" width="180" />
<el-table-column label="姓名" prop="name" />
<el-table-column label="数值 1" prop="amount1" sortable />
<el-table-column label="数值 2" prop="amount2" sortable />
<el-table-column label="数值 3" prop="amount3" sortable />
</el-table>
<el-table
:data="tableData"
:span-method="objectSpanMethod"
border
style="width: 100%; margin-top: 20px"
>
<el-table-column label="ID" prop="id" width="180" />
<el-table-column label="姓名" prop="name" />
<el-table-column label="数值 1" prop="amount1" />
<el-table-column label="数值 2" prop="amount2" />
<el-table-column label="数值 3" prop="amount3" />
</el-table>
</div>
</template>
<script>
export default {
setup() {
const arraySpanMethod = ({ rowIndex, columnIndex }) => {
if (rowIndex % 2 === 0) {
if (columnIndex === 0) {
return [1, 2]
} else if (columnIndex === 1) {
return [0, 0]
}
}
}
const objectSpanMethod = ({ rowIndex, columnIndex }) => {
if (columnIndex === 0) {
if (rowIndex % 2 === 0) {
return {
rowspan: 2,
colspan: 1,
}
} else {
return {
rowspan: 0,
colspan: 0,
}
}
}
}
return {
arraySpanMethod,
objectSpanMethod,
tableData: [
{
id: '12987122',
name: '王小虎',
amount1: '234',
amount2: '3.2',
amount3: 10,
},
{
id: '12987123',
name: '王小虎',
amount1: '165',
amount2: '4.43',
amount3: 12,
},
{
id: '12987124',
name: '王小虎',
amount1: '324',
amount2: '1.9',
amount3: 9,
},
{
id: '12987125',
name: '王小虎',
amount1: '621',
amount2: '2.2',
amount3: 17,
},
{
id: '12987126',
name: '王小虎',
amount1: '539',
amount2: '4.1',
amount3: 15,
},
],
}
},
}
</script>

View File

@ -0,0 +1,90 @@
<template>
<h3>多选</h3>
<el-table
ref="multipleTable"
:data="tableData"
style="width: 100%"
tooltip-effect="dark"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column label="日期" width="120">
<template #default="scope">{{ scope.row.date }}</template>
</el-table-column>
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="地址" prop="address" />
</el-table>
<div style="margin-top: 20px">
<el-button type @click="toggleSelection([tableData[1], tableData[2]])">切换第二第三行的选中状态</el-button>
<el-button type @click="toggleAllSelection()">切换所有行选中状态</el-button>
<el-button type @click="toggleSelection()">取消选择</el-button>
</div>
</template>
<script>
import { ref, getCurrentInstance } from 'vue'
export default {
setup() {
const multipleSelection = ref([])
const instance = getCurrentInstance()
const toggleSelection = rows => {
if (rows) {
rows.forEach(row => {
instance.refs.multipleTable.toggleRowSelection(row)
})
} else {
instance.refs.multipleTable.clearSelection()
}
}
const handleSelectionChange = val => {
multipleSelection.value = val
}
const toggleAllSelection = () => {
instance.refs.multipleTable.toggleAllSelection()
}
return {
tableData: [
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-08',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-06',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-07',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
],
multipleSelection,
toggleSelection,
handleSelectionChange,
toggleAllSelection,
}
},
}
</script>

View File

@ -0,0 +1,82 @@
<template>
<h3>多级表头</h3>
<el-table :data="tableData" border style="width: 100%">
<el-table-column label="日期" prop="date" width="150" />
<el-table-column label="配送信息">
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="地址">
<el-table-column label="省份" prop="province" width="120" />
<el-table-column label="市区" prop="city" width="120" />
<el-table-column label="地址" prop="address" width="300" />
<el-table-column label="邮编" prop="zip" width="120" />
</el-table-column>
</el-table-column>
</el-table>
</template>
<script>
export default {
setup() {
return {
tableData: [
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333,
},
// {
// date: '2016-05-04',
// name: '',
// province: '',
// city: '',
// address: ' 1518 ',
// zip: 200333
// },
// {
// date: '2016-05-01',
// name: '',
// province: '',
// city: '',
// address: ' 1518 ',
// zip: 200333
// },
// {
// date: '2016-05-08',
// name: '',
// province: '',
// city: '',
// address: ' 1518 ',
// zip: 200333
// },
// {
// date: '2016-05-06',
// name: '',
// province: '',
// city: '',
// address: ' 1518 ',
// zip: 200333
// },
// {
// date: '2016-05-07',
// name: '',
// province: '',
// city: '',
// address: ' 1518 ',
// zip: 200333
// }
],
}
},
}
</script>

View File

@ -0,0 +1,61 @@
<template>
<div class="block">
<h3>带状态表格</h3>
<el-table :data="tableData" :row-class-name="tableRowClassName" style="width: 100%">
<el-table-column label="日期" prop="date" width="180" />
<el-table-column label="姓名" prop="name" width="180" />
<el-table-column label="地址" prop="address" />
</el-table>
</div>
</template>
<script lang='ts'>
export default {
setup() {
const tableRowClassName = ({ rowIndex }) => {
if (rowIndex === 1) {
return 'warning-row'
} else if (rowIndex === 3) {
return 'success-row'
}
return ''
}
return {
tableRowClassName,
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
}
},
}
</script>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<h3>单选</h3>
<el-table
ref="singleTable"
:data="tableData"
highlight-current-row
style="width: 100%"
@current-change="handleCurrentChange"
>
<el-table-column type="index" width="50" />
<el-table-column label="日期" property="date" width="120" />
<el-table-column label="姓名" property="name" width="120" />
<el-table-column label="地址" property="address" />
</el-table>
<div style="margin-top: 20px">
<el-button type @click="setCurrent(tableData[1])">选中第二行</el-button>
<el-button type @click="setCurrent()">取消选择</el-button>
</div>
</template>
<script>
import { ref, getCurrentInstance } from 'vue'
export default {
setup() {
const currentRow = ref(null)
const instance = getCurrentInstance()
const setCurrent = row => {
instance.refs.singleTable.setCurrentRow(row)
}
const handleCurrentChange = val => {
currentRow.value = val
}
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
currentRow,
setCurrent,
handleCurrentChange,
}
},
}
</script>

View File

@ -0,0 +1,68 @@
<template>
<h3>排序</h3>
<el-button type @click="clearSort">清空排序</el-button>
<el-button type @click="sort">手动为日期排序</el-button>
<el-table
ref="table"
:data="tableData"
:default-sort="{prop: 'date', order: 'descending'}"
style="width: 100%"
>
<el-table-column
label="日期"
prop="date"
sortable
width="180"
/>
<el-table-column
label="姓名"
prop="name"
sortable
width="180"
/>
<el-table-column :formatter="formatter" label="地址" prop="address" />
</el-table>
</template>
<script>
import { getCurrentInstance } from 'vue'
export default {
setup() {
const instance = getCurrentInstance()
const formatter = row => {
return row.address
}
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
formatter,
clearSort: () => {
instance.refs.table.clearSort()
},
sort: () => {
instance.refs.table.sort('date', 'ascending')
},
}
},
}
</script>

View File

@ -0,0 +1,60 @@
<template>
<div class="block">
<h3>带斑马纹表格</h3>
<el-table :data="tableData" stripe style="width: 100%">
<el-table-column label="日期" prop="date" width="180" />
<el-table-column label="姓名" prop="name" width="180" />
<el-table-column label="地址" prop="address" />
</el-table>
</div>
</template>
<script lang='ts'>
export default {
setup() {
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
],
}
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,135 @@
<template>
<h3>树形数据与懒加载</h3>
<div>
<el-table
:data="tableData"
:expand-row-keys="['3']"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
border
row-key="id"
style="width: 100%;margin-bottom: 20px;"
>
<el-table-column
label="日期"
prop="date"
sortable
width="180"
/>
<el-table-column
label="姓名"
prop="name"
sortable
width="180"
/>
<el-table-column label="地址" prop="address" />
</el-table>
<el-table
:data="tableData1"
:load="load"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
border
lazy
row-key="id"
style="width: 100%"
>
<el-table-column label="日期" prop="date" width="180" />
<el-table-column label="姓名" prop="name" width="180" />
<el-table-column label="地址" prop="address" />
</el-table>
</div>
</template>
<script>
export default {
setup() {
const load = (tree, treeNode, resolve) => {
setTimeout(() => {
resolve([
{
id: 31,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
id: 32,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
])
}, 1000)
}
return {
load,
tableData: [
{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
id: 2,
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
children: [
{
id: 31,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
{
id: 32,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
},
],
},
{
id: 4,
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
tableData1: [
{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
},
{
id: 2,
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
},
{
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
hasChildren: true,
},
{
id: 4,
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
},
],
}
},
}
</script>

9
packages/table/index.ts Normal file
View File

@ -0,0 +1,9 @@
import { App } from 'vue'
import Table from './src/table.vue'
import TableColumn from './src/tableColumn'
export default (app: App): void => {
app.component(Table.name, Table)
app.component(TableColumn.name, TableColumn)
}
export { Table, TableColumn }

View File

@ -0,0 +1,12 @@
{
"name": "@element-plus/table",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.9"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.3"
}
}

View File

@ -0,0 +1,195 @@
import { getPropByPath } from '@element-plus/utils/util'
import ElCheckbox from '@element-plus/checkbox/src/checkbox.vue'
import { h } from 'vue'
import { Store, TreeNode, AnyObject, TableColumnCtx } from './table'
export const cellStarts = {
default: {
order: '',
},
selection: {
width: 48,
minWidth: 48,
realWidth: 48,
order: '',
className: 'el-table-column--selection',
},
expand: {
width: 48,
minWidth: 48,
realWidth: 48,
order: '',
},
index: {
width: 48,
minWidth: 48,
realWidth: 48,
order: '',
},
}
// 这些选项不应该被覆盖
export const cellForced = {
selection: {
renderHeader: function ({ store: store_ }) {
const store = store_ as Store
return h(ElCheckbox, {
disabled:
store.states.data.value && store.states.data.value.length === 0,
indeterminate:
store.states.selection.value.length > 0 &&
!store.states.isAllSelected.value,
onClick: store.toggleAllSelection,
modelValue: store.states.isAllSelected.value,
})
},
renderCell: function ({
row: row_,
column: column_,
store: store_,
index_: $index_,
}) {
const store = store_ as Store
const row = row_ as AnyObject
const column = column_ as TableColumnCtx
const index_ = $index_ as string
return h(ElCheckbox, {
disabled: column.selectable
? !column.selectable.call(null, row, index_)
: false,
onInput: () => {
store.commit('rowSelectedChanged', row)
},
nativeOnClick: (event: Event) => event.stopPropagation(),
modelValue: store.isSelected(row),
})
},
sortable: false,
resizable: false,
},
index: {
renderHeader: function ({ column: column_ }) {
const column = column_ as TableColumnCtx
return column.label || '#'
},
renderCell: function ({ index_: $index_, column: column_ }) {
const index_ = $index_ as string
const column = column_ as TableColumnCtx
let i = index_ + 1
const index = column.index
if (typeof index === 'number') {
i = index_ + index
} else if (typeof index === 'function') {
i = index(index_)
}
return h('div', {}, [i])
},
sortable: false,
},
expand: {
renderHeader: function ({ column: column_ }) {
const column = column_ as TableColumnCtx
return column.label || ''
},
renderCell: function ({ row: row_, store: store_ }) {
const store = store_ as Store
const row = row_ as AnyObject
const classes = ['el-table__expand-icon']
if (store.states.expandRows.value.indexOf(row) > -1) {
classes.push('el-table__expand-icon--expanded')
}
const callback = function (e) {
e.stopPropagation()
store.toggleRowExpansion(row)
}
return h(
'div',
{
class: classes,
onClick: callback,
},
[
h('i', {
class: 'el-icon el-icon-arrow-right',
}),
],
)
},
sortable: false,
resizable: false,
className: 'el-table__expand-column',
},
}
export function defaultRenderCell({
row: row_,
column: column_,
index_: $index_,
}) {
const row = row_ as AnyObject
const column = column_ as TableColumnCtx
const index_ = $index_ as string
const property = column.property
const value = property && getPropByPath(row, property, false).v
if (column && column.formatter) {
return column.formatter(row, column, value, index_)
}
return value
}
export function treeCellPrefix({
row: row_,
treeNode: treeNode_,
store: store_,
}) {
const row = row_ as AnyObject
const store = store_ as Store
const treeNode = treeNode_ as TreeNode
if (!treeNode) return null
const ele = []
const callback = function (e) {
e.stopPropagation()
store.loadOrToggle(row)
}
if (treeNode.indent) {
ele.push(
h('span', {
class: 'el-table__indent',
style: { 'padding-left': treeNode.indent + 'px' },
}),
)
}
if (typeof treeNode.expanded === 'boolean' && !treeNode.noLazyChildren) {
const expandClasses = [
'el-table__expand-icon',
treeNode.expanded ? 'el-table__expand-icon--expanded' : '',
]
let iconClasses = ['el-icon-arrow-right']
if (treeNode.loading) {
iconClasses = ['el-icon-loading']
}
ele.push(
h(
'div',
{
class: expandClasses,
onClick: callback,
},
[
h('i', {
class: iconClasses,
}),
],
),
)
} else {
ele.push(
h('span', {
class: 'el-table__placeholder',
}),
)
}
return ele
}

View File

@ -0,0 +1,35 @@
import isServer from '@element-plus/utils/isServer'
const dropdowns = []
!isServer && document.addEventListener('click', function (event: Event) {
dropdowns.forEach(function (dropdown) {
const target = event.target
if (!dropdown || !dropdown.$el) return
if (target === dropdown.$el || dropdown.$el.contains(target)) {
return
}
dropdown.handleOutsideClick && dropdown.handleOutsideClick(event)
})
})
function useDropdown(instance) {
const open = () => {
if (instance) {
dropdowns.push(instance)
}
}
const close = () => {
const index = dropdowns.indexOf(instance)
if (index !== -1) {
dropdowns.splice(instance, 1)
}
}
return {
open,
close,
}
}
export default useDropdown

View File

@ -0,0 +1,259 @@
<template>
<el-popper
ref="tooltip"
:offset="0"
:placement="placement"
:show-arrow="false"
:trigger="['click']"
:visible="tooltipVisible"
effect="light"
popper-class="el-table-filter el-table-filter-padding"
>
<template #default>
<div v-click-outside="handleOutsideClick">
<div v-if="multiple">
<div class="el-table-filter__content">
<el-scrollbar
:native="false"
:noresize="true"
wrap-class="el-table-filter__wrap"
>
<el-checkbox-group
v-model="filteredValue"
class="el-table-filter__checkbox-group"
>
<el-checkbox
v-for="filter in filters"
:key="filter.value"
:label="filter.value"
>
{{ filter.text }}
</el-checkbox>
</el-checkbox-group>
</el-scrollbar>
</div>
<div class="el-table-filter__bottom">
<button
:class="{ 'is-disabled': filteredValue.length === 0 }"
:disabled="filteredValue.length === 0"
type
@click="handleConfirm"
>
{{ t('el.table.confirmFilter') }}
</button>
<button type @click="handleReset">
{{ t('el.table.resetFilter') }}
</button>
</div>
</div>
<ul v-else class="el-table-filter__list">
<li
:class="{
'is-active': filterValue === undefined || filterValue === null,
}"
class="el-table-filter__list-item"
@click="handleSelect(null)"
>
{{ t('el.table.clearFilter') }}
</li>
<li
v-for="filter in filters"
:key="filter.value"
:class="{ 'is-active': isActive(filter) }"
:label="filter.value"
class="el-table-filter__list-item"
@click="handleSelect(filter.value)"
>
{{ filter.text }}
</li>
</ul>
</div>
</template>
<template #trigger>
<span
class="el-table__column-filter-trigger el-none-outline"
@click="tooltipVisible = true"
>
<i
:class="[
'el-icon-arrow-down',
column.filterOpened ? 'el-icon-arrow-up' : '',
]"
></i>
</span>
</template>
</el-popper>
</template>
<script lang='ts'>
import { Popper as ElPopper } from '@element-plus/popper'
import { t } from '@element-plus/locale'
import { ClickOutside } from '@element-plus/directives'
import useDropdown from './dropdown'
import ElCheckbox from '@element-plus/checkbox/src/checkbox.vue'
import ElCheckboxGroup from '@element-plus/checkbox/src/checkbox-group.vue'
import ElScrollbar from '@element-plus/scrollbar/src/index'
import {
ref,
computed,
getCurrentInstance,
watch,
WritableComputedRef,
PropType,
} from 'vue'
import { Store, TableColumnCtx, TableHeader } from './table'
export default {
name: 'ElTableFilterPanel',
directives: {
ClickOutside,
},
components: {
ElCheckbox,
ElCheckboxGroup,
ElScrollbar,
ElPopper,
},
props: {
placement: {
type: String,
default: 'bottom-start',
},
store: {
type: Object as PropType<Store>,
},
column: {
type: Object as PropType<TableColumnCtx>,
},
upDataColumn: {
type: Function,
},
},
setup(props) {
const instance = getCurrentInstance()
const parent = instance.parent as TableHeader
if (!parent.filterPanels.value[props.column.id]) {
parent.filterPanels.value[props.column.id] = instance
}
const tooltipVisible = ref(false)
const { open, close } = useDropdown(instance)
const filters = computed(() => {
return props.column && props.column.filters
})
const filterValue = computed({
get: () => (props.column.filteredValue || [])[0],
set: (value: string) => {
if (filteredValue.value) {
if (typeof value !== 'undefined' && value !== null) {
filteredValue.value.splice(0, 1, value)
} else {
filteredValue.value.splice(0, 1)
}
}
},
})
const filteredValue: WritableComputedRef<unknown[]> = computed({
get() {
if (props.column) {
return props.column.filteredValue || []
}
return []
},
set(value: unknown[]) {
if (props.column) {
props.upDataColumn('filteredValue', value)
}
},
})
const multiple = computed(() => {
if (props.column) {
return props.column.filterMultiple
}
return true
})
const isActive = filter => {
return filter.value === filterValue.value
}
const handleOutsideClick = () => {
setTimeout(() => {
tooltipVisible.value = false
}, 16)
}
const handleConfirm = () => {
confirmFilter(filteredValue.value)
handleOutsideClick()
}
const handleReset = () => {
filteredValue.value = []
confirmFilter(filteredValue.value)
handleOutsideClick()
}
const handleSelect = (_filterValue?: string | string[]) => {
filterValue.value = _filterValue
if (typeof _filterValue !== 'undefined' && _filterValue !== null) {
confirmFilter(filteredValue.value)
} else {
confirmFilter([])
}
handleOutsideClick()
}
const confirmFilter = (filteredValue: unknown[]) => {
props.store.commit('filterChange', {
column: props.column,
values: filteredValue,
})
props.store.updateAllSelected()
}
watch(
tooltipVisible,
value => {
// todo
if (props.column) {
props.upDataColumn('filterOpened', value)
}
if (value) {
open()
} else {
close()
}
},
{
immediate: true,
},
)
return {
tooltipVisible,
handleOutsideClick,
multiple,
filteredValue,
filterValue,
filters,
handleConfirm,
handleReset,
handleSelect,
isActive,
t,
}
},
}
</script>
<style>
.el-table-filter-padding {
padding: 0;
border: 1px solid #ebeef5 !important;
}
.el-none-outline {
outline: none;
}
</style>

View File

@ -0,0 +1,20 @@
import { h } from 'vue'
import { TableColumnCtx } from './table'
export function hGutter() {
return h('col', {
name: 'gutter',
})
}
export function hColgroup(columns: TableColumnCtx[], hasGutter = false) {
return h('colgroup', {}, [
...columns.map(column =>
h('col', {
name: column.id,
key: column.id,
}),
),
hasGutter && hGutter(),
])
}

View File

@ -0,0 +1,73 @@
import {
onBeforeMount,
onUnmounted,
onMounted,
onUpdated,
computed,
getCurrentInstance,
} from 'vue'
import { TableLayout, TableHeader, Table } from './table'
function useLayoutObserver(root: Table) {
const instance = getCurrentInstance() as TableHeader
onBeforeMount(() => {
tableLayout.value.addObserver(instance)
})
onMounted(() => {
onColumnsChange(tableLayout.value)
onScrollableChange(tableLayout.value)
})
onUpdated(() => {
onColumnsChange(tableLayout.value)
onScrollableChange(tableLayout.value)
})
onUnmounted(() => {
tableLayout.value.removeObserver(instance)
})
const tableLayout = computed(() => {
const layout = root.layout as TableLayout
if (!layout) {
throw new Error('Can not find table layout.')
}
return layout
})
const onColumnsChange = (layout: TableLayout) => {
const cols = root.vnode.el?.querySelectorAll('colgroup > col')
if (!cols.length) return
const flattenColumns = layout.getFlattenColumns()
const columnsMap = {}
flattenColumns.forEach(column => {
columnsMap[column.id] = column
})
for (let i = 0, j = cols.length; i < j; i++) {
const col = cols[i]
const name = col.getAttribute('name')
const column = columnsMap[name]
if (column) {
col.setAttribute('width', column.realWidth || column.width)
}
}
}
const onScrollableChange = (layout: TableLayout) => {
const cols = root.vnode.el.querySelectorAll('colgroup > col[name=gutter]')
for (let i = 0, j = cols.length; i < j; i++) {
const col = cols[i]
col.setAttribute('width', layout.scrollY.value ? layout.gutterWidth : '0')
}
const ths = root.vnode.el.querySelectorAll('th.gutter')
for (let i = 0, j = ths.length; i < j; i++) {
const th = ths[i]
th.style.width = layout.scrollY.value ? layout.gutterWidth + 'px' : '0'
th.style.display = layout.scrollY.value ? '' : 'none'
}
}
return {
tableLayout: tableLayout.value,
onColumnsChange,
onScrollableChange,
}
}
export default useLayoutObserver

View File

@ -0,0 +1,83 @@
import { arrayFind } from '@element-plus/utils/util'
import { getRowIdentity } from '../util'
import { ref, getCurrentInstance, unref } from 'vue'
import { WatcherPropsData, Table, AnyObject } from '../table'
function useCurrent(watcherData: WatcherPropsData) {
const instance = getCurrentInstance() as Table
const _currentRowKey = ref(null)
const currentRow = ref(null)
const setCurrentRowKey = (key: string) => {
instance.store.assertRowKey()
_currentRowKey.value = key
setCurrentRowByKey(key)
}
const restoreCurrentRowKey = () => {
_currentRowKey.value = null
}
const setCurrentRowByKey = (key: string) => {
const { data = [], rowKey } = watcherData
let _currentRow = null
if (rowKey.value) {
_currentRow = arrayFind(
unref(data),
item => getRowIdentity(item, rowKey.value) === key,
)
}
currentRow.value = _currentRow
}
const updateCurrentRow = (_currentRow: AnyObject) => {
const oldCurrentRow = currentRow.value
if (_currentRow && _currentRow !== oldCurrentRow) {
currentRow.value = _currentRow
instance.emit('current-change', currentRow.value, oldCurrentRow)
return
}
if (!_currentRow && oldCurrentRow) {
currentRow.value = null
instance.emit('current-change', null, oldCurrentRow)
}
}
const updateCurrentRowData = () => {
const rowKey = watcherData.rowKey.value
// data 为 null 时,解构时的默认值会被忽略
const data = watcherData.data.value || []
const oldCurrentRow = currentRow.value
// 当 currentRow 不在 data 中时尝试更新数据
if (data.indexOf(oldCurrentRow) === -1 && oldCurrentRow) {
if (rowKey) {
const currentRowKey = getRowIdentity(oldCurrentRow, rowKey)
setCurrentRowByKey(currentRowKey)
} else {
currentRow.value = null
}
if (currentRow.value === null) {
instance.emit('current-change', null, oldCurrentRow)
}
} else if (_currentRowKey.value) {
// 把初始时下设置的 rowKey 转化成 rowData
setCurrentRowByKey(_currentRowKey.value)
restoreCurrentRowKey()
}
}
return {
setCurrentRowKey,
restoreCurrentRowKey,
setCurrentRowByKey,
updateCurrentRow,
updateCurrentRowData,
states: {
_currentRowKey,
currentRow,
},
}
}
export default useCurrent

View File

@ -0,0 +1,73 @@
import { ref, getCurrentInstance } from 'vue'
import { toggleRowStatus, getKeysMap, getRowIdentity } from '../util'
import { WatcherPropsData, Table } from '../table'
function useExpand(watcherData: WatcherPropsData) {
const instance = getCurrentInstance() as Table
const defaultExpandAll = ref(false)
const expandRows = ref([])
const updateExpandRows = () => {
const data = watcherData.data.value || []
const rowKey = watcherData.rowKey.value
if (defaultExpandAll.value) {
expandRows.value = data.slice()
} else if (rowKey) {
// TODO这里的代码可以优化
const expandRowsMap = getKeysMap(expandRows.value, rowKey)
expandRows.value = data.reduce((prev, row) => {
const rowId = getRowIdentity(row, rowKey)
const rowInfo = expandRowsMap[rowId]
if (rowInfo) {
prev.push(row)
}
return prev
}, [])
} else {
expandRows.value = []
}
}
const toggleRowExpansion = (row, expanded: boolean | undefined) => {
const changed = toggleRowStatus(expandRows.value, row, expanded)
if (changed) {
instance.emit('expand-change', row, expandRows.value.slice())
instance.store.scheduleLayout()
}
}
const setExpandRowKeys = (rowKeys: string[]) => {
instance.store.assertRowKey()
// TODO这里的代码可以优化
const data = watcherData.data.value || []
const rowKey = watcherData.rowKey.value
const keysMap = getKeysMap(data, rowKey)
expandRows.value = rowKeys.reduce((prev: string[], cur: string) => {
const info = keysMap[cur]
if (info) {
prev.push(info.row)
}
return prev
}, [])
}
const isRowExpanded = (row): boolean => {
const rowKey = watcherData.rowKey.value
if (rowKey) {
const expandMap = getKeysMap(expandRows.value, rowKey)
return !!expandMap[getRowIdentity(row, rowKey)]
}
return expandRows.value.indexOf(row) !== -1
}
return {
updateExpandRows,
toggleRowExpansion,
setExpandRowKeys,
isRowExpanded,
states: {
expandRows,
defaultExpandAll,
},
}
}
export default useExpand

View File

@ -0,0 +1,41 @@
import Store from './index'
import { debounce } from 'lodash'
import { Table } from '../table'
export function createStore(table: Table, initialState = {}) {
if (!table) {
throw new Error('Table is required.')
}
const store = Store()
// fix https://github.com/ElemeFE/element/issues/14075
// related pr https://github.com/ElemeFE/element/pull/14146
store.toggleAllSelection = debounce(store._toggleAllSelection, 10)
Object.keys(initialState).forEach(key => {
store.states[key].value = initialState[key]
})
return store
}
export function mapStates<T>(mapper: T): any {
const res = {}
Object.keys(mapper).forEach(key => {
const value = mapper[key]
let fn
if (typeof value === 'string') {
fn = function () {
return this.store.states[value]
}
} else if (typeof value === 'function') {
fn = function () {
return value.call(this, this.store.states)
}
} else {
console.error('invalid value type')
}
if (fn) {
res[key] = fn
}
})
return res as any
}

View File

@ -0,0 +1,173 @@
import { nextTick, getCurrentInstance, unref } from 'vue'
import { arrayFind } from '@element-plus/utils/util'
import useWatcher from './watcher'
import { Table, Store, TableColumnCtx } from '../table'
function replaceColumn(array: TableColumnCtx[], column: TableColumnCtx) {
return array.map(item => {
if (item.id === column.id) {
return column
} else if (item.children?.length > 0) {
item.children = replaceColumn(item.children, column)
}
return item
})
}
function useStore(): Store {
const instance = getCurrentInstance() as Table
const mutations = {
setData(states, data) {
const dataInstanceChanged = unref(states.data) !== data
states.data.value = data
states._data.value = data
instance.store.execQuery()
// 数据变化,更新部分数据。
// 没有使用 computed而是手动更新部分数据 https://github.com/vuejs/vue/issues/6660#issuecomment-331417140
instance.store.updateCurrentRowData()
instance.store.updateExpandRows()
if (unref(states.reserveSelection)) {
instance.store.assertRowKey()
instance.store.updateSelectionByRowKey()
} else {
if (dataInstanceChanged) {
instance.store.clearSelection()
} else {
instance.store.cleanSelection()
}
}
instance.store.updateAllSelected()
instance.store.updateTableScrollY()
},
insertColumn(states, column, index, parent) {
if (index < -1) return
const array = unref(states._columns)
if (!parent) {
array.splice(index, 0, column)
states._columns.value = array
} else {
if (parent && !parent.children) {
parent.children = []
}
parent.children.push(column)
const newColumns = replaceColumn(array, parent)
states._columns.value = newColumns
}
if (column.type === 'selection') {
states.selectable.value = column.selectable
states.reserveSelection.value = column.reserveSelection
}
if (instance.$ready) {
instance.store.updateColumns() // hack for dynamics insert column
instance.store.scheduleLayout()
}
},
removeColumn(states, column, parent) {
const array = unref(states._columns) || []
if (parent) {
parent.children.splice(
parent.children.findIndex(item => item.id === column.id),
1,
)
states._columns.value = replaceColumn(array, parent)
} else {
array.splice(array.indexOf(column), 1)
states._columns.value = array
}
if (instance.$ready) {
instance.store.updateColumns() // hack for dynamics remove column
instance.store.scheduleLayout()
}
},
sort(states, options) {
const { prop, order, init } = options
if (prop) {
const column = arrayFind(
unref(states.columns),
column => column.property === prop,
)
if (column) {
column.order = order
instance.store.updateSort(column, prop, order)
instance.store.commit('changeSortCondition', { init })
}
}
},
changeSortCondition(states, options) {
// 修复 pr https://github.com/ElemeFE/element/pull/15012 导致的 bug
const { sortingColumn: column, sortProp: prop, sortOrder: order } = states
if (unref(order) === null) {
states.sortingColumn.value = null
states.sortProp.value = null
}
const ingore = { filter: true }
instance.store.execQuery(ingore)
if (!options || !(options.silent || options.init)) {
instance.emit('sort-change', {
column: unref(column),
prop: unref(prop),
order: unref(order),
})
}
instance.store.updateTableScrollY()
},
filterChange(states, options) {
const { column, values, silent } = options
const newFilters = instance.store.updateFilters(column, values)
instance.store.execQuery()
if (!silent) {
instance.emit('filter-change', newFilters)
}
instance.store.updateTableScrollY()
},
toggleAllSelection() {
instance.store.toggleAllSelection()
},
rowSelectedChanged(states, row) {
instance.store.toggleRowSelection(row)
instance.store.updateAllSelected()
},
setHoverRow(states, row) {
states.hoverRow.value = row
},
setCurrentRow(states, row) {
instance.store.updateCurrentRow(row)
},
}
const commit = function (name, ...args) {
const mutations = instance.store.mutations
if (mutations[name]) {
mutations[name].apply(instance, [instance.store.states].concat(args))
} else {
throw new Error(`Action not found: ${name}`)
}
}
const updateTableScrollY = function () {
nextTick(instance.layout.updateScrollY.apply(instance.layout))
}
const watcher = useWatcher()
return {
...watcher,
mutations,
commit,
updateTableScrollY,
}
}
export default useStore

View File

@ -0,0 +1,200 @@
import { walkTreeNode, getRowIdentity } from '../util'
import { ref, computed, watch, getCurrentInstance, unref } from 'vue'
import { WatcherPropsData, Table, fn } from '../table'
function useTree(watcherData: WatcherPropsData) {
const expandRowKeys = ref([])
const treeData = ref({})
const indent = ref(16)
const lazy = ref(false)
const lazyTreeNodeMap = ref({})
const lazyColumnIdentifier = ref('hasChildren')
const childrenColumnName = ref('children')
const instance = getCurrentInstance() as Table
const normalizedData = computed(() => {
if (!watcherData.rowKey.value) return {}
const data = watcherData.data.value || []
return normalize(data)
})
const normalizedLazyNode = computed(() => {
const rowKey = watcherData.rowKey.value
const keys = Object.keys(lazyTreeNodeMap.value)
const res = {}
if (!keys.length) return res
keys.forEach(key => {
if (lazyTreeNodeMap.value[key].length) {
const item = { children: [] }
lazyTreeNodeMap.value[key].forEach(row => {
const currentRowKey = getRowIdentity(row, rowKey)
item.children.push(currentRowKey)
if (row[lazyColumnIdentifier.value] && !res[currentRowKey]) {
res[currentRowKey] = { children: [] }
}
})
res[key] = item
}
})
return res
})
const normalize = data => {
const rowKey = watcherData.rowKey.value
const res = {}
walkTreeNode(
data,
(parent, children, level) => {
const parentId = getRowIdentity(parent, rowKey)
if (Array.isArray(children)) {
res[parentId] = {
children: children.map(row => getRowIdentity(row, rowKey)),
level,
}
} else if (lazy.value) {
// 当 children 不存在且 lazy 为 true该节点即为懒加载的节点
res[parentId] = {
children: [],
lazy: true,
level,
}
}
},
childrenColumnName.value,
lazyColumnIdentifier.value,
)
return res
}
const updateTreeData = () => {
const nested = normalizedData.value
const normalizedLazyNode_ = normalizedLazyNode.value
const keys = Object.keys(nested)
const newTreeData = {}
if (keys.length) {
const oldTreeData = unref(treeData)
const defaultExpandAll = instance.store?.states.defaultExpandAll.value
const rootLazyRowKeys = []
const getExpanded = (oldValue, key) => {
const included =
defaultExpandAll ||
(expandRowKeys.value && expandRowKeys.value.indexOf(key) !== -1)
return !!((oldValue && oldValue.expanded) || included)
}
// 合并 expanded 与 display确保数据刷新后状态不变
keys.forEach(key => {
const oldValue = oldTreeData[key]
const newValue = { ...nested[key] }
newValue.expanded = getExpanded(oldValue, key)
if (newValue.lazy) {
const { loaded = false, loading = false } = oldValue || {}
newValue.loaded = !!loaded
newValue.loading = !!loading
rootLazyRowKeys.push(key)
}
newTreeData[key] = newValue
})
// 根据懒加载数据更新 treeData
const lazyKeys = Object.keys(normalizedLazyNode_)
if (lazy.value && lazyKeys.length && rootLazyRowKeys.length) {
lazyKeys.forEach(key => {
const oldValue = oldTreeData[key]
const lazyNodeChildren = normalizedLazyNode_[key].children
if (rootLazyRowKeys.indexOf(key) !== -1) {
// 懒加载的 root 节点,更新一下原有的数据,原来的 children 一定是空数组
if (newTreeData[key].children.length !== 0) {
throw new Error('[ElTable]children must be an empty array.')
}
newTreeData[key].children = lazyNodeChildren
} else {
const { loaded = false, loading = false } = oldValue || {}
newTreeData[key] = {
lazy: true,
loaded: !!loaded,
loading: !!loading,
expanded: getExpanded(oldValue, key),
children: lazyNodeChildren,
level: '',
}
}
})
}
}
treeData.value = newTreeData
instance.store?.updateTableScrollY()
}
watch(() => normalizedData.value, updateTreeData)
watch(() => normalizedLazyNode.value, updateTreeData)
const updateTreeExpandKeys = value => {
expandRowKeys.value = value
updateTreeData()
}
const toggleTreeExpansion = (row, expanded) => {
instance.store.assertRowKey()
const rowKey = watcherData.rowKey.value
const id = getRowIdentity(row, rowKey)
const data = id && treeData.value[id]
if (id && data && 'expanded' in data) {
const oldExpanded = data.expanded
expanded = typeof expanded === 'undefined' ? !data.expanded : expanded
treeData.value[id].expanded = expanded
if (oldExpanded !== expanded) {
instance.emit('expand-change', row, expanded)
}
instance.store.updateTableScrollY()
}
}
const loadOrToggle = row => {
instance.store.assertRowKey()
const rowKey = watcherData.rowKey.value
const id = getRowIdentity(row, rowKey)
const data = treeData.value[id]
if (lazy.value && data && 'loaded' in data && !data.loaded) {
loadData(row, id, data)
} else {
toggleTreeExpansion(row, undefined)
}
}
const loadData = (row, key, treeNode) => {
const { load } = instance.props
if (load && !treeData.value[key].loaded) {
treeData.value[key].loading = true
;(load as fn)(row, treeNode, data => {
if (!Array.isArray(data)) {
throw new Error('[ElTable] data must be an array')
}
treeData.value[key].loading = false
treeData.value[key].loaded = true
treeData.value[key].expanded = true
if (data.length) {
lazyTreeNodeMap.value[key] = data
}
instance.emit('expand-change', row, true)
})
}
}
return {
loadData,
loadOrToggle,
toggleTreeExpansion,
updateTreeExpandKeys,
updateTreeData,
normalize,
states: {
expandRowKeys,
treeData,
indent,
lazy,
lazyTreeNodeMap,
lazyColumnIdentifier,
childrenColumnName,
},
}
}
export default useTree

View File

@ -0,0 +1,494 @@
import { ref, getCurrentInstance, unref } from 'vue'
import merge from '@element-plus/utils/merge'
import {
getKeysMap,
getRowIdentity,
getColumnById,
getColumnByKey,
orderBy,
toggleRowStatus,
} from '../util'
import useExpand from './expand'
import useCurrent from './current'
import useTree from './tree'
import { AnyObject, Table } from '../table'
const sortData = (data, states) => {
const sortingColumn = states.sortingColumn
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
return data
}
return orderBy(
data,
states.sortProp,
states.sortOrder,
sortingColumn.sortMethod,
sortingColumn.sortBy,
)
}
const doFlattenColumns = columns => {
const result = []
columns.forEach(column => {
if (column.children) {
// eslint-disable-next-line prefer-spread
result.push.apply(result, doFlattenColumns(column.children))
} else {
result.push(column)
}
})
return result
}
function useWatcher() {
const instance = getCurrentInstance() as Table
const rowKey = ref(null)
const data = ref([])
const _data = ref([])
const isComplex = ref(false)
const _columns = ref([])
const originColumns = ref([])
const columns = ref([])
const fixedColumns = ref([])
const rightFixedColumns = ref([])
const leafColumns = ref([])
const fixedLeafColumns = ref([])
const rightFixedLeafColumns = ref([])
const leafColumnsLength = ref(0)
const fixedLeafColumnsLength = ref(0)
const rightFixedLeafColumnsLength = ref(0)
const isAllSelected = ref(false)
const selection = ref([])
const reserveSelection = ref(false)
const selectOnIndeterminate = ref(false)
const selectable = ref(null)
const filters = ref({})
const filteredData = ref(null)
const sortingColumn = ref(null)
const sortProp = ref(null)
const sortOrder = ref(null)
const hoverRow = ref(null)
// 检查 rowKey 是否存在
const assertRowKey = () => {
if (!rowKey.value) throw new Error('[ElTable] prop row-key is required')
}
// 更新列
const updateColumns = () => {
fixedColumns.value = _columns.value.filter(
column => column.fixed === true || column.fixed === 'left',
)
rightFixedColumns.value = _columns.value.filter(
column => column.fixed === 'right',
)
if (
fixedColumns.value.length > 0 &&
_columns.value[0] &&
_columns.value[0].type === 'selection' &&
!_columns.value[0].fixed
) {
_columns.value[0].fixed = true
fixedColumns.value.unshift(_columns.value[0])
}
const notFixedColumns = _columns.value.filter(column => !column.fixed)
originColumns.value = []
.concat(fixedColumns.value)
.concat(notFixedColumns)
.concat(rightFixedColumns.value)
const leafColumns = doFlattenColumns(notFixedColumns)
const fixedLeafColumns = doFlattenColumns(fixedColumns.value)
const rightFixedLeafColumns = doFlattenColumns(rightFixedColumns.value)
leafColumnsLength.value = leafColumns.length
fixedLeafColumnsLength.value = fixedLeafColumns.length
rightFixedLeafColumnsLength.value = rightFixedLeafColumns.length
columns.value = []
.concat(fixedLeafColumns)
.concat(leafColumns)
.concat(rightFixedLeafColumns)
isComplex.value =
fixedColumns.value.length > 0 || rightFixedColumns.value.length > 0
}
// 更新 DOM
const scheduleLayout = (needUpdateColumns: boolean, immediate = false) => {
if (needUpdateColumns) {
updateColumns()
}
if (immediate) {
instance.vnode.state.doLayout()
} else {
instance.vnode.state.debouncedUpdateLayout()
}
}
// 选择
const isSelected = row => {
return selection.value.indexOf(row) > -1
}
const clearSelection = () => {
isAllSelected.value = false
const oldSelection = selection.value
if (oldSelection.length) {
selection.value = []
instance.emit('selection-change', [])
}
}
const cleanSelection = () => {
let deleted
if (rowKey.value) {
deleted = []
const selectedMap = getKeysMap(selection.value, rowKey.value)
const dataMap = getKeysMap(data.value, rowKey.value)
for (const key in selectedMap) {
if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
deleted.push(selectedMap[key].row)
}
}
} else {
deleted = selection.value.filter(
item => data.value.indexOf(item) === -1,
)
}
if (deleted.length) {
const newSelection = selection.value.filter(
item => deleted.indexOf(item) === -1,
)
selection.value = newSelection
instance.emit('selection-change', newSelection.slice())
}
}
const toggleRowSelection = (row, selected, emitChange = true) => {
const changed = toggleRowStatus(selection.value, row, selected)
if (changed) {
const newSelection = (selection.value || []).slice()
// 调用 API 修改选中值,不触发 select 事件
if (emitChange) {
instance.emit('select', newSelection, row)
}
instance.emit('selection-change', newSelection)
}
}
const _toggleAllSelection = () => {
// when only some rows are selected (but not all), select or deselect all of them
// depending on the value of selectOnIndeterminate
const value = selectOnIndeterminate.value
? !isAllSelected.value
: !(isAllSelected.value || selection.value.length)
isAllSelected.value = value
let selectionChanged = false
data.value.forEach((row, index) => {
if (selectable.value) {
if (
selectable.value.call(null, row, index) &&
toggleRowStatus(selection.value, row, value)
) {
selectionChanged = true
}
} else {
if (toggleRowStatus(selection.value, row, value)) {
selectionChanged = true
}
}
})
if (selectionChanged) {
instance.emit(
'selection-change',
selection.value ? selection.value.slice() : [],
)
}
instance.emit('select-all', selection.value)
}
const updateSelectionByRowKey = () => {
const selectedMap = getKeysMap(selection.value, rowKey.value)
data.value.forEach(row => {
const rowId = getRowIdentity(row, rowKey.value)
const rowInfo = selectedMap[rowId]
if (rowInfo) {
selection.value[rowInfo.index] = row
}
})
}
const updateAllSelected = () => {
// data 为 null 时,解构时的默认值会被忽略
if (data.value?.length === 0) {
isAllSelected.value = false
return
}
let selectedMap
if (rowKey.value) {
selectedMap = getKeysMap(selection.value, rowKey.value)
}
const isSelected = function (row) {
if (selectedMap) {
return !!selectedMap[getRowIdentity(row, rowKey.value)]
} else {
return selection.value.indexOf(row) !== -1
}
}
let isAllSelected_ = true
let selectedCount = 0
for (let i = 0, j = (data.value || []).length; i < j; i++) {
const item = data.value[i]
const isRowSelectable =
selectable.value && selectable.value.call(null, item, i)
if (!isSelected(item)) {
if (!selectable.value || isRowSelectable) {
isAllSelected_ = false
break
}
} else {
selectedCount++
}
}
if (selectedCount === 0) isAllSelected_ = false
isAllSelected.value = isAllSelected_
}
// 过滤与排序
const updateFilters = (columns, values) => {
if (!Array.isArray(columns)) {
columns = [columns]
}
const filters_ = {}
columns.forEach(col => {
filters.value[col.id] = values
filters_[col.columnKey || col.id] = values
})
return filters_
}
const updateSort = (column, prop, order) => {
if (sortingColumn.value && sortingColumn.value !== column) {
sortingColumn.value.order = null
}
sortingColumn.value = column
sortProp.value = prop
sortOrder.value = order
}
const execFilter = () => {
let sourceData = unref(_data)
Object.keys(filters.value).forEach(columnId => {
const values = filters.value[columnId]
if (!values || values.length === 0) return
const column = getColumnById(
{
columns: columns.value,
},
columnId,
)
if (column && column.filterMethod) {
sourceData = sourceData.filter(row => {
return values.some(value =>
column.filterMethod.call(null, value, row, column),
)
})
}
})
filteredData.value = sourceData
}
const execSort = () => {
data.value = sortData(filteredData.value, {
sortingColumn: sortingColumn.value,
sortProp: sortProp.value,
sortOrder: sortOrder.value,
})
}
// 根据 filters 与 sort 去过滤 data
const execQuery = ignore => {
if (!(ignore && ignore.filter)) {
execFilter()
}
execSort()
}
const clearFilter = columnKeys => {
const {
tableHeader,
fixedTableHeader,
rightFixedTableHeader,
} = instance.refs as AnyObject
let panels = {}
if (tableHeader) panels = merge(panels, tableHeader.filterPanels)
if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels)
if (rightFixedTableHeader)
panels = merge(panels, rightFixedTableHeader.filterPanels)
const keys = Object.keys(panels)
if (!keys.length) return
if (typeof columnKeys === 'string') {
columnKeys = [columnKeys]
}
if (Array.isArray(columnKeys)) {
const columns_ = columnKeys.map(key =>
getColumnByKey(
{
columns: columns.value,
},
key,
),
)
keys.forEach(key => {
const column = columns_.find(col => col.id === key)
if (column) {
column.filteredValue = []
}
})
instance.store.commit('filterChange', {
column: columns_,
values: [],
silent: true,
multi: true,
})
} else {
keys.forEach(key => {
const column = columns.value.find(col => col.id === key)
if (column) {
column.filteredValue = []
}
})
filters.value = {}
instance.store.commit('filterChange', {
column: {},
values: [],
silent: true,
})
}
}
const clearSort = () => {
if (!sortingColumn.value) return
updateSort(null, null, null)
instance.store.commit('changeSortCondition', {
silent: true,
})
}
const {
setExpandRowKeys,
toggleRowExpansion,
updateExpandRows,
states: expandStates,
isRowExpanded,
} = useExpand({
data,
rowKey,
})
const {
updateTreeExpandKeys,
toggleTreeExpansion,
loadOrToggle,
states: treeStates,
} = useTree({
data,
rowKey,
})
const {
updateCurrentRowData,
updateCurrentRow,
setCurrentRowKey,
states: currentData,
} = useCurrent({
data,
rowKey,
})
// 适配层expand-row-keys 在 Expand 与 TreeTable 中都有使用
const setExpandRowKeysAdapter = val => {
// 这里会触发额外的计算,但为了兼容性,暂时这么做
setExpandRowKeys(val)
updateTreeExpandKeys(val)
}
// 展开行与 TreeTable 都要使用
const toggleRowExpansionAdapter = (row, expanded) => {
const hasExpandColumn = columns.value.some(({ type }) => type === 'expand')
if (hasExpandColumn) {
toggleRowExpansion(row, expanded)
} else {
toggleTreeExpansion(row, expanded)
}
}
return {
assertRowKey,
updateColumns,
scheduleLayout,
isSelected,
clearSelection,
cleanSelection,
toggleRowSelection,
_toggleAllSelection,
updateSelectionByRowKey,
updateAllSelected,
updateFilters,
updateCurrentRow,
updateSort,
execFilter,
execSort,
execQuery,
clearFilter,
clearSort,
toggleRowExpansion,
setExpandRowKeysAdapter,
setCurrentRowKey,
toggleRowExpansionAdapter,
isRowExpanded,
updateExpandRows,
updateCurrentRowData,
loadOrToggle,
states: {
rowKey,
data,
_data,
isComplex,
_columns,
originColumns,
columns,
fixedColumns,
rightFixedColumns,
leafColumns,
fixedLeafColumns,
rightFixedLeafColumns,
leafColumnsLength,
fixedLeafColumnsLength,
rightFixedLeafColumnsLength,
isAllSelected,
selection,
reserveSelection,
selectOnIndeterminate,
selectable,
filters,
filteredData,
sortingColumn,
sortProp,
sortOrder,
hoverRow,
...expandStates,
...treeStates,
...currentData,
},
}
}
export default useWatcher

View File

@ -0,0 +1,124 @@
import { getCurrentInstance, VNode, ref, h } from 'vue'
import { getStyle, hasClass } from '@element-plus/utils/dom'
import { getCell, getColumnByCell } from '../util'
import { debounce } from 'lodash'
import { TableBodyProps } from './table-body'
import { Table, AnyObject, TableColumnCtx } from '../table'
function useEvents(props: TableBodyProps) {
const instance = getCurrentInstance()
const parent = instance.parent as Table
const tooltipVisible = ref(false)
const tooltipContent = ref('')
const tooltipTrigger = ref(h('div'))
const handleEvent = (event: Event, row: AnyObject, name: string) => {
const table = parent
const cell = getCell(event)
let column: TableColumnCtx
if (cell) {
column = getColumnByCell(
{
columns: props.store.states.columns.value,
},
cell,
)
if (column) {
table.emit(`cell-${name}`, row, column, cell, event)
}
}
table.emit(`row-${name}`, row, column, event)
}
const handleDoubleClick = (event: Event, row: AnyObject) => {
handleEvent(event, row, 'dblclick')
}
const handleClick = (event: Event, row: AnyObject) => {
props.store.commit('setCurrentRow', row)
handleEvent(event, row, 'click')
}
const handleContextMenu = (event: Event, row: AnyObject) => {
handleEvent(event, row, 'contextmenu')
}
const handleMouseEnter = debounce(function (index: number) {
props.store.commit('setHoverRow', index)
}, 30)
const handleMouseLeave = debounce(function () {
props.store.commit('setHoverRow', null)
}, 30)
const handleCellMouseEnter = (event: MouseEvent, row: AnyObject) => {
const table = parent
const cell = getCell(event)
if (cell) {
const column = getColumnByCell(
{
columns: props.store.states.columns.value,
},
cell,
)
const hoverState = (table.hoverState = { cell, column, row })
table.emit(
'cell-mouse-enter',
hoverState.row,
hoverState.column,
hoverState.cell,
event,
)
}
// 判断是否text-overflow, 如果是就显示tooltip
const cellChild = (event.target as HTMLElement).querySelector(
'.cell',
) as HTMLElement
if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
return
}
// use range width instead of scrollWidth to determine whether the text is overflowing
// to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
const range = document.createRange()
range.setStart(cellChild, 0)
range.setEnd(cellChild, cellChild.childNodes.length)
const rangeWidth = range.getBoundingClientRect().width
const padding =
(parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
(parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0)
if (
rangeWidth + padding > cellChild.offsetWidth ||
cellChild.scrollWidth > cellChild.offsetWidth
) {
// TODO 会引起整个 Table 的重新渲染,需要优化
tooltipContent.value = cell.innerText || cell.textContent
tooltipVisible.value = true
// TODO 动态绑定触发的元素会导致开发模式下产生警告
tooltipTrigger.value = (cell as unknown) as VNode
}
}
const handleCellMouseLeave = event => {
tooltipVisible.value = false
const cell = getCell(event)
if (!cell) return
const oldHoverState = parent.hoverState
parent.emit(
'cell-mouse-leave',
oldHoverState?.row,
oldHoverState?.column,
oldHoverState?.cell,
event,
)
}
return {
handleDoubleClick,
handleClick,
handleContextMenu,
handleMouseEnter,
handleMouseLeave,
handleCellMouseEnter,
handleCellMouseLeave,
tooltipVisible,
tooltipContent,
tooltipTrigger,
}
}
export default useEvents

View File

@ -0,0 +1,106 @@
import { defineComponent, getCurrentInstance, h, watch, PropType } from 'vue'
import useLayoutObserver from '../layout-observer'
import { removeClass, addClass } from '@element-plus/utils/dom'
import isServer from '@element-plus/utils/isServer'
// import ElTooltip from '@element-plus/tooltip/src/index'
import { Table, Store } from '../table'
import { TableBodyProps } from './table-body'
import useRender from './render-helper'
import { hColgroup } from '../h-helper'
export default defineComponent({
name: 'ElTableBody',
props: {
store: {
required: true,
type: Object as PropType<Store>,
},
stripe: Boolean,
context: {
default: () => ({}),
type: Object,
},
rowClassName: [String, Function],
rowStyle: [Object, Function],
fixed: {
type: String,
default: '',
},
highlight: Boolean,
},
setup(props) {
const instance = getCurrentInstance()
const parent = instance.parent as Table
const {
wrappedRowRender,
tooltipVisible,
tooltipContent,
tooltipTrigger,
} = useRender(props as TableBodyProps)
const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent)
watch(
props.store.states.hoverRow,
(newVal: number | null, oldVal: number | null) => {
if (!props.store.states.isComplex.value || isServer) return
let raf = window.requestAnimationFrame
if (!raf) {
raf = fn => window.setTimeout(fn, 16)
}
raf(() => {
const rows = instance.vnode.el.querySelectorAll('.el-table__row')
const oldRow = rows[oldVal]
const newRow = rows[newVal]
if (oldRow) {
removeClass(oldRow, 'hover-row')
}
if (newRow) {
addClass(newRow, 'hover-row')
}
})
},
)
return {
onColumnsChange,
onScrollableChange,
wrappedRowRender,
tooltipVisible,
tooltipContent,
tooltipTrigger,
}
},
render() {
const data = this.store.states.data.value || []
return h(
'table',
{
class: 'el-table__body',
cellspacing: '0',
cellpadding: '0',
border: '0',
},
[
hColgroup(this.store.states.columns.value),
h('tbody', {}, [
data.reduce((acc, row) => {
return acc.concat(this.wrappedRowRender(row, acc.length))
}, []),
// h(
// ElTooltip,
// {
// modelValue: this.tooltipVisible,
// content: this.tooltipContent,
// manual: true,
// effect: this.$parent.tooltipEffect,
// placement: 'top',
// },
// {
// default: () => this.tooltipTrigger,
// },
// ),
]),
],
)
},
})

View File

@ -0,0 +1,249 @@
import { h, getCurrentInstance, computed } from 'vue'
import useEvents from './events-helper'
import useStyles from './styles-helper'
import { arrayFindIndex } from '@element-plus/utils/util'
import { getRowIdentity } from '../util'
import { TableBodyProps } from './table-body'
import { RenderRowData, AnyObject, Table } from '../table'
function useRender(props: TableBodyProps) {
const instance = getCurrentInstance()
const parent = instance.parent as Table
const {
handleDoubleClick,
handleClick,
handleContextMenu,
handleMouseEnter,
handleMouseLeave,
handleCellMouseEnter,
handleCellMouseLeave,
tooltipVisible,
tooltipContent,
tooltipTrigger,
} = useEvents(props)
const {
getRowStyle,
getRowClass,
getCellStyle,
getCellClass,
getSpan,
getColspanRealWidth,
} = useStyles(props)
const firstDefaultColumnIndex = computed(() => {
return arrayFindIndex(
props.store.states.columns.value,
({ type }) => type === 'default',
)
})
const getKeyOfRow = (row: AnyObject, index: number) => {
const rowKey = parent.props.rowKey as string
if (rowKey) {
return getRowIdentity(row, rowKey)
}
return index
}
const rowRender = (row, index_, treeRowData) => {
const { indent, columns } = props.store.states
const rowClasses = getRowClass(row, index_)
let display = true
if (treeRowData) {
rowClasses.push('el-table__row--level-' + treeRowData.level)
display = treeRowData.display
}
const displayStyle = display
? null
: {
display: 'none',
}
return h(
'tr',
{
style: [displayStyle, getRowStyle(row, index_)],
class: rowClasses,
key: getKeyOfRow(row, index_),
onDblclick: $event => handleDoubleClick($event, row),
onClick: $event => handleClick($event, row),
onContextmenu: $event => handleContextMenu($event, row),
onMouseenter: () => handleMouseEnter(index_),
onMouseleave: handleMouseLeave,
},
columns.value.map((column, cellIndex) => {
const { rowspan, colspan } = getSpan(row, column, index_, cellIndex)
if (!rowspan || !colspan) {
return null
}
const columnData = { ...column }
columnData.realWidth = getColspanRealWidth(
columns.value,
colspan,
cellIndex,
)
// debugger;
const data: RenderRowData = {
store: props.store,
_self: props.context || parent,
column: columnData,
row,
index_,
}
if (cellIndex === firstDefaultColumnIndex.value && treeRowData) {
data.treeNode = {
indent: treeRowData.level * indent.value,
level: treeRowData.level,
}
if (typeof treeRowData.expanded === 'boolean') {
data.treeNode.expanded = treeRowData.expanded
// 表明是懒加载
if ('loading' in treeRowData) {
data.treeNode.loading = treeRowData.loading
}
if ('noLazyChildren' in treeRowData) {
data.treeNode.noLazyChildren = treeRowData.noLazyChildren
}
}
}
return h(
'td',
{
style: getCellStyle(index_, cellIndex, row, column),
class: getCellClass(index_, cellIndex, row, column),
rowspan,
colspan,
onMouseenter: $event => handleCellMouseEnter($event, row),
onMouseleave: handleCellMouseLeave,
},
[column.renderCell(data)],
)
}),
)
}
const wrappedRowRender = (row, index_) => {
const store = props.store as any
const { isRowExpanded, assertRowKey } = store
const {
treeData,
lazyTreeNodeMap,
childrenColumnName,
rowKey,
} = store.states
const hasExpandColumn = store.states.columns.value.some(
({ type }) => type === 'expand',
)
if (hasExpandColumn && isRowExpanded(row)) {
const renderExpanded = parent.renderExpanded
const tr = rowRender(row, index_, undefined)
if (!renderExpanded) {
console.error('[Element Error]renderExpanded is required.')
return tr
}
// 使用二维数组,避免修改 index_
return [
[
tr,
h(
'tr',
{
key: 'expanded-row__' + tr.key,
},
[
h(
'td',
{
colspan: store.states.columns.value.length,
class: 'el-table__expanded-cell',
},
[renderExpanded({ row, index_, store })],
),
],
),
],
]
} else if (Object.keys(treeData.value).length) {
assertRowKey()
// TreeTable 时rowKey 必须由用户设定,不使用 getKeyOfRow 计算
// 在调用 rowRender 函数时,仍然会计算 rowKey不太好的操作
const key = getRowIdentity(row, rowKey.value)
let cur = treeData.value[key]
let treeRowData = null
if (cur) {
treeRowData = {
expanded: cur.expanded,
level: cur.level,
display: true,
}
if (typeof cur.lazy === 'boolean') {
if (typeof cur.loaded === 'boolean' && cur.loaded) {
treeRowData.noLazyChildren = !(cur.children && cur.children.length)
}
treeRowData.loading = cur.loading
}
}
const tmp = [rowRender(row, index_, treeRowData)]
// 渲染嵌套数据
if (cur) {
// currentRow 记录的是 index所以还需主动增加 TreeTable 的 index
let i = 0
const traverse = (children, parent) => {
if (!(children && children.length && parent)) return
children.forEach(node => {
// 父节点的 display 状态影响子节点的显示状态
const innerTreeRowData = {
display: parent.display && parent.expanded,
level: parent.level + 1,
expanded: false,
noLazyChildren: false,
loading: false,
}
const childKey = getRowIdentity(node, rowKey.value)
if (childKey === undefined || childKey === null) {
throw new Error('for nested data item, row-key is required.')
}
cur = { ...treeData.value[childKey] }
// 对于当前节点,分成有无子节点两种情况。
// 如果包含子节点的,设置 expanded 属性。
// 对于它子节点的 display 属性由它本身的 expanded 与 display 共同决定。
if (cur) {
innerTreeRowData.expanded = cur.expanded
// 懒加载的某些节点level 未知
cur.level = cur.level || innerTreeRowData.level
cur.display = !!(cur.expanded && innerTreeRowData.display)
if (typeof cur.lazy === 'boolean') {
if (typeof cur.loaded === 'boolean' && cur.loaded) {
innerTreeRowData.noLazyChildren = !(
cur.children && cur.children.length
)
}
innerTreeRowData.loading = cur.loading
}
}
i++
tmp.push(rowRender(node, index_ + i, innerTreeRowData))
if (cur) {
const nodes =
lazyTreeNodeMap.value[childKey] ||
node[childrenColumnName.value]
traverse(nodes, cur)
}
})
}
// 对于 root 节点display 一定为 true
cur.display = true
const nodes =
lazyTreeNodeMap.value[key] || row[childrenColumnName.value]
traverse(nodes, cur)
}
return tmp
} else {
return rowRender(row, index_, undefined)
}
}
return {
wrappedRowRender,
tooltipVisible,
tooltipContent,
tooltipTrigger,
}
}
export default useRender

View File

@ -0,0 +1,165 @@
import { getCurrentInstance } from 'vue'
import { TableBodyProps } from './table-body'
import { Table, AnyObject, TableColumnCtx } from '../table'
function useStyles(props: TableBodyProps) {
const instance = getCurrentInstance()
const parent = instance.parent as Table
const isColumnHidden = index => {
if (props.fixed === 'left') {
return index >= props.store.states.fixedLeafColumnsLength.value
} else if (props.fixed === 'right') {
return (
index <
props.store.states.columns.value.length -
props.store.states.rightFixedLeafColumnsLength.value
)
} else {
return (
index < props.store.states.fixedLeafColumnsLength.value ||
index >=
props.store.states.columns.value.length -
props.store.states.rightFixedLeafColumnsLength.value
)
}
}
const getRowStyle = (row: AnyObject, rowIndex: number) => {
const rowStyle = parent.props.rowStyle
if (typeof rowStyle === 'function') {
return rowStyle.call(null, {
row,
rowIndex,
})
}
return rowStyle || null
}
const getRowClass = (row: AnyObject, rowIndex: number) => {
const classes = ['el-table__row']
if (
parent.props.highlightCurrentRow &&
row === props.store.states.currentRow.value
) {
classes.push('current-row')
}
if (props.stripe && rowIndex % 2 === 1) {
classes.push('el-table__row--striped')
}
const rowClassName = parent.props.rowClassName
if (typeof rowClassName === 'string') {
classes.push(rowClassName)
} else if (typeof rowClassName === 'function') {
classes.push(
rowClassName.call(null, {
row,
rowIndex,
}),
)
}
if (props.store.states.expandRows.value.indexOf(row) > -1) {
classes.push('expanded')
}
return classes
}
const getCellStyle = (
rowIndex: number,
columnIndex: number,
row: AnyObject,
column: TableColumnCtx,
) => {
const cellStyle = parent.props.cellStyle
if (typeof cellStyle === 'function') {
return cellStyle.call(null, {
rowIndex,
columnIndex,
row,
column,
})
}
return cellStyle
}
const getCellClass = (
rowIndex: number,
columnIndex: number,
row: AnyObject,
column: TableColumnCtx,
) => {
const classes = [column.id, column.align, column.className]
if (isColumnHidden(columnIndex)) {
classes.push('is-hidden')
}
const cellClassName = parent.props.cellClassName
if (typeof cellClassName === 'string') {
classes.push(cellClassName)
} else if (typeof cellClassName === 'function') {
classes.push(
cellClassName.call(null, {
rowIndex,
columnIndex,
row,
column,
}),
)
}
return classes.join(' ')
}
const getSpan = (
row: AnyObject,
column: TableColumnCtx,
rowIndex: number,
columnIndex: number,
) => {
let rowspan = 1
let colspan = 1
const fn = parent.props.spanMethod
if (typeof fn === 'function') {
const result = fn({
row,
column,
rowIndex,
columnIndex,
})
if (Array.isArray(result)) {
rowspan = result[0]
colspan = result[1]
} else if (typeof result === 'object') {
rowspan = result.rowspan
colspan = result.colspan
}
}
return { rowspan, colspan }
}
const getColspanRealWidth = (
columns: TableColumnCtx[],
colspan: number,
index: number,
) => {
if (colspan < 1) {
return columns[index].realWidth
}
const widthArr = columns
.map(({ realWidth }) => realWidth)
.slice(index, index + colspan)
return widthArr.reduce((acc, width) => acc + width, -1)
}
return {
getRowStyle,
getRowClass,
getCellStyle,
getCellClass,
getSpan,
getColspanRealWidth,
isColumnHidden,
}
}
export default useStyles

View File

@ -0,0 +1,15 @@
import { Store, AnyObject, fn } from '../table'
interface TableBodyProps {
store: Store
stripe?: boolean
context: AnyObject
rowClassName: string | fn
rowStyle: AnyObject | fn
fixed: string
highlight: boolean
}
export {
TableBodyProps,
}

View File

@ -0,0 +1,225 @@
import {
defineComponent,
ref,
onBeforeMount,
onMounted,
computed,
getCurrentInstance,
h,
} from 'vue'
import { cellStarts } from '../config'
import { mergeOptions, compose } from '../util'
import ElCheckbox from '@element-plus/checkbox/src/checkbox.vue'
import { TableColumnCtx, TableColumn } from '../table'
import useWatcher from './watcher-helper'
import useRender from './render-helper'
let columnIdSeed = 1
export default defineComponent({
name: 'ElTableColumn',
components: {
ElCheckbox,
},
props: {
type: {
type: String,
default: 'default',
},
label: String,
className: String,
labelClassName: String,
property: String,
prop: String,
width: {
type: [Object, Number, String],
default: () => {
return {}
},
},
minWidth: {
type: [Object, Number, String],
default: () => {
return {}
},
},
renderHeader: Function,
sortable: {
type: [Boolean, String],
default: false,
},
sortMethod: Function,
sortBy: [String, Function, Array],
resizable: {
type: Boolean,
default: true,
},
columnKey: String,
align: String,
headerAlign: String,
showTooltipWhenOverflow: Boolean,
showOverflowTooltip: Boolean,
fixed: [Boolean, String],
formatter: Function,
selectable: Function,
reserveSelection: Boolean,
filterMethod: Function,
filteredValue: Array,
filters: Array,
filterPlacement: String,
filterMultiple: {
type: Boolean,
default: true,
},
index: [Number, Function],
sortOrders: {
type: Array,
default() {
return ['ascending', 'descending', null]
},
validator(val: unknown[]) {
return val.every(
(order: string) =>
['ascending', 'descending', null].indexOf(order) > -1,
)
},
},
},
setup(prop, { slots }) {
const instance = getCurrentInstance() as TableColumn
const columnConfig = ref({})
const props = (prop as unknown) as TableColumnCtx
const row = ref({})
const r = ref({})
const index_ = ref(0)
const owner = computed(() => {
let parent = instance.parent as any
while (parent && !parent.tableId) {
parent = parent.parent
}
return parent
})
const { registerNormalWatchers, registerComplexWatchers } = useWatcher(
owner,
props,
)
const {
columnId,
isSubColumn,
realHeaderAlign,
columnOrTableParent,
setColumnWidth,
setColumnForcedProps,
setColumnRenders,
getPropsData,
getColumnElIndex,
realAlign,
} = useRender(props, slots, owner)
const parent = columnOrTableParent.value
columnId.value =
(parent.tableId || parent.columnId) + '_column_' + columnIdSeed++
onBeforeMount(() => {
isSubColumn.value = owner.value !== parent
const type = props.type || 'default'
const sortable = props.sortable === '' ? true : props.sortable
const defaults = {
...cellStarts[type],
id: columnId.value,
type: type,
property: props.prop || props.property,
align: realAlign,
headerAlign: realHeaderAlign,
showOverflowTooltip:
props.showOverflowTooltip || props.showTooltipWhenOverflow,
// filter 相关属性
filterable: props.filters || props.filterMethod,
filteredValue: [],
filterPlacement: '',
isColumnGroup: false,
filterOpened: false,
// sort 相关属性
sortable: sortable,
// index 列
index: props.index,
}
const basicProps = [
'columnKey',
'label',
'className',
'labelClassName',
'type',
'renderHeader',
'formatter',
'fixed',
'resizable',
]
const sortProps = ['sortMethod', 'sortBy', 'sortOrders']
const selectProps = ['selectable', 'reserveSelection']
const filterProps = [
'filterMethod',
'filters',
'filterMultiple',
'filterOpened',
'filteredValue',
'filterPlacement',
]
let column = getPropsData(basicProps, sortProps, selectProps, filterProps)
column = mergeOptions(defaults, column)
// 注意 compose 中函数执行的顺序是从右到左
const chains = compose(
setColumnRenders,
setColumnWidth,
setColumnForcedProps,
)
column = chains(column)
columnConfig.value = column
// 注册 watcher
registerNormalWatchers()
registerComplexWatchers()
})
onMounted(() => {
const parent = columnOrTableParent.value
const children = isSubColumn.value
? parent.vnode.el.children
: parent.refs.hiddenColumns?.children
const columnIndex = getColumnElIndex(children || [], instance.vnode.el)
owner.value.store.commit(
'insertColumn',
columnConfig.value,
columnIndex,
isSubColumn.value ? parent.columnConfig.value : null,
)
})
instance.columnId = columnId.value
// eslint-disable-next-line
instance.columnConfig = columnConfig
return {
row,
r,
index_,
columnId,
columnConfig,
}
},
render() {
return h(
'div',
this.$slots.default?.({
store: {},
_self: {},
column: {},
row: {},
index_: undefined,
}),
)
},
})

View File

@ -0,0 +1,163 @@
import {
getCurrentInstance,
h,
ComputedRef,
ref,
computed,
watchEffect,
} from 'vue'
import { cellForced, defaultRenderCell, treeCellPrefix } from '../config'
import { parseWidth, parseMinWidth } from '../util'
import { TableColumn, TableColumnCtx } from '../table'
function useRender(props: TableColumnCtx, slots, owner: ComputedRef<any>) {
const instance = (getCurrentInstance() as unknown) as TableColumn
const columnId = ref('')
const isSubColumn = ref(false)
const realAlign = ref<string>()
const realHeaderAlign = ref<string>()
watchEffect(() => {
realAlign.value = !!props.align ? 'is-' + props.align : null
// nextline help render
realAlign.value
})
watchEffect(() => {
realHeaderAlign.value = !!props.headerAlign
? 'is-' + props.headerAlign
: realAlign.value
// nextline help render
realHeaderAlign.value
})
const columnOrTableParent = computed(() => {
let parent = (instance.vnode as any).vParent || (instance.parent as any)
while (parent && !parent.tableId && !parent.columnId) {
parent = (parent.vnode as any).vParent || (parent.parent as any)
}
return parent
})
const realWidth = ref(parseWidth(props.width))
const realMinWidth = ref(parseMinWidth(props.minWidth))
const setColumnWidth = column => {
if (realWidth.value) column.width = realWidth.value
if (realMinWidth.value) {
column.minWidth = realMinWidth.value
}
if (!column.minWidth) {
column.minWidth = 80
}
column.realWidth =
column.width === undefined ? column.minWidth : column.width
return column
}
const setColumnForcedProps = column => {
// 对于特定类型的 column某些属性不允许设置
const type = column.type
const source = cellForced[type] || {}
Object.keys(source).forEach(prop => {
const value = source[prop]
if (value !== undefined) {
column[prop] = prop === 'className' ? `${column[prop]} ${value}` : value
}
})
return column
}
const checkSubColumn = children => {
if (children instanceof Array) {
children.forEach(child => check(child))
} else {
check(children)
}
function check(item) {
if (item?.type?.name === 'ElTableColumn') {
item.vParent = instance
}
}
}
const setColumnRenders = column => {
// renderHeader 属性不推荐使用。
if (props.renderHeader) {
console.warn(
'[Element Warn][TableColumn]Comparing to render-header, scoped-slot header is easier to use. We recommend users to use scoped-slot header.',
)
} else if (column.type !== 'selection') {
column.renderHeader = scope => {
// help render
instance.columnConfig.value['label']
const renderHeader = slots.header
return renderHeader ? renderHeader(scope) : column.label
}
}
let originRenderCell = column.renderCell
// TODO: 这里的实现调整
if (column.type === 'expand') {
// 对于展开行renderCell 不允许配置的。在上一步中已经设置过,这里需要简单封装一下。
column.renderCell = data =>
h(
'div',
{
class: 'cell',
},
[originRenderCell(data)],
)
owner.value.renderExpanded = data => {
return slots.default ? slots.default(data) : slots.default
}
} else {
originRenderCell = originRenderCell || defaultRenderCell
// 对 renderCell 进行包装
column.renderCell = data => {
let children = null
if (slots.default) {
children = slots.default(data)
} else {
children = originRenderCell(data)
}
const prefix = treeCellPrefix(data)
const props = {
class: 'cell',
style: {},
}
if (column.showOverflowTooltip) {
props.class += ' el-tooltip'
props.style = {
width: (data.column.realWidth || data.column.width) - 1 + 'px',
}
}
checkSubColumn(children)
return h('div', props, [prefix, children])
}
}
return column
}
const getPropsData = (...propsKey: unknown[]) => {
return propsKey.reduce((prev, cur) => {
if (Array.isArray(cur)) {
cur.forEach(key => {
prev[key] = props[key]
})
}
return prev
}, {})
}
const getColumnElIndex = (children, child) => {
return [].indexOf.call(children, child)
}
return {
columnId,
realAlign,
isSubColumn,
realHeaderAlign,
columnOrTableParent,
setColumnWidth,
setColumnForcedProps,
setColumnRenders,
getPropsData,
getColumnElIndex,
}
}
export default useRender

View File

@ -0,0 +1,73 @@
import { watch, getCurrentInstance, ComputedRef } from 'vue'
import { TableColumnCtx, TableColumn } from '../table'
function useWatcher(owner: ComputedRef<any>, props_: TableColumnCtx) {
const instance = (getCurrentInstance() as unknown) as TableColumn
const registerComplexWatchers = () => {
const props = ['fixed']
const aliases = {
realWidth: 'width',
realMinWidth: 'minWidth',
}
const allAliases = props.reduce((prev, cur) => {
prev[cur] = cur
return prev
}, aliases)
Object.keys(allAliases).forEach(key => {
const columnKey = aliases[key]
if (props_.hasOwnProperty(columnKey)) {
watch(
() => props_[columnKey],
newVal => {
instance.columnConfig.value[columnKey] = newVal
const updateColumns = columnKey === 'fixed'
owner.value.store.scheduleLayout(updateColumns)
},
)
}
})
}
const registerNormalWatchers = () => {
const props = [
'label',
'property',
'filters',
'filterMultiple',
'sortable',
'index',
'formatter',
'className',
'labelClassName',
'showOverflowTooltip',
]
// 一些属性具有别名
const aliases = {
prop: 'property',
realAlign: 'align',
realHeaderAlign: 'headerAlign',
}
const allAliases = props.reduce((prev, cur) => {
prev[cur] = cur
return prev
}, aliases)
Object.keys(allAliases).forEach(key => {
const columnKey = aliases[key]
if (props_.hasOwnProperty(columnKey)) {
watch(
() => props_[columnKey],
newVal => {
instance.columnConfig.value[columnKey] = newVal
},
)
}
})
}
return {
registerComplexWatchers,
registerNormalWatchers,
}
}
export default useWatcher

View File

@ -0,0 +1,126 @@
import { defineComponent, h } from 'vue'
import { TableFooter } from '../table'
import { hGutter, hColgroup } from '../h-helper'
import useStyle from './style-helper'
export default defineComponent({
name: 'ElTableFooter',
props: {
fixed: {
type: String,
default: '',
},
store: {
required: true,
type: Object,
},
summaryMethod: Function,
sumText: String,
border: Boolean,
defaultSort: {
type: Object,
default() {
return {
prop: '',
order: '',
}
},
},
},
setup(props: TableFooter) {
const {
hasGutter,
getRowClasses,
columns,
} = useStyle(props)
return {
getRowClasses,
hasGutter,
columns,
}
},
render() {
let sums = []
if (this.summaryMethod) {
sums = this.summaryMethod({ columns: this.columns, data: this.store.states.data.value })
} else {
this.columns.forEach((column, index) => {
if (index === 0) {
sums[index] = this.sumText
return
}
const values = this.store.states.data.value.map(item => Number(item[column.property]))
const precisions = []
let notNumber = true
values.forEach(value => {
if (!isNaN(value)) {
notNumber = false
const decimal = ('' + value).split('.')[1]
precisions.push(decimal ? decimal.length : 0)
}
})
const precision = Math.max.apply(null, precisions)
if (!notNumber) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr)
if (!isNaN(value)) {
return parseFloat((prev + curr).toFixed(Math.min(precision, 20)))
} else {
return prev
}
}, 0)
} else {
sums[index] = ''
}
})
}
return h(
'table',
{
class: 'el-table__footer',
cellspacing: '0',
cellpadding: '0',
border: '0',
},
[
hColgroup(this.columns, this.hasGutter),
h(
'tbody',
{
class: [{ 'has-gutter': this.hasGutter }],
},
[
h(
'tr',
{},
[
...this.columns.map((column, cellIndex) => h(
'td',
{
key: cellIndex,
colspan: column.colSpan,
rowspan: column.rowSpan,
class: this.getRowClasses(column, cellIndex),
},
[
h(
'div',
{
class: ['cell', column.labelClassName],
},
[
sums[cellIndex],
],
),
],
)),
this.hasGutter && hGutter(),
],
),
],
),
],
)
},
})

View File

@ -0,0 +1,34 @@
import { computed, getCurrentInstance } from 'vue'
import { Table } from '../table'
function useMapState() {
const instance = getCurrentInstance()
const table = instance.parent as Table
const store = table.store
const leftFixedLeafCount = computed(() => {
return store.states.fixedLeafColumnsLength.value
})
const rightFixedLeafCount = computed(() => {
return store.states.rightFixedColumns.value.length
})
const columnsCount = computed(() => {
return store.states.columns.value.length
})
const leftFixedCount = computed(() => {
return store.states.fixedColumns.value.length
})
const rightFixedCount = computed(() => {
return store.states.rightFixedColumns.value.length
})
return {
leftFixedLeafCount,
rightFixedLeafCount,
columnsCount,
leftFixedCount,
rightFixedCount,
columns: store.states.columns,
}
}
export default useMapState

View File

@ -0,0 +1,66 @@
import { computed, getCurrentInstance } from 'vue'
import { Table, TableFooter, TableColumnCtx } from '../table'
import useMapState from './mapState-helper'
function useStyle(props: TableFooter) {
const instance = getCurrentInstance()
const table = instance.parent as Table
const store = table.store
const {
leftFixedLeafCount,
rightFixedLeafCount,
columnsCount,
leftFixedCount,
rightFixedCount,
columns,
} = useMapState()
const hasGutter = computed(() => {
return !props.fixed && table.layout.gutterWidth
})
const isCellHidden = (
index: number,
columns: TableColumnCtx[],
column: TableColumnCtx,
) => {
if (props.fixed || props.fixed === 'left') {
return index >= leftFixedLeafCount.value
} else if (props.fixed === 'right') {
let before = 0
for (let i = 0; i < index; i++) {
before += columns[i].colSpan
}
return before < columnsCount.value - rightFixedLeafCount.value
} else if (!props.fixed && column.fixed) {
// hide cell when footer instance is not fixed and column is fixed
return true
} else {
return (
index < leftFixedCount.value ||
index >= columnsCount.value - rightFixedCount.value
)
}
}
const getRowClasses = (column: TableColumnCtx, cellIndex: number) => {
const classes = [column.id, column.align, column.labelClassName]
if (column.className) {
classes.push(column.className)
}
if (isCellHidden(cellIndex, store.states.columns.value, column)) {
classes.push('is-hidden')
}
if (!column.children) {
classes.push('is-leaf')
}
return classes
}
return {
hasGutter,
getRowClasses,
columns,
}
}
export default useStyle

View File

@ -0,0 +1,208 @@
import { getCurrentInstance, ref } from 'vue'
import { hasClass, addClass, removeClass } from '@element-plus/utils/dom'
import isServer from '@element-plus/utils/isServer'
import { TableColumnCtx, Table } from '../table'
import { TableHeaderProps } from './table-header'
function useEvent(props: TableHeaderProps, emit) {
const instance = getCurrentInstance()
const parent = instance.parent as Table
const handleFilterClick = (event: Event) => {
event.stopPropagation()
return
}
const handleHeaderClick = (event: Event, column: TableColumnCtx) => {
if (!column.filters && column.sortable) {
handleSortClick(event, column, false)
} else if (column.filterable && !column.sortable) {
handleFilterClick(event)
}
parent.emit('header-click', column, event)
}
const handleHeaderContextMenu = (event: Event, column: TableColumnCtx) => {
parent.emit('header-contextmenu', column, event)
}
const draggingColumn = ref(null)
const dragging = ref(false)
const dragState = ref({})
const handleMouseDown = (event: MouseEvent, column: TableColumnCtx) => {
if (isServer) return
if (column.children && column.children.length > 0) return
/* istanbul ignore if */
if (draggingColumn.value && props.border) {
dragging.value = true
const table = parent
emit('set-drag-visible', true)
const tableEl = table.vnode.el
const tableLeft = tableEl.getBoundingClientRect().left
const columnEl = instance.vnode.el.querySelector(`th.${column.id}`)
const columnRect = columnEl.getBoundingClientRect()
const minLeft = columnRect.left - tableLeft + 30
addClass(columnEl, 'noclick')
dragState.value = {
startMouseLeft: event.clientX,
startLeft: columnRect.right - tableLeft,
startColumnLeft: columnRect.left - tableLeft,
tableLeft,
}
const resizeProxy = table.refs.resizeProxy as HTMLElement
resizeProxy.style.left = (dragState.value as any).startLeft + 'px'
document.onselectstart = function () {
return false
}
document.ondragstart = function () {
return false
}
const handleMouseMove = (event: MouseEvent) => {
const deltaLeft =
event.clientX - (dragState.value as any).startMouseLeft
const proxyLeft = (dragState.value as any).startLeft + deltaLeft
resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px'
}
const handleMouseUp = () => {
if (dragging.value) {
const { startColumnLeft, startLeft } = dragState.value as any
const finalLeft = parseInt(resizeProxy.style.left, 10)
const columnWidth = finalLeft - startColumnLeft
column.width = column.realWidth = columnWidth
table.emit(
'header-dragend',
column.width,
startLeft - startColumnLeft,
column,
event,
)
props.store.scheduleLayout(false, true)
document.body.style.cursor = ''
dragging.value = false
draggingColumn.value = null
dragState.value = {}
emit('set-drag-visible', false)
}
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
document.onselectstart = null
document.ondragstart = null
setTimeout(function () {
removeClass(columnEl, 'noclick')
}, 0)
}
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
}
}
const handleMouseMove = (event: MouseEvent, column: TableColumnCtx) => {
if (column.children && column.children.length > 0) return
let target = event.target as HTMLElement
while (target && target.tagName !== 'TH') {
target = target.parentNode as HTMLElement
}
if (!column || !column.resizable) return
if (!dragging.value && props.border) {
const rect = target.getBoundingClientRect()
const bodyStyle = document.body.style
if (rect.width > 12 && rect.right - event.pageX < 8) {
bodyStyle.cursor = 'col-resize'
if (hasClass(target, 'is-sortable')) {
target.style.cursor = 'col-resize'
}
draggingColumn.value = column
} else if (!dragging.value) {
bodyStyle.cursor = ''
if (hasClass(target, 'is-sortable')) {
target.style.cursor = 'pointer'
}
draggingColumn.value = null
}
}
}
const handleMouseOut = () => {
if (isServer) return
document.body.style.cursor = ''
}
const toggleOrder = ({ order, sortOrders }) => {
if (order === '') return sortOrders[0]
const index = sortOrders.indexOf(order || null)
return sortOrders[index > sortOrders.length - 2 ? 0 : index + 1]
}
const handleSortClick = (
event: Event,
column: TableColumnCtx,
givenOrder: string | boolean,
) => {
event.stopPropagation()
const order =
column.order === givenOrder ? null : givenOrder || toggleOrder(column)
let target = event.target as HTMLElement
while (target && target.tagName !== 'TH') {
target = target.parentNode as HTMLElement
}
if (target && target.tagName === 'TH') {
if (hasClass(target, 'noclick')) {
removeClass(target, 'noclick')
return
}
}
if (!column.sortable) return
const states = props.store.states
let sortProp = states.sortProp.value
let sortOrder
const sortingColumn = states.sortingColumn.value
if (
sortingColumn !== column ||
(sortingColumn === column && sortingColumn.order === null)
) {
if (sortingColumn) {
sortingColumn.order = null
}
states.sortingColumn.value = column
sortProp = column.property
}
if (!order) {
sortOrder = column.order = null
} else {
sortOrder = column.order = order
}
states.sortProp.value = sortProp
states.sortOrder.value = sortOrder
parent.store.commit('changeSortCondition')
}
return {
handleHeaderClick,
handleHeaderContextMenu,
handleMouseDown,
handleMouseMove,
handleMouseOut,
handleSortClick,
handleFilterClick,
}
}
export default useEvent

View File

@ -0,0 +1,234 @@
import {
defineComponent,
getCurrentInstance,
computed,
onMounted,
nextTick,
ref,
h,
} from 'vue'
import ElCheckbox from '@element-plus/checkbox/src/checkbox.vue'
import FilterPanel from '../filter-panel.vue'
import useLayoutObserver from '../layout-observer'
import useEvent from './event-helper'
import useStyle from './style.helper'
import useUtils from './utils-helper'
import { hColgroup } from '../h-helper'
import { Table, TableHeader } from '../table'
import { TableHeaderProps } from './table-header'
export default defineComponent({
name: 'ElTableHeader',
components: {
ElCheckbox,
},
props: {
fixed: {
type: String,
default: '',
},
store: {
required: true,
type: Object,
},
border: Boolean,
defaultSort: {
type: Object,
default() {
return {
prop: '',
order: '',
}
},
},
},
setup(props: TableHeaderProps, { emit }) {
const instance = getCurrentInstance() as TableHeader
const parent = instance.parent as Table
const storeData = parent.store.states
const filterPanels = ref({})
const {
tableLayout,
onColumnsChange,
onScrollableChange,
} = useLayoutObserver(parent)
const hasGutter = computed(() => {
return !props.fixed && tableLayout.gutterWidth
})
onMounted(() => {
nextTick(() => {
const { prop, order } = props.defaultSort
const init = true
parent.store.commit('sort', { prop, order, init })
})
})
const {
handleHeaderClick,
handleHeaderContextMenu,
handleMouseDown,
handleMouseMove,
handleMouseOut,
handleSortClick,
handleFilterClick,
} = useEvent(props, emit)
const {
getHeaderRowStyle,
getHeaderRowClass,
getHeaderCellStyle,
getHeaderCellClass,
} = useStyle(props)
const { isGroup, toggleAllSelection, columnRows } = useUtils(props)
instance.vnode.state = {
onColumnsChange,
onScrollableChange,
}
// eslint-disable-next-line
instance.filterPanels = filterPanels
return {
columns: storeData.columns,
filterPanels,
hasGutter,
onColumnsChange,
onScrollableChange,
columnRows,
getHeaderRowClass,
getHeaderRowStyle,
getHeaderCellClass,
getHeaderCellStyle,
handleHeaderClick,
handleHeaderContextMenu,
handleMouseDown,
handleMouseMove,
handleMouseOut,
handleSortClick,
handleFilterClick,
isGroup,
toggleAllSelection,
}
},
render() {
return h(
'table',
{
border: '0',
cellpadding: '0',
cellspacing: '0',
class: 'el-table__header',
},
[
hColgroup(this.columns, this.hasGutter),
h(
'thead',
{
class: { 'is-group': this.isGroup, 'has-gutter': this.hasGutter },
},
this.columnRows.map((subColumns, rowIndex) =>
h(
'tr',
{
class: this.getHeaderRowClass(rowIndex),
key: rowIndex,
style: this.getHeaderRowStyle(rowIndex),
},
subColumns.map((column, cellIndex) =>
h(
'th',
{
class: this.getHeaderCellClass(
rowIndex,
cellIndex,
subColumns,
column,
),
colspan: column.colSpan,
key: `${column.id}-thead`,
rowSpan: column.rowSpan,
style: this.getHeaderCellStyle(
rowIndex,
cellIndex,
subColumns,
column,
),
onClick: $event => this.handleHeaderClick($event, column),
onContextmenu: $event =>
this.handleHeaderContextMenu($event, column),
onMousedown: $event =>
this.handleMouseDown($event, column),
onMouseMove: $event =>
this.handleMouseMove($event, column),
onMouseout: this.handleMouseOut,
},
[
h(
'div',
{
class: [
'cell',
column.filteredValue &&
column.filteredValue.length > 0
? 'highlight'
: '',
column.labelClassName,
],
},
[
column.renderHeader
? column.renderHeader({
column,
index_: cellIndex,
store: this.store,
_self: this.$parent,
})
: column.label,
column.sortable &&
h(
'span',
{
onClick: $event =>
this.handleSortClick($event, column),
class: 'caret-wrapper',
},
[
h('i', {
onClick: $event =>
this.handleSortClick(
$event,
column,
'ascending',
),
class: 'sort-caret ascending',
}),
h('i', {
onClick: $event =>
this.handleSortClick(
$event,
column,
'descending',
),
class: 'sort-caret descending',
}),
],
),
column.filterable &&
h(FilterPanel, {
store: this.$parent.store,
placement: column.filterPlacement || 'bottom-start',
column: column,
upDataColumn: (key, value) => {
column[key] = value
},
}),
],
),
],
),
),
),
),
),
],
)
},
})

View File

@ -0,0 +1,124 @@
import { getCurrentInstance } from 'vue'
import { TableColumnCtx, AnyObject, Table } from '../table'
import { TableHeaderProps } from './table-header'
function useStyle(props: TableHeaderProps) {
const instance = getCurrentInstance()
const parent = instance.parent as Table
const storeData = parent.store.states
const isCellHidden = (index: number, columns: TableColumnCtx[]): boolean => {
let start = 0
for (let i = 0; i < index; i++) {
start += columns[i].colSpan
}
const after = start + columns[index].colSpan - 1
if (props.fixed === 'left') {
return after >= storeData.fixedLeafColumnsLength.value
} else if (props.fixed === 'right') {
return (
start <
storeData.columns.value.length -
storeData.rightFixedLeafColumnsLength.value
)
} else {
return (
after < storeData.fixedLeafColumnsLength.value ||
start >=
storeData.columns.value.length -
storeData.rightFixedLeafColumnsLength.value
)
}
}
const getHeaderRowStyle = (rowIndex: number) => {
const headerRowStyle = parent.props.headerRowStyle
if (typeof headerRowStyle === 'function') {
return headerRowStyle.call(null, { rowIndex })
}
return headerRowStyle
}
const getHeaderRowClass = (rowIndex: number): string => {
const classes = []
const headerRowClassName = parent.props.headerRowClassName
if (typeof headerRowClassName === 'string') {
classes.push(headerRowClassName)
} else if (typeof headerRowClassName === 'function') {
classes.push(headerRowClassName.call(null, { rowIndex }))
}
return classes.join(' ')
}
const getHeaderCellStyle = (
rowIndex: number,
columnIndex: number,
row: AnyObject,
column: TableColumnCtx,
) => {
const headerCellStyle = parent.props.headerCellStyle
if (typeof headerCellStyle === 'function') {
return headerCellStyle.call(null, {
rowIndex,
columnIndex,
row,
column,
})
}
return headerCellStyle
}
const getHeaderCellClass = (
rowIndex: number,
columnIndex: number,
row: AnyObject,
column: TableColumnCtx,
) => {
const classes = [
column.id,
column.order,
column.headerAlign,
column.className,
column.labelClassName,
]
if (
rowIndex === 0 &&
isCellHidden(columnIndex, (row as unknown) as TableColumnCtx[])
) {
classes.push('is-hidden')
}
if (!column.children) {
classes.push('is-leaf')
}
if (column.sortable) {
classes.push('is-sortable')
}
const headerCellClassName = parent.props.headerCellClassName
if (typeof headerCellClassName === 'string') {
classes.push(headerCellClassName)
} else if (typeof headerCellClassName === 'function') {
classes.push(
headerCellClassName.call(null, {
rowIndex,
columnIndex,
row,
column,
}),
)
}
return classes.join(' ')
}
return {
getHeaderRowStyle,
getHeaderRowClass,
getHeaderCellStyle,
getHeaderCellClass,
}
}
export default useStyle

View File

@ -0,0 +1,12 @@
import { Store } from '../table'
interface TableHeaderProps {
fixed: string
store: Store
border: boolean
defaultSort: any
}
export {
TableHeaderProps,
}

View File

@ -0,0 +1,86 @@
import { getCurrentInstance, computed } from 'vue'
import { Table, TableColumnCtx } from '../table'
import { TableHeaderProps } from './table-header'
const getAllColumns = (columns: TableColumnCtx[]): TableColumnCtx[] => {
const result = []
columns.forEach(column => {
if (column.children) {
result.push(column)
// eslint-disable-next-line prefer-spread
result.push.apply(result, getAllColumns(column.children))
} else {
result.push(column)
}
})
return result
}
const convertToRows = (originColumns: TableColumnCtx[]): TableColumnCtx[] => {
let maxLevel = 1
const traverse = (column: TableColumnCtx, parent: TableColumnCtx) => {
if (parent) {
column.level = parent.level + 1
if (maxLevel < column.level) {
maxLevel = column.level
}
}
if (column.children) {
let colSpan = 0
column.children.forEach(subColumn => {
traverse(subColumn, column)
colSpan += subColumn.colSpan
})
column.colSpan = colSpan
} else {
column.colSpan = 1
}
}
originColumns.forEach(column => {
column.level = 1
traverse(column, undefined)
})
const rows = []
for (let i = 0; i < maxLevel; i++) {
rows.push([])
}
const allColumns = getAllColumns(originColumns)
allColumns.forEach(column => {
if (!column.children) {
column.rowSpan = maxLevel - column.level + 1
} else {
column.rowSpan = 1
}
rows[column.level - 1].push(column)
})
return rows
}
function useUtils(props: TableHeaderProps) {
const instance = getCurrentInstance()
const parent = instance.parent as Table
const columnRows = computed(() => {
return convertToRows(props.store.states.originColumns.value)
})
const isGroup = computed(() => {
const result = columnRows.value.length > 1
if (result) parent.vnode.state.isGroup.value = true
return result
})
const toggleAllSelection = (event: Event) => {
event.stopPropagation()
parent.store.commit('toggleAllSelection')
}
return {
isGroup,
toggleAllSelection,
columnRows,
}
}
export default useUtils

View File

@ -0,0 +1,313 @@
import { nextTick, ref, isRef, Ref } from 'vue'
import scrollbarWidth from '@element-plus/utils/scrollbar-width'
import isServer from '@element-plus/utils/isServer'
import { parseHeight } from './util'
import { AnyObject, Table, Store, TableHeader, TableColumnCtx } from './table'
class TableLayout {
observers: TableHeader[]
table: Table
store: Store
columns: TableColumnCtx[]
fit: boolean
showHeader: boolean
height: Ref<null | number>
scrollX: Ref<boolean>
scrollY: Ref<boolean>
bodyWidth: Ref<null | number>
fixedWidth: Ref<null | number>
rightFixedWidth: Ref<null | number>
tableHeight: Ref<null | number>
headerHeight: Ref<null | number> // Table Header Height
appendHeight: Ref<null | number> // Append Slot Height
footerHeight: Ref<null | number> // Table Footer Height
viewportHeight: Ref<null | number> // Table Height - Scroll Bar Height
bodyHeight: Ref<null | number> // Table Height - Table Header Height
fixedBodyHeight: Ref<null | number> // Table Height - Table Header Height - Scroll Bar Height
gutterWidth: number
constructor(options: AnyObject) {
this.observers = []
this.table = null
this.store = null
this.columns = []
this.fit = true
this.showHeader = true
this.height = ref(null)
this.scrollX = ref(false)
this.scrollY = ref(false)
this.bodyWidth = ref(null)
this.fixedWidth = ref(null)
this.rightFixedWidth = ref(null)
this.tableHeight = ref(null)
this.headerHeight = ref(44)
this.appendHeight = ref(0)
this.footerHeight = ref(44)
this.viewportHeight = ref(null)
this.bodyHeight = ref(null)
this.fixedBodyHeight = ref(null)
this.gutterWidth = scrollbarWidth()
for (const name in options) {
if (options.hasOwnProperty(name)) {
if (isRef(this[name])) {
this[name].value = options[name]
} else {
this[name] = options[name]
}
}
}
if (!this.table) {
throw new Error('table is required for Table Layout')
}
if (!this.store) {
throw new Error('store is required for Table Layout')
}
}
updateScrollY() {
const height = this.height.value
if (height === null) return false
const bodyWrapper = this.table.refs.bodyWrapper as HTMLElement
if (this.table.vnode.el && bodyWrapper) {
const body = bodyWrapper.querySelector('.el-table__body') as HTMLElement
const prevScrollY = this.scrollY.value
const scrollY = body.offsetHeight > this.bodyHeight.value
this.scrollY.value = scrollY
return prevScrollY !== scrollY
}
return false
}
setHeight(value: string | number, prop = 'height') {
if (isServer) return
const el = this.table.vnode.el
value = parseHeight(value)
this.height.value = Number(value)
if (!el && (value || value === 0))
return nextTick(() => this.setHeight(value, prop))
if (typeof value === 'number') {
el.style[prop] = value + 'px'
this.updateElsHeight()
} else if (typeof value === 'string') {
el.style[prop] = value
this.updateElsHeight()
}
}
setMaxHeight(value: string | number) {
this.setHeight(value, 'max-height')
}
getFlattenColumns(): TableColumnCtx[] {
const flattenColumns = []
const columns = this.table.store.states.columns.value
columns.forEach(column => {
if (column.isColumnGroup) {
// eslint-disable-next-line prefer-spread
flattenColumns.push.apply(flattenColumns, column.columns)
} else {
flattenColumns.push(column)
}
})
return flattenColumns
}
updateElsHeight() {
if (!this.table.$ready) return nextTick(() => this.updateElsHeight())
const {
headerWrapper: headerWrapper_,
appendWrapper: appendWrapper_,
footerWrapper: footerWrapper_,
} = this.table.refs
const appendWrapper = appendWrapper_ as HTMLElement
const headerWrapper = headerWrapper_ as HTMLElement
const footerWrapper = footerWrapper_ as HTMLElement
this.appendHeight.value = appendWrapper ? appendWrapper.offsetHeight : 0
if (this.showHeader && !headerWrapper) return
// fix issue (https://github.com/ElemeFE/element/pull/16956)
const headerTrElm = headerWrapper
? headerWrapper.querySelector('.el-table__header tr')
: null
const noneHeader = this.headerDisplayNone(headerTrElm as HTMLElement)
const headerHeight = (this.headerHeight.value = !this.showHeader
? 0
: headerWrapper.offsetHeight)
if (
this.showHeader &&
!noneHeader &&
headerWrapper.offsetWidth > 0 &&
(this.table.store.states.columns.value || []).length > 0 &&
headerHeight < 2
) {
return nextTick(() => this.updateElsHeight())
}
const tableHeight = (this.tableHeight.value = this.table.vnode.el.clientHeight)
const footerHeight = (this.footerHeight.value = footerWrapper
? footerWrapper.offsetHeight
: 0)
if (this.height.value !== null) {
this.bodyHeight.value =
tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0)
}
this.fixedBodyHeight.value = this.scrollX.value
? this.bodyHeight.value - this.gutterWidth
: this.bodyHeight.value
const noData = !(
this.store.states.data.value && this.store.states.data.value.length
)
this.viewportHeight.value = this.scrollX.value
? tableHeight - (noData ? 0 : this.gutterWidth)
: tableHeight
this.updateScrollY()
this.notifyObservers('scrollable')
}
headerDisplayNone(elm: HTMLElement) {
if (!elm) return true
let headerChild = elm
while (headerChild.tagName !== 'DIV') {
if (getComputedStyle(headerChild).display === 'none') {
return true
}
headerChild = headerChild.parentElement
}
return false
}
updateColumnsWidth() {
if (isServer) return
const fit = this.fit
const bodyWidth = this.table.vnode.el.clientWidth
let bodyMinWidth = 0
const flattenColumns = this.getFlattenColumns()
const flexColumns = flattenColumns.filter(
column => typeof column.width !== 'number',
)
flattenColumns.forEach(column => {
// Clean those columns whose width changed from flex to unflex
if (typeof column.width === 'number' && column.realWidth)
column.realWidth = null
})
if (flexColumns.length > 0 && fit) {
flattenColumns.forEach(column => {
bodyMinWidth += column.width || column.minWidth || 80
})
const scrollYWidth = this.scrollY.value ? this.gutterWidth : 0
if (bodyMinWidth <= bodyWidth - scrollYWidth) {
// DON'T HAVE SCROLL BAR
this.scrollX.value = false
const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth
if (flexColumns.length === 1) {
flexColumns[0].realWidth =
(flexColumns[0].minWidth || 80) + totalFlexWidth
} else {
const allColumnsWidth = flexColumns.reduce(
(prev, column) => prev + (column.minWidth || 80),
0,
)
const flexWidthPerPixel = totalFlexWidth / allColumnsWidth
let noneFirstWidth = 0
flexColumns.forEach((column, index) => {
if (index === 0) return
const flexWidth = Math.floor(
(column.minWidth || 80) * flexWidthPerPixel,
)
noneFirstWidth += flexWidth
column.realWidth = (column.minWidth || 80) + flexWidth
})
flexColumns[0].realWidth =
(flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth
}
} else {
// HAVE HORIZONTAL SCROLL BAR
this.scrollX.value = true
flexColumns.forEach(function(column) {
column.realWidth = column.minWidth
})
}
this.bodyWidth.value = Math.max(bodyMinWidth, bodyWidth)
this.table.vnode.state.resizeState.value.width = this.bodyWidth.value
} else {
flattenColumns.forEach(column => {
if (!column.width && !column.minWidth) {
column.realWidth = 80
} else {
column.realWidth = column.width || column.minWidth
}
bodyMinWidth += column.realWidth
})
this.scrollX.value = bodyMinWidth > bodyWidth
this.bodyWidth.value = bodyMinWidth
}
const fixedColumns = this.store.states.fixedColumns.value
if (fixedColumns.length > 0) {
let fixedWidth = 0
fixedColumns.forEach(function(column) {
fixedWidth += column.realWidth || column.width
})
this.fixedWidth.value = fixedWidth
}
const rightFixedColumns = this.store.states.rightFixedColumns.value
if (rightFixedColumns.length > 0) {
let rightFixedWidth = 0
rightFixedColumns.forEach(function(column) {
rightFixedWidth += column.realWidth || column.width
})
this.rightFixedWidth.value = rightFixedWidth
}
this.notifyObservers('columns')
this.updateElsHeight()
}
addObserver(observer: TableHeader) {
this.observers.push(observer)
}
removeObserver(observer: TableHeader) {
const index = this.observers.indexOf(observer)
if (index !== -1) {
this.observers.splice(index, 1)
}
}
notifyObservers(event: string) {
const observers = this.observers
observers.forEach(observer => {
switch (event) {
case 'columns':
observer.vnode.state?.onColumnsChange(this)
break
case 'scrollable':
observer.vnode.state?.onScrollableChange(this)
break
default:
throw new Error(`Table Layout don't have event ${event}.`)
}
})
}
}
export default TableLayout

378
packages/table/src/table.d.ts vendored Normal file
View File

@ -0,0 +1,378 @@
import { Ref, ComponentInternalInstance, VNode } from 'vue'
interface fn {
(...args: any[]): any
}
interface AnyObject {
[key: string]: any
}
interface WatcherPropsData {
data: Ref<any[]>
rowKey: Ref<string>
}
interface StoreCurrent {
updateCurrentRow: (_currentRow: any) => void
updateCurrentRowData: () => void
setCurrentRowKey: (key: string) => void
states: {
_currentRowKey: Ref<string>
currentRow: Ref<AnyObject>
}
}
interface StoreExpand {
setExpandRowKeys: (rowKeys: string[]) => void
toggleRowExpansion: (row, expanded: boolean | undefined) => void
updateExpandRows: () => void
isRowExpanded: (row: AnyObject) => boolean
states: {
expandRows: Ref<AnyObject[]>
defaultExpandAll: Ref<boolean>
}
}
interface StoreTree {
updateTreeExpandKeys: (value: string[]) => void
toggleTreeExpansion: (row, expanded) => void
loadOrToggle: (row: any) => void
states: {
expandRowKeys: Ref<string[]>
treeData: Ref<AnyObject>
indent: Ref<number>
lazy: Ref<boolean>
lazyTreeNodeMap: Ref<AnyObject>
lazyColumnIdentifier: Ref<string>
childrenColumnName: Ref<string>
}
}
interface StoreMutations {
setData: (states: StoreStates, data: AnyObject[]) => void
insertColumn: (
states: StoreStates,
column: AnyObject,
index: number | undefined,
parent: AnyObject,
) => void
removeColumn: (
states: StoreStates,
column: AnyObject,
parent: AnyObject,
) => void
sort: (states: StoreStates, options: Sort) => void
changeSortCondition: (states: StoreStates, options: Sort) => void
filterChange: (states: StoreStates, options: Filter) => void
toggleAllSelection: () => void
rowSelectedChanged: (states: StoreStates, row: AnyObject) => void
setHoverRow: (states: StoreStates, row: AnyObject) => void
setCurrentRow: (states: StoreStates, row: AnyObject) => void
}
interface StoreStates {
rowKey: Ref<string>
data: Ref<AnyObject[]>
_data: Ref<AnyObject[]>
isComplex: Ref<boolean>
_columns: Ref<TableColumnCtx[]>
originColumns: Ref<TableColumnCtx[]>
columns: Ref<TableColumnCtx[]>
fixedColumns: Ref<TableColumnCtx[]>
rightFixedColumns: Ref<TableColumnCtx[]>
leafColumns: Ref<TableColumnCtx[]>
fixedLeafColumns: Ref<TableColumnCtx[]>
rightFixedLeafColumns: Ref<TableColumnCtx[]>
leafColumnsLength: Ref<number>
fixedLeafColumnsLength: Ref<number>
rightFixedLeafColumnsLength: Ref<number>
isAllSelected: Ref<boolean>
selection: Ref<AnyObject[]>
reserveSelection: Ref<boolean>
selectOnIndeterminate: Ref<boolean>
selectable: (item: AnyObject, i: number) => Ref<boolean>
filters: AnyObject
filteredData: Ref<AnyObject[]>
sortingColumn: AnyObject
sortProp: Ref<null> | Ref<string>
sortOrder: Ref<null> | Ref<string>
hoverRow: Ref<unknown>
expandRows: Ref<AnyObject[]>
defaultExpandAll: Ref<boolean>
expandRowKeys: Ref<string[]>
treeData: Ref<AnyObject>
indent: Ref<number>
lazy: Ref<boolean>
lazyTreeNodeMap: Ref<AnyObject>
lazyColumnIdentifier: Ref<string>
childrenColumnName: Ref<string>
_currentRowKey: Ref<string>
currentRow: Ref<AnyObject>
}
interface Store {
assertRowKey: () => void
updateColumns: () => void
scheduleLayout: (needUpdateColumns?: boolean, immediate?: boolean) => void
isSelected: (row: any) => boolean
clearSelection: () => void
cleanSelection: () => void
toggleRowSelection: (row: any, selected?: any[], emitChange?: boolean) => void
_toggleAllSelection: () => void
updateSelectionByRowKey: () => void
updateAllSelected: () => void
updateFilters: (columns: any | any[], values: any[]) => AnyObject
updateSort: (column: any, prop: string, order: string) => void
execFilter: () => void
execSort: () => void
execQuery: (ignore?: { filter?: any; }) => void
clearFilter: (columnKeys: string | string[]) => void
clearSort: () => void
setExpandRowKeysAdapter: (val: string[]) => void
toggleRowExpansionAdapter: (row, expanded) => void
setExpandRowKeys?: (rowKeys: string[]) => void
toggleRowExpansion: (row, expanded?: boolean | undefined) => void
updateExpandRows: () => void
isRowExpanded: (row: AnyObject) => boolean
updateCurrentRow: (_currentRow: any) => void
updateCurrentRowData: () => void
setCurrentRowKey: (key: string | number) => void
updateTreeExpandKeys?: (value: string[]) => void
toggleTreeExpansion?: (row, expanded) => void
loadOrToggle: (row: any) => void
commit: (name: string, ...args) => void
updateTableScrollY: () => void
toggleAllSelection?: () => void
mutations: StoreMutations
states: StoreStates
}
interface Sort {
prop: string
order: string
init?: any
silent?: any
}
interface Filter {
column: AnyObject
values: string[]
silent: any
}
interface TableLayout {
observers: TableHeader[]
table: Table
store: Store
columns: TableColumnCtx[]
fit: boolean
showHeader: boolean
height: Ref<null | string | number>
scrollX: Ref<boolean>
scrollY: Ref<boolean>
bodyWidth: Ref<null | string | number>
fixedWidth: Ref<null | string | number>
rightFixedWidth: Ref<null | string | number>
tableHeight: Ref<null | string | number>
headerHeight: Ref<number>
appendHeight: Ref<number>
footerHeight: Ref<number>
viewportHeight: Ref<null | string | number>
bodyHeight: Ref<null | string | number>
fixedBodyHeight: Ref<null | string | number>
gutterWidth: number
updateScrollY: () => void
setHeight: (value: string, prop?: string) => void
setMaxHeight: (value: string) => void
getFlattenColumns: () => AnyObject[]
updateElsHeight: () => void
headerDisplayNone: (elm: HTMLElement) => void
updateColumnsWidth: () => void
addObserver: (observer: TableHeader) => void
removeObserver: (observer: TableHeader) => void
notifyObservers: (event: string) => void
}
interface TreeNode {
expanded?: boolean
loading?: boolean
noLazyChildren?: boolean
indent?: number
level?: number
}
interface RenderRowData {
store: any
_self: any
column: any
row: any
index_: any
treeNode?: TreeNode
}
interface TableFooter {
fixed: string
store: unknown
summaryMethod: () => void
sumText: string
border: boolean
defaultSort: Sort
}
interface TableProps {
data: any[]
size: string
width: string | number
height: string | number
maxHeight: string | number
fit: boolean
stripe: boolean
border: boolean
rowKey: string | fn
context: unknown
showHeader: boolean
showSummary: boolean
sumText: string
summaryMethod: fn
rowClassName: string | fn
rowStyle: unknown | fn
cellClassName: string | fn
cellStyle: unknown | fn
headerRowClassName: string | fn
headerRowStyle: unknown | fn
headerCellClassName: string | fn
headerCellStyle: unknown | fn
highlightCurrentRow: boolean
currentRowKey: string | number
emptyText: string
expandRowKeys: any[]
defaultExpandAll: boolean
defaultSort: unknown
tooltipEffect: string
spanMethod: fn
selectOnIndeterminate: boolean
indent: number
treeProps: any
lazy: boolean
load: fn
}
interface TableRefs {
headerWrapper: HTMLElement
footerWrapper: HTMLElement
fixedBodyWrapper: HTMLElement
rightFixedBodyWrapper: HTMLElement
bodyWrapper: HTMLElement
[key: string]: unknown
}
interface TableVnodeState {
isGroup: Ref<boolean>
resizeState: Ref<{
width: any
height: any
}>
doLayout: () => void
debouncedUpdateLayout: () => void
}
interface Table extends ComponentInternalInstance {
$ready: boolean
hoverState?: null | {
cell: HTMLElement
column: TableColumnCtx
row: AnyObject
}
renderExpanded: fn
store: Store
layout: TableLayout
refs: TableRefs
tableId: string
vnode: VNode & {
state: TableVnodeState
}
}
interface TableHeader extends ComponentInternalInstance {
vnode: {
state: {
onColumnsChange
onScrollableChange
}
} & VNode
filterPanels: Ref<AnyObject>
}
interface TableColumnCtx {
id?: string
realWidth: number
type: string
label: string
className: string
labelClassName: string
property: string
prop: string
width: number
minWidth: number
renderHeader: fn
sortable: boolean | string
sortMethod: fn
sortBy: string | fn | unknown[]
resizable: {
type: boolean
default: true
}
columnKey: string
align: string
headerAlign: string
showTooltipWhenOverflow: boolean
showOverflowTooltip: boolean
fixed: boolean | string
formatter: fn
selectable: fn
reserveSelection: boolean
filterMethod: fn
filteredValue: unknown[]
filters: unknown[]
filterPlacement: string
filterMultiple: {
type: boolean
default: true
}
index: number | fn
sortOrders: unknown[]
renderCell: (data: AnyObject) => void
colSpan: number
rowSpan: number
children: TableColumnCtx[]
level: number
filterable: boolean | fn
order: string
isColumnGroup: boolean
columns: TableColumnCtx[]
}
interface TableColumn extends ComponentInternalInstance {
vnode: {
vParent: ComponentInternalInstance
} & VNode
columnId: string
columnConfig: Ref<AnyObject>
}
export {
TreeNode,
RenderRowData,
TableFooter,
TableProps,
WatcherPropsData,
Store,
Table,
AnyObject,
TableHeader,
TableColumnCtx,
TableLayout,
fn,
TableColumn,
}

View File

@ -0,0 +1,436 @@
<template>
<div
:class="[{
'el-table--fit': fit,
'el-table--striped': stripe,
'el-table--border': border || isGroup,
'el-table--hidden': isHidden,
'el-table--group': isGroup,
'el-table--fluid-height': maxHeight,
'el-table--scrollable-x': layout.scrollX.value,
'el-table--scrollable-y': layout.scrollY.value,
'el-table--enable-row-hover': !store.states.isComplex.value,
'el-table--enable-row-transition': (store.states.data.value || []).length !== 0 && (store.states.data.value || []).length < 100
}, tableSize ? `el-table--${ tableSize }` : '']"
class="el-table"
@mouseleave="handleMouseLeave()"
>
<div ref="hiddenColumns" class="hidden-columns">
<slot></slot>
</div>
<div
v-if="showHeader"
ref="headerWrapper"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__header-wrapper"
>
<table-header
ref="tableHeader"
:border="border"
:default-sort="defaultSort"
:store="store"
:style="{
width: layout.bodyWidth.value ? layout.bodyWidth.value + 'px' : ''
}"
@set-drag-visible="setDragVisible"
/>
</div>
<div
ref="bodyWrapper"
:class="[layout.scrollX.value ? `is-scrolling-${scrollPosition}` : 'is-scrolling-none']"
:style="[bodyHeight]"
class="el-table__body-wrapper"
>
<table-body
:context="context"
:highlight="highlightCurrentRow"
:row-class-name="rowClassName"
:row-style="rowStyle"
:store="store"
:stripe="stripe"
:style="{
width: bodyWidth
}"
/>
<div
v-if="!data || data.length === 0"
ref="emptyBlock"
:style="emptyBlockStyle"
class="el-table__empty-block"
>
<span class="el-table__empty-text">
<slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot>
</span>
</div>
<div v-if="$slots.append" ref="appendWrapper" class="el-table__append-wrapper">
<slot name="append"></slot>
</div>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
ref="footerWrapper"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__footer-wrapper"
>
<table-footer
:border="border"
:default-sort="defaultSort"
:store="store"
:style="{
width: layout.bodyWidth.value ? layout.bodyWidth.value + 'px' : ''
}"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
/>
</div>
<div
v-if="store.states.fixedColumns.value.length > 0"
ref="fixedWrapper"
v-mousewheel="handleFixedMousewheel"
:style="[{
width: layout.fixedWidth.value ? layout.fixedWidth.value + 'px' : ''
},
fixedHeight]"
class="el-table__fixed"
>
<div v-if="showHeader" ref="fixedHeaderWrapper" class="el-table__fixed-header-wrapper">
<table-header
ref="fixedTableHeader"
:border="border"
:store="store"
:style="{
width: bodyWidth
}"
fixed="left"
@set-drag-visible="setDragVisible"
/>
</div>
<div
ref="fixedBodyWrapper"
:style="[{
top: layout.headerHeight.value + 'px'
},
fixedBodyHeight]"
class="el-table__fixed-body-wrapper"
>
<table-body
:highlight="highlightCurrentRow"
:row-class-name="rowClassName"
:row-style="rowStyle"
:store="store"
:stripe="stripe"
:style="{
width: bodyWidth
}"
fixed="left"
/>
<div v-if="$slots.append" :style="{ height: layout.appendHeight.value + 'px'}" class="el-table__append-gutter"></div>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
ref="fixedFooterWrapper"
class="el-table__fixed-footer-wrapper"
>
<table-footer
:border="border"
:store="store"
:style="{
width: bodyWidth
}"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
fixed="left"
/>
</div>
</div>
<div
v-if="store.states.rightFixedColumns.value.length > 0"
ref="rightFixedWrapper"
v-mousewheel="handleFixedMousewheel"
:style="[{
width: layout.rightFixedWidth.value ? layout.rightFixedWidth.value + 'px' : '',
right: layout.scrollY.value ? (border ? layout.gutterWidth : (layout.gutterWidth || 0)) + 'px' : ''
},
fixedHeight]"
class="el-table__fixed-right"
>
<div v-if="showHeader" ref="rightFixedHeaderWrapper" class="el-table__fixed-header-wrapper">
<table-header
ref="rightFixedTableHeader"
:border="border"
:store="store"
:style="{
width: bodyWidth
}"
fixed="right"
@set-drag-visible="setDragVisible"
/>
</div>
<div
ref="rightFixedBodyWrapper"
:style="[
{top: layout.headerHeight.value + 'px'},
fixedBodyHeight
]"
class="el-table__fixed-body-wrapper"
>
<table-body
:highlight="highlightCurrentRow"
:row-class-name="rowClassName"
:row-style="rowStyle"
:store="store"
:stripe="stripe"
:style="{
width: bodyWidth
}"
fixed="right"
/>
<div v-if="$slots.append" :style="{ height: layout.appendHeight.value + 'px' }" class="el-table__append-gutter"></div>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
ref="rightFixedFooterWrapper"
class="el-table__fixed-footer-wrapper"
>
<table-footer
:border="border"
:store="store"
:style="{
width: bodyWidth
}"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
fixed="right"
/>
</div>
</div>
<div
v-if="store.states.rightFixedColumns.value.length > 0"
ref="rightFixedPatch"
:style="{
width: layout.scrollY.value ? layout.gutterWidth + 'px' : '0',
height: layout.headerHeight.value + 'px'
}"
class="el-table__fixed-right-patch"
></div>
<div v-show="resizeProxyVisible" ref="resizeProxy" class="el-table__column-resize-proxy"></div>
</div>
</template>
<script lang='ts'>
import { defineComponent, getCurrentInstance, computed } from 'vue'
import { createStore } from '@element-plus/table/src/store/helper'
import { t } from '@element-plus/locale'
import mousewheel from '@element-plus/directives/mousewheel/index'
import TableLayout from '@element-plus/table/src/table-layout'
import TableHeader from './table-header/index'
import TableBody from './table-body/index'
import TableFooter from './table-footer/index'
import { debounce } from 'lodash'
import useUtils from './table/utils-helper'
import useStyle from './table/style-helper'
import { TableProps, Table } from './table'
let tableIdSeed = 1
export default defineComponent({
name: 'ElTable',
directives: {
mousewheel,
},
components: {
TableHeader,
TableBody,
TableFooter,
},
props: {
data: {
type: Array,
default: function () {
return []
},
},
size: String,
width: [String, Number],
height: [String, Number],
maxHeight: [String, Number],
fit: {
type: Boolean,
default: true,
},
stripe: Boolean,
border: Boolean,
rowKey: [String, Function],
showHeader: {
type: Boolean,
default: true,
},
showSummary: Boolean,
sumText: String,
summaryMethod: Function,
rowClassName: [String, Function],
rowStyle: [Object, Function],
cellClassName: [String, Function],
cellStyle: [Object, Function],
headerRowClassName: [String, Function],
headerRowStyle: [Object, Function],
headerCellClassName: [String, Function],
headerCellStyle: [Object, Function],
highlightCurrentRow: Boolean,
currentRowKey: [String, Number],
emptyText: String,
expandRowKeys: Array,
defaultExpandAll: Boolean,
defaultSort: Object,
tooltipEffect: String,
spanMethod: Function,
selectOnIndeterminate: {
type: Boolean,
default: true,
},
indent: {
type: Number,
default: 16,
},
treeProps: {
type: Object,
default() {
return {
hasChildren: 'hasChildren',
children: 'children',
}
},
},
lazy: Boolean,
load: Function,
},
emits: [
'select',
'select-all',
'selection-change',
'cell-mouse-enter',
'cell-mouse-leave',
'cell-click',
'cell-dblclick',
'row-click',
'row-contextmenu',
'row-dblclick',
'header-click',
'header-contextmenu',
'sort-change',
'filter-change',
'current-change',
'header-dragend',
'expand-change',
],
setup(props: TableProps) {
let table = getCurrentInstance() as Table
const store = createStore(table, {
rowKey: props.rowKey,
defaultExpandAll: props.defaultExpandAll,
selectOnIndeterminate: props.selectOnIndeterminate,
// TreeTable
indent: props.indent,
lazy: props.lazy,
lazyColumnIdentifier: props.treeProps.hasChildren || 'hasChildren',
childrenColumnName: props.treeProps.children || 'children',
data: props.data,
})
table.store = store
const layout = new TableLayout({
store: table.store,
table,
fit: props.fit,
showHeader: props.showHeader,
})
table.layout = layout
const shouldUpdateHeight = computed(() => {
return (
props.height ||
props.maxHeight ||
store.states.fixedColumns.value.length > 0 ||
store.states.rightFixedColumns.value.length > 0
)
})
/**
* open functions
*/
const {
setCurrentRow,
toggleRowSelection,
clearSelection,
clearFilter,
toggleAllSelection,
toggleRowExpansion,
clearSort,
doLayout,
sort,
} = useUtils(store, layout, shouldUpdateHeight)
const {
isHidden,
renderExpanded,
setDragVisible,
isGroup,
handleMouseLeave,
handleHeaderFooterMousewheel,
tableSize,
bodyHeight,
emptyBlockStyle,
handleFixedMousewheel,
fixedHeight,
fixedBodyHeight,
resizeProxyVisible,
bodyWidth,
resizeState,
scrollPosition,
} = useStyle(props, layout, store, table, doLayout)
const debouncedUpdateLayout = debounce(() => doLayout(), 50)
const tableId = 'el-table_' + tableIdSeed++
table.tableId = tableId
table.vnode.state = {
isGroup,
resizeState,
doLayout,
debouncedUpdateLayout,
}
return {
layout,
store,
handleHeaderFooterMousewheel,
handleMouseLeave,
tableId,
tableSize,
isHidden,
renderExpanded,
resizeProxyVisible,
resizeState,
isGroup,
scrollPosition,
bodyWidth,
bodyHeight,
emptyBlockStyle,
debouncedUpdateLayout,
handleFixedMousewheel,
fixedHeight,
fixedBodyHeight,
setCurrentRow,
toggleRowSelection,
clearSelection,
clearFilter,
toggleAllSelection,
toggleRowExpansion,
clearSort,
doLayout,
sort,
t,
setDragVisible,
context: table,
}
},
})
</script>

View File

@ -0,0 +1,312 @@
import { onMounted, onUnmounted, computed, ref, watchEffect, watch } from 'vue'
import {
addResizeListener,
removeResizeListener,
} from '@element-plus/utils/resize-event'
import { throttle } from 'lodash'
import { parseHeight } from '../util'
import {
TableProps,
Table,
AnyObject,
TableLayout,
Store,
TableColumnCtx,
fn,
} from '../table'
function useStyle(
props: TableProps,
layout: TableLayout,
store: Store,
table: Table,
doLayout: fn,
) {
const isHidden = ref(false)
const renderExpanded = ref(null)
const resizeProxyVisible = ref(false)
const setDragVisible = (visible: boolean) => {
resizeProxyVisible.value = visible
}
const resizeState = ref({
width: null,
height: null,
})
const isGroup = ref(false)
const scrollPosition = ref('left')
watchEffect(() => {
layout.setHeight(props.height as string)
})
watchEffect(() => {
layout.setMaxHeight(props.maxHeight as string)
})
watchEffect(() => {
if (!store.states.rowKey.value) return
store.setCurrentRowKey(props.currentRowKey)
})
watch(
() => props.data,
() => {
table.store.commit('setData', props.data)
},
{
immediate: true,
},
)
watchEffect(() => {
if (props.expandRowKeys) {
store.setExpandRowKeysAdapter(props.expandRowKeys)
}
})
const handleMouseLeave = () => {
table.store.commit('setHoverRow', null)
if (table.hoverState) table.hoverState = null
}
const handleHeaderFooterMousewheel = (event, data) => {
const { pixelX, pixelY } = data
if (Math.abs(pixelX) >= Math.abs(pixelY)) {
(table.refs.bodyWrapper as AnyObject).scrollLeft += data.pixelX / 5
}
}
const shouldUpdateHeight = computed(() => {
return (
props.height ||
props.maxHeight ||
store.states.fixedColumns.value.length > 0 ||
store.states.rightFixedColumns.value.length > 0
)
})
onMounted(() => {
bindEvents()
store.updateColumns()
doLayout()
resizeState.value = {
width: table.vnode.el.offsetWidth,
height: table.vnode.el.offsetHeight,
}
// init filters
store.states.columns.value.forEach((column: TableColumnCtx) => {
if (column.filteredValue && column.filteredValue.length) {
table.store.commit('filterChange', {
column,
values: column.filteredValue,
silent: true,
})
}
})
table.$ready = true
})
const syncPostion = throttle(function () {
const {
scrollLeft,
scrollTop,
offsetWidth,
scrollWidth,
} = table.refs.bodyWrapper
const {
headerWrapper,
footerWrapper,
fixedBodyWrapper,
rightFixedBodyWrapper,
} = table.refs
if (headerWrapper) headerWrapper.scrollLeft = scrollLeft
if (footerWrapper) footerWrapper.scrollLeft = scrollLeft
if (fixedBodyWrapper) fixedBodyWrapper.scrollTop = scrollTop
if (rightFixedBodyWrapper) rightFixedBodyWrapper.scrollTop = scrollTop
const maxScrollLeftPosition = scrollWidth - offsetWidth - 1
if (scrollLeft >= maxScrollLeftPosition) {
scrollPosition.value = 'right'
} else if (scrollLeft === 0) {
scrollPosition.value = 'left'
} else {
scrollPosition.value = 'middle'
}
}, 20)
const bindEvents = () => {
table.refs.bodyWrapper.addEventListener('scroll', syncPostion, {
passive: true,
})
if (props.fit) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
addResizeListener(table.vnode.el, resizeListener)
}
}
onUnmounted(() => {
unbindEvents()
})
const unbindEvents = () => {
table.refs.bodyWrapper?.removeEventListener('scroll', syncPostion, true)
if (props.fit) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
removeResizeListener(table.vnode.el, resizeListener)
}
}
const resizeListener = () => {
if (!table.$ready) return
let shouldUpdateLayout = false
const el = table.vnode.el
const { width: oldWidth, height: oldHeight } = resizeState.value
const width = el.offsetWidth
if (oldWidth !== width) {
shouldUpdateLayout = true
}
const height = el.offsetHeight
if ((props.height || shouldUpdateHeight.value) && oldHeight !== height) {
shouldUpdateLayout = true
}
if (shouldUpdateLayout) {
resizeState.value = {
width,
height,
}
doLayout()
}
}
const tableSize = computed(() => {
return props.size
})
const bodyWidth = computed(() => {
const { bodyWidth: bodyWidth_, scrollY, gutterWidth } = layout
return bodyWidth_.value
? (bodyWidth_.value as number) - (scrollY.value ? gutterWidth : 0) + 'px'
: ''
})
const bodyHeight = computed(() => {
const headerHeight = layout.headerHeight.value || 0
const bodyHeight = layout.bodyHeight.value
const footerHeight = layout.footerHeight.value || 0
if (props.height) {
return {
height: bodyHeight ? bodyHeight + 'px' : '',
}
} else if (props.maxHeight) {
const maxHeight = parseHeight(props.maxHeight)
if (typeof maxHeight === 'number') {
return {
'max-height':
maxHeight -
footerHeight -
(props.showHeader ? headerHeight : 0) +
'px',
}
}
}
return {}
})
const emptyBlockStyle = computed(() => {
if (props.data && props.data.length) return null
let height = '100%'
if (layout.appendHeight.value) {
height = `calc(100% - ${layout.appendHeight.value}px)`
}
return {
width: bodyWidth.value,
height,
}
})
/**
* fix layout
*/
const handleFixedMousewheel = (event, data) => {
const bodyWrapper = table.refs.bodyWrapper as AnyObject
if (Math.abs(data.spinY) > 0) {
const currentScrollTop = bodyWrapper.scrollTop
if (data.pixelY < 0 && currentScrollTop !== 0) {
event.preventDefault()
}
if (
data.pixelY > 0 &&
bodyWrapper.scrollHeight - bodyWrapper.clientHeight > currentScrollTop
) {
event.preventDefault()
}
bodyWrapper.scrollTop += Math.ceil(data.pixelY / 5)
} else {
bodyWrapper.scrollLeft += Math.ceil(data.pixelX / 5)
}
}
const fixedHeight = computed(() => {
if (props.maxHeight) {
if (props.showSummary) {
return {
bottom: 0,
}
}
return {
bottom:
layout.scrollX.value && props.data.length
? layout.gutterWidth + 'px'
: '',
}
} else {
if (props.showSummary) {
return {
height: layout.tableHeight.value
? layout.tableHeight.value + 'px'
: '',
}
}
return {
height: layout.viewportHeight.value
? layout.viewportHeight.value + 'px'
: '',
}
}
})
const fixedBodyHeight = computed(() => {
if (props.height) {
return {
height: layout.fixedBodyHeight.value
? layout.fixedBodyHeight.value + 'px'
: '',
}
} else if (props.maxHeight) {
let maxHeight = parseHeight(props.maxHeight)
if (typeof maxHeight === 'number') {
maxHeight = layout.scrollX.value
? maxHeight - layout.gutterWidth
: maxHeight
if (props.showHeader) {
maxHeight -= layout.headerHeight.value
}
maxHeight -= layout.footerHeight.value
return {
'max-height': maxHeight + 'px',
}
}
}
return {}
})
return {
isHidden,
renderExpanded,
setDragVisible,
isGroup,
handleMouseLeave,
handleHeaderFooterMousewheel,
tableSize,
bodyHeight,
emptyBlockStyle,
handleFixedMousewheel,
fixedHeight,
fixedBodyHeight,
resizeProxyVisible,
bodyWidth,
resizeState,
scrollPosition,
}
}
export default useStyle

View File

@ -0,0 +1,50 @@
import { ComputedRef } from 'vue'
import { Store, AnyObject, TableLayout } from '../table'
function useUtils(store: Store, layout: TableLayout, shouldUpdateHeight: ComputedRef<unknown>) {
const setCurrentRow = (row: AnyObject) => {
store.commit('setCurrentRow', row)
}
const toggleRowSelection = (row, selected) => {
store.toggleRowSelection(row, selected, false)
store.updateAllSelected()
}
const clearSelection = () => {
store.clearSelection()
}
const clearFilter = (columnKeys: string[]) => {
store.clearFilter(columnKeys)
}
const toggleAllSelection = () => {
store.commit('toggleAllSelection')
}
const toggleRowExpansion = (row, expanded) => {
store.toggleRowExpansionAdapter(row, expanded)
}
const clearSort = () => {
store.clearSort()
}
const doLayout = () => {
if (shouldUpdateHeight.value) {
layout.updateElsHeight()
}
layout.updateColumnsWidth()
}
const sort = (prop, order) => {
store.commit('sort', { prop, order })
}
return {
setCurrentRow,
toggleRowSelection,
clearSelection,
clearFilter,
toggleAllSelection,
toggleRowExpansion,
clearSort,
doLayout,
sort,
}
}
export default useUtils

View File

@ -0,0 +1,3 @@
import ElTableColumn from './table-column'
export default ElTableColumn

295
packages/table/src/util.ts Normal file
View File

@ -0,0 +1,295 @@
import { getValueByPath } from '@element-plus/utils/util'
import { AnyObject, TableColumnCtx } from './table'
export const getCell = function (event: Event): HTMLElement {
let cell = event.target as HTMLElement
while (cell && cell.tagName.toUpperCase() !== 'HTML') {
if (cell.tagName.toUpperCase() === 'TD') {
return cell
}
cell = cell.parentNode as HTMLElement
}
return null
}
const isObject = function (obj) {
return obj !== null && typeof obj === 'object'
}
export const orderBy = function (array, sortKey, reverse, sortMethod, sortBy) {
if (
!sortKey &&
!sortMethod &&
(!sortBy || (Array.isArray(sortBy) && !sortBy.length))
) {
return array
}
if (typeof reverse === 'string') {
reverse = reverse === 'descending' ? -1 : 1
} else {
reverse = reverse && reverse < 0 ? -1 : 1
}
const getKey = sortMethod
? null
: function (value, index) {
if (sortBy) {
if (!Array.isArray(sortBy)) {
sortBy = [sortBy]
}
return sortBy.map(function (by) {
if (typeof by === 'string') {
return getValueByPath(value, by)
} else {
return by(value, index, array)
}
})
}
if (sortKey !== '$key') {
if (isObject(value) && '$value' in value) value = value.$value
}
return [isObject(value) ? getValueByPath(value, sortKey) : value]
}
const compare = function (a, b) {
if (sortMethod) {
return sortMethod(a.value, b.value)
}
for (let i = 0, len = a.key.length; i < len; i++) {
if (a.key[i] < b.key[i]) {
return -1
}
if (a.key[i] > b.key[i]) {
return 1
}
}
return 0
}
return array
.map(function (value, index) {
return {
value: value,
index: index,
key: getKey ? getKey(value, index) : null,
}
})
.sort(function (a, b) {
let order = compare(a, b)
if (!order) {
// make stable https://en.wikipedia.org/wiki/Sorting_algorithm#Stability
order = a.index - b.index
}
return order * reverse
})
.map(item => item.value)
}
export const getColumnById = function (
table: {
columns: TableColumnCtx[]
},
columnId: string,
): null | TableColumnCtx {
let column = null
table.columns.forEach(function (item) {
if (item.id === columnId) {
column = item
}
})
return column
}
export const getColumnByKey = function (
table: {
columns: TableColumnCtx[]
},
columnKey: string,
): TableColumnCtx {
let column = null
for (let i = 0; i < table.columns.length; i++) {
const item = table.columns[i]
if (item.columnKey === columnKey) {
column = item
break
}
}
return column
}
export const getColumnByCell = function (
table: {
columns: TableColumnCtx[]
},
cell: HTMLElement,
): null | TableColumnCtx {
const matches = (cell.className || '').match(/el-table_[^\s]+/gm)
if (matches) {
return getColumnById(table, matches[0])
}
return null
}
export const getRowIdentity = (
row: AnyObject,
rowKey: string | ((row: AnyObject) => any),
): string => {
if (!row) throw new Error('row is required when get row identity')
if (typeof rowKey === 'string') {
if (rowKey.indexOf('.') < 0) {
return row[rowKey]
}
const key = rowKey.split('.')
let current = row
for (let i = 0; i < key.length; i++) {
current = current[key[i]]
}
return (current as unknown) as string
} else if (typeof rowKey === 'function') {
return rowKey.call(null, row)
}
}
export const getKeysMap = function (
array: AnyObject[],
rowKey: string,
): AnyObject {
const arrayMap = {};
(array || []).forEach((row, index) => {
arrayMap[getRowIdentity(row, rowKey)] = { row, index }
})
return arrayMap
}
function hasOwn(obj: AnyObject, key: string): boolean {
return Object.prototype.hasOwnProperty.call(obj, key)
}
export function mergeOptions<T, K>(defaults: T, config: K): T & K {
const options = {} as T & K
let key
for (key in defaults) {
options[key] = defaults[key]
}
for (key in config) {
if (hasOwn(config, key)) {
const value = config[key]
if (typeof value !== 'undefined') {
options[key] = value
}
}
}
return options
}
export function parseWidth(width: number | string): number | string {
if (width !== undefined) {
width = parseInt(width as string, 10)
if (isNaN(width)) {
width = null
}
}
return width
}
export function parseMinWidth(minWidth): number {
if (typeof minWidth !== 'undefined') {
minWidth = parseWidth(minWidth)
if (isNaN(minWidth)) {
minWidth = 80
}
}
return minWidth
}
export function parseHeight(height: number | string) {
if (typeof height === 'number') {
return height
}
if (typeof height === 'string') {
if (/^\d+(?:px)?$/.test(height)) {
return parseInt(height, 10)
} else {
return height
}
}
return null
}
// https://github.com/reduxjs/redux/blob/master/src/compose.js
export function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
export function toggleRowStatus(
statusArr: AnyObject[],
row: AnyObject,
newVal: boolean,
): boolean {
let changed = false
const index = statusArr.indexOf(row)
const included = index !== -1
const addRow = () => {
statusArr.push(row)
changed = true
}
const removeRow = () => {
statusArr.splice(index, 1)
changed = true
}
if (typeof newVal === 'boolean') {
if (newVal && !included) {
addRow()
} else if (!newVal && included) {
removeRow()
}
} else {
if (included) {
removeRow()
} else {
addRow()
}
}
return changed
}
export function walkTreeNode(
root,
cb,
childrenKey = 'children',
lazyKey = 'hasChildren',
) {
const isNil = array => !(Array.isArray(array) && array.length)
function _walker(parent, children, level) {
cb(parent, children, level)
children.forEach(item => {
if (item[lazyKey]) {
cb(item, null, level + 1)
return
}
const children = item[childrenKey]
if (!isNil(children)) {
_walker(item, children, level + 1)
}
})
}
root.forEach(item => {
if (item[lazyKey]) {
cb(item, null, 0)
return
}
const children = item[childrenKey]
if (!isNil(children)) {
_walker(item, children, 0)
}
})
}

View File

@ -180,3 +180,16 @@ export function useGlobalConfig() {
}
return {}
}
export const arrayFindIndex = function (
arr: Array<unknown>,
pred: (any) => boolean,
): number {
return arr.findIndex(pred)
}
export const arrayFind = function (
arr: Array<unknown>,
pred: (any) => boolean,
): any {
return arr.find(pred)
}

View File

@ -358,7 +358,7 @@ When there are too many columns, you can fix some of them.
fixed="right"
label="Operations"
width="120">
<template slot-scope="scope">
<template v-slot="scope">
<el-button @click="handleClick" type="text" size="small">Detail</el-button>
<el-button type="text" size="small">Edit</el-button>
</template>
@ -567,7 +567,7 @@ When the the data is dynamically changed, you might want the table to have a max
fixed="right"
label="Operations"
width="120">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
@click.native.prevent="deleteRow(scope.$index, tableData)"
type="text"
@ -848,7 +848,7 @@ You can also select multiple rows.
<el-table-column
label="Date"
width="120">
<template slot-scope="scope">{{ scope.row.date }}</template>
<template v-slot="scope">{{ scope.row.date }}</template>
</el-table-column>
<el-table-column
property="name"
@ -1026,7 +1026,7 @@ Filter the table to find desired data.
:filters="[{ text: 'Home', value: 'Home' }, { text: 'Office', value: 'Office' }]"
:filter-method="filterTag"
filter-placement="bottom-end">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag
:type="scope.row.tag === 'Home' ? 'primary' : 'success'"
disable-transitions>{{scope.row.tag}}</el-tag>
@ -1097,7 +1097,7 @@ Customize table column so it can be integrated with other components.
<el-table-column
label="Date"
width="180">
<template slot-scope="scope">
<template v-slot="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>
@ -1105,19 +1105,23 @@ Customize table column so it can be integrated with other components.
<el-table-column
label="Name"
width="180">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>Name: {{ scope.row.name }}</p>
<p>Addr: {{ scope.row.address }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
<template v-slot="scope">
<el-popover effect="light" trigger="hover" placement="top">
<template #default>
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
</template>
<template #trigger>
<div class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column
label="Operations">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">Edit</el-button>
@ -1185,13 +1189,13 @@ Customize table header so it can be even more customized.
</el-table-column>
<el-table-column
align="right">
<template slot="header" slot-scope="scope">
<template #header v-slot="scope">
<el-input
v-model="search"
size="mini"
placeholder="Type to search"/>
</template>
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">Edit</el-button>
@ -1251,7 +1255,7 @@ When the row content is too long and you do not want to display the horizontal s
:data="tableData"
style="width: 100%">
<el-table-column type="expand">
<template slot-scope="props">
<template v-slot="props">
<p>State: {{ props.row.state }}</p>
<p>City: {{ props.row.city }}</p>
<p>Address: {{ props.row.address }}</p>

View File

@ -358,7 +358,7 @@ Cuando se tienen demasiadas columnas, puede fijar alguna de estas.
fixed="right"
label="Operaciones"
width="120">
<template slot-scope="scope">
<template v-slot="scope">
<el-button @click="handleClick" type="text" size="small">Detalle</el-button>
<el-button type="text" size="small">Editar</el-button>
</template>
@ -567,7 +567,7 @@ Cuando los datos se modifican dinámicamente, es posible que necesite que la tab
fixed="right"
label="Operaciones"
width="120">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
@click.native.prevent="deleteRow(scope.$index, tableData)"
type="text"
@ -848,7 +848,7 @@ También puede seleccionar múltiples filas.
<el-table-column
label="Fecha"
width="120">
<template slot-scope="scope">{{ scope.row.date }}</template>
<template v-slot="scope">{{ scope.row.date }}</template>
</el-table-column>
<el-table-column
property="name"
@ -1026,7 +1026,7 @@ Filtra la tabla para encontrar la información que necesita.
:filters="[{ text: 'Home', value: 'Home' }, { text: 'Office', value: 'Office' }]"
:filter-method="filterTag"
filter-placement="bottom-end">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag
:type="scope.row.tag === 'Home' ? 'primary' : 'success'"
disable-transitions>{{scope.row.tag}}</el-tag>
@ -1098,7 +1098,7 @@ Personalice la columna de la tabla para que pueda integrarse con otros component
<el-table-column
label="Fecha"
width="180">
<template slot-scope="scope">
<template v-slot="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>
@ -1106,19 +1106,23 @@ Personalice la columna de la tabla para que pueda integrarse con otros component
<el-table-column
label="Nombre"
width="180">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>Name: {{ scope.row.name }}</p>
<p>Addr: {{ scope.row.address }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
<template v-slot="scope">
<el-popover effect="light" trigger="hover" placement="top">
<template #default>
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
</template>
<template #trigger>
<div class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column
label="Operaciones">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">Editar</el-button>
@ -1186,13 +1190,13 @@ Se puede personalizar el encabezado de la tabla para que se pueda adaptar aún m
</el-table-column>
<el-table-column
align="right">
<template slot="header" slot-scope="scope">
<template #header v-slot="scope">
<el-input
v-model="search"
size="mini"
placeholder="Type to search"/>
</template>
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">Edit</el-button>
@ -1253,7 +1257,7 @@ Cuando el contenido de la fila es demasiado largo y busca no mostrar la barra de
:data="tableData"
style="width: 100%">
<el-table-column type="expand">
<template slot-scope="props">
<template v-slot="props">
<p>Estado: {{ props.row.state }}</p>
<p>Ciudad: {{ props.row.city }}</p>
<p>Dirección: {{ props.row.address }}</p>
@ -1921,4 +1925,4 @@ Puede personalizar el índice de la fila con la propiedad `type=index` de las co
| Name | Description |
|------|--------|
| — | Contenido personalizado para las columnas de la tabla. El parámetro del scope es { row, column, $index } |
| header | Contenido personalizado para el encabezado de la tabla. El parámetro del scope es { column, $index } |
| header | Contenido personalizado para el encabezado de la tabla. El parámetro del scope es { column, $index } |

View File

@ -358,7 +358,7 @@ Lorsque qu'il y a beaucoup de colonnes, il peut être utile d'en fixer certaines
fixed="right"
label="Opérations"
width="120">
<template slot-scope="scope">
<template v-slot="scope">
<el-button @click="handleClick" type="text" size="small">Detail</el-button>
<el-button type="text" size="small">Editer</el-button>
</template>
@ -567,7 +567,7 @@ Quand les données changent dynamiquement, vous pouvez avoir besoin d'une hauteu
fixed="right"
label="Opérations"
width="120">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
@click.native.prevent="deleteRow(scope.$index, tableData)"
type="text"
@ -848,7 +848,7 @@ Vous pouvez aussi sélectionner plusieurs lignes.
<el-table-column
label="Date"
width="120">
<template slot-scope="scope">{{ scope.row.date }}</template>
<template v-slot="scope">{{ scope.row.date }}</template>
</el-table-column>
<el-table-column
property="name"
@ -1026,7 +1026,7 @@ Vous pouvez filtrer la table pour obtenir rapidement les lignes désirées.
:filters="[{ text: 'Home', value: 'Home' }, { text: 'Office', value: 'Office' }]"
:filter-method="filterTag"
filter-placement="bottom-end">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag
:type="scope.row.tag === 'Home' ? 'primary' : 'success'"
disable-transitions>{{scope.row.tag}}</el-tag>
@ -1098,7 +1098,7 @@ Vous pouvez customiser le contenu des colonnes afin de pouvoir utiliser d'autres
<el-table-column
label="Date"
width="180">
<template slot-scope="scope">
<template v-slot="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>
@ -1106,19 +1106,23 @@ Vous pouvez customiser le contenu des colonnes afin de pouvoir utiliser d'autres
<el-table-column
label="Nom"
width="180">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>Nom: {{ scope.row.name }}</p>
<p>Addr: {{ scope.row.address }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
<template v-slot="scope">
<el-popover effect="light" trigger="hover" placement="top">
<template #default>
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
</template>
<template #trigger>
<div class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column
label="Opérations">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">Editer</el-button>
@ -1187,13 +1191,13 @@ Vous pouvez également personnaliser le header de la table.
</el-table-column>
<el-table-column
align="right">
<template slot="header" slot-scope="scope">
<template #header v-slot="scope">
<el-input
v-model="search"
size="mini"
placeholder="Type to search"/>
</template>
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">Editer</el-button>
@ -1254,7 +1258,7 @@ Lorsque le contenu d'une ligne est trop long et que vous ne souhaitez pas affich
:data="tableData"
style="width: 100%">
<el-table-column type="expand">
<template slot-scope="props">
<template v-slot="props">
<p>État: {{ props.row.state }}</p>
<p>Ville: {{ props.row.city }}</p>
<p>Adresse: {{ props.row.address }}</p>

View File

@ -358,7 +358,7 @@
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<template v-slot="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
@ -564,7 +564,7 @@
fixed="right"
label="操作"
width="120">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
@click.native.prevent="deleteRow(scope.$index, tableData)"
type="text"
@ -846,7 +846,7 @@
<el-table-column
label="日期"
width="120">
<template slot-scope="scope">{{ scope.row.date }}</template>
<template v-slot="scope">{{ scope.row.date }}</template>
</el-table-column>
<el-table-column
prop="name"
@ -1026,7 +1026,7 @@
:filters="[{ text: '家', value: '家' }, { text: '公司', value: '公司' }]"
:filter-method="filterTag"
filter-placement="bottom-end">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag
:type="scope.row.tag === '家' ? 'primary' : 'success'"
disable-transitions>{{scope.row.tag}}</el-tag>
@ -1097,7 +1097,7 @@
<el-table-column
label="日期"
width="180">
<template slot-scope="scope">
<template v-slot="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>
@ -1105,18 +1105,22 @@
<el-table-column
label="姓名"
width="180">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
<template v-slot="scope">
<el-popover effect="light" trigger="hover" placement="top">
<template #default>
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
</template>
<template #trigger>
<div class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
@ -1175,7 +1179,7 @@
:data="tableData"
style="width: 100%">
<el-table-column type="expand">
<template slot-scope="props">
<template v-slot="props">
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="商品名称">
<span>{{ props.row.name }}</span>
@ -1436,13 +1440,13 @@
</el-table-column>
<el-table-column
align="right">
<template slot="header" slot-scope="scope">
<template #header v-slot="scope">
<el-input
v-model="search"
size="mini"
placeholder="输入关键字搜索"/>
</template>
<template slot-scope="scope">
<template v-slot="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">Edit</el-button>

View File

@ -2066,6 +2066,7 @@
"@popperjs/core@^2.4.4":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==
"@rollup/plugin-commonjs@^15.1.0":
version "15.1.0"
@ -2111,12 +2112,34 @@
dependencies:
type-detect "4.0.8"
"@sinonjs/fake-timers@^6.0.1":
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/formatio@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
dependencies:
"@sinonjs/commons" "^1"
"@sinonjs/samsam" "^5.0.2"
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.1.0.tgz#3afe719232b541bb6cf3411a4c399a188de21ec0"
integrity sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg==
dependencies:
"@sinonjs/commons" "^1.6.0"
lodash.get "^4.4.2"
type-detect "^4.0.8"
"@sinonjs/text-encoding@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@types/anymatch@*":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
@ -4663,6 +4686,11 @@ diff-sequences@^26.3.0:
version "26.3.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.3.0.tgz#62a59b1b29ab7fd27cef2a33ae52abe73042d0a2"
diff@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@ -7387,6 +7415,15 @@ jest-worker@^26.2.1, jest-worker@^26.3.0:
merge-stream "^2.0.0"
supports-color "^7.0.0"
jest-worker@^26.3.0:
version "26.3.0"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f"
integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==
dependencies:
"@types/node" "*"
merge-stream "^2.0.0"
supports-color "^7.0.0"
jest@^26.4.2:
version "26.4.2"
resolved "https://registry.yarnpkg.com/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312"
@ -7522,6 +7559,11 @@ just-debounce@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea"
just-extend@^4.0.2:
version "4.1.1"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282"
integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -8510,6 +8552,17 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nise@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/fake-timers" "^6.0.0"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
path-to-regexp "^1.7.0"
no-case@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8"
@ -8646,6 +8699,11 @@ normalize-url@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
normalize-wheel@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz#aec886affdb045070d856447df62ecf86146ec45"
integrity sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=
now-and-later@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c"
@ -9240,6 +9298,13 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@ -10414,6 +10479,19 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
sinon@^9.0.3:
version "9.0.3"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.3.tgz#bffc3ec28c936332cd2a41833547b9eed201ecff"
integrity sha512-IKo9MIM111+smz9JGwLmw5U1075n1YXeAq8YeSFlndCLhAL5KGn6bLgu7b/4AYHTV/LcEMcRm2wU2YiL55/6Pg==
dependencies:
"@sinonjs/commons" "^1.7.2"
"@sinonjs/fake-timers" "^6.0.1"
"@sinonjs/formatio" "^5.0.1"
"@sinonjs/samsam" "^5.1.0"
diff "^4.0.2"
nise "^4.0.4"
supports-color "^7.1.0"
sisteransi@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@ -11320,7 +11398,7 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@4.0.8:
type-detect@4.0.8, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"