mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-02 03:08:21 +08:00
feat(table): table (#407)
Co-authored-by: winerlu <winerlu@tencent.com> Co-authored-by: zazzaz <izazzaz@hotmail.com>
This commit is contained in:
parent
f4f81af246
commit
2985a71751
@ -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",
|
||||
|
@ -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'
|
||||
|
28
packages/directives/mousewheel/index.ts
Normal file
28
packages/directives/mousewheel/index.ts
Normal 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
|
@ -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)
|
||||
|
@ -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'
|
||||
|
33
packages/hooks/use-migrating/index.ts
Normal file
33
packages/hooks/use-migrating/index.ts
Normal 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
|
2193
packages/table/__tests__/table.spec.ts
Normal file
2193
packages/table/__tests__/table.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
46
packages/table/doc/basic.vue
Normal file
46
packages/table/doc/basic.vue
Normal 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>
|
||||
|
70
packages/table/doc/border.vue
Normal file
70
packages/table/doc/border.vue
Normal 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>
|
||||
|
58
packages/table/doc/diyHeader.vue
Normal file
58
packages/table/doc/diyHeader.vue
Normal 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>
|
60
packages/table/doc/diyIndex.vue
Normal file
60
packages/table/doc/diyIndex.vue
Normal 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>
|
81
packages/table/doc/diyTemplate.vue
Normal file
81
packages/table/doc/diyTemplate.vue
Normal 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>
|
108
packages/table/doc/expandRow.vue
Normal file
108
packages/table/doc/expandRow.vue
Normal 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>
|
89
packages/table/doc/filter.vue
Normal file
89
packages/table/doc/filter.vue
Normal 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>
|
71
packages/table/doc/fixedCol.vue
Normal file
71
packages/table/doc/fixedCol.vue
Normal 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>
|
85
packages/table/doc/fixedColRow.vue
Normal file
85
packages/table/doc/fixedColRow.vue
Normal 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>
|
95
packages/table/doc/fixedHeader.vue
Normal file
95
packages/table/doc/fixedHeader.vue
Normal 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>
|
||||
|
104
packages/table/doc/footerCount.vue
Normal file
104
packages/table/doc/footerCount.vue
Normal 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>
|
24
packages/table/doc/index.stories.ts
Normal file
24
packages/table/doc/index.stories.ts
Normal 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',
|
||||
}
|
94
packages/table/doc/maxHeight.vue
Normal file
94
packages/table/doc/maxHeight.vue
Normal 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>
|
103
packages/table/doc/mergeRowCol.vue
Normal file
103
packages/table/doc/mergeRowCol.vue
Normal 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>
|
90
packages/table/doc/mulCheck.vue
Normal file
90
packages/table/doc/mulCheck.vue
Normal 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>
|
82
packages/table/doc/mulHeader.vue
Normal file
82
packages/table/doc/mulHeader.vue
Normal 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>
|
61
packages/table/doc/rowClassName.vue
Normal file
61
packages/table/doc/rowClassName.vue
Normal 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>
|
||||
|
62
packages/table/doc/singleSelect.vue
Normal file
62
packages/table/doc/singleSelect.vue
Normal 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>
|
68
packages/table/doc/sort.vue
Normal file
68
packages/table/doc/sort.vue
Normal 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>
|
60
packages/table/doc/stripe.vue
Normal file
60
packages/table/doc/stripe.vue
Normal 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>
|
||||
|
135
packages/table/doc/treeTable.vue
Normal file
135
packages/table/doc/treeTable.vue
Normal 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
9
packages/table/index.ts
Normal 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 }
|
12
packages/table/package.json
Normal file
12
packages/table/package.json
Normal 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"
|
||||
}
|
||||
}
|
195
packages/table/src/config.ts
Normal file
195
packages/table/src/config.ts
Normal 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
|
||||
}
|
35
packages/table/src/dropdown.ts
Normal file
35
packages/table/src/dropdown.ts
Normal 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
|
259
packages/table/src/filter-panel.vue
Normal file
259
packages/table/src/filter-panel.vue
Normal 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>
|
20
packages/table/src/h-helper.ts
Normal file
20
packages/table/src/h-helper.ts
Normal 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(),
|
||||
])
|
||||
}
|
73
packages/table/src/layout-observer.ts
Normal file
73
packages/table/src/layout-observer.ts
Normal 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
|
83
packages/table/src/store/current.ts
Normal file
83
packages/table/src/store/current.ts
Normal 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
|
73
packages/table/src/store/expand.ts
Normal file
73
packages/table/src/store/expand.ts
Normal 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
|
41
packages/table/src/store/helper.ts
Normal file
41
packages/table/src/store/helper.ts
Normal 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
|
||||
}
|
173
packages/table/src/store/index.ts
Normal file
173
packages/table/src/store/index.ts
Normal 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
|
200
packages/table/src/store/tree.ts
Normal file
200
packages/table/src/store/tree.ts
Normal 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
|
494
packages/table/src/store/watcher.ts
Normal file
494
packages/table/src/store/watcher.ts
Normal 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
|
124
packages/table/src/table-body/events-helper.ts
Normal file
124
packages/table/src/table-body/events-helper.ts
Normal 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
|
106
packages/table/src/table-body/index.ts
Normal file
106
packages/table/src/table-body/index.ts
Normal 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,
|
||||
// },
|
||||
// ),
|
||||
]),
|
||||
],
|
||||
)
|
||||
},
|
||||
})
|
249
packages/table/src/table-body/render-helper.ts
Normal file
249
packages/table/src/table-body/render-helper.ts
Normal 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
|
165
packages/table/src/table-body/styles-helper.ts
Normal file
165
packages/table/src/table-body/styles-helper.ts
Normal 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
|
15
packages/table/src/table-body/table-body.d.ts
vendored
Normal file
15
packages/table/src/table-body/table-body.d.ts
vendored
Normal 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,
|
||||
}
|
225
packages/table/src/table-column/index.ts
Normal file
225
packages/table/src/table-column/index.ts
Normal 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,
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
163
packages/table/src/table-column/render-helper.ts
Normal file
163
packages/table/src/table-column/render-helper.ts
Normal 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
|
73
packages/table/src/table-column/watcher-helper.ts
Normal file
73
packages/table/src/table-column/watcher-helper.ts
Normal 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
|
126
packages/table/src/table-footer/index.ts
Normal file
126
packages/table/src/table-footer/index.ts
Normal 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(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
},
|
||||
})
|
34
packages/table/src/table-footer/mapState-helper.ts
Normal file
34
packages/table/src/table-footer/mapState-helper.ts
Normal 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
|
66
packages/table/src/table-footer/style-helper.ts
Normal file
66
packages/table/src/table-footer/style-helper.ts
Normal 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
|
208
packages/table/src/table-header/event-helper.ts
Normal file
208
packages/table/src/table-header/event-helper.ts
Normal 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
|
234
packages/table/src/table-header/index.ts
Normal file
234
packages/table/src/table-header/index.ts
Normal 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
|
||||
},
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
},
|
||||
})
|
124
packages/table/src/table-header/style.helper.ts
Normal file
124
packages/table/src/table-header/style.helper.ts
Normal 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
|
12
packages/table/src/table-header/table-header.d.ts
vendored
Normal file
12
packages/table/src/table-header/table-header.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { Store } from '../table'
|
||||
|
||||
interface TableHeaderProps {
|
||||
fixed: string
|
||||
store: Store
|
||||
border: boolean
|
||||
defaultSort: any
|
||||
}
|
||||
|
||||
export {
|
||||
TableHeaderProps,
|
||||
}
|
86
packages/table/src/table-header/utils-helper.ts
Normal file
86
packages/table/src/table-header/utils-helper.ts
Normal 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
|
313
packages/table/src/table-layout.ts
Normal file
313
packages/table/src/table-layout.ts
Normal 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
378
packages/table/src/table.d.ts
vendored
Normal 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,
|
||||
}
|
436
packages/table/src/table.vue
Normal file
436
packages/table/src/table.vue
Normal 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>
|
312
packages/table/src/table/style-helper.ts
Normal file
312
packages/table/src/table/style-helper.ts
Normal 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
|
50
packages/table/src/table/utils-helper.ts
Normal file
50
packages/table/src/table/utils-helper.ts
Normal 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
|
3
packages/table/src/tableColumn.ts
Normal file
3
packages/table/src/tableColumn.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import ElTableColumn from './table-column'
|
||||
|
||||
export default ElTableColumn
|
295
packages/table/src/util.ts
Normal file
295
packages/table/src/util.ts
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 } |
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
82
yarn.lock
82
yarn.lock
@ -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"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user