Files
schoolNews/schoolNewsWeb/src/views/public/article/components/ArticleAdd.vue

385 lines
10 KiB
Vue
Raw Normal View History

2025-10-28 19:04:35 +08:00
<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="resource.title">
<el-input v-model="articleForm.resource.title" placeholder="请输入文章标题" maxlength="100" show-word-limit />
</el-form-item>
<!-- 分类和标签 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="文章分类" prop="resource.tagID">
<el-select v-model="articleForm.resource.tagID" placeholder="请选择分类" style="width: 100%" :loading="categoryLoading" value-key="tagID">
<el-option
v-for="category in categoryList"
:key="category.tagID || category.id"
:label="category.name"
:value="category.tagID||''"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 封面图 -->
<el-form-item label="封面图片">
<FileUpload
:as-dialog="false"
list-type="cover"
v-model:cover-url="articleForm.resource.coverImage"
accept="image/*"
:max-size="2"
module="article"
tip="建议尺寸 800x450"
@success="handleCoverUploadSuccess"
@remove="removeCover"
/>
</el-form-item>
<!-- 文章内容 -->
<el-form-item label="文章内容" prop="resource.content">
<RichTextComponent ref="editorRef" v-model="articleForm.resource.content" height="500px"
placeholder="请输入文章内容..." />
</el-form-item>
<!-- 发布设置 -->
<el-form-item label="发布设置">
<el-row :gutter="20">
<el-col :span="8">
<el-checkbox v-model="articleForm.resource.allowComment">允许评论</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox v-model="articleForm.resource.isTop">置顶文章</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox v-model="articleForm.resource.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>
<!-- 文章预览组件 -->
<ArticleShow
v-model="previewVisible"
:as-dialog="true"
title="文章预览"
width="900px"
:article-data="articleForm.resource"
:category-list="categoryList"
:show-edit-button="false"
@close="previewVisible = false"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
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 { ArticleShow } from '.';
import { resourceTagApi, resourceApi } from '@/apis/resource';
2025-11-12 19:16:50 +08:00
import { crontabApi } from '@/apis/crontab';
2025-10-28 19:04:35 +08:00
import { ResourceVO, Tag, TagType } from '@/types/resource';
defineOptions({
name: 'ArticleAdd'
});
interface Props {
articleId?: string;
showBackButton?: boolean;
backButtonText?: string;
2025-11-12 16:10:34 +08:00
initialData?: ResourceVO;
2025-11-12 19:16:50 +08:00
collectionItemId?: string;
2025-10-28 19:04:35 +08:00
}
const props = withDefaults(defineProps<Props>(), {
showBackButton: true,
backButtonText: '返回'
});
const emit = defineEmits<{
'back': [];
'publish-success': [id: string];
'save-draft-success': [];
}>();
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<Tag[]>([]);
const tagList = ref<Tag[]>([]);
const categoryLoading = ref(false);
const tagLoading = ref(false);
// 表单数据
const articleForm = ref<ResourceVO>({
resource: {},
tags: [],
});
// 表单验证规则
const rules = {
'resource.title': [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
{ min: 5, max: 100, message: '标题长度在 5 到 100 个字符', trigger: 'blur' }
],
'resource.tagID': [
{ required: true, message: '请选择文章分类', trigger: 'change' }
],
'resource.content': [
{ required: true, message: '请输入文章内容', trigger: 'blur' }
]
};
// 加载分类列表
async function loadCategoryList() {
try {
categoryLoading.value = true;
const result = await resourceTagApi.getTagsByType(TagType.ARTICLE_CATEGORY);
if (result.success) {
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;
2025-11-12 16:10:34 +08:00
const result = await resourceTagApi.getTagList({});
2025-10-28 19:04:35 +08:00
if (result.success) {
tagList.value = result.dataList || [];
} else {
ElMessage.error(result.message || '加载标签失败');
}
} catch (error) {
console.error('加载标签失败:', error);
ElMessage.error('加载标签失败');
} finally {
tagLoading.value = false;
}
}
// 返回
function handleBack() {
emit('back');
}
// 发布文章
async function handlePublish() {
try {
await formRef.value?.validate();
publishing.value = true;
2025-11-12 19:16:50 +08:00
// 如果是从数据采集转换过来的,使用转换接口
if (props.collectionItemId) {
await handleConvertFromCollection();
2025-10-28 19:04:35 +08:00
} else {
2025-11-12 19:16:50 +08:00
// 普通创建资源
2025-11-12 16:10:34 +08:00
const result = await resourceApi.createResource(articleForm.value);
if (result.success) {
2025-11-12 19:16:50 +08:00
const resourceID = result.data?.resource?.resourceID || '';
2025-11-12 16:10:34 +08:00
ElMessage.success('发布成功');
2025-11-12 19:16:50 +08:00
emit('publish-success', resourceID);
2025-11-12 16:10:34 +08:00
} else {
ElMessage.error(result.message || '发布失败');
}
2025-10-28 19:04:35 +08:00
}
} catch (error) {
console.error('发布失败:', error);
2025-11-12 19:16:50 +08:00
ElMessage.error('发布失败');
2025-10-28 19:04:35 +08:00
} finally {
publishing.value = false;
}
}
2025-11-12 19:16:50 +08:00
// 从数据采集转换为资源
async function handleConvertFromCollection() {
if (!props.collectionItemId) return;
try {
// 1. 先创建资源(使用用户编辑后的内容)
const createResult = await resourceApi.createResource(articleForm.value);
if (!createResult.success) {
ElMessage.error(createResult.message || '创建资源失败');
return;
}
const resourceID = createResult.data?.resource?.resourceID || '';
// 2. 更新采集项状态,关联resourceID
try {
await crontabApi.updateCollectionItemStatus(props.collectionItemId, 1);
console.log('采集项状态已更新, resourceID:', resourceID);
} catch (error) {
console.warn('更新采集项状态失败:', error);
// 不影响主流程,资源已经创建成功
}
ElMessage.success('转换成功');
emit('publish-success', resourceID);
} catch (error) {
console.error('转换失败:', error);
throw error;
}
}
2025-10-28 19:04:35 +08:00
// 保存草稿
async function handleSaveDraft() {
savingDraft.value = true;
try {
// TODO: 调用API保存草稿
console.log('保存草稿:', articleForm);
await new Promise(resolve => setTimeout(resolve, 1000));
ElMessage.success('草稿已保存');
emit('save-draft-success');
} catch (error) {
console.error('保存失败:', error);
ElMessage.error('保存失败');
} finally {
savingDraft.value = false;
}
}
// 预览
function handlePreview() {
if (!articleForm.value.resource.title) {
ElMessage.warning('请先输入文章标题');
return;
}
previewVisible.value = true;
}
// 封面上传成功
function handleCoverUploadSuccess(files: any[]) {
if (files && files.length > 0) {
console.log('封面上传成功:', files[0]);
}
}
// 删除封面
function removeCover() {
articleForm.value.resource.coverImage = '';
}
onMounted(async () => {
// 并行加载分类和标签数据
await Promise.all([
loadCategoryList(),
loadTagList()
]);
2025-11-12 16:10:34 +08:00
// 如果有初始数据,使用初始数据填充表单
if (props.initialData) {
articleForm.value = {
resource: { ...props.initialData.resource },
tags: [...(props.initialData.tags || [])]
};
return;
}
// 如果是编辑模式,加载文章数据
2025-10-28 19:04:35 +08:00
if (props.articleId) {
try {
isEdit.value = true;
const result = await resourceApi.getResourceById(props.articleId);
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);
}
</style>