2026-07-30-wealth-mall-ui-enhance.md 17 KB

Wealth Page & Mall Homepage UI Enhancement Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Clean up the wealth (富维度) page by removing duplicate entry and redundant user card, densify icon grid, and enhance the mall homepage with search bar and featured product sections.

Architecture: Two independent frontend-only changes targeting single-page Vue components in the uni-app mini-program. No backend changes needed. Task 1-3 modify pages/wealth/index.vue (main package). Task 4-6 modify pages/shop/index/index.vue (shop subpackage).

Tech Stack: uni-app (Vue 2 Options API), WeChat Mini-Program


File Structure

File Status Responsibility
cfc-frontend/pages/wealth/index.vue Modify Remove user-card section, densify to 4-column grid, remove duplicate 推广中心 menu entry
cfc-frontend/pages/shop/index/index.vue Modify Add search bar, enhance category grid, add recommended products section

Task 1: Remove user-card section from wealth page

Files:

  • Modify: cfc-frontend/pages/wealth/index.vue:37-45

The user-card (avatar, nickname, role) at the top of the wealth page is redundant because the profile page already shows this info and the TabBar already provides access. We remove the entire <view class="user-card"> block.

  • Step 1: Remove user-card template block

Edit pages/wealth/index.vue, remove lines 37-45 (the user-card view block):

Current lines 37-45:
 <!-- 用户卡片 -->
      <view class="user-card" @click="goToProfile">
        <view class="avatar">👤</view>
        <view class="user-info">
          <text class="nickname">{{ nickname || '未设置昵称' }}</text>
          <text class="role">{{ role === 'parent' ? '家长模式' : (role === 'child' ? '孩子模式' : '成长规划师模式') }}</text>
        </view>
        <text class="card-arrow">›</text>
      </view>

Remove from <!-- 用户卡片 --> through the closing </view>.

  • Step 2: Remove related data properties

In the data() function, remove the nickname property (line 217: nickname: '') since it's no longer used in the template. Also remove the nickname assignment in onShow() (line 279: this.nickname = (userInfo && userInfo.nickname) || '').

  • Step 3: Remove unused CSS

Remove the .user-card, .avatar, .user-info, .nickname, .role, .card-arrow CSS rule block (lines 559-598), since the user-card DOM is gone.

  • Step 4: Verify

Run: lsp_diagnostics on pages/wealth/index.vue — expect 0 errors.


Task 2: Make service grids 4 columns instead of 3

Files:

  • Modify: cfc-frontend/pages/wealth/index.vue

The service-grid items currently use width: 33.33% (3 columns). Change to width: 25% (4 columns) to make icons denser.

  • Step 1: Change CSS width

Edit .service-item CSS rule (line 722-728):

Change:

.service-item {
  width: 33.33%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 16rpx 0;
}

To:

.service-item {
  width: 25%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 16rpx 0;
}
  • Step 2: Verify

Run: lsp_diagnostics on pages/wealth/index.vue — expect 0 errors.


Task 3: Remove 推广中心 from bottom menu

Files:

  • Modify: cfc-frontend/pages/wealth/index.vue

The "推广中心" menu item duplicates "创客中心" (both navigate to /pages/promotion/index). Remove the "推广中心" menu entry from the bottom menu list.

  • Step 1: Remove the duplicate menu item

Edit pages/wealth/index.vue, remove lines 179-182:

        <view class="menu-item" @click="goToPromotion">
          <text>📣 推广中心</text>
          <text class="arrow">›</text>
        </view>
  • Step 2: Remove the unused goToPromotion method

Remove the goToPromotion() method (lines 491-493):

    goToPromotion() {
      uni.navigateTo({ url: '/pages/promotion/index' })
    },
  • Step 3: Verify

Run: lsp_diagnostics on pages/wealth/index.vue — expect 0 errors.


Task 4: Add search bar to mall homepage

Files:

  • Modify: cfc-frontend/pages/shop/index/index.vue

Add a search bar at the top of the mall homepage so users can search products by keyword.

  • Step 1: Add search bar template

Insert immediately after the member-banner </view> closing tag (after line 13) and before the cat-level1 section:

    <!-- 搜索栏 -->
    <view class="search-bar" @click="goSearch">
      <text class="search-icon">🔍</text>
      <text class="search-placeholder">搜索商品</text>
    </view>
  • Step 2: Add goSearch method

Add to the methods block:

    goSearch() {
      uni.navigateTo({ url: '/pages/shop/search/search' })
    },
  • Step 3: Register the search page in pages.json

Add to the shop subpackage in pages.json (after the index/index entry, around line 430-435):

        {
          "path": "search/search",
          "style": {
            "navigationBarTitleText": "搜索商品"
          }
        },
  • Step 4: Create search page

Create cfc-frontend/pages/shop/search/search.vue:

