yunzer_go/frontend/src/views/user/profile/editArticle.vue

471 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="content-section">
<div class="content-header">
<h2 class="content-title">{{ isEditMode ? "编辑文章" : "发布文章" }}</h2>
<p class="content-desc">
{{ isEditMode ? "修改您的文章内容" : "分享您的技术见解" }}
</p>
</div>
<div class="content-body">
<el-form
:model="articleForm"
label-width="80px"
:rules="formRules"
ref="formRef"
>
<el-form-item label="标题" prop="title" required>
<el-input
v-model="articleForm.title"
placeholder="请输入文章标题"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item label="分类" prop="category" required>
<el-tree-select
v-model="articleForm.category"
placeholder="请选择分类"
:data="categories"
:props="{ label: 'name', value: 'id', children: 'children' }"
:render-after-expand="false"
filterable
check-strictly
/>
</el-form-item>
<el-form-item label="作者" required>
<el-input v-model="articleForm.author" placeholder="请输入作者" />
</el-form-item>
<el-form-item label="标签">
<el-input
v-model="articleForm.tags"
placeholder="请输入标签,用逗号分隔"
/>
</el-form-item>
<el-form-item label="是否转载" required>
<el-radio-group v-model="articleForm.is_trans" size="default">
<el-radio-button label="是" value="1"></el-radio-button>
<el-radio-button label="否" value="0"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="articleForm.is_trans === '1'"
label="转载链接"
required
>
<el-input v-model="articleForm.transurl" placeholder="请输入链接" />
</el-form-item>
<el-form-item label="描述">
<el-input
type="textarea"
:rows="4"
v-model="articleForm.desc"
placeholder="请输入描述"
/>
</el-form-item>
<el-form-item label="封面图片">
<el-upload
v-model:file-list="fileList"
list-type="picture-card"
:limit="1"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleUploadRemove"
:before-upload="beforeUpload"
>
<el-icon><Plus /></el-icon>
<template #tip>
<div class="el-upload__tip">
请上传封面图片,建议尺寸 800x400px
</div>
</template>
</el-upload>
</el-form-item>
<el-form-item label="内容" prop="content" required>
<quill-editor
v-model="articleForm.content"
placeholder="请输入文章内容..."
:height="400"
/>
</el-form-item>
</el-form>
<div class="action-buttons">
<el-button @click="goBack">取消</el-button>
<el-button type="primary" @click="submitArticle" :loading="loading">
{{ isEditMode ? "保存修改" : "发布文章" }}
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { article } from "@/api/article";
import { update } from "@/api/api";
interface ArticleForm {
title: string;
category: number | undefined;
tags: string;
author: string;
content: string;
desc: string;
is_trans: string;
transurl: string;
image: string;
}
interface Category {
value: string;
label: string;
}
const router = useRouter();
const route = useRoute();
// 响应式数据
const isEditMode = ref(false);
const loading = ref(false);
const formRef = ref();
const fileList = ref<any[]>([]);
const selectedFile = ref<File | null>(null);
// 表单数据
const articleForm = ref<ArticleForm>({
title: "",
category: undefined as number | undefined,
tags: "",
desc: "",
author: "",
content: "",
is_trans: "0",
transurl: "",
image: "",
});
// 表单验证规则
const formRules = {
title: [
{ required: true, message: "请输入文章标题", trigger: "blur" },
{
min: 1,
max: 100,
message: "标题长度在 1 到 100 个字符",
trigger: "blur",
},
],
category: [
{
required: true,
message: "请选择文章分类",
trigger: "change",
validator: (rule: any, value: any, callback: any) => {
if (value === undefined || value === null || value === 0) {
callback(new Error("请选择文章分类"));
} else {
callback();
}
},
},
],
content: [
{ required: true, message: "请输入文章内容", trigger: "blur" },
{ min: 10, message: "内容不能少于10个字符", trigger: "blur" },
],
};
// 分类选项
const categories = ref<Category[]>([]);
// 获取文章分类
const getArticleCategories = async () => {
const response: any = await article.getArticleCategory();
categories.value = response.data.data;
};
// 获取用户信息
const getUserId = () => {
const userStr = localStorage.getItem("user");
if (userStr) {
try {
const user = JSON.parse(userStr);
return user.uid;
} catch (error) {
console.error("解析用户信息失败:", error);
return null;
}
}
return null;
};
// 初始化数据
const initData = async () => {
// 获取文章分类
await getArticleCategories();
// 检查是否为编辑模式
const articleId = route.query.id as string;
if (articleId) {
isEditMode.value = true;
loadArticle(articleId);
}
};
// 加载文章数据(编辑模式)
const loadArticle = async (articleId: string) => {
loading.value = true;
try {
const response = await article.getArticleDetail(articleId);
if (response.data.code === 0) {
const articleData = response.data.data.article; // 修正数据路径
articleForm.value = {
title: articleData.title || "",
category: articleData.cate || undefined,
tags: articleData.tags || "",
desc: articleData.desc || "",
author: articleData.author || "",
content: articleData.content || "",
is_trans: articleData.is_trans || "0",
transurl: articleData.transurl || "",
image: articleData.image || "",
};
// 如果有封面图片设置fileList
if (articleData.image) {
// 拼接完整的图片URL
const fullImageUrl = articleData.image.startsWith('http')
? articleData.image
: `http://localhost:8000${articleData.image}`;
fileList.value = [
{
name: "cover.jpg",
url: fullImageUrl,
uid: Date.now(),
},
];
}
} else {
ElMessage.error(response.data.msg || "加载文章失败");
}
} catch (error) {
console.error("加载文章失败:", error);
ElMessage.error("加载文章失败,请重试");
} finally {
loading.value = false;
}
};
// 上传处理方法
const handleFileChange = (file: any, fileList: any[]) => {
// 存储选择的文件,用于后续上传
selectedFile.value = file.raw;
};
const uploadCoverImage = async (): Promise<string | null> => {
if (!selectedFile.value) {
return articleForm.value.image; // 如果没有新选择文件返回原有图片URL
}
const formData = new FormData();
// 只发送文件字段,测试是否能上传成功
formData.append('file', selectedFile.value, selectedFile.value.name);
try {
const response = await update.uploadImage(formData);
// 检查响应状态
if (response.status === 200) {
const responseData = response.data;
if (responseData.code === 0) {
// 根据后端返回格式data.url
const imageUrl = responseData.data?.url;
if (imageUrl) {
ElMessage.success("封面上传成功");
return imageUrl;
} else {
console.error('无法获取图片URL:', responseData);
ElMessage.error("封面上传失败无法获取图片URL");
return null;
}
} else {
ElMessage.error(responseData.msg || "封面上传失败");
return null;
}
} else {
ElMessage.error("封面上传失败:服务器响应异常");
return null;
}
} catch (error: any) {
console.error('上传错误:', error);
const errorMessage = error.response?.data?.msg || error.message || "封面上传失败";
ElMessage.error(errorMessage);
return null;
}
};
const handleUploadRemove = (file: any, fileList: any[]) => {
selectedFile.value = null;
articleForm.value.image = "";
};
const beforeUpload = (file: any) => {
const isImage = file.type.startsWith("image/");
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isImage) {
ElMessage.error("只能上传图片文件!");
return false;
}
if (!isLt5M) {
ElMessage.error("上传图片大小不能超过 5MB!");
return false;
}
return true;
};
// 提交文章(发布或更新)
const submitArticle = async () => {
if (!formRef.value) return;
const userId = getUserId();
if (!userId) {
ElMessage.warning("请先登录");
return;
}
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return;
loading.value = true;
try {
// 先上传封面图片(如果有选择新文件)
const coverImageUrl = await uploadCoverImage();
if (coverImageUrl === null && selectedFile.value) {
// 上传失败且有选择新文件
loading.value = false;
return;
}
const articleData = {
title: articleForm.value.title,
content: articleForm.value.content,
cate: articleForm.value.category || 0,
user_id: userId,
desc: articleForm.value.desc,
author: articleForm.value.author,
is_trans: articleForm.value.is_trans,
transurl:
articleForm.value.is_trans === "1" ? articleForm.value.transurl : "",
image: coverImageUrl || "",
};
if (isEditMode.value) {
// 更新文章
const articleId = route.query.id as string;
const updateData = {
...articleData,
id: articleId,
};
const response = await article.updateArticle(articleId, updateData);
if (response.data.code === 0) {
ElMessage.success("文章更新成功");
router.push("/user/profile");
} else {
ElMessage.error(response.data.msg || "文章更新失败");
}
} else {
// 发布新文章
const response = await article.publishArticle(articleData);
if (response.data.code === 0) {
ElMessage.success("文章发布成功");
router.push("/user/profile");
} else {
ElMessage.error(response.data.msg || "文章发布失败");
}
}
} catch (error) {
console.error("操作失败:", error);
ElMessage.error("操作失败,请重试");
} finally {
loading.value = false;
}
});
};
// 返回上一页
const goBack = () => {
router.back();
};
onMounted(() => {
initData();
});
</script>
<style lang="less" scoped>
.content-section {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.content-header {
margin-bottom: 32px;
text-align: center;
.content-title {
font-size: 24px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
.content-desc {
color: #909399;
font-size: 14px;
}
}
.content-body {
background: #fff;
}
.el-form {
.el-form-item {
margin-bottom: 24px;
}
.el-textarea {
::v-deep(.el-textarea__inner) {
resize: vertical;
min-height: 200px;
}
}
}
.action-buttons {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid #e4e7ed;
}
</style>