Просмотр исходного кода

cfc-web: article read-only mode for published articles

ArticleManage.vue:
- Edit: draft + never-published only
- View: published or ever-published (readonly)
- Copy: all states

ArticleEdit.vue:
- ?readonly=1 → read-only detail mode
- Published/ever-published → auto readonly
- handleCopyAsNew() for in-place copy
Xiaogang Liao 2 месяцев назад
Родитель
Сommit
76cb04c9d3
2 измененных файлов с 143 добавлено и 29 удалено
  1. 102 26
      cfc-web/src/views/admin/ArticleEdit.vue
  2. 41 3
      cfc-web/src/views/admin/ArticleManage.vue

+ 102 - 26
cfc-web/src/views/admin/ArticleEdit.vue

@@ -2,73 +2,82 @@
   <div class="article-edit">
     <el-card>
       <div slot="header">
-        <span>{{ isEdit ? '编辑文章' : '新建文章' }}</span>
+        <span>{{ readonly ? '文章详情' : (isEdit ? '编辑文章' : '新建文章') }}</span>
         <div style="float: right;">
           <el-button @click="$router.push('/article-manage')">返回列表</el-button>
-          <el-button type="primary" @click="handleSave(false)" :loading="saving">保存草稿</el-button>
-          <el-button type="success" @click="handleSave(true)" :loading="publishing">发布</el-button>
+          <template v-if="readonly">
+            <el-button type="default" @click="handleCopyAsNew">复制并新建</el-button>
+          </template>
+          <template v-else>
+            <el-button type="primary" @click="handleSave(false)" :loading="saving">保存草稿</el-button>
+            <el-button type="success" @click="handleSave(true)" :loading="publishing">发布</el-button>
+          </template>
         </div>
       </div>
 
       <el-form :model="form" label-width="120px" v-loading="loading">
         <el-form-item label="标题">
-          <el-input v-model="form.title" placeholder="请输入文章标题" maxlength="200" show-word-limit />
+          <el-input v-model="form.title" placeholder="请输入文章标题" maxlength="200" show-word-limit :disabled="readonly" />
         </el-form-item>
 
         <el-form-item label="分类">
-          <el-select v-model="form.categoryId" placeholder="选择分类" style="width: 300px;">
+          <el-select v-model="form.categoryId" placeholder="选择分类" style="width: 300px;" :disabled="readonly">
             <el-option v-for="cat in categories" :key="cat.id" :label="cat.name" :value="cat.id" />
           </el-select>
         </el-form-item>
 
         <el-form-item label="摘要">
-          <el-input v-model="form.summary" type="textarea" :rows="3" placeholder="请输入文章摘要" maxlength="500" show-word-limit />
+          <el-input v-model="form.summary" type="textarea" :rows="3" placeholder="请输入文章摘要" maxlength="500" show-word-limit :disabled="readonly" />
         </el-form-item>
 
         <el-form-item label="封面图">
-          <el-upload
-            :action="uploadActionUrl"
-            :headers="uploadHeaders"
-            :on-success="handleUploadSuccess"
-            :on-error="handleUploadError"
-            :before-upload="beforeUpload"
-            :show-file-list="false"
-            accept="image/jpeg,image/png,image/gif,image/webp"
-          >
-            <img v-if="form.coverImage" :src="form.coverImage" class="cover-preview" />
-            <el-button v-else type="primary" plain>+ 上传封面</el-button>
-          </el-upload>
-          <el-button v-if="form.coverImage" size="mini" type="text" style="margin-left: 10px;" @click="form.coverImage = ''">清除</el-button>
+          <template v-if="!readonly">
+            <el-upload
+              :action="uploadActionUrl"
+              :headers="uploadHeaders"
+              :on-success="handleUploadSuccess"
+              :on-error="handleUploadError"
+              :before-upload="beforeUpload"
+              :show-file-list="false"
+              accept="image/jpeg,image/png,image/gif,image/webp"
+            >
+              <img v-if="form.coverImage" :src="form.coverImage" class="cover-preview" />
+              <el-button v-else type="primary" plain>+ 上传封面</el-button>
+            </el-upload>
+            <el-button v-if="form.coverImage" size="mini" type="text" style="margin-left: 10px;" @click="form.coverImage = ''">清除</el-button>
+          </template>
+          <img v-else-if="form.coverImage" :src="form.coverImage" class="cover-preview" />
+          <span v-else class="text-muted">无封面图</span>
         </el-form-item>
 
         <el-form-item label="内容">
