bianjunhui 7d930d876f 批发 3 giorni fa
..
src 7d930d876f 批发 3 giorni fa
README.md 7d930d876f 批发 3 giorni fa

README.md

Longyi Wholesale Module

一个用于 Bagisto 的批发询盘(Wholesale)模块,对应 asteriahair.com/wholesale 的询盘功能。前端用户通过 API 提交批发询盘,后台管理员在 Sales 菜单下查看和管理这些询盘记录。

功能特性

  • ✅ 前台公开 API 提交询盘(无需登录)
  • ✅ 询盘字段校验(姓名、邮箱必填)
  • ✅ 中英文提示文案
  • ✅ 后台 Sales 菜单新增「批发询盘」目录
  • ✅ 后台询盘列表(DataGrid:搜索 / 排序 / 分页)
  • ✅ 基于角色的权限控制(ACL)
  • ✅ 历史数据导入命令(php artisan wholesale:import-message)

目录结构

Longyi/Wholesale/
├── src/
│   ├── Config/
│   │   ├── admin-menu.php          # 后台菜单(sales.wholesale)
│   │   └── acl.php                 # 权限配置
│   ├── Console/
│   │   └── Commands/
│   │       └── ImportMessageData.php # 历史数据导入命令
│   ├── Database/
│   │   ├── Migrations/             # message 表迁移
│   │   └── message.sql             # 历史数据 SQL(Navicat 导出)
│   ├── DataGrids/
│   │   └── MessageDataGrid.php     # 后台列表
│   ├── Helpers/
│   │   └── ApiResponse.php         # 统一 API 返回格式
│   ├── Http/Controllers/
│   │   ├── Admin/WholesaleController.php   # 后台控制器
│   │   └── WholesaleController.php         # 前台 API 控制器
│   ├── Models/
│   │   └── Message.php             # 询盘模型
│   ├── Providers/
│   │   └── WholesaleServiceProvider.php
│   ├── Resources/
│   │   ├── lang/{en,zh_CN}/app.php # 中英文文案
│   │   └── views/admin/index.blade.php
│   └── Routes/
│       ├── admin-routes.php        # 后台路由
│       └── shop-routes.php         # 前台 API 路由

安装步骤

1. 确认插件已注册

bootstrap/providers.php 中已添加:

Longyi\Wholesale\Providers\WholesaleServiceProvider::class,

composer.json 的 autoload.psr-4 中已添加命名空间映射:

"Longyi\\Wholesale\\": "packages/Longyi/Wholesale/src/"

2. 生成自动加载

composer dump-autoload

3. 运行数据库迁移

php artisan migrate

这将创建 message 表,用于存储批发询盘记录。

4. 清理缓存

php artisan config:clear
php artisan route:clear
php artisan view:clear

5. 配置后台角色权限

首次使用需要到后台 设置 → 角色 中,为管理员角色勾选以下权限,否则侧边栏不显示「批发询盘」菜单:

  • sales.wholesale —— 查看批发询盘列表

数据表结构

message 表:

字段 类型 说明
message_id int unsigned PK, auto increment 主键
name varchar(200) 姓名
email varchar(50) 邮箱
number varchar(300) 联系方式
message varchar(600) 留言内容
country varchar(50) 国家
created_at timestamp 提交时间
business_name varchar(300) 公司名称
business_role varchar(50) 公司角色

注意:表的主键为 message_id(非 id),且只有 created_at 字段(无 updated_at),模型已做相应适配($timestamps = true,CREATED_AT = 'created_at',UPDATED_AT = null)。

历史数据导入

模块内置了一条 artisan 命令,用于将历史询盘数据(src/Database/message.sql,Navicat 导出的 MySQL 文件)批量导入到现有的 message 表。

命令

php artisan wholesale:import-message

参数

参数 说明 默认值
--file SQL 文件路径 包内 src/Database/message.sql
--batch 每次批量插入的行数 500

示例

# 使用默认文件、默认批大小导入
php artisan wholesale:import-message

# 指定 SQL 文件路径
php artisan wholesale:import-message --file=/path/to/message.sql

# 调整批量插入大小
php artisan wholesale:import-message --batch=1000

