一、需求背景
需要在现有的表格里面实现以下两个功能,
1、表格的列的顺序更改
2、表格列宽的调整
用户操作后需要保存到接口,刷新或重新进入都要复现保存后的操作结果
二、功能详解
表格列宽的调整功能,可以在网上找到很多详细案例很好解决。
难点在于表格的列的顺序更改功能,在vue3.0和react里有一些ui组件可以直接满足这个功能,但是在vue2.0内还是需要自行改造的,
具体思路如下:
1、使用components功能,给table的columns增加拖拽时用到的方法,
kotlin
<a-table
:bordered="true"
:id="dragTableId"
:columns="filterColumns"
:data-source="data"
:pagination="{
pageSize: 20
}"
:scroll="{ y: scrollY, x: '100%' }"
tableLayout="fixed"
:components="components"
/>
this.components = {
header: {
cell: (h, props, children) => {
return this.setDragCell(h, props, children, {
isOnlyChangeWidth: false,
isTableCanDrag: true,
tableColumns: this.columns, // 全部内容
filterColumns: this.filterColumns, // 筛选内容,当前展示的内容,需要根据表结构封装
boxId: 'project-list', // 页面id
tableId: this.dragTableId // 表的id
})
}
}
}
2、点击表头文字时新增一个表头icon表示可以移动,
ini
removeDom () { // 清除表头icon
const thDom = document.getElementById('thDom')
if (thDom) {
thDom.remove()
}
window.removeEventListener('mousemove', this.moveWhthMouseMethod, false)
},
createDom (col, boxId) { // 初始化表头icon
const thDom = document.createElement('li')
thDom.id = 'thDom'
thDom.innerText = col.title || col.remarkTitle
var box = document.getElementById(boxId)
box.appendChild(thDom)
window.addEventListener('mousemove', this.moveWhthMouseMethod, false)
},
moveWhthMouseMethod (ev) { // 创建表头鼠标跟随元素
const thDom = document.getElementById('thDom')
let addPageX = 0
let addPageY = 0
if (this.dragTableId === 'planTableId') {
addPageX = -50
addPageY = -30
}
thDom.style.left = (ev.pageX - 300 - addPageX) + 'px'
thDom.style.top = (ev.pageY - 150 - addPageY) + 'px'
},
3、在鼠标移动的时候计算当前移动的位置,在相应的位置增加蓝色的高亮,
4、鼠标放松的时候删除蓝色高亮、表头icon、移动表格原列,增加表格新的移动列,
ini
getTargetIndex (filterColumns, targetWidth, colIndex, colWidth) { // 监听当前的拖拽到列表的哪个下标,注意前拖和后拖的区别
if (targetWidth === 0) return
if (targetWidth > 0 && targetWidth < colWidth) return
if (targetWidth < 0 && targetWidth > -30) return
let lenWidth = 0
let targetIndex = null
if (targetWidth > 0) { // 向后拖
for (let i = 0; i < filterColumns.length; i++) {
if (i > colIndex) {
lenWidth += filterColumns[i].width
if (lenWidth > targetWidth - colWidth) {
targetIndex = i
return targetIndex // 往index右边放置
}
}
}
} else { // 向前拖 反向遍历
for (let i = filterColumns.length - 1; i >= 0; i--) {
if (i < colIndex) {
lenWidth -= filterColumns[i].width
if (lenWidth < targetWidth) {
targetIndex = i
return targetIndex // 往index左放置
}
}
}
}
},
removeHighLight (tableId) { // 清除蓝色高亮线
const thList = document.getElementById(tableId).querySelectorAll('.thHighLight')
if (thList.length < 1) return
for (let i = 0; i < thList.length; i++) {
thList[i].remove()
}
},
createHighLight (tableId, targetIndex, direction) { // 创建蓝色高亮线
const thList = document.getElementById(tableId).querySelectorAll('th')
const thHighLight = document.createElement('span')
thHighLight.classList.add('thHighLight', direction)
thList[targetIndex].appendChild(thHighLight)
const trList = document.getElementById(tableId).querySelector('.ant-table-tbody').querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
const tdList = trList[i].querySelectorAll('td')
const tdHighLight = thHighLight.cloneNode(true)
tdList[targetIndex].style.position = 'relative'
tdList[targetIndex].appendChild(tdHighLight)
}
},
5、把使用的方法返回到columns,
kotlin
const dragChangeColumn = {
key: col.dataIndex + 1 || col.key + 1,
class: ['table-draggable-change-column', col.changeColumnLeft ? 'change-column-left' : ''],
attrs: {
w: (col.width > (attrsWidth.length * 12)) ? attrsWidth.length * 12 : 20,
x: 30,
z: 1,
axis: 'x',
draggable: true,
transform: 'none',
resizable: false
},
draggable: true,
resizable: false,
on: {
// 拖动时把isdrag参数设置为true
dragging: (x) => {
// 建立跟随元素
if (!isOnlyChangeWidth) {
this.disableSelection(tableId)
}
if (!isdom) {
this.createDom(col, boxId)
isdom = true
}
const targetWidth = targetBefore === 0 ? x : x - targetBefore // 本次移动的X值
const targetIndex = this.getTargetIndex(filterColumns, targetWidth, colIndex, col.width) // 移动到的位置
if ((targetIndex || String(targetIndex) === '0') && targetIndexBefore !== targetIndex) {
this.removeHighLight(tableId)
if (targetWidth > colIndex) {
this.createHighLight(tableId, isCanSelect ? targetIndex + 1 : targetIndex, 'right')
} else {
this.createHighLight(tableId, isCanSelect ? targetIndex + 1 : targetIndex, 'left')
}
targetIndexBefore = targetIndex
}
if (targetWidth === 0 || (targetWidth > 0 && targetWidth < col.width) || (targetWidth < 0 && targetWidth > -30)) {
this.removeHighLight(tableId)
targetIndexBefore = null
}
isDrag = true
},
// 拖动结束后把isdrag参数设置为false
dragstop: (x) => {
isDrag = true
this.removeDom() // 删除跟随元素
this.removeHighLight(tableId)
targetIndexBefore = null
isdom = false
// 获取当前元素将要移动到的位置
const targetWidth = targetBefore === 0 ? x : x - targetBefore // 本次移动的X值
const targetIndex = this.getTargetIndex(filterColumns, targetWidth, colIndex, col.width) // 移动到的位置
if ((targetIndex || String(targetIndex) === '0')) {
this.changeColumns(tableColumns, filterColumns, targetIndex, colIndex)
targetBefore = x
}
// eslint-disable-next-line vue/no-async-in-computed-properties
setTimeout(() => {
isDrag = false
}, 300)
if (!isOnlyChangeWidth) {
setTimeout(() => {
this.ableSelection(tableId)
}, 2000)
}
}
}
}
kotlin
// 渲染vue-draggable-resizable插件到column中去,即可实现拖拽
const dragWidth = h('vue-draggable-resizable', { ...dragPropsWidth })
const dragColumn = h('vue-draggable-resizable', { ...dragChangeColumn })
return h('th', { ...restProps, class: 'resize-table-th' }, [...children, dragWidth,
6、初始化时,有历史保存数据就使用,如果没有历史保存数据就使用默认数据,
csharp
async mounted () {
this.tableColumnsNew = await this.getColumns({
tableId: this.dragTableId,
initColumns: this.projectListInitColumns // 默认数据
})
}
7、调整后的Columns会渲染回页面上,并且在接口保存。
kotlin
changeColumnsWidths (tableColumns, filterColumns, colIndex, col) { // 列宽更换时的数据处理
const oldIndex = tableColumns.findIndex(item => { // 获取tableColumns当前列的Index
const k = item.dataIndex || item.key
return k === filterColumns[colIndex].dataIndex
})
this.tableColumnsNew.splice(oldIndex, 1, col)
this.setColumns(this.dragTableId, this.tableColumnsNew)
},
changeColumns (tableColumns, filterColumns, targetIndex, colIndex) { // 列顺序更换的数据处理
const newIndex = tableColumns.findIndex(col => { // 获取tableColumns的目标Index
const k = col.dataIndex || col.key
return k === filterColumns[targetIndex].dataIndex
})
const oldIndex = tableColumns.findIndex(col => { // 获取tableColumns当前列的Index
const k = col.dataIndex || col.key
return k === filterColumns[colIndex].dataIndex
})
const tableColumnsNew = JSON.parse(JSON.stringify(tableColumns))
const newColumn = filterColumns.splice(colIndex, 1)[0]
if (targetIndex > colIndex) {
tableColumnsNew.splice(newIndex + 1, 0, newColumn)
tableColumnsNew.splice(oldIndex, 1)
} else {
tableColumnsNew.splice(oldIndex, 1)
tableColumnsNew.splice(newIndex, 0, newColumn)
}
this.tableColumnsNew = JSON.parse(JSON.stringify(tableColumnsNew))
this.setColumns(this.dragTableId, this.tableColumnsNew)
},
三、完整代码
setTableDragMixin.js
kotlin
import { getAction, postAction } from '@/api/api'
import debounce from 'lodash/debounce' // 防抖
export default {
props: {
},
data () {
this.setColumns = debounce(this.setColumns, 1000) // 表头数据保存时的防抖
this.changeColumnsWidths = debounce(this.changeColumnsWidths, 200) // 列宽更换的防抖
return {
tableColumnsNew: [] // 更改后的Columns
}
},
computed: {
tableColumnsNewString () { // 协助监听tableColumnsNew是否有更改
return JSON.stringify(this.tableColumnsNew)
}
},
beforeDestroy () { // 在组件生命周期结束的时候销毁。
window.removeEventListener('mousemove', this.moveWhthMouseMethod, false)
},
mounted () {
},
methods: {
columnsCheckFilter () { // 筛选表头展示的尾列补全
this.addLastCulumnWidth()
},
addLastCulumnWidth () { // 筛选表头展示的尾列补全,如果筛选表头后,表格宽度不够总表宽,就增加最后一列列宽来补全
let allWidth = 0
if (this.isTableCanSelect) {
allWidth += 60
}
this.tableColumnsNew.map(item => {
if (item.show && item.dataIndex) {
allWidth += item.width
}
})
const dragTableWidth = document.getElementById(this.dragTableId).offsetWidth
const addWidth = (dragTableWidth - allWidth) - 2 // 增加的宽度
if (addWidth > 0) {
for (let i = this.tableColumnsNew.length - 1; i >= 0; i--) {
if (this.tableColumnsNew[i].show && this.tableColumnsNew[i].width) {
this.tableColumnsNew[i].width += addWidth
this.setColumns(this.dragTableId, this.tableColumnsNew)
return
}
}
}
},
ableSelection (tableId) { // 列拖拽结束时页面允许被选中,选中表外其他的元素来取消表内自动选中的状态
const element = document.getElementById(tableId)
element.classList.remove('userUnSelect')
if (this.$refs.focusClick) {
this.$refs.focusClick.focus()
}
},
disableSelection (tableId) { // 列拖拽时页面禁止被选中
const element = document.getElementById(tableId)
element.classList.add('userUnSelect')
},
setColumns (tableId, columns) { // 保存表头信息
const columnDetails = JSON.stringify(columns)
postAction('Windchill/ptc1/user/tableView/createOrUpdate', {
userName: sessionStorage.getItem('username'),
tableName: tableId,
columnDetails: columnDetails
}).then(res => {
if (res.data.success) {
} else {
this.$toast({
text: res.data.message,
type: 'warning'
})
}
})
},
getColumns (params) { // 获取表头
const { tableId, initColumns } = params
return getAction('Windchill/ptc1/user/tableView/selectByUserNameAndTableName', {
userName: sessionStorage.getItem('username'),
tableName: tableId
}).then(res => {
if (res.data.success) {
if (res.data.result) {
return JSON.parse(res.data.result.columnDetails) // zqtodo改回来
// this.setColumns(tableId, initColumns)
// return initColumns
} else { // 未存过信息用初始化表头, 并保存表头
// this.setColumns(tableId, initColumns)
return initColumns
}
} else {
this.$toast({
text: res.data.message,
type: 'warning'
})
}
})
},
changeColumnsWidths (tableColumns, filterColumns, colIndex, col) { // 列宽更换时的数据处理
const oldIndex = tableColumns.findIndex(item => { // 获取tableColumns当前列的Index
const k = item.dataIndex || item.key
return k === filterColumns[colIndex].dataIndex
})
this.tableColumnsNew.splice(oldIndex, 1, col)
this.setColumns(this.dragTableId, this.tableColumnsNew)
},
changeColumns (tableColumns, filterColumns, targetIndex, colIndex) { // 列顺序更换的数据处理
const newIndex = tableColumns.findIndex(col => { // 获取tableColumns的目标Index
const k = col.dataIndex || col.key
return k === filterColumns[targetIndex].dataIndex
})
const oldIndex = tableColumns.findIndex(col => { // 获取tableColumns当前列的Index
const k = col.dataIndex || col.key
return k === filterColumns[colIndex].dataIndex
})
const tableColumnsNew = JSON.parse(JSON.stringify(tableColumns))
const newColumn = filterColumns.splice(colIndex, 1)[0]
if (targetIndex > colIndex) {
tableColumnsNew.splice(newIndex + 1, 0, newColumn)
tableColumnsNew.splice(oldIndex, 1)
} else {
tableColumnsNew.splice(oldIndex, 1)
tableColumnsNew.splice(newIndex, 0, newColumn)
}
this.tableColumnsNew = JSON.parse(JSON.stringify(tableColumnsNew))
this.setColumns(this.dragTableId, this.tableColumnsNew)
},
removeHighLight (tableId) { // 清除蓝色高亮线
const thList = document.getElementById(tableId).querySelectorAll('.thHighLight')
if (thList.length < 1) return
for (let i = 0; i < thList.length; i++) {
thList[i].remove()
}
},
createHighLight (tableId, targetIndex, direction) { // 创建蓝色高亮线
const thList = document.getElementById(tableId).querySelectorAll('th')
const thHighLight = document.createElement('span')
thHighLight.classList.add('thHighLight', direction)
thList[targetIndex].appendChild(thHighLight)
const trList = document.getElementById(tableId).querySelector('.ant-table-tbody').querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
const tdList = trList[i].querySelectorAll('td')
const tdHighLight = thHighLight.cloneNode(true)
tdList[targetIndex].style.position = 'relative'
tdList[targetIndex].appendChild(tdHighLight)
}
},
removeDom () { // 清除表头icon
const thDom = document.getElementById('thDom')
if (thDom) {
thDom.remove()
}
window.removeEventListener('mousemove', this.moveWhthMouseMethod, false)
},
createDom (col, boxId) { // 初始化表头icon
const thDom = document.createElement('li')
thDom.id = 'thDom'
thDom.innerText = col.title || col.remarkTitle
var box = document.getElementById(boxId)
box.appendChild(thDom)
window.addEventListener('mousemove', this.moveWhthMouseMethod, false)
},
moveWhthMouseMethod (ev) { // 创建表头鼠标跟随元素
const thDom = document.getElementById('thDom')
let addPageX = 0
let addPageY = 0
if (this.dragTableId === 'planTableId') {
addPageX = -50
addPageY = -30
}
thDom.style.left = (ev.pageX - 300 - addPageX) + 'px'
thDom.style.top = (ev.pageY - 150 - addPageY) + 'px'
},
getTargetIndex (filterColumns, targetWidth, colIndex, colWidth) { // 监听当前的拖拽到列表的哪个下标,注意前拖和后拖的区别
if (targetWidth === 0) return
if (targetWidth > 0 && targetWidth < colWidth) return
if (targetWidth < 0 && targetWidth > -30) return
let lenWidth = 0
let targetIndex = null
if (targetWidth > 0) { // 向后拖
for (let i = 0; i < filterColumns.length; i++) {
if (i > colIndex) {
lenWidth += filterColumns[i].width
if (lenWidth > targetWidth - colWidth) {
targetIndex = i
return targetIndex // 往index右边放置
}
}
}
} else { // 向前拖 反向遍历
for (let i = filterColumns.length - 1; i >= 0; i--) {
if (i < colIndex) {
lenWidth -= filterColumns[i].width
if (lenWidth < targetWidth) {
targetIndex = i
return targetIndex // 往index左放置
}
}
}
}
},
setDragCell (h, props, children, params) { // 给table的columns增加拖拽时用到的方法,
const { key, ...restProps } = props
const { isTableCanDrag, tableColumns, filterColumns, boxId, tableId, isOnlyChangeWidth, isTableCanSelect } = params
// 非必填 - isTableCanSelect: 是否可以勾选(表是否有前面那一排)
// 必填 - isOnlyChangeWidth: 是否只允许改变列宽,不允许改变列排序
// 必填 - isTableCanDrag: 是否允许允许改变列宽和改变列排序
// 必填 - tableColumns: 全部列内容
// 必填 - filterColumns: 展示列内容
// 非必填 - boxId: 页面id
// 必填 - tableId: 表格id
if (!isTableCanDrag) { // 如果不添加功能就直接不给功能
return h('th', { ...restProps }, [...children])
}
// 由于drag事件结束后必然会出发click事件,所以我们需要一个参数去判断当前操作是click点击事件还是drag拖拽事件
let isDrag = false
// 获取当前key值所对应的column信息
const col = filterColumns.find(col => {
const k = col.dataIndex || col.key
return k === key
})
const colIndex = filterColumns.findIndex(col => {
const k = col.dataIndex || col.key
return k === key
})
// 如果col没有设置宽度,则直接原样渲染回去,不添加拉伸表格功能
if (!col || !col.width) {
return h('th', { ...restProps }, [...children])
}
// 如果存在宽度,则设置vue-draggable-resizable插件渲染参数,具体可以参考相关文档
const dragPropsWidth = {
key: col.dataIndex || col.key,
class: (filterColumns.length - 2) === colIndex ? 'table-draggable-handle-last' : 'table-draggable-handle',
attrs: {
w: 10,
x: col.width,
z: 1,
axis: 'x',
draggable: true,
transform: 'none',
resizable: false
},
draggable: true,
resizable: false,
on: {
// 拖动时把isdrag参数设置为true
dragging: (x) => {
isDrag = true
col.width = Math.max(x, 50)
},
// 拖动结束后把isdrag参数设置为false
dragstop: () => {
isDrag = true
// eslint-disable-next-line vue/no-async-in-computed-properties
setTimeout(() => {
isDrag = false
this.changeColumnsWidths(tableColumns, filterColumns, colIndex, col)
}, 300)
}
}
}
if (isOnlyChangeWidth) {
const dragWidth = h('vue-draggable-resizable', { ...dragPropsWidth })
return h('th', { ...restProps, class: 'resize-table-th' }, [...children, dragWidth])
}
let targetBefore = 0 // 当前x是否是上次的累加值
let targetIndexBefore = null // 当前x是否是上次的累加值
let isdom = false // 当前是否有跟随元素
let isCanSelect = false // 我负责的,我创建的等列可选,前面多一行拉动时清除选中,改正高亮
if (isTableCanSelect) {
isCanSelect = true
}
const attrsWidth = col.title || col.remarkTitle
const dragChangeColumn = {
key: col.dataIndex + 1 || col.key + 1,
class: ['table-draggable-change-column', col.changeColumnLeft ? 'change-column-left' : ''],
attrs: {
w: (col.width > (attrsWidth.length * 12)) ? attrsWidth.length * 12 : 20,
x: 30,
z: 1,
axis: 'x',
draggable: true,
transform: 'none',
resizable: false
},
draggable: true,
resizable: false,
on: {
// 拖动时把isdrag参数设置为true
dragging: (x) => {
// 建立跟随元素
if (!isOnlyChangeWidth) {
this.disableSelection(tableId)
}
if (!isdom) {
this.createDom(col, boxId)
isdom = true
}
const targetWidth = targetBefore === 0 ? x : x - targetBefore // 本次移动的X值
const targetIndex = this.getTargetIndex(filterColumns, targetWidth, colIndex, col.width) // 移动到的位置
if ((targetIndex || String(targetIndex) === '0') && targetIndexBefore !== targetIndex) {
this.removeHighLight(tableId)
if (targetWidth > colIndex) {
this.createHighLight(tableId, isCanSelect ? targetIndex + 1 : targetIndex, 'right')
} else {
this.createHighLight(tableId, isCanSelect ? targetIndex + 1 : targetIndex, 'left')
}
targetIndexBefore = targetIndex
}
if (targetWidth === 0 || (targetWidth > 0 && targetWidth < col.width) || (targetWidth < 0 && targetWidth > -30)) {
this.removeHighLight(tableId)
targetIndexBefore = null
}
isDrag = true
},
// 拖动结束后把isdrag参数设置为false
dragstop: (x) => {
isDrag = true
this.removeDom() // 删除跟随元素
this.removeHighLight(tableId)
targetIndexBefore = null
isdom = false
// 获取当前元素将要移动到的位置
const targetWidth = targetBefore === 0 ? x : x - targetBefore // 本次移动的X值
const targetIndex = this.getTargetIndex(filterColumns, targetWidth, colIndex, col.width) // 移动到的位置
if ((targetIndex || String(targetIndex) === '0')) {
this.changeColumns(tableColumns, filterColumns, targetIndex, colIndex)
targetBefore = x
}
// eslint-disable-next-line vue/no-async-in-computed-properties
setTimeout(() => {
isDrag = false
}, 300)
if (!isOnlyChangeWidth) {
setTimeout(() => {
this.ableSelection(tableId)
}, 2000)
}
}
}
}
// 取出column的click事件,对事件进行判断,如果现在isDrag参数为true,则截胡,防止拖动后触发click事件
if (restProps.on && restProps.on.click) {
const clickFunc = restProps.on.click
restProps.on.click = (event) => {
if (isDrag) {
return
}
clickFunc(event)
}
}
// 渲染vue-draggable-resizable插件到column中去,即可实现拖拽
const dragWidth = h('vue-draggable-resizable', { ...dragPropsWidth })
const dragColumn = h('vue-draggable-resizable', { ...dragChangeColumn })
return h('th', { ...restProps, class: 'resize-table-th' }, [...children, dragWidth, dragColumn])
}
}
}
project-list.vue 引用方法文件
xml
<template>
<!-- id 一定要写-->
<div class="project-list" id="project-list">
<customFilter
class="position-efined-customFilter"
:columns="columns"
:type="'projectList'"
@columnsCheckFilter="columnsCheckFilter"/>
<!-- 增加components, id, :scroll-->
<a-table
:bordered="true"
:id="dragTableId"
:columns="filterColumns"
:data-source="data"
:pagination="{
pageSize: 20
}"
:scroll="{ y: scrollY, x: '100%' }"
tableLayout="fixed"
:components="components"
/>
</div>
</template>
<script>
import { projectListInitColumns } from './displayConfig/displayConfig'
import setTableDragMixin from '@/components/utils/setTableDragMixin.js'
const customFilter = () => import('@/components/display/custom-filter.vue')
export default {
mixins: [setTableDragMixin],
components: {
customFilter
},
data () {
this.components = {
header: {
cell: (h, props, children) => {
return this.setDragCell(h, props, children, {
isOnlyChangeWidth: false,
isTableCanDrag: true,
tableColumns: this.columns, // 全部内容
filterColumns: this.filterColumns, // 筛选内容,当前展示的内容,需要根据表结构封装
boxId: 'project-list', // 页面id
tableId: this.dragTableId // 表的id
})
}
}
}
return {
// data需要增加show/dataIndex/width/最后的空title
projectListInitColumns,
dragTableId: 'project-list-table',
columns: []
}
},
computed: {
filterColumns () {
if (this.columns && this.columns.length > 0) {
return this.columns.filter((col) => { if (col.show) { return col } })
} else {
return []
}
}
},
watch: {
tableColumnsNewString: {
deep: true,
immediate: true,
handler (val) {
if (val && val.length > 2) {
this.columns = this.tableColumnsNew
// this.setColumns(this.dragTableId, this.columns)
this.columns.forEach((item, index) => { // 如果需要合并列,要重新渲染, 该参数无法保存在接口
if (!item.customCell && this.infoColumns[index].customCell) {
item.customCell = (record) => this.defineUpdateCustomCell(record, item.dataIndex)
}
return item
})
}
}
}
},
async mounted () {
this.tableColumnsNew = await this.getColumns({
tableId: this.dragTableId,
initColumns: this.projectListInitColumns
})
this.addLastCulumnWidth()
}
</script>
四、优化建议
当前的表头存储的是全部数据。因此在读取表头数据时,如果表头中使用了某些方法,需要在监听里面重新赋值方法才能成功调用,由于这个设计,每次在更改表头默认信息时,都需要后端手动清除历史保存数据。在对比了Choerodon UI 的拖拉拽组件以后,建议在使用这组方法时,把当前的数据改造为只存储表头的id, 列宽,列顺序的存储格式,同时加上对应的通用读取数据的方法,能够更好的使用功能和维护数据。