mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
feat(mcp): 为内置工具添加分类方法并优化订单列表搜索逻辑
为所有 MCP 内置工具实现 category() 分类方法,支持管理后台按业务分组展示;优化订单列表关键词搜索逻辑,使用闭包显式 OR 条件匹配订单号和商品标题,避免管道字段静默丢弃问题;新增优惠券库存调整原子方法,提升并发安全性;同步更新相关文档和注释,完善分类展示规则说明。
This commit is contained in:
parent
1415e1d261
commit
3b5bd37bfb
@ -14,7 +14,7 @@ CRMEB v8 电商系统,三端一体:
|
||||
|
||||
| 区域 | 目录 | 技术栈 |
|
||||
| --- | --- | --- |
|
||||
| 后端 | `backend/` | PHP 8.2-8.4 + ThinkPHP 8 + MySQL 8 + Redis,多应用入口 `adminapi` / `api` / `outapi` |
|
||||
| 后端 | `backend/` | PHP 8.2-8.4 + ThinkPHP 8 + MySQL 8 + Redis,多应用入口 `adminapi` / `api` |
|
||||
| 管理后台 | `frontend/admin/` | Vue 3.5 + Element Plus 2.9 + Pinia 2 + vue-router 4 + Vite 5(纯 JS) |
|
||||
| 移动端 | `frontend/uni-app/` | uni-app 官方 CLI(Vite 5)+ Vue 3 + Pinia + Sass |
|
||||
|
||||
@ -79,8 +79,8 @@ CRMEB v8 电商系统,三端一体:
|
||||
| `crmeb-admin-dev` | 管理后台页面/组件/接口/路由/插件页面开发 |
|
||||
| `crmeb-admin-ui` | 管理端 UI 规范:设计令牌、布局、页面骨架、组件与交互、深色模式、评审清单 |
|
||||
| `crmeb-uniapp-dev` | 移动端 `frontend/uni-app` 页面、API 封装、条件编译、分包、动态渲染 |
|
||||
| `crmeb-order-dev` | 订单中台全生命周期开发手册:交易模式、产品类型订单链路、订单事件、状态流转、售后退款 |
|
||||
| `crmeb-dev` | 二开总览与流程导航(含 `references/` 分类子文档,如创建插件/驱动/产品类型/用户端页面) |
|
||||
| `project-standard-bootstrap` | 项目统一工程规范引导:审计/初始化/修复 `.crmeb` 规则骨架、工具入口、目录契约与文档体系(模板类技能,仅按需用于规范化治理) | 「项目规范化」「工程规范审计」「新项目初始化」 |
|
||||
|
||||
## 子智能体索引(`.crmeb/agents/`)
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
| --- | --- | --- |
|
||||
| 项目约定与任务路由 | [AGENTS.md](AGENTS.md) | 决定本次任务需要读取哪些规则和技能 |
|
||||
| 开发规则 | [规则索引](AGENTS.md#规则索引crmebrules) | 架构、代码、数据库、安全、接口等强制约束 |
|
||||
| 操作手册 | [技能索引](AGENTS.md#技能索引crmebskills) | 7 个专项技能,详细教程在各自 `references/` 中 |
|
||||
| 操作手册 | [技能索引](AGENTS.md#技能索引crmebskills) | 8 个专项技能,详细教程在各自 `references/` 中 |
|
||||
| 专项角色 | [角色索引](AGENTS.md#子智能体索引crmebagents) | 架构检查、模块构建、路由校验的执行流程 |
|
||||
| 各端补充约定 | [管理端](scopes/admin.md)、[移动端](scopes/uni-app.md) | 只在对应端任务中读取 |
|
||||
| 项目说明 | [文档索引](../help/docs/文档索引.md) | 项目、架构、模块与运行说明,不代替当前规则或执行授权 |
|
||||
@ -20,7 +20,7 @@
|
||||
AGENTS.md AI 开发入口与统一规则索引
|
||||
README.md 人工查阅入口
|
||||
rules/ 当前开发规则
|
||||
skills/ 7 个技能及按需加载的参考手册
|
||||
skills/ 8 个技能及按需加载的参考手册
|
||||
agents/ 3 个专项角色说明
|
||||
scopes/ 管理端、移动端补充约定
|
||||
```
|
||||
|
||||
@ -99,18 +99,11 @@ v8.1.xu 采用三层架构,事件监听器分布在核心层和模块层:
|
||||
|------|---------------|------|
|
||||
| product/normal | [event.php](../../backend/modules/product/normal/event.php) | 实物商品订单生命周期 + 售后 + 扩展数据 |
|
||||
| product/virtual | [event.php](../../backend/modules/product/virtual/event.php) | 虚拟商品订单生命周期 + 售后 |
|
||||
| marketing/recharge | [event.php](../../backend/modules/marketing/recharge/event.php) | 充值营销功能 + 服务代理 |
|
||||
| trade/integral | [event.php](../../backend/modules/trade/integral/event.php) | 积分商城订单 + 退款 |
|
||||
| trade/combination | [event.php](../../backend/modules/trade/combination/event.php) | 拼团生命周期 + 活动数据代理 |
|
||||
| trade/bargain | [event.php](../../backend/modules/trade/bargain/event.php) | 砍价生命周期 + 活动数据代理 |
|
||||
| trade/seckill | [event.php](../../backend/modules/trade/seckill/event.php) | 秒杀活动状态查询 |
|
||||
| distribution | [event.php](../../backend/modules/distribution/event.php) | 分销佣金 + 用户扩展 + 服务代理 |
|
||||
| marketing/lottery | [event.php](../../backend/modules/marketing/lottery/event.php) | 抽奖次数缓存 + 营销代理 |
|
||||
| marketing/signin | [event.php](../../backend/modules/marketing/signin/event.php) | 签到提醒 + 营销代理 |
|
||||
| marketing/newuser | [event.php](../../backend/modules/marketing/newuser/event.php) | 新人注册奖励 |
|
||||
| channel/wechat_mp | [event.php](../../backend/modules/channel/wechat_mp/event.php) | 公众号渠道 + 物流跟踪 + 粉丝同步 |
|
||||
| channel/wechat_mini | [event.php](../../backend/modules/channel/wechat_mini/event.php) | 小程序渠道 + 直播 API + 小程序码 |
|
||||
|
||||
> 其余已安装模块(如 `aiagent`)当前 `event.php` 为空监听,未注册事件时不入上表;新增监听后同步登记。
|
||||
|
||||
---
|
||||
|
||||
## 四、核心事件清单
|
||||
|
||||
@ -16,6 +16,7 @@ description: 前端强制红线、接口契约变更同步清单与管理端 UI
|
||||
6. **事件总线**:`@/utils/bus` 的 `off` 必须传与 `on` 相同的具名函数引用。
|
||||
7. **编辑纪律**:同一文件多处修改必须逐处顺序编辑,禁止同批并行编辑同一文件(会互相覆盖静默丢失改动)。
|
||||
8. **版权头**:新写文件不添加 CRMEB 版权头。
|
||||
9. **SchemaPage 组件是跨系统契约,禁止随手改写**:`frontend/admin/src/components/SchemaPage/components/` 下的组件(尤其 SchemaForm 注册的 form-create 自定义组件 SchemaEditor / SchemaCoupon / SchemaCheckboxGroup)被后端 `backend/crmeb/utils/FormBuilder.php` 规则生成、schema 驱动页面与已编译插件 UMD(内含独立打包副本)多方引用,为单一业务需求改写通用组件即违规。能力扩展必须向后兼容:新增可选 props、默认行为与既有 props 语义不变、提交数据结构不变;同一提交同步 FormBuilder 对应方法的 props 文档,并回归全部既有调用方。业务个性化需求优先在业务侧封装组件或经后端规则 props 表达,确需改契约组件时先在方案中列明影响面。
|
||||
|
||||
## 二、移动端红线(frontend/uni-app)
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ description: 二开改动归档升级程序包规范——首个正式版本发
|
||||
|
||||
## 3. 版本增量脚本(首发后包的骨架,必做)
|
||||
|
||||
- 位置:`backend/upgrade/versions/` 目录下,文件名按版本号 `vX.Y.Z.php`;返回数组结构以 [v6.0.2.php](../../backend/upgrade/versions/v6.0.2.php) 为模板:`version` / `code` / `description` / `update_sql` / `update_file`。
|
||||
- 位置:`backend/upgrade/versions/` 目录下,文件名按版本号 `vX.Y.Z.php`;脚本返回数组,键为 `version` / `code` / `description` / `update_sql` / `update_file`。
|
||||
- 版本号:`code = major*100 + minor*10 + patch`(如 v8.0.1 → 801),与文件名严格一致,**只增不减**。
|
||||
- `update_sql` 条目:`type` 语义(1 建表、2 删表、3 加字段、4 改字段、5 删字段、6 加数据、7 改数据、8 删数据、-1 直接执行);SQL 中表名一律用 `` `@table` `` 占位符,同时在 `table` 字段给出去前缀表名;**禁止硬编码 `eb_` 前缀**。
|
||||
- **SQL 必须幂等**:加字段/索引前先判存在;插数据用 `REPLACE INTO` 或先查后插;UPDATE/DELETE 必须带明确 WHERE。幂等的原因:执行方按 `upgrade_log` 跳过已执行版本,但同一脚本在别的站点可能因手工二开已部分落库。
|
||||
|
||||
@ -58,7 +58,7 @@ description: 目录与文档规范(全仓库唯一真源)- 三类知识目
|
||||
**2. Git 合并冲突(文档并发修改)**
|
||||
|
||||
- Markdown 冲突禁止机械整取(`--ours` / `--theirs` 一键覆盖),须按语义合并双方改动。
|
||||
- 索引表(`.crmeb/AGENTS.md`、`tests/README.md` 等)冲突必须**合并双方新增行**后去重,禁止丢弃任何一方的登记。
|
||||
- 索引表(`.crmeb/AGENTS.md`、`help/docs/文档索引.md` 等)冲突必须**合并双方新增行**后去重,禁止丢弃任何一方的登记。
|
||||
- 冲突解决后必须全量重跑 `php think docs:check`,error 清零方可提交(同第五节)。
|
||||
|
||||
**3. 本地副本与缓存冲突**
|
||||
|
||||
@ -13,9 +13,8 @@
|
||||
已有实现参考:
|
||||
- [backend/modules/product/normal](../../../../../backend/modules/product/normal) — 实物商品
|
||||
- [backend/modules/product/virtual](../../../../../backend/modules/product/virtual) — 虚拟商品
|
||||
- [backend/modules/product/cardkey](../../../../../backend/modules/product/cardkey) — 卡密商品
|
||||
|
||||
> `coupon`(卡券)产品类型尚未实现,目录不存在。
|
||||
> `cardkey`(卡密)与 `coupon`(卡券)产品类型尚未实现,目录不存在。
|
||||
|
||||
## 二、前置约束(必须遵守)
|
||||
|
||||
|
||||
@ -546,7 +546,7 @@ event('ProductDetailExtend', $eventData);
|
||||
|---------|-----------|-----------------|---------------------|
|
||||
| normal | [normal/.../ProductDetailExtendListener.php](../../../../../backend/modules/product/normal/listener/ProductDetailExtendListener.php) | custom_form/params_list/label_list/protection_list/type_info/fulfillment_preview | spec(matrix) + fulfillment + store + presale |
|
||||
| virtual | [virtual/.../ProductDetailExtendListener.php](../../../../../backend/modules/product/virtual/listener/ProductDetailExtendListener.php) | custom_form/params_list/label_list/protection_list/type_info/fulfillment_preview | spec(matrix) + fulfillment |
|
||||
| cardkey | [cardkey/.../ProductDetailExtendListener.php](../../../../../backend/modules/product/cardkey/listener/ProductDetailExtendListener.php) | custom_form/params_list/label_list/protection_list/type_info/fulfillment_preview | spec(chips) + fulfillment(自动发放说明) |
|
||||
| cardkey | `modules/product/cardkey/listener/ProductDetailExtendListener.php`(未随当前版本提供) | custom_form/params_list/label_list/protection_list/type_info/fulfillment_preview | spec(chips) + fulfillment(自动发放说明) |
|
||||
|
||||
### 7.3 监听器实现规范
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ SELECT = 0
|
||||
|
||||
`backend/public/install.lock` 存在时跳过安装向导(删除后重新进入安装页)。
|
||||
|
||||
> 全新安装由安装向导导入 `backend/public/install/crmeb_table.sql` 和 `backend/public/install/crmeb_data.sql`,再逐项执行必需模块、默认模块和默认驱动 Installer;不需要手动补跑历史升级 SQL。
|
||||
> 全新安装由安装向导导入 `backend/public/install/crmeb_table.sql` 和 `backend/public/install/data/` 基础数据,再逐项执行必需模块、默认模块和默认驱动 Installer;不需要手动补跑历史升级 SQL。
|
||||
|
||||
## 四、数据库 / Redis 连接信息速查
|
||||
|
||||
@ -84,7 +84,7 @@ SELECT = 0
|
||||
|
||||
## 五、首版数据库变更规范(二开必备)
|
||||
|
||||
- 核心表结构直接同步到 `backend/public/install/crmeb_table.sql`,核心初始数据同步到 `backend/public/install/crmeb_data.sql`;模块自有结构和数据由对应 `Installer.php` 管理
|
||||
- 核心表结构直接同步到 `backend/public/install/crmeb_table.sql`,核心初始数据同步到 `backend/public/install/data/`;模块自有结构和数据由对应 `Installer.php` 管理
|
||||
- 当前首版不新增版本升级脚本;未来明确进入发布后维护阶段时,再建立增量升级链
|
||||
- SQL 文件必须保存为 **UTF-8 无 BOM**;导入时连接必须指定 `utf8mb4`,否则中文注释(表/列注释)会存成乱码 `?`
|
||||
- 手动验证 SQL 时的导入命令:
|
||||
|
||||
@ -557,7 +557,7 @@ Notice::to($uid)->sendMsg('my_new_event', '我的新通知', [
|
||||
|
||||
### 10.2 实际 payload 与默认数据核对
|
||||
|
||||
`variable` 仍用 `{key}描述` 格式供后台插入,但 key 必须与监听器 `sendMsg()` 的实际数组一致;模板 `{key}` 和微信 `key_list` 不得引用不存在的业务字段。`to(0)` 为平台 type=2,`to($uid)` 为用户 type=1,同 mark 双侧分别声明。动态发货两个 mark 要显式登记。短信旧编号只属于一号通(`sms_driver_key=yihaotong`),微信 `tempkey` 是短编号,实际商户 `tempid` 留空并由后台配置。重复启用只补空字段,不覆盖商户修改。同键空归属记录由所属 Installer 条件认领并补齐空字段,不重置已有模板、状态或商户凭据;已有其他插件归属的同键记录仍拒绝抢占。通知种子回归脚本属于本地测试资产,当前可用入口必须登记在根 `tests/README.md` 后再作为验收依据。
|
||||
`variable` 仍用 `{key}描述` 格式供后台插入,但 key 必须与监听器 `sendMsg()` 的实际数组一致;模板 `{key}` 和微信 `key_list` 不得引用不存在的业务字段。`to(0)` 为平台 type=2,`to($uid)` 为用户 type=1,同 mark 双侧分别声明。动态发货两个 mark 要显式登记。短信旧编号只属于一号通(`sms_driver_key=yihaotong`),微信 `tempkey` 是短编号,实际商户 `tempid` 留空并由后台配置。重复启用只补空字段,不覆盖商户修改。同键空归属记录由所属 Installer 条件认领并补齐空字段,不重置已有模板、状态或商户凭据;已有其他插件归属的同键记录仍拒绝抢占。通知种子回归脚本属于本地测试资产(仓库根 `tests` 目录为本地登记处,可缺失),登记后才能作为验收依据。
|
||||
|
||||
### 10.3 后台配置模板
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
alwaysApply: false
|
||||
description: 分销返佣业务参考 - 返佣层级结构与计算规则说明;当前返佣链路由 OrderPaySuccess 等事件与 distribution 模块监听器接入,历史流程仅供迁移定位
|
||||
---
|
||||
> **⚠️ 版本说明**:当前返佣链路已中台化——订单支付成功由 `app/services/order/OrderSuccessServices.php` 触发 `OrderPaySuccess` 事件(基于 `eb_order_center` 中台表),`modules/distribution/listener/` 下监听器负责订单快照、佣金发放和用户扩展。本文保留的旧流程只供迁移定位,当前字段与行为以代码为准。
|
||||
> **⚠️ 版本说明**:当前返佣链路已中台化——订单支付成功由 `app/services/order/OrderSuccessServices.php` 触发 `OrderPaySuccess` 事件(基于 `eb_order_center` 中台表);`modules/distribution/listener/` 下监听器负责订单快照、佣金发放和用户扩展,但 **distribution 模块未随当前版本提供**,表中相关路径为规划结构,实际以自身二开实现为准。本文保留的旧流程只供迁移定位,当前字段与行为以代码为准。
|
||||
|
||||
## 二、核心架构
|
||||
|
||||
@ -27,9 +27,9 @@ description: 分销返佣业务参考 - 返佣层级结构与计算规则说明
|
||||
| 服务文件 | 职责 | 关键方法 |
|
||||
|---------|------|---------|
|
||||
| [UserBrokerageServices.php](../../../../../backend/app/services/user/UserBrokerageServices.php) | 用户佣金管理 | `brokerageIncome()`、`orderRefundBrokerageBack()` |
|
||||
| [OrderCreateSnapshotListener.php](../../../../../backend/modules/distribution/listener/OrderCreateSnapshotListener.php) | 订单创建后写入分销快照 | `OrderCreateAfter` |
|
||||
| [OrderPaidListener.php](../../../../../backend/modules/distribution/listener/OrderPaidListener.php) | 支付成功后的分销处理 | `OrderPaySuccess` |
|
||||
| [OrderSpreadBrokerageListener.php](../../../../../backend/modules/distribution/listener/OrderSpreadBrokerageListener.php) | 返佣规则与佣金分配 | `OrderSpreadBrokerage` |
|
||||
| `modules/distribution/listener/OrderCreateSnapshotListener.php`(未随版本提供) | 订单创建后写入分销快照 | `OrderCreateAfter` |
|
||||
| `modules/distribution/listener/OrderPaidListener.php`(未随版本提供) | 支付成功后的分销处理 | `OrderPaySuccess` |
|
||||
| `modules/distribution/listener/OrderSpreadBrokerageListener.php`(未随版本提供) | 返佣规则与佣金分配 | `OrderSpreadBrokerage` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ public function bcDec($key, string $incField, string $inc, string $keyField = nu
|
||||
| 管理员增加积分 | setUserInfo() | [UserServices.php](../../../../../backend/app/services/user/UserServices.php) | system_add |
|
||||
| 后台调整积分 | setUserInfo() | [UserServices.php](../../../../../backend/app/services/user/UserServices.php) | system_add |
|
||||
| 下单赠送积分 | paySuccess() | [OrderSuccessServices.php](../../../../../backend/app/services/order/OrderSuccessServices.php)(约 L106 触发 `OrderPaySuccess` 事件) | gain/product_gain |
|
||||
| 抽奖中奖 | sendPrize() | [LuckLotteryRecordServices.php](../../../../../backend/modules/marketing/lottery/service/LuckLotteryRecordServices.php)(抽奖模块已迁移至 modules/marketing/lottery/,`app/services/activity/` 下无 lottery/) | lottery_add |
|
||||
| 抽奖中奖 | sendPrize() | `modules/marketing/lottery/service/LuckLotteryRecordServices.php`(抽奖模块未随当前版本提供,`app/services/activity/` 下无 lottery/) | lottery_add |
|
||||
| 订单退款返还 | execute() | [RefundExecutorServices.php](../../../../../backend/app/services/order/RefundExecutorServices.php) | pay_product_integral_back |
|
||||
|
||||
### 4.2 积分减少场景
|
||||
|
||||
@ -113,7 +113,7 @@ $token = $jwtAuth->getToken($uid, 'user'); // 仅签发(
|
||||
$jwtAuth->verifyToken(); // 验签(leeway 60 秒)
|
||||
```
|
||||
|
||||
真实先例:登录签发 `backend/app/services/user/LoginServices.php:58`;校验基础 `backend/crmeb/auth/AuthTokenVerifier.php:57`;开放接口鉴权 `backend/modules/service/outapi/service/OutAccountServices.php:95`。
|
||||
真实先例:登录签发 `backend/app/services/user/LoginServices.php:58`;校验基础 `backend/crmeb/auth/AuthTokenVerifier.php:57`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -82,7 +82,7 @@ Form::checkbox($field, $title, array $value = [])->options([...]);
|
||||
```
|
||||
|
||||
- `options()` 传二维数组也可:`[['value' => 1, 'label' => '开启'], ...]`
|
||||
- **条件联动**(真实示例 `backend/modules/service/kefu/service/service/StoreServiceAutoReplyServices.php:104`):
|
||||
- **条件联动**:
|
||||
|
||||
```php
|
||||
Form::radio('type', '回复类型', $data['type'] ?? 'text')
|
||||
|
||||
@ -92,7 +92,7 @@ foreach ($goodsList as &$goods) {
|
||||
|
||||
源码:`backend/crmeb/utils/Canvas.php`。基于 GD 库把多张图片 + 多段文字合成一张图(邀请海报、线下收款码),保存到 uploads 并返回相对路径。`__call` 魔法提供全部 `set*` 配置方法。
|
||||
|
||||
### 3.1 使用流程(真实调用 `backend/modules/channel/wechat_mp/service/WechatServices.php:201`)
|
||||
### 3.1 使用流程
|
||||
|
||||
```php
|
||||
use crmeb\utils\Canvas;
|
||||
|
||||
@ -99,7 +99,7 @@ mark 在调度时经 `Str::camel()` 归一,**任务标识一律使用驼峰命
|
||||
|
||||
## 五、数据表 eb_system_crontab
|
||||
|
||||
建表见 `backend/public/install/crmeb_table.sql`(「eb_system_crontab:定时器」),种子见 `backend/public/install/crmeb_data.sql`(同名注释段);旧表 `eb_system_timer` 已更名为 `eb_system_crontab`。
|
||||
建表见 `backend/public/install/crmeb_table.sql`(「eb_system_crontab:定时器」),种子见 `backend/public/install/data/`(同名注释段);旧表 `eb_system_timer` 已更名为 `eb_system_crontab`。
|
||||
|
||||
关键字段与调度行为的关系:
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ AfterSaleRuleResolver::resolve()(合并,app/services/order/rule/)
|
||||
| 退款方案 | `buildRefundPlan / beforeRefundSubmit / applyRefundEffects` | 定制退款金额构成与资金副作用;**副作用必须经 `RefundEffectContextInterface::once()` 登记**保证幂等 |
|
||||
| 表单与动作 | `getFormConfig / getUserActions` | 申请表单、用户侧可用动作 |
|
||||
|
||||
参考实现:实物 [NormalAfterSaleHandler](../../../../backend/modules/product/normal/handler/NormalAfterSaleHandler.php)(标准 7 天)、卡密 [CardkeyAfterSaleHandler](../../../../backend/modules/product/cardkey/handler/CardkeyAfterSaleHandler.php)(1 小时 + 退款回收卡密)。
|
||||
参考实现:实物 [NormalAfterSaleHandler](../../../../backend/modules/product/normal/handler/NormalAfterSaleHandler.php)(标准 7 天);卡密 `modules/product/cardkey/handler/CardkeyAfterSaleHandler.php`(1 小时 + 退款回收卡密,卡密模块未随当前版本提供)。
|
||||
|
||||
## 三、售后单流程
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ public function advance(string $orderNo, string $extStatus, array $extra = []):
|
||||
| `OrderCenterListExtendListener` | 按需 | 列表注入扩展字段 |
|
||||
| `OrderTakeListener / OrderDeliveryListener / OrderCommentListener` | 实物类必须 | 收货/发货/评价推进(纯虚拟类通常无这些节点) |
|
||||
|
||||
监听器内的通用防御写法:先查中台记录校验 `product_type`/`source_type` 防误触发,再用扩展表当前状态做幂等短路,最后 `advance()`。示例见 [modules/product/cardkey/listener/OrderPaidListener](../../../../backend/modules/product/cardkey/listener/OrderPaidListener.php)。
|
||||
监听器内的通用防御写法:先查中台记录校验 `product_type`/`source_type` 防误触发,再用扩展表当前状态做幂等短路,最后 `advance()`。示例参考 `modules/product/cardkey/listener/OrderPaidListener.php`(卡密模块未随当前版本提供,路径为规划参考)。
|
||||
|
||||
## 六、状态异常排查指引
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 实物 normal | [modules/product/normal](../../../../backend/modules/product/normal/) | `normal_order` | `eb_pt_normal_order` | 手动发货 → 物流 → 收货(可选安装) | 是 |
|
||||
| 虚拟 virtual | [modules/product/virtual](../../../../backend/modules/product/virtual/) | `virtual_order` | `eb_pt_virtual_order` | 支付后自动发放网盘链接+提取码 | 否 |
|
||||
| 卡密 cardkey | [modules/product/cardkey](../../../../backend/modules/product/cardkey/) | `cardkey_order` | `eb_pt_cardkey_order` | 支付后自动发卡,退款自动回收卡密 | 否 |
|
||||
| 卡密 cardkey | `modules/product/cardkey/`(未随当前版本提供) | `cardkey_order` | `eb_pt_cardkey_order` | 支付后自动发卡,退款自动回收卡密 | 否 |
|
||||
|
||||
> `coupon`(卡券)类型规划中未实现。活动订单(秒杀/拼团/砍价)不建独立 source_type,复用底层产品类型的订单链路,活动维度经 `activity_type` 关联,见 [交易模式](trade-mode.md#3-3-交易模式与订单表的关系)。
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
| `OrderCenterListExtend` 事件 | 向订单列表注入扩展字段(listener 直接改 `$event['list']` 引用) |
|
||||
| `OrderCenterDetailExtend` 事件 | 向详情注入扩展数据 + 自声明渲染 section(`extend` + `extend_sections`,中台不硬编码插件 section) |
|
||||
|
||||
监听器示例:[modules/product/normal/listener/OrderCenterDetailExtendListener.php](../../../../backend/modules/product/normal/listener/OrderCenterDetailExtendListener.php)、卡密详情注入发卡数据 [modules/product/cardkey/listener/OrderCenterDetailExtendListener.php](../../../../backend/modules/product/cardkey/listener/OrderCenterDetailExtendListener.php)。
|
||||
监听器示例:[modules/product/normal/listener/OrderCenterDetailExtendListener.php](../../../../backend/modules/product/normal/listener/OrderCenterDetailExtendListener.php);卡密详情注入发卡数据见 `modules/product/cardkey/listener/OrderCenterDetailExtendListener.php`(卡密模块未随当前版本提供,路径为规划参考)。
|
||||
|
||||
## 四、新产品类型接入订单链路最小清单
|
||||
|
||||
@ -98,5 +98,5 @@
|
||||
|
||||
- Handler 契约:[crmeb/trade/product/ProductTypeHandlerInterface.php](../../../../backend/crmeb/trade/product/ProductTypeHandlerInterface.php)、基类 [BaseProductTypeHandler.php](../../../../backend/crmeb/trade/product/BaseProductTypeHandler.php)
|
||||
- OrderAdapter 契约:[crmeb/trade/order/type/OrderTypeAdapterInterface.php](../../../../backend/crmeb/trade/order/type/OrderTypeAdapterInterface.php)、基类 [BaseOrderTypeAdapter.php](../../../../backend/crmeb/trade/order/type/BaseOrderTypeAdapter.php)
|
||||
- 标准实现:实物 [modules/product/normal/](../../../../backend/modules/product/normal/)(全流程最全)、卡密 [modules/product/cardkey/](../../../../backend/modules/product/cardkey/)(自动履约 + 退款回收)
|
||||
- 标准实现:实物 [modules/product/normal/](../../../../backend/modules/product/normal/)(全流程最全);卡密 `modules/product/cardkey/`(自动履约 + 退款回收,未随当前版本提供)
|
||||
- 完整创建教程:[crmeb-dev/03-系统商品说明](../../crmeb-dev/references/03-系统商品说明/product-type.md)
|
||||
|
||||
@ -99,10 +99,12 @@
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `normal` | 普通购买 | `DefaultTradeModeHandler` | [app/AppService.php](../../../../backend/app/AppService.php)(boot 中注册) | 0 | 否 |
|
||||
| `member` | 会员购买 | `MemberTradeModeHandler` | [app/AppService.php](../../../../backend/app/AppService.php)(boot 中注册) | 0 | 否 |
|
||||
| `seckill` | 秒杀 | `SeckillActivityHandler` | [modules/trade/seckill/App.php](../../../../backend/modules/trade/seckill/App.php) | 1 | 是 |
|
||||
| `bargain` | 砍价 | `BargainActivityHandler` | [modules/trade/bargain/App.php](../../../../backend/modules/trade/bargain/App.php) | 2 | 是 |
|
||||
| `combination` | 拼团 | `CombinationActivityHandler` | [modules/trade/combination/App.php](../../../../backend/modules/trade/combination/App.php) | 3 | 是 |
|
||||
| `integral` | 积分兑换 | `IntegralActivityHandler` | [modules/trade/integral/App.php](../../../../backend/modules/trade/integral/App.php) | — | 是 |
|
||||
| `seckill` | 秒杀 | `SeckillActivityHandler` | `modules/trade/seckill/App.php`(未随当前版本提供) | 1 | 是 |
|
||||
| `bargain` | 砍价 | `BargainActivityHandler` | `modules/trade/bargain/App.php`(未随当前版本提供) | 2 | 是 |
|
||||
| `combination` | 拼团 | `CombinationActivityHandler` | `modules/trade/combination/App.php`(未随当前版本提供) | 3 | 是 |
|
||||
| `integral` | 积分兑换 | `IntegralActivityHandler` | `modules/trade/integral/App.php`(未随当前版本提供) | — | 是 |
|
||||
|
||||
> 上表营销类模式(秒杀/砍价/拼团/积分兑换)为规划扩展,模块目录未随当前版本提供;接入方式按本节注册写法实现。
|
||||
|
||||
注册写法(模块 `App.php::register()`):
|
||||
|
||||
@ -155,6 +157,6 @@ $app->make(TradeModeRegistry::class)->register('seckill', SeckillActivityHandler
|
||||
|
||||
- 注册器:[backend/crmeb/trade/mode/TradeModeRegistry.php](../../../../backend/crmeb/trade/mode/TradeModeRegistry.php)
|
||||
- 契约:[TradeModeHandlerInterface.php](../../../../backend/crmeb/trade/mode/TradeModeHandlerInterface.php)、基类 [BaseTradeModeHandler.php](../../../../backend/crmeb/trade/mode/BaseTradeModeHandler.php)、兜底 [DefaultTradeModeHandler.php](../../../../backend/crmeb/trade/mode/DefaultTradeModeHandler.php)
|
||||
- 完整示例:拼团 [modules/trade/combination/](../../../../backend/modules/trade/combination/)(钩子覆盖最全)、积分 [modules/trade/integral/](../../../../backend/modules/trade/integral/)(免支付 + 独立订单表)
|
||||
- 完整示例:拼团 `modules/trade/combination/`(钩子覆盖最全)、积分 `modules/trade/integral/`(免支付 + 独立订单表)——两者均未随当前版本提供,路径为规划参考
|
||||
- 调用方参考:下单 [CenterOrderController.php](../../../../backend/app/api/controller/v1/order/CenterOrderController.php)、购物车校验 [StoreCartServices.php](../../../../backend/app/services/order/StoreCartServices.php)
|
||||
- 注册中心体检命令:[app/command/RegistryCheck.php](../../../../backend/app/command/RegistryCheck.php)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: mcp-tools
|
||||
description: 插件向 MCP 服务贡献工具的完整流程:实现 crmeb\interfaces\McpToolInterface 契约、监听 McpToolsRegister 事件注册工具、工具命名规范、description 与参数 Schema 编写规范、写工具确认声明、异常通道现状与 curl 自测。插件需要让 AI 客户端通过 /api/mcp 调用本插件能力时阅读。
|
||||
description: 插件向 MCP 服务贡献工具的完整流程:实现 crmeb\interfaces\McpToolInterface 契约、监听 McpToolsRegister 事件注册工具、工具命名规范、description 与参数 Schema 编写规范、写工具确认声明、label/category 后台展示可选方法、异常通道现状与 curl 自测。插件需要让 AI 客户端通过 /api/mcp 调用本插件能力时阅读。
|
||||
---
|
||||
|
||||
# 插件扩展 MCP 工具
|
||||
@ -60,6 +60,11 @@ class CardKeyOrderListTool implements McpToolInterface
|
||||
return '卡密订单';
|
||||
}
|
||||
|
||||
public function category(): string
|
||||
{
|
||||
return '订单';
|
||||
}
|
||||
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return [
|
||||
@ -81,8 +86,9 @@ class CardKeyOrderListTool implements McpToolInterface
|
||||
要点:
|
||||
|
||||
- 构造器注入 `app/` 层 DAO/Service(容器解析,依赖方向 `modules → app` 合法)。
|
||||
- 写工具额外实现可选方法 `public function interaction(): string`,返回 `'execute'` 声明"修改系统数据、需客户端确认";不实现或返回空串视为只读。采用可选方法而非接口成员,保证已注册工具无需随核心改动。
|
||||
- 写工具额外实现可选方法 `public function interaction(): string`,返回 `'execute'` 声明"修改系统数据、需客户端确认";删除类写操作(不可逆移除数据)返回 `'delete'`,后台按「(删)」标注展示、AI 客户端同样需确认;不实现或返回空串视为只读。采用可选方法而非接口成员,保证已注册工具无需随核心改动。
|
||||
- 建议实现可选方法 `public function label(): string` 返回简短中文名称(2~6 字,如「卡密订单」):后台工具列表与账号权限勾选优先展示中文名,`description()` 面向 AI 模型、常为长段说明,不适合做勾选文案;不实现或返回空串时回退展示 `description()`。
|
||||
- 建议实现可选方法 `public function category(): string` 返回分类名(2~6 字中文):后台工具列表与账号权限勾选按分类分组展示。内置分类名与展示顺序由 `McpToolRegistry::CATEGORY_ORDER` 约定(用户、商品、订单、售后退款、营销、系统设置、消息通知、开发支持),按业务领域选最贴近的一个;插件也可返回自定义分类名,后台按出现顺序追加为新分组。不实现或返回空串时归入「其他」分组排在最后。
|
||||
- 同类工具集中放插件 `mcp/` 目录,文件名随意(自动发现约定只对模块内置 `tools/` 目录生效,插件工具一律走事件注册)。
|
||||
|
||||
### 2. 创建监听器
|
||||
@ -144,9 +150,9 @@ return [
|
||||
|
||||
补充要点:
|
||||
|
||||
- `label()` 与 `description()` 分工:`label()` 是给人看的短中文名(2~6 字,后台工具列表与账号权限勾选展示),`description()` 是给模型看的说明书,两者不要互相替代。
|
||||
- `label()` 与 `description()` 分工:`label()` 是给人看的短中文名(2~6 字,后台工具列表与账号权限勾选展示),`description()` 是给模型看的说明书,两者不要互相替代;`category()` 只承担后台分组,不写入 `description()`。
|
||||
- `description()` 用中文撰写即可,模型理解无障碍;工具名 `name()` 保持 `crmeb_` 前缀 ASCII 标识,这是调用契约,不允许中文。
|
||||
- 管理端展示链路:`label`/`description` 随 `tools/list` 下发,AI Agent「同步工具」后写入工具缓存,在 MCP 管理页与对话工具清单中展示;**修改描述后需在内置 MCP 上重新同步工具才对 AI Agent 生效**(服务端 `/api/mcp` 即时生效,无缓存)。
|
||||
- 管理端展示链路:`label`/`category`/`description` 随 `tools/list` 下发,AI Agent「同步工具」后写入工具缓存,在 MCP 管理页与对话工具清单中展示;**修改描述后需在内置 MCP 上重新同步工具才对 AI Agent 生效**(服务端 `/api/mcp` 即时生效,无缓存)。
|
||||
|
||||
## 强制约定
|
||||
|
||||
@ -154,7 +160,7 @@ return [
|
||||
| --- | --- |
|
||||
| 工具命名 | `crmeb_{插件标识}_{动作}`(如 `crmeb_cardkey_order_list`)。同名注册会覆盖既有工具,命名必须带插件标识 |
|
||||
| 参数 Schema | 字段 `description` 必须与 `execute()` 实现严格一致:声明的筛选能力、格式、枚举值实现里必须真的存在(如实现在就匹配订单号/商品标题,不得写"匹配收件人");通配符转义等校验在实现内兜底 |
|
||||
| 写工具 | 必须声明 `interaction(): 'execute'`,客户端据该声明在执行前取得用户确认。写操作的业务校验与授权不得因 AI 调用而豁免(见 [安全红线.md](../../../rules/安全红线.md)) |
|
||||
| 写工具 | 必须声明 `interaction(): 'execute'`(删除类不可逆操作返回 `'delete'`,后台按「删」标注),客户端据该声明在执行前取得用户确认。写操作的业务校验与授权不得因 AI 调用而豁免(见 [安全红线.md](../../../rules/安全红线.md)) |
|
||||
| 数据访问 | 读系统表走自有 DAO 只读;写数据只能调用对应核心 Service 公开方法,禁止直写系统表(红线见 [插件开发规范.md](../../../rules/插件开发规范.md) 第三节) |
|
||||
| 生命周期 | 插件禁用/卸载后 `event.php` 不再加载,工具自动失效,Installer 无需任何清理逻辑 |
|
||||
|
||||
|
||||
@ -53,5 +53,5 @@ php think make:module demo --label=演示模块 --type=service
|
||||
- [ ] `php think event:check` 无新增孤儿事件
|
||||
- [ ] 涉及插件清单变更时 `php think plugin:manifest-check` 通过
|
||||
- [ ] 接口契约变更已同步两端 `api` 目录、菜单权限与安装 SQL(核对清单见 [.crmeb/rules/前端规范.md](.crmeb/rules/前端规范.md))
|
||||
- [ ] 数据表变更已同步 `backend/public/install/crmeb_table.sql` / `crmeb_data.sql`
|
||||
- [ ] 数据表变更已同步 `backend/public/install/crmeb_table.sql`(基础数据见 `backend/public/install/data/`)
|
||||
- [ ] 提交信息符合上述格式、按主题分批
|
||||
|
||||
@ -66,7 +66,7 @@ Dev Containers 配置(`.devcontainer/`)不在仓库内,已在 `.gitignore`
|
||||
|
||||
## 首次安装配置
|
||||
|
||||
1. 创建数据库,依次导入 `backend/public/install/crmeb_table.sql`(表结构)与 `crmeb_data.sql`(基础数据)
|
||||
1. 创建数据库,导入 `backend/public/install/crmeb_table.sql`(表结构)与 `backend/public/install/data/` 下的基础数据(推荐直接访问安装向导自动完成)
|
||||
2. 复制 `backend/.env.example` 为 `backend/.env`,按实际环境修改(文件内有逐项注释)
|
||||
3. 设置 `backend/runtime`、`backend/public` 目录权限为 777
|
||||
4. 访问 `http://域名/admin`,默认账号 `admin` / 密码 `crmeb.com`
|
||||
@ -173,7 +173,6 @@ php think workerman restart
|
||||
| [模块与驱动](help/docs/模块与驱动.md) | 默认安装清单与各业务模块功能说明 |
|
||||
| [管理端 API](help/docs/api/管理端接口.md) | AdminAPI 接口文档 |
|
||||
| [用户端 API](help/docs/api/用户端接口.md) | 用户端 API 接口文档 |
|
||||
| [对外接口](help/docs/api/对外接口.md) | OutAPI REST 接口文档(第三方接入) |
|
||||
| [MCP 接口](help/docs/api/MCP接口.md) | 独立 MCP 服务的接入、认证、工具与排障说明 |
|
||||
| [数据库设计](help/docs/数据库设计.md) | 数据库表结构设计 |
|
||||
| [开发指南](help/docs/开发指南.md) | 开发环境、规范、流程 |
|
||||
@ -191,8 +190,8 @@ URL 前缀自动路由到对应应用:
|
||||
| 前缀 | 应用 | 说明 |
|
||||
|------|------|------|
|
||||
| `/admin` | 管理端静态页面 | Vue 管理后台入口,不是 API 前缀 |
|
||||
| `/adminapi` | `app/adminapi/` + 模块 `route/admin.php` | 管理后台 API;客服工作台位于 `/adminapi/kefu/*` |
|
||||
| `/api` | `app/api/` + 模块 `route/user.php` | 商城用户 API、模块用户端接口及 `/api/outapi` 开放平台 |
|
||||
| `/adminapi` | `app/adminapi/` + 模块 `route/admin.php` | 管理后台 API |
|
||||
| `/api` | `app/api/` + 模块 `route/user.php` | 商城用户 API、模块用户端接口 |
|
||||
|
||||
---
|
||||
|
||||
@ -234,7 +233,7 @@ URL 前缀自动路由到对应应用:
|
||||
| Claude Code | 自动读取 [CLAUDE.md](CLAUDE.md) |
|
||||
| 支持 llms.txt 的工具 / 网页抓取类 AI | 读取 [llms.txt](llms.txt) |
|
||||
|
||||
所有入口最终指向 [.crmeb/AGENTS.md](.crmeb/AGENTS.md):其中定义了项目硬约束(依赖方向、分层、插件零侵入红线)、13 条开发规则、7 个开发技能与 3 个专项子智能体,AI 按索引按需加载即可获得与内部开发者一致的约定。
|
||||
所有入口最终指向 [.crmeb/AGENTS.md](.crmeb/AGENTS.md):其中定义了项目硬约束(依赖方向、分层、插件零侵入红线)、16 条开发规则、8 个开发技能与 3 个专项子智能体,AI 按索引按需加载即可获得与内部开发者一致的约定。
|
||||
|
||||
### 连接本项目的 MCP 开发工具
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ v8 全新升级为中台化电商架构:**商品中台**统一管理多产品
|
||||
1.系统安全:JWT认证、角色权限校验、操作日志、系统生产日志、文件校验、数据备份;
|
||||
2.高性能:Redis缓存、think-queue消息队列、Workerman长连接网关、多种云存储、支持集群部署;
|
||||
3.多语言:管理端与用户端均支持多语言(vue-i18n),自动识别浏览器语言;
|
||||
4.驱动扩展:支付、上传、短信、邮件、物流、打印、机器人、采集、发票十大类驱动,同一能力多家供应商自由切换;
|
||||
4.驱动扩展:支付、上传、短信、邮件、物流、电子面单、打印、机器人、采集、发票十大类驱动,同一能力多家供应商自由切换;
|
||||
5.实时通信:统一 WebSocket 网关(/ws),支撑后台实时通知等场景;
|
||||
6.一号通:通用第三方扩展,支持短信、物流查询、电子面单、电子发票、商品采集。
|
||||
~~~
|
||||
|
||||
@ -42,10 +42,11 @@ class DocsCheck extends Command
|
||||
'backend/runtime', 'backend/public/uploads', 'backend/public/install.lock',
|
||||
'docker/mysql/data', 'docker/mysql/log', 'docker/nginx/log',
|
||||
'frontend/admin/dist', 'frontend/uni-app/dist',
|
||||
'tests/', 'backend/tests/',
|
||||
];
|
||||
|
||||
/** 行内路径豁免的目录前缀:过程文档与可分发模板描述的是计划/待办路径,存在性随实施变化 */
|
||||
protected array $inlinePathExemptDirs = ['tests/plans/', 'tests/reports/', 'tests/skills/'];
|
||||
protected array $inlinePathExemptDirs = ['tests/plans/', 'tests/reports/', 'tests/skills/', '.crmeb/skills/project-standard-bootstrap/'];
|
||||
|
||||
/** @var string 仓库根目录(运行时定位) */
|
||||
protected string $repoRoot = '';
|
||||
|
||||
@ -269,6 +269,22 @@ class StoreCouponIssueDao extends BaseDao
|
||||
})->select()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按增量原子调整剩余可领取数量
|
||||
* @param int $id
|
||||
* @param int $incCount 总量增量(负数表示缩减发布数量)
|
||||
* @return int 影响行数
|
||||
*/
|
||||
public function incRemainCount(int $id, int $incCount): int
|
||||
{
|
||||
$query = $this->getModel()->where('id', $id);
|
||||
if ($incCount < 0) {
|
||||
// 并发兜底:校验后有新领取导致剩余量不足以缩减时拒绝
|
||||
$query->where('remain_count', '>=', -$incCount);
|
||||
}
|
||||
return $query->inc('remain_count', $incCount)->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一条优惠券信息
|
||||
* @param int $id
|
||||
|
||||
@ -52,11 +52,12 @@ class StoreProductLog extends BaseModel
|
||||
public function storeName()
|
||||
{
|
||||
return $this->hasOne(\app\model\product\CenterProductModel::class, 'id', 'product_id')->bind([
|
||||
'store_name',
|
||||
// 中台商品表 eb_product_center 无 store_name 列,名称存于 name,按别名绑定
|
||||
'store_name' => 'name',
|
||||
'image',
|
||||
'product_price'=>'price',
|
||||
'product_price' => 'price',
|
||||
'stock',
|
||||
'is_show'
|
||||
'is_show',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -93,16 +93,40 @@ class StoreCouponIssueServices extends BaseService
|
||||
*/
|
||||
public function saveCoupon($data)
|
||||
{
|
||||
if ($data['id']) {
|
||||
$res = $this->dao->update($data['id'], [
|
||||
'coupon_title' => $data['coupon_title'],
|
||||
'title' => $data['coupon_title'],
|
||||
'total_count' => $data['total_count'],
|
||||
'remain_count' => $data['total_count'],
|
||||
'receive_limit' => $data['receive_limit'],
|
||||
'status' => $data['status'],
|
||||
]);
|
||||
if (!$res) throw new AdminException('修改失败');
|
||||
if (!empty($data['id'])) {
|
||||
$issueInfo = $this->dao->get((int)$data['id']);
|
||||
if (!$issueInfo) throw new AdminException('优惠券不存在');
|
||||
|
||||
$totalCount = (int)$data['total_count'];
|
||||
if ($issueInfo['is_permanent'] != 1) {
|
||||
// 与新增口径一致:每人限领不得超过发布数量
|
||||
if ((int)$data['receive_limit'] > $totalCount) {
|
||||
throw new AdminException('用户领取数量不能大于发布数量');
|
||||
}
|
||||
// 已领取数量 = 原总量 - 原剩余量;发布数量不得低于已领取数量
|
||||
$issuedCount = (int)$issueInfo['total_count'] - (int)$issueInfo['remain_count'];
|
||||
if ($totalCount < $issuedCount) {
|
||||
throw new AdminException('发布数量不能小于已领取数量');
|
||||
}
|
||||
} else {
|
||||
// 无限张券数量字段不参与(固定为 0)
|
||||
$totalCount = 0;
|
||||
}
|
||||
|
||||
// 剩余量按总量增量原子增减,避免覆盖并发领取的扣减
|
||||
$this->transaction(function () use ($issueInfo, $totalCount, $data) {
|
||||
$incCount = $totalCount - (int)$issueInfo['total_count'];
|
||||
if ($incCount != 0 && !$this->dao->incRemainCount((int)$issueInfo['id'], $incCount)) {
|
||||
throw new AdminException('发布数量不能小于已领取数量');
|
||||
}
|
||||
$this->dao->update((int)$issueInfo['id'], [
|
||||
'coupon_title' => $data['coupon_title'],
|
||||
'title' => $data['coupon_title'],
|
||||
'total_count' => $totalCount,
|
||||
'receive_limit' => $data['receive_limit'],
|
||||
'status' => $data['status'],
|
||||
]);
|
||||
});
|
||||
return (int)$data['id'];
|
||||
}
|
||||
|
||||
@ -134,10 +158,10 @@ class StoreCouponIssueServices extends BaseService
|
||||
|
||||
if ($data['start_time'] && $data['start_use_time']) {
|
||||
|
||||
if ($data['start_time'] < date('Y-m-d 00:00:00')) {
|
||||
if ($data['start_time'] < strtotime(date('Y-m-d 00:00:00'))) {
|
||||
throw new AdminException('开始领取时间不能小于当前时间');
|
||||
}
|
||||
if ($data['start_use_time'] < date('Y-m-d 00:00:00')) {
|
||||
if ($data['start_use_time'] < strtotime(date('Y-m-d 00:00:00'))) {
|
||||
throw new AdminException('开始使用时间不能小于当前时间');
|
||||
}
|
||||
if ($data['start_use_time'] < $data['start_time']) {
|
||||
|
||||
@ -28,6 +28,13 @@ namespace crmeb\interfaces;
|
||||
* (2~6 字,如「订单列表」),供管理后台工具列表与账号权限勾选展示;
|
||||
* 不实现或返回空串时后台回退展示 description()。同样采用可选方法而非
|
||||
* 接口成员,保证第三方已注册工具无需改动。
|
||||
*
|
||||
* 可选约定:工具类可额外实现 `category(): string` 方法返回分类名
|
||||
* (2~6 字中文,如「订单」「系统设置」),供管理后台按分类分组展示工具
|
||||
* 列表与账号权限勾选项;不实现或返回空串时后台归入「其他」分组。内置
|
||||
* 工具使用 McpToolRegistry::CATEGORY_ORDER 约定的分类名,第三方插件工具
|
||||
* 可返回自定义分类名,后台按出现顺序追加为新分组。同样采用可选方法而非
|
||||
* 接口成员,保证第三方已注册工具无需改动。
|
||||
*/
|
||||
interface McpToolInterface
|
||||
{
|
||||
|
||||
@ -52,6 +52,8 @@ interface SchemaPageProvider
|
||||
* @param int $limit 每页条数
|
||||
* @param array $order 排序(如 ['add_time' => 'desc']),空时使用插件默认排序
|
||||
* @return array ['list' => array 行数据, 'count' => int 总条数]
|
||||
* 行数据必须携带唯一 id:前端行动作(详情/表单/动作)固定取 row.id 回查,
|
||||
* 数据库记录用主键,内存拼装的列表用稳定唯一标识(排序/筛选不得改变 id 语义)
|
||||
*/
|
||||
public function list(array $where, int $page, int $limit, array $order = []): array;
|
||||
|
||||
|
||||
@ -90,17 +90,22 @@ class FormBuilder extends Form
|
||||
}
|
||||
|
||||
/**
|
||||
* 多列勾选组(form-create 自定义组件)
|
||||
* 多列勾选组(form-create 自定义组件 · 契约组件)
|
||||
*
|
||||
* 前端 SchemaForm 已注册 type=SchemaCheckboxGroup 的自定义组件:
|
||||
* 网格布局多列展示勾选项,支持全选联动与选项悬停提示(option.title,
|
||||
* 支持多行文本)。
|
||||
* 前端 SchemaForm 已注册 type=SchemaCheckboxGroup 的自定义组件。
|
||||
* ⚠️ 契约组件:前端实现被 schema 驱动页面与已编译插件 UMD 多方引用,
|
||||
* 改前端组件只能向后兼容扩展(新增可选 props、默认行为不变,红线见
|
||||
* .crmeb/rules/前端规范.md 第一节),并同步本方法注释。已支持能力:
|
||||
* - 网格布局多列展示(columns),支持全选联动与半选态;
|
||||
* - 选项 title 悬停多行提示;选项 group 归并分组渲染(分组标题全选/清空 + 已选计数);
|
||||
* - 选项 disabled=true 单项禁用;props 传 disabled=true 整组禁用;
|
||||
* - props 传 max=N 限制最多勾选 N 项(默认 0 不限)。
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param string $title 字段标题
|
||||
* @param array $value 已勾选值数组
|
||||
* @param array $options 选项列表 [{label, value[, title]}]
|
||||
* @param array $props 组件 props 覆盖:columns 每行列数(默认 2)、selectAll 是否显示全选(默认 true)
|
||||
* @param array $options 选项列表 [{label, value[, group][, title][, disabled]}]
|
||||
* @param array $props 组件 props 覆盖:columns 每行列数(默认 2)、selectAll 是否显示全选(默认 true)、disabled 整组禁用、max 最多可勾选数(默认 0 不限)
|
||||
* @return \FormBuilder\Driver\CustomComponent
|
||||
*/
|
||||
public static function checkboxGroup($field, $title, array $value = [], array $options = [], array $props = [])
|
||||
|
||||
@ -28,6 +28,10 @@
|
||||
- 工具返回空数据时直接说明未查到,不编造记录
|
||||
- 只调用本次对话挂载的工具清单中列出的工具,不要调用未列出的工具
|
||||
- 需要调用工具时,按系统提示词约定的 JSON 格式输出工具调用;拿到工具结果后再继续回答或调用下一个工具
|
||||
- 调用工具返回「无权访问工具 / 没有该工具权限」时,说明该 MCP 账号未勾选此工具,按顺序引导管理员两步处理,缺一不可;处理完成前不要反复重试,也不要换其他方式绕过:
|
||||
1. 打开 MCP 权限设置 [MCP 配置](/admin/plugin/mcp/config),找到对应账号点「编辑权限」,勾选该工具并保存;
|
||||
2. 再到 AI 对话页 [/admin/aiagent/index](/admin/aiagent/index) 左上角「MCP 管理」,对内置 MCP 执行「同步工具」,新勾选的工具与权限才会进入 AI 助手的工具缓存;
|
||||
3. 同步完成后引导用户重试原操作
|
||||
|
||||
## 四、操作确认与用户交互机制
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
---
|
||||
name: CRMEB 业务助手
|
||||
description: 用当前已授权的 CRMEB MCP 工具查询商品、订单、真实售后、优惠券与用户数据,核对统计口径后给出业务答复。
|
||||
name: 业务助手
|
||||
description: 工具查询商品、订单、真实售后、优惠券与用户数据,核对统计口径后给出业务答复。
|
||||
status: 1
|
||||
---
|
||||
|
||||
# CRMEB 业务助手
|
||||
# 业务助手
|
||||
|
||||
处理实时业务问题时,先确定对象、筛选条件、时间范围和指标,再从**本次下发的工具清单**选择工具。内置 MCP 工具对话名称带 `builtin.` 前缀;`crmeb_ask_user` 等对话虚拟工具不带此前缀。未挂载或未授权的工具不可调用,也不可把旧查询结果说成当前数据。
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ aiagent/
|
||||
| `eb_ai_agent_task_run` | 任务运行记录(状态、触发方式、摘要、错误、工具次数、用量、起止时间) |
|
||||
| `eb_ai_mcp_server` | MCP Server 注册表(url、api_key、headers、timeout、tools 同步缓存、server_key 唯一) |
|
||||
|
||||
依赖核心表 `eb_ai_model`(模型管理,**非本模块自有表**):本模块升级时幂等补充 `full_url` 列,卸载不删除该列。
|
||||
依赖核心表 `eb_ai_model`(模型管理,**非本模块自有表**):模型管理为系统级能力,`full_url` 等字段由核心维护,本模块安装/卸载不改动该表结构。
|
||||
|
||||
## 内置资产(.crmeb/)
|
||||
|
||||
@ -68,13 +68,13 @@ aiagent/
|
||||
| `skills/{key}/*.md` | 附属资料,由模型经 `crmeb_skill_load` 按需加载 | 同上 |
|
||||
| `skills/{key}/EXAMPLES.md` | 维护者回归用例,**不进入模型上下文** | 同上(保留名) |
|
||||
|
||||
内置 Skill:`crmeb-assistant`(业务助手)、`systemsetting`(系统设置)、`doc`(功能说明)、`emailformat`(邮件格式)、`crontab`(系统定时任务)。
|
||||
内置 Skill:`crmeb-assistant`(业务助手)、`user`(用户管理)、`coupon`(优惠券管理)、`systemsetting`(系统设置)、`doc`(功能说明)、`emailformat`(邮件格式)、`crontab`(系统定时任务)。编写要求见 [help/docs/AI Agent 模块.md](../../../help/docs/AI%20Agent%20模块.md)「Skill 体系」。
|
||||
|
||||
## 与 service_mcp 的关系
|
||||
|
||||
本模块**不直接引用** `service_mcp` 模块的类(避免跨模块互引),一律经 `RemoteMcpClient` 以 HTTP 调用其 `/api/mcp` 端点。因此:
|
||||
|
||||
- 业务工具(商品/订单/售后/优惠券/用户/配置等 33 个)由 `service_mcp` 提供,本模块只负责发现、授权与转发。
|
||||
- 业务工具(商品/订单/售后/优惠券/用户/配置等,当前清单见 [help/docs/api/MCP接口.md](../../../../help/docs/api/MCP接口.md))由 `service_mcp` 提供,本模块只负责发现、授权与转发。
|
||||
- 内置 MCP 可用性要求 `service_mcp` 已启用 + `mcp_enabled=1`(安装默认开启)+ `mcp_appkey` 非空(`McpToolService::available()`)。
|
||||
- 内置服务工具缓存为空时读取会自动补同步一次(失败 10 分钟冷却),新装站点开箱即用;也可在 MCP 管理页手动「同步工具」。
|
||||
- 工具的确认交互声明(`interaction`)随「同步工具」写入缓存,`service_mcp` 侧声明变更后需重新同步内置 MCP 才生效。
|
||||
@ -85,19 +85,15 @@ aiagent/
|
||||
- Skill 路由已显式声明 `Route::pattern(['key' => '[A-Za-z0-9_-]+'])`,因全局路由变量规则不含连字符。
|
||||
- Skill 附属资料的文件名经查询参数 `file` 传递(文件名可能含中文与点号),不走路由变量。
|
||||
- Web 运行用户需对 `modules/aiagent/.crmeb/skills/` 具备**写权限**。
|
||||
- 任务指令与 Skill 正文都会进入模型上下文;改动涉及安全边界的提示词时,同步检查 `tests/aiagent/` 的回归断言。
|
||||
- 任务指令与 Skill 正文都会进入模型上下文;改动涉及安全边界的提示词时,人工回归对话工具循环、确认卡与无人值守邮件白名单流程。
|
||||
|
||||
## 定向回归
|
||||
|
||||
不依赖数据库,从 `backend/` 目录执行(仓库无全局 PHPUnit 配置):
|
||||
模块无随仓库分发的自动化测试脚本(`backend/tests/` 为本地可选目录,缺失属正常)。涉及提示词、工具白名单或任务调度的改动,建议在本地环境人工回归:
|
||||
|
||||
```bash
|
||||
php tests/aiagent/AgentChatFlowTest.php
|
||||
php tests/aiagent/BuiltinSkillInstructionsTest.php
|
||||
php tests/mcp/McpContractTest.php
|
||||
```
|
||||
|
||||
aiagent 的两个脚本**成功时无输出、退出码 0**,失败抛异常并打印原因。
|
||||
1. 对话页流式对话一次含工具调用的问答,确认 `tool` 事件与最终答复正常;
|
||||
2. 触发一个写工具(如系统配置更新),确认确认卡弹出、拒绝与批准两条路径均正确;
|
||||
3. 建一个每分钟任务跑一次,确认运行记录、摘要与通知投递符合预期。
|
||||
|
||||
## 注意事项
|
||||
|
||||
|
||||
@ -38,6 +38,14 @@ class McpToolRegistry
|
||||
*/
|
||||
private const TOOL_NAMESPACE = 'modules\\service\\mcp\\tools\\';
|
||||
|
||||
/**
|
||||
* 内置工具分类展示顺序(与各工具 category() 返回值对应)
|
||||
*
|
||||
* 仅约束展示顺序:未知分类(第三方插件工具自定义)按出现顺序追加在后,
|
||||
* 未声明分类的工具由后台归入「其他」分组排在最后。
|
||||
*/
|
||||
public const CATEGORY_ORDER = ['用户', '商品', '订单', '售后退款', '财务', '营销', '系统设置', '消息通知', '开发支持'];
|
||||
|
||||
/**
|
||||
* 自动发现内置工具(扫描 tools/ 目录)
|
||||
*
|
||||
@ -181,11 +189,43 @@ class McpToolRegistry
|
||||
$definition['label'] = $label;
|
||||
}
|
||||
}
|
||||
// 可选中文分类:仅实现了 category() 且非空的工具携带该字段,供后台分组展示
|
||||
if (method_exists($tool, 'category')) {
|
||||
$category = trim((string)$tool->category());
|
||||
if ($category !== '') {
|
||||
$definition['category'] = $category;
|
||||
}
|
||||
}
|
||||
$definitions[] = $definition;
|
||||
}
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按约定顺序整理分类清单(去重)
|
||||
*
|
||||
* CATEGORY_ORDER 中的已知分类按约定顺序在前,其余分类(第三方插件
|
||||
* 自定义)按出现顺序追加在后;未声明分类(空串)不返回,由调用方
|
||||
* 归入「其他」分组。
|
||||
*
|
||||
* @param array $definitions 工具定义列表(allDefinitions() 返回结构)
|
||||
* @return array 分类名列表
|
||||
*/
|
||||
public static function orderedCategories(array $definitions): array
|
||||
{
|
||||
$seen = [];
|
||||
foreach ($definitions as $definition) {
|
||||
$category = trim((string)($definition['category'] ?? ''));
|
||||
if ($category !== '' && !in_array($category, $seen, true)) {
|
||||
$seen[] = $category;
|
||||
}
|
||||
}
|
||||
return array_merge(
|
||||
array_values(array_intersect(self::CATEGORY_ORDER, $seen)),
|
||||
array_values(array_diff($seen, self::CATEGORY_ORDER))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用工具(tools/call)
|
||||
*
|
||||
|
||||
@ -174,8 +174,8 @@ class AccountListPage extends BaseSchemaPageProvider
|
||||
// 多列勾选组:两列展示 + 全选联动 + 英文名/描述悬停提示(option.title)
|
||||
$field[] = Form::checkboxGroup('tools', '授权工具', $checked, $this->toolOptions())
|
||||
->info($isSystem
|
||||
? '系统账号供 AI Agent 内置工具源调用,收紧白名单即收紧 AI Agent 可见范围(调整后需在 AI Agent 的 MCP 管理页重新「同步工具」);标注(写)的为写入/发送类工具'
|
||||
: '账号仅能调用勾选的工具;标注(写)的为写入/发送类工具')
|
||||
? '系统账号供 AI Agent 内置工具源调用,收紧白名单即收紧 AI Agent 可见范围(调整后需在 AI Agent 的 MCP 管理页重新「同步工具」);标注(写)的为写入/发送类工具,标注(删)的为删除类工具'
|
||||
: '账号仅能调用勾选的工具;标注(写)的为写入/发送类工具,标注(删)的为删除类工具')
|
||||
->validate([
|
||||
['required' => true, 'message' => '请至少勾选一个工具', 'type' => 'array', 'trigger' => 'change'],
|
||||
]);
|
||||
@ -289,7 +289,8 @@ class AccountListPage extends BaseSchemaPageProvider
|
||||
* 工具名 → 展示文案映射(当前已注册的全部工具:内置 + 插件经事件追加)
|
||||
*
|
||||
* 文案优先取工具中文名(label()),未提供时回退功能描述,最后回退
|
||||
* 工具名;写工具在文案后标注(写),便于管理员识别写入/发送类工具。
|
||||
* 工具名;写工具在文案后标注(写)、删除类工具标注(删),便于管理员
|
||||
* 识别写入/发送与删除类工具。
|
||||
*
|
||||
* @return array 工具名 => 展示文案
|
||||
*/
|
||||
@ -304,7 +305,9 @@ class AccountListPage extends BaseSchemaPageProvider
|
||||
if ($label === '') {
|
||||
$label = (string)$definition['name'];
|
||||
}
|
||||
if ((string)($definition['interaction'] ?? '') === 'execute') {
|
||||
if ((string)($definition['interaction'] ?? '') === 'delete') {
|
||||
$label .= '(删)';
|
||||
} elseif ((string)($definition['interaction'] ?? '') === 'execute') {
|
||||
$label .= '(写)';
|
||||
}
|
||||
$map[(string)$definition['name']] = $label;
|
||||
@ -313,18 +316,24 @@ class AccountListPage extends BaseSchemaPageProvider
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具勾选选项(文案取自 toolLabelMap())
|
||||
* 工具勾选选项(文案取自 toolLabelMap(),按分类分组排序)
|
||||
*
|
||||
* 选项按注册中心约定分类顺序排列(插件自定义分类按出现顺序追加,
|
||||
* 未声明分类的归入「其他」),group 字段供前端渲染分组标题与分组全选;
|
||||
* title 携带英文名 + 描述作为悬停提示:form-create 渲染 checkbox 时会把
|
||||
* 选项的额外字段透传给 el-checkbox 根节点,原生 title 随之生效。
|
||||
*
|
||||
* @return array {label, value, title} 选项列表(按工具名排序)
|
||||
* @return array {label, value, group, title} 选项列表
|
||||
*/
|
||||
private function toolOptions(): array
|
||||
{
|
||||
$labelMap = $this->toolLabelMap();
|
||||
$definitions = McpToolRegistry::booted()->allDefinitions();
|
||||
usort($definitions, fn(array $a, array $b) => strcmp((string)$a['name'], (string)$b['name']));
|
||||
// 分组排序:约定分类在前,插件自定义分类次之,「其他」兜底;组内按工具名排序
|
||||
$order = array_merge(McpToolRegistry::orderedCategories($definitions), ['其他']);
|
||||
usort($definitions, fn(array $a, array $b) => array_search($this->categoryOf($a), $order, true)
|
||||
<=> array_search($this->categoryOf($b), $order, true)
|
||||
?: strcmp((string)$a['name'], (string)$b['name']));
|
||||
$options = [];
|
||||
foreach ($definitions as $definition) {
|
||||
$name = (string)$definition['name'];
|
||||
@ -333,11 +342,28 @@ class AccountListPage extends BaseSchemaPageProvider
|
||||
if ($description !== '') {
|
||||
$title .= "\n" . $description;
|
||||
}
|
||||
$options[] = ['label' => $labelMap[$name], 'value' => $name, 'title' => $title];
|
||||
$options[] = [
|
||||
'label' => $labelMap[$name],
|
||||
'value' => $name,
|
||||
'group' => $this->categoryOf($definition),
|
||||
'title' => $title,
|
||||
];
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具所属分类(未声明分类的工具归入「其他」)
|
||||
*
|
||||
* @param array $definition 工具定义(allDefinitions() 返回结构)
|
||||
* @return string 分类名
|
||||
*/
|
||||
private function categoryOf(array $definition): string
|
||||
{
|
||||
$category = trim((string)($definition['category'] ?? ''));
|
||||
return $category !== '' ? $category : '其他';
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序白名单化(支持 id/add_time 升降序,默认 id 倒序)
|
||||
*
|
||||
|
||||
@ -46,16 +46,29 @@ class ToolListPage extends BaseSchemaPageProvider
|
||||
'title' => 'MCP 工具列表',
|
||||
'search' => [
|
||||
['field' => 'keyword', 'label' => '关键词', 'type' => 'input', 'placeholder' => '中文名/工具名称/功能描述'],
|
||||
['field' => 'category', 'label' => '分类', 'type' => 'select', 'placeholder' => '全部分类', 'options' => $this->categoryOptions()],
|
||||
],
|
||||
'columns' => [
|
||||
['prop' => 'index', 'label' => '序号', 'type' => 'text', 'width' => 70],
|
||||
['prop' => 'name', 'label' => '工具名称', 'type' => 'text', 'width' => 220, 'sortable' => true],
|
||||
['prop' => 'label', 'label' => '中文名称', 'type' => 'text', 'width' => 130],
|
||||
['prop' => 'category', 'label' => '分类', 'type' => 'tag', 'width' => 100,
|
||||
'map' => [
|
||||
'用户' => ['theme' => 'primary'],
|
||||
'商品' => ['theme' => 'success'],
|
||||
'订单' => ['theme' => 'warning'],
|
||||
'售后退款' => ['theme' => 'danger'],
|
||||
'营销' => ['theme' => 'warning'],
|
||||
'系统设置' => ['theme' => 'info'],
|
||||
'消息通知' => ['theme' => 'primary'],
|
||||
'开发支持' => ['theme' => 'success'],
|
||||
]],
|
||||
['prop' => 'description', 'label' => '功能描述', 'type' => 'ellipsis'],
|
||||
['prop' => 'params_count', 'label' => '参数数', 'type' => 'number', 'width' => 90],
|
||||
['prop' => 'interaction', 'label' => '执行方式', 'type' => 'tag', 'width' => 110,
|
||||
'map' => [
|
||||
'execute' => ['label' => '需写入授权', 'theme' => 'warning'],
|
||||
'execute' => ['label' => '写入需确认', 'theme' => 'warning'],
|
||||
'delete' => ['label' => '删除需确认', 'theme' => 'danger'],
|
||||
'' => ['label' => '直接执行', 'theme' => 'success'],
|
||||
]],
|
||||
],
|
||||
@ -86,6 +99,11 @@ class ToolListPage extends BaseSchemaPageProvider
|
||||
|| str_contains($item['description'], $keyword);
|
||||
}));
|
||||
}
|
||||
// 分类筛选:选项与行数据的 category 同源(未声明分类归入「其他」)
|
||||
$category = trim((string)($where['category'] ?? ''));
|
||||
if ($category !== '') {
|
||||
$all = array_values(array_filter($all, fn(array $item) => (string)$item['category'] === $category));
|
||||
}
|
||||
|
||||
// 列排序(当前支持 name 列升/降序,保持分页结果稳定;默认注册顺序)
|
||||
$nameOrder = (string)($order['name'] ?? '');
|
||||
@ -116,10 +134,12 @@ class ToolListPage extends BaseSchemaPageProvider
|
||||
$fields = [
|
||||
['label' => '工具名称', 'value' => $tool['name']],
|
||||
['label' => '中文名称', 'value' => $tool['label']],
|
||||
['label' => '分类', 'value' => $tool['category']],
|
||||
['label' => '功能描述', 'value' => $tool['description']],
|
||||
['label' => '执行方式', 'value' => $tool['interaction'], 'type' => 'tag',
|
||||
'map' => [
|
||||
'execute' => ['label' => '需写入授权', 'theme' => 'warning'],
|
||||
'execute' => ['label' => '写入需确认', 'theme' => 'warning'],
|
||||
'delete' => ['label' => '删除需确认', 'theme' => 'danger'],
|
||||
'' => ['label' => '直接执行', 'theme' => 'success'],
|
||||
]],
|
||||
];
|
||||
@ -132,7 +152,7 @@ class ToolListPage extends BaseSchemaPageProvider
|
||||
/**
|
||||
* 加载全部工具定义(内置工具注册到注册中心后取全量定义,幂等)
|
||||
*
|
||||
* @return array 行数据列表(index/name/label/description/params_count/interaction/inputSchema)
|
||||
* @return array 行数据列表(id=注册顺序下标/index/name/label/category/description/params_count/interaction/inputSchema)
|
||||
*/
|
||||
protected function loadDefinitions(): array
|
||||
{
|
||||
@ -140,10 +160,16 @@ class ToolListPage extends BaseSchemaPageProvider
|
||||
|
||||
$rows = [];
|
||||
foreach ($registry->allDefinitions() as $index => $definition) {
|
||||
// 未声明分类(含第三方插件工具)归入「其他」,与筛选选项、权限勾选分组口径一致
|
||||
$category = trim((string)($definition['category'] ?? ''));
|
||||
$rows[] = [
|
||||
// 行 id = 注册顺序下标(同一部署内注册顺序确定,详情按下标回查;
|
||||
// 前端行动作固定取 row.id,漏传会导致详情恒落在第 0 条)
|
||||
'id' => $index,
|
||||
'index' => $index,
|
||||
'name' => (string)($definition['name'] ?? ''),
|
||||
'label' => (string)($definition['label'] ?? ''),
|
||||
'category' => $category !== '' ? $category : '其他',
|
||||
'description' => (string)($definition['description'] ?? ''),
|
||||
'params_count' => count((array)($definition['inputSchema']['properties'] ?? [])),
|
||||
'interaction' => (string)($definition['interaction'] ?? ''),
|
||||
@ -152,4 +178,18 @@ class ToolListPage extends BaseSchemaPageProvider
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类筛选选项(注册中心约定顺序 + 「其他」兜底)
|
||||
*
|
||||
* @return array {label, value} 选项列表
|
||||
*/
|
||||
private function categoryOptions(): array
|
||||
{
|
||||
$categories = array_merge(
|
||||
McpToolRegistry::orderedCategories(McpToolRegistry::booted()->allDefinitions()),
|
||||
['其他']
|
||||
);
|
||||
return array_map(fn(string $category) => ['label' => $category, 'value' => $category], $categories);
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,12 @@ class AfterSaleListTool implements McpToolInterface
|
||||
{
|
||||
return '售后记录查询';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '售后退款';
|
||||
}
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -31,6 +31,12 @@ class AfterSaleStatisticsTool implements McpToolInterface
|
||||
{
|
||||
return '售后统计';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '售后退款';
|
||||
}
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -64,6 +64,15 @@ class CategoryDetailTool implements McpToolInterface
|
||||
return '分类详情';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '商品';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -65,6 +65,15 @@ class CategoryListTool implements McpToolInterface
|
||||
return '分类列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '商品';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -79,6 +79,15 @@ class ConfigTabListTool implements McpToolInterface
|
||||
return '配置分组目录';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '系统设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -39,6 +39,12 @@ class CouponClaimListTool implements McpToolInterface
|
||||
{
|
||||
return '用户券记录';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '营销';
|
||||
}
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -39,6 +39,12 @@ class CouponClaimStatisticsTool implements McpToolInterface
|
||||
{
|
||||
return '发券统计';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '营销';
|
||||
}
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -51,6 +51,12 @@ class CouponHoldingStatisticsTool implements McpToolInterface
|
||||
return '持券统计';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '营销';
|
||||
}
|
||||
|
||||
/** @return array MCP 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -65,6 +65,15 @@ class CouponListTool implements McpToolInterface
|
||||
return '优惠券配置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '营销';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -52,6 +52,12 @@ class CouponUsageStatisticsTool implements McpToolInterface
|
||||
return '用券统计';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '营销';
|
||||
}
|
||||
|
||||
/** @return array MCP 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -106,6 +106,15 @@ class DevDocsSearchTool implements McpToolInterface
|
||||
return '开发文档检索';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '开发支持';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -94,6 +94,15 @@ class DevRouteSearchTool implements McpToolInterface
|
||||
return '路由定义检索';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '开发支持';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -67,6 +67,15 @@ class DevTableSchemaTool implements McpToolInterface
|
||||
return '表结构查询';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '开发支持';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -88,6 +88,15 @@ class DriverConfigUpdateTool implements McpToolInterface
|
||||
return '驱动配置修改';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '系统设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -80,6 +80,15 @@ class DriverListTool implements McpToolInterface
|
||||
return '驱动列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '系统设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -68,6 +68,15 @@ class EmailSendTool implements McpToolInterface
|
||||
return '发送邮件';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '消息通知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -70,6 +70,15 @@ class EntWechatSendTool implements McpToolInterface
|
||||
return '企微机器人推送';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '消息通知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -64,6 +64,15 @@ class ExpressListTool implements McpToolInterface
|
||||
return '物流公司列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '订单';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -42,6 +42,12 @@ class InventoryAlertTool implements McpToolInterface
|
||||
return '库存预警';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '商品';
|
||||
}
|
||||
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -66,6 +66,15 @@ class MenuListTool implements McpToolInterface
|
||||
return '后台菜单查询';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '系统设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -51,6 +51,15 @@ class ModuleListTool implements McpToolInterface
|
||||
return '模块清单查询';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '系统设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -66,6 +66,15 @@ class OrderDetailTool implements McpToolInterface
|
||||
return '订单详情';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '订单';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -67,6 +67,15 @@ class OrderListTool implements McpToolInterface
|
||||
return '订单列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '订单';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
@ -99,20 +108,36 @@ class OrderListTool implements McpToolInterface
|
||||
if (isset($args['status']) && $args['status'] !== '') {
|
||||
$where[] = ['status_code', '=', $args['status']];
|
||||
}
|
||||
// 关键词搜索:转义通配符防止注入(中台表无收件人字段,匹配订单号/商品标题)
|
||||
|
||||
// 关键词搜索(订单号/商品标题):管道字段写法(order_no|title)会被
|
||||
// BaseDao::filterWhere 当作不存在列静默丢弃,必须用闭包显式 OR,不进 search 条件数组
|
||||
// 转义通配符防止注入;中台表无收件人字段
|
||||
$keyword = '';
|
||||
if (!empty($args['keyword'])) {
|
||||
$keyword = addcslashes($args['keyword'], '%_');
|
||||
$where[] = ['order_no|title', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
$applyKeyword = static function (\think\db\Query $query) use ($keyword): void {
|
||||
$query->where(function (\think\db\Query $q) use ($keyword) {
|
||||
$q->whereLike('order_no', '%' . $keyword . '%')
|
||||
->whereOr('title', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
};
|
||||
|
||||
$listQuery = $this->dao->search($where);
|
||||
$countQuery = $this->dao->search($where);
|
||||
if ($keyword !== '') {
|
||||
$applyKeyword($listQuery);
|
||||
$applyKeyword($countQuery);
|
||||
}
|
||||
|
||||
$list = $this->dao->search($where)
|
||||
$list = $listQuery
|
||||
->field('id,order_no,uid,source_type,total_price,pay_price,pay_status,status_code,refund_status,shipping_type,add_time')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$count = $this->dao->search($where)->count();
|
||||
$count = $countQuery->count();
|
||||
return ['list' => $list, 'count' => $count];
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,12 @@ class OrderLogisticsTool implements McpToolInterface
|
||||
{
|
||||
return '订单物流';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '订单';
|
||||
}
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -35,6 +35,12 @@ class OrderStatisticsTool implements McpToolInterface
|
||||
return '订单统计';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '订单';
|
||||
}
|
||||
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -67,6 +67,15 @@ class ProductDetailTool implements McpToolInterface
|
||||
return '商品详情';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '商品';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -88,6 +88,15 @@ class ProductListTool implements McpToolInterface
|
||||
return '商品列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '商品';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -46,6 +46,12 @@ class ProductRankingTool implements McpToolInterface
|
||||
return '商品排行';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '商品';
|
||||
}
|
||||
|
||||
/** @return array MCP 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -51,6 +51,12 @@ class ProductStatisticsTool implements McpToolInterface
|
||||
return '商品统计';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '商品';
|
||||
}
|
||||
|
||||
/** @return array MCP 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -65,6 +65,15 @@ class RefundDetailTool implements McpToolInterface
|
||||
return '退款标记详情';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '售后退款';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -64,6 +64,15 @@ class RefundListTool implements McpToolInterface
|
||||
return '退款订单列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '售后退款';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -67,6 +67,15 @@ class SystemConfigListTool implements McpToolInterface
|
||||
return '系统配置查询';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '系统设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -84,6 +84,15 @@ class SystemConfigUpdateTool implements McpToolInterface
|
||||
return '系统配置修改';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '系统设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -67,6 +67,15 @@ class UserDetailTool implements McpToolInterface
|
||||
return '用户详情';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
|
||||
@ -53,7 +53,8 @@ class UserListTool implements McpToolInterface
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return '查询用户列表,支持按昵称/手机号关键词筛选,返回 UID、昵称、余额、积分、等级、注册时间等字段;'
|
||||
return '查询用户列表,支持按昵称/手机号关键词筛选,支持按用户标签筛选(label_id 传标签 ID 数组,满足任一标签即命中,'
|
||||
. '标签 ID 可通过 crmeb_user_label_list 查询),返回 UID、昵称、余额、积分、等级、注册时间等字段;'
|
||||
. '手机号已脱敏(中间四位掩码),不含用户收货地址与订单/消费记录,'
|
||||
. '查询单个用户完整信息请用 crmeb_user_detail。';
|
||||
}
|
||||
@ -67,6 +68,15 @@ class UserListTool implements McpToolInterface
|
||||
return '用户列表';
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具分类(管理后台分组展示)
|
||||
* @return string
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/**
|
||||
* 入参 Schema
|
||||
* @return array
|
||||
@ -79,6 +89,11 @@ class UserListTool implements McpToolInterface
|
||||
'page' => ['type' => 'number', 'description' => '页码'],
|
||||
'limit' => ['type' => 'number', 'description' => '每页数量'],
|
||||
'keyword' => ['type' => 'string', 'description' => '搜索关键词,匹配昵称/手机号'],
|
||||
'label_id' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer'],
|
||||
'description' => '按用户标签筛选,标签 ID 数组(满足任一标签即命中),标签 ID 可通过 crmeb_user_label_list 查询',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
@ -86,7 +101,7 @@ class UserListTool implements McpToolInterface
|
||||
/**
|
||||
* 执行工具
|
||||
*
|
||||
* @param array $args 参数(page/limit/keyword)
|
||||
* @param array $args 参数(page/limit/keyword/label_id)
|
||||
* @return array 用户列表和总数
|
||||
*/
|
||||
public function execute(array $args): array
|
||||
@ -101,18 +116,43 @@ class UserListTool implements McpToolInterface
|
||||
$where[] = ['nickname|phone', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
$list = $this->dao->search($where)
|
||||
// 标签筛选:兼容单个 ID 或 ID 数组,空值跳过
|
||||
$labelIds = [];
|
||||
if (!empty($args['label_id'])) {
|
||||
$labelIds = is_array($args['label_id']) ? $args['label_id'] : [$args['label_id']];
|
||||
$labelIds = array_values(array_filter(array_map('intval', $labelIds)));
|
||||
}
|
||||
|
||||
$list = $this->searchQuery($where, $labelIds)
|
||||
->field('uid,nickname,avatar,phone,now_money,integral,level,add_time')
|
||||
->order('uid desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$count = $this->dao->search($where)->count();
|
||||
$count = $this->searchQuery($where, $labelIds)->count();
|
||||
foreach ($list as &$item) {
|
||||
$item['phone'] = SensitiveData::phone((string)($item['phone'] ?? ''));
|
||||
}
|
||||
unset($item);
|
||||
return ['list' => $list, 'count' => $count];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询:基础条件 + 标签子查询(用户拥有任一所选标签即命中)
|
||||
*
|
||||
* @param array $where 基础查询条件
|
||||
* @param array $labelIds 标签 ID 数组
|
||||
* @return \think\db\Query
|
||||
*/
|
||||
private function searchQuery(array $where, array $labelIds)
|
||||
{
|
||||
$query = $this->dao->search($where);
|
||||
if ($labelIds) {
|
||||
$query->whereIn('uid', function ($subQuery) use ($labelIds) {
|
||||
$subQuery->name('user_label_relation')->whereIn('label_id', $labelIds)->field('uid')->select();
|
||||
});
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,12 @@ class UserStatisticsTool implements McpToolInterface
|
||||
return '用户统计';
|
||||
}
|
||||
|
||||
/** @return string 工具分类(管理后台分组展示) */
|
||||
public function category(): string
|
||||
{
|
||||
return '用户';
|
||||
}
|
||||
|
||||
/** @return array 入参 JSON Schema */
|
||||
public function inputSchema(): array
|
||||
{
|
||||
|
||||
@ -6,22 +6,70 @@
|
||||
class="schema-checkbox-group__all"
|
||||
:model-value="allChecked"
|
||||
:indeterminate="indeterminate"
|
||||
:disabled="disabled"
|
||||
@change="toggleAll"
|
||||
>
|
||||
全选
|
||||
</el-checkbox>
|
||||
<!-- 分组模式:选项携带 group 字段时按分组渲染,分组标题支持分组全选/清空 -->
|
||||
<template v-if="sections">
|
||||
<div v-for="section in sections" :key="section.name" class="schema-checkbox-group__section">
|
||||
<div class="schema-checkbox-group__head">
|
||||
<el-checkbox
|
||||
:model-value="sectionAllChecked(section)"
|
||||
:indeterminate="sectionIndeterminate(section)"
|
||||
:disabled="disabled"
|
||||
@change="(checked) => toggleSection(section, checked)"
|
||||
>
|
||||
{{ section.name }}
|
||||
</el-checkbox>
|
||||
<span class="schema-checkbox-group__num"
|
||||
>{{ sectionCheckedCount(section) }}/{{ section.options.length }}</span
|
||||
>
|
||||
</div>
|
||||
<el-checkbox-group
|
||||
class="schema-checkbox-group__grid"
|
||||
:style="gridStyle"
|
||||
:disabled="disabled"
|
||||
:model-value="checkedKeys"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<!-- 选项:option.title 悬停多行提示;option.disabled 单项禁用;达 max 上限后未勾选项禁用 -->
|
||||
<el-tooltip
|
||||
v-for="opt in section.options"
|
||||
:key="keyOf(opt.value)"
|
||||
:disabled="!tooltipText(opt)"
|
||||
placement="top"
|
||||
effect="dark"
|
||||
>
|
||||
<template #content>
|
||||
<span class="schema-checkbox-group__tip">{{ tooltipText(opt) }}</span>
|
||||
</template>
|
||||
<el-checkbox :value="keyOf(opt.value)" :disabled="isOptionDisabled(opt)">{{ opt.label }}</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 平铺模式:选项无 group 字段时保持原有多列网格 -->
|
||||
<el-checkbox-group
|
||||
v-else
|
||||
class="schema-checkbox-group__grid"
|
||||
:style="gridStyle"
|
||||
:model-value="checkedValues"
|
||||
:disabled="disabled"
|
||||
:model-value="checkedKeys"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<!-- 选项:option.title 存在时悬停展示多行提示(如工具英文名 + 描述) -->
|
||||
<el-tooltip v-for="opt in options" :key="opt.value" :disabled="!tooltipText(opt)" placement="top" effect="dark">
|
||||
<el-tooltip
|
||||
v-for="opt in options"
|
||||
:key="keyOf(opt.value)"
|
||||
:disabled="!tooltipText(opt)"
|
||||
placement="top"
|
||||
effect="dark"
|
||||
>
|
||||
<template #content>
|
||||
<span class="schema-checkbox-group__tip">{{ tooltipText(opt) }}</span>
|
||||
</template>
|
||||
<el-checkbox :value="opt.value">{{ opt.label }}</el-checkbox>
|
||||
<el-checkbox :value="keyOf(opt.value)" :disabled="isOptionDisabled(opt)">{{ opt.label }}</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
@ -29,12 +77,24 @@
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用页面多列勾选组(form-create 自定义组件)
|
||||
* 通用页面多列勾选组(form-create 自定义组件 · 契约组件)
|
||||
*
|
||||
* 网格布局展示勾选项(columns 控制每行列数,默认 2 列),支持全选联动
|
||||
* (全选/清空/半选态),选项携带 title 时悬停展示多行提示。
|
||||
* 后端经 Form::checkboxGroup() 生成规则,值为选项 value 数组,
|
||||
* 校验等表单行为与普通字段一致。
|
||||
* ⚠️ 跨系统契约,禁止为单一业务需求随手改写:后端 Form::checkboxGroup()
|
||||
* (backend/crmeb/utils/FormBuilder.php)生成 type=SchemaCheckboxGroup 规则,
|
||||
* SchemaForm 统一注册渲染,已编译插件 UMD 内含独立打包副本。
|
||||
* 能力扩展一律「新增可选 props + 默认行为与既有 props 语义不变」,
|
||||
* 并同步 FormBuilder 的 props 文档;红线见 .crmeb/rules/前端规范.md 第一节。
|
||||
*
|
||||
* Props 契约(均可由后端规则 props 下发):
|
||||
* - modelValue 已勾选值数组,提交回传选项原始类型;
|
||||
* - options 选项 [{label, value[, group][, title][, disabled]}]:
|
||||
* group 归并分组渲染(分组标题全选/清空 + 已选计数,组间保持选项给定顺序,
|
||||
* 后端负责排序)、title 悬停多行提示、disabled=true 单项禁用;
|
||||
* - selectAll 是否显示全选(默认 true);columns 每行列数(默认 2);
|
||||
* - disabled 整组禁用(默认 false);
|
||||
* - max 最多可勾选数(默认 0 不限):达上限后未勾选项禁用、批量全选按顺序
|
||||
* 截断,取消勾选不受限;禁用项不参与批量全选/清空(其勾选态不可被改写)。
|
||||
* 值统一按字符串 key 比对渲染、提交还原原始类型,规避后端数值/字符串类型不一致。
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
import { ElCheckbox, ElCheckboxGroup, ElTooltip } from 'element-plus';
|
||||
@ -42,12 +102,12 @@ import { ElCheckbox, ElCheckboxGroup, ElTooltip } from 'element-plus';
|
||||
defineOptions({ name: 'SchemaCheckboxGroup' });
|
||||
|
||||
const props = defineProps({
|
||||
/** 已勾选值数组(form-create field 值绑定) */
|
||||
/** 已勾选值数组(form-create field 值绑定,提交回传原始类型) */
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
/** 选项列表 [{label, value[, title]}](后端规则 props 传入) */
|
||||
/** 选项列表 [{label, value[, group][, title][, disabled]}](后端规则 props 传入) */
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
@ -67,23 +127,57 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/** 整组禁用 */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** 最多可勾选数,0 不限;达上限后未勾选项禁用,批量全选按顺序截断 */
|
||||
max: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
/** 选项值列表 */
|
||||
const optionValues = computed(() => props.options.map((opt) => String(opt.value)));
|
||||
/** 选项值字符串化 key(渲染比对统一用 key,规避数值/字符串类型不一致) */
|
||||
function keyOf(value) {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
/** 已勾选值 */
|
||||
const checkedValues = computed(() => (props.modelValue || []).map(String));
|
||||
/** key → 原始 value 映射(提交时还原原始类型) */
|
||||
const keyOrigin = computed(() => {
|
||||
const map = new Map();
|
||||
for (const opt of props.options) map.set(keyOf(opt.value), opt.value);
|
||||
return map;
|
||||
});
|
||||
|
||||
/** 全选态:选项全部勾选 */
|
||||
const allChecked = computed(
|
||||
() => optionValues.value.length > 0 && optionValues.value.every((value) => checkedValues.value.includes(value)),
|
||||
/** 已勾选 key 数组 */
|
||||
const checkedKeys = computed(() => (props.modelValue || []).map(keyOf));
|
||||
|
||||
/** 已勾选 key 集合 */
|
||||
const checkedSet = computed(() => new Set(checkedKeys.value));
|
||||
|
||||
/** 全部选项 key(按选项顺序) */
|
||||
const allKeys = computed(() => props.options.map((opt) => keyOf(opt.value)));
|
||||
|
||||
/** 可交互选项 key(仅剔除单项 disabled,不掺入 max 派生态,保证批量清空可达) */
|
||||
const enabledKeys = computed(() =>
|
||||
props.options.filter((opt) => props.disabled !== true && opt?.disabled !== true).map((opt) => keyOf(opt.value)),
|
||||
);
|
||||
|
||||
/** 单项是否禁用:整组禁用 / 单项 disabled / 达 max 上限且未勾选 */
|
||||
function isOptionDisabled(opt) {
|
||||
if (props.disabled || opt?.disabled === true) return true;
|
||||
return props.max > 0 && !checkedSet.value.has(keyOf(opt.value)) && checkedKeys.value.length >= props.max;
|
||||
}
|
||||
|
||||
/** 全选态:选项全部勾选 */
|
||||
const allChecked = computed(() => allKeys.value.length > 0 && allKeys.value.every((key) => checkedSet.value.has(key)));
|
||||
|
||||
/** 半选态:部分勾选 */
|
||||
const indeterminate = computed(() => checkedValues.value.length > 0 && !allChecked.value);
|
||||
const indeterminate = computed(() => checkedKeys.value.length > 0 && !allChecked.value);
|
||||
|
||||
/** 网格列数 */
|
||||
const gridStyle = computed(() => ({
|
||||
@ -91,13 +185,76 @@ const gridStyle = computed(() => ({
|
||||
gridTemplateColumns: `repeat(${Math.max(1, props.columns)}, 1fr)`,
|
||||
}));
|
||||
|
||||
/** 全选/清空联动 */
|
||||
/** 分组视图:任一选项携带 group 时按 group 归并(保持选项给定顺序),否则 null 走平铺 */
|
||||
const sections = computed(() => {
|
||||
if (!props.options.some((opt) => String(opt?.group ?? '') !== '')) return null;
|
||||
const map = new Map();
|
||||
for (const opt of props.options) {
|
||||
const name = String(opt?.group ?? '') || '其他';
|
||||
if (!map.has(name)) map.set(name, { name, options: [] });
|
||||
map.get(name).options.push(opt);
|
||||
}
|
||||
return [...map.values()];
|
||||
});
|
||||
|
||||
/** 分组内已勾选数 */
|
||||
function sectionCheckedCount(section) {
|
||||
const keys = new Set(section.options.map((opt) => keyOf(opt.value)));
|
||||
return checkedKeys.value.filter((key) => keys.has(key)).length;
|
||||
}
|
||||
|
||||
/** 分组全选态:组内选项全部勾选 */
|
||||
function sectionAllChecked(section) {
|
||||
return section.options.length > 0 && sectionCheckedCount(section) === section.options.length;
|
||||
}
|
||||
|
||||
/** 分组半选态:组内部分勾选 */
|
||||
function sectionIndeterminate(section) {
|
||||
const count = sectionCheckedCount(section);
|
||||
return count > 0 && count < section.options.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一提交入口:去重后既有勾选优先保留,新增按顺序受 max 上限约束
|
||||
* (达上限的新增忽略,取消勾选不受限),提交还原选项原始类型
|
||||
*/
|
||||
function commitKeys(keys) {
|
||||
if (props.disabled) return;
|
||||
const proposed = [...new Set(keys)];
|
||||
const kept = proposed.filter((key) => checkedSet.value.has(key));
|
||||
const added = proposed.filter((key) => !checkedSet.value.has(key));
|
||||
const room = props.max > 0 ? Math.max(0, props.max - kept.length) : added.length;
|
||||
const next = kept.concat(added.slice(0, room));
|
||||
emit(
|
||||
'update:modelValue',
|
||||
next.map((key) => keyOrigin.value.get(key)).filter((value) => value !== undefined),
|
||||
);
|
||||
}
|
||||
|
||||
/** 全选/清空联动(仅操作可交互项:禁用项的勾选态不被批量操作改写) */
|
||||
function toggleAll(checked) {
|
||||
emit('update:modelValue', checked ? [...optionValues.value] : []);
|
||||
if (checked) {
|
||||
commitKeys([...checkedKeys.value, ...enabledKeys.value]);
|
||||
} else {
|
||||
const enabledSet = new Set(enabledKeys.value);
|
||||
commitKeys(checkedKeys.value.filter((key) => !enabledSet.has(key)));
|
||||
}
|
||||
}
|
||||
|
||||
/** 分组全选/清空(仅操作组内可交互项,其余分组保持不变) */
|
||||
function toggleSection(section, checked) {
|
||||
const enabledSet = new Set(
|
||||
section.options.filter((opt) => props.disabled !== true && opt?.disabled !== true).map((opt) => keyOf(opt.value)),
|
||||
);
|
||||
if (checked) {
|
||||
commitKeys([...checkedKeys.value, ...enabledSet]);
|
||||
} else {
|
||||
commitKeys(checkedKeys.value.filter((key) => !enabledSet.has(key)));
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdate(values) {
|
||||
emit('update:modelValue', values || []);
|
||||
commitKeys(values || []);
|
||||
}
|
||||
|
||||
/** 选项悬停提示(title 支持多行文本) */
|
||||
@ -115,6 +272,26 @@ function tooltipText(opt) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__section {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__num {
|
||||
color: var(--prev-color-text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&__grid {
|
||||
width: 100%;
|
||||
|
||||
|
||||
@ -261,4 +261,4 @@ fetch(`${settings.apiBaseURL}/custom_admin_js`)
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
loadStatisticsScript('https://cdn.oss.9gt.net/js/es.js?version=bzv6.0.0');
|
||||
loadStatisticsScript('https://cdn.oss.9gt.net/js/es.js?version=kyv8.0.0');
|
||||
|
||||
@ -99,7 +99,8 @@ curl 'https://example.com/api/mcp' \
|
||||
- `description`:用途与业务口径;
|
||||
- `inputSchema`:参数 JSON Schema;
|
||||
- `interaction`:可选,值为 `execute` 时表示写操作,客户端应在执行前取得用户确认;
|
||||
- `label`:可选,简短中文名称,供管理后台工具列表与账号权限勾选展示,未实现 `label()` 方法的工具不返回该字段。
|
||||
- `label`:可选,简短中文名称,供管理后台工具列表与账号权限勾选展示,未实现 `label()` 方法的工具不返回该字段;
|
||||
- `category`:可选,业务分类名(内置工具为 用户/商品/订单/售后退款/营销/系统设置/消息通知/开发支持 之一),管理后台按该字段分组展示工具列表与权限勾选项;插件工具可返回自定义分类名(按出现顺序追加为新分组),未实现 `category()` 方法的工具不返回该字段,后台归入「其他」分组。
|
||||
|
||||
### 4.3 调用只读工具
|
||||
|
||||
@ -141,14 +142,14 @@ curl 'https://example.com/api/mcp' \
|
||||
|
||||
## 5. 内置工具
|
||||
|
||||
当前自动发现 36 个内置工具,其中 33 个业务工具、3 个开发知识工具。标记“写”的工具还会返回 `interaction: "execute"`。
|
||||
当前自动发现 47 个内置工具,其中 44 个业务工具、3 个开发知识工具。标记“写”的工具还会返回 `interaction: "execute"`。业务工具均声明 `category` 分类(用户、商品、订单、售后退款、营销、系统设置、消息通知、开发支持),后台「MCP 工具」列表与账号权限勾选按分类分组展示。
|
||||
|
||||
| 业务域 | 工具 |
|
||||
|---|---|
|
||||
| 商品与分类 | `crmeb_product_list`、`crmeb_product_detail`、`crmeb_product_statistics`、`crmeb_product_ranking`、`crmeb_inventory_alert`、`crmeb_category_list`、`crmeb_category_detail` |
|
||||
| 订单与物流 | `crmeb_order_list`、`crmeb_order_detail`、`crmeb_order_statistics`、`crmeb_order_logistics`、`crmeb_order_express_list` |
|
||||
| 售后与退款 | `crmeb_after_sale_list`、`crmeb_after_sale_statistics`、`crmeb_refund_list`、`crmeb_refund_detail` |
|
||||
| 优惠券 | `crmeb_coupon_list`、`crmeb_coupon_claim_list`、`crmeb_coupon_claim_statistics`、`crmeb_coupon_holding_statistics`、`crmeb_coupon_usage_statistics` |
|
||||
| 优惠券 | `crmeb_coupon_list`、`crmeb_coupon_create`(写)、`crmeb_coupon_claim_list`、`crmeb_coupon_claim_statistics`、`crmeb_coupon_holding_statistics`、`crmeb_coupon_usage_statistics` |
|
||||
| 用户 | `crmeb_user_list`、`crmeb_user_detail`、`crmeb_user_statistics` |
|
||||
| 系统配置 | `crmeb_config_list`、`crmeb_config_tab_list`、`crmeb_config_update`(写) |
|
||||
| 菜单与模块 | `crmeb_menu_list`、`crmeb_module_list` |
|
||||
|
||||
@ -10,8 +10,6 @@
|
||||
| 用户端 API | `/api` | H5、小程序、App | 无认证/可选登录/强制登录 |
|
||||
| 用户端 v2 | `/api/v2` | 新版用户功能 | 与用户端相同 |
|
||||
| 中台 API | `/api/center` | 用户端统一商品/订单 | 商品可游客,订单强制登录 |
|
||||
| 客服工作台 | `/adminapi/kefu` | 客服工作台/游客咨询 | 授权组使用客服 token,游客组免登录 |
|
||||
| 对外 API | `/api/outapi` | 第三方系统 | 外部账号 token + 路由权限 |
|
||||
| 独立 MCP | `POST /api/mcp` | AI 客户端 | `mcp_enabled` 总开关 + MCP 账号密钥(`X-Mcp-Key` 或标准 Authorization) |
|
||||
| AI Agent | `/adminapi/aiagent` | 管理后台 | 管理员 token + 角色权限 |
|
||||
|
||||
@ -225,7 +223,6 @@ ThinkPHP 路由示例:
|
||||
|---|---|
|
||||
| [管理端 API](管理端接口.md) | `backend/app/adminapi/route/*.php`、模块 `route/admin.php` |
|
||||
| [用户端 API](用户端接口.md) | `backend/app/api/route/*.php`、模块 `route/user.php` |
|
||||
| [对外 API](对外接口.md) | `backend/modules/service/outapi/route/user.php` |
|
||||
|
||||
当前核心和模块接口数量较多,本文档重点说明稳定契约、分组和关键接口。接口的完整方法、路径、Controller 映射和中间件始终以路由文件为最终事实来源。
|
||||
|
||||
|
||||
@ -203,12 +203,10 @@ backend/modules/*/*/route/admin.php
|
||||
|
||||
主要模块管理路由包括:
|
||||
|
||||
- `distribution`:分销与代理;
|
||||
- `wechat_mini`、`wechat_mp`:渠道管理;
|
||||
- `normal`、`virtual`、`cardkey`、`hotel`、`ticket`:产品类型;
|
||||
- `bargain`、`combination`、`integral`、`seckill`:交易活动;
|
||||
- `signin`、`lottery`、`newuser`、`channel_code`:营销;
|
||||
- `kefu`、`outapi`、`file_manage`:服务/系统模块。
|
||||
- `channel/h5`、`channel/wechat_mini`:渠道;
|
||||
- `product_normal`、`product_virtual`:产品类型;
|
||||
- `user_signin`:营销;
|
||||
- `service_mcp`:MCP 服务;
|
||||
- `aiagent`:AI 对话、自动化任务、MCP 服务连接、Skill 与附属资料管理。
|
||||
|
||||
模块未启用时,对应路由不会加载。
|
||||
|
||||
@ -303,7 +303,6 @@ return app('json')->fail('操作失败', $context);
|
||||
| 用户端核心 | `backend/app/api/route/*.php` |
|
||||
| 模块管理端 | `backend/modules/**/route/admin.php` |
|
||||
| 模块用户端 | `backend/modules/**/route/user.php` |
|
||||
| 客服 | `backend/modules/service/kefu/route/admin.php`(管理、工作台、游客)与 `route/user.php`(商城用户) |
|
||||
|
||||
### 6.2 新增路由检查
|
||||
|
||||
@ -411,9 +410,7 @@ npm run build
|
||||
|
||||
### 11.2 当前测试现状
|
||||
|
||||
测试脚本按端存放,统一入口是仓库根目录的 [测试总索引](../../tests/README.md)。该索引登记当前可用脚本、运行命令、前置条件和已知验证缺口;不要在开发指南中维护第二份易漂移的文件清单。
|
||||
|
||||
后端 `backend/tests/` 是被 `.gitignore` 排除的本地运行件,不随仓库分发;管理端和用户端测试位于各自 `scripts/`。按改动选用对应脚本,执行前先阅读索引中的依赖和写入范围。有专项脚本不等于具备统一 PHPUnit 配置或完整端到端覆盖,实际执行结果应由具体业务变更另行记录。
|
||||
测试脚本按端存放;仓库根 `tests` 目录是本地测试登记处(可缺失,不随仓库分发),存在时以其中索引为准。管理端和用户端测试位于各自 `scripts/`。按改动选用对应脚本,执行前先确认依赖和写入范围。有专项脚本不等于具备统一 PHPUnit 配置或完整端到端覆盖,实际执行结果应由具体业务变更另行记录。
|
||||
|
||||
建议优先补充:
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
| 项目 | CRMEB v8 |
|
||||
| 发布阶段 | 首版开发阶段;插件清单与 Installer 版本统一为 `1.0.0` |
|
||||
| 文档检查分支 | `v8.0.dev`(本次核对快照,不限制后续开发分支) |
|
||||
| 文档检查日期 | 2026-09-17 |
|
||||
| 文档检查日期 | 2026-09-24 |
|
||||
| 后端 | ThinkPHP 8、多应用模式、PHP 8.2-8.4 |
|
||||
| 管理端 | Vue 3.5、Vue Router 4、Pinia 2、Element Plus 2.9、Vite 5(纯 JS) |
|
||||
| 用户端 | uni-app 官方 CLI、Vue 3、Pinia 2、Vite 5,多端条件编译;源码在 `frontend/uni-app/src/` |
|
||||
@ -35,7 +35,7 @@
|
||||
| 管理端开发 | [开发指南](开发指南.md)、[管理端 API](api/管理端接口.md)、[模块说明](模块与驱动.md) |
|
||||
| uni-app 开发 | [开发指南](开发指南.md)、[用户端 API](api/用户端接口.md)、[项目功能](项目功能.md) |
|
||||
| 产品/活动扩展开发 | [模块说明](模块与驱动.md)、[开发指南](开发指南.md) |
|
||||
| 第三方集成 | [对外接口](api/对外接口.md)、[MCP 接口](api/MCP接口.md)、[安全与配置](安全与配置.md) |
|
||||
| 第三方集成 | [MCP 接口](api/MCP接口.md)、[安全与配置](安全与配置.md) |
|
||||
| 测试 | [项目功能](项目功能.md)、[API 总览](api/接口总览.md)、[开发指南](开发指南.md) |
|
||||
| 实施/运维 | [部署指南](部署指南.md)、[运维手册](运维手册.md)、[安全与配置](安全与配置.md) |
|
||||
|
||||
@ -46,9 +46,8 @@
|
||||
| [项目说明](项目说明.md) | 项目定位、适用场景、角色、术语、目录总览、能力边界 |
|
||||
| [项目功能](项目功能.md) | 管理端、用户端、交易、营销、渠道、客服、开放平台等功能 |
|
||||
| [项目架构](项目架构.md) | 技术栈、逻辑分层、核心链路、扩展机制、运行时组件 |
|
||||
| [模块说明](模块与驱动.md) | 22 个模块、30 个驱动、默认安装清单、安装生命周期与扩展规则 |
|
||||
| [模块说明](模块与驱动.md) | 7 个模块、15 个驱动、默认安装清单、安装生命周期与扩展规则 |
|
||||
| [AI Agent 模块](AI%20Agent%20模块.md) | `aiagent` 模块功能详述:对话与工具循环、模型/MCP/Skill 配置、自动化任务、数据表、接口与权限、已知限制 |
|
||||
| [AI Agent 产品介绍](AI%20Agent%20产品介绍.md) | CRMEB AI 助手对外宣传口径:核心能力、安全机制与典型场景(功能事实以 AI Agent 模块文档为准) |
|
||||
| [数据库设计](数据库设计.md) | 数据来源、核心实体、表域划分、迁移与一致性原则 |
|
||||
| [开发指南](开发指南.md) | 环境准备、启动、目录约定、开发步骤、质量检查 |
|
||||
| [部署指南](部署指南.md) | Docker、手动部署、反向代理、后台进程和发布检查 |
|
||||
@ -57,7 +56,6 @@
|
||||
| [API 总览](api/接口总览.md) | API 入口、认证、响应、分页、错误处理与版本策略 |
|
||||
| [管理端 API](api/管理端接口.md) | `/adminapi` 接口域、权限链和业务路由索引 |
|
||||
| [用户端 API](api/用户端接口.md) | `/api` 接口域、游客/登录接口、业务分组与多端请求 |
|
||||
| [对外接口](api/对外接口.md) | `/api/outapi` 授权及商品、订单、售后、用户等接口 |
|
||||
| [MCP 接口](api/MCP接口.md) | 独立 `/api/mcp` 的安装启用、认证、协议、工具、写入授权与排障 |
|
||||
|
||||
## 事实来源
|
||||
@ -83,7 +81,7 @@
|
||||
| 代码变更 | 需更新文档 |
|
||||
|---|---|
|
||||
| 新增/删除模块或驱动 | `模块与驱动.md`、`项目功能.md` |
|
||||
| `aiagent` 模块的功能、内置 Skill、业务工具、任务调度或依赖变化 | `AI Agent 模块.md`(涉及对外能力口径时同步核对 `AI Agent 产品介绍.md`) |
|
||||
| `aiagent` 模块的功能、内置 Skill、业务工具、任务调度或依赖变化 | `AI Agent 模块.md` |
|
||||
| 新增 API 或修改认证/响应 | `api/` 下对应文档 |
|
||||
| `service_mcp` 模块的 MCP 工具清单或接入方式变化 | `api/MCP接口.md`、`AI Agent 模块.md`、根 `README.md`「连接本项目的 MCP 开发工具」 |
|
||||
| 数据表、字段或迁移变化 | `数据库设计.md` |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 模块与驱动说明
|
||||
|
||||
> 核对日期:2026-09-17。以下数量与默认安装标记来自仓库 `plugin.json`,不是当前数据库的启用状态。
|
||||
> 核对日期:2026-09-24。以下数量与默认安装标记来自仓库 `plugin.json`,不是当前数据库的启用状态。
|
||||
|
||||
## 1. 扩展体系概览
|
||||
|
||||
@ -8,20 +8,21 @@
|
||||
|
||||
| 类型 | 目录 | 本次检查数量 | 典型内容 |
|
||||
|---|---|---:|---|
|
||||
| 业务模块 | `backend/modules/` | 22 个清单 | 路由、控制器、服务、表、菜单、事件、前端页面 |
|
||||
| 第三方驱动 | `backend/driver/` | 30 个清单 | 同一能力下的供应商实现与配置 |
|
||||
| 业务模块 | `backend/modules/` | 7 个清单 | 路由、控制器、服务、表、菜单、事件、前端页面 |
|
||||
| 第三方驱动 | `backend/driver/` | 15 个清单 | 同一能力下的供应商实现与配置 |
|
||||
|
||||
业务模块改变“系统能做什么”,驱动改变“同一能力由哪个供应商完成”。例如“用户充值”是业务模块,“微信支付 V3”是支付驱动。
|
||||
|
||||
### 1.1 全新安装的默认清单
|
||||
|
||||
安装向导 `backend/public/install/index.php` 分别调用 `ModuleInstaller` 的必需模块、默认模块和默认驱动安装流程。只读取清单顶层 `required`、`default_install`,配置字段内的 `required` 仅代表该配置必填。
|
||||
清单顶层 `required` 标记必装保护模块,`default_install` 标记默认安装的模块与驱动,两者在全新安装时都会自动初始化。只读取清单顶层标记,配置字段内的 `required` 仅代表该配置必填。
|
||||
|
||||
新装自动安装 6 个模块:
|
||||
新装自动安装 7 个模块(1 个必装 + 6 个默认安装):
|
||||
|
||||
| key | 名称 | 标记 | 是否可卸载/禁用 |
|
||||
|---|---|---|---|
|
||||
| `product_normal` | 实物商品 | `required=true` | 否,基础保护模块 |
|
||||
| `product_virtual` | 网盘产品 | `default_install=true` | 是 |
|
||||
| `channel/h5` | H5 浏览器 | `default_install=true` | 是 |
|
||||
| `channel/wechat_mini` | 微信小程序 | `default_install=true` | 是 |
|
||||
| `user_signin` | 签到活动 | `default_install=true` | 是 |
|
||||
|
||||
@ -141,7 +141,7 @@ location / {
|
||||
优先通过项目安装向导或既定安装流程执行,基础表来自:
|
||||
|
||||
- `backend/public/install/crmeb_table.sql`;
|
||||
- `backend/public/install/crmeb_data.sql`;
|
||||
- `backend/public/install/data/` 基础数据;
|
||||
- 必需模块、默认模块和默认驱动 Installer,具体清单见 [模块说明](模块与驱动.md)。
|
||||
|
||||
安装后立即:
|
||||
|
||||
@ -268,7 +268,7 @@ php think workerman start --d
|
||||
## 10. 已知边界与维护提示
|
||||
|
||||
- 插件启用状态依赖数据库,静态阅读目录不能判断生产实际开启了哪些功能。
|
||||
- 当前可发现 22 个模块、30 个驱动;新装默认安装 6 个模块和 15 个驱动,详见 [模块说明](模块与驱动.md)。安装不等于第三方服务已配置可用。
|
||||
- 当前可发现 7 个模块、15 个驱动;新装默认安装 7 个模块和 15 个驱动,详见 [模块说明](模块与驱动.md)。安装不等于第三方服务已配置可用。
|
||||
- 基础安装 SQL 与模块 Installer 共同定义首版新装结构。仓库保留历史升级设施,但不把历史 SQL 当作新装必跑步骤。
|
||||
- 管理端和用户端存在较多历史兼容路由,新增能力应优先使用现有中台、注册中心和模块规范。
|
||||
- API 业务状态主要在 JSON 的 `status` 字段中表达,不能只依赖 HTTP 状态码判断成功失败。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user