557 lines
12 KiB
Vue
557 lines
12 KiB
Vue
|
<!--
|
|||
|
* @Author: your name
|
|||
|
* @Date: 2020-09-08 16:22:26
|
|||
|
* @LastEditTime: 2021-02-04 16:52:15
|
|||
|
* @LastEditors: Please set LastEditors
|
|||
|
* @Description: In User Settings Edit
|
|||
|
* @FilePath: \dbadmin\src\components\TableForm\Table.vue
|
|||
|
-->
|
|||
|
<template>
|
|||
|
<div class="table__component" ref="root" :style="rootStyle">
|
|||
|
<el-button-group>
|
|||
|
<!-- 普通功能按钮插槽 -->
|
|||
|
<slot
|
|||
|
name="btns"
|
|||
|
:selectable="multiselect && selectedRows.length > 0"
|
|||
|
:disabled="!multiselect || selectedRows.length === 0"
|
|||
|
:selectrows="selectedRows"
|
|||
|
:multiStepAction="multiStepReq"
|
|||
|
></slot>
|
|||
|
|
|||
|
<!-- 数据导出按钮插槽 -->
|
|||
|
<slot v-if="allowExport" name="export-btn" :disabled="selectedRows.length === 0" :action="exportData" :loading="exporting"></slot>
|
|||
|
</el-button-group>
|
|||
|
|
|||
|
<el-table
|
|||
|
:data="list"
|
|||
|
@selection-change="onSelectionChange"
|
|||
|
@current-change="$emit('current-change', $event)"
|
|||
|
ref="table"
|
|||
|
:height="tableHeight"
|
|||
|
v-loading="loading"
|
|||
|
:span-method="SpanMethod"
|
|||
|
:default-expand-all="expandAll"
|
|||
|
:class="tableClass"
|
|||
|
v-bind="tableOptions"
|
|||
|
>
|
|||
|
<el-table-column v-if="enableDrag" v-bind="dragColumnOptions">
|
|||
|
<i slot-scope="{ row }" class="el-icon-s-fold handle" style="font-size: 16px; cursor: move"></i>
|
|||
|
</el-table-column>
|
|||
|
<el-table-column type="selection" v-if="multiselect || allowExport" />
|
|||
|
<slot></slot>
|
|||
|
</el-table>
|
|||
|
|
|||
|
<!-- 分页 -->
|
|||
|
<div v-if="visiblePage" class="page">
|
|||
|
<el-pagination
|
|||
|
:total="total"
|
|||
|
:page-size="size"
|
|||
|
background
|
|||
|
layout="sizes, prev, pager, next"
|
|||
|
:current-page.sync="currentPage"
|
|||
|
:page-sizes="[...(isDev ? [1, 2, 5] : []), 10, 20, 50, 100, 200]"
|
|||
|
@size-change="onSizeChange"
|
|||
|
></el-pagination>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</template>
|
|||
|
|
|||
|
<script>
|
|||
|
import { get, del as RequestDelete } from '@/utils/request'
|
|||
|
import Vue from 'vue'
|
|||
|
import { Component, Prop, Watch, Ref, ProvideReactive, Provide } from 'vue-property-decorator'
|
|||
|
import { toTime, toDateTime, toDate } from '@/utils/util'
|
|||
|
import { downloadXlsx } from '@/utils/excel'
|
|||
|
import { debounce } from 'underscore'
|
|||
|
import { getModule } from 'vuex-module-decorators'
|
|||
|
import QueryStore, { DEFAULT_TAG as DEFAULT_QUERY_TAG } from '@/store/modules/query'
|
|||
|
import Sortable from 'sortablejs'
|
|||
|
|
|||
|
// Table组件只读取不写vuex里面的查询参数,但会修改分页参数(入currentPage,pageSize)
|
|||
|
import store from '@/store'
|
|||
|
const queryStore = getModule(QueryStore, store)
|
|||
|
|
|||
|
@Component({
|
|||
|
name: 'Table'
|
|||
|
})
|
|||
|
export default class Table extends Vue {
|
|||
|
@Prop({ type: Boolean, default: false })
|
|||
|
enableDrag
|
|||
|
|
|||
|
@Prop({ type: Object, default: () => ({}) })
|
|||
|
dragColumnOptions
|
|||
|
|
|||
|
@Prop({ type: Object, default: () => ({}) })
|
|||
|
tableOptions
|
|||
|
|
|||
|
@Prop({ type: Boolean, default: false })
|
|||
|
expandAll
|
|||
|
|
|||
|
@Prop({ type: String })
|
|||
|
tableClass
|
|||
|
|
|||
|
@Prop({ type: Boolean, default: false })
|
|||
|
multiselect
|
|||
|
|
|||
|
@Prop({ type: Boolean, default: true })
|
|||
|
visiblePage
|
|||
|
|
|||
|
@Prop({ type: Function })
|
|||
|
responseHandler
|
|||
|
|
|||
|
/**
|
|||
|
* 是否允许导出数据
|
|||
|
*/
|
|||
|
@Prop({ type: Boolean, default: false })
|
|||
|
allowExport
|
|||
|
|
|||
|
/**
|
|||
|
* 导出数据时需要特殊处理的字段
|
|||
|
* {
|
|||
|
* [字段名]:处理方式
|
|||
|
* }
|
|||
|
*/
|
|||
|
@Prop({ type: Object, default: () => ({}) })
|
|||
|
exportSpecialField
|
|||
|
|
|||
|
@Prop({ type: String, required: true })
|
|||
|
api
|
|||
|
|
|||
|
@Prop({ type: Function })
|
|||
|
SpanMethod
|
|||
|
|
|||
|
@Prop({ type: [String, Number] })
|
|||
|
height
|
|||
|
|
|||
|
@Prop({ type: String, default: DEFAULT_QUERY_TAG })
|
|||
|
tag
|
|||
|
|
|||
|
@Ref('table')
|
|||
|
tableRef
|
|||
|
|
|||
|
@Ref('root')
|
|||
|
rootRef
|
|||
|
|
|||
|
/**
|
|||
|
* 当前页码
|
|||
|
*/
|
|||
|
// currentPage = 1
|
|||
|
|
|||
|
get currentPage() {
|
|||
|
if (!this.pageData) return 1
|
|||
|
if (!this.pageData.page) this.pageData.page = 1
|
|||
|
return this.pageData.page
|
|||
|
}
|
|||
|
|
|||
|
set currentPage(page) {
|
|||
|
if (!this.pageData) return
|
|||
|
this.pageData.page = page
|
|||
|
return page
|
|||
|
}
|
|||
|
|
|||
|
// size = 20
|
|||
|
|
|||
|
get size() {
|
|||
|
if (!this.pageData) return 20
|
|||
|
if (!this.pageData.size) this.pageData.size = 20
|
|||
|
return this.pageData.size
|
|||
|
}
|
|||
|
|
|||
|
set size(size) {
|
|||
|
if (!this.pageData) return
|
|||
|
this.pageData.size = size
|
|||
|
return size
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 列表数据
|
|||
|
*/
|
|||
|
@ProvideReactive('table_data')
|
|||
|
list = []
|
|||
|
|
|||
|
/**
|
|||
|
* 列表数据是否加载中
|
|||
|
*/
|
|||
|
loading = false
|
|||
|
|
|||
|
/**
|
|||
|
* 列表数据总长度
|
|||
|
*/
|
|||
|
total = 1
|
|||
|
|
|||
|
/**
|
|||
|
* 选中的行
|
|||
|
*/
|
|||
|
selectedRows = []
|
|||
|
|
|||
|
/**
|
|||
|
* 数据导出处理中
|
|||
|
*/
|
|||
|
exporting = false
|
|||
|
|
|||
|
rootHeight = null
|
|||
|
|
|||
|
tableHeightData = 50
|
|||
|
|
|||
|
get tableHeight() {
|
|||
|
if (this.propHeightOk) {
|
|||
|
return this.height
|
|||
|
}
|
|||
|
|
|||
|
return this.tableHeightData
|
|||
|
}
|
|||
|
|
|||
|
get propHeightOk() {
|
|||
|
return ['string', 'number'].includes(typeof this.height)
|
|||
|
}
|
|||
|
|
|||
|
get rootStyle() {
|
|||
|
const style = {}
|
|||
|
if (this.rootHeight) {
|
|||
|
style.height = `${this.rootHeight}px`
|
|||
|
}
|
|||
|
|
|||
|
return style
|
|||
|
}
|
|||
|
|
|||
|
pageData = {}
|
|||
|
|
|||
|
get path() {
|
|||
|
return this.$route.path
|
|||
|
}
|
|||
|
|
|||
|
async created() {
|
|||
|
queryStore.init({ path: this.path, tag: this.tag })
|
|||
|
queryStore.registerFetch({ path: this.path, func: this.fetch, tag: this.tag })
|
|||
|
|
|||
|
this.$set(this, 'pageData', queryStore.pageData[this.$route.path][this.tag])
|
|||
|
await this.fetch()
|
|||
|
this.resizeHeight()
|
|||
|
}
|
|||
|
|
|||
|
async mounted() {
|
|||
|
this.initSortable()
|
|||
|
window.addEventListener('resize', this.resizeHeight)
|
|||
|
await this.$nextTick()
|
|||
|
}
|
|||
|
|
|||
|
initSortable() {
|
|||
|
if (!this.enableDrag) return
|
|||
|
const tbody = this.tableRef.$el.querySelector('.el-table__body-wrapper table tbody')
|
|||
|
if (!tbody) return
|
|||
|
|
|||
|
const onEnd = async event => {
|
|||
|
const list = this.list.splice(0)
|
|||
|
await this.$nextTick()
|
|||
|
const { oldIndex, newIndex } = event
|
|||
|
const oldObj = list[oldIndex]
|
|||
|
const newObj = list[newIndex]
|
|||
|
oldObj.sort = newIndex
|
|||
|
newObj.sort = oldIndex
|
|||
|
list[oldIndex] = newObj
|
|||
|
list[newIndex] = oldObj
|
|||
|
this.list.push(...list)
|
|||
|
this.$emit('sort', this.list)
|
|||
|
this.$emit('sort-change', { newObj, oldObj })
|
|||
|
}
|
|||
|
|
|||
|
new Sortable(tbody, {
|
|||
|
group: `TableComponent:${this._uid}`,
|
|||
|
handle: '.el-icon-s-fold.handle',
|
|||
|
draggable: 'tr.el-table__row',
|
|||
|
onEnd
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
@Watch('api')
|
|||
|
onWatchApiChange(api) {
|
|||
|
this.currentPage = 1
|
|||
|
this.fetch()
|
|||
|
}
|
|||
|
|
|||
|
beforeDestroy() {
|
|||
|
window.removeEventListener('resize', this.resizeHeight)
|
|||
|
queryStore.unregisterFetch(this.path, this.tag)
|
|||
|
}
|
|||
|
|
|||
|
async setHeight() {
|
|||
|
if (this.propHeightOk) return
|
|||
|
if (!this.tableRef) await this.$nextTick()
|
|||
|
if (!this.tableRef || !this.tableRef.$el) return
|
|||
|
this.rootHeight = document.body.clientHeight - this.tableRef.$el.offsetTop - 60
|
|||
|
this.tableHeightData = this.rootHeight - 85
|
|||
|
}
|
|||
|
|
|||
|
resizeHeight = debounce(this.setHeight.bind(this), 300)
|
|||
|
|
|||
|
@Watch('currentPage')
|
|||
|
/**
|
|||
|
* 页码有变化时获取数据
|
|||
|
*/
|
|||
|
onWatchCurrentPageChange(page) {
|
|||
|
this.fetch()
|
|||
|
}
|
|||
|
|
|||
|
onSizeChange(size) {
|
|||
|
this.size = size
|
|||
|
this.currentPage = 1
|
|||
|
this.fetch()
|
|||
|
}
|
|||
|
|
|||
|
onSelectionChange(rows) {
|
|||
|
this.$emit('selection-change', rows)
|
|||
|
this.$set(this, 'selectedRows', rows)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 导出数据
|
|||
|
* @param {string} name 工作薄名
|
|||
|
* @param {string} filename 文件名
|
|||
|
* @param {Array} 英文字段-中文转换表
|
|||
|
*/
|
|||
|
exportData(name, filename, fields) {
|
|||
|
if (this.exporting) return
|
|||
|
this.exporting = true
|
|||
|
const data = this.selectedRows.map(item => {
|
|||
|
const result = {}
|
|||
|
|
|||
|
fields.forEach(([en, cn]) => {
|
|||
|
result[cn] = item[en]
|
|||
|
|
|||
|
// 特殊处理的字段
|
|||
|
if (en in this.exportSpecialField) {
|
|||
|
const type = this.exportSpecialField[en]
|
|||
|
switch (type) {
|
|||
|
case 'time':
|
|||
|
result[cn] = toTime(result[cn])
|
|||
|
return
|
|||
|
case 'date-time':
|
|||
|
result[cn] = toDateTime(result[cn])
|
|||
|
return
|
|||
|
case 'date':
|
|||
|
result[cn] = toDate(result[cn])
|
|||
|
return
|
|||
|
default:
|
|||
|
break
|
|||
|
}
|
|||
|
} else if (typeof result[cn] === 'boolean') {
|
|||
|
result[cn] = result[cn] ? '是' : '否'
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
return result
|
|||
|
})
|
|||
|
downloadXlsx(
|
|||
|
{
|
|||
|
[name]: data
|
|||
|
},
|
|||
|
filename
|
|||
|
)
|
|||
|
this.exporting = false
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取列表数据
|
|||
|
*/
|
|||
|
@Provide('fetch')
|
|||
|
async fetch(query) {
|
|||
|
if (this.loading) return
|
|||
|
this.loading = true
|
|||
|
let data, params
|
|||
|
|
|||
|
if (query) {
|
|||
|
this.currentPage = 1
|
|||
|
params = query
|
|||
|
} else {
|
|||
|
params = queryStore.data[this.path][this.tag]
|
|||
|
}
|
|||
|
|
|||
|
Object.entries(params).forEach(([key, value]) => {
|
|||
|
if ([undefined, NaN, null, ''].includes(value) || key === '__ob__') {
|
|||
|
delete params[key]
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
const page_params = {}
|
|||
|
|
|||
|
if (this.visiblePage) {
|
|||
|
page_params.limit = this.size
|
|||
|
page_params.page = this.currentPage
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
data = await get(this.api, {
|
|||
|
params: {
|
|||
|
...params,
|
|||
|
...page_params
|
|||
|
}
|
|||
|
})
|
|||
|
this.$emit('response', data)
|
|||
|
} catch (err) {
|
|||
|
this.loading = false
|
|||
|
return
|
|||
|
}
|
|||
|
this.total = data.count
|
|||
|
this.currentPage = data.page || data.currentPage
|
|||
|
const list = data.results.map(detail => {
|
|||
|
if (detail.created_time) {
|
|||
|
detail.created_time = toDateTime(detail.created_time)
|
|||
|
}
|
|||
|
if (detail.date_joined) {
|
|||
|
detail.date_joined = toDateTime(detail.date_joined)
|
|||
|
}
|
|||
|
if (detail.release_time) {
|
|||
|
detail.release_time = toDateTime(detail.release_time)
|
|||
|
}
|
|||
|
if (this.responseHandler) return this.responseHandler(detail)
|
|||
|
return detail
|
|||
|
})
|
|||
|
this.$emit('fetch-success', list)
|
|||
|
this.list.splice(0)
|
|||
|
this.$set(this, 'list', list)
|
|||
|
await this.$nextTick()
|
|||
|
this.$emit('get-table-ref', this.$refs.table)
|
|||
|
this.loading = false
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 下一页
|
|||
|
*/
|
|||
|
nextPage() {
|
|||
|
this.currentPage++
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 上一页
|
|||
|
*/
|
|||
|
prevPage() {
|
|||
|
this.currentPage--
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 重置当前页码
|
|||
|
*/
|
|||
|
resetPage() {
|
|||
|
this.currentPage = 1
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 切换某行某列的布尔值
|
|||
|
* @param {number} rowIndex 行
|
|||
|
* @param {string} name 字段
|
|||
|
* @param {boolean} status 修改的布尔值
|
|||
|
*/
|
|||
|
toggleColumnBoolean(rowIndex, name, status) {
|
|||
|
const detail = this.list[rowIndex]
|
|||
|
if (!detail) throw new Error(`列${rowIndex}不存在`)
|
|||
|
if (typeof detail[name] !== 'boolean') throw new Error(`列${rowIndex}-键${name}不是布尔值`)
|
|||
|
const oldValue = detail[name]
|
|||
|
detail[name] = status
|
|||
|
this.$emit('column-bool-change', {
|
|||
|
row: rowIndex,
|
|||
|
name,
|
|||
|
value: status,
|
|||
|
oldValue
|
|||
|
})
|
|||
|
return status
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 设置某行某列的值
|
|||
|
* @param {number} rowIndex 行
|
|||
|
* @param {string} name 字段
|
|||
|
* @param {any} value 修改的值
|
|||
|
*/
|
|||
|
setColumnValue(rowIndex, name, value) {
|
|||
|
const detail = this.list[rowIndex]
|
|||
|
if (!detail) throw new Error(`列${rowIndex}不存在`)
|
|||
|
const oldValue = detail[name]
|
|||
|
detail[name] = value
|
|||
|
this.$emit('column-value-change', {
|
|||
|
row: rowIndex,
|
|||
|
name,
|
|||
|
value,
|
|||
|
oldValue
|
|||
|
})
|
|||
|
return value
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 格式化日期对象
|
|||
|
* @param {string|Date} date 日期对象/字符串
|
|||
|
* @returns {string}
|
|||
|
*/
|
|||
|
formatDate(date, time = true) {
|
|||
|
return time ? toDateTime(date) : toDate(date)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 格式化日期对象
|
|||
|
* @param {string|Date} date 日期对象/字符串
|
|||
|
* @returns {string}
|
|||
|
*/
|
|||
|
formatTime(date) {
|
|||
|
return toTime(date)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 调用删除接口
|
|||
|
* @param {string} api 接口地址
|
|||
|
* @param {string} message 提示消息
|
|||
|
* @returns {Promise<boolean>} 是否删除成功
|
|||
|
*/
|
|||
|
async del(api, message = '是否确认删除') {
|
|||
|
await this.$confirm(message)
|
|||
|
try {
|
|||
|
await RequestDelete(api)
|
|||
|
} catch (err) {
|
|||
|
console.error(`
|
|||
|
Table组件调用删除接口失败
|
|||
|
接口地址 ${api}
|
|||
|
原因:
|
|||
|
${err.message || err}
|
|||
|
`)
|
|||
|
return false
|
|||
|
}
|
|||
|
this.fetch()
|
|||
|
return true
|
|||
|
}
|
|||
|
|
|||
|
async multiStepReq(ids, req, options = {}) {
|
|||
|
const baseOptions = {
|
|||
|
confirmMessage: '是否确认此操作?',
|
|||
|
successMessage: '批量操作成功'
|
|||
|
}
|
|||
|
options = Object.assign({}, baseOptions, options)
|
|||
|
await this.$confirm(options.confirmMessage)
|
|||
|
for (const id of ids) {
|
|||
|
await req(id, this.selectedRows)
|
|||
|
}
|
|||
|
this.$message.success(options.successMessage)
|
|||
|
this.fetch()
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<style lang="scss" scoped>
|
|||
|
.page {
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
margin-top: 1em;
|
|||
|
}
|
|||
|
</style>
|
|||
|
|
|||
|
<style lang="scss">
|
|||
|
.mo3 {
|
|||
|
overflow: hidden;
|
|||
|
-webkit-line-clamp: 3;
|
|||
|
-webkit-box-orient: vertical;
|
|||
|
display: -webkit-box;
|
|||
|
text-overflow: ellipsis;
|
|||
|
}
|
|||
|
|
|||
|
.el-tooltip__popper {
|
|||
|
max-width: 500px;
|
|||
|
}
|
|||
|
</style>
|