You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

557 lines
12 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
* @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里面的查询参数但会修改分页参数入currentPagepageSize
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>