mirror of
https://github.com/iczer/vue-antd-admin
synced 2025-04-06 04:00:06 +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',
|
||||
name: '颜色复选框',
|
||||
component: () => import('@/pages/components/Palette')
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
name: '高级表格',
|
||||
component: () => import('@/pages/components/Table')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user