罗永浩 пре 5 месеци
родитељ
комит
b51e6f0dad

+ 218 - 0
addons/admin/src/views/exam/articleList.vue

@@ -0,0 +1,218 @@
+<template>
+    <div class="ele-body">
+        <el-card shadow="never">
+            <!-- 搜索表单 -->
+            <el-form :model="table.where" label-width="90px" class="ele-form-search"
+                @keyup.enter.native="$refs.table.reload()" @submit.native.prevent>
+                <el-row :gutter="15">
+                    <el-col :md="6" :sm="12">
+                        <el-form-item label="试卷类型:">
+                            <el-select v-model="table.where.paper_type" placeholder="请选择试卷类型" clearable
+                                class="ele-fluid">
+                                <el-option label="单招题" :value="1" />
+                                <el-option label="对口题" :value="2" />
+                                <el-option label="专升本题" :value="3" />
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="6" :sm="12">
+                        <el-form-item label="文章标题:">
+                            <el-input v-model="table.where.keyword" placeholder="请输入文章标题" clearable />
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="6" :sm="12">
+                        <div class="ele-form-actions">
+                            <el-button type="primary" @click="$refs.table.reload()" icon="el-icon-search"
+                                class="ele-btn-icon">查询</el-button>
+                            <el-button @click="resetSearch">重置</el-button>
+                        </div>
+                    </el-col>
+                </el-row>
+            </el-form>
+
+            <!-- 操作按钮 -->
+            <div class="ele-table-tool ele-table-tool-default">
+                <el-button @click="openForm()" type="primary" icon="el-icon-plus" class="ele-btn-icon mr-10"
+                    size="small" v-if="permission.includes(permissionMap['add'])">新增复习资料</el-button>
+                <el-button @click="openImport()" type="success" icon="el-icon-upload2" class="ele-btn-icon mr-10"
+                    size="small" v-if="permission.includes(permissionMap['add'])">导入Excel</el-button>
+                <el-button @click="remove()" type="danger" icon="el-icon-delete" class="ele-btn-icon mr-10" size="small"
+                    v-if="permission.includes(permissionMap['delete'])">批量删除</el-button>
+            </div>
+
+            <!-- 数据表格 -->
+            <ele-data-table ref="table" :config="table" :choose.sync="choose" height="calc(100vh - 315px)"
+                highlight-current-row>
+                <template slot-scope="{ index }">
+                    <el-table-column type="selection" width="45" align="center" fixed="left" />
+                    <el-table-column type="index" :index="index" label="编号" width="60" align="center" fixed="left" />
+                    <el-table-column label="文章标题" width="300">
+                        <template slot-scope="{ row }">
+                            <text-ellipsis :text="row.title" :max-length="20" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column 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 label="内容" min-width="200">
+                        <template slot-scope="{ row }">
+                            <el-button type="text" @click="showContentDialog(row)" size="mini">
+                                查看内容
+                            </el-button>
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="操作" width="160" fixed="right">
+                        <template slot-scope="{ row }">
+                            <el-link @click="openForm(row)" icon="el-icon-edit" type="primary" :underline="false"
+                                v-if="permission.includes(permissionMap['edit'])">修改</el-link>
+                            <el-popconfirm title="确定要删除此复习资料吗?" @confirm="remove(row)" class="ele-action">
+                                <el-link slot="reference" icon="el-icon-delete" type="danger" :underline="false"
+                                    v-if="permission.includes(permissionMap['delete'])">删除</el-link>
+                            </el-popconfirm>
+                        </template>
+                    </el-table-column>
+                </template>
+            </ele-data-table>
+        </el-card>
+
+        <article-form :visible.sync="formDialogVisible" :is-edit="isEdit" :article-id="formData.id" :default-type="21"
+            :default-paper-type="paperType" @saved="$refs.table.reload()" />
+
+        <!-- Excel导入弹窗 -->
+        <article-excel-import :visible.sync="importDialogVisible" @imported="$refs.table.reload()" />
+
+        <!-- 内容查看弹窗 -->
+        <el-dialog title="内容预览" :visible.sync="contentDialogVisible" width="800px" append-to-body>
+            <div v-html="currentContent" class="content-preview"></div>
+            <div slot="footer">
+                <el-button @click="contentDialogVisible = false">关闭</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+import ArticleForm from "./component/ArticleForm.vue"
+import ArticleExcelImport from "./component/ArticleExcelImport.vue"
+
+export default {
+    props: {
+        type: { type: Number, default: 21 }, // 复习资料类型
+        paperType: { type: Number, default: 1 }, // 试卷类型:1-单招题,2-对口题,3-专升本题
+        permissionMap: { type: Object, default: null },
+    },
+    name: "ArticleList",
+    components: { ArticleForm, ArticleExcelImport },
+    data() {
+        return {
+            table: {
+                url: "/article/index",
+                where: {
+                    keyword: '',
+                    type: 21, // 写死为复习资料类型
+                    paper_type: this.paperType
+                }
+            },
+            choose: [],
+            formDialogVisible: false,
+            isEdit: false,
+            importDialogVisible: false,
+            contentDialogVisible: false,
+            currentContent: '',
+            formData: {
+                id: null,
+                title: "",
+                type: 21,
+                paper_type: this.paperType,
+                content: ""
+            }
+        };
+    },
+    computed: { ...mapGetters(["permission"]) },
+    methods: {
+        openForm(row) {
+            this.isEdit = !!row;
+            this.formData = row ? {
+                id: row.id,
+                title: row.title,
+                type: 21,
+                paper_type: row.paper_type,
+                content: row.content
+            } : {
+                id: null,
+                title: "",
+                type: 21,
+                paper_type: this.paperType,
+                content: ""
+            };
+            this.formDialogVisible = true;
+        },
+        openImport() {
+            this.importDialogVisible = true;
+        },
+        resetSearch() {
+            this.table.where = {
+                keyword: '',
+                type: 21,
+                paper_type: this.paperType
+            };
+            this.$refs.table.reload();
+        },
+        getPaperTypeText(paperType) {
+            const types = {
+                1: '单招题',
+                2: '对口题',
+                3: '专升本题'
+            };
+            return types[paperType] || '未知';
+        },
+        getPaperTypeTagType(paperType) {
+            const types = {
+                1: 'primary',
+                2: 'success',
+                3: 'warning'
+            };
+            return types[paperType] || '';
+        },
+        showContentDialog(row) {
+            this.currentContent = row.content || '暂无内容';
+            this.contentDialogVisible = true;
+        },
+        editStatus(row) {
+            this.$http
+                .post("/article/status", { id: row.id, status: row.status })
+                .then((res) => {
+                    if (res.data.code !== 0) this.$message.error(res.data.msg);
+                });
+        },
+        remove(row) {
+            let ids = row ? [row.id] : this.choose.map((d) => d.id);
+            if (ids.length === 0) return this.$message.error("请至少选择一条数据");
+            this.$confirm("确定要删除选中的复习资料吗?", "提示", { type: "warning" }).then(() => {
+                this.$http.post("/article/delete", { id: ids }).then((res) => {
+                    if (res.data.code === 0) {
+                        this.$message.success(res.data.msg);
+                        this.$refs.table.reload();
+                    } else this.$message.error(res.data.msg);
+                });
+            });
+        }
+    },
+};
+</script>
+
+<style scoped>
+.content-preview {
+    max-height: 500px;
+    overflow-y: auto;
+    padding: 10px;
+    border: 1px solid #e4e7ed;
+    border-radius: 4px;
+    background-color: #fafafa;
+}
+</style>