<template>
  <view class="search-container">
    <!-- 搜索输入框 -->
    <view class="search-header">
      <view class="search-input-wrap">
        <text class="search-icon">🔍</text>
        <input
          class="search-input"
          v-model="keyword"
          :focus="true"
          placeholder="输入商品名称"
          @confirm="onSearch"
          @input="onInput"
        />
        <text v-if="keyword" class="clear-btn" @click="clearKeyword">✕</text>
      </view>
      <text class="search-cancel" @click="goBack">取消</text>
    </view>

    <!-- 搜索建议 / 历史记录 -->
    <view v-if="!searched" class="suggest-wrap">
      <view class="suggest-section" v-if="history.length">
        <view class="suggest-header">
          <text class="suggest-title">搜索历史</text>
          <text class="clear-history" @click="clearHistory">清除</text>
        </view>
        <view class="history-tags">
          <text
            v-for="(item, idx) in history"
            :key="idx"
            class="history-tag"
            @click="searchKeyword(item)"
          >{{ item }}</text>
        </view>
      </view>
    </view>

    <!-- 搜索结果 -->
    <view v-else class="result-wrap">
      <view v-if="results.length === 0" class="empty-wrap">
        <text class="empty-icon">📦</text>
        <text class="empty-text">没有找到相关商品</text>
      </view>
      <view v-else class="product-grid">
        <view
          v-for="item in results"
          :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>
          </view>
        </view>
      </view>
      <view v-if="loadingMore" class="loading-more">
        <text class="loading-text">加载中...</text>
      </view>
    </view>
  </view>
</template>

<script>
import { productList } from '@/utils/api.js'
export default {
  data() {
    return {
      keyword: '',
      searched: false,
      results: [],
      page: 1,
      size: 20,
      loadingMore: false,
      noMore: false,
      history: []
    }
  },
  onLoad() {
    var historyStr = uni.getStorageSync('searchHistory')
    this.history = historyStr ? JSON.parse(historyStr) : []
  },
  methods: {
    onInput(e) {
      this.keyword = e.detail.value
    },
    onSearch() {
      if (!this.keyword.trim()) return
      this.searched = true
      this.page = 1
      this.results = []
      this.noMore = false
      this.saveHistory()
      this.loadResults()
    },
    searchKeyword(keyword) {
      this.keyword = keyword
      this.onSearch()
    },
    loadResults() {
      if (this.loadingMore) return
      this.loadingMore = true
      var params = { page: this.page, size: this.size, keyword: this.keyword.trim() }
      productList(params).then(res => {
        this.loadingMore = false
        if (res.code === 200 && res.data) {
          var records = res.data.records || []
          if (this.page === 1) {
            this.results = records
          } else {
            this.results = this.results.concat(records)
          }
          var total = res.data.total || 0
          this.noMore = this.results.length >= total
        }
      }).catch(() => {
        this.loadingMore = false
      })
    },
    saveHistory() {
      var kw = this.keyword.trim()
      if (!kw) return
      var list = this.history.filter(function(h) { return h !== kw })
      list.unshift(kw)
      if (list.length > 10) list = list.slice(0, 10)
      this.history = list
      uni.setStorageSync('searchHistory', JSON.stringify(list))
    },
    clearHistory() {
      this.history = []
      uni.removeStorageSync('searchHistory')
    },
    clearKeyword() {
      this.keyword = ''
      this.searched = false
      this.results = []
    },
    goBack() {
      uni.navigateBack()
    },
    goDetail(id) {
      uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
    }
  }
}
</script>

<style scoped>
.search-container {
  min-height: 100vh;
  background: #f5f5f5;
}
.search-header {
  display: flex;
  align-items: center;
  padding: 16rpx 20rpx;
  background: #fff;
}
.search-input-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  background: #f5f5f5;
  border-radius: 50rpx;
  padding: 0 24rpx;
  height: 72rpx;
}
.search-icon {
  font-size: 32rpx;
  margin-right: 12rpx;
  flex-shrink: 0;
}
.search-input {
  flex: 1;
  font-size: 28rpx;
  color: #333;
  height: 72rpx;
}
.clear-btn {
  font-size: 28rpx;
  color: #999;
  padding: 8rpx;
  flex-shrink: 0;
}
.search-cancel {
  font-size: 28rpx;
  color: #666;
  margin-left: 20rpx;
  flex-shrink: 0;
}
.suggest-wrap {
  padding: 20rpx;
}
.suggest-section {
  background: #fff;
  border-radius: 16rpx;
  padding: 24rpx;
  margin-bottom: 20rpx;
}
.suggest-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
}
.suggest-title {
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
}
.clear-history {
  font-size: 24rpx;
  color: #999;
}
.history-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 16rpx;
}
.history-tag {
  padding: 12rpx 24rpx;
  background: #f5f5f5;
  border-radius: 30rpx;
  font-size: 24rpx;
  color: #666;
}
.result-wrap {
  padding: 0 20rpx;
}
.product-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 20rpx 0;
}
.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;
}
.empty-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 200rpx;
}
.empty-icon {
  font-size: 100rpx;
  margin-bottom: 30rpx;
}
.empty-text {
  font-size: 28rpx;
  color: #999;
}
.loading-more {
  text-align: center;
  padding: 30rpx;
}
.loading-text {
  font-size: 24rpx;
  color: #999;
}
.bottom-spacer {
  height: 120rpx;
}
</style>
  • Step 5: Add search bar CSS to shop/index