-          <el-input v-model="form.content" type="textarea" :rows="15" placeholder="请输入文章内容(支持 HTML)&#10;&#10;提示:此为纯文本编辑器,后续将升级为富文本编辑器" />
+          <el-input v-model="form.content" type="textarea" :rows="15" placeholder="请输入文章内容(支持 HTML)&#10;&#10;提示:此为纯文本编辑器,后续将升级为富文本编辑器" :disabled="readonly" />
           <div style="color: #999; font-size: 12px; margin-top: 4px;">当前为纯文本输入,后续将升级为富文本编辑器</div>
         </el-form-item>
 
         <el-form-item label="标签">
-          <el-input v-model="form.tags" placeholder="逗号分隔,如: 育儿,亲子,心理" />
+          <el-input v-model="form.tags" placeholder="逗号分隔,如: 育儿,亲子,心理" :disabled="readonly" />
         </el-form-item>
 
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="作者">
-              <el-input v-model="form.author" placeholder="文章作者" />
+              <el-input v-model="form.author" placeholder="文章作者" :disabled="readonly" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="阅读时间(分)">
-              <el-input-number v-model="form.readTime" :min="1" :max="999" />
+              <el-input-number v-model="form.readTime" :min="1" :max="999" :disabled="readonly" />
             </el-form-item>
           </el-col>
         </el-row>
 
         <el-form-item label="五维权重">
-          <dimension-weight-picker v-model="form.dimensionWeights" :show-presets="true" />
+          <dimension-weight-picker v-model="form.dimensionWeights" :show-presets="true" :readonly="readonly" />
         </el-form-item>
 
         <el-form-item label="浏览权限">
-          <el-radio-group v-model="form.visibility">
+          <el-radio-group v-model="form.visibility" :disabled="readonly">
             <el-radio label="public">公开</el-radio>
             <el-radio label="login">登录可见</el-radio>
             <el-radio label="private">私密</el-radio>
