|
|
@@ -1,22 +1,50 @@
|
|
|
<template>
|
|
|
- <el-dialog :title="isEdit ? '编辑复习资料' : '新增复习资料'" append-to-body :visible.sync="visibleInternal" width="800px"
|
|
|
+ <el-dialog :title="isEdit ? '编辑复习资料' : '新增复习资料'" append-to-body :visible.sync="visibleInternal" min-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 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-form-item label="分类" prop="cate_id">
|
|
|
+ <el-select v-model="formData.cate_id" placeholder="请选择分类" class="ele-fluid" clearable>
|
|
|
+ <el-option v-for="category in categoryOptions" :key="category.id" :label="category.name"
|
|
|
+ :value="category.id" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="文章内容" prop="content">
|
|
|
- <tinymce-editor v-model="formData.content" :init="editorConfig" />
|
|
|
+ <el-form-item label="内容类型" prop="content_type">
|
|
|
+ <el-radio-group v-model="formData.content_type" @change="onContentTypeChange">
|
|
|
+ <el-radio :label="1">富文本编辑</el-radio>
|
|
|
+ <el-radio :label="2">Word文档</el-radio>
|
|
|
+ <el-radio :label="3">图片</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="内容" prop="content">
|
|
|
+ <!-- 富文本编辑器 -->
|
|
|
+ <tinymce-editor v-if="formData.content_type === 1" v-model="formData.content"
|
|
|
+ :init="editorConfig" />
|
|
|
+
|
|
|
+ <!-- Word文档上传 -->
|
|
|
+ <div v-else-if="formData.content_type === 2" class="word-upload-container">
|
|
|
+ <el-upload class="word-uploader" :show-file-list="false" :before-upload="beforeWordUpload"
|
|
|
+ :auto-upload="false" :on-change="onWordFileChange" accept=".doc,.docx">
|
|
|
+ <el-button type="primary" icon="el-icon-upload">选择Word文档</el-button>
|
|
|
+ </el-upload>
|
|
|
+ <div v-if="formData.word_file" class="word-file-info">
|
|
|
+ <i class="el-icon-document"></i>
|
|
|
+ <span>{{ formData.word_file_name }}</span>
|
|
|
+ <el-button type="text" @click="removeWordFile" size="mini">删除</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图片上传 -->
|
|
|
+ <div v-else-if="formData.content_type === 3" class="image-upload-container">
|
|
|
+ <uploadImage :limit="1" v-model="formData.image_url" @upload-success="onImageUploadSuccess">
|
|
|
+ </uploadImage>
|
|
|
+ </div>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
|
|
|
@@ -31,18 +59,20 @@
|
|
|
|
|
|
<script>
|
|
|
import TinymceEditor from '@/components/TinymceEditor/index.vue'
|
|
|
+import uploadImage from '@/components/uploadImage.vue'
|
|
|
|
|
|
export default {
|
|
|
name: "ArticleForm",
|
|
|
components: {
|
|
|
- TinymceEditor
|
|
|
+ TinymceEditor,
|
|
|
+ uploadImage
|
|
|
},
|
|
|
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 }
|
|
|
+ article_cate_type: { type: Number, default: 1 }
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
@@ -53,13 +83,19 @@ export default {
|
|
|
id: null,
|
|
|
title: "",
|
|
|
type: 21, // 固定为复习资料类型
|
|
|
- paper_type: this.defaultPaperType,
|
|
|
- content: ""
|
|
|
+ cate_id: null,
|
|
|
+ content: "",
|
|
|
+ content_type: 1, // 内容类型:1-富文本,2-Word文档,3-图片
|
|
|
+ word_file: null, // Word文档文件对象
|
|
|
+ word_file_name: "", // Word文档名称
|
|
|
+ image_url: "" // 图片URL
|
|
|
},
|
|
|
+ categoryOptions: [],
|
|
|
formRules: {
|
|
|
- title: [{ required: true, message: "请输入文章标题", trigger: "blur" }],
|
|
|
- paper_type: [{ required: true, message: "请选择试卷类型", trigger: "change" }],
|
|
|
- content: [{ required: true, message: "请输入文章内容", trigger: "blur" }]
|
|
|
+ title: [{ required: true, message: "请输入标题", trigger: "blur" }],
|
|
|
+ cate_id: [{ required: true, message: "请选择分类", trigger: "change" }],
|
|
|
+ content_type: [{ required: true, message: "请选择内容类型", trigger: "change" }],
|
|
|
+ content: [{ required: true, message: "请输入内容", trigger: "blur" }]
|
|
|
},
|
|
|
editorConfig: {
|
|
|
height: 400,
|
|
|
@@ -67,7 +103,41 @@ export default {
|
|
|
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'
|
|
|
+ toolbar_drawer: 'sliding',
|
|
|
+ // 确保HTML内容能正确显示
|
|
|
+ valid_elements: '*[*]',
|
|
|
+ valid_children: '+body[style]',
|
|
|
+ // 允许所有HTML标签
|
|
|
+ extended_valid_elements: 'img[src|alt|width|height|style|class],div[class|style],p[style],h1[style],h2[style],h3[style],hr[style]',
|
|
|
+ // 确保图片能正确显示
|
|
|
+ convert_urls: false,
|
|
|
+ // 允许内联样式
|
|
|
+ allow_script_urls: true,
|
|
|
+ // 保持HTML格式
|
|
|
+ keep_styles: true,
|
|
|
+ // 不清理HTML
|
|
|
+ cleanup: false,
|
|
|
+ cleanup_on_startup: false,
|
|
|
+ verify_html: false,
|
|
|
+ // 允许所有属性
|
|
|
+ allow_html_data_urls: true,
|
|
|
+ // 不自动转换URL
|
|
|
+ relative_urls: false,
|
|
|
+ remove_script_host: false,
|
|
|
+ // 保持原始HTML结构
|
|
|
+ preserve_cdata: true,
|
|
|
+ // 允许所有CSS样式
|
|
|
+ allow_conditional_comments: true,
|
|
|
+ // 不自动格式化
|
|
|
+ auto_focus: false,
|
|
|
+ // 保持换行
|
|
|
+ convert_newlines_to_brs: true,
|
|
|
+ // 不自动清理
|
|
|
+ cleanup_on_startup: false,
|
|
|
+ // 允许所有标签
|
|
|
+ valid_elements: '*[*]',
|
|
|
+ // 不验证HTML
|
|
|
+ verify_html: false
|
|
|
}
|
|
|
};
|
|
|
},
|
|
|
@@ -86,10 +156,14 @@ export default {
|
|
|
id: null,
|
|
|
title: "",
|
|
|
type: 21,
|
|
|
- paper_type: this.defaultPaperType,
|
|
|
- content: ""
|
|
|
+ cate_id: null,
|
|
|
+ content: "",
|
|
|
+ content_type: 1,
|
|
|
+ word_file: null,
|
|
|
+ word_file_name: "",
|
|
|
+ image_url: ""
|
|
|
};
|
|
|
- this.loaded = true;
|
|
|
+ this.loadCategories();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -97,17 +171,29 @@ export default {
|
|
|
methods: {
|
|
|
async loadDetail() {
|
|
|
const res = await this.$http.get(`/article/info?id=${this.articleId}`);
|
|
|
+ console.log('Article info response:', res.data);
|
|
|
if (res.data.code === 0) {
|
|
|
const data = res.data.data;
|
|
|
+ console.log('Article data:', data);
|
|
|
+ // 先加载分类选项
|
|
|
+ await this.loadCategories();
|
|
|
+ console.log('Category options loaded:', this.categoryOptions);
|
|
|
+ // 然后设置表单数据
|
|
|
this.formData = {
|
|
|
id: data.id,
|
|
|
title: data.title,
|
|
|
type: 21,
|
|
|
- paper_type: data.paper_type || this.defaultPaperType,
|
|
|
- content: data.content
|
|
|
+ cate_id: data.cate_id || null,
|
|
|
+ content: data.content || "",
|
|
|
+ content_type: data.content_type || 1,
|
|
|
+ word_file: null, // 编辑时不加载文件对象
|
|
|
+ word_file_name: data.word_file_name || "",
|
|
|
+ image_url: data.image_url || ""
|
|
|
};
|
|
|
+ console.log('Form data set:', this.formData);
|
|
|
+ } else {
|
|
|
+ await this.loadCategories();
|
|
|
}
|
|
|
- this.loaded = true;
|
|
|
},
|
|
|
async saveForm() {
|
|
|
this.$refs.formRef.validate(async valid => {
|
|
|
@@ -119,7 +205,7 @@ export default {
|
|
|
id: this.formData.id,
|
|
|
title: this.formData.title,
|
|
|
type: 21,
|
|
|
- paper_type: this.formData.paper_type,
|
|
|
+ cate_id: this.formData.cate_id,
|
|
|
content: this.formData.content,
|
|
|
status: 1, // 默认发布状态
|
|
|
sort: 0, // 默认排序
|
|
|
@@ -127,7 +213,6 @@ export default {
|
|
|
tags: '', // 默认标签
|
|
|
cover: '', // 默认封面
|
|
|
description: '', // 默认描述
|
|
|
- cate_id: 0 // 默认分类
|
|
|
};
|
|
|
|
|
|
const res = await this.$http.post("/article/edit", submitData);
|
|
|
@@ -142,6 +227,196 @@ export default {
|
|
|
this.saving = false;
|
|
|
}
|
|
|
});
|
|
|
+ },
|
|
|
+ async loadCategories() {
|
|
|
+ try {
|
|
|
+ const res = await this.$http.get('/articleCategory/options', {
|
|
|
+ params: {
|
|
|
+ type: this.article_cate_type
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (res.data.code === 0) {
|
|
|
+ this.categoryOptions = res.data.data || [];
|
|
|
+ }
|
|
|
+ this.loaded = true;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载分类选项失败:', error);
|
|
|
+ this.loaded = true;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 内容类型变化处理
|
|
|
+ onContentTypeChange(type) {
|
|
|
+ // 切换内容类型时清空其他类型的数据,但保留已解析的内容
|
|
|
+ if (type === 1) {
|
|
|
+ // 富文本编辑 - 不清空content,保留已解析的内容
|
|
|
+ this.formData.word_file = null;
|
|
|
+ this.formData.word_file_name = '';
|
|
|
+ this.formData.image_url = '';
|
|
|
+ } else if (type === 2) {
|
|
|
+ // Word文档 - 如果还没有解析内容,则清空content
|
|
|
+ if (!this.formData.word_file) {
|
|
|
+ this.formData.content = '';
|
|
|
+ }
|
|
|
+ this.formData.image_url = '';
|
|
|
+ } else if (type === 3) {
|
|
|
+ // 图片 - 如果还没有解析内容,则清空content
|
|
|
+ if (!this.formData.image_url) {
|
|
|
+ this.formData.content = '';
|
|
|
+ }
|
|
|
+ this.formData.word_file = null;
|
|
|
+ this.formData.word_file_name = '';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // Word文档上传前验证
|
|
|
+ beforeWordUpload(file) {
|
|
|
+ const isWord = file.type === 'application/msword' ||
|
|
|
+ file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
|
+ if (!isWord) {
|
|
|
+ this.$message.error('只能上传Word文档!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const isLt10M = file.size / 1024 / 1024 < 10;
|
|
|
+ if (!isLt10M) {
|
|
|
+ this.$message.error('Word文档大小不能超过 10MB!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 返回false阻止自动上传,我们使用on-change处理
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // Word文档文件选择变化
|
|
|
+ onWordFileChange(file, fileList) {
|
|
|
+ if (file.raw) {
|
|
|
+ this.formData.word_file = file.raw;
|
|
|
+ this.formData.word_file_name = file.name;
|
|
|
+ this.$message.success('Word文档选择成功');
|
|
|
+
|
|
|
+ // 直接解析Word文档内容
|
|
|
+ this.parseWordContentFromFile(file.raw);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除Word文档
|
|
|
+ removeWordFile() {
|
|
|
+ this.formData.word_file = null;
|
|
|
+ this.formData.word_file_name = '';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片上传前验证
|
|
|
+ beforeImageUpload(file) {
|
|
|
+ const isImage = file.type.startsWith('image/');
|
|
|
+ if (!isImage) {
|
|
|
+ this.$message.error('只能上传图片!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const isLt2M = file.size / 1024 / 1024 < 2;
|
|
|
+ if (!isLt2M) {
|
|
|
+ this.$message.error('图片大小不能超过 2MB!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片上传成功
|
|
|
+ onImageUploadSuccess(uploadData) {
|
|
|
+ this.$message.success('图片上传成功');
|
|
|
+
|
|
|
+ // 将图片内容显示在富文本编辑器中
|
|
|
+ this.parseImageContent(uploadData.url, uploadData.fileName || '上传的图片');
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片上传失败
|
|
|
+ onImageUploadError(error) {
|
|
|
+ this.$message.error('图片上传失败');
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除图片
|
|
|
+ removeImage() {
|
|
|
+ this.formData.image_url = '';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 解析Word文档内容(从文件对象)
|
|
|
+ async parseWordContentFromFile(file) {
|
|
|
+ try {
|
|
|
+ // 切换到富文本编辑模式
|
|
|
+ this.formData.content_type = 1;
|
|
|
+
|
|
|
+ // 使用前端解析Word文档
|
|
|
+ const content = await this.parseWordWithFrontendFromFile(file);
|
|
|
+
|
|
|
+ // 将解析的内容设置到富文本编辑器中
|
|
|
+ this.formData.content = content;
|
|
|
+ this.$message.success('Word文档内容解析成功,已加载到编辑器中');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('解析Word文档失败:', error);
|
|
|
+ // 解析失败时的备用内容
|
|
|
+ this.formData.content = `<div class="word-document">
|
|
|
+ <h3>Word文档:${file.name}</h3>
|
|
|
+ <p>文件大小:${(file.size / 1024 / 1024).toFixed(2)} MB</p>
|
|
|
+ <p>上传时间:${new Date().toLocaleString()}</p>
|
|
|
+ <p>注意:Word文档内容解析失败,请手动编辑内容</p>
|
|
|
+ </div>`;
|
|
|
+ this.$message.warning('Word文档内容解析失败,请手动编辑内容');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 前端解析Word文档(从文件对象)
|
|
|
+ async parseWordWithFrontendFromFile(file) {
|
|
|
+ try {
|
|
|
+ // 导入mammoth
|
|
|
+ const mammoth = require('mammoth');
|
|
|
+
|
|
|
+ // 读取文件内容
|
|
|
+ const arrayBuffer = await file.arrayBuffer();
|
|
|
+
|
|
|
+ // 使用mammoth解析Word文档
|
|
|
+ const result = await mammoth.convertToHtml({ arrayBuffer: arrayBuffer });
|
|
|
+
|
|
|
+ let content = '';
|
|
|
+
|
|
|
+ if (result.value) {
|
|
|
+ // 获取解析后的HTML内容
|
|
|
+ content = '<div class="word-content">' + result.value + '</div>';
|
|
|
+
|
|
|
+ // 如果有警告信息,在控制台显示
|
|
|
+ if (result.messages && result.messages.length > 0) {
|
|
|
+ console.log('Word文档解析警告:', result.messages);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$message.success('Word文档解析成功');
|
|
|
+ } else {
|
|
|
+ content = '<div class="word-content"><p>Word文档解析失败,请手动输入或粘贴内容。</p></div>';
|
|
|
+ this.$message.warning('Word文档解析失败,请手动输入内容');
|
|
|
+ }
|
|
|
+
|
|
|
+ return content;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('前端解析Word文档失败:', error);
|
|
|
+ this.$message.error('Word文档解析失败: ' + error.message);
|
|
|
+ return '<div class="word-content"><p>Word文档解析失败,请手动输入内容。</p></div>';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // HTML转义函数
|
|
|
+ escapeHtml(text) {
|
|
|
+ const div = document.createElement('div');
|
|
|
+ div.textContent = text;
|
|
|
+ return div.innerHTML;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 解析图片内容
|
|
|
+ parseImageContent(url, fileName) {
|
|
|
+ // 切换到富文本编辑模式
|
|
|
+ this.formData.content_type = 1;
|
|
|
+
|
|
|
+ // 将图片内容设置到富文本编辑器中
|
|
|
+ this.formData.content = `<div class="image-content">
|
|
|
+ <img src="${url}" alt="${fileName}" style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px; margin: 10px 0;">
|
|
|
+ </div>`;
|
|
|
+
|
|
|
+ this.$message.success('图片已加载到编辑器中,您可以继续编辑');
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
@@ -156,4 +431,79 @@ export default {
|
|
|
.article-form-dialog~.v-modal {
|
|
|
z-index: 3001 !important;
|
|
|
}
|
|
|
+
|
|
|
+/* Word文档上传样式 */
|
|
|
+.word-upload-container {
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 20px;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.word-file-info {
|
|
|
+ margin-top: 10px;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #f0f9ff;
|
|
|
+ border: 1px solid #bae6fd;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.word-file-info i {
|
|
|
+ color: #0369a1;
|
|
|
+ margin-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.word-file-info span {
|
|
|
+ flex: 1;
|
|
|
+ color: #0369a1;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图片上传样式 */
|
|
|
+.image-upload-container {
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 20px;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.image-uploader {
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+.uploaded-image {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ object-fit: cover;
|
|
|
+ border-radius: 6px;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+}
|
|
|
+
|
|
|
+.image-uploader-icon {
|
|
|
+ font-size: 28px;
|
|
|
+ color: #8c939d;
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ line-height: 200px;
|
|
|
+ text-align: center;
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
+ border-radius: 6px;
|
|
|
+ background-color: #fafafa;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.image-uploader-icon:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.image-actions {
|
|
|
+ margin-top: 10px;
|
|
|
+}
|
|
|
</style>
|