Jelajahi Sumber

feat(cfc-frontend): redesign article-center with search/nav/dimension-param; enhance mind/articles with search and unified detail

article-center/index: custom nav bar with search and + button, dimension URL param support, fix mind/wisdom dimension mapping. mind/articles: add search bar to nav, change goDetail to article-center/article-detail for unified detail page.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
User 2 bulan lalu
induk
melakukan
dbcbdb2726
2 mengubah file dengan 255 tambahan dan 38 penghapusan
  1. 139 9
      cfc-frontend/pages/article-center/index.vue
  2. 116 29
      cfc-frontend/pages/mind/articles.vue

+ 139 - 9
cfc-frontend/pages/article-center/index.vue

@@ -1,5 +1,28 @@
 <template>
   <view class="ac-container">
+    <!-- 自定义导航栏 -->
+    <view class="kc-nav">
+      <view class="kc-nav-left" @click="onBack">
+        <text class="kc-back-icon">←</text>
+      </view>
+      <view class="kc-nav-search" @click="focusSearch">
+        <text class="kc-search-icon">🔍</text>
+        <input
+          v-if="searchFocused || searchKeyword"
+          class="kc-search-input"
+          v-model="searchKeyword"
+          placeholder="搜索文章..."
+          confirm-type="search"
+          @confirm="onSearch"
+          @blur="onSearchBlur"
+        />
+        <text v-else class="kc-search-placeholder">搜索文章...</text>
+      </view>
+      <view class="kc-nav-right" @click="goEdit">
+        <text class="kc-add-btn">+</text>
+      </view>
+    </view>
+
     <!-- Tab 导航 -->
     <view class="ac-tabs">
       <scroll-view scroll-x enable-flex show-scrollbar="false" class="tab-scroll">
