hookehuyr

feat: 完善首页导航功能并更新项目文档

- 为首页网格导航添加跳转功能(计划书、入职相关、签单相关、家办相关、产品知识库)
- 为产品卡片按钮添加跳转(产品资料→知识库、计划书→计划书页面)
- 为"查看更多"链接添加跳转到知识库
- 工具箱暂时显示"功能开发中"提示
- 更新 CLAUDE.md 以反映当前项目架构和开发规范

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -15,40 +15,76 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `pnpm dev:swan` - Baidu mini-program development
- `pnpm dev:tt` - ByteDance mini-program development
## Project Overview
**Manulife WeApp** (臻奇智荟圈) is a wealth management WeChat mini-program built with Taro 4 + Vue 3 + NutUI.
### Business Modules
The app currently includes the following main features:
- **Product Showcase** - Hot products display and search
- **Family Office** (家办) - Family office services
- **Knowledge Base** (资料知识库) - Training materials and case studies
- **Contract Signing** (签单) - Contract signing workflow
- **User Center** - Personal profile, favorites, feedback
## Project Architecture
This is a **Taro 4 + Vue 3 + NutUI** WeChat mini-program template with built-in authentication, offline support, and weak network handling.
This is a **Taro 4 + Vue 3 + NutUI** WeChat mini-program with built-in authentication and reusable navigation components.
### Technology Stack
- **Framework**: Taro 4.x (multi-platform mini-program framework)
- **UI**: Vue 3 + NutUI 4.x (JD.com's UI library)
- **State Management**: Pinia
- **Framework**: Taro 4.1.9 + Vue 3.3.0 + Composition API
- **UI Library**: NutUI 4.3.13 (JD.com's UI library for Taro)
- **State Management**: Pinia 3.0.3 + taro-plugin-pinia
- **HTTP Client**: axios-miniprogram
- **Styling**: Less + TailwindCSS (dual design width system)
- **Styling**: Less + TailwindCSS 3.x (dual design width system)
- **Build Tool**: Webpack 5
- **Navigation**: Custom TabBar + enhanced navigation hooks
### Dual Design Width System
The project uses **two different design widths** configured in `config/index.js:16-23`:
- **NutUI components**: 375px base width
- **All other pages**: 750px base width
When working with styles, always check if you're using NutUI components or custom components to determine which width to reference.
When working with styles:
- Check if you're using NutUI components → reference 375px designs
- Custom page layouts → reference 750px designs
### Key Architectural Patterns
#### 1. Authentication Flow (Required)
#### 1. Reusable Navigation Components
**TabBar Component** (`src/components/TabBar.vue`):
- Fixed bottom navigation bar
- Auto-adapts to safe areas (notch/home indicator)
- Supports icon + text layout
- Active state highlighting
- Used across: Index, Mine, Family Office, Knowledge Base, Signing pages
**NavHeader Component** (`src/components/NavHeader.vue`):
- Custom navigation header with back button
- Transparent/background variants
- Safe area padding for notch devices
- Replaces default Taro navigation bar
**IconFont Component** (`src/components/IconFont.vue`):
- Icon font wrapper for custom icons
- Supports size and color customization
#### 2. Authentication Flow (Required)
The project has a sophisticated authentication system with automatic session management:
**Startup Flow** (`src/app.js:26-214`):
1. App saves launch path for auth callback
2. Checks network status and handles weak network scenarios
3. Attempts silent auth if not authenticated
4. On auth success, enables offline booking cache polling
4. On auth success, enables offline features
**Core Files**:
- `src/utils/authRedirect.js` - All auth logic (silent refresh, navigation, state)
- `src/utils/request.js` - HTTP client with 401 auto-refresh interceptor
- `src/pages/auth/index.vue` - Auth page (must be preserved)
- `src/pages/login/index.vue` - Login page
**How 401 Auto-Refresh Works** (`src/utils/request.js:241-276`):
1. API returns 401
......@@ -59,34 +95,6 @@ The project has a sophisticated authentication system with automatic session man
**Important**: The backend MUST provide `/srv/?a=openid_wxapp` endpoint for WeChat login.
#### 2. Offline/Weak Network Support
The app gracefully handles network issues through multiple mechanisms:
**Network Detection** (`src/utils/network.js`):
- Monitors network status changes
- Detects weak network conditions
- Provides offline caching capabilities
**Offline Booking Cache** (`src/composables/useOfflineBookingCache.js`):
- Caches booking data for offline access
- Supports reading/writing cached data
- Used when network is unavailable
**Cache Polling** (`src/composables/useOfflineBookingCachePolling.js`):
- Background polling to refresh cached data
- Reference-counted enable/disable mechanism
- Only runs when network is available
**Weak Network UI** (`src/utils/uiText.js`):
- Centralized copy management for weak network scenarios
- Provides modal options for user interaction
**Fallback Flow** (`src/utils/request.js:143-168`):
1. Request times out or fails
2. Checks if offline cache exists
3. If yes, redirects to offline booking list
4. If no, shows weak network modal
#### 3. API Layer Architecture
**API Definition Pattern** (`src/api/index.js`):
......@@ -97,7 +105,7 @@ export const yourAPI = (params) => {
```
**Request Wrapper** (`src/api/fn.js`):
- All API calls go through this wrapper
- All API calls should go through this wrapper
- Handles common error scenarios
- Provides consistent interface
......@@ -105,9 +113,9 @@ export const yourAPI = (params) => {
- `buildApiUrl(action, params)` - Constructs full API URL
- Automatically merges default parameters from `src/utils/config.js`
#### 4. Navigation System
#### 4. Enhanced Navigation System
**Enhanced Navigation Hook** (`src/hooks/useGo.js`):
**useGo Hook** (`src/hooks/useGo.js`):
```javascript
import { useGo } from '@/hooks/useGo'
const go = useGo()
......@@ -121,6 +129,49 @@ go('/page', { id: 123 }) // With query params
- Used for auth callback navigation
- Automatically managed by auth flow
### Page Structure
All pages follow this directory structure:
```
src/pages/your-page/
├── index.vue # Page component (must use <script setup>)
├── index.config.js # Page configuration (navigationBarTitleText, etc.)
└── assets/ # Page-specific assets (optional)
```
### Current Pages
**Core Pages**:
1. `pages/index/index` - Home page (product showcase, search, grid navigation)
2. `pages/auth/index` - Authentication page
3. `pages/login/index` - Login page
**Business Pages**:
4. `pages/family-office/index` - Family office services
5. `pages/knowledge-base/index` - Knowledge base (training materials, cases)
6. `pages/signing/index` - Contract signing
7. `pages/plan/index` - Business plan management
8. `pages/favorites/index` - User favorites
9. `pages/feedback/index` - User feedback
10. `pages/avatar/index` - Avatar settings
11. `pages/mine/index` - User profile
**Auxiliary Pages**:
12. `pages/onboarding/index` - New user onboarding
### Component Library
**Reusable Components**:
- `TabBar.vue` - Bottom navigation bar
- `NavHeader.vue` - Custom navigation header
- `IconFont.vue` - Icon font wrapper
**Feature Components**:
- `qrCode.vue` - QR code display
- `qrCodeSearch.vue` - QR code scanner
- `PosterBuilder/` - Poster generation
- `time-picker-data/` - Time picker data
### Path Aliases
All configured in `config/index.js:30-38`:
```javascript
......@@ -148,6 +199,12 @@ All configured in `config/index.js:30-38`:
- NutUI auto-import
- Platform-specific settings
**App Config** (`src/app.config.js`):
- Page routes registration
- Window configuration
- Tab bar configuration (optional)
- Subpackages (if needed)
## Important Implementation Details
### Session Management
......@@ -169,20 +226,176 @@ The auth system uses Promise singletons to prevent concurrent login attempts:
- Both trigger weak network fallback flow
### NutUI Auto-Import
NutUI components are auto-imported via unplugin-vue-components (`config/index.js:91-93`). No manual imports needed.
NutUI components are auto-imported via unplugin-vue-components (`config/index.js:91-93`).
**No manual imports needed** - just use components directly in templates.
### TailwindCSS Integration
- Preflight disabled for mini-program compatibility
- `rem2rpx` conversion enabled
- Content paths configured in `tailwind.config.js`
- Use Tailwind for layout, spacing, colors
- Use Less for component-specific styles, animations, pseudo-elements
### Styling Guidelines
**When to use TailwindCSS** (80% of cases):
```vue
<div class="flex items-center justify-between p-4 bg-white">
<h1 class="text-xl font-bold text-gray-900">Title</h1>
</div>
```
**When to use Less** (20% of cases):
- Component-specific styles
- Deep selectors (`:deep()`)
- Animations and transitions
- Pseudo-elements (`::before`, `::after`)
```less
<style lang="less" scoped>
.custom-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
:deep(.nut-button) {
background-color: rgba(255, 255, 255, 0.2);
}
}
</style>
```
## Optional Features
These features can be removed if not needed:
- **WeChat Pay**: `src/utils/wechatPay.js`, `src/api/wx/`
- **WeChat Pay**: `src/utils/wechatPay.js`, `src/api/wx/pay.js`
- **QR Code**: `src/components/qrCode.vue`, `src/components/qrCodeSearch.vue`
- **Poster Builder**: `src/components/PosterBuilder/`
- **Time Picker**: `src/components/time-picker-data/`
- **Offline Cache**: Entire offline booking cache system
- **Offline Cache**: Entire offline booking cache system (if not used)
## Development Workflow
### Adding New Pages
1. **Create page directory**:
```bash
src/pages/your-page/
├── index.vue
└── index.config.js
```
2. **Configure page** (`index.config.js`):
```javascript
export default {
navigationBarTitleText: 'Your Page Title',
enablePullDownRefresh: true,
backgroundColor: '#f5f5f5'
}
```
3. **Register route** in `src/app.config.js`:
```javascript
pages: [
'pages/your-page/index',
// ...
]
```
4. **Use composition API** in `index.vue`:
```vue
<script setup>
import { ref, onMounted } from 'vue'
import { useLoad, useShow } from '@tarojs/taro'
useLoad((options) => {
console.log('Page loaded with options:', options)
})
// Your component logic
</script>
```
5. **Add navigation** (optional TabBar integration):
- Import and use `TabBar` component
- Configure active state based on route
### Adding API Calls
1. **Define API** in `src/api/index.js`:
```javascript
export const getProductListAPI = (params) => {
return buildApiUrl('product_list', params)
}
```
2. **Use in page**:
```javascript
import { getProductListAPI } from '@/api'
import { fn } from '@/api/fn'
const fetchProducts = async () => {
try {
const res = await fn(getProductListAPI({ page: 1 }))
if (res.code === 1) {
products.value = res.data
}
} catch (err) {
console.error('Failed to fetch products:', err)
}
}
```
### Using Navigation
**Recommended**: Use `useGo` hook for enhanced navigation:
```javascript
import { useGo } from '@/hooks/useGo'
const go = useGo()
// Navigate to page
go('/pages/detail/index')
// Navigate with params
go('/pages/detail/index', { id: 123 })
// Go back
go.back()
```
**Alternative**: Use Taro's built-in navigation:
```javascript
import Taro from '@tarojs/taro'
Taro.navigateTo({
url: '/pages/detail/index?id=123'
})
```
### State Management with Pinia
**Create store** (`src/stores/yourStore.js`):
```javascript
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useYourStore = defineStore('yourStore', () => {
const state = ref(null)
function setState(newState) {
state.value = newState
}
return { state, setState }
})
```
**Use in component**:
```javascript
import { useYourStore } from '@/stores/yourStore'
const store = useYourStore()
store.setState('new value')
```
## Critical Files Summary
......@@ -198,29 +411,68 @@ These features can be removed if not needed:
3. **`src/stores/main.js`** - Primary state management
4. **`src/stores/router.js`** - Route state for auth callbacks
### UI/UX
### Reusable Components
1. **`src/components/TabBar.vue`** - Bottom navigation bar
2. **`src/components/NavHeader.vue`** - Custom navigation header
3. **`src/components/IconFont.vue`** - Icon wrapper
### UI/UX Utilities
1. **`src/utils/uiText.js`** - Centralized copy management
2. **`src/utils/network.js`** - Network status utilities
3. **`src/composables/`** - Reusable composition API hooks
## Development Workflow
When adding new features:
1. Define API in `src/api/index.js` using `buildApiUrl()`
2. Create page in `src/pages/your-page/`
3. Add route to `src/app.config.js`
4. Use `useGo()` hook for navigation
5. Leverage Pinia stores for state management
6. Follow dual design width rules (375px for NutUI, 750px for custom)
When modifying auth/network behavior:
- Always read both `authRedirect.js` and `request.js` together
- Test 401 refresh flow thoroughly
- Verify weak network fallback scenarios
- Check offline cache interactions
When debugging:
- Check `src/utils/config.js` for correct BASE_URL
- Verify sessionid in localStorage
- Enable verbose logging in request interceptor
- Use Taro's built-in network status monitoring
3. **`src/hooks/useGo.js`** - Enhanced navigation hook
## Debugging Tips
When debugging issues:
1. **Check environment config**:
- Verify `BASE_URL` in `src/utils/config.js`
- Check business module identifier `f` and `client_name`
2. **Verify authentication**:
- Check `sessionid` in localStorage
- Enable verbose logging in `src/utils/request.js` interceptor
- Test 401 refresh flow
3. **Network issues**:
- Use Taro's built-in network status monitoring
- Check weak network fallback scenarios
- Verify offline cache interactions
4. **Styling problems**:
- Confirm design width (375px vs 750px)
- Check if NutUI component or custom component
- Verify TailwindCSS classes are applied
5. **Navigation issues**:
- Check route is registered in `src/app.config.js`
- Verify page directory structure matches route
- Use `useGo` hook for consistent navigation
## Best Practices
### Component Development
- Use `<script setup>` syntax
- Composables for reusable logic
- Props should have type definitions
- Use `emit` for child-to-parent communication
- Prefer TailwindCSS for styling
### API Integration
- Always check `res.code === 1` for success
- Use `try/catch` for error handling
- Show loading states during requests
- Handle network errors gracefully
### Performance
- Use page lazy loading (subpackages)
- Optimize images with CDN parameters
- Avoid large data sets without pagination
- Clean up timers and listeners in `onUnmounted`
### Code Style
- Follow Vue 3 Composition API patterns
- Use descriptive variable names
- Keep functions focused and small (< 50 lines)
- Add JSDoc comments for complex functions
- Run `pnpm lint` before committing
......
......@@ -22,7 +22,12 @@
<!-- Grid Icons -->
<view class="bg-white rounded-[32rpx] shadow-sm p-[40rpx] mb-[24rpx]">
<view class="flex flex-wrap">
<view class="flex flex-col items-center w-1/3 mb-[40rpx]" v-for="(item, index) in loopData0" :key="index">
<view
class="flex flex-col items-center w-1/3 mb-[40rpx]"
v-for="(item, index) in loopData0"
:key="index"
@tap="handleGridNav(item)"
>
<view class="w-[88rpx] h-[88rpx] rounded-[24rpx] bg-blue-50 flex items-center justify-center mb-[16rpx]">
<IconFont :name="item.icon" class="text-blue-600" size="24" />
</view>
......@@ -35,7 +40,7 @@
<view class="bg-white rounded-[32rpx] shadow-sm p-[32rpx] mb-[24rpx]">
<view class="flex justify-between items-center mb-[24rpx]">
<text class="text-gray-900 text-[32rpx] font-bold">热卖产品:</text>
<view class="flex items-center text-blue-600">
<view class="flex items-center text-blue-600" @tap="go('/pages/knowledge-base/index')">
<text class="text-[26rpx] mr-[4rpx]">查看更多</text>
<IconFont name="RectRight" size="12" />
</view>
......@@ -56,9 +61,21 @@
</view>
</view>
<view class="flex justify-between gap-[24rpx]">
<nut-button plain color="#2563EB"
class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600">产品资料</nut-button>
<nut-button color="#2563EB" class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0">计划书</nut-button>
<nut-button
plain
color="#2563EB"
class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600"
@tap="go('/pages/knowledge-base/index')"
>
产品资料
</nut-button>
<nut-button
color="#2563EB"
class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0"
@tap="go('/pages/plan/index')"
>
计划书
</nut-button>
</view>
</view>
......@@ -77,9 +94,21 @@
</view>
</view>
<view class="flex justify-between gap-[24rpx]">
<nut-button plain color="#2563EB"
class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600">产品资料</nut-button>
<nut-button color="#2563EB" class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0">计划书</nut-button>
<nut-button
plain
color="#2563EB"
class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600"
@tap="go('/pages/knowledge-base/index')"
>
产品资料
</nut-button>
<nut-button
color="#2563EB"
class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0"
@tap="go('/pages/plan/index')"
>
计划书
</nut-button>
</view>
</view>
</view>
......@@ -88,7 +117,7 @@
<view class="bg-white rounded-[32rpx] shadow-sm p-[32rpx]">
<view class="flex justify-between items-center mb-[24rpx]">
<text class="text-gray-900 text-[32rpx] font-bold">本周热门资料</text>
<view class="flex items-center text-blue-600">
<view class="flex items-center text-blue-600" @tap="go('/pages/knowledge-base/index')">
<text class="text-[26rpx] mr-[4rpx]">查看更多</text>
<IconFont name="RectRight" size="12" />
</view>
......@@ -150,19 +179,33 @@ import { useGo } from '@/hooks/useGo';
import TabBar from '@/components/TabBar.vue';
import IconFont from '@/components/IconFont.vue';
// Data
// Grid navigation data with routes
const loopData0 = shallowRef([
{ icon: 'Order', lanhutext0: '计划书' },
{ icon: 'My', lanhutext0: '入职相关' },
{ icon: 'Cart', lanhutext0: '签单相关' },
{ icon: 'Home', lanhutext0: '家办相关' },
{ icon: 'Category', lanhutext0: '产品知识库' },
{ icon: 'Star', lanhutext0: '工具箱' },
{ icon: 'Order', lanhutext0: '计划书', route: '/pages/plan/index' },
{ icon: 'My', lanhutext0: '入职相关', route: '/pages/onboarding/index' },
{ icon: 'Cart', lanhutext0: '签单相关', route: '/pages/signing/index' },
{ icon: 'Home', lanhutext0: '家办相关', route: '/pages/family-office/index' },
{ icon: 'Category', lanhutext0: '产品知识库', route: '/pages/knowledge-base/index' },
{ icon: 'Star', lanhutext0: '工具箱', route: null }, // 待开发
]);
// Navigation
const go = useGo();
// Handle grid navigation click
const handleGridNav = (item) => {
if (!item.route) {
Taro.showToast({
title: '功能开发中',
icon: 'none',
duration: 2000
});
return;
}
go(item.route);
};
useShareAppMessage(() => {
return {
title: '臻奇智荟圈',
......