# 开发文档:代理商消息通知页面 > **需求**:在「账户管理」菜单下新增「消息通知」页面(代理商账户余额提醒列表)。 > **环境**:dev `192.168.1.181:3306`(root/kaixinjiuhao);prd 上线需替换 parent_id 后重跑。 > **涉及库**:`xiaoqu_comples_d`(菜单/字段/权限)+ `xiaoqu_franchisee_d`(业务数据)。 --- ## 1. 数据库变更 ### 1.1 业务表 — `xiaoqu_franchisee_d`(dataSource2 / m2) ```sql USE xiaoqu_franchisee_d; CREATE TABLE `agent_message_notice` ( `id` bigint(8) NOT NULL AUTO_INCREMENT, `create_id` bigint(8) DEFAULT '0' COMMENT '创建id', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `modify_id` bigint(8) DEFAULT '0' COMMENT '修改id', `modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `soft_delete` int(4) DEFAULT '0' COMMENT '数据状态', `agent_id` bigint(8) DEFAULT '0' COMMENT '代理商ID', `agent_name` varchar(64) DEFAULT '' COMMENT '代理商名称', `notify_user_name` varchar(32) DEFAULT '' COMMENT '消息通知人员', `notify_mobile` varchar(20) DEFAULT '' COMMENT '消息通知人员手机号', `notify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消息通知时间', `content` varchar(512) DEFAULT '' COMMENT '消息通知内容', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代理商消息通知'; ``` ### 1.2 菜单 + 字段 + 权限(权限库) 包含 4 张表:`webpage` / `webpage_authority` / `page_field` / `field_webpage_authority`,在一个事务里完成。 > **环境对应的权限 schema**: > - dev → `xiaoqu_comples_d` > - test → `test` > - prod → `xiaoqu_comples_p` > > 切环境只需改第一行 `USE`,其余语句通用。 > **关键改动(2026-05-14)**:`webpage_authority` 和 `field_webpage_authority` 的 INSERT 从硬编码 `(1, 102)` 改为**动态复用同 parent 下兄弟菜单的授权范围**。这样 dev/test/prod 跑同一份 SQL,新菜单的可见性自动跟同分类下的兄弟菜单一致,避免授权遗漏。 ```sql USE xiaoqu_comples_d; -- dev;test 改 USE test; prod 改 USE xiaoqu_comples_p; START TRANSACTION; -- ───────────────────────────────────────────────────────────── -- ① webpage:菜单本体(挂到「账户管理」id=131 之下) -- ───────────────────────────────────────────────────────────── INSERT INTO webpage (title, param, icon, description, order_by, parent_id, `type`, `status`, `path`, is_table_show) VALUES ('消息通知', '', '', '', 6, 131, 0, 1, 'messageNotice', 1); SET @new_web_id := LAST_INSERT_ID(); SELECT @new_web_id AS new_webpage_id; -- ───────────────────────────────────────────────────────────── -- ② webpage_authority:菜单授权(动态复用同 parent 下兄弟菜单的授权 authority 并集) -- ───────────────────────────────────────────────────────────── INSERT INTO webpage_authority (web_id, authority_id, title, `path`, parent_id, check_status, `type`, is_table_show, order_by) SELECT @new_web_id, sa.authority_id, '消息通知', 'messageNotice', 131, 1, 0, 1, 6 FROM ( SELECT DISTINCT authority_id FROM webpage_authority WHERE web_id IN (SELECT id FROM webpage WHERE parent_id = 131 AND id <> @new_web_id) AND soft_delete = 0 ) sa; -- ───────────────────────────────────────────────────────────── -- ③ page_field:页面字段定义(对应 AgentMessageNoticeOutput 的字段) -- is_table_show=1 → 列表列;nav_select=1 → 顶部搜索区;is_select=1 → 详情/编辑面板可选 -- ───────────────────────────────────────────────────────────── INSERT INTO page_field (web_id, cn_name, en_name, class_name, field_type, order_by, is_select, nav_select, is_table_show, param) VALUES -- 列表显示字段(按截图列顺序排) (@new_web_id, '代理商名称', 'agentName', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 2, 1, 0, 1, ''), (@new_web_id, '消息通知人员', 'notifyUserName', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 3, 1, 1, 1, ''), (@new_web_id, '消息通知人员手机号', 'notifyMobile', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 4, 1, 1, 1, ''), (@new_web_id, '消息通知时间', 'notifyTime', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'date', 5, 1, 0, 1, ''), (@new_web_id, '消息通知内容', 'content', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 6, 1, 0, 1, ''), -- 搜索区隐藏字段(用于「渠道商」下拉传 agentId) (@new_web_id, '渠道商', 'agentId', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'inputSelect', 7, 1, 1, 0, ''), -- 公共审计字段(不展示,仅用于权限/数据元配置完整) (@new_web_id, 'id', 'id', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 32, 1, 0, 0, ''), (@new_web_id, '创建id', 'createId', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'inputSelect',33, 1, 0, 0, ''), (@new_web_id, '创建时间', 'createTime', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'date', 34, 1, 0, 0, ''), (@new_web_id, '修改id', 'modifyId', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'inputSelect',35, 1, 0, 0, ''), (@new_web_id, '修改时间', 'modifyTime', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'date', 36, 1, 0, 0, ''), (@new_web_id, '数据状态', 'softDelete', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 37, 1, 0, 0, ''); -- ───────────────────────────────────────────────────────────── -- ④ field_webpage_authority:字段级权限 -- 跟随 ② 步骤里授予的 authority 范围 CROSS JOIN page_field 批量生成 -- ───────────────────────────────────────────────────────────── INSERT INTO field_webpage_authority (web_id, authority_id, cn_name, en_name, is_select, nav_select, field_type, field_id, order_by, check_status, param, is_table_show) SELECT pf.web_id, wa.authority_id, pf.cn_name, pf.en_name, pf.is_select, pf.nav_select, pf.field_type, pf.id, pf.order_by, 1, pf.param, pf.is_table_show FROM page_field pf CROSS JOIN ( SELECT DISTINCT authority_id FROM webpage_authority WHERE web_id = @new_web_id AND soft_delete = 0 ) wa WHERE pf.web_id = @new_web_id; -- ───────────────────────────────────────────────────────────── -- ⑤ 校验 -- ───────────────────────────────────────────────────────────── SELECT id, parent_id, title, path, order_by FROM webpage WHERE id = @new_web_id; SELECT COUNT(*) AS authority_grants FROM webpage_authority WHERE web_id = @new_web_id; SELECT COUNT(*) AS field_count FROM page_field WHERE web_id = @new_web_id; SELECT COUNT(*) AS field_grants_total FROM field_webpage_authority WHERE web_id = @new_web_id; -- 期待 field_grants_total = field_count × authority_grants -- dev: 12 × 2 = 24 / test: 12 × N(N=兄弟菜单 authority 并集大小) / prod 类似 COMMIT; -- 失败请改 ROLLBACK ``` > **各环境实测授权范围(2026-05-14 核对兄弟菜单授权数)**: > - dev:账户管理下兄弟菜单授权数都是 2(仅 1+102) → 消息通知也是 2 > - test:兄弟菜单授权数 8~12 不等(导自线上)→ 取并集后预计 13~15 个 authority > - prod:上线前用 §4 校验脚本现查 > > **已执行情况**: > - dev (`xiaoqu_comples_d`):webpage.id=386,webpage_authority=2 条(1, 102),field_webpage_authority=24 条(12 字段 × 2 authority) > - test (`test`, 2026-05-14):webpage.id=388,webpage_authority=12 条(authority_id: 1, 22, 23, 32, 41, 49, 52, 65, 68, 70, 75, 80),field_webpage_authority=144 条(12 × 12) ### 1.3 字段配置说明 | 字段 (en_name) | order_by | nav_select 搜索区 | is_table_show 列表 | field_type 控件 | 截图对应 | |---|---:|:---:|:---:|---|---| | agentName | 2 | ✗ | ✓ | string | 列表「代理商名称」 | | notifyUserName | 3 | ✓ | ✓ | string | 搜索「人员名称」+ 列表 | | notifyMobile | 4 | ✓ | ✓ | string | 搜索「手机号码」+ 列表 | | notifyTime | 5 | ✗ | ✓ | date | 列表「消息通知时间」 | | content | 6 | ✗ | ✓ | string | 列表「消息通知内容」 | | agentId | 7 | ✓ | ✗ | inputSelect | 搜索「渠道商」下拉(传 ID,cn_name 显示为"渠道商") | | id / createId / createTime / modifyId / modifyTime / softDelete | 32-37 | ✗ | ✗ | — | DTO 公共字段,仅元数据完整性,不展示 | > **`agentId` 用 `inputSelect` 控件**:参考账户列表 (web_id=138) 同样手法 —— 搜索区显示"渠道商"下拉,实际传 `agentId`;列表不显示这个字段,只显示 `agentName`。 --- ## 2. 后端改动 — `xiaoqu-franchisee` ### 2.1 包结构与文件 ``` xiaoqu-publicbeanandmap/src/main/java/me/iiv/iivframework/ ├── beans/b2/AgentMessageNotice.java # MyBatis-Plus @TableName("agent_message_notice") ├── mapper/m2/AgentMessageNoticeMapper.java # extends BaseMapper └── service/s2/ ├── AgentMessageNoticeService.java # interface IService └── impl/AgentMessageNoticeServiceImpl.java # extends ServiceImpl xiaoqu-franchisee/src/main/java/me/iiv/iivframework/controller/agentMessage/ ├── AgentMessageNoticeController.java # @RequestMapping("/agentMessageNotice") ├── input/AgentMessageNoticeQueryInput.java └── output/AgentMessageNoticeOutput.java # ⚠️ 全限定类名必须与 page_field.class_name 一致 ``` > ⚠️ `page_field.class_name` 我填了 `me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput`,后端 DTO 必须放这个路径,否则后台「字段权限配置」用反射找类会失败。 ### 2.2 API | Method | URL | 说明 | |---|---|---| | POST | `/agentMessageNotice/list` | 分页列表(含搜索条件) | **Input** ```java public class AgentMessageNoticeQueryInput { private Long agentId; // 渠道商下拉 private String notifyUserName; // 人员名称 模糊 private String notifyMobile; // 手机号码 模糊 private Integer page = 1; private Integer limit = 10; } ``` **Output** ```java public class AgentMessageNoticeOutput { private Long id; private Long createId; private String createTime; private Long modifyId; private String modifyTime; private Integer softDelete; private Long agentId; private String agentName; private String notifyUserName; private String notifyMobile; private String notifyTime; // 格式化 yyyy-MM-dd HH:mm:ss private String content; } ``` **查询逻辑** ```java Page p = new Page<>(input.getPage(), input.getLimit()); LambdaQueryWrapper qw = Wrappers.lambdaQuery() .eq(AgentMessageNotice::getSoftDelete, 0) .eq(input.getAgentId() != null, AgentMessageNotice::getAgentId, input.getAgentId()) .like(StringUtils.isNotBlank(input.getNotifyUserName()), AgentMessageNotice::getNotifyUserName, input.getNotifyUserName()) .like(StringUtils.isNotBlank(input.getNotifyMobile()), AgentMessageNotice::getNotifyMobile, input.getNotifyMobile()) .orderByDesc(AgentMessageNotice::getNotifyTime); ``` ### 2.3 数据来源(待 PM 澄清) 页面只读。记录写入方需明确: - A. XXL-Job 定时扫描代理商余额,触发阈值就写一条(推荐) - B. 余额变更/提现流程内嵌写入 → 确认后补一个 `AgentMessageNoticeService.send(agentId, content)` 方法 + 触发点 / 定时任务。 --- ## 3. 前端改动 — `vue-manage-system` ### 3.1 路由 `src/router/module/accoutManageRouter.js`(注意原拼写 `accout`)追加: ```js { path: '/messageNotice', name: 'messageNotice', component: () => import('@/components/page/accountManage/messageNotice.vue'), meta: { title: '消息通知' } } ``` ### 3.2 API `src/api/api.js`: ```js agentMessageNotice: { list: '/agentMessageNotice/list' } ``` `src/api/accountService.js`(或新建 `agentMessageNoticeService.js`): ```js export const agentMessageNoticeService = { list(obj) { return request.post($api.agentMessageNotice.list, obj); } }; ``` ### 3.3 页面 `src/components/page/accountManage/messageNotice.vue`,骨架参考 `outAllDoc.vue`: - 搜索区:渠道商(el-select,options 用 `dropDownService` 拿代理列表)/ 人员名称(el-input)/ 手机号码(el-input)/ 清空 + 搜索 - 列表:序号 / 代理商名称 / 消息通知人员 / 消息通知人员手机号 / 消息通知时间 / 消息通知内容 - 分页:el-pagination - 可继承 `standTableMixin` --- ## 4. 上线执行顺序 1. **DEV (`xiaoqu_franchisee_d` + `xiaoqu_comples_d`)** - 业务库执行 §1.1(建表 `agent_message_notice`) - 权限库 `USE xiaoqu_comples_d;` 执行 §1.2(事务) - 后端编码 → `mvn package` → 部署 - 前端编码 → `yarn build-dev` → 部署 2. **DEV 冒烟** - 管理员账号登录,左侧菜单出现「账户管理 > 消息通知」 - 进入页面:列表接口返回(空数据也算成功) - 后台「权限组管理 → 配置页面内容权限」能看到这个页面的所有字段 - 手工 INSERT 几条 `agent_message_notice` 数据,验证列表渲染 / 三个搜索条件 / 排序 3. **TEST (`xiaoqu_franchisee_d` + `test`)** - 业务表 `agent_message_notice` 共享 dev 业务库,已存在 — **无需建表** - 权限库 `USE test;` 执行 §1.2 SQL(动态 SELECT 自动复用兄弟菜单授权,**SQL 完全不改**) - 部署后端/前端到 test 环境 4. **PRD 上线前必查**(只做 sanity check,§1.2 SQL 不需要替换硬编码) ```sql USE xiaoqu_comples_p; -- ① 「账户管理」parent_id 是否仍是 131 SELECT id FROM webpage WHERE title='账户管理' AND parent_id=0; -- ② 「账户管理」下兄弟菜单 + 当前授权 authority 集合大小 SELECT w.id, w.title, COUNT(DISTINCT wa.authority_id) AS auth_cnt FROM webpage w LEFT JOIN webpage_authority wa ON wa.web_id=w.id AND wa.soft_delete=0 WHERE w.parent_id=131 GROUP BY w.id, w.title; -- ③ 防重复执行:确认 messageNotice 尚未存在 SELECT id, title, path FROM webpage WHERE path='messageNotice'; ``` 5. **PRD 上线**:§1.2 SQL 把 `USE` 改成 `USE xiaoqu_comples_p` 后**整段照跑**;如 prod「账户管理」parent_id 与 dev 不同(131),把 §1.2 里所有 `131` 替换成 prod 实际值即可,其余无需改 --- ## 5. 风险与注意 | 项 | 风险 | 处理 | |---|---|---| | `parent_id=131` 硬编码 | PRD 不一定一致 | §4 ① sanity check 取真实 id | | ~~`authority_id=1/102` 硬编码~~ | 已用动态 SELECT 替换 | §1.2 webpage_authority/field_webpage_authority INSERT 改为子查询自动复用同 parent 下兄弟菜单授权 | | `page_field.class_name` 全限定类名 | 后端 DTO 路径必须严格一致 | 改包名必同步改 SQL 或反向更新 | | `LAST_INSERT_ID()` 在 CROSS JOIN 之后 | 自增 id 会消耗但不回退 | 业务无影响 | | 字段中文名歧义 | "渠道商"≈"代理商",截图列名是"代理商名称",搜索区下拉是"渠道商" | 按截图分别用,与现有 138 页面风格一致 | | 数据来源未明确 | 表建好但永远空 | 触发逻辑确认后补开发 | | dev 兄弟菜单授权集合极简(仅 1+102) | dev SQL 执行时仅给了 2 个 authority;test/prod 上同一份动态 SQL 会拿到更大集合(test 上 13~15 个) | 设计上接受(按"跟兄弟菜单可见性一致"原则),dev 不补 |