提交修改,后台管理页面bug修复,已经发布后台管理界面V1.0版本

This commit is contained in:
2025-12-25 17:56:09 +08:00
parent 845f1c6618
commit b1da484431
23 changed files with 614 additions and 257 deletions

View File

@@ -0,0 +1,19 @@
# Change: 管理端侧边栏改为 Element Plus Drawer + Menu事件触发
## Why
当前移动端侧边栏的显示/隐藏依赖 class + CSStransform实现在不同样式覆盖或构建产物下容易出现“class 变化但 UI 无反应”的问题,导致点击菜单按钮无法可靠展开。
## What Changes
- 移动端侧边栏改为使用 Element Plus 的 `el-drawer` 作为抽屉容器,依靠组件自带的显示/隐藏机制。
- 菜单改为 Element Plus 的 `el-menu`router 模式),用于导航到:首页、会话记录管理、用户管理。
- Header 菜单按钮改为**事件触发打开抽屉**(不再通过切换 class 驱动 CSS 动画)。
## Non-goals
- 不新增页面、不新增路由、不改动权限/鉴权逻辑。
## Impact
- Affected specs: `openspec/specs/backend-admin/spec.md`
- Affected code:
- `admin-web/src/components/Layout/Header.vue`
- `admin-web/src/components/Layout/Sidebar.vue`
- `admin-web/src/components/Layout/Layout.vue`

View File

@@ -0,0 +1,18 @@
## MODIFIED Requirements
### Requirement: 菜单布局Element Plus Drawer + Menu
管理端导航菜单 SHALL 使用 Element Plus 组件实现,并在移动端使用抽屉式菜单以保证可用性:
- 桌面/平板端显示固定侧边栏菜单Element Plus `el-menu`
- 手机端:点击 Header 菜单按钮后打开抽屉Element Plus `el-drawer`),抽屉内部为 `el-menu`
- 抽屉关闭:由 `el-drawer` 自带交互完成(点击遮罩/关闭动作),不依赖 class + CSS transform
#### Scenario: 手机端点击按钮打开菜单
- **WHEN** 管理员在手机端点击 Header 的菜单按钮
- **THEN** 系统通过事件触发打开 `el-drawer`
- **AND** 抽屉内显示 `el-menu` 导航项
#### Scenario: 手机端点击菜单项自动收起
- **WHEN** 管理员在抽屉中点击任一菜单项
- **THEN** 路由跳转到对应页面
- **AND** 抽屉自动关闭

View File

