Files
tyapi-frontend/src/components/common/ExportDialog.vue
2025-12-13 16:25:05 +08:00

322 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog
v-model="visible"
:title="title"
:width="isMobile ? '90%' : '600px'"
:close-on-click-modal="false"
class="export-dialog"
>
<div class="space-y-4">
<!-- 企业选择 -->
<div v-if="showCompanySelect">
<label class="block text-sm font-medium text-gray-700 mb-2">选择企业</label>
<el-select
v-model="exportOptions.companyIds"
multiple
filterable
placeholder="搜索并选择企业(不选则导出所有)"
class="w-full"
clearable
:loading="companyLoading"
@focus="loadCompanyOptions"
@visible-change="handleCompanyVisibleChange"
>
<el-option
v-for="company in companyOptions"
:key="company.id"
:label="company.company_name"
:value="company.id"
/>
<div v-if="companyLoading" class="text-center py-2">
<span class="text-gray-500">加载中...</span>
</div>
</el-select>
</div>
<!-- 产品选择 -->
<div v-if="showProductSelect">
<label class="block text-sm font-medium text-gray-700 mb-2">选择产品</label>
<el-select
v-model="exportOptions.productIds"
multiple
filterable
remote
reserve-keyword
placeholder="搜索并选择产品(不选则导出所有)"
class="w-full"
clearable
:remote-method="handleProductSearch"
:loading="productLoading"
@focus="loadProductOptions"
@visible-change="handleProductVisibleChange"
>
<el-option
v-for="product in productOptions"
:key="product.id"
:label="product.name"
:value="product.id"
/>
<div v-if="productLoading" class="text-center py-2">
<span class="text-gray-500">加载中...</span>
</div>
</el-select>
</div>
<!-- 充值类型选择 -->
<div v-if="showRechargeTypeSelect">
<label class="block text-sm font-medium text-gray-700 mb-2">充值类型</label>
<el-select
v-model="exportOptions.rechargeType"
placeholder="选择充值类型(不选则导出所有)"
class="w-full"
clearable
>
<el-option label="全部" value="" />
<el-option label="支付宝" value="alipay" />
<el-option label="转账" value="transfer" />
<el-option label="赠送" value="gift" />
</el-select>
</div>
<!-- 状态选择 -->
<div v-if="showStatusSelect">
<label class="block text-sm font-medium text-gray-700 mb-2">状态</label>
<el-select
v-model="exportOptions.status"
placeholder="选择状态(不选则导出所有)"
class="w-full"
clearable
>
<el-option label="全部" value="" />
<el-option label="待处理" value="pending" />
<el-option label="成功" value="success" />
<el-option label="失败" value="failed" />
</el-select>
</div>
<!-- 时间范围 -->
<div v-if="showDateRange">
<label class="block text-sm font-medium text-gray-700 mb-2">时间范围</label>
<el-date-picker
v-model="exportOptions.dateRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
class="w-full"
/>
</div>
<!-- 导出格式 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">导出格式</label>
<el-radio-group v-model="exportOptions.format">
<el-radio value="excel">Excel (.xlsx)</el-radio>
<el-radio value="csv">CSV (.csv)</el-radio>
</el-radio-group>
</div>
</div>
<template #footer>
<div :class="['flex justify-end gap-3', isMobile ? 'flex-col' : '']">
<el-button @click="handleCancel" :class="isMobile ? 'w-full' : ''">取消</el-button>
<el-button
type="primary"
:loading="loading"
@click="handleConfirm"
:class="isMobile ? 'w-full' : ''"
>
确认导出
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { productApi, userApi } from '@/api'
import { useMobileTable } from '@/composables/useMobileTable'
import { reactive, ref, watch } from 'vue'
// 移动端检测
const { isMobile } = useMobileTable()
// Props
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
title: {
type: String,
default: '导出数据'
},
loading: {
type: Boolean,
default: false
},
// 控制显示哪些筛选选项
showCompanySelect: {
type: Boolean,
default: true
},
showProductSelect: {
type: Boolean,
default: true
},
showRechargeTypeSelect: {
type: Boolean,
default: false
},
showStatusSelect: {
type: Boolean,
default: false
},
showDateRange: {
type: Boolean,
default: true
}
})
// Emits
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
// 响应式数据
const visible = ref(false)
const exportOptions = reactive({
companyIds: [],
productIds: [],
rechargeType: '',
status: '',
dateRange: [],
format: 'excel'
})
// 企业选项
const companyOptions = ref([])
const companyLoading = ref(false)
// 产品选项
const productOptions = ref([])
const productLoading = ref(false)
const productSearchKeyword = ref('')
// 监听modelValue变化
watch(() => props.modelValue, (newVal) => {
visible.value = newVal
})
watch(visible, (newVal) => {
emit('update:modelValue', newVal)
})
// 企业相关方法
const loadCompanyOptions = async () => {
if (companyLoading.value) return
try {
companyLoading.value = true
const response = await userApi.getUserList({
page: 1,
page_size: 1000,
is_certified: true // 只加载已认证用户
})
companyOptions.value = response.data?.items?.map(user => ({
id: user.id,
company_name: user.enterprise_info?.company_name || user.phone || '未知企业'
})) || []
} catch (error) {
console.error('加载企业选项失败:', error)
} finally {
companyLoading.value = false
}
}
const handleCompanyVisibleChange = (visible) => {
if (visible && companyOptions.value.length === 0) {
loadCompanyOptions()
}
}
// 产品相关方法
const loadProductOptions = async () => {
if (productLoading.value) return
try {
productLoading.value = true
const response = await productApi.getProducts({
page: 1,
page_size: 1000,
name: productSearchKeyword.value
})
productOptions.value = response.data?.items?.map(product => ({
id: product.id,
name: product.name
})) || []
} catch (error) {
console.error('加载产品选项失败:', error)
} finally {
productLoading.value = false
}
}
const handleProductSearch = (keyword) => {
productSearchKeyword.value = keyword
loadProductOptions()
}
const handleProductVisibleChange = (visible) => {
if (visible && productOptions.value.length === 0) {
loadProductOptions()
}
}
// 事件处理
const handleConfirm = () => {
emit('confirm', { ...exportOptions })
}
const handleCancel = () => {
visible.value = false
emit('cancel')
}
// 重置选项
const resetOptions = () => {
exportOptions.companyIds = []
exportOptions.productIds = []
exportOptions.rechargeType = ''
exportOptions.status = ''
exportOptions.dateRange = []
exportOptions.format = 'excel'
}
// 暴露方法给父组件
defineExpose({
resetOptions
})
</script>
<style scoped>
/* 样式保持简洁主要依赖Tailwind CSS */
</style>
<style>
/* 导出弹窗移动端优化 */
@media (max-width: 768px) {
.export-dialog :deep(.el-dialog__body) {
padding: 16px;
max-height: 80vh;
overflow-y: auto;
}
}
</style>