+ 347 - 0
addons/admin/src/views/exam/component/ArticleExcelImport.vue

@@ -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>

+ 159 - 0
addons/admin/src/views/exam/component/ArticleForm.vue

@@ -0,0 +1,159 @@
+<template>
+    <el-dialog :title="isEdit ? '编辑复习资料' : '新增复习资料'" append-to-body :visible.sync="visibleInternal" width="800px"
+        custom-class="article-form-dialog">
+        <div v-if="loaded">
+            <el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px">
+                <el-form-item label="文章标题" prop="title">
+                    <el-input v-model="formData.title" placeholder="请输入文章标题" />
+                </el-form-item>
+
+                <el-form-item label="试卷类型" prop="paper_type">
+                    <el-select v-model="formData.paper_type" placeholder="请选择试卷类型" class="ele-fluid">
+                        <el-option label="单招题" :value="1" />
+                        <el-option label="对口题" :value="2" />
+                        <el-option label="专升本题" :value="3" />
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item label="文章内容" prop="content">
+                    <tinymce-editor v-model="formData.content" :init="editorConfig" />
+                </el-form-item>
+            </el-form>
+
+            <div slot="footer">
+                <el-button @click="visibleInternal = false">取消</el-button>
+                <el-button type="primary" @click="saveForm" :loading="saving">保存</el-button>
+            </div>
+        </div>
+        <div v-else style="text-align:center;padding:50px 0;">加载中...</div>
+    </el-dialog>
+</template>
+
+<script>
+import TinymceEditor from '@/components/TinymceEditor/index.vue'
+
+export default {
+    name: "ArticleForm",
+    components: {
+        TinymceEditor
+    },
+    props: {
+        visible: { type: Boolean, default: false },
+        isEdit: { type: Boolean, default: false },
+        articleId: { type: [Number, null], default: null },
+        defaultType: { type: Number, default: 21 },
+        defaultPaperType: { type: Number, default: 1 }
+    },
+    data() {
+        return {
+            visibleInternal: this.visible,
+            loaded: false,
+            saving: false,
+            formData: {
+                id: null,
+                title: "",
+                type: 21, // 固定为复习资料类型
+                paper_type: this.defaultPaperType,
+                content: ""
+            },
+            formRules: {
+                title: [{ required: true, message: "请输入文章标题", trigger: "blur" }],
+                paper_type: [{ required: true, message: "请选择试卷类型", trigger: "change" }],
+                content: [{ required: true, message: "请输入文章内容", trigger: "blur" }]
+            },
+            editorConfig: {
+                height: 400,
+                branding: false,
+                language: 'zh_CN',
+                plugins: 'code print preview fullscreen paste searchreplace save autosave link autolink image imagetools media table codesample lists advlist hr charmap emoticons anchor directionality pagebreak quickbars nonbreaking visualblocks visualchars wordcount',
+                toolbar: 'fullscreen preview code | undo redo | forecolor backcolor | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | formatselect fontselect fontsizeselect | link image media emoticons charmap anchor pagebreak codesample | ltr rtl',
+                toolbar_drawer: 'sliding'
+            }
+        };
+    },
+    watch: {
+        visible(val) {
+            this.visibleInternal = val;
+        },
+        visibleInternal(val) {
+            this.$emit("update:visible", val);
+            if (val) {
+                this.loaded = false;
+                if (this.isEdit && this.articleId) {
+                    this.loadDetail();
+                } else {
+                    this.formData = {
+                        id: null,
+                        title: "",
+                        type: 21,
+                        paper_type: this.defaultPaperType,
+                        content: ""
+                    };
+                    this.loaded = true;
+                }
+            }
+        }
+    },
+    methods: {
+        async loadDetail() {
+            const res = await this.$http.get(`/article/info?id=${this.articleId}`);
+            if (res.data.code === 0) {
+                const data = res.data.data;
+                this.formData = {
+                    id: data.id,
+                    title: data.title,
+                    type: 21,
+                    paper_type: data.paper_type || this.defaultPaperType,
+                    content: data.content
+                };
+            }
+            this.loaded = true;
+        },
+        async saveForm() {
+            this.$refs.formRef.validate(async valid => {
+                if (!valid) return;
+                this.saving = true;
+                try {
+                    // 只提交需要的字段
+                    const submitData = {
+                        id: this.formData.id,
+                        title: this.formData.title,
+                        type: 21,
+                        paper_type: this.formData.paper_type,
+                        content: this.formData.content,
+                        status: 1, // 默认发布状态
+                        sort: 0, // 默认排序
+                        author: '', // 默认作者
+                        tags: '', // 默认标签
+                        cover: '', // 默认封面
+                        description: '', // 默认描述
+                        cate_id: 0 // 默认分类
+                    };
+
+                    const res = await this.$http.post("/article/edit", submitData);
+                    if (res.data.code === 0) {
+                        this.$message.success(this.isEdit ? "修改成功" : "新增成功");
+                        this.visibleInternal = false;
+                        this.$emit("saved");
+                    } else {
+                        this.$message.error(res.data.msg);
+                    }
+                } finally {
+                    this.saving = false;
+                }
+            });
+        }
+    }
+};
+</script>
+<style>
+/* 内层弹窗 */
+.article-form-dialog {
+    z-index: 3002 !important;
+}
+
+/* 内层弹窗的遮罩层 */
+.article-form-dialog~.v-modal {
+    z-index: 3001 !important;
+}
+</style>