@@ -0,0 +1,9 @@
# Tasks — 管理端侧边栏改为 Element Plus Drawer + Menu
- [x] 更新规范:在本 change 下补充 delta specbackend-admin
- [x] 更新现行 spec`openspec/specs/backend-admin/spec.md` 的“菜单布局”要求
- [x] Header 改造点击菜单按钮触发事件open
- [x] Sidebar 改造:移动端使用 `el-drawer`,内部使用 `el-menu`
- [x] Layout 改造:接收 Header 事件,控制抽屉打开;抽屉关闭由 `el-drawer` v-model 驱动
- [x] 删除旧的 class+CSS 开关逻辑(不再依赖 `.sidebar--open`、transform
- [ ] 前端构建验证:`npm run build`admin-web

View File

@@ -0,0 +1,15 @@
# Change: 优化管理端会话列表日期筛选(移动端)
## Why
当前管理端会话列表使用单个 `daterange` 控件,在移动端交互与可用性较差;且缺少常用快捷时间范围,导致筛选效率低。
## What Changes
- 将“时间范围”从单个 `daterange` 控件改为 **两个独立日期选择器(开始日期、结束日期)**
- 新增 **快捷范围选择器**今天、最近3天、最近7天、最近30天。
- 默认筛选范围为 **最近7天**
- 做好移动端适配:在小屏下控件纵向排列、全宽显示。
## Impact
- Affected specs: `openspec/specs/backend-admin/spec.md`
- Affected code: `admin-web/src/views/ConversationList.vue`
- Backend API 不变:继续使用 `POST /api/Admin/QueryConversations``StartTime/EndTime` 过滤能力。

View File

@@ -0,0 +1,23 @@
## MODIFIED Requirements
### Requirement: 会话记录筛选(时间范围)
管理端会话记录页面 SHALL 提供便于移动端使用的时间范围筛选:
- 使用两个独立日期选择器:开始日期、结束日期
- 提供快捷范围选择器今天、最近3天、最近7天、最近30天
- 默认范围为最近7天
#### Scenario: 默认最近7天
- **WHEN** 管理员进入会话记录页面
- **THEN** 页面默认选中“最近7天”
- **AND** 请求会携带对应的 `StartTime/EndTime`
#### Scenario: 使用快捷范围
- **WHEN** 管理员选择“今天/最近3天/最近7天/最近30天”
- **THEN** 系统更新开始/结束日期
- **AND** 点击查询后按该范围过滤会话记录
#### Scenario: 移动端可用性
- **WHEN** 管理员在移动端访问会话记录页面
- **THEN** 快捷选择器与开始/结束日期选择器纵向排列
- **AND** 控件全宽显示,避免横向挤压导致误触

View File

@@ -0,0 +1,10 @@
# Tasks — 优化管理端会话列表日期筛选(移动端)
- [x] 更新管理端会话筛选 UI`daterange` 改为开始/结束两个日期选择器
- [x] 增加快捷范围选择器今天、最近3天、最近7天、最近30天
- [x] 默认值设置为最近7天并确保“重置”后仍回到默认范围
- [x] 调整请求参数:`StartTime/EndTime` 发送为可解析的 DateTime字符串/ISO确保后端可正确绑定
- [x] 移动端适配:小屏纵向排列、控件全宽
- [x] 更新 openspec
- [x]`openspec/specs/backend-admin/spec.md` 补充/修改相关 Requirement & Scenario
- [x] 在本 change 下添加 delta spec`openspec/changes/update-admin-conversation-date-filter/specs/backend-admin/spec.md`

View File

@@ -0,0 +1,48 @@
# Implementation — 修改登录并新增后台统计接口
## 已完成改动
1. 登录逻辑:`WxCheckMvc/Controllers/LoginController.cs`
- 修改点:在 `Login` 方法中,原来的存在用户分支不做更改;现在在判断 `count != 0`(用户存在)分支下新增:
```csharp
using (MySqlCommand updateCmd = new MySqlCommand("UPDATE xcx_users SET UpdateTime = NOW() WHERE UserKey = @UserKey", _connection))
{
updateCmd.Parameters.AddWithValue("@UserKey", openId);
await updateCmd.ExecuteNonQueryAsync();
}
```
- 目的:显式写入 `UpdateTime`,表示用户在本次登录调用中活跃。
2. 新增统计接口:`WxCheckMvc/Controllers/AdminController.cs`
- 新增方法 `QueryStats()`GET
- ActiveUsersSELECT COUNT(1) FROM xcx_users WHERE UpdateTime >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND UserKey <> '' AND PhoneNumber <> ''
- TotalConversationsSELECT COUNT(1) FROM xcx_conversation
- TodayNewConversationsSELECT COUNT(1) FROM xcx_conversation WHERE CreateTime >= CURDATE() AND CreateTime < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
- TotalUsersSELECT COUNT(1) FROM xcx_users WHERE UserKey <> '' AND PhoneNumber <> ''
- 返回 DTO`AdminStatsResponse { long ActiveUsers, long TotalConversations, long TodayNewConversations, long TotalUsers }`
3. 前端首页统计卡片改造:`admin-web/src/views/Home.vue`
- 修改点:首页不再通过 `QueryUsers` / `QueryConversations` 拉全量数据后在前端计算统计值;改为直接调用 `GET /api/Admin/QueryStats`
- 映射关系:
- 活跃用户卡片:`ActiveUsers`
- 会话记录卡片:`TotalConversations`
- 总用户数卡片:`TotalUsers`
- 今日新增会话卡片:`TodayNewConversations`
## 测试与验证建议
- 本地运行 `dotnet build`:确认编译通过。
- 使用 Postman / curl 调用 `POST /api/Login/Login`(传入可换取 openid 的 `code` 或在测试中直接模拟 openid确认在用户存在时 `UpdateTime` 被修改。
- 调用 `GET /api/Admin/QueryStats` 验证返回结构并和数据库结果比对。
- 访问管理端首页,确认四张统计卡片展示值与接口返回一致。
## 备注
- `xcx_users.UpdateTime` 已在数据库 schema 中定义为 `DEFAULT current_timestamp() ON UPDATE CURRENT_TIMESTAMP`,因此显式 `UPDATE` 是可行且语义明确的(无迁移需求)。
- 建议后续将统计接口限制为管理员访问(加入 `[Authorize(Roles = "Admin")]` 或使用策略)。

View File

@@ -0,0 +1,40 @@
# 修改登录逻辑并新增后台统计接口
## 1. 问题描述
当前系统登录逻辑(`POST /api/Login/Login`
- 使用微信 `code` 换取 `openid` 并以 `openid` 作为 `UserKey`
- 若数据库中不存在该 `UserKey`,系统会自动插入一条记录。
- 如果用户已存在count != 0当前实现不对用户表做任何更新包括 `UpdateTime`)。
另外,后台管理侧需要一个简洁的统计接口,用于展示系统关键指标(活跃用户、会话统计等),当前并没有统一的聚合接口。
同时,管理端首页当前通过拉取用户列表与会话列表在前端计算统计数据,调用开销大且不一致。需要改为直接使用聚合统计接口返回的四个指标。
## 2. 目标与提议
- 修改登录逻辑:当用户存在时,**更新该用户的 `UpdateTime` 为本次接口调用时间**UTC/服务器当前时间)。该字段将作为“最近登录/活跃”标准。
- 新增后台统计接口:`GET /api/Admin/QueryStats`返回单行统计数据JSON包含
1. ActiveUsers最近 7 天有登录(`UpdateTime` 在最近 7 天)且 `UserKey``PhoneNumber` 都不为空的用户数
2. TotalConversations`xcx_conversation` 表的总记录数
3. TodayNewConversations`xcx_conversation` 表中 `CreateTime` 在“今天”内的记录数
4. TotalUsers`xcx_users` 表中 `UserKey``PhoneNumber` 都不为空的用户总数
- 前端首页统计卡片改造管理端首页HomeSHALL 直接调用 `GET /api/Admin/QueryStats`,并将返回的四个数字映射到四张统计卡片中(不再通过 QueryUsers/QueryConversations 在前端计算)。
## 3. 兼容性与风险
- 数据库层无需新增字段:`xcx_users.UpdateTime` 已存在且带有 `ON UPDATE CURRENT_TIMESTAMP`。但显式执行 `UPDATE ... SET UpdateTime = NOW()` 可以确保在登录时明确写入(而不是依赖其他 UPDATE 操作触发)。
- 该变更不影响现有注册/资料完善逻辑。
- 需要为统计接口添加合理权限控制(建议在未来迭代中加入鉴权)。
## 4. 数据验证与示例SQL
- 活跃用户最近7天
SELECT COUNT(1) FROM xcx_users WHERE UpdateTime >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND UserKey <> '' AND PhoneNumber <> '';
- 今日新增会话:
SELECT COUNT(1) FROM xcx_conversation WHERE CreateTime >= CURDATE() AND CreateTime < DATE_ADD(CURDATE(), INTERVAL 1 DAY);
- 总会话、总用户:分别用 COUNT(1) 聚合。

View File

@@ -0,0 +1,25 @@
# Tasks — 修改登录并新增后台统计接口
1. 代码实现(后端) ✅
- 修改 `WxCheckMvc/Controllers/LoginController.cs`:当已存在用户时执行 `UPDATE xcx_users SET UpdateTime = NOW() WHERE UserKey = @UserKey`
- 新增 `WxCheckMvc/Controllers/AdminController.cs``QueryStats()` GET 方法,返回聚合统计结果(见实现细节)。
2. 前端改造(后台管理首页) ✅
- 修改 `admin-web/src/views/Home.vue`:系统统计卡片改为调用 `GET /Admin/QueryStats`,四张卡片分别展示接口返回的四个数字。
3. 单元/集成测试(可选但建议)
- 登录逻辑:模拟已存在用户的登录,断言 `UpdateTime` 更新为最近时间(或至少发生了写入)。
- 统计接口:在已知测试数据上断言返回的四个指标正确。
4. 文档与 Spec ✅
- 更新 `openspec/specs/backend-api/spec.md`,记录 Login 行为变更和新增 `GET /api/Admin/QueryStats` 的 API Contract。
- 更新 `openspec/specs/backend-admin/spec.md`,记录管理端首页统计使用 `QueryStats`
- 在 openspec 新建变更记录(已完成)。
5. 发布与验证
- 运行 `dotnet build` 确认编译通过
- 本地或测试环境调用接口验证结果
6. 可选:鉴权与监控(后续迭代)
-`QueryStats` 加上管理员鉴权JWT/Role
- 添加 Prometheus 或应用层度量埋点,记录统计接口调用频次

View File

@@ -108,6 +108,22 @@
- **THEN** 显示抽屉式菜单(点击菜单按钮展开)
- **AND** 菜单包含:首页、会话记录管理、用户管理等功能入口
实现约束(移动端可靠性):
- 手机端抽屉菜单 SHOULD 使用 Element Plus `el-drawer` 自带的显示/隐藏机制
- 菜单 SHOULD 使用 Element Plus `el-menu`router 模式)
- 抽屉的打开 SHALL 由 Header 菜单按钮通过事件触发
- 不依赖 class + CSS transform 的方式实现“展开/收起”(避免出现 class 变化但 UI 无响应)
#### Scenario: 手机端点击按钮打开菜单(事件触发)
- **WHEN** 管理员在手机端点击 Header 的菜单按钮
- **THEN** 系统通过事件触发打开 `el-drawer`
- **AND** 抽屉内显示 `el-menu` 导航项
#### Scenario: 手机端点击菜单项自动收起
- **WHEN** 管理员在抽屉中点击任一菜单项
- **THEN** 路由跳转到对应页面
- **AND** 抽屉自动关闭
### Requirement: 模块化设计
前端管理网站 SHALL 采用模块化设计思路。
@@ -173,6 +189,23 @@
- **WHEN** 管理端提交包含多个筛选条件的查询请求
- **THEN** 系统返回同时满足所有条件的会话记录
### Requirement: 会话记录筛选(时间范围,移动端友好)
管理端会话记录页面 SHALL 提供便于移动端使用的时间范围筛选控件:
- 使用两个独立日期选择器:开始日期、结束日期
- 提供快捷范围选择器今天、最近3天、最近7天、最近30天
- 默认范围为最近7天
#### Scenario: 默认最近7天
- **WHEN** 管理员进入会话记录页面
- **THEN** 页面默认选中“最近7天”
- **AND** 查询请求携带对应的 `StartTime/EndTime`
#### Scenario: 移动端可用性
- **WHEN** 管理员在移动端访问会话记录页面
- **THEN** 快捷选择器与开始/结束日期选择器纵向排列
- **AND** 控件全宽显示,避免横向挤压
### Requirement: 查询用户列表(管理端)
系统 SHALL 提供接口返回可用用户列表,用于管理侧选择/筛选。
@@ -195,6 +228,25 @@
- **THEN** 返回已完善基础信息且未禁用的用户
- **AND** 结果按首次登录时间倒序排列
### Requirement: 首页统计(管理端)
系统 SHALL 提供聚合统计接口供管理端首页展示关键指标。
接口:`GET /api/Admin/QueryStats`
返回(成功):
- `success: true`
- `data`:包含以下字段的对象
- `ActiveUsers`:最近 7 天内 `UpdateTime` 有更新,且 `UserKey``PhoneNumber` 均不为空的用户数
- `TotalConversations``xcx_conversation` 总记录数
- `TodayNewConversations``xcx_conversation.CreateTime` 在“今天”内的记录数
- `TotalUsers``xcx_users``UserKey``PhoneNumber` 均不为空的用户总数
#### Scenario: 首页加载统计卡片
- **WHEN** 管理员进入首页Home
- **THEN** 前端调用 `GET /api/Admin/QueryStats`
- **AND** 将返回的四个数字分别展示到“系统统计卡片”的四个位置
## Known Limitations
- 当前接口未提供分页与导出能力(若管理端数据量很大,需在后续能力中补齐)。
- 当前未强制鉴权JWT 配置存在但未启用认证中间件/授权标注)。

