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

fix(frontend): resolve 5 UI bugs across CFC mini-program

FRONTEND-01: 删除未注册的商城页面 (pages/shop/index.vue)
  - 只保留 subPackages 中正确注册的 pages/shop/index/index.vue

FRONTEND-02: 缩短过渡页显示时间 5s→2s
  - action/wisdom/mind/body/index + profile + index 页面

FRONTEND-03: 智页推荐阅读'更多'导航修复
  - wisdom/index.vue goMoreArticles() 修正为目标路由

FRONTEND-04: 行页面推荐阅读缺失 (relatedDimensions JSON 解析)
  - 新增 parseRelatedDimensions() 方法处理 JSON 数组字符串/逗号分隔/数组
  - 修复 action/wisdom/mind 三页面 loadFeaturedArticles

FRONTEND-05: 行页面移除孩子管理快捷操作组件
  - 删除 UserQuickEntry 组件及其 import 和注册
Xiaogang Liao 3 месяцев назад
Родитель
Сommit
8793fb1ae8

+ 16 - 13
cfc-frontend/pages/action/index.vue

@@ -52,16 +52,6 @@
       :intimacyMap="intimacyMapForGraph"
       @memberTap="goMemberDetail" />
 
-    <!-- 登录后:用户快捷入口 -->
-    <UserQuickEntry
-      v-if="isLoggedIn"
-      :userName="userName"
-      roleName="家长"
-      :currentChildName="currentChildName"
-      :children="children"
-      @childChanged="onChildChanged"
-      @scrollTo="scrollToSection" />
-
     <!-- ===== 维度任务/活动/商品 ===== -->
     <DimensionTasks
       v-if="isLoggedIn"
@@ -127,7 +117,6 @@ import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
-import UserQuickEntry from '../../components/UserQuickEntry.vue'
 import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
@@ -140,7 +129,7 @@ import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import { getVisibleSections, getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getContactList, getVisibleFamilyMembers, getFeaturedArticles, getHealthAlerts, getMilestones } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, ContactCard, ContactImport, FamilyRelationGraph, FamilyRelationshipSection, WuxingSandbox },
+  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, ContactCard, ContactImport, FamilyRelationGraph, FamilyRelationshipSection, WuxingSandbox },
   data() {
     return {
       showTabTransition: true,
@@ -426,6 +415,20 @@ export default {
       })
     },
     goLogin: function() { uni.navigateTo({ url: '/pages/login/login' }) },
+    parseRelatedDimensions: function(val) {
+      if (!val) return []
+      if (Array.isArray(val)) return val
+      if (typeof val === 'string') {
+        try {
+          var parsed = JSON.parse(val)
+          return Array.isArray(parsed) ? parsed : []
+        } catch (e) {
+          // 逗号分隔格式 fallback: "action,mind"
+          return val.split(',').map(function(s) { return s.trim() }).filter(function(s) { return s })
+        }
+      }
+      return []
+    },
     loadFeaturedArticles: function() {
       var self = this
       getFeaturedArticles({ size: 5, dimensionCode: 'action' }).then(function(res) {
@@ -448,7 +451,7 @@ export default {
               views: item.viewCount ? String(item.viewCount) : '0',
               likes: '0',
               date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
-              relatedDimensions: item.relatedDimensions || []
+              relatedDimensions: self.parseRelatedDimensions(item.relatedDimensions)
             }
           })
         }

+ 14 - 1
cfc-frontend/pages/mind/index.vue

@@ -666,6 +666,19 @@ export default {
     },
 
     // ===== 文章 =====
+    parseRelatedDimensions: function(val) {
+      if (!val) return []
+      if (Array.isArray(val)) return val
+      if (typeof val === 'string') {
+        try {
+          var parsed = JSON.parse(val)
+          return Array.isArray(parsed) ? parsed : []
+        } catch (e) {
+          return val.split(',').map(function(s) { return s.trim() }).filter(function(s) { return s })
+        }
+      }
+      return []
+    },
     loadFeaturedArticles: function() {
       var self = this
       getFeaturedArticles({ size: 5 }).then(function(res) {
@@ -688,7 +701,7 @@ export default {
               views: item.viewCount ? String(item.viewCount) : '0',
               likes: '0',
               date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
-              relatedDimensions: item.relatedDimensions || []
+              relatedDimensions: self.parseRelatedDimensions(item.relatedDimensions)
             }
           })
         }

