feat(dealer): 添加客户列表功能并优化邀请流程
- 新增客户列表页面,实现客户数据获取和筛选功能 - 添加客户状态管理工具函数 - 优化邀请流程,支持绑定推荐关系 - 调整提现功能,增加调试组件 - 修复申请经销商功能中的推荐人ID逻辑
This commit is contained in:
108
src/dealer/customer/README.md
Normal file
108
src/dealer/customer/README.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 客户管理页面
|
||||
|
||||
## 功能概述
|
||||
|
||||
这是一个完整的客户管理页面,支持客户数据的展示、筛选和搜索功能。
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 数据源
|
||||
- 使用 `pageUsers` API 从 User 表读取客户数据
|
||||
- 支持按状态筛选用户(status: 0 表示正常状态)
|
||||
|
||||
### 2. 状态管理
|
||||
客户状态包括:
|
||||
- **全部** - 显示所有客户
|
||||
- **跟进中** - 正在跟进的潜在客户
|
||||
- **已签约** - 已经签约的客户
|
||||
- **已取消** - 已取消合作的客户
|
||||
|
||||
### 3. 顶部Tabs筛选
|
||||
- 支持按客户状态筛选
|
||||
- 显示每个状态的客户数量统计
|
||||
- 实时更新统计数据
|
||||
|
||||
### 4. 搜索功能
|
||||
支持多字段搜索:
|
||||
- 客户姓名(realName)
|
||||
- 昵称(nickname)
|
||||
- 用户名(username)
|
||||
- 手机号(phone)
|
||||
- 用户ID(userId)
|
||||
|
||||
### 5. 客户信息展示
|
||||
每个客户卡片显示:
|
||||
- 客户姓名和状态标签
|
||||
- 手机号码
|
||||
- 注册时间
|
||||
- 用户ID、余额、积分等统计信息
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 组件结构
|
||||
```
|
||||
CustomerManagement
|
||||
├── 搜索栏 (SearchBar)
|
||||
├── 状态筛选Tabs
|
||||
└── 客户列表
|
||||
└── 客户卡片项
|
||||
```
|
||||
|
||||
### 主要状态
|
||||
- `list`: 客户数据列表
|
||||
- `loading`: 加载状态
|
||||
- `activeTab`: 当前选中的状态Tab
|
||||
- `searchValue`: 搜索关键词
|
||||
|
||||
### 工具函数
|
||||
使用 `@/utils/customerStatus` 工具函数管理客户状态:
|
||||
- `getStatusText()`: 获取状态文本
|
||||
- `getStatusTagType()`: 获取状态标签类型
|
||||
- `getStatusOptions()`: 获取状态选项列表
|
||||
|
||||
## 使用的组件
|
||||
|
||||
### NutUI 组件
|
||||
- `Tabs` / `TabPane`: 状态筛选标签页
|
||||
- `SearchBar`: 搜索输入框
|
||||
- `Tag`: 状态标签
|
||||
- `Loading`: 加载指示器
|
||||
- `Space`: 间距布局
|
||||
|
||||
### 图标
|
||||
- `Phone`: 手机号图标
|
||||
- `User`: 用户图标
|
||||
|
||||
## 数据流
|
||||
|
||||
1. 页面初始化时调用 `fetchCustomerData()` 获取用户数据
|
||||
2. 为每个用户添加客户状态(目前使用随机状态,实际项目中应从数据库获取)
|
||||
3. 根据当前Tab和搜索条件筛选数据
|
||||
4. 渲染客户列表
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 临时实现
|
||||
- 当前使用 `getRandomStatus()` 生成随机客户状态
|
||||
- 实际项目中应该:
|
||||
1. 在数据库中添加客户状态字段
|
||||
2. 修改后端API返回真实的客户状态
|
||||
3. 删除随机状态生成函数
|
||||
|
||||
### 扩展建议
|
||||
1. 添加客户详情页面
|
||||
2. 支持客户状态的修改操作
|
||||
3. 添加客户添加/编辑功能
|
||||
4. 支持批量操作
|
||||
5. 添加导出功能
|
||||
6. 支持更多筛选条件(注册时间、地区等)
|
||||
|
||||
## 文件结构
|
||||
```
|
||||
src/dealer/customer/
|
||||
├── index.tsx # 主页面组件
|
||||
└── README.md # 说明文档
|
||||
|
||||
src/utils/
|
||||
└── customerStatus.ts # 客户状态工具函数
|
||||
```
|
||||
3
src/dealer/customer/index.config.ts
Normal file
3
src/dealer/customer/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '客户列表'
|
||||
})
|
||||
184
src/dealer/customer/index.tsx
Normal file
184
src/dealer/customer/index.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, {useState, useEffect, useCallback} from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {Loading, Tabs, TabPane, Tag} from '@nutui/nutui-react-taro'
|
||||
import {Phone} from '@nutui/icons-react-taro'
|
||||
import {pageUsers} from "@/api/system/user";
|
||||
import type {User as UserType} from "@/api/system/user/model";
|
||||
import {
|
||||
CustomerStatus,
|
||||
getStatusText,
|
||||
getStatusTagType,
|
||||
getRandomStatus,
|
||||
getStatusOptions
|
||||
} from '@/utils/customerStatus';
|
||||
|
||||
// 扩展User类型,添加客户状态
|
||||
interface CustomerUser extends UserType {
|
||||
customerStatus?: CustomerStatus;
|
||||
}
|
||||
|
||||
const CustomerManagement: React.FC = () => {
|
||||
const [list, setList] = useState<CustomerUser[]>([])
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [activeTab, setActiveTab] = useState<CustomerStatus>('all')
|
||||
const [searchValue, setSearchValue] = useState<string>('')
|
||||
|
||||
// Tab配置
|
||||
const tabList = getStatusOptions();
|
||||
|
||||
// 获取客户数据
|
||||
const fetchCustomerData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 获取用户列表,status: 0 表示正常状态
|
||||
const res = await pageUsers({ status: 0 });
|
||||
if (res?.list) {
|
||||
// 为每个用户添加随机的客户状态(实际项目中应该从后端获取真实状态)
|
||||
const customersWithStatus: CustomerUser[] = res.list.map(user => ({
|
||||
...user,
|
||||
customerStatus: getRandomStatus() // 临时使用随机状态,实际应该从数据库获取
|
||||
}));
|
||||
setList(customersWithStatus);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取客户数据失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
// 根据当前Tab和搜索条件筛选数据
|
||||
const getFilteredList = () => {
|
||||
let filteredList = list;
|
||||
|
||||
// 按状态筛选
|
||||
if (activeTab !== 'all') {
|
||||
filteredList = filteredList.filter(customer => customer.customerStatus === activeTab);
|
||||
}
|
||||
|
||||
// 按搜索关键词筛选
|
||||
if (searchValue.trim()) {
|
||||
const keyword = searchValue.trim().toLowerCase();
|
||||
filteredList = filteredList.filter(customer =>
|
||||
(customer.realName && customer.realName.toLowerCase().includes(keyword)) ||
|
||||
(customer.nickname && customer.nickname.toLowerCase().includes(keyword)) ||
|
||||
(customer.username && customer.username.toLowerCase().includes(keyword)) ||
|
||||
(customer.phone && customer.phone.includes(keyword)) ||
|
||||
(customer.userId && customer.userId.toString().includes(keyword))
|
||||
);
|
||||
}
|
||||
|
||||
return filteredList;
|
||||
};
|
||||
|
||||
// 获取各状态的统计数量
|
||||
const getStatusCounts = () => {
|
||||
const counts = {
|
||||
all: list.length,
|
||||
pending: 0,
|
||||
signed: 0,
|
||||
cancelled: 0
|
||||
};
|
||||
|
||||
list.forEach(customer => {
|
||||
if (customer.customerStatus && counts.hasOwnProperty(customer.customerStatus)) {
|
||||
counts[customer.customerStatus]++;
|
||||
}
|
||||
});
|
||||
|
||||
return counts;
|
||||
};
|
||||
|
||||
// 初始化数据
|
||||
useEffect(() => {
|
||||
fetchCustomerData();
|
||||
}, [fetchCustomerData]);
|
||||
|
||||
// 渲染客户项
|
||||
const renderCustomerItem = (customer: CustomerUser) => (
|
||||
<View key={customer.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||
<View className="flex items-center mb-3">
|
||||
<View className="flex-1">
|
||||
<View className="flex items-center justify-between mb-1">
|
||||
<Text className="font-semibold text-gray-800 mr-2">
|
||||
{customer.realName || customer.nickname || customer.username}
|
||||
</Text>
|
||||
{customer.customerStatus && (
|
||||
<Tag type={getStatusTagType(customer.customerStatus)}>
|
||||
{getStatusText(customer.customerStatus)}
|
||||
</Tag>
|
||||
)}
|
||||
</View>
|
||||
<View className="flex items-center mb-1">
|
||||
<Phone size={12} className="mr-1" />
|
||||
<Text className="text-xs text-gray-500">
|
||||
{customer.phone || '未填写'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="text-xs text-gray-500">
|
||||
注册时间:{customer.createTime}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 渲染客户列表
|
||||
const renderCustomerList = () => {
|
||||
const filteredList = getFilteredList();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View className="flex items-center justify-center py-8">
|
||||
<Loading />
|
||||
<Text className="text-gray-500 mt-2">加载中...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (filteredList.length === 0) {
|
||||
return (
|
||||
<View className="flex items-center justify-center py-8">
|
||||
<Text className="text-gray-500">暂无客户数据</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="p-4">
|
||||
{filteredList.map(renderCustomerItem)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="min-h-screen bg-gray-50">
|
||||
{/* 顶部Tabs */}
|
||||
<View className="bg-white">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(value) => setActiveTab(value as CustomerStatus)}
|
||||
>
|
||||
{tabList.map(tab => {
|
||||
const counts = getStatusCounts();
|
||||
const count = counts[tab.value as keyof typeof counts] || 0;
|
||||
return (
|
||||
<TabPane
|
||||
key={tab.value}
|
||||
title={`${tab.label}${count > 0 ? `(${count})` : ''}`}
|
||||
value={tab.value}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</View>
|
||||
|
||||
{/* 客户列表 */}
|
||||
{renderCustomerList()}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerManagement;
|
||||
Reference in New Issue
Block a user