|
|
@@ -0,0 +1,347 @@
|
|
|
+<template>
|
|
|
+ <el-dialog :visible.sync="visible" :before-close="handleClose" custom-class="excel-dialog"
|
|
|
+ :close-on-click-modal="false" :fullscreen="isFullScreen">
|
|
|
+ <template #title>
|
|
|
+ <span style="cursor: move;">导入复习资料 Excel</span>
|
|
|
+ <el-button type="text" @click="toggleFullScreen" style="float: right; margin-right: 40px;">
|
|
|
+ {{ isFullScreen ? '退出全屏' : '全屏' }}
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 操作按钮区域 -->
|
|
|
+ <div class="import-actions">
|
|
|
+ <el-button type="success" icon="el-icon-download" @click="downloadTemplate">
|
|
|
+ 下载导入模板
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Excel导入区域 -->
|
|
|
+ <div class="excel-import-area">
|
|
|
+ <el-upload ref="upload" action="" :auto-upload="false" :on-change="handleFileChange"
|
|
|
+ :on-remove="handleFileRemove" :limit="1" accept=".xlsx,.xls" drag>
|
|
|
+ <i class="el-icon-upload"></i>
|
|
|
+ <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
|
+ <div class="el-upload__tip" slot="tip">
|
|
|
+ <p>只能上传xlsx/xls文件,且不超过10MB</p>
|
|
|
+ <p><strong>Excel格式要求:</strong></p>
|
|
|
+ <p>• 第一行必须是标题行:名称、类型、内容</p>
|
|
|
+ <p>• 类型支持:1/单招/单招题、2/对口/对口题、3/专升本/专升本题</p>
|
|
|
+ <p>• 内容支持富文本格式</p>
|
|
|
+ </div>
|
|
|
+ </el-upload>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Excel预览区域 -->
|
|
|
+ <div v-if="excelData.length > 0" class="excel-preview">
|
|
|
+ <h4>Excel预览 (共 {{ excelData.length }} 条数据)</h4>
|
|
|
+ <el-table :data="excelData" border stripe max-height="400" style="width: 100%">
|
|
|
+ <el-table-column type="index" label="序号" width="60" />
|
|
|
+ <el-table-column prop="name" label="名称" width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="type" label="类型(原始)" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="paper_type" label="试卷类型" width="120">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="getPaperTypeTagType(row.paper_type)">
|
|
|
+ {{ getPaperTypeText(row.paper_type) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="content" label="内容" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column label="操作" width="100">
|
|
|
+ <template slot-scope="{ row, $index }">
|
|
|
+ <el-button type="text" @click="removeRow($index)" style="color: #f56c6c;">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部操作按钮 -->
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
+ <el-button @click="handleClose">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleConfirm" :loading="importing">
|
|
|
+ {{ importing ? '导入中...' : '确认导入' }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import * as XLSX from 'xlsx'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "ArticleExcelImport",
|
|
|
+ props: {
|
|
|
+ visible: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ isFullScreen: false,
|
|
|
+ importing: false,
|
|
|
+ excelData: [],
|
|
|
+ currentFile: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 下载导入模板
|
|
|
+ downloadTemplate() {
|
|
|
+ // 创建模板数据
|
|
|
+ const templateData = [
|
|
|
+ ['名称', '类型', '内容'],
|
|
|
+ ['示例复习资料1', '1', '这是单招题的复习内容示例'],
|
|
|
+ ['示例复习资料2', '对口', '这是对口题的复习内容示例'],
|
|
|
+ ['示例复习资料3', '专升本题', '这是专升本题的复习内容示例']
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 创建工作簿
|
|
|
+ const wb = XLSX.utils.book_new();
|
|
|
+ const ws = XLSX.utils.aoa_to_sheet(templateData);
|
|
|
+
|
|
|
+ // 设置列宽
|
|
|
+ ws['!cols'] = [
|
|
|
+ { width: 30 }, // 名称列
|
|
|
+ { width: 15 }, // 类型列
|
|
|
+ { width: 50 } // 内容列
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 添加工作表到工作簿
|
|
|
+ XLSX.utils.book_append_sheet(wb, ws, '复习资料导入模板');
|
|
|
+
|
|
|
+ // 下载文件
|
|
|
+ const fileName = `复习资料导入模板_${new Date().toISOString().slice(0, 10)}.xlsx`;
|
|
|
+ XLSX.writeFile(wb, fileName);
|
|
|
+
|
|
|
+ this.$message.success('模板下载成功');
|
|
|
+ },
|
|
|
+
|
|
|
+ // 切换全屏
|
|
|
+ toggleFullScreen() {
|
|
|
+ this.isFullScreen = !this.isFullScreen;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理文件选择
|
|
|
+ handleFileChange(file, fileList) {
|
|
|
+ this.currentFile = file.raw;
|
|
|
+ this.readExcel(file.raw);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理文件移除
|
|
|
+ handleFileRemove(file, fileList) {
|
|
|
+ this.currentFile = null;
|
|
|
+ this.excelData = [];
|
|
|
+ },
|
|
|
+
|
|
|
+ // 读取Excel文件
|
|
|
+ readExcel(file) {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (e) => {
|
|
|
+ try {
|
|
|
+ const data = new Uint8Array(e.target.result);
|
|
|
+ const workbook = XLSX.read(data, { type: 'array' });
|
|
|
+ const sheetName = workbook.SheetNames[0];
|
|
|
+ const worksheet = workbook.Sheets[sheetName];
|
|
|
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
|
|
+
|
|
|
+ if (jsonData.length < 2) {
|
|
|
+ this.$message.error('Excel文件数据不足,至少需要标题行和一行数据');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理数据
|
|
|
+ this.processExcelData(jsonData);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Excel读取错误:', error);
|
|
|
+ this.$message.error('Excel文件读取失败,请检查文件格式');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.readAsArrayBuffer(file);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理Excel数据
|
|
|
+ processExcelData(jsonData) {
|
|
|
+ const headers = jsonData[0];
|
|
|
+ const dataRows = jsonData.slice(1);
|
|
|
+
|
|
|
+ // 验证表头
|
|
|
+ const requiredHeaders = ['名称', '类型', '内容'];
|
|
|
+ const headerMap = {};
|
|
|
+ headers.forEach((header, index) => {
|
|
|
+ if (requiredHeaders.includes(header)) {
|
|
|
+ headerMap[header] = index;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (Object.keys(headerMap).length < requiredHeaders.length) {
|
|
|
+ this.$message.error(`Excel表头必须包含: ${requiredHeaders.join(', ')}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理数据行
|
|
|
+ this.excelData = dataRows.map((row, index) => {
|
|
|
+ const name = row[headerMap['名称']] || '';
|
|
|
+ const type = row[headerMap['类型']] || '';
|
|
|
+ const content = row[headerMap['内容']] || '';
|
|
|
+
|
|
|
+ // 处理类型转换
|
|
|
+ let paper_type = null;
|
|
|
+ if (type) {
|
|
|
+ const typeStr = String(type).trim();
|
|
|
+ if (typeStr === '1' || typeStr === '单招' || typeStr === '单招题') {
|
|
|
+ paper_type = 1;
|
|
|
+ } else if (typeStr === '2' || typeStr === '对口' || typeStr === '对口题') {
|
|
|
+ paper_type = 2;
|
|
|
+ } else if (typeStr === '3' || typeStr === '专升本' || typeStr === '专升本题') {
|
|
|
+ paper_type = 3;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: String(name).trim(),
|
|
|
+ type: String(type).trim(),
|
|
|
+ paper_type: paper_type,
|
|
|
+ content: String(content).trim()
|
|
|
+ };
|
|
|
+ }).filter(item => item.name && item.content); // 过滤空数据
|
|
|
+
|
|
|
+ if (this.excelData.length === 0) {
|
|
|
+ this.$message.warning('没有找到有效的数据行');
|
|
|
+ } else {
|
|
|
+ this.$message.success(`成功读取 ${this.excelData.length} 条数据`);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取试卷类型文本
|
|
|
+ getPaperTypeText(paperType) {
|
|
|
+ const types = {
|
|
|
+ 1: '单招题',
|
|
|
+ 2: '对口题',
|
|
|
+ 3: '专升本题'
|
|
|
+ };
|
|
|
+ return types[paperType] || '未知';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取试卷类型标签类型
|
|
|
+ getPaperTypeTagType(paperType) {
|
|
|
+ const types = {
|
|
|
+ 1: 'primary',
|
|
|
+ 2: 'success',
|
|
|
+ 3: 'warning'
|
|
|
+ };
|
|
|
+ return types[paperType] || '';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除行
|
|
|
+ removeRow(index) {
|
|
|
+ this.excelData.splice(index, 1);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 确认导入
|
|
|
+ async handleConfirm() {
|
|
|
+ if (this.excelData.length === 0) {
|
|
|
+ this.$message.error('请先上传Excel文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证数据
|
|
|
+ const invalidRows = this.excelData.filter(item => !item.name || !item.content || !item.paper_type);
|
|
|
+ if (invalidRows.length > 0) {
|
|
|
+ this.$message.error('存在无效数据,请检查名称、内容和试卷类型是否完整');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.importing = true;
|
|
|
+ try {
|
|
|
+ // 准备导入数据
|
|
|
+ const importData = {
|
|
|
+ articles: this.excelData.map(item => ({
|
|
|
+ title: item.name,
|
|
|
+ paper_type: item.paper_type,
|
|
|
+ content: item.content,
|
|
|
+ type: 21 // 固定为复习资料类型
|
|
|
+ }))
|
|
|
+ };
|
|
|
+
|
|
|
+ // 调用导入API
|
|
|
+ const response = await this.$http.post('/article/importReviewMaterials', importData);
|
|
|
+
|
|
|
+ if (response.data.code === 0) {
|
|
|
+ this.$message.success(`成功导入 ${this.excelData.length} 条复习资料`);
|
|
|
+ this.$emit('imported');
|
|
|
+ this.handleClose();
|
|
|
+ } else {
|
|
|
+ this.$message.error(response.data.msg || '导入失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('导入错误:', error);
|
|
|
+ this.$message.error('导入失败:' + (error.message || '网络错误'));
|
|
|
+ } finally {
|
|
|
+ this.importing = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 关闭对话框
|
|
|
+ handleClose() {
|
|
|
+ this.$emit('update:visible', false);
|
|
|
+ this.resetForm();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重置表单
|
|
|
+ resetForm() {
|
|
|
+ this.excelData = [];
|
|
|
+ this.currentFile = null;
|
|
|
+ this.importing = false;
|
|
|
+ if (this.$refs.upload) {
|
|
|
+ this.$refs.upload.clearFiles();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.excel-dialog {
|
|
|
+ z-index: 3002 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.excel-dialog~.v-modal {
|
|
|
+ z-index: 3001 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.import-actions {
|
|
|
+ margin: 20px 0;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.excel-import-area {
|
|
|
+ margin: 20px 0;
|
|
|
+ padding: 20px;
|
|
|
+ border: 2px dashed #d9d9d9;
|
|
|
+ border-radius: 6px;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.excel-preview {
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.excel-preview h4 {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+
|
|
|
+.mb-10 {
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.mt-16 {
|
|
|
+ margin-top: 16px;
|
|
|
+}
|
|
|
+</style>
|