添加表格导出功能

This commit is contained in:
GGJ
2024-12-26 15:56:32 +08:00
parent aed771578a
commit 46dc032c4c
14 changed files with 2453 additions and 2827 deletions

View File

@@ -42,6 +42,7 @@
"vue-draggable-resizable": "3.0.0-beta.2", "vue-draggable-resizable": "3.0.0-beta.2",
"vue-router": "4", "vue-router": "4",
"vxe-table": "^4.5.17", "vxe-table": "^4.5.17",
"vxe-table-plugin-export-xlsx": "^4.0.7",
"xe-utils": "^3.5.14" "xe-utils": "^3.5.14"
}, },
"devDependencies": { "devDependencies": {

4987
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@
</el-button> </el-button>
<el-button @click="onComSearch" v-if="showSearch" type="primary" :icon="Search">查询</el-button> <el-button @click="onComSearch" v-if="showSearch" type="primary" :icon="Search">查询</el-button>
<el-button @click="onResetForm" v-if="showSearch && showReset" :icon="RefreshLeft">重置</el-button> <el-button @click="onResetForm" v-if="showSearch && showReset" :icon="RefreshLeft">重置</el-button>
<el-button @click="onExport" v-if="showExport" :loading="tableStore.table.loading" type="primary"
icon="el-icon-Download">导出</el-button>
</template> </template>
<slot name="operation"></slot> <slot name="operation"></slot>
</div> </div>
@@ -55,6 +57,7 @@ interface Props {
nextFlag?: boolean //控制时间是否可以往后推 nextFlag?: boolean //控制时间是否可以往后推
theCurrentTime?: boolean //控制时间前3天展示上个月时间 theCurrentTime?: boolean //控制时间前3天展示上个月时间
showReset?: boolean //是否显示重置 showReset?: boolean //是否显示重置
showExport?: boolean //导出控制
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -63,7 +66,8 @@ const props = withDefaults(defineProps<Props>(), {
showSearch: true, showSearch: true,
nextFlag: false, nextFlag: false,
theCurrentTime: true, theCurrentTime: true,
showReset: true showReset: true,
showExport:false
}) })
// 动态计算table高度 // 动态计算table高度
let resizeObserver = new ResizeObserver(entries => { let resizeObserver = new ResizeObserver(entries => {
@@ -169,7 +173,10 @@ const onResetForm = () => {
const setInterval = (val: any) => { const setInterval = (val: any) => {
datePickerRef.value.setInterval(val) datePickerRef.value.setInterval(val)
} }
// 导出
const onExport = () => {
tableStore.onTableAction('export', {showAllFlag:true})
}
defineExpose({ onComSearch, areaRef, setDatePicker, setInterval, datePickerRef, showSelectChange, computedSearchRow }) defineExpose({ onComSearch, areaRef, setDatePicker, setInterval, datePickerRef, showSelectChange, computedSearchRow })
</script> </script>

View File

@@ -1,40 +1,23 @@
<template> <template>
<div :style="{ height: tableStore.table.height }"> <div :style="{ height: tableStore.table.height }">
<vxe-table <vxe-table ref="tableRef" height="auto" :data="tableStore.table.data" v-loading="tableStore.table.loading"
ref="tableRef" v-bind="Object.assign({}, defaultAttribute, $attrs)" @checkbox-all="selectChangeEvent"
height="auto" @checkbox-change="selectChangeEvent" :showOverflow="showOverflow">
:data="tableStore.table.data"
v-loading="tableStore.table.loading"
v-bind="Object.assign({}, defaultAttribute, $attrs)"
@checkbox-all="selectChangeEvent"
@checkbox-change="selectChangeEvent"
>
<!-- Column 组件内部是 el-table-column --> <!-- Column 组件内部是 el-table-column -->
<template v-if="isGroup"> <template v-if="isGroup">
<GroupColumn :column="tableStore.table.column" /> <GroupColumn :column="tableStore.table.column" />
</template> </template>
<template v-else> <template v-else>
<Column <Column :attr="item" :key="key + '-column'" v-for="(item, key) in tableStore.table.column"
:attr="item" :tree-node="item.treeNode">
:key="key + '-column'"
v-for="(item, key) in tableStore.table.column"
:tree-node="item.treeNode"
>
<!-- tableStore 预设的列 render 方案 --> <!-- tableStore 预设的列 render 方案 -->
<template v-if="item.render" #default="scope"> <template v-if="item.render" #default="scope">
<FieldRender <FieldRender :field="item" :row="scope.row" :column="scope.column" :index="scope.rowIndex" :key="key +
:field="item" '-' +
:row="scope.row" item.render +
:column="scope.column" '-' +
:index="scope.rowIndex" (item.field ? '-' + item.field + '-' + scope.row[item.field] : '')
:key=" " />
key +
'-' +
item.render +
'-' +
(item.field ? '-' + item.field + '-' + scope.row[item.field] : '')
"
/>
</template> </template>
</Column> </Column>
</template> </template>
@@ -43,22 +26,17 @@
</div> </div>
<div v-if="tableStore.showPage" class="table-pagination"> <div v-if="tableStore.showPage" class="table-pagination">
<el-pagination <el-pagination :currentPage="tableStore.table.params!.pageNum" :page-size="tableStore.table.params!.pageSize"
:currentPage="tableStore.table.params!.pageNum" :page-sizes="pageSizes" background
:page-size="tableStore.table.params!.pageSize"
:page-sizes="pageSizes"
background
:layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'" :layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'"
:total="tableStore.table.total" :total="tableStore.table.total" @size-change="onTableSizeChange"
@size-change="onTableSizeChange" @current-change="onTableCurrentChange"></el-pagination>
@current-change="onTableCurrentChange"
></el-pagination>
</div> </div>
<slot name="footer"></slot> <slot name="footer"></slot>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, nextTick, inject, computed, onMounted } from 'vue' import { ref, nextTick, inject, computed, onMounted, watch } from 'vue'
import type { ElTable } from 'element-plus' import type { ElTable } from 'element-plus'
import { VxeTableEvents, VxeTableInstance } from 'vxe-table' import { VxeTableEvents, VxeTableInstance } from 'vxe-table'
import FieldRender from '@/components/table/fieldRender/index.vue' import FieldRender from '@/components/table/fieldRender/index.vue'
@@ -66,19 +44,22 @@ import Column from '@/components/table/column/index.vue'
import GroupColumn from '@/components/table/column/groupColumn.vue' import GroupColumn from '@/components/table/column/groupColumn.vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import type TableStoreClass from '@/utils/tableStore' import type TableStoreClass from '@/utils/tableStore'
import { useRouter } from 'vue-router'
import { defaultAttribute } from '@/components/table/defaultAttribute' import { defaultAttribute } from '@/components/table/defaultAttribute'
const config = useConfig() const config = useConfig()
const tableRef = ref<VxeTableInstance>() const tableRef = ref<VxeTableInstance>()
const tableStore = inject('tableStore') as TableStoreClass const tableStore = inject('tableStore') as TableStoreClass
const router = useRouter()
interface Props extends /* @vue-ignore */ Partial<InstanceType<typeof ElTable>> { interface Props extends /* @vue-ignore */ Partial<InstanceType<typeof ElTable>> {
isGroup?: boolean isGroup?: boolean
showOverflow?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
isGroup: false isGroup: false,
showOverflow: true
}) })
onMounted(() => { onMounted(() => {
tableStore.table.ref = tableRef.value as VxeTableInstance tableStore.table.ref = tableRef.value as VxeTableInstance
@@ -93,7 +74,7 @@ const onTableCurrentChange = (val: number) => {
} }
const pageSizes = computed(() => { const pageSizes = computed(() => {
let defaultSizes = [10, 20, 50, 100] let defaultSizes = [10, 20, 50, 100, 200]
if (tableStore.table.params!.pageSize) { if (tableStore.table.params!.pageSize) {
if (!defaultSizes.includes(tableStore.table.params!.pageSize)) { if (!defaultSizes.includes(tableStore.table.params!.pageSize)) {
defaultSizes.push(tableStore.table.params!.pageSize) defaultSizes.push(tableStore.table.params!.pageSize)
@@ -113,6 +94,37 @@ const selectChangeEvent: VxeTableEvents.CheckboxChange<any> = ({ checked }) => {
const getRef = () => { const getRef = () => {
return tableRef.value return tableRef.value
} }
watch(
() => tableStore.table.allFlag,
newVal => {
if (tableStore.table.allFlag) {
tableRef.value?.exportData({
filename: document.querySelectorAll('.ba-nav-tab.active')[0].textContent || '', // 文件名字
sheetName: 'Sheet1',
type: 'xlsx', //导出文件类型 xlsx 和 csv
useStyle: true,
data: tableStore.table.allData, // 数据源 // 过滤那个字段导出
columnFilterMethod: function (column: any) {
return !(column.column.title === undefined || column.column.title === '序号' || column.column.title === '操作')
}
})
tableStore.table.allFlag = false
}
}
)
watch(
() => tableStore.table.data,
newVal => {
tableStore.onTableAction('selection-change', [])
}
)
defineExpose({ defineExpose({
getRef getRef

View File

@@ -14,7 +14,11 @@ import '@fortawesome/fontawesome-free/css/all.css'
import '@/styles/index.scss' import '@/styles/index.scss'
import '@/assets/font/iconfont.css' import '@/assets/font/iconfont.css'
import { ElDialog } from 'element-plus' import { ElDialog } from 'element-plus'
import ExcelJS from 'exceljs'
import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx'
VXETable.use(VXETablePluginExportXLSX, {
ExcelJS
})
window.XEUtils = XEUtils window.XEUtils = XEUtils

35
src/utils/tableMethod.ts Normal file
View File

@@ -0,0 +1,35 @@
// 过滤表格导出数据
export const filtration = (arr: Array<{ dealFlag?: number; fileFlag?: number; onlineEvaluate?: number }>) => {
const dealFlagMap: Record<number, string> = {
0: '未处理',
1: '已处理',
2: '已处理,无结果',
3: '计算失败'
}
const fileFlagMap: Record<number, string> = {
0: '不存在',
1: '存在'
}
arr.forEach((item: any) => {
if (item.dealFlag !== undefined) {
item.dealFlag = dealFlagMap[item.dealFlag] || ''
}
if (item.fileFlag !== undefined) {
item.fileFlag = fileFlagMap[item.fileFlag] || ''
}
if (item.onlineEvaluate !== undefined) {
if (item.onlineEvaluate == null) {
} else if (item.onlineEvaluate == 3.14159) {
item.onlineEvaluate = '/'
} else if (item.onlineEvaluate * 100 > 90) {
item.onlineEvaluate = '优'
} else if (item.onlineEvaluate * 100 > 60) {
item.onlineEvaluate = '良'
} else {
item.onlineEvaluate = '差'
}
}
})
return arr
}

View File

@@ -3,19 +3,20 @@ import createAxios from '@/utils/request'
import { requestPayload } from '@/utils/request' import { requestPayload } from '@/utils/request'
import { Method } from 'axios' import { Method } from 'axios'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import { filtration } from './tableMethod'
interface TableStoreParams { interface TableStoreParams {
url: string // 请求地址 url: string // 请求地址
pk?: string pk?: string
column: TableColumn[] column: TableColumn[]
params?: anyObj params?: anyObj
method?: Method // 请求方式 method?: Method // 请求方式
isWebPaging?: boolean // 是否前端分页 isWebPaging?: boolean // 是否前端分页
showPage?: boolean //是否需要分页 showPage?: boolean //是否需要分页
paramsPOST?: boolean // post请求 params传参 paramsPOST?: boolean // post请求 params传参
publicHeight?: number //计算高度 publicHeight?: number //计算高度
resetCallback?: () => void // 重置 resetCallback?: () => void // 重置
loadCallback?: () => void // 接口调用后的回调 loadCallback?: () => void // 接口调用后的回调
beforeSearchFun?: () => void // 接口调用前的回调 beforeSearchFun?: () => void // 接口调用前的回调
} }
@@ -31,6 +32,8 @@ export default class TableStore {
ref: null, ref: null,
selection: [], selection: [],
data: [], data: [],
allData: [],
allFlag: false,
webPagingData: [], webPagingData: [],
total: 0, total: 0,
params: { params: {
@@ -78,26 +81,28 @@ export default class TableStore {
}, },
requestPayload(this.method, this.table.params, this.paramsPOST) requestPayload(this.method, this.table.params, this.paramsPOST)
) )
).then((res: any) => { )
if (res.data) { .then((res: any) => {
this.table.data = res.data.records || res.data if (res.data) {
this.table.total = res.data?.total || res.data.length || 0 this.table.data = res.data.records || res.data
} else { this.table.total = res.data?.total || res.data.length || 0
this.table.data = [] } else {
this.table.total = 0 this.table.data = []
} this.table.total = 0
if (Array.isArray(res)) { }
this.table.data = res if (Array.isArray(res)) {
} this.table.data = res
if (this.isWebPaging) { }
this.table.webPagingData = window.XEUtils.chunk(this.table.data, this.table.params.pageSize) if (this.isWebPaging) {
this.table.data = this.table.webPagingData[this.table.params.pageNum - 1] this.table.webPagingData = window.XEUtils.chunk(this.table.data, this.table.params.pageSize)
} this.table.data = this.table.webPagingData[this.table.params.pageNum - 1]
this.table.loadCallback && this.table.loadCallback() }
this.table.loading = false this.table.loadCallback && this.table.loadCallback()
}).catch(() => { this.table.loading = false
this.table.loading = false })
}) .catch(() => {
this.table.loading = false
})
} }
/** /**
@@ -173,6 +178,25 @@ export default class TableStore {
() => { () => {
console.warn('No action defined') console.warn('No action defined')
} }
],
[
'export',
() => {
// this.index()
let params = { ...this.table.params, pageNum: 1, pageSize: this.table.total }
createAxios(
Object.assign(
{
url: this.url,
method: this.method
},
requestPayload(this.method, params, this.paramsPOST)
)
).then(res => {
this.table.allData = filtration(res.data.records || res.data)
this.table.allFlag = data.showAllFlag || true
})
}
] ]
]) ])
const action = actionFun.get(event) || actionFun.get('default') const action = actionFun.get(event) || actionFun.get('default')

View File

@@ -1,6 +1,6 @@
<template> <template>
<!-- 异常事件 --> <!-- 异常事件 -->
<TableHeader datePicker ref="refheader"> <TableHeader datePicker ref="refheader" showExport>
<!-- <template v-slot:select> <!-- <template v-slot:select>
<el-form-item label="数据来源"> <el-form-item label="数据来源">
<el-cascader <el-cascader

View File

@@ -1,5 +1,5 @@
<template> <template>
<TableHeader datePicker ref="refheader"> <TableHeader datePicker ref="refheader" showExport>
<template v-slot:select> <template v-slot:select>
<el-form-item label="数据来源"> <el-form-item label="数据来源">
<el-cascader v-model.trim="tableStore.table.params.cascader" placeholder="请选择数据来源" <el-cascader v-model.trim="tableStore.table.params.cascader" placeholder="请选择数据来源"

View File

@@ -1,5 +1,5 @@
<template> <template>
<TableHeader datePicker ref="refheader"> <TableHeader datePicker ref="refheader" showExport>
<template v-slot:select> <template v-slot:select>
<el-form-item label="数据来源"> <el-form-item label="数据来源">
<el-cascader v-model.trim="tableStore.table.params.cascader" placeholder="请选择数据来源" <el-cascader v-model.trim="tableStore.table.params.cascader" placeholder="请选择数据来源"

View File

@@ -1,6 +1,6 @@
<template> <template>
<div ref="refheader" v-if="!isWaveCharts"> <div ref="refheader" v-if="!isWaveCharts">
<TableHeader datePicker> <TableHeader datePicker showExport>
<template v-slot:select> <template v-slot:select>
<el-form-item label="数据来源"> <el-form-item label="数据来源">
<el-cascader placeholder="请选择数据来源" @change="sourceChange" <el-cascader placeholder="请选择数据来源" @change="sourceChange"

View File

@@ -223,9 +223,7 @@
{{ child.anotherName }}: {{ child.anotherName }}:
{{ child.dataValue === 3.1415926 ? '暂无数据' : child.dataValue }} {{ child.dataValue === 3.1415926 ? '暂无数据' : child.dataValue }}
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="item.children.length" class="box-card-div"> <div v-else-if="item.children.length" class="box-card-div">
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">

View File

@@ -47,21 +47,30 @@
{{ deviceData.appCheck }} {{ deviceData.appCheck }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-tabs v-model.trim="dataSet" type="border-card" class="device-manage-box-card" @tab-click="handleClick"> <div style="position: relative">
<el-tab-pane lazy :label="item.name" :name="item.id" v-for="(item, index) in deviceData.dataSetList"
:key="index"></el-tab-pane>
<div :style="{ height: tableHeight }" v-loading="tableLoading"> <el-tabs v-model.trim="dataSet" type="border-card" class="device-manage-box-card"
<vxe-table v-bind="defaultAttribute" :data="tableData" height="auto" style="width: 100%"> @tab-click="handleClick">
<vxe-column type="seq" title="序号" width="80"></vxe-column> <el-tab-pane lazy :label="item.name" :name="item.id" v-for="(item, index) in deviceData.dataSetList"
<vxe-column field="name" title="数据名称"></vxe-column> :key="index"></el-tab-pane>
<vxe-column field="phasic" title="相别"></vxe-column> <div :style="{ height: tableHeight }" v-loading="tableLoading">
<vxe-column field="type" title="数据类型"></vxe-column> <vxe-table v-bind="defaultAttribute" :data="tableData" height="auto" ref="xTableRef"
<vxe-column field="unit" title="单位"></vxe-column> style="width: 100%">
<vxe-column field="startTimes" title="开始次数"></vxe-column> <vxe-column type="seq" title="序号" width="80"></vxe-column>
<vxe-column field="endTimes" title="结束次数"></vxe-column> <vxe-column field="name" title="数据名称"></vxe-column>
</vxe-table> <vxe-column field="phasic" title="相别"></vxe-column>
</div> <vxe-column field="type" title="数据类型"></vxe-column>
</el-tabs> <vxe-column field="unit" title="单位"></vxe-column>
<vxe-column field="startTimes" title="开始次数"></vxe-column>
<vxe-column field="endTimes" title="结束次数"></vxe-column>
</vxe-table>
</div>
</el-tabs>
<el-button icon="el-icon-Download" type="primary" @click="exportData" class="export-btn">导出</el-button>
</div>
</div> </div>
<el-empty v-else description="请选择设备" class="device-manage-right" /> <el-empty v-else description="请选择设备" class="device-manage-right" />
<MangePopup ref="mangePopup" /> <MangePopup ref="mangePopup" />
@@ -103,6 +112,7 @@ const tableData = ref([])
const tableHeight = mainHeight(260).height const tableHeight = mainHeight(260).height
const mangePopup = ref() const mangePopup = ref()
const activeName = ref(0) const activeName = ref(0)
const xTableRef = ref()
//治理设备和便携式设备切换判断 //治理设备和便携式设备切换判断
const deviceType = ref('0') const deviceType = ref('0')
const deviceTypeChange = (val: any, obj: any) => { const deviceTypeChange = (val: any, obj: any) => {
@@ -250,6 +260,21 @@ const handleRestartDevice = () => {
deviceRestartLoading.value = false deviceRestartLoading.value = false
}) })
} }
const exportData = () => {
xTableRef.value.exportData({
filename: deviceData.value.dataSetList.filter((item: any) => item.id == dataSet.value)[0]?.name, // 文件名字
sheetName: 'Sheet1',
type: 'xlsx', //导出文件类型 xlsx 和 csv
useStyle: true,
download: false,
data: tableData.value, // 数据源 // 过滤那个字段导出
columnFilterMethod: function (column, $columnIndex) {
return !(column.$columnIndex === 0)
}
})
}
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -279,4 +304,11 @@ const handleRestartDevice = () => {
-webkit-text-security: disc !important; -webkit-text-security: disc !important;
} }
} }
.export-btn {
position: absolute;
top: 4px;
right: 10px;
z-index: 10;
}
</style> </style>

2
types/table.d.ts vendored
View File

@@ -8,6 +8,8 @@ declare global {
interface CnTable { interface CnTable {
ref: VxeTableInstance | null ref: VxeTableInstance | null
data: TableRow[] | any data: TableRow[] | any
allData: TableRow[] | any
allFlag: Boolean
// 前端分页数据 // 前端分页数据
webPagingData: TableRow[][] webPagingData: TableRow[][]
// 表格加载状态 // 表格加载状态