@@ -99,6 +108,8 @@ export default {
       loading: false,
       saving: false,
       publishing: false,
+      readonly: false,
+      articleWasPublished: false,
       categories: [],
       form: {
         id: null,
@@ -130,7 +141,13 @@ export default {
   created() {
     this.loadCategories()
     const id = this.$route.query.id
-    if (id) {
+    const copyId = this.$route.query.copyId
+    this.readonly = this.$route.query.readonly === '1'
+
+    if (copyId) {
+      this.isEdit = false
+      this.loadForCopy(copyId)
+    } else if (id) {
       this.isEdit = true
       this.loadDetail(id)
     }
@@ -152,6 +169,10 @@ export default {
         const res = await adminArticleDetail({ id })
         if (res.data) {
           const d = res.data
+          this.articleWasPublished = !!(d.publishedAt || d.status === 'published')
+          if (this.readonly || this.articleWasPublished) {
+            this.readonly = true
+          }
           this.form = {
             id: d.id,
             categoryId: d.categoryId,
@@ -181,6 +202,42 @@ export default {
         this.loading = false
       }
     },
+    async loadForCopy(id) {
+      this.loading = true
+      try {
+        const res = await adminArticleDetail({ id })
+        if (res.data) {
+          const d = res.data
+          this.form = {
+            id: null,
+            categoryId: d.categoryId,
+            title: (d.title || '') + '(副本)',
+            summary: d.summary || '',
+            coverImage: d.coverImage || '',
+            content: d.content || '',
+            tags: d.tags || '',
+            author: d.author || '',
+            readTime: d.readTime || 3,
+            relatedDimensions: d.relatedDimensions || [],
+            dimensionWeights: [],
+            visibility: d.visibility || 'public',
+            visibleTo: d.visibleTo || []
+          }
+          try {
+            const dwRes = await getDimensionWeights('article', d.id)
+            if (dwRes.data && dwRes.data.length) {
+              await saveDimensionWeights('article', null, dwRes.data)
+              this.form.dimensionWeights = dwRes.data
+            }
+          } catch (e) {
+          }
+        }
+      } catch (e) {
+        this.$message.error('加载文章失败')
+      } finally {
+        this.loading = false
+      }
+    },
     beforeUpload(file) {
       const isImage = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/webp'
       if (!isImage) {
@@ -261,6 +318,25 @@ export default {
         this.saving = false
         this.publishing = false
       }
+    },
+    handleCopyAsNew() {
+      const data = this.buildSubmitData()
+      delete data.id
+      data.title = (data.title || '').replace('(副本)', '') + '(副本)'
+      data.status = 'draft'
+      this.saving = true
+      adminArticleCreate(data).then(res => {
+        if (res.data && res.data.id) {
+          this.$message.success('已复制为草稿')
+          this.$router.push('/article-edit?id=' + res.data.id)
+        } else {
+          this.$message.error('复制失败')
+        }
+      }).catch(e => {
+        this.$message.error(e.message || '复制失败')
+      }).finally(() => {
+        this.saving = false
+      })
     }
   }
 }

+ 41 - 3
cfc-web/src/views/admin/ArticleManage.vue

@@ -56,9 +56,13 @@
             {{ row.publishedAt || '-' }}
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="280" fixed="right">
+        <el-table-column label="操作" width="320" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" type="primary" plain @click="$router.push('/article-edit?id=' + row.id)">编辑</el-button>
+            <!-- 草稿且从未发布过:可编辑 -->
+            <el-button v-if="row.status === 'draft' && !row.publishedAt" size="mini" type="primary" plain @click="$router.push('/article-edit?id=' + row.id)">编辑</el-button>
+            <!-- 已发布或曾发布过的草稿:只能查看 -->
+            <el-button v-else size="mini" type="info" plain @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
+            <el-button size="mini" type="default" @click="handleCopy(row)">复制</el-button>
             <el-button v-if="row.status === 'draft'" size="mini" type="success" @click="handlePublish(row)">发布</el-button>
             <el-button v-if="row.status === 'published'" size="mini" type="warning" @click="handleUnpublish(row)">下架</el-button>
             <el-button
@@ -93,7 +97,9 @@ import {
   adminArticlePublish,
   adminArticleToggleFeatured,
   adminArticleDelete,
-  adminArticleCategoriesList
+  adminArticleCategoriesList,
+  adminArticleDetail,
+  adminArticleCreate
 } from '@/api/article.js'
 
 export default {
@@ -195,6 +201,38 @@ export default {
           this.$message.error(e.message || '删除失败')
         }
       }).catch(() => {})
+    },
+    async handleCopy(row) {
+      try {
+        const res = await adminArticleDetail({ id: row.id })
+        if (!res.data) {
+          this.$message.error('文章不存在')
+          return
+        }
+        const d = res.data
+        const copyData = {
+          categoryId: d.categoryId,
+          title: d.title ? d.title + '(副本)' : '',
+          summary: d.summary || '',
+          coverImage: d.coverImage || '',
+          content: d.content || '',
+          tags: d.tags || '',
+          author: d.author || '',
+          readTime: d.readTime || 3,
+          visibility: d.visibility || 'public',
+          visibleTo: d.visibleTo || [],
+          status: 'draft'
+        }
+        const createRes = await adminArticleCreate(copyData)
+        if (createRes.data && createRes.data.id) {
+          this.$message.success('已复制为草稿')
+          this.loadList()
+        } else {
+          this.$message.error('复制失败')
+        }
+      } catch (e) {
+        this.$message.error(e.message || '复制失败')
+      }
     }
   }
 }