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

762 lines
19 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>
<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>