行为说明

  1. 清空现有表:导入前会先 TRUNCATE 现有 message 表,再导入 SQL 文件中的数据(全量替换)。
  2. 只解析 INSERT 语句:跳过 DROP TABLE、CREATE TABLE、注释等语句,不会重建表结构。
  3. 字段映射:SQL 文件中的 Email(大写)字段会正确映射到现有表的 email(小写)列。
  4. 类型处理:
    • message_id 转为整数,保留原始主键值;
    • NULL 和空字符串转为 null;
    • 支持 \'、\r\n、\t 等 SQL 转义字符。
  5. 容错:列数不匹配的异常行会被跳过并输出告警,不中断整体导入。

前端 API

提交批发询盘

请求:

POST /api/wholesale
Content-Type: application/json
{
    "name": "John Doe",
    "email": "john@example.com",
    "number": "+1 234 567 8900",
    "message": "I'm interested in your wholesale products.",
    "country": "United States",
    "business_name": "ABC Trading Co.",
    "business_role": "Distributor"
}

字段说明:

字段 必填 类型 长度限制
name ✅ string 200
email ✅ email 50
number ❌ string 300
message ❌ string 600
country ❌ string 50
business_name ❌ string 300
business_role ❌ string 50

成功响应:

{
    "success": true,
    "message": "Your enquiry has been submitted successfully.",
    "data": {
        "id": 1
    }
}

失败响应:

{
    "success": false,
    "message": "Error message",
    "data": []
}

前台 API 的所有提示信息统一使用英文,不随当前语言(locale)变化。

防恶意提交

接口内置了双重防护,防止恶意重复提交:

  1. IP 限流:
    • 同一 IP 在 60 秒内最多提交 1 次;
    • 同一 IP 在 24 小时内最多提交 5 次;
    • 超出任一限制返回 429。
{
    "success": false,
    "message": "Too many requests. Please try again later.",
    "data": []
}
  1. 重复提交检测:同一 IP + 同一邮箱在 60 秒内只允许提交一次,重复提交返回 429。
{
    "success": false,
    "message": "You have already submitted an enquiry. Please try again later.",
    "data": []
}

阈值与窗口期通过 WholesaleController 中的常量 MAX_ATTEMPTS、DECAY_SECONDS、DAILY_MAX_ATTEMPTS、DAILY_DECAY_SECONDS、DUPLICATE_SECONDS 配置。

统一返回格式

本模块的 API 统一使用 Longyi\Wholesale\Helpers\ApiResponse 返回,格式与 RewardPoints 模块一致:

{
    "success": true,
    "message": "Success",
    "data": {}
}

支持的静态方法:

方法 说明
ApiResponse::success($data, $message, $code) 成功响应(默认 200)
ApiResponse::paginated($data, $paginator, $message, $code) 带分页的成功响应
ApiResponse::error($message, $data, $code) 错误响应(默认 400)
ApiResponse::unauthorized($message) 未授权响应(401)
ApiResponse::validationError($message, $errors) 验证错误响应(422)

后台管理

访问入口

后台侧边栏 Sales → 批发询盘(Wholesale),路由:

GET  /admin/sales/wholesale       # 询盘列表

列表功能

  • 列表展示:ID、姓名、邮箱、联系方式、公司名称、公司角色、国家、留言内容、提交时间
  • 支持按姓名 / 邮箱 / 联系方式 / 公司名称 / 留言内容搜索
  • 支持按 ID、时间等排序、分页

JavaScript / Fetch 使用示例

async function submitWholesaleEnquiry(formData) {
    try {
        const response = await fetch('/api/wholesale', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content,
            },
            body: JSON.stringify({
                name: formData.name,
                email: formData.email,
                number: formData.number,
                message: formData.message,
                country: formData.country,
                business_name: formData.business_name,
                business_role: formData.business_role,
            }),
        });

        const data = await response.json();

        if (data.success) {
            console.log('提交成功:', data.data);
            return data.data;
        } else {
            console.error('提交失败:', data.message);
        }
    } catch (error) {
        console.error('错误:', error);
    }
}

依赖要求

  • PHP >= 8.2
  • Laravel >= 11.0
  • Bagisto 2.x

许可证

MIT License

作者

Longyi Team dev@longyi.com