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 | 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 |
Files:
cfc-frontend/pages/wealth/index.vue:37-45The 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.
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>.
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) || '').
Remove the .user-card, .avatar, .user-info, .nickname, .role, .card-arrow CSS rule block (lines 559-598), since the user-card DOM is gone.
Run: lsp_diagnostics on pages/wealth/index.vue — expect 0 errors.
Files:
cfc-frontend/pages/wealth/index.vueThe service-grid items currently use width: 33.33% (3 columns). Change to width: 25% (4 columns) to make icons denser.
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;
}
Run: lsp_diagnostics on pages/wealth/index.vue — expect 0 errors.
Files:
cfc-frontend/pages/wealth/index.vueThe "推广中心" menu item duplicates "创客中心" (both navigate to /pages/promotion/index). Remove the "推广中心" menu entry from the bottom menu list.
Edit pages/wealth/index.vue, remove lines 179-182:
<view class="menu-item" @click="goToPromotion">
<text>📣 推广中心</text>
<text class="arrow">›</text>
</view>
Remove the goToPromotion() method (lines 491-493):
goToPromotion() {
uni.navigateTo({ url: '/pages/promotion/index' })
},
Run: lsp_diagnostics on pages/wealth/index.vue — expect 0 errors.
Files:
cfc-frontend/pages/shop/index/index.vueAdd a search bar at the top of the mall homepage so users can search products by keyword.
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>
Add to the methods block:
goSearch() {
uni.navigateTo({ url: '/pages/shop/search/search' })
},
Add to the shop subpackage in pages.json (after the index/index entry, around line 430-435):
{
"path": "search/search",
"style": {
"navigationBarTitleText": "搜索商品"
}
},
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>
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;
}
Run: lsp_diagnostics on both pages/shop/index/index.vue and pages/shop/search/search.vue — expect 0 errors.
Files:
cfc-frontend/pages/shop/index/index.vueThe current category grid is a 2-column full-width layout. Enhance it with rounded icons and auto-arrangement for a more visual mall feel.
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.
Run: lsp_diagnostics on pages/shop/index/index.vue — expect 0 errors.
Files:
cfc-frontend/pages/shop/index/index.vueAdd a "为你推荐" (Recommended for You) product carousel section between the categories and the main product list.
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>
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(() => {})
},
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)
},
Run: lsp_diagnostics on pages/shop/index/index.vue — expect 0 errors.
[ ] 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"