+ 0 - 425
cfc-frontend/pages/shop/index.vue

@@ -1,425 +0,0 @@
-<template>
-  <view class="container">
-    <!-- 购物车入口 -->
-    <view class="cart-entry" @click="goCart">
-      <text class="cart-icon">🛒</text>
-    </view>
-    <!-- Level 1 分类:2列横排网格 -->
-    <view class="cat-level1">
-      <view
-        v-for="cat in categories"
-        :key="cat.id"
-        :class="['cat1-item', currentCat1 && currentCat1.id === cat.id ? 'active' : '']"
-        @click="onCat1Select(cat)"
-      >
-        <image v-if="cat.image" class="cat1-icon" :src="cat.image" mode="aspectFit" />
-        <view v-else class="cat1-icon-placeholder">
-          <text class="cat1-icon-text">{{ cat.name.substr(0, 1) }}</text>
-        </view>
-        <text class="cat1-name">{{ cat.name }}</text>
-      </view>
-    </view>
-
-    <!-- Level 2 子分类:横向滚动chips -->
-    <scroll-view
-      v-if="currentCat1 && currentCat1.children && currentCat1.children.length"
-      scroll-x
-      enable-flex
-      show-scrollbar="false"
-      class="cat-level2-scroll"
-    >
-      <view class="cat2-chips">
-        <view
-          :class="['cat2-chip', !currentCat2 ? 'active' : '']"
-          @click="onCat2Select(null)"
-        >全部</view>
-        <view
-          v-for="child in currentCat1.children"
-          :key="child.id"
-          :class="['cat2-chip', currentCat2 && currentCat2.id === child.id ? 'active' : '']"
-          @click="onCat2Select(child)"
-        >{{ child.name }}</view>
-      </view>
-    </scroll-view>
-
-    <!-- 商品列表 -->
-    <scroll-view
-      scroll-y
-      class="product-list"
-      @scrolltolower="onLoadMore"
-      refresher-enabled
-      :refresher-triggered="refreshing"
-      @refresherrefresh="onRefresh"
-    >
-      <view v-if="loading && products.length === 0" class="loading-wrap">
-        <text class="loading-text">加载中...</text>
-      </view>
-      <view v-else-if="products.length === 0" class="empty-wrap">
-        <text class="empty-icon">📦</text>
-        <text class="empty-text">暂无商品</text>
-      </view>
-      <view v-else class="grid">
-        <view
-          v-for="item in products"
-          :key="item.id"
-          class="product-card"
-          @click="goDetail(item.id)"
-        >
-          <image
-            class="cover"
-            :src="item.image || item.coverImage || '/static/default-product.png'"
-            mode="aspectFill"
-          />
-          <view class="card-body">
-            <text class="product-name">{{ item.name }}</text>
-            <view class="price-row">
-              <text class="price">{{ formatPriceWithSymbol(item.price) }}</text>
-              <text v-if="item.memberPrice" class="member-price">会员{{ formatPriceWithSymbol(item.memberPrice) }}</text>
-            </view>
-            <text class="vendor-name">{{ item.brandName || '平台官方' }}</text>
-          </view>
-        </view>
-      </view>
-      <view v-if="loadingMore" class="loading-more">
-        <text class="loading-text">加载中...</text>
-      </view>
-      <view v-if="noMore && products.length > 0" class="no-more">
-        <text class="no-more-text">— 没有更多了 —</text>
-      </view>
-      <view v-if="!isLoggedIn" class="login-hint-bar">
-        <text class="login-hint-text">🔒 登录查看全部商品和会员价</text>
-      </view>
-      <view class="bottom-spacer"></view>
-    </scroll-view>
-
-
-  </view>
-</template>
-
-<script>
-import { goodsCategory, productList } from '@/utils/api.js'
-export default {
-  data() {
-    return {
-      categories: [],
-      currentCat1: null,
-      currentCat2: null,
-      products: [],
-      page: 1,
-      size: 20,
-      loading: false,
-      loadingMore: false,
-      refreshing: false,
-      noMore: false,
-      isLoggedIn: false
-    }
-  },
-  onLoad() {
-    this.isLoggedIn = !!uni.getStorageSync('token')
-    this.loadCategories()
-    this.loadProducts()
-  },
-  onShow() {
-    this.isLoggedIn = !!uni.getStorageSync('token')
-  },
-  methods: {
-    loadCategories() {
-      goodsCategory('0').then(res => {
-        if (res.code === 200 && res.data) {
-          this.categories = res.data || []
-        }
-      }).catch(() => {})
-    },
-    onCat1Select(cat) {
-      if (this.currentCat1 && this.currentCat1.id === cat.id) return
-      this.currentCat1 = cat
-      this.currentCat2 = null
-      this.page = 1
-      this.products = []
-      this.noMore = false
-      this.loadProducts()
-    },
-    onCat2Select(cat) {
-      this.currentCat2 = cat
-      this.page = 1
-      this.products = []
-      this.noMore = false
-      this.loadProducts()
-    },
-    loadProducts() {
-      if (this.loading) return
-      this.loading = true
-      const params = { page: this.page, size: this.size }
-      if (this.currentCat2) {
-        params.categoryId = this.currentCat2.id
-      } else if (this.currentCat1) {
-        params.categoryId = this.currentCat1.id
-      }
-      productList(params).then(res => {
-        this.loading = false
-        this.refreshing = false
-        if (res.code === 200 && res.data) {
-          const records = res.data.records || []
-          if (this.page === 1) {
-            this.products = records
-          } else {
-            this.products = this.products.concat(records)
-          }
-          const total = res.data.total || 0
-          this.noMore = this.products.length >= total
-        }
-      }).catch(() => {
-        this.loading = false
-        this.refreshing = false
-      })
-    },
-    onRefresh() {
-      this.refreshing = true
-      this.page = 1
-      this.noMore = false
-      this.loadProducts()
-    },
-    onLoadMore() {
-      if (this.noMore || this.loadingMore) return
-      this.page++
-      this.loadingMore = true
-      const params = { page: this.page, size: this.size }
-      if (this.currentCat2) {
-        params.categoryId = this.currentCat2.id
-      } else if (this.currentCat1) {
-        params.categoryId = this.currentCat1.id
-      }
-      productList(params).then(res => {
-        this.loadingMore = false
-        if (res.code === 200 && res.data) {
-          const records = res.data.records || []
-          this.products = this.products.concat(records)
-          const total = res.data.total || 0
-          this.noMore = this.products.length >= total
-        }
-      }).catch(() => {
-        this.loadingMore = false
-      })
-    },
-    goDetail(id) {
-      uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
-    },
-    goCart() {
-      uni.navigateTo({ url: '/pages/shop/cart/cart' })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  display: flex;
-  flex-direction: column;
-  height: 100vh;
-  background: #f5f5f5;
-}
-/* ==================== 分类布局 ==================== */
-/* Level 1 分类:2列横排网格 */
-.cat-level1 {
-  display: flex;
-  flex-wrap: wrap;
-  padding: 16rpx 16rpx 8rpx;
-  background: #fff;
-  border-bottom: 1rpx solid #eee;
-}
-.cat1-item {
-  width: 50%;
-  box-sizing: border-box;
-  padding: 8rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  text-align: center;
-  border-radius: 12rpx;
-  margin-bottom: 8rpx;
-}
-.cat1-item.active {
-  background: rgba(249, 115, 22, 0.08);
-}
-.cat1-icon {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  margin-bottom: 6rpx;
-  background: #f5f5f5;
-}
-.cat1-icon-placeholder {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  margin-bottom: 6rpx;
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.cat1-icon-text {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #fff;
-}
-.cat1-name {
-  font-size: 24rpx;
-  color: #333;
-  max-width: 140rpx;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-.cat1-item.active .cat1-name {
-  color: #F97316;
-  font-weight: bold;
-}
-
-/* Level 2 子分类:横向滚动chips */
-.cat-level2-scroll {
-  background: #fff;
-  white-space: nowrap;
-  padding: 12rpx 16rpx;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-.cat2-chips {
-  display: inline-flex;
-  gap: 12rpx;
-  padding: 0 4rpx;
-}
-.cat2-chip {
-  display: inline-block;
-  padding: 8rpx 24rpx;
-  font-size: 24rpx;
-  color: #666;
-  background: #f0f0f0;
-  border-radius: 30rpx;
-  flex-shrink: 0;
-}
-.cat2-chip.active {
-  background: #F97316;
-  color: #fff;
-}
-
-/* ==================== 商品列表 ==================== */
-.product-list {
-  flex: 1;
-  height: calc(100vh - 220rpx);
-}
-.login-hint-bar {
-  text-align: center;
-  padding: 20rpx;
-  background: #fef9e7;
-  border-top: 1rpx solid #f0e6c0;
-}
-.login-hint-text {
-  font-size: 24rpx;
-  color: #b8860b;
-}
-.bottom-spacer {
-  height: 120rpx;
-}
-.grid {
-  display: flex;
-  flex-wrap: wrap;
-  padding: 20rpx;
-  justify-content: space-between;
-}
-.product-card {
-  width: 49%;
-  background: #fff;
-  border-radius: 16rpx;
-  overflow: hidden;
-  margin-bottom: 20rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
-}
-.cover {
-  width: 100%;
-  height: 320rpx;
-  background: #f0f0f0;
-}
-.card-body {
-  padding: 16rpx;
-}
-.product-name {
-  display: block;
-  font-size: 28rpx;
-  color: #333;
-  lines: 2;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  display: -webkit-box;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-  margin-bottom: 12rpx;
-}
-.price-row {
-  display: flex;
-  align-items: baseline;
-  margin-bottom: 8rpx;
-}
-.price {
-  font-size: 32rpx;
-  color: #F97316;
-  font-weight: bold;
-  margin-right: 12rpx;
-}
-.member-price {
-  font-size: 22rpx;
-  color: #999;
-}
-.vendor-name {
-  font-size: 22rpx;
-  color: #999;
-}
-.loading-wrap,
-.empty-wrap {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding-top: 200rpx;
-}
-.loading-text {
-  font-size: 26rpx;
-  color: #999;
-}
-.empty-icon {
-  font-size: 100rpx;
-  margin-bottom: 30rpx;
-}
-.empty-text {
-  font-size: 28rpx;
-  color: #999;
-}
-.loading-more,
-.no-more {
-  text-align: center;
-  padding: 30rpx;
-}
-.loading-text {
-  font-size: 24rpx;
-  color: #999;
-}
-.no-more-text {
-  font-size: 24rpx;
-  color: #ccc;
-}
-.cart-entry {
-  position: fixed;
-  top: 20rpx;
-  right: 20rpx;
-  z-index: 99;
-  width: 80rpx;
-  height: 80rpx;
-  background: rgba(255,255,255,0.95);
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
-}
-.cart-icon {
-  font-size: 40rpx;
-}
-</style>

+ 14 - 1
cfc-frontend/pages/wisdom/index.vue

@@ -437,6 +437,19 @@ export default {
     goMoreProducts: function() {
       uni.navigateTo({ url: '/pages/shop/index' })
     },
+    parseRelatedDimensions: function(val) {
+      if (!val) return []
+      if (Array.isArray(val)) return val
+      if (typeof val === 'string') {
+        try {
+          var parsed = JSON.parse(val)
+          return Array.isArray(parsed) ? parsed : []
+        } catch (e) {
+          return val.split(',').map(function(s) { return s.trim() }).filter(function(s) { return s })
+        }
+      }
+      return []
+    },
     loadFeaturedArticles: function() {
       var self = this
       getFeaturedArticles({ size: 5, dimensionCode: 'wisdom' }).then(function(res) {
@@ -459,7 +472,7 @@ export default {
               views: item.viewCount ? String(item.viewCount) : '0',
               likes: '0',
               date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
-              relatedDimensions: item.relatedDimensions || []
+              relatedDimensions: self.parseRelatedDimensions(item.relatedDimensions)
             }
           })
         }

+ 6 - 1
tests/ISSUE-TRACKING.md

@@ -27,6 +27,11 @@
 | ISSUE-002 | Article publish 500 错误 | v10 | 2026/7/4 |
 | Articles list public | 公开文章列表 500 错误 | v10 | 2026/7/4 |
 | Admin article list | 管理端文章列表 500 错误 | v10 | 2026/7/4 |
+| FRONTEND-01 | 商城 '更多' 按钮无法跳转 | v11 | 2026/7/7 |
+| FRONTEND-02 | 过渡页显示时间过长 (5s→2s) | v11 | 2026/7/7 |
+| FRONTEND-03 | 智页面推荐阅读 '更多' 导航到未注册路由 | v11 | 2026/7/7 |
+| FRONTEND-04 | 行页面缺少推荐阅读 (relatedDimensions JSON 解析) | v11 | 2026/7/7 |
+| FRONTEND-05 | 行页面移除孩子管理快捷操作组件 | v11 | 2026/7/7 |
 
 ---
-*最后更新: 2026-07-06*
+*最后更新: 2026-07-07*