View File

@@ -22,6 +22,7 @@
行为:
-`xcx_users` 不存在该 `UserKey`,系统 SHALL 自动插入一条用户记录(资料可为空)。
- 若用户 `IsDisabled = 1`,系统 SHALL 返回 `success: false`
-`xcx_users` 中已经存在该 `UserKey`,系统 SHALL 在登录流程中**更新该记录的 `UpdateTime` 为当前服务器时间**(用于“最近登录/活跃”判断)。
#### Scenario: 首次登录自动建档
- **WHEN** 提交的 `code` 能换取有效 `openid`
@@ -196,6 +197,23 @@
- **WHEN** Stream 中无可读消息
- **THEN** 返回 `success: true` 且数据为空
### Requirement: 管理端统计接口QueryStats
系统 SHALL 提供聚合统计接口 `GET /api/Admin/QueryStats`,返回单行 JSON`data` 对象字段如下:
- `ActiveUsers` (number):最近 7 天内 `UpdateTime` >= DATE_SUB(NOW(), INTERVAL 7 DAY) 且 `UserKey``PhoneNumber` 都不为空的用户数
- `TotalConversations` (number)`xcx_conversation` 的总记录数
- `TodayNewConversations` (number)`xcx_conversation``CreateTime >= CURDATE() AND CreateTime < DATE_ADD(CURDATE(), INTERVAL 1 DAY)` 的记录数
- `TotalUsers` (number)`xcx_users``UserKey``PhoneNumber` 都不为空的用户总数
响应(成功):
- `success: true`
- `data`: { `ActiveUsers`, `TotalConversations`, `TodayNewConversations`, `TotalUsers` }
行为/安全:
- 当前实现为匿名访问,建议在后续迭代中加上管理员鉴权(例如 `[Authorize(Roles = "Admin")]` 或策略)。
- 为保证查询性能,建议为 `xcx_users.UpdateTime``xcx_conversation.CreateTime` 建立适当索引(若数据量很大)。
## Known Limitations
- 当前 JWT 配置存在,但认证中间件与授权标注未启用;接口默认可匿名访问。
- `MessageType` 的过滤条件存在实现差异:部分接口仅在 `MessageType == 1` 时追加过滤(不覆盖 2