| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- <template>
- <div class="article-edit">
- <el-card class="article-card">
- <template #header>
- <div class="card-header">
- <h3>{{ isEdit ? '编辑文章' : '新增文章' }}</h3>
- </div>
- </template>
-
- <el-form
- ref="formRef"
- :model="form"
- :rules="rules"
- label-width="100px"
- v-loading="loading"
- >
- <el-form-item label="文章分类" prop="category_id">
- <el-select v-model="form.category_id" placeholder="请选择分类" style="width: 100%">
- <el-option
- v-for="item in categories"
- :key="item.id"
- :label="item.name"
- :value="item.id"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="文章标题" prop="title">
- <el-input v-model="form.title" placeholder="请输入文章标题" maxlength="128" show-word-limit />
- </el-form-item>
- <el-form-item label="封面图片" prop="cover">
- <div class="cover-upload-container">
- <div class="cover-preview" @click="fileManagerVisible = true">
- <img v-if="form.cover" :src="form.cover" class="cover-image" />
- <div v-else class="cover-placeholder">
- <el-icon class="cover-uploader-icon"><Plus /></el-icon>
- <div class="placeholder-text">选择封面图片</div>
- </div>
- </div>
- <div class="cover-actions">
- <el-button size="small" @click="fileManagerVisible = true">选择图片</el-button>
- <el-button v-if="form.cover" size="small" type="danger" @click="removeCover">删除封面</el-button>
- </div>
- </div>
- <div class="tip-text">建议上传尺寸: 16:9, 最佳尺寸: 1200x675</div>
-
- <FileManager
- v-model="fileManagerVisible"
- :multiple="false"
- accept="image/*"
- :max-size="2 * 1024 * 1024"
- @confirm="handleFileManagerConfirm"
- />
- </el-form-item>
- <el-form-item label="作者" prop="author">
- <el-input v-model="form.author" placeholder="请输入作者名称" maxlength="64" />
- </el-form-item>
- <el-form-item label="摘要" prop="summary">
- <el-input
- v-model="form.summary"
- type="textarea"
- placeholder="请输入文章摘要"
- maxlength="255"
- :rows="3"
- show-word-limit
- />
- </el-form-item>
- <el-form-item label="内容" prop="content">
- <div class="editor-container">
- <RichEditor
- v-model="form.content"
- :height="400"
- :uploadRequest="uploadImage"
- />
- </div>
- </el-form-item>
- <el-form-item label="置顶" prop="is_top">
- <el-radio-group v-model="form.is_top">
- <el-radio :label="1">是</el-radio>
- <el-radio :label="0">否</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-radio-group v-model="form.status">
- <el-radio :label="0">草稿</el-radio>
- <el-radio :label="1">发布</el-radio>
- <el-radio :label="2">下线</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="submitForm" :loading="submitLoading">保存</el-button>
- <el-button @click="goBack">取消</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- </div>
- </template>
- <script>
- import { defineComponent, ref, reactive, onMounted, computed } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { ElMessage } from 'element-plus'
- import { Plus } from '@element-plus/icons-vue'
- import RichEditor from '@/components/RichEditor.vue'
- import FileManager from '@/components/flysystem/FileManager.vue'
- import request from '@/utils/request'
- import { useNews } from '../../composables/useNews'
- export default defineComponent({
- name: 'ArticleEdit',
- components: {
- Plus,
- RichEditor,
- FileManager
- },
- setup() {
- const route = useRoute()
- const router = useRouter()
- const formRef = ref(null)
- const submitLoading = ref(false)
- const fileManagerVisible = ref(false)
-
- const {
- loading,
- categories,
- getCategoryList,
- getArticleDetail,
- createArticle,
- updateArticle
- } = useNews()
- // 是否是编辑模式
- const isEdit = computed(() => {
- return !!route.params.id
- })
- // 表单数据
- const form = reactive({
- id: null,
- category_id: '',
- title: '',
- cover: '',
- summary: '',
- content: '',
- author: '',
- is_top: 0,
- status: 0
- })
- // 表单验证规则
- const rules = {
- category_id: [
- { required: true, message: '请选择文章分类', trigger: 'change' }
- ],
- title: [
- { required: true, message: '请输入文章标题', trigger: 'blur' },
- { min: 1, max: 128, message: '长度在 1 到 128 个字符', trigger: 'blur' }
- ],
- summary: [
- { max: 255, message: '最多 255 个字符', trigger: 'blur' }
- ],
- content: [
- { required: true, message: '请输入文章内容', trigger: 'blur' }
- ],
- author: [
- { max: 64, message: '最多 64 个字符', trigger: 'blur' }
- ]
- }
- // 初始化
- const init = async () => {
- await getCategoryList()
- console.log('已加载分类数据:', categories.value)
-
- // 检查分类数据是否正确
- if (categories.value && categories.value.length > 0) {
- console.log('第一个分类项:', categories.value[0])
- console.log('分类项字段:', Object.keys(categories.value[0]))
- } else {
- console.error('分类数据为空或格式不正确')
- }
-
- if (isEdit.value) {
- // 编辑模式,加载文章详情
- const id = route.params.id
- const article = await getArticleDetail(id)
-
- if (article) {
- // 填充表单
- Object.keys(form).forEach(key => {
- if (article[key] !== undefined) {
- form[key] = article[key]
- }
- })
- }
- }
- }
- // 上传图片(用于富文本编辑器)
- const uploadImage = async (file) => {
- // 文件大小限制: 2MB
- const isLt2M = file.size / 1024 / 1024 < 2
- if (!isLt2M) {
- ElMessage.error('图片大小不能超过 2MB!')
- return Promise.reject('图片大小不能超过 2MB')
- }
-
- try {
- // 创建FormData对象
- const formData = new FormData()
- formData.append('file', file)
-
- // 调用上传接口
- const response = await request.post('/news/upload', formData, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- })
-
- if (response.code === 200) {
- return response.data.url
- } else {
- ElMessage.error(response.msg || '上传失败')
- return Promise.reject('上传失败')
- }
- } catch (error) {
- console.error('上传图片失败:', error)
- ElMessage.error('上传图片失败')
- return Promise.reject('上传失败')
- }
- }
- // 文件管理器确认选择
- const handleFileManagerConfirm = (result) => {
- console.log('文件管理器返回结果:', result)
-
- // 单选模式下,result是一个文件对象;多选模式下,result是数组
- if (result) {
- const file = Array.isArray(result) ? result[0] : result
- if (file && file.file_url) {
- form.cover = file.file_url
- ElMessage.success('封面图片选择成功')
- } else {
- ElMessage.error('文件信息不完整')
- }
- }
- fileManagerVisible.value = false
- }
- // 删除封面图片
- const removeCover = () => {
- form.cover = ''
- ElMessage.success('封面图片已删除')
- }
- // 提交表单
- const submitForm = async () => {
- if (!formRef.value) return
-
- await formRef.value.validate(async (valid) => {
- if (valid) {
- submitLoading.value = true
- try {
- let success = false
- console.log('提交表单数据:', {
- category_id: form.category_id,
- title: form.title,
- cover: form.cover ? '有封面图' : '无封面图',
- summary: form.summary,
- content: form.content ? '有内容' : '无内容',
- author: form.author,
- publish_time: form.publish_time,
- sort: form.sort,
- is_top: form.is_top,
- status: form.status
- })
-
- if (isEdit.value) {
- // 编辑
- success = await updateArticle(form.id, {
- category_id: form.category_id,
- title: form.title,
- cover: form.cover,
- summary: form.summary,
- content: form.content,
- author: form.author,
- is_top: form.is_top,
- status: form.status
- })
- } else {
- // 新增
- success = await createArticle({
- category_id: form.category_id,
- title: form.title,
- cover: form.cover,
- summary: form.summary,
- content: form.content,
- author: form.author,
- is_top: form.is_top,
- status: form.status
- })
- }
-
- console.log('提交结果:', success)
-
- if (success) {
- goBack()
- }
- } finally {
- submitLoading.value = false
- }
- }
- })
- }
- // 返回列表页
- const goBack = () => {
- router.push('/news?tab=article')
- }
- onMounted(() => {
- init()
- })
- return {
- isEdit,
- loading,
- categories,
- form,
- rules,
- formRef,
- submitLoading,
- uploadImage,
- fileManagerVisible,
- handleFileManagerConfirm,
- removeCover,
- submitForm,
- goBack
- }
- }
- })
- </script>
- <style scoped>
- .article-edit {
- padding: 20px;
- }
- .article-card {
- margin-bottom: 20px;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .cover-upload-container {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .cover-actions {
- display: flex;
- gap: 10px;
- }
- .cover-preview {
- cursor: pointer;
- transition: all 0.3s;
- }
- .cover-preview:hover {
- opacity: 0.8;
- }
- .cover-placeholder {
- width: 178px;
- height: 100px;
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- transition: border-color 0.3s;
- background-color: #fafafa;
- }
- .cover-placeholder:hover {
- border-color: #409eff;
- }
- .cover-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- margin-bottom: 8px;
- }
- .placeholder-text {
- font-size: 12px;
- color: #8c939d;
- }
- .cover-image {
- width: 178px;
- height: 100px;
- object-fit: cover;
- border-radius: 6px;
- cursor: pointer;
- }
- .editor-container {
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- }
- .tip-text {
- font-size: 12px;
- color: #999;
- margin-top: 5px;
- }
- </style>
|