feat(list): 订单/通知/组局列表分页,生产 NODE_ENV 切换 production
- 仓储层 getOrders/getOrdersForUser/getActivities/getUserNotifications 支持 LIMIT/OFFSET,新增 countOrders/countOrdersForUser/countActivities/getUserNotificationTotal - 接口支持 page/pageSize/status/keyword/payType/creatorId/category,返回 list/total/hasMore;未传 page 保持旧数组格式,兼容后台与工作台 - 小程序订单/通知/组局三页触底加载,筛选与搜索下推服务端,避免只过滤当前页 - 新增 116 套件/758 用例,全绿;线上实测分页与筛选接口正常
这个提交包含在:
@@ -515,7 +515,7 @@ describe('ClientFeatures (TDD 测试套件: 用户端功能闭环)', () => {
|
||||
|
||||
const repoPath = path.join(serverSrcDir, 'db', 'repository.ts');
|
||||
const repoContent = fs.readFileSync(repoPath, 'utf-8');
|
||||
expect(repoContent).toContain("WHERE (u.status IS NULL OR u.status != '已封禁')");
|
||||
expect(repoContent).toContain("(u.status IS NULL OR u.status != '已封禁')");
|
||||
expect(repoContent).toContain("UPDATE activities SET status = 'CANCELLED' WHERE creator_id = ? AND status = 'RECRUITING'");
|
||||
|
||||
const rolePath = path.join(rootDir, 'pwuniapp', 'common', 'role.js');
|
||||
|
||||
60
server/tests/pagination.spec.ts
普通文件
60
server/tests/pagination.spec.ts
普通文件
@@ -0,0 +1,60 @@
|
||||
// 已读取AGENTS.md
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* 列表分页闸门:订单 / 消息通知 / 组局
|
||||
* 要求:服务端支持 page/pageSize 并返回 total/hasMore;不传 page 时保持旧数组格式兼容;
|
||||
* 小程序三页必须支持触底加载更多。
|
||||
*/
|
||||
describe('列表分页(订单 / 通知 / 组局)', () => {
|
||||
const root = path.resolve(__dirname, '../../');
|
||||
const read = (rel: string) => fs.readFileSync(path.join(root, rel), 'utf-8');
|
||||
const repo = read('server/src/db/repository.ts');
|
||||
const indexCode = read('server/src/index.ts');
|
||||
const api = read('pwuniapp/common/api.js');
|
||||
const orderList = read('pwuniapp/pages/order/list.vue');
|
||||
const notifications = read('pwuniapp/pages/user/notifications.vue');
|
||||
const activityList = read('pwuniapp/pages/activity/list.vue');
|
||||
|
||||
it('仓储层必须使用 LIMIT/OFFSET 分页,而不是一次拉全量', () => {
|
||||
expect(repo).toContain('LIMIT ${pageSize} OFFSET ${offset}');
|
||||
expect(repo).toContain('LIMIT ${take} OFFSET ${skip}');
|
||||
expect(repo).toContain('async countOrders(');
|
||||
expect(repo).toContain('async countOrdersForUser(');
|
||||
expect(repo).toContain('async countActivities(');
|
||||
expect(repo).toContain('async getUserNotificationTotal(');
|
||||
});
|
||||
|
||||
it('接口必须返回 total/hasMore,未传 page 时保持旧数组格式', () => {
|
||||
expect(indexCode).toContain("if (Number(req.query.page) > 0)");
|
||||
expect(indexCode).toContain('hasMore: (page - 1) * pageSize');
|
||||
// 旧调用(管理后台 / 工作台)不传 page 仍拿数组
|
||||
expect(indexCode).toContain('const paged = Number(req.query.page) > 0;');
|
||||
expect(indexCode).toContain('data: orders');
|
||||
expect(indexCode).toContain('data: acts');
|
||||
});
|
||||
|
||||
it('小程序 API 层传递分页参数并保持旧调用兼容', () => {
|
||||
expect(api).toContain('page=${Math.floor(Number(options.page))}');
|
||||
expect(api).toContain('pageSize=${Math.floor(Number(options.pageSize) || 20)}');
|
||||
expect(api).toContain("return paged ? { list: [], total: 0, page: 1, pageSize: 20, hasMore: false } : [];");
|
||||
expect(api).toContain('if (Number(extra.page) > 0)');
|
||||
});
|
||||
|
||||
it('订单 / 通知 / 组局页面必须支持触底加载更多', () => {
|
||||
expect(orderList).toContain('onReachBottom');
|
||||
expect(orderList).toContain('loadLiveOrders(false)');
|
||||
expect(notifications).toContain('@scrolltolower="loadMore"');
|
||||
expect(notifications).toContain('loadData(false)');
|
||||
expect(activityList).toContain('onReachBottom');
|
||||
expect(activityList).toContain('fetchLiveActivities(false)');
|
||||
});
|
||||
|
||||
it('筛选与搜索必须下推到服务端,避免只过滤当前页造成漏数据', () => {
|
||||
expect(orderList).toContain('status: currentTab.value');
|
||||
expect(activityList).toContain('keyword: searchKeyword.value.trim()');
|
||||
expect(activityList).toContain('payType: currentTab.value');
|
||||
expect(activityList).toContain('creatorId: currentTab.value');
|
||||
});
|
||||
});
|
||||
@@ -21,16 +21,16 @@ describe('第十二批读接口数据隔离闸门 (订单 / 我的组局 / 分
|
||||
it('仓储层提供按用户隔离的 getOrdersForUser', () => {
|
||||
expect(repoCode).toContain('async getOrdersForUser(');
|
||||
const start = repoCode.indexOf('async getOrdersForUser(');
|
||||
const block = repoCode.slice(start, start + 1400);
|
||||
const block = repoCode.slice(start, start + 2200);
|
||||
expect(block).toContain('buyer_id = ?');
|
||||
expect(block).toContain('claimed_by = ?');
|
||||
expect(block).toContain('companion_name IN (SELECT name FROM companions');
|
||||
});
|
||||
|
||||
it('订单列表:管理端全量,普通用户仅本人相关', () => {
|
||||
const block = routeBlock("app.get('/api/orders',");
|
||||
const block = routeBlock("app.get('/api/orders',", 1800);
|
||||
expect(block).toContain('isVerifiedAdminRequest(req)');
|
||||
expect(block).toContain('repo.getOrdersForUser(uid)');
|
||||
expect(block).toContain('repo.getOrdersForUser(uid,');
|
||||
expect(block).toContain("'查看订单列表'");
|
||||
});
|
||||
});
|
||||
@@ -50,7 +50,7 @@ describe('第十二批读接口数据隔离闸门 (订单 / 我的组局 / 分
|
||||
describe('3. 小程序端订单列表携带当前用户身份', () => {
|
||||
it('getOrders 必须带上当前用户 id', () => {
|
||||
const start = apiCode.indexOf('export async function getOrders(');
|
||||
const block = apiCode.slice(start, start + 300);
|
||||
const block = apiCode.slice(start, start + 900);
|
||||
expect(block).toContain('/orders?userId=${normalizeUserId()}');
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户