Add search bar styles to pages/shop/index/index.vue:

/* 搜索栏 */
.search-bar {
  display: flex;
  align-items: center;
  background: #fff;
  margin: 16rpx 16rpx 0;
  padding: 16rpx 24rpx;
  border-radius: 50rpx;
  border: 1rpx solid #eee;
}
.search-icon {
  font-size: 32rpx;
  margin-right: 12rpx;
}
.search-placeholder {
  font-size: 26rpx;
  color: #bbb;
}
  • Step 6: Verify

Run: lsp_diagnostics on both pages/shop/index/index.vue and pages/shop/search/search.vue — expect 0 errors.


Task 5: Enhance category grid layout on mall homepage

Files:

  • Modify: cfc-frontend/pages/shop/index/index.vue

The current category grid is a 2-column full-width layout. Enhance it with rounded icons and auto-arrangement for a more visual mall feel.

  • Step 1: Improve category item CSS

Update the category styles in pages/shop/index/index.vue:

Replace existing .cat1-item rule:

.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;
}

With:

.cat1-item {
  width: 25%;
  box-sizing: border-box;
  padding: 16rpx 8rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  border-radius: 16rpx;
  margin-bottom: 8rpx;
}

This changes from 2 columns to 4 columns for a denser, more mall-like category grid.

  • Step 2: Verify

Run: lsp_diagnostics on pages/shop/index/index.vue — expect 0 errors.


Task 6: Add recommended products carousel to mall homepage

Files:

  • Modify: cfc-frontend/pages/shop/index/index.vue

Add a "为你推荐" (Recommended for You) product carousel section between the categories and the main product list.

  • Step 1: Add recommended section template

Insert after the cat-level2-scroll closing </scroll-view> and before the main product-list <scroll-view>:

    <!-- 为你推荐 -->
    <view class="recommend-section" v-if="recommended.length > 0">
      <view class="recommend-header">
        <text class="recommend-title">🔥 为你推荐</text>
      </view>
      <scroll-view scroll-x enable-flex show-scrollbar="false" class="recommend-scroll">
        <view
          v-for="item in recommended"
          :key="item.id"
          class="recommend-card"
          @click="goDetail(item.id)"
        >
          <image
            class="recommend-img"
            :src="item.image || item.coverImage || '/static/default-product.png'"
            mode="aspectFill"
          />
          <text class="recommend-name">{{ item.name }}</text>
          <text class="recommend-price">{{ formatPriceWithSymbol(item.price) }}</text>
        </view>
      </scroll-view>
    </view>
  • Step 2: Add data and method

Add to data():

      recommended: [],

Add to onLoad() after this.loadProducts():

    this.loadRecommended()

Add method:

    loadRecommended() {
      productList({ page: 1, size: 6, sort: 'sales' }).then(res => {
        if (res.code === 200 && res.data) {
          this.recommended = res.data.records || []
        }
      }).catch(() => {})
    },
  • Step 3: Add recommend section CSS

Add to styles:

/* 为你推荐 */
.recommend-section {
  margin: 16rpx;
  background: #fff;
  border-radius: 16rpx;
  padding: 20rpx;
}
.recommend-header {
  margin-bottom: 16rpx;
}
.recommend-title {
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
}
.recommend-scroll {
  white-space: nowrap;
  padding-bottom: 8rpx;
}
.recommend-card {
  display: inline-flex;
  flex-direction: column;
  width: 200rpx;
  margin-right: 16rpx;
  flex-shrink: 0;
}
.recommend-img {
  width: 200rpx;
  height: 200rpx;
  border-radius: 12rpx;
  background: #f0f0f0;
  margin-bottom: 8rpx;
}
.recommend-name {
  font-size: 24rpx;
  color: #333;
  lines: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-bottom: 4rpx;
}
.recommend-price {
  font-size: 26rpx;
  color: #F97316;
  font-weight: bold;
}

Also add formatPriceWithSymbol helper if not already present. Check the script for it - if missing, add:

    formatPriceWithSymbol(price) {
      if (price === null || price === undefined) return ''
      return '¥' + parseFloat(price).toFixed(2)
    },
  • Step 4: Verify

Run: lsp_diagnostics on pages/shop/index/index.vue — expect 0 errors.


Task 7: Commit

  • [ ] Step 1: Stage and commit

    git add pages/wealth/index.vue pages/shop/index/index.vue pages/shop/search/search.vue pages.json
    git commit -m "feat: wealth page cleanup and mall homepage enhancement
    
    - Remove user-card section from wealth page (redundant)
    - Densify service grid to 4 columns
    - Remove duplicate 推广中心 menu entry
    - Add search bar and search page to mall homepage
    - Enhance category grid to 4 columns
    - Add recommended products carousel"