diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json
index 09aee83..f15b4ca 100644
--- a/.workbuddy/expert-history.json
+++ b/.workbuddy/expert-history.json
@@ -11,7 +11,18 @@
"usedAt": 1775709039214,
"industryId": "all"
}
+ ],
+ "2d8018ea3c7f4b92a608c23c2ee6211a": [
+ {
+ "expertId": "SeniorDeveloper",
+ "name": "Will",
+ "profession": "高级开发工程师",
+ "avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
+ "promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
+ "usedAt": 1776102350082,
+ "industryId": "all"
+ }
]
},
- "lastUpdated": 1775713542885
+ "lastUpdated": 1776103090047
}
\ No newline at end of file
diff --git a/.workbuddy/memory/2026-04-14.md b/.workbuddy/memory/2026-04-14.md
new file mode 100644
index 0000000..c2c5bed
--- /dev/null
+++ b/.workbuddy/memory/2026-04-14.md
@@ -0,0 +1,42 @@
+# 2026-04-14 日志
+
+## 项目:客资管理系统(Customer Lead Management System)
+
+### 需求背景
+用户委托开发一个客资管理系统,具备以下功能:
+1. **客资派单** - 管理员直接派单给业务员
+2. **全民推荐** - 任何人可推荐客户赚取推荐费
+3. **推荐人报备** - 注册用户可报备客户
+4. **实时跟进** - 实时查看跟进情况和成交状态
+5. **多管理员** - 支持多管理员设置
+6. **数据统计导出** - 统计报表功能
+
+### 涉及项目
+- Java后端: `/Users/gxwebsoft/JAVA/mp-java`
+- Vue后台管理: `/Users/gxwebsoft/VUE/mp-vue`
+- 小程序端: `/Users/gxwebsoft/VUE/template-10582`
+
+### 状态
+- [x] 探索代码结构
+- [x] 设计数据库schema
+- [x] 设计API接口
+- [x] 实施开发
+
+### 实施成果
+
+#### Java后端 (`/Users/gxwebsoft/JAVA/mp-java`)
+- SQL脚本: `docs/sql/customer_lead_system.sql`
+- Entity: CustomerLeadEntity, LeadDispatch, LeadFollowLog, LeadReferral, LeadStatistics, UserRoleExtend
+- Mapper: CustomerLeadMapper, LeadDispatchMapper, LeadFollowLogMapper, LeadReferralMapper
+- Service: CustomerLeadService, LeadReferralService
+- Controller: CustomerLeadController, LeadReferralController
+- 总结文档: `docs/ai/customer-lead-system-summary.md`
+
+#### Vue后台 (`/Users/gxwebsoft/VUE/mp-vue`)
+- API: `api/cms/customerLead/index.ts`, `model.ts`
+- 页面: `views/cms/customerLead/index.vue`
+
+#### 小程序端 (`/Users/gxwebsoft/VUE/template-10582`)
+- API: `api/shop/referral.ts`
+- 页面: `dealer/referral/index.tsx`
+- 首页入口: 在分销商首页添加「推荐客户」功能
diff --git a/src/api/shop/referral.ts b/src/api/shop/referral.ts
new file mode 100644
index 0000000..7a3a1d8
--- /dev/null
+++ b/src/api/shop/referral.ts
@@ -0,0 +1,135 @@
+/**
+ * 客资推荐人 API
+ * 小程序端调用
+ */
+import request from '@/utils/request'
+
+/**
+ * 推荐人报备参数
+ */
+export interface ReferralParam {
+ customerName: string
+ customerPhone: string
+ customerCompany?: string
+ requirement?: string
+ appointmentTime?: string
+ remarks?: string
+}
+
+/**
+ * 推荐人报备结果
+ */
+export interface ReferralResult {
+ referralId: number
+ referralCode: string
+ customerName: string
+ customerPhone: string
+ referralFee?: number
+ referralStatus: number
+ referralStatusText: string
+ createTime: string
+}
+
+/**
+ * 推荐人统计
+ */
+export interface ReferralStats {
+ totalCount: number
+ pendingCount: number
+ validCount: number
+ settledCount: number
+ pendingAmount: number
+}
+
+/**
+ * 推荐人记录
+ */
+export interface ReferralRecord {
+ referralId: number
+ referredLeadId: number
+ customerName: string
+ customerPhone: string
+ referralFee: number
+ referralStatus: number
+ referralStatusText: string
+ leadStatus?: number
+ leadStatusText?: string
+ dealAmount?: number
+ createTime: string
+ settlementTime?: string
+}
+
+/**
+ * 注册用户报备客户
+ */
+export function addReferral(data: ReferralParam) {
+ return request.post<{ code: number; message: string; data: ReferralResult }>(
+ '/lead/referral/user',
+ data
+ )
+}
+
+/**
+ * 获取推荐人的推荐记录
+ */
+export function getReferralList(params: { pageNum?: number; pageSize?: number }) {
+ return request.get<{ code: number; message: string; data: { list: ReferralRecord[]; total: number } }>(
+ '/lead/referral/page',
+ { params }
+ )
+}
+
+/**
+ * 获取推荐人统计
+ */
+export function getReferralStats(userId: number) {
+ return request.get<{ code: number; message: string; data: ReferralStats }>(
+ `/lead/referral/stats/${userId}`
+ )
+}
+
+/**
+ * 生成推荐码
+ */
+export function generateReferralCode() {
+ return request.get<{ code: number; message: string; data: string }>(
+ '/lead/referral/generateCode'
+ )
+}
+
+/**
+ * 获取当前用户的推荐码
+ */
+export function getMyReferralCode() {
+ return request.get<{ code: number; message: string; data: { referralCode: string } }>(
+ '/lead/referral/my/code'
+ )
+}
+
+/**
+ * 确认推荐有效(管理员)
+ */
+export function confirmReferral(referralId: number) {
+ return request.put<{ code: number; message: string }>(
+ `/lead/referral/confirm/${referralId}`
+ )
+}
+
+/**
+ * 作废推荐(管理员)
+ */
+export function invalidateReferral(referralId: number, reason?: string) {
+ return request.put<{ code: number; message: string }>(
+ `/lead/referral/invalidate/${referralId}`,
+ { reason }
+ )
+}
+
+/**
+ * 结算推荐费(管理员)
+ */
+export function settleReferral(referralId: number) {
+ return request.put<{ code: number; message: string }>(
+ `/lead/referral/settle/${referralId}`
+ )
+}
diff --git a/src/dealer/index.tsx b/src/dealer/index.tsx
index 97f7a95..c4e4c3b 100644
--- a/src/dealer/index.tsx
+++ b/src/dealer/index.tsx
@@ -7,7 +7,8 @@ import {
QrCode,
ArrowRight,
Purse,
- People
+ People,
+ Service
} from '@nutui/icons-react-taro'
import {useDealerUser} from '@/hooks/useDealerUser'
import {useThemeStyles} from '@/hooks/useTheme'
@@ -250,6 +251,14 @@ const DealerIndex: React.FC = () => {
+ navigateToPage('/dealer/referral/index')}>
+
+
+
+
+
+
+
navigateToPage('/dealer/qrcode/index')}>
diff --git a/src/dealer/referral/index.config.ts b/src/dealer/referral/index.config.ts
new file mode 100644
index 0000000..df28c7e
--- /dev/null
+++ b/src/dealer/referral/index.config.ts
@@ -0,0 +1,3 @@
+{
+ "navigationBarTitleText": "推荐客户赚佣金"
+}
diff --git a/src/dealer/referral/index.scss b/src/dealer/referral/index.scss
new file mode 100644
index 0000000..076818b
--- /dev/null
+++ b/src/dealer/referral/index.scss
@@ -0,0 +1,132 @@
+.referral-page {
+ min-height: 100vh;
+ background-color: #f5f5f7;
+}
+
+.stats-section {
+ padding: 24px 16px;
+ color: #fff;
+
+ .stats-title {
+ margin-bottom: 16px;
+ text-align: center;
+ }
+
+ .stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 8px;
+
+ .stat-item {
+ text-align: center;
+
+ .stat-value {
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+
+ .stat-label {
+ font-size: 12px;
+ }
+ }
+ }
+}
+
+.form-section {
+ padding: 0 16px 16px;
+
+ .section-title {
+ margin-bottom: 12px;
+ }
+
+ .form-card {
+ background: #fff;
+ border-radius: 12px;
+ padding: 8px 0;
+
+ .nut-input-text {
+ flex: 1;
+ text-align: right;
+ }
+
+ .submit-btn {
+ padding: 16px;
+
+ button {
+ width: 100%;
+ border-radius: 8px;
+ height: 44px;
+ line-height: 44px;
+ }
+ }
+
+ .tips {
+ padding: 0 16px 16px;
+ text-align: center;
+ }
+ }
+}
+
+.records-section {
+ padding: 0 16px 16px;
+
+ .section-title {
+ margin-bottom: 12px;
+ }
+
+ .empty-state {
+ background: #fff;
+ border-radius: 12px;
+ padding: 40px;
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .record-card {
+ background: #fff;
+ border-radius: 12px;
+ padding: 16px;
+ margin-bottom: 12px;
+
+ .record-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+
+ .customer-info {
+ display: flex;
+ align-items: center;
+ }
+
+ .status-tag {
+ padding: 4px 10px;
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: 500;
+ }
+ }
+
+ .record-body {
+ .record-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 6px 0;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid #f0f0f0;
+ }
+ }
+ }
+ }
+
+ .no-more {
+ text-align: center;
+ padding: 16px;
+ }
+}
diff --git a/src/dealer/referral/index.tsx b/src/dealer/referral/index.tsx
new file mode 100644
index 0000000..bb8c57e
--- /dev/null
+++ b/src/dealer/referral/index.tsx
@@ -0,0 +1,323 @@
+import React, {useState, useEffect} from 'react'
+import {View, Text, ScrollView, Input, Button} from '@tarojs/components'
+import {ConfigProvider, Field, Cell, CellGroup, Toast} from '@nutui/nutui-react-taro'
+import {useDealerUser} from '@/hooks/useDealerUser'
+import Taro from '@tarojs/taro'
+import {addReferral, getReferralList, getReferralStats} from '@/api/shop/referral'
+import './index.scss'
+
+// 状态映射
+const STATUS_MAP: Record = {
+ 0: {text: '待确认', color: '#ff9800'},
+ 1: {text: '有效', color: '#4caf50'},
+ 2: {text: '无效', color: '#9e9e9e'},
+ 3: {text: '已结算', color: '#2196f3'}
+}
+
+const ReferralPage: React.FC = () => {
+ const {dealerUser} = useDealerUser()
+ const [loading, setLoading] = useState(false)
+ const [submitting, setSubmitting] = useState(false)
+
+ // 表单数据
+ const [formData, setFormData] = useState({
+ customerName: '',
+ customerPhone: '',
+ customerCompany: '',
+ requirement: '',
+ remarks: ''
+ })
+
+ // 统计
+ const [stats, setStats] = useState({
+ totalCount: 0,
+ pendingCount: 0,
+ validCount: 0,
+ settledCount: 0,
+ pendingAmount: 0
+ })
+
+ // 记录列表
+ const [records, setRecords] = useState([])
+ const [page, setPage] = useState(1)
+ const [hasMore, setHasMore] = useState(true)
+
+ // 加载数据
+ const loadData = async () => {
+ if (!dealerUser?.userId) return
+
+ try {
+ setLoading(true)
+
+ // 获取统计
+ const statsRes = await getReferralStats(dealerUser.userId)
+ if (statsRes.data.code === 0) {
+ setStats(statsRes.data.data)
+ }
+
+ // 获取列表
+ const listRes = await getReferralList({pageNum: 1, pageSize: 10})
+ if (listRes.data.code === 0) {
+ setRecords(listRes.data.data.list || [])
+ setHasMore(listRes.data.data.list?.length === 10)
+ }
+ } catch (error) {
+ console.error('加载失败', error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ loadData()
+ }, [dealerUser])
+
+ // 输入处理
+ const handleInput = (field: string, value: string) => {
+ setFormData(prev => ({...prev, [field]: value}))
+ }
+
+ // 表单验证
+ const validateForm = () => {
+ if (!formData.customerName.trim()) {
+ Toast.text('请输入客户姓名')
+ return false
+ }
+ if (!formData.customerPhone.trim()) {
+ Toast.text('请输入客户电话')
+ return false
+ }
+ if (!/^1[3-9]\d{9}$/.test(formData.customerPhone)) {
+ Toast.text('请输入正确的手机号')
+ return false
+ }
+ return true
+ }
+
+ // 提交报备
+ const handleSubmit = async () => {
+ if (!validateForm()) return
+
+ try {
+ setSubmitting(true)
+ const res = await addReferral(formData)
+
+ if (res.data.code === 0) {
+ Toast.text('报备成功!')
+ // 清空表单
+ setFormData({
+ customerName: '',
+ customerPhone: '',
+ customerCompany: '',
+ requirement: '',
+ remarks: ''
+ })
+ // 刷新数据
+ loadData()
+ } else {
+ Toast.text(res.data.message || '报备失败')
+ }
+ } catch (error: any) {
+ Toast.text(error.message || '报备失败')
+ } finally {
+ setSubmitting(false)
+ }
+ }
+
+ // 拨打电话
+ const handleCall = (phone: string) => {
+ if (phone) {
+ Taro.makePhoneCall({phoneNumber: phone})
+ }
+ }
+
+ // 加载更多
+ const loadMore = async () => {
+ if (!hasMore || loading) return
+
+ try {
+ const nextPage = page + 1
+ const res = await getReferralList({pageNum: nextPage, pageSize: 10})
+ if (res.data.code === 0 && res.data.data.list) {
+ setRecords(prev => [...prev, ...res.data.data.list])
+ setPage(nextPage)
+ setHasMore(res.data.data.list.length === 10)
+ }
+ } catch (error) {
+ console.error('加载更多失败', error)
+ }
+ }
+
+ return (
+
+ {/* 头部统计 */}
+
+
+ 我的推荐奖励
+
+
+
+ {stats.totalCount}
+ 总推荐
+
+
+ {stats.pendingCount}
+ 待确认
+
+
+ {stats.validCount}
+ 有效
+
+
+ ¥{stats.pendingAmount.toFixed(2)}
+ 待结算
+
+
+
+
+ {/* 报备表单 */}
+
+
+ 推荐新客户
+
+
+
+ |
+ handleInput('customerName', e.detail.value)}
+ />
+ |
+
+ handleInput('customerPhone', e.detail.value)}
+ />
+ |
+
+ handleInput('customerCompany', e.detail.value)}
+ />
+ |
+
+ handleInput('requirement', e.detail.value)}
+ style={{height: '80px', textAlign: 'left'}}
+ />
+ |
+
+
+
+
+
+
+
+
+ 报备成功后,业务员会尽快联系您的客户。成交后您将获得相应佣金奖励。
+
+
+
+
+
+ {/* 推荐记录 */}
+
+
+ 我的推荐记录
+
+
+ {records.length === 0 ? (
+
+ 暂无推荐记录
+ 快去推荐客户吧
+
+ ) : (
+
+ {records.map((item) => {
+ const statusInfo = STATUS_MAP[item.referralStatus] || STATUS_MAP[0]
+ return (
+
+
+
+ {item.customerName}
+ handleCall(item.customerPhone)}
+ >
+ {item.customerPhone}
+
+
+
+ {statusInfo.text}
+
+
+
+
+
+ 推荐时间
+ {item.createTime}
+
+ {item.referralFee > 0 && (
+
+ 奖励金额
+
+ ¥{item.referralFee.toFixed(2)}
+
+
+ )}
+ {item.leadStatusText && (
+
+ 客户状态
+ {item.leadStatusText}
+
+ )}
+
+
+ )
+ })}
+
+ {!hasMore && records.length > 0 && (
+
+ 没有更多了
+
+ )}
+
+ )}
+
+
+ {/* 底部安全区 */}
+
+
+ )
+}
+
+export default ReferralPage