前端: - 新增 EmptyLayout 空壳布局(无侧边栏/导航) - 新增 requestNoAuth Axios 实例(不注入 token) - 新增 integralExternal 路由模块(/integral-external/*) - permission.js 加入 whiteListPrefixes 前缀白名单跳过登录 - 新增 phoneDesensitize 手机号脱敏过滤器 - 新增三个免认证页面: · 积分订单页(/integral-external/order) · 用户积分页(/integral-external/user,手机号脱敏) · 用户积分明细子页(/integral-external/user/integral-detail) 后端: - 新增 ExternalIntegralController(无 @PreAuthorize) · GET /api/external/integral/order/list · GET /api/external/integral/user/list · POST /api/external/integral/log/list - WebSecurityConfig 加入 /api/external/integral/** permitAll 文档与工具: - 新增 coding plan、schedule、测试报告 - 新增 start-backend.sh / start-frontend.sh 本地启动脚本 - 新增 .mvn/wrapper/maven-wrapper.properties Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
23 KiB
积分模块新增页面 — Coding Plan
版本:v1.0 日期:2026-03-30 范围:管理后台(backend-adminend)新增积分订单、用户积分、用户积分明细三个独立页面
1. 需求概述
在管理后台中新增三个独立页面,用于积分业务的外部查看与运营。所有页面需跳过用户登录验证,按后端 API 最小修改原则,尽量复用现有后端接口。
| 序号 | 页面 | 参考原页面 | 说明 |
|---|---|---|---|
| 1 | 积分订单 | /order/index |
新建独立页面,展示积分相关订单 |
| 2 | 用户积分 | /user/index |
新建独立页面,增加 wa_users 相关字段 |
| 3 | 用户积分明细 | 用户管理 → 账户详情 → 积分明细 | 子页面,复用 /admin/user/integral/list 接口 |
2. 技术架构分析
2.1 技术栈
管理后台前端基于 Vue 2 + Vue CLI + Element UI + Vue Router (history mode) + Vuex + Axios。
2.2 现有认证机制
认证逻辑位于 src/permission.js,通过 router.beforeEach 全局守卫实现。未登录时除白名单路由外,一律重定向至 /login。
白名单当前值:['/login', '/auth-redirect']
请求拦截器(src/utils/request.js)会在 header 中附加 Authori-zation token,后端返回 401 时自动跳转登录页。
2.3 关键参考文件
| 文件 | 说明 |
|---|---|
src/views/order/index.vue |
订单列表页,约 40k 行,含筛选/表格/分页/操作 |
src/views/user/list/index.vue |
用户管理页,含多条件筛选、用户详情弹窗 |
src/views/user/integral/index.vue |
积分日志页(242 行),表格 + 搜索 + 分页 |
src/api/integral.js |
积分接口:integralListApi → POST /admin/user/integral/list |
src/api/user.js |
用户接口:userListApi → GET /admin/user/list |
src/router/modules/order.js |
订单路由定义 |
src/router/modules/user.js |
用户路由定义 |
src/router/index.js |
主路由,含 constantRoutes 和白名单 |
src/permission.js |
全局路由守卫(登录校验) |
src/utils/request.js |
Axios 封装,token 注入 & 401 拦截 |
2.4 wa_users 表字段(需要在用户积分页展示)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
int | 主键 |
username |
string | 用户名 |
nickname |
string | 昵称 |
mobile |
string | 手机号 |
money |
BigDecimal | 账户余额 |
selfBonus |
BigDecimal | 个人奖金 |
shareBonus |
BigDecimal | 分享奖金 |
score |
BigDecimal | 积分 |
level |
int | 等级 |
status |
int | 状态(0=禁用, 1=启用) |
isVip |
int | VIP(0=否, 1=是) |
isResell |
int | 可转卖(0=否, 1=是) |
joinTime |
timestamp | 注册时间 |
lastTime |
timestamp | 最后登录 |
3. 整体方案设计
3.1 目录结构规划
src/
├── views/
│ └── integral-external/ # 新增:积分外部页面目录
│ ├── order/
│ │ └── index.vue # 积分订单页面
│ ├── user/
│ │ └── index.vue # 用户积分页面
│ └── user-integral-detail/
│ └── index.vue # 用户积分明细子页面
├── api/
│ └── integralExternal.js # 新增:积分外部页面 API 集合
├── router/
│ └── modules/
│ └── integralExternal.js # 新增:积分外部路由模块
└── layout/
└── EmptyLayout.vue # 新增:空白布局(无侧边栏/顶栏)
3.2 跳过登录验证方案
采用多层级免登录策略,确保页面完全绕过认证:
第一层:路由白名单
在 src/permission.js 的 whiteList 中添加新页面路径前缀:
const whiteList = ['/login', '/auth-redirect', '/integral-external'];
同时修改白名单匹配逻辑,从精确匹配改为前缀匹配:
// 修改前
if (whiteList.indexOf(to.path) !== -1)
// 修改后
if (whiteList.some(path => to.path.startsWith(path)))
第二层:无 token 请求支持
新建一个不注入 token、不拦截 401 的 Axios 实例 requestNoAuth,供外部页面 API 使用:
// src/utils/requestNoAuth.js
import axios from 'axios';
import { Message } from 'element-ui';
import SettingMer from '@/utils/settingMer';
const service = axios.create({
baseURL: SettingMer.apiBaseURL,
timeout: 60000,
});
// 不注入 token,不拦截 401 跳转
service.interceptors.response.use(
(response) => {
const res = response.data;
if (res.code !== 0 && res.code !== 200) {
Message({ message: res.msg || '请求失败', type: 'error' });
return Promise.reject(new Error(res.msg || '请求失败'));
}
return res.data;
},
(error) => {
Message({ message: '网络请求失败', type: 'error' });
return Promise.reject(error);
},
);
export default service;
第三层:空白布局
新建 EmptyLayout.vue,不包含侧边栏、顶栏和权限组件,作为外部页面的容器:
<template>
<div class="integral-external-layout">
<router-view />
</div>
</template>
4. 各页面详细设计
4.1 积分订单页面
路由:/integral-external/order
参考:src/views/order/index.vue
功能要点
从原订单页面中提取积分订单相关的核心功能,去除权限校验(v-hasPermi)和管理操作(编辑、发货、退款等),保留只读展示。
筛选条件
| 筛选项 | 类型 | 说明 |
|---|---|---|
| 订单状态 | RadioGroup | 全部/未支付/未发货/待收货/交易完成 等 |
| 时间选择 | DateRangePicker | 快捷选项 + 自定义范围 |
| 订单号 | Input | 精确搜索 |
表格列
| 列 | 字段 | 宽度 |
|---|---|---|
| 订单号 | orderId |
210 |
| 订单类型 | orderType |
110 |
| 收货人 | realName |
100 |
| 商品信息 | productList |
400 |
| 支付金额 | payPrice |
100 |
| 支付方式 | payType |
100 |
| 订单状态 | status |
100 |
| 创建时间 | createTime |
150 |
API 复用
直接复用现有订单列表接口。需确认后端是否允许无 token 调用,若不允许,需后端新增一个免认证的订单查询接口(或在已有接口上增加免认证标注)。
// src/api/integralExternal.js
export function getIntegralOrderList(params) {
return requestNoAuth({
url: '/admin/order/list', // 复用原接口或后端新增免认证接口
method: 'get',
params,
});
}
实现步骤
- 复制
order/index.vue为基础模板 - 删除所有
v-hasPermi权限指令 - 删除操作列(编辑价格、发货、退款等按钮)
- 删除导出功能
- 将 API 调用替换为
requestNoAuth版本 - 简化订单类型筛选,只保留积分相关类型
- 去除 Vuex store 依赖
4.2 用户积分页面
路由:/integral-external/user
参考:src/views/user/list/index.vue
功能要点
基于用户列表页精简,增加 wa_users 表的积分/奖金相关字段展示,提供积分明细跳转入口。
筛选条件
| 筛选项 | 类型 | 说明 |
|---|---|---|
| 用户搜索 | Input | 姓名/手机号/用户名 |
| 时间选择 | DateRangePicker | 注册时间范围 |
表格列
| 列 | 字段 | 来源 | 说明 |
|---|---|---|---|
| 用户ID | uid |
CRMEB | 系统用户ID |
| 用户昵称 | nickname |
CRMEB | — |
| 手机号 | phone |
CRMEB | — |
| 系统积分 | integral |
CRMEB | CRMEB 系统积分 |
| WA用户名 | wa_username |
wa_users | WA系统用户名 |
| 账户余额 | wa_money |
wa_users | WA账户余额 |
| 个人奖金 | wa_selfBonus |
wa_users | 可提现奖金 |
| 分享奖金 | wa_shareBonus |
wa_users | 推荐奖金 |
| WA积分 | wa_score |
wa_users | WA系统积分 |
| 用户等级 | wa_level |
wa_users | — |
| 状态 | wa_status |
wa_users | 启用/禁用 |
| 注册时间 | createTime |
CRMEB | — |
| 操作 | — | — | 查看积分明细 |
API 方案
方案 A(推荐 — 最小后端修改):前端分别调用用户列表接口和 WA 用户信息接口,在前端做数据合并。
// 复用原用户列表
export function getUserListNoAuth(params) {
return requestNoAuth({
url: '/admin/user/list',
method: 'get',
params,
});
}
// 复用前端 WA 用户信息接口(需确认是否免认证)
export function getWaUserInfo(userId) {
return requestNoAuth({
url: '/api/front/wa/user/info',
method: 'post',
data: { userId },
});
}
方案 B(若后端配合):后端新增一个聚合接口,一次性返回 CRMEB 用户 + wa_users 的合并数据。
操作列
"查看积分明细" 按钮,点击后跳转至用户积分明细子页面,携带 uid 参数:
this.$router.push({
path: '/integral-external/user/integral-detail',
query: { uid: row.uid },
});
实现步骤
- 以
user/list/index.vue为参考创建精简版页面 - 删除所有权限指令、分组/标签/等级筛选、操作按钮(编辑、设为分销员等)
- 删除 Tab 切换(全部/有效/无效用户)
- 在表格中增加 wa_users 字段列
- 实现前端数据合并逻辑(逐行匹配或批量查询)
- 添加"查看积分明细"操作按钮
- 将所有 API 替换为
requestNoAuth版本
4.3 用户积分明细子页面
路由:/integral-external/user/integral-detail
参考:src/views/user/integral/index.vue(242 行)
后端 API:POST /admin/user/integral/list(复用)
功能要点
该页面完整复用原积分日志页的展示逻辑,通过 URL query 参数 uid 锁定指定用户,隐藏"用户搜索"字段。
筛选条件
| 筛选项 | 类型 | 说明 |
|---|---|---|
| 用户ID | 隐藏字段 | 从 URL query uid 自动获取 |
| 时间选择 | DateRangePicker | 日期范围 |
表格列(完全复用原页面)
| 列 | 字段 | 说明 |
|---|---|---|
| ID | id |
记录ID |
| 用户ID | uid |
— |
| 用户昵称 | nickName |
— |
| 标题 | title |
积分变动标题 |
| 积分变动 | integral |
+/- 显示 |
| 剩余积分 | balance |
变动后余额 |
| 类型 | type |
增加(1)/扣减(2) |
| 关联类型 | linkType |
订单/签到/系统 |
| 状态 | status |
订单创建/冻结期/完成/失效 |
| 备注 | mark |
— |
| 创建时间 | createTime |
— |
API 复用
export function getIntegralLogNoAuth(data) {
return requestNoAuth({
url: '/admin/user/integral/list', // 直接复用原接口
method: 'post',
data,
});
}
页面头部信息
在表格上方展示当前用户的积分概览卡片:
┌──────────────────────────────────────┐
│ 用户:张三 (UID: 1001) │
│ 积分:1,200 个人奖金:350 │
│ [← 返回用户积分列表] │
└──────────────────────────────────────┘
字段说明:积分取自
eb_user表的integral字段(BigDecimal,用户剩余积分);个人奖金取自wa_users表的selfBonus字段。
实现步骤
- 复制
user/integral/index.vue作为基础 - 从 URL query 中读取
uid,自动注入搜索参数 - 隐藏"用户搜索"和"用户ID"输入框(已通过 query 锁定)
- 添加顶部用户信息概览卡片
- 添加"返回"按钮
- 替换 API 为
requestNoAuth版本
5. 路由配置
5.1 新增路由模块
// src/router/modules/integralExternal.js
const EmptyLayout = () => import('@/layout/EmptyLayout');
const integralExternalRouter = {
path: '/integral-external',
component: EmptyLayout,
redirect: '/integral-external/order',
hidden: true, // 不在侧边栏显示
children: [
{
path: 'order',
component: () => import('@/views/integral-external/order/index'),
name: 'IntegralExternalOrder',
meta: { title: '积分订单' },
},
{
path: 'user',
component: () => import('@/views/integral-external/user/index'),
name: 'IntegralExternalUser',
meta: { title: '用户积分' },
},
{
path: 'user/integral-detail',
component: () => import('@/views/integral-external/user-integral-detail/index'),
name: 'IntegralExternalUserDetail',
meta: { title: '用户积分明细' },
},
],
};
export default integralExternalRouter;
5.2 注册路由
在 src/router/index.js 中将新模块加入 constantRoutes:
import integralExternalRouter from './modules/integralExternal';
export const constantRoutes = [
integralExternalRouter, // 积分外部页面(免登录)
storeRouter,
orderRouter,
// ...其余路由
];
5.3 修改权限守卫
在 src/permission.js 中扩展白名单:
const whiteList = ['/login', '/auth-redirect', '/integral-external'];
// 匹配逻辑改为前缀匹配
if (whiteList.some(path => to.path.startsWith(path))) {
next();
}
6. 后端 API 影响评估
6.1 可直接复用的接口
| 接口 | Method | 免认证现状 | 所需改动 |
|---|---|---|---|
/admin/user/integral/list |
POST | 需认证 | 需后端为外部调用新增免认证入口,或前端伪造 token |
/admin/user/list |
GET | 需认证 | 同上 |
/admin/order/list |
GET | 需认证 | 同上 |
6.2 推荐的后端最小改动方案
按照"最小修改原则",建议后端在现有 Controller 基础上新增一套免认证的映射路径,内部直接调用相同的 Service 方法:
新路径 → 复用的 Service 方法
/api/external/integral/order/list → OrderService.list()
/api/external/integral/user/list → UserService.list()(补充 wa_users 字段)
/api/external/integral/log/list → IntegralService.list()
只需新建一个 ExternalIntegralController,加 @RestController 免认证注解,约 50-80 行代码。
6.3 wa_users 字段集成
方案 A:后端在用户列表接口返回中直接 JOIN wa_users 表,新增字段返回。 方案 B:前端先拿用户列表,再批量查 wa_users 信息,前端做合并。
推荐方案 A(后端改动更少,前端实现更简单)。
7. 开发任务清单
Phase 1:基础设施(预计 0.5 天)
| # | 任务 | 文件 |
|---|---|---|
| 1.1 | 创建 EmptyLayout.vue 空白布局 |
src/layout/EmptyLayout.vue |
| 1.2 | 创建 requestNoAuth.js 免认证请求实例 |
src/utils/requestNoAuth.js |
| 1.3 | 创建 integralExternal.js 路由模块 |
src/router/modules/integralExternal.js |
| 1.4 | 注册路由到 constantRoutes |
src/router/index.js |
| 1.5 | 修改 permission.js 白名单 |
src/permission.js |
| 1.6 | 创建 integralExternal.js API 文件 |
src/api/integralExternal.js |
Phase 2:积分订单页面(预计 1 天)
| # | 任务 |
|---|---|
| 2.1 | 基于 order/index.vue 创建精简版积分订单页 |
| 2.2 | 去除权限校验、操作按钮、导出功能 |
| 2.3 | 接入 requestNoAuth 请求 |
| 2.4 | 测试筛选、分页、数据展示 |
Phase 3:用户积分页面(预计 1.5 天)
| # | 任务 |
|---|---|
| 3.1 | 基于 user/list/index.vue 创建精简版用户积分页 |
| 3.2 | 去除高级筛选、权限、操作按钮 |
| 3.3 | 增加 wa_users 字段列(奖金、积分、余额等) |
| 3.4 | 实现数据合并逻辑(前端或后端) |
| 3.5 | 添加"查看积分明细"跳转按钮 |
| 3.6 | 测试数据展示与跳转 |
Phase 4:用户积分明细子页面(预计 0.5 天)
| # | 任务 |
|---|---|
| 4.1 | 基于 user/integral/index.vue 创建积分明细页 |
| 4.2 | 通过 URL query 读取 uid 并锁定用户 |
| 4.3 | 添加用户积分概览卡片 |
| 4.4 | 添加返回按钮 |
| 4.5 | 接入 requestNoAuth 请求 |
| 4.6 | 测试分页、筛选、数据展示 |
Phase 5:联调与验收(预计 0.5 天)
| # | 任务 |
|---|---|
| 5.1 | 无 token 状态下完整流程测试 |
| 5.2 | 页面间跳转逻辑验证 |
| 5.3 | 后端免认证接口联调 |
| 5.4 | 兼容性和响应式测试 |
总计预估工时:4 天
8. 测试方案
8.1 免登录访问测试
| 编号 | 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|---|
| A-01 | 无 token 直接访问积分订单页 | 清除浏览器所有 cookie/sessionStorage,直接访问 /integral-external/order |
页面正常加载,不跳转至 /login |
| A-02 | 无 token 直接访问用户积分页 | 同上,访问 /integral-external/user |
页面正常加载,不跳转至 /login |
| A-03 | 无 token 直接访问积分明细页 | 同上,访问 /integral-external/user/integral-detail?uid=1 |
页面正常加载,不跳转至 /login |
| A-04 | 免登录页面不影响原有认证 | 无 token 访问 /order/index(原页面) |
仍然正常跳转至 /login |
| A-05 | 已登录用户访问免登录页面 | 管理员登录后访问 /integral-external/order |
页面正常加载,不受登录态影响 |
8.2 积分订单页面测试
| 编号 | 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|---|
| B-01 | 默认加载 | 进入页面 | 表格展示订单列表,分页信息正确 |
| B-02 | 按订单状态筛选 | 依次点击"未支付""未发货""交易完成"等状态 | 表格数据按状态正确过滤,数量标签更新 |
| B-03 | 按时间范围筛选 | 选择起止日期 | 仅显示时间范围内的订单 |
| B-04 | 按订单号搜索 | 输入完整订单号,点击搜索 | 精确匹配到对应订单 |
| B-05 | 重置筛选条件 | 设置筛选条件后点击重置 | 所有筛选项恢复默认,表格展示全部数据 |
| B-06 | 分页切换 | 切换页码、修改每页显示数 | 数据正确刷新,分页器状态正确 |
| B-07 | 空数据状态 | 搜索不存在的订单号 | 表格显示空状态提示,无 JS 报错 |
| B-08 | 无操作列 | 检查表格列 | 不存在编辑、发货、退款等操作按钮 |
8.3 用户积分页面测试
| 编号 | 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|---|
| C-01 | 默认加载 | 进入页面 | 用户列表正常展示,含 CRMEB 和 wa_users 字段 |
| C-02 | wa_users 字段展示 | 查看表格列 | 个人奖金(selfBonus)、账户余额(money)等 wa_users 字段正确显示 |
| C-03 | 积分字段来源验证 | 对比数据库 eb_user.integral 值 |
页面显示的积分与 eb_user 表一致 |
| C-04 | wa_users 无关联数据 | 查看无 wa_users 记录的 CRMEB 用户行 | wa_users 相关列显示 - 或 0,不报错 |
| C-05 | 用户搜索 | 输入姓名/手机号搜索 | 正确过滤,支持模糊匹配 |
| C-06 | 跳转积分明细 | 点击某用户行的"查看积分明细" | 正确跳转至 /integral-external/user/integral-detail?uid=xxx |
| C-07 | 分页功能 | 切换页码和每页条数 | 数据正确刷新 |
| C-08 | 无权限指令残留 | 审查页面 DOM | 不存在 v-hasPermi 相关的隐藏元素或报错 |
8.4 用户积分明细子页面测试
| 编号 | 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|---|
| D-01 | 带 uid 参数加载 | 访问 ?uid=1001 |
自动加载 uid=1001 的积分明细,顶部概览卡片显示用户信息 |
| D-02 | 概览卡片数据验证 | 对比数据库值 | 积分值 = eb_user.integral,个人奖金 = wa_users.selfBonus |
| D-03 | 无 uid 参数访问 | 访问不带 ?uid 参数的页面 |
页面给出"缺少用户参数"提示,或重定向至用户积分列表 |
| D-04 | 无效 uid 访问 | 访问 ?uid=999999(不存在的用户) |
表格为空,概览卡片显示空状态,无 JS 报错 |
| D-05 | 时间范围筛选 | 选择日期范围 | 积分明细按时间正确过滤 |
| D-06 | 积分变动显示 | 查看积分变动列 | 增加显示绿色 +,扣减显示红色 - |
| D-07 | 状态与关联类型 | 查看状态和关联类型列 | 订单创建/冻结期/完成/失效 正确渲染标签颜色;订单/签到/系统 正确显示 |
| D-08 | 返回按钮 | 点击"返回用户积分列表" | 正确跳转回 /integral-external/user |
| D-09 | 分页功能 | 切换页码(15/30/45/60) | 数据正确刷新 |
8.5 接口与数据测试
| 编号 | 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|---|
| E-01 | 免认证接口可达性 | 无 token 调用各外部接口 | 返回 200 及正确业务数据,不返回 401 |
| E-02 | 原认证接口不受影响 | 无 token 调用原 /admin/user/list 等接口 |
仍返回 401 |
| E-03 | 接口仅读不写 | 尝试对免认证接口发送写操作请求 | 返回 403 或方法不允许 |
| E-04 | 大数据量分页 | 请求 limit=60,数据总量 > 1000 | 分页正确,响应时间 < 3s |
| E-05 | 边界参数 | page=0、limit=-1、uid=null 等异常参数 | 接口返回友好错误信息,不产生 500 |
| E-06 | 数据脱敏验证 | 检查返回的手机号字段 | 中间 4 位做掩码处理(如 138****8888) |
8.6 兼容性与 UI 测试
| 编号 | 测试场景 | 预期结果 |
|---|---|---|
| F-01 | Chrome 最新版 | 页面布局正常,功能正常 |
| F-02 | Firefox 最新版 | 页面布局正常,功能正常 |
| F-03 | Edge 最新版 | 页面布局正常,功能正常 |
| F-04 | 1920×1080 分辨率 | 表格列宽合理,无横向滚动条溢出 |
| F-05 | 1366×768 分辨率 | 表格可横向滚动,筛选栏自动换行 |
| F-06 | EmptyLayout 布局验证 | 页面无侧边栏、无顶部导航栏、无面包屑 |
| F-07 | 加载状态 | 数据加载中显示 loading 动画 |
8.7 测试执行时间规划
| 阶段 | 内容 | 预计时间 |
|---|---|---|
| 冒烟测试 | Phase 1 基础设施完成后,验证 A-01 ~ A-05 免登录链路 | 0.5h |
| 功能测试 | 每个页面开发完成后,执行对应 B/C/D 组用例 | 每页面 1~2h |
| 接口联调测试 | 后端免认证接口就绪后,执行 E 组用例 | 1h |
| 回归测试 | 全部开发完成后,执行全量用例 | 2h |
| 兼容性测试 | 回归通过后,执行 F 组用例 | 1h |
9. 注意事项
- 安全风险:免登录页面直接暴露后台数据,建议后端对免认证接口做 IP 白名单或 API Key 鉴权。
- 数据脱敏:用户手机号等敏感字段建议做中间位掩码处理(如
138****8888)。 - 接口幂等:所有免认证接口仅开放读取权限(GET/查询),禁止写操作。
- 路由隔离:新页面使用
EmptyLayout,与管理后台主布局完全隔离,避免引入侧边栏/权限组件的副作用。 - 组件依赖:新页面可复用 Element UI 组件,但避免引入需要 Vuex store(如用户信息、权限)的业务组件。