296 lines
6.7 KiB
Vue
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>
|