|
|
@@ -0,0 +1,144 @@
|
|
|
+<template>
|
|
|
+ <div v-if="hasSelection" class="list-export-dropdown">
|
|
|
+ <el-button
|
|
|
+ class="btn-ghost list-export-dropdown__main"
|
|
|
+ :disabled="disabled"
|
|
|
+ :loading="exporting"
|
|
|
+ @click="handleExportFiltered"
|
|
|
+ >
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ 导出筛选结果
|
|
|
+ </el-button>
|
|
|
+ <el-dropdown
|
|
|
+ trigger="click"
|
|
|
+ :disabled="disabled || exporting"
|
|
|
+ @command="handleCommand"
|
|
|
+ >
|
|
|
+ <el-button class="btn-ghost list-export-dropdown__caret" :disabled="disabled || exporting">
|
|
|
+ <el-icon><ArrowDown /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item command="selected" :disabled="!selectedIds?.length">
|
|
|
+ 导出已选
|
|
|
+ </el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-button
|
|
|
+ v-else
|
|
|
+ class="btn-ghost"
|
|
|
+ :disabled="disabled"
|
|
|
+ :loading="exporting"
|
|
|
+ @click="handleExportFiltered"
|
|
|
+ >
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ 导出
|
|
|
+ </el-button>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref } from 'vue'
|
|
|
+ import { ArrowDown, Download } from '@element-plus/icons-vue'
|
|
|
+ import { ElMessage } from 'element-plus'
|
|
|
+ import {
|
|
|
+ filenameFromContentDisposition,
|
|
|
+ pickBlobFromExportResponse,
|
|
|
+ triggerBlobDownload
|
|
|
+ } from '@/utils/list-export-download'
|
|
|
+
|
|
|
+ defineOptions({ name: 'ListExportDropdown' })
|
|
|
+
|
|
|
+ export type ListExportPayload = Record<string, any> & {
|
|
|
+ ids?: string[]
|
|
|
+ columns?: string[]
|
|
|
+ }
|
|
|
+
|
|
|
+ const props = withDefaults(
|
|
|
+ defineProps<{
|
|
|
+ exportFn: (payload: ListExportPayload) => Promise<any>
|
|
|
+ getFilterParams: () => Record<string, any>
|
|
|
+ selectedIds?: string[]
|
|
|
+ hasSelection?: boolean
|
|
|
+ visibleColumns?: string[]
|
|
|
+ filename?: string
|
|
|
+ disabled?: boolean
|
|
|
+ }>(),
|
|
|
+ {
|
|
|
+ selectedIds: () => [],
|
|
|
+ hasSelection: false,
|
|
|
+ visibleColumns: undefined,
|
|
|
+ filename: '导出.xlsx',
|
|
|
+ disabled: false
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ const exporting = ref(false)
|
|
|
+
|
|
|
+ function buildPayload(withIds: boolean): ListExportPayload {
|
|
|
+ const payload: ListExportPayload = { ...props.getFilterParams() }
|
|
|
+ if (props.visibleColumns?.length) {
|
|
|
+ payload.columns = [...props.visibleColumns]
|
|
|
+ }
|
|
|
+ if (withIds) {
|
|
|
+ payload.ids = [...(props.selectedIds || [])]
|
|
|
+ }
|
|
|
+ return payload
|
|
|
+ }
|
|
|
+
|
|
|
+ async function runExport(withIds: boolean) {
|
|
|
+ if (exporting.value || props.disabled) return
|
|
|
+ if (withIds && !(props.selectedIds?.length > 0)) return
|
|
|
+ exporting.value = true
|
|
|
+ try {
|
|
|
+ const res = await props.exportFn(buildPayload(withIds))
|
|
|
+ const { blob, filenameHeader } = pickBlobFromExportResponse(res)
|
|
|
+ const name = filenameFromContentDisposition(filenameHeader, props.filename)
|
|
|
+ triggerBlobDownload(blob, name)
|
|
|
+ ElMessage.success('导出成功')
|
|
|
+ } catch (e: any) {
|
|
|
+ const msg = e?.message || e?.msg || '导出失败'
|
|
|
+ ElMessage.error(msg)
|
|
|
+ } finally {
|
|
|
+ exporting.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleExportFiltered() {
|
|
|
+ void runExport(false)
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleCommand(cmd: string) {
|
|
|
+ if (cmd === 'selected') void runExport(true)
|
|
|
+ }
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .list-export-dropdown {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: stretch;
|
|
|
+ vertical-align: middle;
|
|
|
+
|
|
|
+ :deep(.el-button) {
|
|
|
+ .el-icon + span,
|
|
|
+ span + .el-icon {
|
|
|
+ margin-left: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .list-export-dropdown__main {
|
|
|
+ border-top-right-radius: 0;
|
|
|
+ border-bottom-right-radius: 0;
|
|
|
+ margin-right: -1px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .list-export-dropdown__caret {
|
|
|
+ border-top-left-radius: 0;
|
|
|
+ border-bottom-left-radius: 0;
|
|
|
+ min-width: 36px;
|
|
|
+ padding: 0 10px;
|
|
|
+ }
|
|
|
+</style>
|