Преглед изворни кода

feat(article-center): add article edit page with publish and draft

Sisyphus пре 4 месеци
родитељ
комит
f2919c3725
1 измењених фајлова са 359 додато и 0 уклоњено
  1. 359 0
      cfc-frontend/pages/article-center/article-edit.vue

+ 359 - 0
cfc-frontend/pages/article-center/article-edit.vue

@@ -0,0 +1,359 @@
+<template>
+  <view class="edit-container">
+    <!-- 自定义导航 -->
+    <view class="edit-header">
+      <view class="header-left" @click="onBack">
+        <text class="back-icon">←</text>
+        <text class="back-text">返回</text>
+      </view>
+      <text class="header-title">写成长记录</text>
+      <view class="header-right" @click="goMyPosts">
+        <text class="history-text">历史</text>
+      </view>
+    </view>
+
+    <scroll-view scroll-y class="edit-form">
+      <!-- 封面图 -->
+      <view class="form-cover" @click="chooseCover">
+        <image v-if="coverImage" :src="coverImage" mode="aspectFill" class="cover-preview" />
+        <view v-else class="cover-placeholder">
+          <text class="cover-icon">📷</text>
+          <text class="cover-hint">添加封面</text>
+        </view>
+        <view v-if="coverImage" class="cover-change" @click.stop="chooseCover">
+          <text class="change-text">更换</text>
+        </view>
+      </view>
+
+      <!-- 标题 -->
+      <view class="form-group">
+        <input
+          class="form-input title-input"
+          v-model="title"
+          placeholder="给这篇记录取个标题"
+          maxlength="50"
+        />
+        <text class="char-count">{{ title.length }}/50</text>
+      </view>
+
+      <!-- 维度选择 -->
+      <view class="form-group">
+        <text class="form-label">关联维度(选填)</text>
+        <view class="dimension-chips">
+          <view
+            v-for="dim in dimensionList"
+            :key="dim.code"
+            :class="['dim-chip', selectedDimensions.indexOf(dim.code) >= 0 ? 'active' : '']"
+            :style="selectedDimensions.indexOf(dim.code) >= 0 ? { background: dim.color, color: '#fff', borderColor: dim.color } : {}"
+            @click="toggleDimension(dim.code)"
+          >
+            {{ dim.name }}
+          </view>
+        </view>
+      </view>
+
+      <!-- 正文 -->
+      <view class="form-group">
+        <textarea
+          class="form-textarea"
+          v-model="content"
+          placeholder="记录孩子的成长点滴..."
+          maxlength="2000"
+        />
+        <text class="char-count">{{ content.length }}/2000</text>
+      </view>
+
+      <!-- 可见范围 -->
+      <view class="form-group">
+        <text class="form-label">可见范围</text>
+        <view class="visibility-options">
+          <view
+            :class="['vis-option', visibility === 'private' ? 'active' : '']"
+            @click="visibility = 'private'"
+          >
+            <text class="vis-icon">🏠</text>
+            <text class="vis-text">仅家庭</text>
+            <text class="vis-desc">仅家庭成员可见</text>
+          </view>
+          <view
+            :class="['vis-option', visibility === 'public' ? 'active' : '']"
+            @click="visibility = 'public'"
+          >
+            <text class="vis-icon">🌍</text>
+            <text class="vis-text">公开发布</text>
+            <text class="vis-desc">所有人可见,需审核</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 发布按钮 -->
+      <button class="publish-btn" :disabled="!canPublish" @click="onPublish">
+        {{ publishing ? '发布中...' : '发布' }}
+      </button>
+      <view style="height: 60rpx;"></view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+import { publishArticle } from '@/utils/api.js'
+import config from '@/config.js'
+
+export default {
+  data() {
+    return {
+      title: '',
+      coverImage: '',
+      content: '',
+      visibility: 'private',
+      selectedDimensions: [],
+      publishing: false,
+      dimensionList: [
+        { code: 'body', name: '身', color: '#FF8C42' },
+        { code: 'mind', name: '智', color: '#6366F1' },
+        { code: 'wisdom', name: '心', color: '#FF6B9D' },
+        { code: 'action', name: '行', color: '#10B981' },
+        { code: 'wealth', name: '富', color: '#F59E0B' }
+      ]
+    }
+  },
+  computed: {
+    canPublish: function() {
+      return this.title.trim().length > 0 && this.content.trim().length > 0 && !this.publishing
+    }
+  },
+  methods: {
+    chooseCover() {
+      var self = this
+      uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: function(res) {
+          var tempPath = res.tempFilePaths[0]
+          // 上传图片
+          var token = uni.getStorageSync('token')
+          uni.uploadFile({
+            url: (config.default && config.default.API_BASE_URL) || config.API_BASE_URL || '' + '/api/upload/image',
+            filePath: tempPath,
+            name: 'file',
+            header: {
+              'Authorization': token ? 'Bearer ' + token : ''
+            },
+            success: function(uploadRes) {
+              try {
+                var data = JSON.parse(uploadRes.data)
+                if (data && data.code === 200) {
+                  self.coverImage = data.data
+                } else {
+                  uni.showToast({ title: '上传失败', icon: 'none' })
+                }
+              } catch (e) {
+                uni.showToast({ title: '上传失败', icon: 'none' })
+              }
+            },
+            fail: function() {
+              uni.showToast({ title: '上传失败', icon: 'none' })
+            }
+          })
+        }
+      })
+    },
+    toggleDimension(code) {
+      var idx = this.selectedDimensions.indexOf(code)
+      if (idx >= 0) {
+        this.selectedDimensions.splice(idx, 1)
+      } else {
+        this.selectedDimensions.push(code)
+      }
+    },
+    onBack() {
+      if (this.title || this.content) {
+        var self = this
+        uni.showModal({
+          title: '提示',
+          content: '是否保存草稿?',
+          success: function(res) {
+            if (res.confirm) {
+              uni.setStorageSync('article_draft', {
+                title: self.title,
+                coverImage: self.coverImage,
+                content: self.content,
+                visibility: self.visibility,
+                selectedDimensions: self.selectedDimensions
+              })
+            }
+            uni.navigateBack()
+          }
+        })
+      } else {
+        uni.navigateBack()
+      }
+    },
+    goMyPosts() {
+      uni.navigateTo({ url: '/pages/article-center/my-posts' })
+    },
+    async onPublish() {
+      if (!this.canPublish) return
+      this.publishing = true
+      try {
+        var res = await publishArticle({
+          title: this.title.trim(),
+          content: this.content.trim(),
+          coverImage: this.coverImage,
+          visibility: this.visibility,
+          relatedDimensions: this.selectedDimensions.join(',')
+        })
+        if (res.code === 200) {
+          var msg = this.visibility === 'public'
+            ? '发布成功,等待审核'
+            : '发布成功,已对家庭可见'
+          uni.showToast({ title: msg, icon: 'success', duration: 2000 })
+          uni.removeStorageSync('article_draft')
+          var self = this
+          setTimeout(function() {
+            uni.navigateBack()
+          }, 1500)
+        } else {
+          uni.showToast({ title: res.message || '发布失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '发布失败', icon: 'none' })
+      } finally {
+        this.publishing = false
+      }
+    }
+  },
+  onLoad() {
+    // 加载草稿
+    try {
+      var draft = uni.getStorageSync('article_draft')
+      if (draft) {
+        this.title = draft.title || ''
+        this.coverImage = draft.coverImage || ''
+        this.content = draft.content || ''
+        this.visibility = draft.visibility || 'private'
+        this.selectedDimensions = draft.selectedDimensions || []
+      }
+    } catch (e) {}
+  }
+}
+</script>
+
+<style scoped>
+.edit-container { min-height: 100vh; background: #f5f7fa; }
+.edit-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 80rpx 30rpx 20rpx;
+  background: #fff;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+}
+.header-left { display: flex; align-items: center; }
+.back-icon { font-size: 36rpx; margin-right: 8rpx; }
+.back-text { font-size: 26rpx; color: #333; }
+.header-title { font-size: 32rpx; font-weight: bold; color: #333; }
+.header-right { }
+.history-text { font-size: 26rpx; color: #5B9BD5; }
+.edit-form { padding: 20rpx 30rpx; }
+/* 封面 */
+.form-cover {
+  width: 100%;
+  height: 300rpx;
+  background: #eee;
+  border-radius: 16rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  position: relative;
+}
+.cover-preview { width: 100%; height: 100%; }
+.cover-placeholder {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+}
+.cover-icon { font-size: 60rpx; margin-bottom: 10rpx; }
+.cover-hint { font-size: 24rpx; color: #999; }
+.cover-change {
+  position: absolute;
+  right: 16rpx;
+  bottom: 16rpx;
+  background: rgba(0,0,0,0.5);
+  color: #fff;
+  padding: 6rpx 20rpx;
+  border-radius: 20rpx;
+  font-size: 22rpx;
+}
+/* 表单组 */
+.form-group {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+}
+.form-input {
+  width: 100%;
+  font-size: 30rpx;
+  color: #333;
+  border: none;
+  outline: none;
+}
+.title-input { font-weight: bold; }
+.char-count { display: block; text-align: right; font-size: 20rpx; color: #bbb; margin-top: 8rpx; }
+.form-label { font-size: 24rpx; color: #666; display: block; margin-bottom: 16rpx; }
+/* 维度 */
+.dimension-chips { display: flex; flex-wrap: wrap; gap: 16rpx; }
+.dim-chip {
+  padding: 10rpx 28rpx;
+  border-radius: 30rpx;
+  font-size: 24rpx;
+  border: 2rpx solid #ddd;
+  color: #666;
+}
+.dim-chip.active { color: #fff; border-color: transparent; }
+/* 文本框 */
+.form-textarea {
+  width: 100%;
+  height: 300rpx;
+  font-size: 28rpx;
+  color: #333;
+  line-height: 1.6;
+  border: none;
+  outline: none;
+  resize: none;
+}
+/* 可见范围 */
+.visibility-options { display: flex; gap: 20rpx; }
+.vis-option {
+  flex: 1;
+  padding: 24rpx;
+  border-radius: 12rpx;
+  border: 2rpx solid #eee;
+  text-align: center;
+}
+.vis-option.active { border-color: #5B9BD5; background: #E8F4FD; }
+.vis-icon { font-size: 40rpx; display: block; margin-bottom: 8rpx; }
+.vis-text { font-size: 26rpx; font-weight: bold; color: #333; display: block; }
+.vis-desc { font-size: 20rpx; color: #999; display: block; margin-top: 4rpx; }
+/* 发布按钮 */
+.publish-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  background: linear-gradient(135deg, #F97316, #EA580C);
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+  border-radius: 44rpx;
+  text-align: center;
+  border: none;
+  margin-top: 20rpx;
+}
+.publish-btn[disabled] { opacity: 0.4; }
+.publish-btn::after { border: none; }
+</style>