mirror of
https://github.com/iczer/vue-antd-admin.git
synced 2025-04-06 03:57:44 +08:00
feat: add AdvanceTable.vue component; ⭐
新增:高级表格;
This commit is contained in:
parent
3d3e56de12
commit
517c1959d8
166
src/components/table/advance/ActionColumns.vue
Normal file
166
src/components/table/advance/ActionColumns.vue
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<div class="action-columns" ref="root">
|
||||||
|
<a-tooltip title="列设置" :get-popup-container="() => $refs.root">
|
||||||
|
<a-popover v-model="visible" placement="bottomRight" trigger="click" :get-popup-container="() => $refs.root">
|
||||||
|
<div slot="title">
|
||||||
|
<a-checkbox :indeterminate="indeterminate" :checked="checkAll" @change="onCheckAllChange" class="check-all" />列展示
|
||||||
|
<a-button @click="resetColumns" style="float: right" type="link" size="small">重置</a-button>
|
||||||
|
</div>
|
||||||
|
<a-list style="width: 100%" size="small" :key="i" v-for="(col, i) in columns" slot="content">
|
||||||
|
<a-list-item>
|
||||||
|
<a-checkbox v-model="col.visible" @change="e => onCheckChange(e, col)"/>
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<template slot="actions">
|
||||||
|
<a-tooltip title="固定在列头" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon :class="['left', {active: col.fixed === 'left'}]" @click="fixColumn('left', col)" type="vertical-align-top" />
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="固定在列尾" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon :class="['right', {active: col.fixed === 'right'}]" @click="fixColumn('right', col)" type="vertical-align-bottom" />
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="添加搜索" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon :class="{active: col.searchAble}" @click="setSearch(col)" type="search" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-list-item>
|
||||||
|
</a-list>
|
||||||
|
<a-icon class="action" type="setting" />
|
||||||
|
</a-popover>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ActionColumns',
|
||||||
|
props: ['columns', 'visibleColumns'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
indeterminate: false,
|
||||||
|
checkAll: true,
|
||||||
|
checkedCounts: this.columns.length,
|
||||||
|
backColumns: cloneDeep(this.columns)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
checkedCounts(val) {
|
||||||
|
this.checkAll = val === this.columns.length
|
||||||
|
this.indeterminate = val > 0 && val < this.columns.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$emit('update:visibleColumns', [...this.columns])
|
||||||
|
for (let col of this.columns) {
|
||||||
|
if (col.visible === undefined) {
|
||||||
|
this.$set(col, 'visible', true)
|
||||||
|
}
|
||||||
|
if (!col.visible) {
|
||||||
|
this.checkedCounts -= 1
|
||||||
|
this.$set(col, 'colSpan', 0)
|
||||||
|
this.$set(col, 'customCell', () => ({style: 'display: none;'}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCheckChange(e, col) {
|
||||||
|
if (!col.visible) {
|
||||||
|
this.checkedCounts -= 1
|
||||||
|
this.$set(col, 'colSpan', 0)
|
||||||
|
this.$set(col, 'customCell', () => ({style: 'display: none;'}))
|
||||||
|
} else {
|
||||||
|
this.checkedCounts += 1
|
||||||
|
this.$set(col, 'colSpan', undefined)
|
||||||
|
this.$set(col, 'customCell', undefined)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fixColumn(fixed, col) {
|
||||||
|
if (fixed !== col.fixed) {
|
||||||
|
this.$set(col, 'fixed', fixed)
|
||||||
|
} else {
|
||||||
|
this.$set(col, 'fixed', undefined)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSearch(col) {
|
||||||
|
this.$set(col, 'searchAble', !col.searchAble)
|
||||||
|
if (!col.searchAble && col.search) {
|
||||||
|
this.resetSearch(col)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetSearch(col) {
|
||||||
|
col.search.value = col.dataType === 'boolean' ? false : undefined
|
||||||
|
col.search.backup = undefined
|
||||||
|
},
|
||||||
|
resetColumns() {
|
||||||
|
const {columns, backColumns} = this
|
||||||
|
let counts = columns.length
|
||||||
|
backColumns.forEach((back, index) => {
|
||||||
|
const column = columns[index]
|
||||||
|
column.visible = back.visible === undefined || back.visible
|
||||||
|
if (column.visible) {
|
||||||
|
this.$set(column, 'colSpan', undefined)
|
||||||
|
this.$set(column, 'customCell', undefined)
|
||||||
|
} else {
|
||||||
|
counts -= 1
|
||||||
|
this.$set(column, 'colSpan', 0)
|
||||||
|
this.$set(column, 'customCell', () => ({style: 'display: none;'}))
|
||||||
|
}
|
||||||
|
if (back.fixed !== undefined) {
|
||||||
|
column.fixed = back.fixed
|
||||||
|
} else {
|
||||||
|
this.$set(column, 'fixed', undefined)
|
||||||
|
}
|
||||||
|
column.searchAble = back.searchAble
|
||||||
|
this.resetSearch(column)
|
||||||
|
})
|
||||||
|
this.checkedCounts = counts
|
||||||
|
this.visible = false
|
||||||
|
this.$emit('reset', this.getConditions(columns))
|
||||||
|
},
|
||||||
|
onCheckAllChange(e) {
|
||||||
|
if (e.target.checked) {
|
||||||
|
this.checkedCounts = this.columns.length
|
||||||
|
this.columns.forEach(col => {
|
||||||
|
col.visible = true
|
||||||
|
this.$set(col, 'colSpan', undefined)
|
||||||
|
this.$set(col, 'customCell', undefined)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.checkedCounts = 0
|
||||||
|
this.columns.forEach(col => {
|
||||||
|
col.visible = false
|
||||||
|
this.$set(col, 'colSpan', 0)
|
||||||
|
this.$set(col, 'customCell', () => ({style: 'display: none;'}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getConditions(columns) {
|
||||||
|
const conditions = {}
|
||||||
|
columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||||
|
.forEach(col => {
|
||||||
|
conditions[col.dataIndex] = col.search.value
|
||||||
|
})
|
||||||
|
return conditions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.action-columns{
|
||||||
|
display: inline-block;
|
||||||
|
.check-all{
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.left,.right{
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
.active{
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
44
src/components/table/advance/ActionSize.vue
Normal file
44
src/components/table/advance/ActionSize.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="action-size" ref="root">
|
||||||
|
<a-tooltip title="密度">
|
||||||
|
<a-dropdown placement="bottomCenter" :trigger="['click']" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon class="action" type="column-height" />
|
||||||
|
<a-menu :selected-keys="[value]" slot="overlay" @click="onClick">
|
||||||
|
<a-menu-item key="default">
|
||||||
|
默认
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
中等
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
紧密
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ActionSize',
|
||||||
|
props: ['value'],
|
||||||
|
inject: ['table'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedKeys: ['middle']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick({key}) {
|
||||||
|
this.$emit('input', key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.action-size{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
239
src/components/table/advance/AdvanceTable.vue
Normal file
239
src/components/table/advance/AdvanceTable.vue
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="table" :id="id" class="advanced-table">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div :class="['header-bar', size]">
|
||||||
|
<div class="title">
|
||||||
|
<template v-if="title">{{title}}</template>
|
||||||
|
<slot v-else-if="$slots.title" name="title"></slot>
|
||||||
|
<template v-else>高级表格</template>
|
||||||
|
</div>
|
||||||
|
<div class="search">
|
||||||
|
<search-area @change="onSearchChange" :columns="columns" >
|
||||||
|
<template :slot="slot" v-for="slot in slots">
|
||||||
|
<slot :name="slot"></slot>
|
||||||
|
</template>
|
||||||
|
</search-area>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<a-tooltip title="刷新">
|
||||||
|
<a-icon @click="refresh" class="action" :type="loading ? 'loading' : 'reload'" />
|
||||||
|
</a-tooltip>
|
||||||
|
<action-size v-model="sSize" class="action" />
|
||||||
|
<action-columns :columns="columns" @reset="onColumnsReset" class="action">
|
||||||
|
<template :slot="slot" v-for="slot in slots">
|
||||||
|
<slot :name="slot"></slot>
|
||||||
|
</template>
|
||||||
|
</action-columns>
|
||||||
|
<a-tooltip title="全屏">
|
||||||
|
<a-icon @click="toggleScreen" class="action" :type="fullScreen ? 'fullscreen-exit' : 'fullscreen'" />
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-table
|
||||||
|
v-bind="{...$options.propsData, title: undefined, loading: false}"
|
||||||
|
:size="sSize"
|
||||||
|
@expandedRowsChange="onExpandedRowsChange"
|
||||||
|
@change="onChange"
|
||||||
|
@expand="onExpand"
|
||||||
|
>
|
||||||
|
<template slot-scope="text, record, index" :slot="slot" v-for="slot in scopedSlots ">
|
||||||
|
<slot :name="slot" v-bind="{text, record, index}"></slot>
|
||||||
|
</template>
|
||||||
|
<template :slot="slot" v-for="slot in slots">
|
||||||
|
<slot :name="slot"></slot>
|
||||||
|
</template>
|
||||||
|
<template slot-scope="record, index, indent, expanded" :slot="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''">
|
||||||
|
<slot v-bind="{record, index, indent, expanded}" :name="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''"></slot>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ActionSize from '@/components/table/advance/ActionSize'
|
||||||
|
import ActionColumns from '@/components/table/advance/ActionColumns'
|
||||||
|
import SearchArea from '@/components/table/advance/SearchArea'
|
||||||
|
export default {
|
||||||
|
name: 'AdvanceTable',
|
||||||
|
components: {SearchArea, ActionColumns, ActionSize},
|
||||||
|
props: {
|
||||||
|
tableLayout: String,
|
||||||
|
bordered: Boolean,
|
||||||
|
childrenColumnName: Array[String],
|
||||||
|
columns: Array,
|
||||||
|
components: Object,
|
||||||
|
dataSource: Array,
|
||||||
|
defaultExpandAllRows: Array[String],
|
||||||
|
expandedRowKeys: Array[String],
|
||||||
|
expandedRowRender: Function,
|
||||||
|
expandIcon: Function,
|
||||||
|
expandRowByClick: Boolean,
|
||||||
|
expandIconColumnIndex: Number,
|
||||||
|
footer: Function,
|
||||||
|
indentSize: Number,
|
||||||
|
loading: Boolean,
|
||||||
|
locale: Object,
|
||||||
|
pagination: Object,
|
||||||
|
rowClassName: Function,
|
||||||
|
rowKey: [String, Function],
|
||||||
|
rowSelection: Object,
|
||||||
|
scroll: Object,
|
||||||
|
showHeader: Boolean,
|
||||||
|
size: String,
|
||||||
|
title: String,
|
||||||
|
customHeaderRow: Function,
|
||||||
|
customRow: Function,
|
||||||
|
getPopupContainer: Function,
|
||||||
|
transformCellText: Function
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
table: this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: `${new Date().getTime()}-${Math.floor(Math.random() * 10)}`,
|
||||||
|
sSize: this.size || 'default',
|
||||||
|
fullScreen: false,
|
||||||
|
conditions: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
slots() {
|
||||||
|
return Object.keys(this.$slots).filter(slot => slot !== 'title')
|
||||||
|
},
|
||||||
|
scopedSlots() {
|
||||||
|
return Object.keys(this.$scopedSlots).filter(slot => slot !== 'expandedRowRender' && slot !== 'title')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.addListener()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.removeListener()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refresh() {
|
||||||
|
this.$emit('refresh', this.conditions)
|
||||||
|
},
|
||||||
|
onSearchChange(conditions) {
|
||||||
|
this.conditions = conditions
|
||||||
|
this.$emit('search', conditions)
|
||||||
|
},
|
||||||
|
toggleScreen() {
|
||||||
|
if (this.fullScreen) {
|
||||||
|
this.outFullScreen()
|
||||||
|
} else {
|
||||||
|
this.inFullScreen()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inFullScreen() {
|
||||||
|
const el = this.$refs.table
|
||||||
|
if (el.requestFullscreen) {
|
||||||
|
el.requestFullscreen()
|
||||||
|
return true
|
||||||
|
} else if (el.webkitRequestFullScreen) {
|
||||||
|
el.webkitRequestFullScreen()
|
||||||
|
return true
|
||||||
|
} else if (el.mozRequestFullScreen) {
|
||||||
|
el.mozRequestFullScreen()
|
||||||
|
return true
|
||||||
|
} else if (el.msRequestFullscreen) {
|
||||||
|
el.msRequestFullscreen()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
this.$message.warn('对不起,您的浏览器不支持全屏模式')
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
outFullScreen() {
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen()
|
||||||
|
} else if (document.webkitCancelFullScreen) {
|
||||||
|
document.webkitCancelFullScreen();
|
||||||
|
} else if (document.mozCancelFullScreen) {
|
||||||
|
document.mozCancelFullScreen()
|
||||||
|
} else if (document.msExitFullscreen) {
|
||||||
|
document.msExiFullscreen()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onColumnsReset(conditions) {
|
||||||
|
this.$emit('reset', conditions)
|
||||||
|
},
|
||||||
|
onExpandedRowsChange(expandedRows) {
|
||||||
|
this.$emit('expandedRowsChange', expandedRows)
|
||||||
|
},
|
||||||
|
onChange(pagination, filters, sorter, options) {
|
||||||
|
this.$emit('expandedRowsChange', pagination, filters, sorter, options)
|
||||||
|
},
|
||||||
|
onExpand(expanded, record) {
|
||||||
|
this.$emit('expandedRowsChange', expanded, record)
|
||||||
|
},
|
||||||
|
addListener() {
|
||||||
|
document.addEventListener('fullscreenchange', this.fullScreenListener)
|
||||||
|
document.addEventListener('webkitfullscreenchange', this.fullScreenListener)
|
||||||
|
document.addEventListener('mozfullscreenchange', this.fullScreenListener)
|
||||||
|
document.addEventListener('msfullscreenchange', this.fullScreenListener)
|
||||||
|
},
|
||||||
|
removeListener() {
|
||||||
|
document.removeEventListener('fullscreenchange', this.fullScreenListener)
|
||||||
|
document.removeEventListener('webkitfullscreenchange', this.fullScreenListener)
|
||||||
|
document.removeEventListener('mozfullscreenchange', this.fullScreenListener)
|
||||||
|
document.removeEventListener('msfullscreenchange', this.fullScreenListener)
|
||||||
|
},
|
||||||
|
fullScreenListener(e) {
|
||||||
|
if (e.target.id === this.id) {
|
||||||
|
this.fullScreen = !this.fullScreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.advanced-table{
|
||||||
|
background-color: @component-background;
|
||||||
|
.header-bar{
|
||||||
|
padding: 16px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
&.middle{
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
&.small{
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-bottom: 0;
|
||||||
|
.title{
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
transition: all 0.3s;
|
||||||
|
font-size: 18px;
|
||||||
|
color: @title-color;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.search{
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
margin: 0 24px;
|
||||||
|
}
|
||||||
|
.actions{
|
||||||
|
text-align: right;
|
||||||
|
font-size: 17px;
|
||||||
|
color: @text-color;
|
||||||
|
.action{
|
||||||
|
margin: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover{
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
262
src/components/table/advance/SearchArea.vue
Normal file
262
src/components/table/advance/SearchArea.vue
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-area" ref="root">
|
||||||
|
<div class="search-item" :key="index" v-for="(col, index) in searchCols">
|
||||||
|
<div v-if="col.dataType === 'boolean'" class="title active">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-switch @change="onSwitchChange" class="switch" v-model="col.search.value" size="small" checked-children="是" un-checked-children="否" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'time'" class="title active">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-time-picker v-model="col.search.value" placeholder="选择时间" @change="(time, timeStr) => onCalendarChange(time, timeStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="time-picker" size="small" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'date'" class="title active">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-date-picker v-model="col.search.value" @change="onDateChange(col)" class="date-picker" size="small" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'datetime'" class="title datetime active">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-date-picker v-model="col.search.value" @change="(date, dateStr) => onCalendarChange(date, dateStr, col)" @openChange="open => onCalendarOpenChange(open, col)" show-time class="datetime-picker" size="small" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'select'" class="title active">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-select :allowClear="true" :options="col.search.selectOptions" v-model="col.search.value" placeholder="请选择..." @change="onSelectChange(col)" class="select" slot="content" size="small">
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
<a-popover v-else @visibleChange="onVisibleChange(col, index)" v-model="col.search.visible" placement="bottom" :trigger="['click']" :get-popup-container="() => $refs.root">
|
||||||
|
<div :class="['title', {active: col.search.value}]">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<div class="value " v-if="col.search.value">: {{col | searchValue}}</div>
|
||||||
|
<a-icon class="icon-down" type="down"/>
|
||||||
|
</div>
|
||||||
|
<div class="operations" slot="content">
|
||||||
|
<a-button @click="onCancel(col)" class="btn" size="small" type="link">取消</a-button>
|
||||||
|
<a-button @click="onConfirm(col)" class="btn" size="small" type="primary">确认</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="search-overlay" slot="title">
|
||||||
|
<a-input :id="`${searchIdPrefix}${index}`" :allow-clear="true" @keyup.esc="onCancel(col)" @keyup.enter="onConfirm(col)" v-model="col.search.value" size="default" />
|
||||||
|
</div>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import fastEqual from 'fast-deep-equal'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SearchArea',
|
||||||
|
props: ['columns'],
|
||||||
|
inject: ['table'],
|
||||||
|
created() {
|
||||||
|
this.columns.forEach(item => {
|
||||||
|
this.$set(item, 'search', {...item.search, visible: false, value: item.dataType === 'boolean' ? false : undefined, format: this.getCalendarFormat(item)})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
searchValue(col) {
|
||||||
|
if (col.dataType === 'time' && col.search.value) {
|
||||||
|
return col.search.value.format('HH:mm:ss')
|
||||||
|
}
|
||||||
|
return col.search.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
searchCols(newVal, oldVal) {
|
||||||
|
if (newVal.length != oldVal.length) {
|
||||||
|
const newConditions = this.getConditions(newVal)
|
||||||
|
if (!fastEqual(newConditions, this.conditions)) {
|
||||||
|
this.conditions = newConditions
|
||||||
|
this.$emit('change', this.conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
conditions: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
searchCols() {
|
||||||
|
return this.columns.filter(item => item.searchAble)
|
||||||
|
},
|
||||||
|
searchIdPrefix() {
|
||||||
|
return this.table.id + '-ipt-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCancel(col) {
|
||||||
|
col.search.value = col.search.backup
|
||||||
|
col.search.visible = false
|
||||||
|
},
|
||||||
|
onConfirm(col) {
|
||||||
|
col.search.backup = col.search.value
|
||||||
|
col.search.visible = false
|
||||||
|
const conditions = this.getConditions(this.searchCols)
|
||||||
|
if (!fastEqual(conditions, this.conditions)) {
|
||||||
|
this.conditions = conditions
|
||||||
|
this.$emit('change', this.conditions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSwitchChange() {
|
||||||
|
this.conditions = this.getConditions(this.searchCols)
|
||||||
|
this.$emit('change', this.conditions)
|
||||||
|
},
|
||||||
|
onSelectChange() {
|
||||||
|
this.conditions = this.getConditions(this.searchCols)
|
||||||
|
this.$emit('change', this.conditions)
|
||||||
|
},
|
||||||
|
onCalendarOpenChange(open, col) {
|
||||||
|
col.search.visible = open
|
||||||
|
const {momentEqual, getConditions} = this
|
||||||
|
const {value, backup, format} = col.search
|
||||||
|
if (!open && !momentEqual(value, backup, format)) {
|
||||||
|
col.search.backup = moment(value)
|
||||||
|
this.conditions = getConditions(this.searchCols)
|
||||||
|
this.$emit('change', this.conditions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCalendarChange(date, dateStr, col) {
|
||||||
|
const {momentEqual, getConditions} = this
|
||||||
|
const {value, backup, format} = col.search
|
||||||
|
if (!col.search.visible && !momentEqual(value, backup, format)) {
|
||||||
|
col.search.backup = moment(value)
|
||||||
|
this.conditions = getConditions(this.searchCols)
|
||||||
|
this.$emit('change', this.conditions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDateChange(col) {
|
||||||
|
const {momentEqual, getConditions} = this
|
||||||
|
const {value, backup} = col.search
|
||||||
|
if (!momentEqual(value, backup, 'YYYY-MM-DD')) {
|
||||||
|
col.search.backup = moment(value)
|
||||||
|
this.conditions = getConditions(this.searchCols)
|
||||||
|
this.$emit('change', this.conditions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getCalendarFormat(col) {
|
||||||
|
const dataType = col.dataType
|
||||||
|
switch(dataType) {
|
||||||
|
case 'time': return 'HH:mm:ss'
|
||||||
|
case 'date': return 'YYYY-MM-DD'
|
||||||
|
case 'datetime': return 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
default: return col.search && col.search.format
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getConditions(columns) {
|
||||||
|
const conditions = {}
|
||||||
|
columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||||
|
.forEach(col => {
|
||||||
|
conditions[col.dataIndex] = col.search.value
|
||||||
|
})
|
||||||
|
return conditions
|
||||||
|
},
|
||||||
|
onVisibleChange(col, index) {
|
||||||
|
if (!col.search.visible) {
|
||||||
|
col.search.value = col.search.backup
|
||||||
|
} else {
|
||||||
|
let input = document.getElementById(`${this.searchIdPrefix}${index}`)
|
||||||
|
if (input) {
|
||||||
|
setTimeout(() => {input.focus()}, 0)
|
||||||
|
} else {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
input = document.getElementById(`${this.searchIdPrefix}${index}`)
|
||||||
|
input.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
momentEqual(target, source, format) {
|
||||||
|
if (target === source) {
|
||||||
|
return true
|
||||||
|
} else if (target && source && target.format(format) === source.format(format)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.search-area{
|
||||||
|
margin: -4px 0;
|
||||||
|
.search-item{
|
||||||
|
margin: 4px 4px;
|
||||||
|
display: inline-block;
|
||||||
|
.title{
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
user-select: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
.switch{
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.time-picker{
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 96px;
|
||||||
|
}
|
||||||
|
.date-picker{
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
.datetime-picker{
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 195px;
|
||||||
|
}
|
||||||
|
.value{
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
flex:1;
|
||||||
|
max-width: 144px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
&.active{
|
||||||
|
background-color: @layout-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-down{
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search-overlay{
|
||||||
|
padding: 8px 0px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.select{
|
||||||
|
margin-left: 4px;
|
||||||
|
max-width: 144px;
|
||||||
|
min-width: 96px;
|
||||||
|
}
|
||||||
|
.operations{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.btn{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
2
src/components/table/advance/index.js
Normal file
2
src/components/table/advance/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import AdvanceTable from './AdvanceTable'
|
||||||
|
export default AdvanceTable
|
168
src/pages/components/Table.vue
Normal file
168
src/pages/components/Table.vue
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<advance-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
title="高级表格-Beta"
|
||||||
|
:loading="loading"
|
||||||
|
rowKey="id"
|
||||||
|
@search="onSearch"
|
||||||
|
@refresh="onRefresh"
|
||||||
|
@reset="onReset"
|
||||||
|
>
|
||||||
|
<template slot="statusTitle">
|
||||||
|
状态<a-icon style="margin: 0 4px" type="info-circle" />
|
||||||
|
</template>
|
||||||
|
<template slot="send" slot-scope="{text}">
|
||||||
|
{{text ? '是' : '否'}}
|
||||||
|
</template>
|
||||||
|
<template slot="status" slot-scope="{text}">
|
||||||
|
{{text | statusStr}}
|
||||||
|
</template>
|
||||||
|
</advance-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AdvanceTable from '@/components/table/advance/AdvanceTable'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
const goods = ['运动鞋', 'T恤', '长裤', '短裤']
|
||||||
|
const dataSource = []
|
||||||
|
const current = new Date().getTime()
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
dataSource.push({
|
||||||
|
id: i,
|
||||||
|
name: goods[Math.floor((Math.random() * 4))],
|
||||||
|
orderId: `${new Date().getTime()}-${Math.floor(Math.random() * 10)}`,
|
||||||
|
status: Math.floor((Math.random() * 4) + 1),
|
||||||
|
send: (i % 2) === 1,
|
||||||
|
sendTime: moment(current - Math.floor((Math.random() * 8000000))).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
orderDate: moment(current - Math.floor((Math.random() * 800000000))).format('YYYY-MM-DD'),
|
||||||
|
auditTime: moment(current - Math.floor((Math.random() * 8000000))).format('HH:mm:ss'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
name: 'Table',
|
||||||
|
components: {AdvanceTable},
|
||||||
|
filters: {
|
||||||
|
statusStr(val) {
|
||||||
|
switch (val) {
|
||||||
|
case 1: return '已下单'
|
||||||
|
case 2: return '已付款'
|
||||||
|
case 3: return '已审核'
|
||||||
|
case 4: return '已发货'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '商品名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
searchAble: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单号',
|
||||||
|
dataIndex: 'orderId'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'status',
|
||||||
|
dataType: 'select',
|
||||||
|
slots: {title: 'statusTitle'},
|
||||||
|
scopedSlots: {customRender: 'status'},
|
||||||
|
search: {
|
||||||
|
selectOptions: [
|
||||||
|
{title: '已下单', value: 1},
|
||||||
|
{title: '已付款', value: 2},
|
||||||
|
{title: '已审核', value: 3},
|
||||||
|
{title: '已发货', value: 4}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发货',
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'send',
|
||||||
|
dataType: 'boolean',
|
||||||
|
scopedSlots: {customRender: 'send'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发货时间',
|
||||||
|
dataIndex: 'sendTime',
|
||||||
|
dataType: 'datetime'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '下单日期',
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'orderDate',
|
||||||
|
dataType: 'date'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审核时间',
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'auditTime',
|
||||||
|
dataType: 'time',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSource: dataSource
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSearch(conditions) {
|
||||||
|
this.loading = true
|
||||||
|
this.searchGoods(conditions).then(result => {
|
||||||
|
this.dataSource = result
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onRefresh(conditions) {
|
||||||
|
this.loading = true
|
||||||
|
this.searchGoods(conditions).then(result => {
|
||||||
|
this.dataSource = result
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onReset(conditions) {
|
||||||
|
this.loading = true
|
||||||
|
this.searchGoods(conditions).then(result => {
|
||||||
|
this.dataSource = result
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async searchGoods(conditions) {
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const result = dataSource.filter(item => {
|
||||||
|
for (let key of Object.keys(conditions)) {
|
||||||
|
if (key === 'sendTime') {
|
||||||
|
if (conditions[key].format('YYYY-MM-DD HH:mm:ss') !== item[key]) return false
|
||||||
|
} else if (key === 'orderDate') {
|
||||||
|
if (conditions[key].format('YYYY-MM-DD') !== item[key]) return false
|
||||||
|
} else if (key === 'auditTime') {
|
||||||
|
if (conditions[key].format('HH:mm:ss') !== item[key]) return false
|
||||||
|
} else if (item[key] !== conditions[key]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(result)
|
||||||
|
}, 300)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -208,6 +208,11 @@ const options = {
|
|||||||
path: 'palette',
|
path: 'palette',
|
||||||
name: '颜色复选框',
|
name: '颜色复选框',
|
||||||
component: () => import('@/pages/components/Palette')
|
component: () => import('@/pages/components/Palette')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'table',
|
||||||
|
name: '高级表格',
|
||||||
|
component: () => import('@/pages/components/Table')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user