From 8d82781bb3d8ec78871c8a3fdbf149fb1d176827 Mon Sep 17 00:00:00 2001 From: andy <594580820@qq.com> Date: Tue, 3 Feb 2026 15:58:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=8F=8A=E9=A1=B9=E7=9B=AE=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 509 ++++++++++++++++++++++++++---------------------- sql/01_init.sql | 10 +- 2 files changed, 278 insertions(+), 241 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0206d5c..0b5b799 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,15 +1,16 @@ -# 泊云智销通 +# 泊云智销通 CRM 系统 ## 项目简介 -泊云智销通是一款面向企业渠道管理的 CRM 系统,旨在解决渠道管理中的核心问题——**客户报备冲突**与**保护期归属**。系统通过唯一有效报备机制与自动到期释放机制,确保同一客户在同一时间只能被一个经销商跟进,并在保护期结束后自动回归可报备池,避免长期占坑,提升渠道效率与公平性。 +泊云智销通是一款面向企业渠道管理的 CRM 系统,核心解决**报备冲突**与**保护期归属**问题。系统通过**唯一有效报备机制**与**自动到期释放机制**,确保同一学校的同一项目在同一时间只能被一个经销商跟进,并在保护期结束后自动释放资源,提升渠道效率与公平性。 ### 核心价值 -- **防撞单机制**:同一客户同一时间只能有一个有效报备 -- **保护期管理**:自动计算和跟踪保护期,保护期到期自动释放 -- **公平竞争**:避免经销商长期占用客户资源 -- **全程可追溯**:所有报备记录永久保留,支持审计 +- **防撞单机制**:基于"学校+产品+项目类型"组合的唯一性校验,避免恶意抢占 +- **保护期管理**:自动计算保护期,到期自动释放,无需人工干预 +- **公平竞争**:防止资源长期占用,所有经销商公平竞争 +- **全程可追溯**:报备记录永久保留,支持进展记录追踪 +- **灵活配置**:支持手动输入学校名称,降低报备门槛 --- @@ -23,12 +24,13 @@ - **安全**: Spring Security + JWT + BCrypt - **API 文档**: Swagger - **构建工具**: Maven +- **定时任务**: Spring Task Scheduler ### 前端技术栈 - **框架**: Vue 3 + TypeScript - **UI 组件**: Element Plus -- **路由**: Vue Router +- **路由**: Vue Router 4 - **状态管理**: Pinia - **HTTP 客户端**: Axios - **构建工具**: Vite @@ -39,7 +41,7 @@ - **前后端分离**: RESTful API 架构 - **MVC 分层**: Controller → Service → Mapper → Entity - **数据库设计**: 遵循范式,合理使用索引 -- **安全设计**: 前端 SHA-256 哈希 + 后端 BCrypt 双重加密 +- **安全设计**: JWT 认证,BCrypt 密码加密 --- @@ -50,15 +52,13 @@ **角色标识**: `role = 0` **权限范围**: -- ✅ 客户管理(全部客户的查看、新增、编辑、删除) -- ✅ 报备管理(全部报备的查看、审核、撤回) +- ✅ 报备管理(全部报备的查看、审核、编辑、作废) - ✅ 经销商管理(经销商的增删改查、重置密码) +- ✅ 学校管理(查询、批量导入学校数据) - ✅ 系统配置(保护期天数等参数配置) -- ✅ 数据报表(经销商活跃度、客户转化率等) - ✅ 查看所有运营数据 **主要职责**: -- 维护客户信息 - 审核报备申请 - 管理经销商账号 - 配置系统参数 @@ -69,30 +69,29 @@ **角色标识**: `role = 1` **权限范围**: -- ✅ 查看自己的客户列表 -- ✅ 提交报备申请(选择客户进行报备) - ✅ 查看自己的报备记录 +- ✅ 提交报备申请 - ✅ 撤回待审核的报备 -- ✅ 查看报备审核状态 +- ✅ 为已通过的报备添加进展记录 - ✅ 修改个人密码 **主要职责**: -- 报备意向客户 -- 跟进保护期内的客户 -- 维护客户信息 -- 管理报备记录 +- 报备意向学校项目 +- 跟进保护期内的项目 +- 维护进展记录 ### 权限矩阵 | 功能模块 | 管理员 | 经销商 | |---------|--------|--------| -| 客户管理 | ✅ 全部 | ✅ 仅查看 | -| 报备管理 | ✅ 全部 | ✅ 自己的报备 | +| 报备管理 | 全部 | 自己的报备 | +| 提交报备 | ✅ | ✅ | | 报备审核 | ✅ | ❌ | +| 报备编辑/作废 | ✅ | ❌ | +| 进展记录 | 全部 | 自己报备的进展 | | 经销商管理 | ✅ | ❌ | +| 学校管理 | ✅ | ❌ | | 系统配置 | ✅ | ❌ | -| 数据报表 | ✅ | ❌ | -| 密码管理 | ✅ 自己的 | ✅ 自己的 | --- @@ -103,111 +102,92 @@ #### 1.1 用户登录 - **路径**: `POST /api/auth/login` - **描述**: 用户登录系统,获取 JWT Token -- **安全**: 前端 SHA-256 哈希 + 后端 BCrypt 加密 +- **安全**: BCrypt 加密存储 - **返回**: Token + 用户基本信息 **默认账号**: - 管理员: `admin` / `admin123` -- 经销商1: `user001` / `admin123` -- 经销商2: `user002` / `admin123` -- 经销商3: `user003` / `admin123` +- 经销商用户: 根据经销商数据 #### 1.2 修改密码 - **路径**: `POST /api/auth/change-password` - **权限**: 所有用户 - **功能**: 用户修改自己的登录密码 - **验证**: 需要输入原密码验证 -- **安全**: 密码 6-20 位,传输前哈希加密 #### 1.3 重置密码 - **路径**: `POST /api/auth/reset-password` - **权限**: 仅管理员 - **功能**: 管理员重置经销商用户的密码 -- **限制**: 不能重置管理员账号的密码 - -#### 1.4 退出登录 -- **路径**: `POST /api/auth/logout` -- **功能**: 清除客户端 Token --- -### 2. 客户管理 +### 2. 报备管理 -#### 2.1 客户列表 -- **路径**: `GET /api/customer/page` -- **权限**: 管理员查看全部,经销商查看全部 -- **功能**: 分页查询客户信息 -- **搜索字段**: 客户名称、所属行业、状态 -- **状态说明**: - - `0 - 可报备`: 未被保护,可报备 - - `1 - 保护中`: 正在保护期内 - - `2 - 已失效`: 保护期已过 - -#### 2.2 新增客户 -- **路径**: `POST /api/customer` -- **权限**: 仅管理员 -- **功能**: 创建新客户信息 -- **必填字段**: 客户名称 -- **可选字段**: 联系电话、地址、所属行业 -- **学校联动**: 支持从学校库自动完成选择 - -#### 2.3 编辑客户 -- **路径**: `PUT /api/customer/{id}` -- **权限**: 仅管理员 -- **功能**: 修改客户信息 - -#### 2.4 删除客户 -- **路径**: `DELETE /api/customer/{id}` -- **权限**: 仅管理员 -- **功能**: 删除客户记录 -- **限制**: 有报备记录的客户需谨慎处理 - -#### 2.5 客户搜索 -- **路径**: `GET /api/customer/search` -- **功能**: 模糊搜索客户名称(用于报备时的客户选择) - ---- - -### 3. 报备管理 - -#### 3.1 报备列表 +#### 2.1 报备列表 - **路径**: `GET /api/report/page` - **权限**: 管理员查看全部,经销商查看自己的 - **功能**: 分页查询报备记录 +- **搜索字段**: 学校名称、状态 - **状态说明**: - `0 - 待审核`: 等待管理员审核 - `1 - 已通过`: 审核通过,保护期内 - `2 - 已驳回`: 审核未通过 - `3 - 已失效`: 保护期已过 + - `4 - 已作废`: 管理员主动作废 -#### 3.2 提交报备 +#### 2.2 提交报备 - **路径**: `POST /api/report` - **权限**: 经销商 -- **功能**: 经销商报备意向客户 -- **必填字段**: 客户ID、报备说明 -- **限制**: - - 同一客户同一时间只能有一个有效报备 - - 已保护的客户无法再次报备 - - 待审核的报备可撤回 +- **功能**: 经销商报备意向学校项目 +- **必填字段**: 学校名称、所属产品、项目类型 +- **可选字段**: 报备说明 +- **防撞单规则**: + - 同一学校的同一产品+项目类型组合,只能有一个有效报备 + - 有效报备定义:状态为"待审核(0)"或"已通过(1)" + - 已失效、已驳回、已作废的报备不影响新报备 -#### 3.3 撤回报备 +#### 2.3 撤回报备 - **路径**: `DELETE /api/report/{id}` - **权限**: 报备所属经销商 - **功能**: 撤回待审核的报备申请 -- **限制**: 已通过和已驳回的报备无法撤回 +- **限制**: 只能撤回状态为"待审核"的报备 -#### 3.4 审核报备 +#### 2.4 审核报备 - **路径**: `PUT /api/report/{id}/audit` - **权限**: 仅管理员 - **功能**: 审核报备申请 - **操作**: - - 通过:设置保护期(默认90天),客户状态改为"保护中" - - 驳回:填写驳回原因,客户保持可报备状态 + - 通过:设置保护期(默认90天),状态变为"已通过" + - 驳回:填写驳回原因,状态变为"已驳回" -#### 3.5 报备详情 +#### 2.5 编辑报备 +- **路径**: `PUT /api/report/{id}` +- **权限**: 仅管理员 +- **功能**: 编辑已通过的报备 +- **可编辑字段**: 所属产品、项目类型、报备说明、状态 +- **作废操作**: 状态改为"已作废"时必须填写作废原因 + +#### 2.6 报备详情 - **路径**: `GET /api/report/{id}` - **权限**: 根据权限查看 -- **功能**: 查看报备详细信息 +- **功能**: 查看报备详细信息和进展记录 + +--- + +### 3. 进展记录管理 + +#### 3.1 添加进展记录 +- **路径**: `POST /api/report-progress` +- **权限**: 经销商只能为自己的报备添加,管理员可查看所有 +- **功能**: 为已通过的报备添加进展记录 +- **限制**: 只能为状态为"已通过"的报备添加进展 + +#### 3.2 编辑进展记录 +- **路径**: `PUT /api/report-progress/{id}` +- **权限**: 进展记录创建者 +- **功能**: 修改进展记录内容 +- **保留历史**: 不支持删除,确保数据完整性 --- @@ -217,18 +197,12 @@ - **路径**: `GET /api/dealer/list` - **权限**: 仅管理员 - **功能**: 查询所有经销商信息 -- **状态**: 启用/禁用 #### 4.2 新增经销商 - **路径**: `POST /api/dealer` - **权限**: 仅管理员 -- **必填字段**: 经销商名称、经销商账号(用作登录用户名)、联系人、联系电话 -- **可选字段**: 邮箱、初始密码 -- **自动创建**: - - 创建经销商记录 - - 创建对应的登录用户 - - 用户名 = 经销商账号 - - 默认密码 = 123456(可自定义) +- **必填字段**: 经销商名称、经销商账号、联系人、联系电话 +- **自动创建**: 创建经销商记录的同时创建对应的登录用户 #### 4.3 编辑经销商 - **路径**: `PUT /api/dealer/{id}` @@ -240,11 +214,6 @@ - **权限**: 仅管理员 - **功能**: 删除经销商及其关联用户 -#### 4.5 重置密码 -- **路径**: `POST /api/auth/reset-password` -- **权限**: 仅管理员 -- **功能**: 重置经销商用户的登录密码 - --- ### 5. 学校管理 @@ -257,8 +226,8 @@ #### 5.2 学校搜索 - **路径**: `GET /api/school/search?keyword=xxx` - **权限**: 所有用户 -- **功能**: 模糊搜索学校名称(用于客户名称自动完成) -- **应用场景**: 新增客户时,输入学校名称,自动匹配学校库 +- **功能**: 模糊搜索学校名称 +- **应用场景**: 报备时学校名称的自动完成提示 #### 5.3 批量导入学校 - **路径**: `POST /api/school/import` @@ -266,13 +235,8 @@ - **功能**: 上传 Excel 文件批量导入学校数据 - **文件格式**: `.xls` 或 `.xlsx` -#### 5.4 从 docs 目录导入 -- **路径**: `POST /api/school/import-from-docs` -- **权限**: 仅管理员 -- **功能**: 直接导入项目 docs 目录下的 `school.xls` 文件 - **学校数据结构**: -- 学校标识码 +- 学校标识码(唯一) - 学校名称 - 所在地(省份 + 城市) @@ -284,21 +248,18 @@ - **配置项**: `report.protect.days` - **默认值**: `90` 天 - **说明**: 控制报备通过后的保护期时长 -- **影响**: - - 保护期计算 - - 自动失效定时任务 #### 6.2 报备冲突控制 -- **配置项**: `report.allow-overlap` +- **配置项**: `report.allow.overlap` - **默认值**: `false` -- **说明**: 是否允许同一客户的报备重叠(仅用于测试) -- **生产**: 必须为 `false` +- **说明**: 是否允许重叠报备(仅用于测试) +- **生产环境**: 必须为 `false` --- ## 业务流程 -### 报备生命周期 +### 报备完整生命周期 ``` ┌─────────────────────────────────────────────────────────────┐ @@ -306,50 +267,61 @@ └─────────────────────────────────────────────────────────────┘ 1. 经销商提交报备 - ├─ 选择客户(只能选择"可报备"状态的客户) - ├─ 填写报备说明 + ├─ 学校名称(可从学校库选择或手动输入) + ├─ 所属产品(必填) + ├─ 项目类型(必填) + ├─ 报备说明(可选) └─ 提交审核 ↓ 状态:待审核(0) - 客户状态:可报备(0) 2. 管理员审核 - ├─ 通过 → 设置保护期(如90天) + ├─ 通过 → 设置保护期(默认90天) │ ├─ 报备状态:已通过(1) - │ └─ 客户状态:保护中(1) + │ └─ 保护期倒计时开始 │ └─ 驳回 → 填写驳回原因 - ├─ 报备状态:已驳回(2) - └─ 客户状态:可报备(0) + └─ 报备状态:已驳回(2) 3. 保护期内 - ├─ 客户被锁定,其他经销商无法报备 - ├─ 原报备经销商可跟进客户 + ├─ 该"学校+产品+项目类型"组合被锁定 + ├─ 其他经销商无法报备相同组合 + ├─ 经销商可添加进展记录 └─ 剩余保护天数倒计时 -4. 保护期到期(定时任务) +4. 管理员操作(可选) + ├─ 编辑报备:修改产品、项目类型、说明 + └─ 作废报备:状态改为已作废(4),填写原因 + └─ 作废后该组合可重新报备 + +5. 保护期到期(定时任务) └─ 每天凌晨1点自动执行 ├─ 报备状态:已通过 → 已失效(3) - └─ 客户状态:保护中 → 可报备(0) - -5. 客户释放 - └─ 所有经销商可再次报备该客户 + └─ 该"学校+产品+项目类型"组合可再次报备 ``` -### 客户状态流转 +### 报备状态转换规则 ``` -可报备(0) ──[提交报备]──> 锁定(其他经销商不可报备) - │ - [管理员审核通过] - ↓ - 保护中(1) ──[90天后]──> 可报备(0) - ↑ - [报备被驳回] - │ - [管理员驳回] - ↓ - 可报备(0) +待审核(0) + ↓ [管理员审核通过] +已通过(1) ←───────┐ + ↓ │ + │ [保护期到期] │ [管理员编辑恢复] + ↓ │ +已失效(3) ─────────┘ + +待审核(0) + ↓ [管理员驳回] +已驳回(2) ←─── 终态 + +待审核(0) + ↓ [经销商撤回] +删除记录 ←─── 物理删除 + +已通过(1) + ↓ [管理员作废] +已作废(4) ←─── 终态 ``` --- @@ -358,39 +330,31 @@ ### 核心表结构 -#### 1. 客户表 (crm_customer) - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | BIGINT | 客户ID(主键) | -| name | VARCHAR(200) | 客户名称 | -| phone | VARCHAR(20) | 联系电话 | -| address | VARCHAR(500) | 地址 | -| industry | VARCHAR(50) | 所属行业 | -| status | TINYINT | 状态:0-可报备 1-保护中 2-已失效 | -| current_dealer_id | BIGINT | 当前报备经销商ID | -| protect_end_date | DATE | 保护期结束日期 | -| created_at | DATETIME | 创建时间 | -| updated_at | DATETIME | 更新时间 | - -#### 2. 报备表 (crm_report) +#### 1. 报备表 (crm_report) | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT | 报备ID(主键) | | dealer_id | BIGINT | 经销商ID | -| customer_id | BIGINT | 客户ID | +| school_id | BIGINT | 学校ID(可为NULL,手动输入时为空) | +| school_name | VARCHAR(255) | 学校名称(冗余存储,方便查询) | +| product | VARCHAR(255) | 所属产品 | +| project_type | VARCHAR(255) | 项目类型 | | description | VARCHAR(500) | 报备说明 | -| status | TINYINT | 状态:0-待审核 1-已通过 2-已驳回 3-已失效 | +| status | TINYINT | 状态:0-待审核 1-已通过 2-已驳回 3-已失效 4-已作废 | | reject_reason | VARCHAR(255) | 驳回原因 | +| cancel_reason | VARCHAR(500) | 作废原因 | | protect_start_date | DATE | 保护期开始日期 | | protect_end_date | DATE | 保护期结束日期 | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | -**唯一索引**: `uk_customer_valid (customer_id, status)` - 同一客户只能有一个有效报备 +**索引**: +- `idx_dealer_id` - 经销商ID索引 +- `idx_status` - 状态索引 +- `idx_protect_end_date` - 保护期结束日期索引(定时任务使用) -#### 3. 经销商表 (crm_dealer) +#### 2. 经销商表 (crm_dealer) | 字段 | 类型 | 说明 | |------|------|------| @@ -404,28 +368,58 @@ | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | -#### 4. 用户表 (crm_user) +#### 3. 用户表 (crm_user) | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT | 用户ID(主键) | | username | VARCHAR(50) | 用户名(登录账号) | | password | VARCHAR(255) | 密码(BCrypt加密) | -| real_name | VARCHAR(50) | 真实姓名 | | dealer_id | BIGINT | 关联经销商ID(管理员为NULL) | | role | TINYINT | 角色:0-管理员 1-经销商用户 | | status | TINYINT | 状态:0-禁用 1-启用 | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | -#### 5. 学校表 (crm_school) +#### 4. 学校表 (crm_school) | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT | 学校ID(主键) | | school_code | VARCHAR(50) | 学校标识码(唯一) | | school_name | VARCHAR(200) | 学校名称 | -| location | VARCHAR(255) | 所在地(省份+城市) | +| location | VARCHAR(255) | 所在地 | +| created_at | DATETIME | 创建时间 | +| updated_at | DATETIME | 更新时间 | + +**索引**: +- `uk_school_code` - 学校标识码唯一索引 +- `idx_school_name` - 学校名称索引 + +#### 5. 报备进展记录表 (crm_report_progress) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | BIGINT | 主键ID | +| report_id | BIGINT | 报备ID | +| progress_content | VARCHAR(500) | 进展内容 | +| created_by | BIGINT | 创建人ID | +| created_at | DATETIME | 创建时间 | +| updated_at | DATETIME | 更新时间 | + +**索引**: +- `idx_report_id` - 报备ID索引 +- `idx_created_by` - 创建人索引 + +#### 6. 系统配置表 (crm_system_config) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | BIGINT | 主键ID | +| config_key | VARCHAR(100) | 配置键(唯一) | +| config_value | VARCHAR(500) | 配置值 | +| config_type | VARCHAR(20) | 配置类型(string/integer/boolean) | +| is_editable | TINYINT | 是否可编辑(1-是 0-否) | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | @@ -441,8 +435,87 @@ **功能**: 1. 查询所有保护期已到的报备(`protect_end_date <= 今天` 且 `status = 1`) 2. 批量更新报备状态为"已失效"(`status = 3`) -3. 批量更新关联客户状态为"可报备"(`status = 0`) -4. 保证渠道资源公平分配 +3. 释放资源,允许相同"学校+产品+项目类型"组合重新报备 + +**核心代码**: `ReportExpireTask.java` + +```java +@Scheduled(cron = "0 0 1 * * ?") +public void handleExpiredReports() { + log.info("开始处理过期报备..."); + try { + reportService.handleExpiredReports(); + log.info("过期报备处理完成"); + } catch (Exception e) { + log.error("处理过期报备失败", e); + } +} +``` + +--- + +## 防撞单机制详解 + +### 核心规则 + +**唯一性维度**: 学校 + 产品 + 项目类型 + +**有效报备定义**: 状态为"待审核(0)"或"已通过(1)"的报备 + +### 校验逻辑 + +```java +// 1. 如果选择了学校(有 school_id) +// 按 school_id + product + project_type 精确匹配 +WHERE school_id = #{schoolId} + AND product = #{product} + AND project_type = #{projectType} + AND status IN (0, 1) + +// 2. 如果手动输入学校(无 school_id) +// 按 school_name + product + project_type 匹配 +WHERE school_name = #{schoolName} + AND product = #{product} + AND project_type = #{projectType} + AND status IN (0, 1) +``` + +### 业务意义 + +- **防止撞单**: 同一项目不能被多个经销商同时报备 +- **公平竞争**: 保护期内锁定,到期后自动释放 +- **灵活性**: 支持手动输入学校名称,降低报备门槛 + +--- + +## 学校选择机制 + +### 学校数据来源 + +学校数据存储在 `crm_school` 表中,包含: +- 学校标识码(唯一) +- 学校名称 +- 所在地 + +### 前端交互 + +1. **自动完成搜索** + - 用户输入学校名称 + - 前端调用 `/api/school/search?keyword=xxx` 接口 + - 后端模糊匹配 `school_name` + - 返回匹配列表供用户选择 + +2. **手动输入** + - 用户可以直接输入学校名称 + - 如果不从下拉列表选择,`school_id` 为 NULL + - `school_name` 保存用户输入的文本 + - 防撞单校验时按 `school_name` 进行匹配 + +### 设计优势 + +- **降低门槛**: 学校不在库中也能报备,不影响业务 +- **数据规范化**: 优先使用学校库数据,保证数据质量 +- **灵活扩展**: 支持批量导入学校数据 --- @@ -459,36 +532,15 @@ #### 权限拦截 - 前端路由守卫:未登录重定向到登录页 - 后端拦截器:验证 Token 有效性 -- 接口权限注解:`@AuthRequired` - 数据权限:经销商只能查看自己的报备 ### 2. 密码安全 -#### 双重加密 -``` -用户输入 → [前端 SHA-256 哈希] → HTTPS 传输 → [后端 BCrypt 加密] → 存储 -``` - #### 密码规则 - 长度:6-20 位 - 加密:BCrypt(自动加盐) -- 传输:SHA-256 哈希后传输 - 修改密码:需验证原密码 -### 3. 数据安全 - -#### SQL 注入防护 -- 使用 MyBatis 参数化查询 -- 所有用户输入都经过参数绑定 - -#### XSS 防护 -- 前端输出自动转义 -- 后端返回 JSON 格式 - -#### CSRF 防护 -- 使用 JWT 无状态认证 -- 不依赖 Session Cookie - --- ## 项目结构 @@ -502,10 +554,10 @@ by-crm/ │ │ ├── config/ # 配置类(CORS、MyBatis、Swagger等) │ │ ├── controller/ # 控制器层 │ │ │ ├── AuthController.java -│ │ │ ├── CustomerController.java │ │ │ ├── ReportController.java │ │ │ ├── DealerController.java -│ │ │ └── SchoolController.java +│ │ │ ├── SchoolController.java +│ │ │ └── ReportProgressController.java │ │ ├── dto/ # 数据传输对象 │ │ ├── entity/ # 实体类 │ │ ├── exception/ # 异常处理 @@ -533,7 +585,6 @@ by-crm/ │ │ │ ├── Login.vue # 登录页 │ │ │ ├── Layout.vue # 主布局 │ │ │ ├── Dashboard.vue # 首页 -│ │ │ ├── Customer.vue # 客户管理 │ │ │ ├── Report.vue # 报备管理 │ │ │ └── Dealer.vue # 经销商管理 │ │ ├── App.vue # 根组件 @@ -542,14 +593,12 @@ by-crm/ │ ├── package.json # 依赖配置 │ └── tsconfig.json # TS 配置 │ -├── sql/ # 数据库脚本 -│ ├── init.sql # 初始化脚本 -│ ├── school_table.sql # 学校表 -│ └── *.sql # 其他 SQL 脚本 +├── docs/ # 文档目录 +│ └── README.md # 项目文档 │ -└── docs/ # 文档目录 - ├── api.md # API 文档(Swagger 导出) - └── password-security.md # 密码安全说明 +└── sql/ # 数据库脚本 + ├── init.sql # 初始化脚本 + └── *.sql # 其他 SQL 脚本 ``` --- @@ -581,7 +630,7 @@ jwt: # 保护期配置 crm: report: - ttl-days: 90 # 保护期天数(默认90天) + protect-days: 90 # 保护期天数(默认90天) allow-overlap: false # 是否允许报备重叠(测试用,生产必须false) ``` @@ -593,9 +642,6 @@ const BASE_URL = 'http://localhost:8080/api' // Token 存储 const TOKEN_KEY = 'token' - -// 路由模式:history 或 hash -const routerMode = 'history' ``` --- @@ -660,21 +706,19 @@ mysql -u root -p by_crm < sql/init.sql - `POST /api/auth/change-password` - 修改密码 - `POST /api/auth/reset-password` - 重置密码 -### 客户接口 -- `GET /api/customer/page` - 分页查询客户 -- `GET /api/customer/{id}` - 获取客户详情 -- `POST /api/customer` - 创建客户 -- `PUT /api/customer/{id}` - 更新客户 -- `DELETE /api/customer/{id}` - 删除客户 -- `GET /api/customer/search?keyword=xxx` - 搜索客户 - ### 报备接口 - `GET /api/report/page` - 分页查询报备 - `GET /api/report/{id}` - 获取报备详情 - `POST /api/report` - 创建报备 +- `PUT /api/report/{id}` - 更新报备 - `DELETE /api/report/{id}` - 撤回报备 - `PUT /api/report/{id}/audit` - 审核报备 +### 进展记录接口 +- `GET /api/report-progress/list?reportId={id}` - 查询进展记录 +- `POST /api/report-progress` - 添加进展记录 +- `PUT /api/report-progress/{id}` - 编辑进展记录 + ### 经销商接口(仅管理员) - `GET /api/dealer/list` - 查询所有经销商 - `POST /api/dealer` - 创建经销商 @@ -686,7 +730,6 @@ mysql -u root -p by_crm < sql/init.sql - `GET /api/school/search?keyword=xxx` - 搜索学校 - `POST /api/school` - 创建学校 - `POST /api/school/import` - 文件上传导入 -- `POST /api/school/import-from-docs` - 从 docs 目录导入 --- @@ -694,20 +737,19 @@ mysql -u root -p by_crm < sql/init.sql ### v1.0.0 (当前版本) -**新增功能**: -- ✅ 客户管理(增删改查) -- ✅ 报备管理(提交、审核、撤回) +**核心功能**: +- ✅ 报备管理(提交、审核、撤回、编辑、作废) - ✅ 经销商管理(增删改查、重置密码) - ✅ 学校管理(查询、搜索、导入) - ✅ 用户认证(登录、密码管理) +- ✅ 进展记录(添加、编辑、查询) - ✅ 自动保护期到期处理 -- ✅ 学校数据自动完成功能 -- ✅ 密码传输加密(SHA-256 + BCrypt 双重加密) +- ✅ 防撞单机制(学校+产品+项目类型) +- ✅ 灵活的学校输入(库选择+手动输入) **安全特性**: - ✅ JWT Token 认证 - ✅ BCrypt 密码加密存储 -- ✅ SHA-256 前端哈希传输 - ✅ 权限注解拦截 - ✅ SQL 注入防护 - ✅ XSS 防护 @@ -716,40 +758,33 @@ mysql -u root -p by_crm < sql/init.sql ## 常见问题 -### 1. 默认密码是什么? -- 新增经销商时,如果不设置初始密码,默认为 `123456` +### 1. 默认账号是什么? - 管理员账号:`admin` / `admin123` -- 经销商账号:`user001~003` / `admin123` +- 经销商账号:根据经销商数据创建 ### 2. 保护期可以修改吗? - 可以,通过修改系统配置 `report.protect.days` - 默认为 90 天,可根据业务需求调整 -### 3. 同一客户可以同时被多个经销商报备吗? +### 3. 同一学校项目可以同时被多个经销商报备吗? - **不可以**,这是系统的核心规则 -- 同一时间只能有一个"已通过"的报备 -- 其他经销商报备该客户会被拒绝 +- 同一"学校+产品+项目类型"组合只能有一个"有效"报备 +- 有效报备定义:状态为"待审核(0)"或"已通过(1)" ### 4. 保护期到期后会发生什么? - 定时任务每天凌晨1点自动执行 - 将过期报备状态改为"已失效" -- 客户状态自动变为"可报备" -- 其他经销商可以再次报备 +- 该"学校+产品+项目类型"组合可重新报备 -### 5. 如何确保传输安全? -- 前端:使用 Web Crypto API 进行 SHA-256 哈希 -- 传输:HTTPS + 哈希后的密码 -- 后端:BCrypt 二次加密存储 -- 三重安全保障 +### 5. 学校名称可以手动输入吗? +- 可以,支持从学校库选择或手动输入 +- 从学校库选择时会保存 `school_id` 和标准学校名称 +- 手动输入时 `school_id` 为 NULL,保存用户输入的文本 -### 6. 管理员可以重置其他管理员密码吗? -- **不可以**,只能重置经销商用户的密码 -- 管理员修改自己的密码通过"修改密码"功能 - -### 7. 学校数据从哪里来? -- 存放在 `docs/school.xls` 文件中 -- 包含:学校标识码、学校名称、所在地 -- 可通过 API 导入到数据库 +### 6. 报备可以作废吗? +- 可以,只有管理员可以作废已通过的报备 +- 作废时必须填写作废原因 +- 作废后该"学校+产品+项目类型"组合可重新报备 --- diff --git a/sql/01_init.sql b/sql/01_init.sql index 6bc7a4a..f017a89 100644 --- a/sql/01_init.sql +++ b/sql/01_init.sql @@ -41,7 +41,6 @@ CREATE TABLE IF NOT EXISTS crm_customer ( CREATE TABLE IF NOT EXISTS crm_report ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '报备ID', dealer_id BIGINT NOT NULL COMMENT '经销商ID', - customer_id BIGINT NOT NULL COMMENT '客户ID', description VARCHAR(500) COMMENT '报备说明', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0-待审核 1-已通过 2-已驳回 3-已失效', reject_reason VARCHAR(255) COMMENT '驳回原因', @@ -49,11 +48,14 @@ CREATE TABLE IF NOT EXISTS crm_report ( protect_end_date DATE DEFAULT NULL COMMENT '保护期结束日期', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + school_id BIGINT DEFAULT NULL COMMENT '学校ID', + school_name VARCHAR(255) COMMENT '学校名称(冗余存储)', + product VARCHAR(255) COMMENT '所属产品', + project_type VARCHAR(255) COMMENT '项目类型', + cancel_reason VARCHAR(500) COMMENT '作废原因', INDEX idx_dealer_id (dealer_id), - INDEX idx_customer_id (customer_id), INDEX idx_status (status), - INDEX idx_protect_end_date (protect_end_date), - UNIQUE KEY uk_customer_valid (customer_id, status) COMMENT '同一客户只能有一个有效报备' + INDEX idx_protect_end_date (protect_end_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报备表'; -- 用户表(管理员和经销商用户)