+ 2 - 2
addons/admin/src/views/exam/index1-5.vue

@@ -1,9 +1,9 @@
 <template>
-    <examList :type=1 :sceneType=5 :permissionMap="permissionMap" />
+    <examList :paperType=1 :type=21 :permissionMap="permissionMap" />
 </template>
 
 <script>
-import examList from "./examList"
+import examList from "./articleList"
 
 export default {
     name: "index1-5",

+ 2 - 2
addons/admin/src/views/exam/index2-5.vue

@@ -1,9 +1,9 @@
 <template>
-    <examList :type=2 :sceneType=5 :permissionMap="permissionMap" />
+    <examList :paperType=2 :type=21 :permissionMap="permissionMap" />
 </template>
 
 <script>
-import examList from "./examList"
+import examList from "./articleList"
 
 export default {
     name: "index2-5",

+ 2 - 2
addons/admin/src/views/exam/index3-5.vue

@@ -1,9 +1,9 @@
 <template>
-    <examList :type=3 :sceneType=5 :permissionMap="permissionMap" />
+    <examList :paperType=3 :type=21 :permissionMap="permissionMap" />
 </template>
 
 <script>
-import examList from "./examList"
+import examList from "./articleList"
 
 export default {
     name: "index3-5",

+ 27 - 0
app/Http/Controllers/Admin/ArticleCategoryController.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Services\Common\ArticleCategoryService;
+
+/**
+ * 文章分类管理-控制器
+ * @author laravel开发员
+ * @since 2020/11/11
+ * Class ArticleCategoryController
+ * @package App\Http\Controllers\Admin
+ */
+class ArticleCategoryController extends Backend
+{
+    /**
+     * 构造函数
+     * @author laravel开发员
+     * @since 2020/11/11
+     * ArticleCategoryController constructor.
+     */
+    public function __construct()
+    {
+        parent::__construct();
+        $this->service = new ArticleCategoryService();
+    }
+}

+ 14 - 1
app/Http/Controllers/Admin/ArticleController.php

@@ -1,6 +1,7 @@
 <?php
 
 namespace App\Http\Controllers\Admin;
+
 use App\Services\Common\ArticleService;
 
 /**
@@ -41,7 +42,7 @@ class ArticleController extends Backend
 
     /**
      * 列表
-     * @return array
+     * @return \Illuminate\Http\JsonResponse
      */
     public function import()
     {
@@ -50,4 +51,16 @@ class ArticleController extends Backend
 
         return response()->json($result);
     }
+
+    /**
+     * 导入复习资料
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function importReviewMaterials()
+    {
+        $data = request()->all();
+        $result = $this->service->importReviewMaterials($data);
+
+        return response()->json($result);
+    }
 }

+ 15 - 0
app/Models/ArticleCategoryModel.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * 文章分类管理-模型
+ * @author laravel开发员
+ * @since 2020/11/11
+ * @package App\Models
+ */
+class ArticleCategoryModel extends BaseModel
+{
+    // 设置数据表
+    protected $table = 'article_cates';
+}

+ 0 - 1
app/Models/ArticleModel.php

@@ -21,5 +21,4 @@ class ArticleModel extends BaseModel
 {
     // 设置数据表
     protected $table = 'article';
-
 }

+ 42 - 0
app/Services/Common/ArticleCategoryService.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Services\Common;
+
+use App\Models\ArticleCategoryModel;
+use App\Services\BaseService;
+
+/**
+ * 文章分类管理-服务类
+ * @author laravel开发员
+ * @since 2020/11/11
+ * Class ArticleCategoryService
+ * @package App\Services\Common
+ */
+class ArticleCategoryService extends BaseService
+{
+    /**
+     * 构造函数
+     * @author laravel开发员
+     * @since 2020/11/11
+     * ArticleCategoryService constructor.
+     */
+    public function __construct()
+    {
+        $this->model = new ArticleCategoryModel();
+    }
+
+    /**
+     * 获取分类选项列表
+     * @return array
+     */
+    public function options()
+    {
+        $datas = $this->model->where(['status' => 1, 'mark' => 1])
+            ->select(['id', 'name'])
+            ->orderBy('sort', 'desc')
+            ->orderBy('id', 'desc')
+            ->get();
+
+        return $datas ? $datas->toArray() : [];
+    }
+}

+ 56 - 2
app/Services/Common/ArticleService.php

@@ -59,6 +59,8 @@ class ArticleService extends BaseService
         $where = ['a.mark' => 1];
         $status = isset($params['status']) ? $params['status'] : 0;
         $type = isset($params['type']) ? $params['type'] : 0;
+        $paperType = isset($params['paper_type']) ? $params['paper_type'] : 0;
+
         if ($status > 0) {
             $where['a.status'] = $status;
         }
@@ -71,6 +73,11 @@ class ArticleService extends BaseService
                     $query->where('a.type', $type);
                 }
             })
