Files
hzdyjs/web/src/views/xg-xx-bak/statistics/components/ExportDialog.vue
T

296 lines
6.7 KiB
Vue

<template>
<el-dialog
title="导出统计数据"
v-model="dialogVisible"
width="500px"
:before-close="handleClose"
append-to-body
>
<el-form
ref="exportForm"
:model="form"
:rules="rules"
label-width="100px"
v-loading="loading"
>
<el-form-item label="导出格式" prop="format">
<el-radio-group v-model="form.format">
<el-radio label="excel">Excel格式</el-radio>
<el-radio label="csv">CSV格式</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="包含内容" prop="includeChart">
<el-checkbox v-model="form.includeChart" :disabled="form.format === 'csv'">
包含图表
</el-checkbox>
</el-form-item>
<el-form-item label="文件名称" prop="fileName">
<el-input
v-model="form.fileName"
placeholder="请输入文件名称"
:suffix="form.format === 'excel' ? '.xlsx' : '.csv'"
/>
</el-form-item>
<el-form-item label="数据预览">
<div class="data-preview">
<div class="preview-header">
<span>共 {{ statisticsData.length }} 条数据</span>
</div>
<div class="preview-content">
<div
v-for="(item, index) in statisticsData.slice(0, 5)"
:key="index"
class="preview-item"
>
<span class="item-label">{{ item.label }}</span>
<span class="item-value">{{ item.value }}</span>
<span class="item-percentage">{{ item.percentage }}%</span>
</div>
<div v-if="statisticsData.length > 5" class="preview-more">
...还有 {{ statisticsData.length - 5 }} 条数据
</div>
</div>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleExport" :loading="loading">
导出
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { exportStatisticsResult, type StatisticsExportQuery } from '@/api/xg-xx/statistics'
// Props
interface Props {
visible: boolean
queryParams: any
statisticsData: Array<{
label: string
value: number
percentage: number
}>
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
queryParams: () => ({}),
statisticsData: () => []
})
// Emits
const emit = defineEmits<{
'update:visible': [value: boolean]
}>()
// 响应式数据
const loading = ref(false)
const exportForm = ref()
// 表单数据
const form = reactive({
format: 'excel' as 'excel' | 'csv',
includeChart: true,
fileName: ''
})
// 表单验证规则
const rules = {
format: [
{ required: true, message: '请选择导出格式', trigger: 'change' }
],
fileName: [
{ required: true, message: '请输入文件名称', trigger: 'blur' },
{ min: 1, max: 50, message: '文件名称长度在 1 到 50 个字符', trigger: 'blur' }
]
}
// 计算属性
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
// 监听器
watch(() => props.visible, (newVal) => {
if (newVal) {
resetForm()
generateFileName()
}
})
watch(() => form.format, (newVal) => {
if (newVal === 'csv') {
form.includeChart = false
}
generateFileName()
})
// 方法定义
const generateFileName = () => {
const now = new Date()
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '')
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '')
let prefix = '统计数据'
if (props.queryParams.dimension) {
const dimensionMap: Record<string, string> = {
college: '学院统计',
major: '专业统计',
class: '班级统计',
grade: '年级统计',
gender: '性别统计',
age: '年龄统计',
political: '政治面貌统计',
nation: '民族统计',
sourceRegion: '生源地统计',
bloodType: '血型统计',
religion: '宗教信仰统计',
studentStatus: '学籍状态统计',
trainingType: '培养方式统计'
}
prefix = dimensionMap[props.queryParams.dimension] || '统计数据'
}
form.fileName = `${prefix}_${dateStr}_${timeStr}`
}
const resetForm = () => {
form.format = 'excel'
form.includeChart = true
form.fileName = ''
}
const handleClose = () => {
dialogVisible.value = false
}
const handleExport = async () => {
if (!exportForm.value) return
try {
await exportForm.value.validate()
} catch (error) {
return
}
loading.value = true
try {
const exportQuery: StatisticsExportQuery = {
...props.queryParams,
format: form.format,
includeChart: form.includeChart
}
const response = await exportStatisticsResult(exportQuery)
// 创建下载链接
const blob = new Blob([response.data], {
type: form.format === 'excel'
? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
: 'text/csv;charset=utf-8'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `${form.fileName}.${form.format === 'excel' ? 'xlsx' : 'csv'}`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
handleClose()
} catch (error) {
console.error('导出失败:', error)
ElMessage.error('导出失败')
} finally {
loading.value = false
}
}
</script>
<style scoped>
.data-preview {
border: 1px solid #e4e7ed;
border-radius: 8px;
overflow: hidden;
}
.preview-header {
padding: 12px 16px;
background: #f8f9fa;
border-bottom: 1px solid #e4e7ed;
font-size: 14px;
color: #606266;
font-weight: 500;
}
.preview-content {
max-height: 200px;
overflow-y: auto;
}
.preview-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
border-bottom: 1px solid #f0f2f5;
font-size: 13px;
}
.preview-item:last-child {
border-bottom: none;
}
.item-label {
flex: 1;
color: #303133;
}
.item-value {
width: 80px;
text-align: right;
color: rgb(93, 135, 255);
font-weight: 500;
}
.item-percentage {
width: 60px;
text-align: right;
color: #909399;
font-size: 12px;
}
.preview-more {
padding: 8px 16px;
text-align: center;
color: #909399;
font-size: 12px;
font-style: italic;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
</style>