@@ -98,10 +121,10 @@ export default {
   data() {
     return {
       tabList: [
-        { code: '', name: '全部推荐', color: '#F97316' },
+        { code: '', name: '全部', color: '#F97316' },
         { code: 'body', name: '身', color: '#FF8C42' },
-        { code: 'mind', name: '智', color: '#6366F1' },
-        { code: 'wisdom', name: '心', color: '#FF6B9D' },
+        { code: 'mind', name: '心', color: '#FF6B9D' },
+        { code: 'wisdom', name: '智', color: '#6366F1' },
         { code: 'action', name: '行', color: '#10B981' },
         { code: 'wealth', name: '富', color: '#F59E0B' }
       ],
@@ -112,17 +135,31 @@ export default {
       total: 0,
       loading: false,
       loadingMore: false,
-      noMore: false
+      noMore: false,
+      searchKeyword: '',
+      searchFocused: false,
+      isLoggedIn: false
     }
   },
-  onLoad() {
+  onLoad(options) {
+    // 支持从外部传入 dimension 参数,默认选中对应 tab
+    if (options && options.dimension) {
+      this.currentTab = options.dimension
+    }
+    this.isLoggedIn = !!uni.getStorageSync('token')
     this.loadArticles()
   },
+  onShow() {
+    this.isLoggedIn = !!uni.getStorageSync('token')
+  },
   methods: {
     async loadArticles(isLoadMore) {
       if (!isLoadMore) {
         if (this.loading) return
         this.loading = true
+        this.page = 1
+        this.articles = []
+        this.noMore = false
       } else {
         if (this.loadingMore || this.noMore) return
         this.loadingMore = true
@@ -132,6 +169,9 @@ export default {
         if (this.currentTab) {
           params.dimensionCode = this.currentTab
         }
+        if (this.searchKeyword && this.searchKeyword.trim()) {
+          params.keyword = this.searchKeyword.trim()
+        }
         var res = await getArticleList(params)
         if (res.code === 200 && res.data) {
           var list = res.data.records || []
@@ -153,6 +193,8 @@ export default {
     onTabChange(code) {
       if (this.currentTab === code) return
       this.currentTab = code
+      this.searchKeyword = ''
+      this.searchFocused = false
       this.page = 1
       this.articles = []
       this.noMore = false
@@ -167,7 +209,12 @@ export default {
       uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
     },
     goEdit() {
-      uni.navigateTo({ url: '/pages/article-center/article-edit' })
+      var isLoggedIn = !!uni.getStorageSync('token')
+      if (isLoggedIn) {
+        uni.navigateTo({ url: '/pages/article-center/article-edit' })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/article-center/index') })
+      }
     },
     formatDate(dateStr) {
       if (!dateStr) return ''
@@ -177,13 +224,30 @@ export default {
       if (!str) return []
       var dimMap = {
         body: { code: 'body', color: '#FF8C42', name: '身' },
-        mind: { code: 'mind', color: '#6366F1', name: '智' },
-        wisdom: { code: 'wisdom', color: '#FF6B9D', name: '心' },
+        mind: { code: 'mind', color: '#FF6B9D', name: '心' },
+        wisdom: { code: 'wisdom', color: '#6366F1', name: '智' },
         action: { code: 'action', color: '#10B981', name: '行' },
         wealth: { code: 'wealth', color: '#F59E0B', name: '富' }
       }
       var codes = str.split(',').map(function(s) { return s.trim().toLowerCase() })
       return codes.filter(function(c) { return dimMap[c] }).map(function(c) { return dimMap[c] })
+    },
+    focusSearch() {
+      this.searchFocused = true
+    },
+    onSearch() {
+      this.page = 1
+      this.articles = []
+      this.noMore = false
+      this.loadArticles()
+    },
+    onSearchBlur() {
+      if (!this.searchKeyword) {
+        this.searchFocused = false
+      }
+    },
+    onBack() {
+      uni.navigateBack()
     }
   }
 }
@@ -195,6 +259,72 @@ export default {
   background: #f5f7fa;
   position: relative;
 }
+
+/* 自定义导航栏 */
+.kc-nav {
+  display: flex;
+  align-items: center;
+  padding: 80rpx 20rpx 16rpx;
+  background: linear-gradient(135deg, #F97316, #EA580C);
+  position: sticky;
+  top: 0;
+  z-index: 100;
+}
+.kc-nav-left {
+  width: 50rpx;
+  height: 50rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+.kc-back-icon {
+  font-size: 36rpx;
+  color: #fff;
+  font-weight: bold;
+}
+.kc-nav-search {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  background: rgba(255,255,255,0.25);
+  border-radius: 32rpx;
+  padding: 10rpx 20rpx;
+  margin: 0 16rpx;
+}
+.kc-search-icon {
+  font-size: 24rpx;
+  margin-right: 10rpx;
+  flex-shrink: 0;
+}
+.kc-search-placeholder {
+  font-size: 24rpx;
+  color: rgba(255,255,255,0.7);
+}
+.kc-search-input {
+  flex: 1;
+  font-size: 24rpx;
+  color: #fff;
+  background: transparent;
+}
+.kc-search-input::placeholder {
+  color: rgba(255,255,255,0.7);
+}
+.kc-nav-right {
+  width: 50rpx;
+  height: 50rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+.kc-add-btn {
+  font-size: 48rpx;
+  color: #fff;
+  font-weight: 300;
+  line-height: 1;
+}
+
 .ac-tabs {
   background: #fff;
   padding: 16rpx 0 12rpx;
@@ -220,7 +350,7 @@ export default {
   font-weight: bold;
 }
 .ac-list {
-  height: calc(100vh - 100rpx);
+  height: calc(100vh - 230rpx);
 }
 /* 骨架屏 */
 .ac-skeleton { padding: 20rpx 24rpx; }

+ 116 - 29
cfc-frontend/pages/mind/articles.vue

@@ -1,37 +1,33 @@
 <template>
   <view class="container">
+    <!-- 自定义导航栏 -->
+    <view class="nav-header">
+      <view class="nav-left" @click="onBack">
+        <text class="nav-back-icon">&#x2190;</text>
+      </view>
+      <view class="nav-search" @click="focusSearch">
+        <text class="nav-search-icon">&#x1F50D;</text>
+        <input
+          v-if="searchFocused || searchKeyword"
+          class="nav-search-input"
+          v-model="searchKeyword"
+          placeholder="搜索文章..."
+          confirm-type="search"
+          @confirm="onSearch"
+          @blur="onSearchBlur"
+        />
+        <text v-else class="nav-search-placeholder">搜索文章...</text>
+      </view>
+      <view class="nav-right" @click="goCreate">
+        <text class="nav-add-btn">+</text>
+      </view>
+    </view>
     <scroll-view scroll-y class="page-scroll" @scrolltolower="onLoadMore">
       <!-- 品牌头部 -->
       <PageBanner :theme="bannerTheme"
         :tagline="bannerTagline"
         :quote="bannerQuote" />
 
-      <!-- 推荐阅读 -->
-      <view class="featured-section" v-if="featuredArticles.length > 0">
-        <view class="section-header">
-          <text class="section-title">🔥 推荐阅读</text>
-          <text class="section-subtitle">精选成长好文</text>
-        </view>
-        <view class="featured-grid">
-          <view
-            v-for="article in featuredArticles"
-            :key="article.id"
-            class="featured-card"
-            @click="goDetail(article.id)"
-          >
-            <view class="featured-category" :style="{ background: article.categoryColor }">
-              <text class="featured-category-text">{{ article.category }}</text>
-            </view>
-            <text class="featured-title">{{ article.title }}</text>
-            <text class="featured-summary">{{ article.summary }}</text>
-            <view class="featured-meta">
-              <text class="featured-views">👁 {{ article.views }}</text>
-              <text class="featured-date">{{ article.date }}</text>
-            </view>
-          </view>
-        </view>
-      </view>
-
       <!-- 维度筛选 -->
       <view class="filter-section">
         <scroll-view scroll-x enable-flex show-scrollbar="false" class="dimension-scroll">
@@ -153,7 +149,9 @@ export default {
       loading: false,
       loadingMore: false,
       noMore: false,
-      isLoggedIn: false
+      isLoggedIn: false,
+      searchKeyword: '',
+      searchFocused: false
     }
   },
   computed: {
@@ -264,6 +262,9 @@ export default {
         if (this.currentDimension !== 'all') {
           params.dimensionCode = this.currentDimension
         }
+        if (this.searchKeyword && this.searchKeyword.trim()) {
+          params.keyword = this.searchKeyword.trim()
+        }
         const res = await getArticleList(params)
         if (res.code === 200 && res.data) {
           var pageData = res.data
@@ -308,10 +309,34 @@ export default {
     },
     goDetail(id) {
       if (this.isLoggedIn) {
-        uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + id })
+        uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/articles') })
+      }
+    },
+    goCreate() {
+      if (this.isLoggedIn) {
+        uni.navigateTo({ url: '/pages/article-center/article-edit' })
       } else {
         uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/articles') })
       }
+    },
+    onBack() {
+      uni.navigateBack()
+    },
+    focusSearch: function() {
+      this.searchFocused = true
+    },
+    onSearch: function() {
+      this.page = 1
+      this.articles = []
+      this.noMore = false
+      this.loadArticles()
+    },
+    onSearchBlur: function() {
+      if (!this.searchKeyword) {
+        this.searchFocused = false
+      }
     }
   }
 }
@@ -325,10 +350,72 @@ export default {
   background: #f5f7fa;
 }
 
+/* 自定义导航栏 */
+.nav-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 80rpx 24rpx 16rpx;
+  background: linear-gradient(135deg, #667eea, #764ba2);
+  position: relative;
+  z-index: 20;
+}
+.nav-left {
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.nav-back-icon {
+  font-size: 36rpx;
+  color: #fff;
+  font-weight: bold;
+}
+.nav-search {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  background: rgba(255,255,255,0.25);
+  border-radius: 32rpx;
+  padding: 10rpx 20rpx;
+  margin: 0 16rpx;
+}
+.nav-search-icon {
+  font-size: 24rpx;
+  margin-right: 10rpx;
+  flex-shrink: 0;
+}
+.nav-search-placeholder {
+  font-size: 24rpx;
+  color: rgba(255,255,255,0.7);
+}
+.nav-search-input {
+  flex: 1;
+  font-size: 24rpx;
+  color: #fff;
+  background: transparent;
+}
+.nav-search-input::placeholder {
+  color: rgba(255,255,255,0.7);
+}
+.nav-right {
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.nav-add-btn {
+  font-size: 48rpx;
+  color: #fff;
+  font-weight: 300;
+  line-height: 1;
+}
+
 /* 页面滚动容器 */
 .page-scroll {
   flex: 1;
-  height: 100vh;
 }
 
 /* 区块通用标题 */