+            ->where(function ($query) use ($paperType) {
+                if ($paperType) {
+                    $query->where('a.paper_type', $paperType);
+                }
+            })
             ->where(function ($query) use ($params) {
                 $keyword = isset($params['keyword']) ? $params['keyword'] : '';
                 if ($keyword) {
@@ -106,6 +113,11 @@ class ArticleService extends BaseService
     {
         $data = request()->all();
 
+        // 处理封面图片
+        if (!empty($data['cover'])) {
+            $data['cover'] = get_image_path(trim($data['cover']));
+        }
+
         $content = isset($data['content']) ? $data['content'] : '';
         if ($content) {
             $data['content'] = set_format_content($content);
@@ -114,7 +126,7 @@ class ArticleService extends BaseService
         // 设置日志标题
         ActionLogModel::setTitle("发布文章或客服咨询信息");
         ActionLogModel::record();
-        return parent::edit($data); // TODO: Change the autogenerated stub
+        return parent::edit($data);
     }
 
     public function importArticles($data)
@@ -147,6 +159,49 @@ class ArticleService extends BaseService
     }
 
     /**
+     * 导入复习资料
+     * @param array $data
+     * @return array
+     */
+    public function importReviewMaterials($data)
+    {
+        DB::beginTransaction();
+        try {
+            $articles = $data['articles'] ?? [];
+
+            foreach ($articles as $item) {
+                if (empty($item['title']) || empty($item['content'])) {
+                    throw new Exception("Title or content missing in import data");
+                }
+
+                DB::table('article')->insert([
+                    'title' => $item['title'],
+                    'content' => $item['content'],
+                    'paper_type' => $item['paper_type'],
+                    'type' => 21, // 复习资料类型
+                    'author' => '',
+                    'tags' => '',
+                    'cover' => '',
+                    'description' => '',
+                    'cate_id' => 0,
+                    'sort' => 0,
+                    'view_num' => 0,
+                    'status' => 1,
+                    'create_time' => time(),
+                    'update_time' => time(),
+                    'mark' => 1,
+                ]);
+            }
+
+            DB::commit();
+            return ['code' => 0, 'msg' => '成功导入 ' . count($articles) . ' 条复习资料'];
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return ['code' => 1, 'msg' => '导入失败: ' . $e->getMessage()];
+        }
+    }
+
+    /**
      * 删除七天之前标记软删除的数据
      */
     public function delete()
@@ -157,5 +212,4 @@ class ArticleService extends BaseService
         $this->model->where('mark', 0)->where('update_time', '<=', time() - 7 * 86400)->delete();
         return parent::delete();
     }
-
 }

+ 4 - 0
routes/web.php

@@ -127,6 +127,10 @@ Route::post('/article/edit', [\App\Http\Controllers\Admin\ArticleController::cla
 Route::post('/article/delete', [\App\Http\Controllers\Admin\ArticleController::class, 'delete']);
 Route::post('/article/status', [\App\Http\Controllers\Admin\ArticleController::class, 'status']);
 Route::post('/article/import', [\App\Http\Controllers\Admin\ArticleController::class, 'import']);
+Route::post('/article/importReviewMaterials', [\App\Http\Controllers\Admin\ArticleController::class, 'importReviewMaterials']);
+
+// 文章分类管理
+Route::get('/articleCategory/options', [\App\Http\Controllers\Admin\ArticleCategoryController::class, 'options']);
 
 // 广告管理
 Route::get('/ad/index', [AdController::class, 'index']);