Files
schoolNews/schoolNewsWeb/src/views/article/ArticleAddView.vue

400 lines
12 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="article-add-view">
<div class="page-header">
<el-button @click="handleBack" :icon="ArrowLeft">返回</el-button>
<h1 class="page-title">{{ isEdit ? '编辑文章' : '创建文章' }}</h1>
</div>
<div class="article-form">
<el-form ref="formRef" :model="articleForm" :rules="rules" label-width="100px" label-position="top">
<!-- 标题 -->
<el-form-item label="文章标题" prop="title">
<el-input v-model="articleForm.title" placeholder="请输入文章标题" maxlength="100" show-word-limit />
</el-form-item>
<!-- 分类和标签 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="文章分类" prop="categoryID">
<el-select v-model="articleForm.categoryID" placeholder="请选择分类" style="width: 100%" :loading="categoryLoading">
<el-option
v-for="category in categoryList"
:key="category.categoryID || category.id"
:label="category.name"
:value="category.categoryID || category.id || ''"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="标签" prop="tags">
<el-select v-model="articleForm.tags" multiple placeholder="请选择标签" style="width: 100%" :loading="tagLoading">
<el-option
v-for="tag in tagList"
:key="tag.id || tag.tagID"
:label="tag.name"
:value="tag.id || tag.tagID || ''"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 封面图 -->
<el-form-item label="封面图片">
<!-- 上传区域 - 只在没有封面图片时显示 -->
<FileUpload
v-if="!articleForm.coverImage"
:as-dialog="false"
accept="image/*"
:max-size="2"
:multiple="false"
tip="建议尺寸800x450px支持jpg、png格式"
@success="handleCoverUploadSuccess"
/>
<!-- 封面预览 - 只在有封面图片时显示 -->
<div v-if="articleForm.coverImage" class="cover-preview">
<img :src="articleForm.coverImage" class="cover" />
<el-button type="danger" size="small" @click="removeCover">删除封面</el-button>
</div>
</el-form-item>
<!-- 文章内容 -->
<el-form-item label="文章内容" prop="content">
<RichTextComponent ref="editorRef" v-model="articleForm.content" height="500px"
placeholder="请输入文章内容..." />
</el-form-item>
<!-- 发布设置 -->
<el-form-item label="发布设置">
<el-row :gutter="20">
<el-col :span="8">
<el-checkbox v-model="articleForm.allowComment">允许评论</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox v-model="articleForm.isTop">置顶文章</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox v-model="articleForm.isRecommend">推荐文章</el-checkbox>
</el-col>
</el-row>
</el-form-item>
<!-- 操作按钮 -->
<el-form-item>
<el-button type="primary" @click="handlePublish" :loading="publishing">
{{ isEdit ? '保存修改' : '立即发布' }}
</el-button>
<el-button @click="handleSaveDraft" :loading="savingDraft">
保存草稿
</el-button>
<el-button @click="handlePreview">
预览
</el-button>
<el-button @click="handleBack">
取消
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 文章预览组件 -->
<ArticleShowView
v-model="previewVisible"
:as-dialog="true"
title="文章预览"
width="900px"
:article-data="articleForm"
:category-list="categoryList"
:show-edit-button="false"
@close="previewVisible = false"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import {
ElForm,
ElFormItem,
ElInput,
ElSelect,
ElOption,
ElButton,
ElRow,
ElCol,
ElCheckbox,
ElMessage
} from 'element-plus';
import { ArrowLeft } from '@element-plus/icons-vue';
import { RichTextComponent } from '@/components/text';
import { FileUpload } from '@/components/file';
import { ArticleShowView } from './index';
import { resourceCategoryApi, resourceTagApi, resourceApi } from '@/apis/resource';
import { Resource, ResourceCategory, Tag } from '@/types/resource';
const router = useRouter();
const route = useRoute();
const formRef = ref();
const editorRef = ref();
const publishing = ref(false);
const savingDraft = ref(false);
const previewVisible = ref(false);
// 是否编辑模式
const isEdit = ref(false);
// 数据状态
const categoryList = ref<ResourceCategory[]>([]);
const tagList = ref<Tag[]>([]);
const categoryLoading = ref(false);
const tagLoading = ref(false);
// 表单数据
const articleForm = ref<Resource>({
title: '',
content: '',
categoryID: '',
author: '',
source: '',
sourceUrl: '',
viewCount: 0,
coverImage: '',
tags: [] as Tag[],
allowComment: true,
isTop: false,
});
// 表单验证规则
const rules = {
title: [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
{ min: 5, max: 100, message: '标题长度在 5 到 100 个字符', trigger: 'blur' }
],
categoryID: [
{ required: true, message: '请选择文章分类', trigger: 'change' }
],
content: [
{ required: true, message: '请输入文章内容', trigger: 'blur' }
]
};
// 加载分类列表
async function loadCategoryList() {
try {
categoryLoading.value = true;
const result = await resourceCategoryApi.getCategoryList();
if (result.success) {
// 数组数据从 dataList 获取
categoryList.value = result.dataList || [];
} else {
ElMessage.error(result.message || '加载分类失败');
}
} catch (error) {
console.error('加载分类失败:', error);
ElMessage.error('加载分类失败');
} finally {
categoryLoading.value = false;
}
}
// 加载标签列表
async function loadTagList() {
try {
tagLoading.value = true;
const result = await resourceTagApi.getTagList();
if (result.success) {
// 数组数据从 dataList 获取
tagList.value = result.dataList || [];
} else {
ElMessage.error(result.message || '加载标签失败');
}
} catch (error) {
console.error('加载标签失败:', error);
ElMessage.error('加载标签失败');
} finally {
tagLoading.value = false;
}
}
// 返回
function handleBack() {
router.back();
}
// 发布文章
async function handlePublish() {
try {
await formRef.value?.validate();
publishing.value = true;
// TODO: 调用API发布文章
console.log('发布文章:', articleForm);
await new Promise(resolve => setTimeout(resolve, 1000));
ElMessage.success(isEdit.value ? '修改成功' : '发布成功');
router.push('/admin/manage/resource/articles');
} catch (error) {
console.error('发布失败:', error);
} finally {
publishing.value = false;
}
}
// 保存草稿
async function handleSaveDraft() {
savingDraft.value = true;
try {
// TODO: 调用API保存草稿
console.log('保存草稿:', articleForm);
await new Promise(resolve => setTimeout(resolve, 1000));
ElMessage.success('草稿已保存');
} catch (error) {
console.error('保存失败:', error);
ElMessage.error('保存失败');
} finally {
savingDraft.value = false;
}
}
// 预览
function handlePreview() {
console.log(articleForm.value.content);
if (!articleForm.value.title) {
ElMessage.warning('请先输入文章标题');
return;
}
previewVisible.value = true;
}
// 封面上传成功
function handleCoverUploadSuccess(files: any[]) {
if (files && files.length > 0) {
const file = files[0];
// 使用文件下载URL构建完整路径
import('@/config').then(config => {
articleForm.value.coverImage = config.FILE_DOWNLOAD_URL + file.id;
});
}
}
// 删除封面
function removeCover() {
articleForm.value.coverImage = '';
}
onMounted(async () => {
// 并行加载分类和标签数据
await Promise.all([
loadCategoryList(),
loadTagList()
]);
// 如果是编辑模式,加载文章数据
const id = route.query.id;
if (id) {
try {
isEdit.value = true;
const result = await resourceApi.getResourceById(id as string);
if (result.success && result.data) {
articleForm.value = result.data;
} else {
ElMessage.error(result.message || '加载文章失败');
}
} catch (error) {
console.error('加载文章失败:', error);
ElMessage.error('加载文章失败');
}
}
});
</script>
<style lang="scss" scoped>
.article-add-view {
min-height: 100vh;
background: #f5f7fa;
padding: 24px;
}
.page-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 24px;
.page-title {
font-size: 24px;
font-weight: 600;
color: #303133;
margin: 0;
}
}
.article-form {
background: white;
border-radius: 8px;
padding: 32px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.cover-uploader {
:deep(.el-upload) {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.2s;
&:hover {
border-color: #409eff;
}
}
}
.cover-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
display: flex;
align-items: center;
justify-content: center;
}
.cover {
width: 178px;
height: 178px;
display: block;
object-fit: cover;
}
.upload-tip {
font-size: 12px;
color: #909399;
margin-top: 8px;
}
.cover-preview {
margin-top: 16px;
position: relative;
.cover {
width: 200px;
height: auto;
border-radius: 4px;
display: block;
margin-bottom: 8px;
}
}
</style>