Files
cpzs-frontend-new/src/views/admin/ExcelImportManagement.vue

762 lines
19 KiB
Vue
Raw Normal View History

<template>
<div class="excel-import-management">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-content">
<h1>📊 Excel数据导入系统</h1>
<p>管理员专用 - {{ currentLotteryTypeName }}Excel文件数据批量导入</p>
</div>
</div>
<!-- 权限检查中 -->
<div v-if="permissionChecking" class="permission-checking">
<div class="checking-content">
<div class="loading-spinner"></div>
<p>正在验证权限...</p>
</div>
</div>
<!-- 主要内容 - 只有有权限时才显示 -->
<div v-else-if="hasPermission" class="import-container">
<!-- 功能区域 -->
<div class="function-area">
<el-row :gutter="20">
<!-- 完整数据导入 -->
<el-col :span="8">
<el-card class="function-card">
<template #header>
<div class="card-header">
<el-icon><Document /></el-icon>
<span>完整数据导入</span>
</div>
</template>
<div class="card-desc">
<p>上传包含T1-T7工作表的Excel文件导入红球蓝球接续系数和组合系数数据</p>
</div>
<div class="upload-section">
<div class="file-input-container">
<input
type="file"
ref="fullDataFileInput"
@change="handleFileSelect($event, 'fullData')"
accept=".xlsx,.xls"
class="file-input"
id="fullDataFile"
/>
<label for="fullDataFile" class="file-label">
<el-icon class="file-icon"><FolderOpened /></el-icon>
<span class="file-text">
{{ fullDataFile ? fullDataFile.name : '选择Excel文件包含T1-T7工作表' }}
</span>
</label>
</div>
<el-button
type="primary"
@click="uploadFullData"
:disabled="!fullDataFile || fullDataUploading"
:loading="fullDataUploading"
style="width: 100%; margin-top: 16px"
>
{{ fullDataUploading ? '导入中...' : '开始导入' }}
</el-button>
<div v-if="fullDataResult" class="result-message" :class="fullDataResult.type">
{{ fullDataResult.message }}
</div>
</div>
</el-card>
</el-col>
<!-- 开奖数据导入覆盖 -->
<el-col :span="8">
<el-card class="function-card">
<template #header>
<div class="card-header">
<el-icon><Warning /></el-icon>
<span>开奖数据导入覆盖</span>
</div>
</template>
<div class="card-desc">
<p>上传包含T10工作表的Excel文件清空并重新导入开奖数据</p>
</div>
<div class="upload-section">
<div class="file-input-container">
<input
type="file"
ref="lotteryFileInput"
@change="handleFileSelect($event, 'lottery')"
accept=".xlsx,.xls"
class="file-input"
id="lotteryFile"
/>
<label for="lotteryFile" class="file-label">
<el-icon class="file-icon"><FolderOpened /></el-icon>
<span class="file-text">
{{ lotteryFile ? lotteryFile.name : '选择Excel文件包含T10工作表' }}
</span>
</label>
</div>
<el-button
type="warning"
@click="uploadLotteryData"
:disabled="!lotteryFile || lotteryUploading"
:loading="lotteryUploading"
style="width: 100%; margin-top: 16px"
>
{{ lotteryUploading ? '导入中...' : '覆盖导入' }}
</el-button>
<div v-if="lotteryResult" class="result-message" :class="lotteryResult.type">
{{ lotteryResult.message }}
</div>
</div>
</el-card>
</el-col>
<!-- 开奖数据追加 -->
<el-col :span="8">
<el-card class="function-card">
<template #header>
<div class="card-header">
<el-icon><Plus /></el-icon>
<span>开奖数据追加</span>
</div>
</template>
<div class="card-desc">
<p>上传包含T10工作表的Excel文件追加导入开奖数据跳过重复期号</p>
</div>
<div class="upload-section">
<div class="file-input-container">
<input
type="file"
ref="appendFileInput"
@change="handleFileSelect($event, 'append')"
accept=".xlsx,.xls"
class="file-input"
id="appendFile"
/>
<label for="appendFile" class="file-label">
<el-icon class="file-icon"><FolderOpened /></el-icon>
<span class="file-text">
{{ appendFile ? appendFile.name : '选择Excel文件包含T10工作表' }}
</span>
</label>
</div>
<el-button
type="success"
@click="appendLotteryData"
:disabled="!appendFile || appendUploading"
:loading="appendUploading"
style="width: 100%; margin-top: 16px"
>
{{ appendUploading ? '追加中...' : '追加导入' }}
</el-button>
<div v-if="appendResult" class="result-message" :class="appendResult.type">
{{ appendResult.message }}
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 导入说明 -->
<el-card class="info-card">
<template #header>
<div class="card-header">
<el-icon><InfoFilled /></el-icon>
<span>导入说明</span>
</div>
</template>
<div class="info-content">
<div class="info-item">
<h4>📋 完整数据导入</h4>
<p> 需要包含T1T2T3T4T5T6T7工作表的Excel文件</p>
<p> 导入红球蓝球接续系数和组合系数数据到相应的数据库表</p>
<p> 适用于系统初始化或全量数据更新</p>
</div>
<div class="info-item">
<h4>🎯 开奖数据导入覆盖</h4>
<p> 需要包含T10工作表的Excel文件</p>
<p> 清空lottery_draws表的现有数据重新导入</p>
<p> 适用于完全替换开奖数据</p>
</div>
<div class="info-item">
<h4> 开奖数据追加</h4>
<p> 需要包含T10工作表的Excel文件</p>
<p> 保留现有数据只添加新的开奖记录</p>
<p> 自动跳过重复的期号适用于增量更新</p>
</div>
</div>
</el-card>
</div>
<!-- 错误提示弹窗 -->
<div v-if="showErrorModal" class="modal-overlay" @click="hideErrorModal">
<div class="modal-content error-modal" @click.stop>
<h3> 导入失败</h3>
<p>{{ errorMessage }}</p>
<button class="btn btn-primary" @click="hideErrorModal">确定</button>
</div>
</div>
</div>
</template>
<script>
import { lotteryApi } from '../../api/index.js'
import { userStore } from '../../store/user.js'
export default {
name: 'AdminExcelImportManagement',
data() {
return {
// 权限验证
hasPermission: false,
permissionChecking: true,
// 文件对象
fullDataFile: null,
lotteryFile: null,
appendFile: null,
// 上传状态
fullDataUploading: false,
lotteryUploading: false,
appendUploading: false,
// 结果信息
fullDataResult: null,
lotteryResult: null,
appendResult: null,
// 错误处理
showErrorModal: false,
errorMessage: ''
}
},
computed: {
// 当前彩票类型名称
currentLotteryTypeName() {
const routePath = this.$route.path
if (routePath.includes('/ssq')) {
return '双色球 - '
} else if (routePath.includes('/dlt')) {
return '大乐透 - '
}
return ''
}
},
async mounted() {
await this.checkPermission()
},
methods: {
// 检查用户权限
async checkPermission() {
try {
const response = await lotteryApi.getLoginUser()
if (response && response.success && response.data) {
const userRole = response.data.userRole
if (userRole === 'admin' || userRole === 'superAdmin') {
this.hasPermission = true
} else {
this.showError('无权限访问此页面,仅限管理员或超级管理员使用')
// 3秒后跳转到管理员登录页
setTimeout(() => {
this.$router.push('/cpzsadmin/login')
}, 3000)
}
} else {
this.showError('获取用户信息失败,请重新登录')
setTimeout(() => {
this.$router.push('/cpzsadmin/login')
}, 3000)
}
} catch (error) {
console.error('权限检查失败:', error)
this.showError('权限验证失败,请重新登录')
setTimeout(() => {
this.$router.push('/cpzsadmin/login')
}, 3000)
} finally {
this.permissionChecking = false
}
},
// 文件选择处理
handleFileSelect(event, type) {
const file = event.target.files[0]
if (!file) return
// 验证文件类型
if (!this.validateFileType(file)) {
this.showError('请选择.xlsx或.xls格式的Excel文件')
event.target.value = ''
return
}
// 验证文件大小限制50MB
if (file.size > 50 * 1024 * 1024) {
this.showError('文件大小不能超过50MB')
event.target.value = ''
return
}
switch (type) {
case 'fullData':
this.fullDataFile = file
this.fullDataResult = null
break
case 'lottery':
this.lotteryFile = file
this.lotteryResult = null
break
case 'append':
this.appendFile = file
this.appendResult = null
break
}
},
// 验证文件类型
validateFileType(file) {
const allowedTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
'application/vnd.ms-excel' // .xls
]
return allowedTypes.includes(file.type) ||
file.name.endsWith('.xlsx') ||
file.name.endsWith('.xls')
},
// 上传完整数据
async uploadFullData() {
if (!this.fullDataFile) return
this.fullDataUploading = true
try {
const response = await lotteryApi.uploadExcelFile(this.fullDataFile)
this.fullDataResult = {
type: 'success',
message: '✅ ' + (response || '完整数据导入成功!')
}
// 清空文件选择
this.fullDataFile = null
this.$refs.fullDataFileInput.value = ''
} catch (error) {
console.error('完整数据导入失败:', error)
this.fullDataResult = {
type: 'error',
message: '❌ ' + (error?.response?.data || error?.message || '导入失败,请重试')
}
} finally {
this.fullDataUploading = false
}
},
// 上传开奖数据(覆盖)
async uploadLotteryData() {
if (!this.lotteryFile) return
this.lotteryUploading = true
try {
const response = await lotteryApi.uploadLotteryDrawsFile(this.lotteryFile)
this.lotteryResult = {
type: 'success',
message: '✅ ' + (response || '开奖数据导入成功!')
}
// 清空文件选择
this.lotteryFile = null
this.$refs.lotteryFileInput.value = ''
} catch (error) {
console.error('开奖数据导入失败:', error)
this.lotteryResult = {
type: 'error',
message: '❌ ' + (error?.response?.data || error?.message || '导入失败,请重试')
}
} finally {
this.lotteryUploading = false
}
},
// 追加开奖数据
async appendLotteryData() {
if (!this.appendFile) return
this.appendUploading = true
try {
const response = await lotteryApi.appendLotteryDrawsFile(this.appendFile)
this.appendResult = {
type: 'success',
message: '✅ ' + (response || '开奖数据追加成功!')
}
// 清空文件选择
this.appendFile = null
this.$refs.appendFileInput.value = ''
} catch (error) {
console.error('开奖数据追加失败:', error)
this.appendResult = {
type: 'error',
message: '❌ ' + (error?.response?.data || error?.message || '追加失败,请重试')
}
} finally {
this.appendUploading = false
}
},
// 显示错误信息
showError(message) {
this.errorMessage = message
this.showErrorModal = true
},
// 隐藏错误弹窗
hideErrorModal() {
this.showErrorModal = false
this.errorMessage = ''
}
}
}
</script>
<style scoped>
.excel-import-management {
min-height: 100vh;
background: var(--color-bg-page, #f0f2f5);
padding: 24px;
}
/* 页面头部 */
.page-header {
text-align: center;
background: linear-gradient(135deg, #3a7bd5, #00d2ff);
padding: 30px;
border-radius: 8px;
color: white;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
margin-bottom: 24px;
}
/* 权限检查样式 */
.permission-checking {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.checking-content {
text-align: center;
color: white;
}
.checking-content p {
margin-top: 20px;
font-size: 16px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.header-content h1 {
font-size: 32px;
margin-bottom: 10px;
font-weight: 600;
}
.header-content p {
font-size: 16px;
opacity: 0.9;
}
/* 主容器 */
.import-container {
max-width: 1400px;
margin: 0 auto;
}
/* 功能区域 */
.function-area {
margin-bottom: 24px;
}
.function-card {
border: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
height: 100%;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: #333;
}
.card-header .el-icon {
font-size: 18px;
color: #409EFF;
}
.card-desc {
margin-bottom: 20px;
}
.card-desc p {
color: #666;
margin: 0;
font-size: 14px;
line-height: 1.5;
}
/* 上传区域 */
.upload-section {
display: flex;
flex-direction: column;
gap: 16px;
}
.file-input-container {
position: relative;
}
.file-input {
display: none;
}
.file-label {
display: flex;
align-items: center;
padding: 12px 16px;
border: 2px dashed #dcdfe6;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
background: #fafafa;
min-height: 60px;
}
.file-label:hover {
border-color: #409eff;
background: #ecf5ff;
}
.file-icon {
font-size: 20px;
margin-right: 12px;
color: #409eff;
}
.file-text {
color: #606266;
font-size: 14px;
flex: 1;
word-break: break-all;
}
/* 减少卡片内边距 */
:deep(.el-card__body) {
padding: 16px !important;
}
/* 结果消息 */
.result-message {
padding: 12px;
border-radius: 6px;
font-weight: 600;
font-size: 14px;
margin-top: 12px;
}
.result-message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.result-message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* 信息卡片 */
.info-card {
border: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 信息说明 */
.info-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.info-item h4 {
color: #333;
margin-bottom: 8px;
font-size: 16px;
font-weight: 600;
}
.info-item p {
color: #666;
margin: 2px 0;
font-size: 14px;
line-height: 1.5;
}
/* 模态框 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 12px;
max-width: 400px;
width: 90%;
text-align: center;
}
.error-modal h3 {
color: #dc3545;
margin-bottom: 15px;
}
.error-modal p {
margin-bottom: 20px;
color: #666;
line-height: 1.5;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
}
.btn-primary {
background: #409eff;
color: white;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.function-area .el-col {
margin-bottom: 20px;
}
}
@media (max-width: 768px) {
.excel-import-management {
padding: 16px;
}
.function-area .el-row {
flex-wrap: wrap;
}
.function-area .el-col {
flex: 0 0 100%;
max-width: 100%;
margin-bottom: 16px;
}
.page-header {
padding: 20px;
}
.page-header h1 {
font-size: 24px;
}
.card-header span {
font-size: 14px;
}
.card-desc {
margin-bottom: 12px;
}
.card-desc p {
font-size: 12px;
line-height: 1.4;
}
.file-label {
padding: 8px 10px;
min-height: 45px;
}
.file-text {
font-size: 12px;
}
.file-icon {
font-size: 16px;
margin-right: 8px;
}
.upload-section {
gap: 12px;
}
.info-item h4 {
font-size: 14px;
}
.info-item p {
font-size: 12px;
}
}
</style>