feat: 添加企业线索管理功能,包括线索的创建、更新、删除和查询接口,更新相关文档和数据库结构
This commit is contained in:
1 parent
d6890462a3
commit
b8c21c24cd
11 files changed
+2500
No files matched your search
@@ -0,0 +1,210 @@
|
||||
# 企业线索收集
|
||||
|
||||
从各个渠道收集用户线索,并进行跟进和转化。
|
||||
|
||||
## 表设计
|
||||
|
||||
```sql
|
||||
-- 企业线索表
|
||||
CREATE TABLE leads (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
lead_no VARCHAR(50) NOT NULL COMMENT '线索编号,系统生成',
|
||||
company_name VARCHAR(200) NOT NULL COMMENT '企业/机构名称',
|
||||
contact_name VARCHAR(100) NOT NULL COMMENT '联系人',
|
||||
contact_phone VARCHAR(20) NOT NULL COMMENT '联系电话',
|
||||
contact_email VARCHAR(100) COMMENT '邮箱地址',
|
||||
requirement TEXT NOT NULL COMMENT '需求描述',
|
||||
extra_info TEXT COMMENT '补充信息',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending-待联系, contacted-已联系, converted-已成交, invalid-已失效',
|
||||
follow_up_date DATETIME COMMENT '跟进日期',
|
||||
follow_up_record TEXT COMMENT '跟进记录',
|
||||
internal_note TEXT COMMENT '内部备注',
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
|
||||
INDEX idx_leads_status (status),
|
||||
INDEX idx_leads_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业线索表';
|
||||
```
|
||||
|
||||
## 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | VARCHAR(36) | 是 | 主键,UUID |
|
||||
| lead_no | VARCHAR(50) | 是 | 线索编号,系统生成,格式:LEAD + 时间戳 + 6位随机数 |
|
||||
| company_name | VARCHAR(200) | 是 | 企业/机构名称 |
|
||||
| contact_name | VARCHAR(100) | 是 | 联系人姓名 |
|
||||
| contact_phone | VARCHAR(20) | 是 | 联系电话 |
|
||||
| contact_email | VARCHAR(100) | 否 | 邮箱地址 |
|
||||
| requirement | TEXT | 是 | 需求描述 |
|
||||
| extra_info | TEXT | 否 | 补充信息 |
|
||||
| status | VARCHAR(20) | 是 | 状态:pending-待联系,contacted-已联系,converted-已成交,invalid-已失效 |
|
||||
| follow_up_date | DATETIME | 否 | 跟进日期 |
|
||||
| follow_up_record | TEXT | 否 | 跟进记录 |
|
||||
| internal_note | TEXT | 否 | 内部备注 |
|
||||
| created_at | DATETIME | 是 | 创建时间 |
|
||||
| updated_at | DATETIME | 是 | 更新时间 |
|
||||
|
||||
## 状态流转
|
||||
|
||||
```
|
||||
pending (待联系) → contacted (已联系) → converted (已成交)
|
||||
↓
|
||||
invalid (已失效)
|
||||
```
|
||||
|
||||
## 接口设计
|
||||
|
||||
### 1. 新增线索(公开接口)
|
||||
```
|
||||
POST /api/leads
|
||||
```
|
||||
**无需鉴权**
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"company_name": "北京科技有限公司",
|
||||
"contact_name": "张三",
|
||||
"contact_phone": "13800138000",
|
||||
"contact_email": "zhangsan@example.com",
|
||||
"requirement": "需要采购50套软件授权",
|
||||
"extra_info": "希望了解批量购买优惠"
|
||||
}
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"lead_no": "LEAD20260201123456ABC123",
|
||||
"company_name": "北京科技有限公司",
|
||||
"contact_name": "张三",
|
||||
"contact_phone": "13800138000",
|
||||
"contact_email": "zhangsan@example.com",
|
||||
"requirement": "需要采购50套软件授权",
|
||||
"status": "pending",
|
||||
"created_at": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 线索列表(管理端)
|
||||
```
|
||||
GET /api/leads
|
||||
```
|
||||
**需要管理员鉴权**
|
||||
|
||||
**请求参数:**
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| page | int | 否 | 页码,默认1 |
|
||||
| page_size | int | 否 | 每页数量,默认10,最大100 |
|
||||
| search | string | 否 | 关键词检索(企业名称/联系人/联系电话) |
|
||||
| status | string | 否 | 状态筛选:pending/contacted/converted/invalid |
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"data": {
|
||||
"leads": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"lead_no": "LEAD20260201123456ABC123",
|
||||
"company_name": "北京科技有限公司",
|
||||
"contact_name": "张三",
|
||||
"contact_phone": "13800138000",
|
||||
"status": "pending",
|
||||
"created_at": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"total_count": 100,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 线索详情(管理端)
|
||||
```
|
||||
GET /api/leads/{id}
|
||||
```
|
||||
**需要管理员鉴权**
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"lead_no": "LEAD20260201123456ABC123",
|
||||
"company_name": "北京科技有限公司",
|
||||
"contact_name": "张三",
|
||||
"contact_phone": "13800138000",
|
||||
"contact_email": "zhangsan@example.com",
|
||||
"requirement": "需要采购50套软件授权",
|
||||
"extra_info": "希望了解批量购买优惠",
|
||||
"status": "contacted",
|
||||
"follow_up_date": "2026-02-05T10:00:00Z",
|
||||
"follow_up_record": "已电话沟通,客户表示下周决策",
|
||||
"internal_note": "重点跟进客户",
|
||||
"created_at": "2026-02-01T12:00:00Z",
|
||||
"updated_at": "2026-02-05T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 线索跟进(管理端)
|
||||
```
|
||||
PUT /api/leads/{id}
|
||||
```
|
||||
**需要管理员鉴权**
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"status": "contacted",
|
||||
"follow_up_date": "2026-02-05T10:00:00Z",
|
||||
"follow_up_record": "已电话沟通,客户表示下周决策",
|
||||
"internal_note": "重点跟进客户"
|
||||
}
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 删除线索(管理端)
|
||||
```
|
||||
DELETE /api/leads/{id}
|
||||
```
|
||||
**需要管理员鉴权**
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 实现文件
|
||||
|
||||
### 新建文件
|
||||
- `backend/internal/models/lead.go` - 线索模型
|
||||
- `backend/internal/repository/lead_repository.go` - 线索仓储
|
||||
- `backend/internal/service/lead_service.go` - 线索服务
|
||||
- `backend/internal/api/handlers/lead_handler.go` - 线索处理器
|
||||
- `backend/migrations/014_create_leads_table.sql` - 数据库迁移
|
||||
|
||||
### 修改文件
|
||||
- `backend/internal/api/routes/router.go` - 添加路由
|
||||
- `backend/internal/database/migration.go` - 添加自动迁移和初始化
|
||||
@@ -2824,6 +2824,447 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/leads": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "管理员获取线索列表,支持分页和筛选",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "获取线索列表",
|
||||
"parameters": [
|
||||
{
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"description": "页码,默认1",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"maximum": 100,
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"description": "每页条数,默认10,最大100",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "关键词检索",
|
||||
"name": "search",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "状态筛选",
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "用户提交企业线索,无需鉴权",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "创建线索",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "线索创建请求",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LeadCreateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "请求参数无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/leads/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据ID获取线索详细信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "获取线索详情",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "更新线索信息,用于线索跟进",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "更新线索",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "线索更新请求",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LeadUpdateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "请求参数无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "删除指定线索",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "删除线索",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/leads/{id}/status": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "快速更新线索状态",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "更新线索状态",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "状态: pending/contacted/converted/invalid",
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "请求参数无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/packages": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -7838,6 +8279,159 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadCreateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"company_name",
|
||||
"contact_name",
|
||||
"contact_phone",
|
||||
"requirement"
|
||||
],
|
||||
"properties": {
|
||||
"company_name": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_email": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
},
|
||||
"contact_name": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_phone": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"minLength": 1
|
||||
},
|
||||
"extra_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"requirement": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadListResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"leads": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"contact_email": {
|
||||
"type": "string"
|
||||
},
|
||||
"contact_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"contact_phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"extra_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_record": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"internal_note": {
|
||||
"type": "string"
|
||||
},
|
||||
"lead_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"requirement": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadUpdateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_name": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_email": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
},
|
||||
"contact_name": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_phone": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"minLength": 1
|
||||
},
|
||||
"extra_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_record": {
|
||||
"type": "string"
|
||||
},
|
||||
"internal_note": {
|
||||
"type": "string"
|
||||
},
|
||||
"requirement": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pending",
|
||||
"contacted",
|
||||
"converted",
|
||||
"invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.License": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -2820,6 +2820,447 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/leads": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "管理员获取线索列表,支持分页和筛选",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "获取线索列表",
|
||||
"parameters": [
|
||||
{
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"description": "页码,默认1",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"maximum": 100,
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"description": "每页条数,默认10,最大100",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "关键词检索",
|
||||
"name": "search",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "状态筛选",
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "用户提交企业线索,无需鉴权",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "创建线索",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "线索创建请求",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LeadCreateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "请求参数无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/leads/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据ID获取线索详细信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "获取线索详情",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "更新线索信息,用于线索跟进",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "更新线索",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "线索更新请求",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LeadUpdateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "请求参数无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "删除指定线索",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "删除线索",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "服务器内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/leads/{id}/status": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "快速更新线索状态",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"企业线索"
|
||||
],
|
||||
"summary": "更新线索状态",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "线索ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "状态: pending/contacted/converted/invalid",
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "请求参数无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "权限不足",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "线索不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/packages": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -7834,6 +8275,159 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadCreateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"company_name",
|
||||
"contact_name",
|
||||
"contact_phone",
|
||||
"requirement"
|
||||
],
|
||||
"properties": {
|
||||
"company_name": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_email": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
},
|
||||
"contact_name": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_phone": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"minLength": 1
|
||||
},
|
||||
"extra_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"requirement": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadListResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"leads": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.LeadResponse"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"contact_email": {
|
||||
"type": "string"
|
||||
},
|
||||
"contact_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"contact_phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"extra_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_record": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"internal_note": {
|
||||
"type": "string"
|
||||
},
|
||||
"lead_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"requirement": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LeadUpdateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_name": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_email": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
},
|
||||
"contact_name": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 1
|
||||
},
|
||||
"contact_phone": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"minLength": 1
|
||||
},
|
||||
"extra_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"follow_up_record": {
|
||||
"type": "string"
|
||||
},
|
||||
"internal_note": {
|
||||
"type": "string"
|
||||
},
|
||||
"requirement": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pending",
|
||||
"contacted",
|
||||
"converted",
|
||||
"invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.License": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1569,6 +1569,114 @@ definitions:
|
||||
description: 上传后的文件URL
|
||||
type: string
|
||||
type: object
|
||||
models.LeadCreateRequest:
|
||||
properties:
|
||||
company_name:
|
||||
maxLength: 200
|
||||
minLength: 1
|
||||
type: string
|
||||
contact_email:
|
||||
maxLength: 100
|
||||
type: string
|
||||
contact_name:
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
contact_phone:
|
||||
maxLength: 20
|
||||
minLength: 1
|
||||
type: string
|
||||
extra_info:
|
||||
type: string
|
||||
requirement:
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- company_name
|
||||
- contact_name
|
||||
- contact_phone
|
||||
- requirement
|
||||
type: object
|
||||
models.LeadListResponse:
|
||||
properties:
|
||||
leads:
|
||||
items:
|
||||
$ref: '#/definitions/models.LeadResponse'
|
||||
type: array
|
||||
page:
|
||||
type: integer
|
||||
page_size:
|
||||
type: integer
|
||||
total_count:
|
||||
type: integer
|
||||
type: object
|
||||
models.LeadResponse:
|
||||
properties:
|
||||
company_name:
|
||||
type: string
|
||||
contact_email:
|
||||
type: string
|
||||
contact_name:
|
||||
type: string
|
||||
contact_phone:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
extra_info:
|
||||
type: string
|
||||
follow_up_date:
|
||||
type: string
|
||||
follow_up_record:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
internal_note:
|
||||
type: string
|
||||
lead_no:
|
||||
type: string
|
||||
requirement:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
models.LeadUpdateRequest:
|
||||
properties:
|
||||
company_name:
|
||||
maxLength: 200
|
||||
minLength: 1
|
||||
type: string
|
||||
contact_email:
|
||||
maxLength: 100
|
||||
type: string
|
||||
contact_name:
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
contact_phone:
|
||||
maxLength: 20
|
||||
minLength: 1
|
||||
type: string
|
||||
extra_info:
|
||||
type: string
|
||||
follow_up_date:
|
||||
type: string
|
||||
follow_up_record:
|
||||
type: string
|
||||
internal_note:
|
||||
type: string
|
||||
requirement:
|
||||
minLength: 1
|
||||
type: string
|
||||
status:
|
||||
enum:
|
||||
- pending
|
||||
- contacted
|
||||
- converted
|
||||
- invalid
|
||||
type: string
|
||||
type: object
|
||||
models.License:
|
||||
properties:
|
||||
activated_at:
|
||||
@@ -3886,6 +3994,278 @@ paths:
|
||||
summary: 获取指定类型的枚举值
|
||||
tags:
|
||||
- 枚举管理
|
||||
/api/leads:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 管理员获取线索列表,支持分页和筛选
|
||||
parameters:
|
||||
- description: 页码,默认1
|
||||
in: query
|
||||
minimum: 1
|
||||
name: page
|
||||
type: integer
|
||||
- description: 每页条数,默认10,最大100
|
||||
in: query
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
name: page_size
|
||||
type: integer
|
||||
- description: 关键词检索
|
||||
in: query
|
||||
name: search
|
||||
type: string
|
||||
- description: 状态筛选
|
||||
in: query
|
||||
name: status
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.LeadListResponse'
|
||||
type: object
|
||||
"401":
|
||||
description: 未认证
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"403":
|
||||
description: 权限不足
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"500":
|
||||
description: 服务器内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 获取线索列表
|
||||
tags:
|
||||
- 企业线索
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 用户提交企业线索,无需鉴权
|
||||
parameters:
|
||||
- description: 线索创建请求
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.LeadCreateRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.LeadResponse'
|
||||
type: object
|
||||
"400":
|
||||
description: 请求参数无效
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"500":
|
||||
description: 服务器内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: 创建线索
|
||||
tags:
|
||||
- 企业线索
|
||||
/api/leads/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 删除指定线索
|
||||
parameters:
|
||||
- description: 线索ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIResponse'
|
||||
"401":
|
||||
description: 未认证
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"403":
|
||||
description: 权限不足
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"404":
|
||||
description: 线索不存在
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"500":
|
||||
description: 服务器内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 删除线索
|
||||
tags:
|
||||
- 企业线索
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 根据ID获取线索详细信息
|
||||
parameters:
|
||||
- description: 线索ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.LeadResponse'
|
||||
type: object
|
||||
"401":
|
||||
description: 未认证
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"403":
|
||||
description: 权限不足
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"404":
|
||||
description: 线索不存在
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"500":
|
||||
description: 服务器内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 获取线索详情
|
||||
tags:
|
||||
- 企业线索
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 更新线索信息,用于线索跟进
|
||||
parameters:
|
||||
- description: 线索ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: 线索更新请求
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.LeadUpdateRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.LeadResponse'
|
||||
type: object
|
||||
"400":
|
||||
description: 请求参数无效
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"401":
|
||||
description: 未认证
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"403":
|
||||
description: 权限不足
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"404":
|
||||
description: 线索不存在
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"500":
|
||||
description: 服务器内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 更新线索
|
||||
tags:
|
||||
- 企业线索
|
||||
/api/leads/{id}/status:
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 快速更新线索状态
|
||||
parameters:
|
||||
- description: 线索ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: '状态: pending/contacted/converted/invalid'
|
||||
in: query
|
||||
name: status
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.LeadResponse'
|
||||
type: object
|
||||
"400":
|
||||
description: 请求参数无效
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"401":
|
||||
description: 未认证
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"403":
|
||||
description: 权限不足
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"404":
|
||||
description: 线索不存在
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 更新线索状态
|
||||
tags:
|
||||
- 企业线索
|
||||
/api/packages:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"license-manager/internal/api/middleware"
|
||||
"license-manager/internal/models"
|
||||
"license-manager/internal/service"
|
||||
"license-manager/pkg/i18n"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type LeadHandler struct {
|
||||
leadService service.LeadService
|
||||
}
|
||||
|
||||
func NewLeadHandler(leadService service.LeadService) *LeadHandler {
|
||||
return &LeadHandler{
|
||||
leadService: leadService,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateLead 创建线索
|
||||
// @Summary 创建线索
|
||||
// @Description 用户提交企业线索,无需鉴权
|
||||
// @Tags 企业线索
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.LeadCreateRequest true "线索创建请求"
|
||||
// @Success 200 {object} models.APIResponse{data=models.LeadResponse} "成功"
|
||||
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
|
||||
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
|
||||
// @Router /api/leads [post]
|
||||
func (h *LeadHandler) CreateLead(c *gin.Context) {
|
||||
lang := middleware.GetLanguage(c)
|
||||
|
||||
var req models.LeadCreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
|
||||
c.JSON(status, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message + ": " + err.Error(),
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
lead, err := h.leadService.CreateLead(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleI18nError(c, err, lang)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: i18n.GetI18nErrorMessage("000000", lang),
|
||||
Data: lead.ToResponse(),
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// GetLeads 获取线索列表
|
||||
// @Summary 获取线索列表
|
||||
// @Description 管理员获取线索列表,支持分页和筛选
|
||||
// @Tags 企业线索
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param page query int false "页码,默认1" minimum(1)
|
||||
// @Param page_size query int false "每页条数,默认10,最大100" minimum(1) maximum(100)
|
||||
// @Param search query string false "关键词检索"
|
||||
// @Param status query string false "状态筛选"
|
||||
// @Success 200 {object} models.APIResponse{data=models.LeadListResponse} "成功"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Failure 403 {object} models.ErrorResponse "权限不足"
|
||||
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
|
||||
// @Router /api/leads [get]
|
||||
func (h *LeadHandler) GetLeads(c *gin.Context) {
|
||||
lang := middleware.GetLanguage(c)
|
||||
|
||||
var req models.LeadListRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
|
||||
c.JSON(status, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message + ": " + err.Error(),
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.leadService.GetLeadList(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleI18nError(c, err, lang)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: i18n.GetI18nErrorMessage("000000", lang),
|
||||
Data: result,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// GetLead 获取线索详情
|
||||
// @Summary 获取线索详情
|
||||
// @Description 根据ID获取线索详细信息
|
||||
// @Tags 企业线索
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "线索ID"
|
||||
// @Success 200 {object} models.APIResponse{data=models.LeadResponse} "成功"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Failure 403 {object} models.ErrorResponse "权限不足"
|
||||
// @Failure 404 {object} models.ErrorResponse "线索不存在"
|
||||
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
|
||||
// @Router /api/leads/{id} [get]
|
||||
func (h *LeadHandler) GetLead(c *gin.Context) {
|
||||
lang := middleware.GetLanguage(c)
|
||||
id := c.Param("id")
|
||||
|
||||
lead, err := h.leadService.GetLead(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleI18nError(c, err, lang)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: i18n.GetI18nErrorMessage("000000", lang),
|
||||
Data: lead.ToResponse(),
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLead 更新线索
|
||||
// @Summary 更新线索
|
||||
// @Description 更新线索信息,用于线索跟进
|
||||
// @Tags 企业线索
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "线索ID"
|
||||
// @Param request body models.LeadUpdateRequest true "线索更新请求"
|
||||
// @Success 200 {object} models.APIResponse{data=models.LeadResponse} "成功"
|
||||
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Failure 403 {object} models.ErrorResponse "权限不足"
|
||||
// @Failure 404 {object} models.ErrorResponse "线索不存在"
|
||||
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
|
||||
// @Router /api/leads/{id} [put]
|
||||
func (h *LeadHandler) UpdateLead(c *gin.Context) {
|
||||
lang := middleware.GetLanguage(c)
|
||||
id := c.Param("id")
|
||||
|
||||
var req models.LeadUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
|
||||
c.JSON(status, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message + ": " + err.Error(),
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
lead, err := h.leadService.UpdateLead(c.Request.Context(), id, &req)
|
||||
if err != nil {
|
||||
handleI18nError(c, err, lang)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: i18n.GetI18nErrorMessage("000000", lang),
|
||||
Data: lead.ToResponse(),
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteLead 删除线索
|
||||
// @Summary 删除线索
|
||||
// @Description 删除指定线索
|
||||
// @Tags 企业线索
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "线索ID"
|
||||
// @Success 200 {object} models.APIResponse{} "成功"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Failure 403 {object} models.ErrorResponse "权限不足"
|
||||
// @Failure 404 {object} models.ErrorResponse "线索不存在"
|
||||
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
|
||||
// @Router /api/leads/{id} [delete]
|
||||
func (h *LeadHandler) DeleteLead(c *gin.Context) {
|
||||
lang := middleware.GetLanguage(c)
|
||||
id := c.Param("id")
|
||||
|
||||
if err := h.leadService.DeleteLead(c.Request.Context(), id); err != nil {
|
||||
handleI18nError(c, err, lang)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: i18n.GetI18nErrorMessage("000000", lang),
|
||||
Data: nil,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLeadStatus 更新线索状态
|
||||
// @Summary 更新线索状态
|
||||
// @Description 快速更新线索状态
|
||||
// @Tags 企业线索
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "线索ID"
|
||||
// @Param status query string true "状态: pending/contacted/converted/invalid"
|
||||
// @Success 200 {object} models.APIResponse{data=models.LeadResponse} "成功"
|
||||
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Failure 403 {object} models.ErrorResponse "权限不足"
|
||||
// @Failure 404 {object} models.ErrorResponse "线索不存在"
|
||||
// @Router /api/leads/{id}/status [put]
|
||||
func (h *LeadHandler) UpdateLeadStatus(c *gin.Context) {
|
||||
lang := middleware.GetLanguage(c)
|
||||
id := c.Param("id")
|
||||
|
||||
status := c.Query("status")
|
||||
if status == "" {
|
||||
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
|
||||
c.JSON(status, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
validStatuses := map[string]bool{
|
||||
string(models.LeadStatusPending): true,
|
||||
string(models.LeadStatusContacted): true,
|
||||
string(models.LeadStatusConverted): true,
|
||||
string(models.LeadStatusInvalid): true,
|
||||
}
|
||||
if !validStatuses[status] {
|
||||
statusCode, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
|
||||
c.JSON(statusCode, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
req := &models.LeadUpdateRequest{
|
||||
Status: status,
|
||||
}
|
||||
|
||||
lead, err := h.leadService.UpdateLead(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleI18nError(c, err, lang)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: i18n.GetI18nErrorMessage("000000", lang),
|
||||
Data: lead.ToResponse(),
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
@@ -50,6 +50,7 @@ func SetupRouter() *gin.Engine {
|
||||
cuInvoiceRepo := repository.NewCuInvoiceRepository(db)
|
||||
adminInvoiceRepo := repository.NewAdminInvoiceRepository(db)
|
||||
packageRepo := repository.NewPackageRepository(db)
|
||||
leadRepo := repository.NewLeadRepository(db)
|
||||
|
||||
// 获取logger实例
|
||||
log := logger.GetLogger()
|
||||
@@ -118,6 +119,8 @@ func SetupRouter() *gin.Engine {
|
||||
adminInvoiceService := service.NewAdminInvoiceService(adminInvoiceRepo, cuOrderRepo, userRepo, cuUserRepo, db)
|
||||
adminInvoiceHandler := handlers.NewAdminInvoiceHandler(adminInvoiceService)
|
||||
packageHandler := handlers.NewPackageHandler(packageService)
|
||||
leadService := service.NewLeadService(leadRepo, db)
|
||||
leadHandler := handlers.NewLeadHandler(leadService)
|
||||
|
||||
// 健康检测接口(无需认证)
|
||||
router.GET("/health", systemHandler.HealthCheck)
|
||||
@@ -143,6 +146,9 @@ func SetupRouter() *gin.Engine {
|
||||
|
||||
// 公共发票下载接口
|
||||
public.GET("/public/invoices/download", adminInvoiceHandler.DownloadByToken)
|
||||
|
||||
// 线索收集(无需鉴权)
|
||||
public.POST("/leads", leadHandler.CreateLead)
|
||||
}
|
||||
|
||||
// 需要认证的接口
|
||||
@@ -204,6 +210,13 @@ func SetupRouter() *gin.Engine {
|
||||
auth.PUT("/packages/:id", packageHandler.UpdatePackage)
|
||||
auth.DELETE("/packages/:id", packageHandler.DeletePackage)
|
||||
auth.PUT("/packages/:id/status", packageHandler.UpdatePackageStatus)
|
||||
|
||||
// 线索管理(管理员)
|
||||
auth.GET("/leads", leadHandler.GetLeads)
|
||||
auth.GET("/leads/:id", leadHandler.GetLead)
|
||||
auth.PUT("/leads/:id", leadHandler.UpdateLead)
|
||||
auth.PUT("/leads/:id/status", leadHandler.UpdateLeadStatus)
|
||||
auth.DELETE("/leads/:id", leadHandler.DeleteLead)
|
||||
}
|
||||
|
||||
// 管理员接口
|
||||
|
||||
@@ -31,6 +31,7 @@ func AutoMigrate() error {
|
||||
&models.AuthorizationChange{},
|
||||
&models.Invoice{}, // 发票表
|
||||
&models.Package{}, // 套餐表
|
||||
&models.Lead{}, // 线索表
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to migrate database: %w", err)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LeadStatus 线索状态
|
||||
type LeadStatus string
|
||||
|
||||
const (
|
||||
LeadStatusPending LeadStatus = "pending" // 待联系
|
||||
LeadStatusContacted LeadStatus = "contacted" // 已联系
|
||||
LeadStatusConverted LeadStatus = "converted" // 已成交
|
||||
LeadStatusInvalid LeadStatus = "invalid" // 已失效
|
||||
)
|
||||
|
||||
// Lead 企业线索
|
||||
type Lead struct {
|
||||
ID string `gorm:"type:varchar(36);primaryKey" json:"id"`
|
||||
LeadNo string `gorm:"type:varchar(50);not null;uniqueIndex" json:"lead_no"`
|
||||
CompanyName string `gorm:"type:varchar(200);not null" json:"company_name"`
|
||||
ContactName string `gorm:"type:varchar(100);not null" json:"contact_name"`
|
||||
ContactPhone string `gorm:"type:varchar(20);not null" json:"contact_phone"`
|
||||
ContactEmail string `gorm:"type:varchar(100)" json:"contact_email"`
|
||||
Requirement string `gorm:"type:text;not null" json:"requirement"`
|
||||
ExtraInfo string `gorm:"type:text" json:"extra_info"`
|
||||
Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"`
|
||||
FollowUpDate *time.Time `gorm:"" json:"follow_up_date"`
|
||||
FollowUpRecord string `gorm:"type:text" json:"follow_up_record"`
|
||||
InternalNote string `gorm:"type:text" json:"internal_note"`
|
||||
CreatedAt time.Time `gorm:"not null" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Lead) TableName() string {
|
||||
return "leads"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前自动设置ID和时间戳
|
||||
func (l *Lead) BeforeCreate(tx *gorm.DB) error {
|
||||
if l.ID == "" {
|
||||
l.ID = uuid.New().String()
|
||||
}
|
||||
now := time.Now()
|
||||
if l.CreatedAt.IsZero() {
|
||||
l.CreatedAt = now
|
||||
}
|
||||
if l.UpdatedAt.IsZero() {
|
||||
l.UpdatedAt = now
|
||||
}
|
||||
// 生成线索编号
|
||||
if l.LeadNo == "" {
|
||||
l.LeadNo = generateLeadNo()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateLeadNo 生成线索编号
|
||||
func generateLeadNo() string {
|
||||
return "LEAD" + time.Now().Format("20060102150405") + randomString(6)
|
||||
}
|
||||
|
||||
// randomString 生成随机字符串
|
||||
func randomString(length int) string {
|
||||
const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
result := make([]byte, length)
|
||||
now := time.Now().UnixNano()
|
||||
for i := range result {
|
||||
result[i] = charset[(now+int64(i)*17)%int64(len(charset))]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// ToResponse 转换为响应结构
|
||||
func (l *Lead) ToResponse() *LeadResponse {
|
||||
return &LeadResponse{
|
||||
ID: l.ID,
|
||||
LeadNo: l.LeadNo,
|
||||
CompanyName: l.CompanyName,
|
||||
ContactName: l.ContactName,
|
||||
ContactPhone: l.ContactPhone,
|
||||
ContactEmail: l.ContactEmail,
|
||||
Requirement: l.Requirement,
|
||||
ExtraInfo: l.ExtraInfo,
|
||||
Status: l.Status,
|
||||
FollowUpDate: l.FollowUpDate,
|
||||
FollowUpRecord: l.FollowUpRecord,
|
||||
InternalNote: l.InternalNote,
|
||||
CreatedAt: l.CreatedAt,
|
||||
UpdatedAt: l.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// LeadResponse 线索响应结构
|
||||
type LeadResponse struct {
|
||||
ID string `json:"id"`
|
||||
LeadNo string `json:"lead_no"`
|
||||
CompanyName string `json:"company_name"`
|
||||
ContactName string `json:"contact_name"`
|
||||
ContactPhone string `json:"contact_phone"`
|
||||
ContactEmail string `json:"contact_email"`
|
||||
Requirement string `json:"requirement"`
|
||||
ExtraInfo string `json:"extra_info"`
|
||||
Status string `json:"status"`
|
||||
FollowUpDate *time.Time `json:"follow_up_date"`
|
||||
FollowUpRecord string `json:"follow_up_record"`
|
||||
InternalNote string `json:"internal_note"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// LeadCreateRequest 创建线索请求
|
||||
type LeadCreateRequest struct {
|
||||
CompanyName string `json:"company_name" binding:"required,min=1,max=200"`
|
||||
ContactName string `json:"contact_name" binding:"required,min=1,max=100"`
|
||||
ContactPhone string `json:"contact_phone" binding:"required,min=1,max=20"`
|
||||
ContactEmail string `json:"contact_email" binding:"omitempty,email,max=100"`
|
||||
Requirement string `json:"requirement" binding:"required,min=1"`
|
||||
ExtraInfo string `json:"extra_info"`
|
||||
}
|
||||
|
||||
// LeadUpdateRequest 更新线索请求
|
||||
type LeadUpdateRequest struct {
|
||||
CompanyName string `json:"company_name" binding:"omitempty,min=1,max=200"`
|
||||
ContactName string `json:"contact_name" binding:"omitempty,min=1,max=100"`
|
||||
ContactPhone string `json:"contact_phone" binding:"omitempty,min=1,max=20"`
|
||||
ContactEmail string `json:"contact_email" binding:"omitempty,email,max=100"`
|
||||
Requirement string `json:"requirement" binding:"omitempty,min=1"`
|
||||
ExtraInfo string `json:"extra_info"`
|
||||
Status string `json:"status" binding:"omitempty,oneof=pending contacted converted invalid"`
|
||||
FollowUpDate *time.Time `json:"follow_up_date"`
|
||||
FollowUpRecord string `json:"follow_up_record"`
|
||||
InternalNote string `json:"internal_note"`
|
||||
}
|
||||
|
||||
// LeadListRequest 线索列表请求
|
||||
type LeadListRequest struct {
|
||||
Page int `form:"page" binding:"omitempty,min=1"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100"`
|
||||
Search string `form:"search"` // 关键词检索
|
||||
Status string `form:"status"` // 状态筛选
|
||||
}
|
||||
|
||||
// LeadListResponse 线索列表响应
|
||||
type LeadListResponse struct {
|
||||
Leads []*LeadResponse `json:"leads"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"license-manager/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LeadRepository 线索仓储接口
|
||||
type LeadRepository interface {
|
||||
Create(lead *models.Lead) error
|
||||
Update(lead *models.Lead) error
|
||||
Delete(id string) error
|
||||
GetByID(id string) (*models.Lead, error)
|
||||
GetList(ctx context.Context, req *models.LeadListRequest) ([]*models.Lead, int64, error)
|
||||
}
|
||||
|
||||
type leadRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewLeadRepository 创建线索仓储
|
||||
func NewLeadRepository(db *gorm.DB) LeadRepository {
|
||||
return &leadRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *leadRepository) Create(lead *models.Lead) error {
|
||||
return r.db.Create(lead).Error
|
||||
}
|
||||
|
||||
func (r *leadRepository) Update(lead *models.Lead) error {
|
||||
return r.db.Save(lead).Error
|
||||
}
|
||||
|
||||
func (r *leadRepository) Delete(id string) error {
|
||||
return r.db.Unscoped().Where("id = ?", id).Delete(&models.Lead{}).Error
|
||||
}
|
||||
|
||||
func (r *leadRepository) GetByID(id string) (*models.Lead, error) {
|
||||
var lead models.Lead
|
||||
err := r.db.Where("id = ?", id).First(&lead).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lead, nil
|
||||
}
|
||||
|
||||
func (r *leadRepository) GetList(ctx context.Context, req *models.LeadListRequest) ([]*models.Lead, int64, error) {
|
||||
var leads []*models.Lead
|
||||
var total int64
|
||||
|
||||
// 默认值
|
||||
page := 1
|
||||
pageSize := 10
|
||||
search := ""
|
||||
status := ""
|
||||
|
||||
if req != nil {
|
||||
if req.Page > 0 {
|
||||
page = req.Page
|
||||
}
|
||||
if req.PageSize > 0 {
|
||||
pageSize = req.PageSize
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
search = req.Search
|
||||
status = req.Status
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&models.Lead{})
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
like := "%" + search + "%"
|
||||
query = query.Where("(company_name LIKE ? OR contact_name LIKE ? OR contact_phone LIKE ?)", like, like, like)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取列表,按创建时间降序
|
||||
err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&leads).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return leads, total, nil
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"license-manager/internal/models"
|
||||
"license-manager/internal/repository"
|
||||
pkgcontext "license-manager/pkg/context"
|
||||
"license-manager/pkg/i18n"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LeadService 线索服务接口
|
||||
type LeadService interface {
|
||||
CreateLead(ctx context.Context, req *models.LeadCreateRequest) (*models.Lead, error)
|
||||
UpdateLead(ctx context.Context, id string, req *models.LeadUpdateRequest) (*models.Lead, error)
|
||||
DeleteLead(ctx context.Context, id string) error
|
||||
GetLead(ctx context.Context, id string) (*models.Lead, error)
|
||||
GetLeadList(ctx context.Context, req *models.LeadListRequest) (*models.LeadListResponse, error)
|
||||
}
|
||||
|
||||
type leadService struct {
|
||||
repo repository.LeadRepository
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewLeadService 创建线索服务
|
||||
func NewLeadService(repo repository.LeadRepository, db *gorm.DB) LeadService {
|
||||
return &leadService{
|
||||
repo: repo,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *leadService) CreateLead(ctx context.Context, req *models.LeadCreateRequest) (*models.Lead, error) {
|
||||
lang := pkgcontext.GetLanguageFromContext(ctx)
|
||||
|
||||
lead := &models.Lead{
|
||||
CompanyName: req.CompanyName,
|
||||
ContactName: req.ContactName,
|
||||
ContactPhone: req.ContactPhone,
|
||||
ContactEmail: req.ContactEmail,
|
||||
Requirement: req.Requirement,
|
||||
ExtraInfo: req.ExtraInfo,
|
||||
Status: string(models.LeadStatusPending),
|
||||
}
|
||||
|
||||
if err := s.repo.Create(lead); err != nil {
|
||||
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
||||
}
|
||||
|
||||
return lead, nil
|
||||
}
|
||||
|
||||
func (s *leadService) UpdateLead(ctx context.Context, id string, req *models.LeadUpdateRequest) (*models.Lead, error) {
|
||||
lang := pkgcontext.GetLanguageFromContext(ctx)
|
||||
|
||||
lead, err := s.repo.GetByID(id)
|
||||
if err != nil {
|
||||
return nil, i18n.NewI18nError("900002", lang)
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.CompanyName != "" {
|
||||
lead.CompanyName = req.CompanyName
|
||||
}
|
||||
if req.ContactName != "" {
|
||||
lead.ContactName = req.ContactName
|
||||
}
|
||||
if req.ContactPhone != "" {
|
||||
lead.ContactPhone = req.ContactPhone
|
||||
}
|
||||
if req.ContactEmail != "" {
|
||||
lead.ContactEmail = req.ContactEmail
|
||||
}
|
||||
if req.Requirement != "" {
|
||||
lead.Requirement = req.Requirement
|
||||
}
|
||||
if req.ExtraInfo != "" {
|
||||
lead.ExtraInfo = req.ExtraInfo
|
||||
}
|
||||
if req.Status != "" {
|
||||
lead.Status = req.Status
|
||||
}
|
||||
lead.FollowUpDate = req.FollowUpDate
|
||||
if req.FollowUpRecord != "" {
|
||||
lead.FollowUpRecord = req.FollowUpRecord
|
||||
}
|
||||
if req.InternalNote != "" {
|
||||
lead.InternalNote = req.InternalNote
|
||||
}
|
||||
lead.UpdatedAt = time.Now()
|
||||
|
||||
if err := s.repo.Update(lead); err != nil {
|
||||
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
||||
}
|
||||
|
||||
return lead, nil
|
||||
}
|
||||
|
||||
func (s *leadService) DeleteLead(ctx context.Context, id string) error {
|
||||
lang := pkgcontext.GetLanguageFromContext(ctx)
|
||||
|
||||
// 检查是否存在
|
||||
_, err := s.repo.GetByID(id)
|
||||
if err != nil {
|
||||
return i18n.NewI18nError("900002", lang)
|
||||
}
|
||||
|
||||
if err := s.repo.Delete(id); err != nil {
|
||||
return i18n.NewI18nError("900004", lang, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *leadService) GetLead(ctx context.Context, id string) (*models.Lead, error) {
|
||||
lang := pkgcontext.GetLanguageFromContext(ctx)
|
||||
|
||||
lead, err := s.repo.GetByID(id)
|
||||
if err != nil {
|
||||
return nil, i18n.NewI18nError("900002", lang)
|
||||
}
|
||||
|
||||
return lead, nil
|
||||
}
|
||||
|
||||
func (s *leadService) GetLeadList(ctx context.Context, req *models.LeadListRequest) (*models.LeadListResponse, error) {
|
||||
leads, total, err := s.repo.GetList(ctx, req)
|
||||
if err != nil {
|
||||
lang := pkgcontext.GetLanguageFromContext(ctx)
|
||||
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
||||
}
|
||||
|
||||
// 转换为响应
|
||||
leadResponses := make([]*models.LeadResponse, len(leads))
|
||||
for i, lead := range leads {
|
||||
leadResponses[i] = lead.ToResponse()
|
||||
}
|
||||
|
||||
page := 1
|
||||
pageSize := 10
|
||||
if req != nil {
|
||||
if req.Page > 0 {
|
||||
page = req.Page
|
||||
}
|
||||
if req.PageSize > 0 {
|
||||
pageSize = req.PageSize
|
||||
}
|
||||
}
|
||||
|
||||
return &models.LeadListResponse{
|
||||
Leads: leadResponses,
|
||||
TotalCount: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 企业线索表
|
||||
CREATE TABLE leads (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
lead_no VARCHAR(50) NOT NULL COMMENT '线索编号,系统生成',
|
||||
company_name VARCHAR(200) NOT NULL COMMENT '企业/机构名称',
|
||||
contact_name VARCHAR(100) NOT NULL COMMENT '联系人',
|
||||
contact_phone VARCHAR(20) NOT NULL COMMENT '联系电话',
|
||||
contact_email VARCHAR(100) COMMENT '邮箱地址',
|
||||
requirement TEXT NOT NULL COMMENT '需求描述',
|
||||
extra_info TEXT COMMENT '补充信息',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending-待联系, contacted-已联系, converted-已成交, invalid-已失效',
|
||||
follow_up_date DATETIME COMMENT '跟进日期',
|
||||
follow_up_record TEXT COMMENT '跟进记录',
|
||||
internal_note TEXT COMMENT '内部备注',
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
|
||||
INDEX idx_leads_status (status),
|
||||
INDEX idx_leads_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业线索表';
|
||||
Reference in new issue
Block a user