后端(补齐前端已入口但无接口的模块): - Coupon 控制器:center 领券中心 / my 我的券(未使用·已使用·已过期+计数)/ receive 领取 含库存与过期校验、重复领取拦截、领券后自动发消息 - Message 控制器:index 列表(分类筛选)/ read 单条已读 / readAll 全部已读 / unread 角标 支持 user 与 master 两种归属 前端(对应页面,全部接真接口): - pages/address/list 服务地址:列表 / 新增·编辑弹层 / 设为默认 / 删除 - pages/coupon/coupon 领券中心 + 我的券(双 Tab + 状态筛选 + 计数) - pages/message/message 消息中心:分类 / 未读圆点 / 点开自动已读 / 全部已读 / 跳订单 - mine.vue 三个入口(地址·优惠券·消息)由「开发中」改为真实路由 - merchant/workbench 服务地址入口接真实路由 - 首页 TabBar「消息」接入消息中心 - api.js 新增 coupon/message 共 7 个接口 后台登录页(UI 与前端主色调统一): - 左侧橙金渐变品牌区:logo 徽标 + 「优匠优服」立体字 + slogan + 460万+/99.9%/1762万 平台实力 + 保障条 - 右侧白色登录卡:橙色渐变按钮、圆角输入框、错误提示条 - 保留 FastAdmin 全部挂钩(token/验证码/keeplogin/@AdminLoginFormBegin 等 14 项) 验证:5 套用例 178 项断言全通过(47+65+27+24+15),配置自检 8/8
86 行
4.5 KiB
JavaScript
86 行
4.5 KiB
JavaScript
/**
|
||
* Phase 4 验证:优惠券 + 消息中心(对应「我的中心」新接入的模块)
|
||
* 运行:node tests/phase4_coupon_message.js
|
||
*/
|
||
const BASE = process.env.API_BASE || 'https://yjyf.gzyakj.com.cn/api';
|
||
|
||
let pass = 0, fail = 0;
|
||
const failures = [];
|
||
const ok = (m, e) => { pass++; console.log(' ✅ ' + m + (e ? ' → ' + e : '')); };
|
||
const bad = (m, e) => { fail++; failures.push(m + ' :: ' + e); console.log(' ❌ ' + m + ' → ' + e); };
|
||
const chk = (c, m, e) => (c ? ok(m, e) : bad(m, e || '断言失败'));
|
||
const sec = (t) => console.log('\n▌' + t);
|
||
|
||
async function api(path, opt) {
|
||
const o = opt || {};
|
||
const headers = {};
|
||
if (o.token) headers.token = o.token;
|
||
let body;
|
||
if (o.data) {
|
||
headers['content-type'] = 'application/x-www-form-urlencoded';
|
||
body = new URLSearchParams(o.data).toString();
|
||
}
|
||
const res = await fetch(BASE + path, { method: o.method || 'GET', headers, body });
|
||
const text = await res.text();
|
||
try { return JSON.parse(text); } catch (e) { throw new Error('非 JSON: ' + text.slice(0, 120)); }
|
||
}
|
||
|
||
(async () => {
|
||
const mobile = '136' + String(Date.now()).slice(-8);
|
||
|
||
console.log('═══════════════════════════════════════════════════');
|
||
console.log(' Phase 4 验证:优惠券 + 消息中心');
|
||
console.log('═══════════════════════════════════════════════════');
|
||
|
||
const login = await api('/auth/login', { method: 'POST', data: { mobile: mobile, code: '123456' } });
|
||
const token = login.data.token;
|
||
chk(login.code === 1, '账号登录');
|
||
|
||
sec('优惠券');
|
||
let r = await api('/coupon/center', { token: token });
|
||
chk(r.code === 1 && r.data.list.length >= 1, '领券中心返回可领券', '共 ' + (r.data.list || []).length + ' 张');
|
||
const c0 = r.data.list[0];
|
||
chk(!!c0.value_text && !!c0.rule_text, '券面值/使用门槛文案就绪', c0.value_text + ' / ' + c0.rule_text);
|
||
chk(c0.received_by_me === false, '未领取状态正确');
|
||
|
||
r = await api('/coupon/receive', { method: 'POST', token: token, data: { coupon_id: c0.id } });
|
||
chk(r.code === 1, '领取优惠券成功', r.msg);
|
||
|
||
r = await api('/coupon/receive', { method: 'POST', token: token, data: { coupon_id: c0.id } });
|
||
chk(r.code !== 1, '重复领取被拦截', r.msg);
|
||
|
||
r = await api('/coupon/my?status=unused', { token: token });
|
||
chk(r.code === 1 && r.data.list.length === 1, '我的券-未使用有 1 张', JSON.stringify(r.data.counts));
|
||
|
||
r = await api('/coupon/center', { token: token });
|
||
const c1 = (r.data.list || []).find(c => Number(c.id) === Number(c0.id));
|
||
chk(c1 && c1.received_by_me === true, '领券中心回显「已领取」');
|
||
|
||
sec('消息中心');
|
||
r = await api('/message/index', { token: token });
|
||
chk(r.code === 1 && r.data.list.length >= 1, '领券后自动生成消息', '共 ' + r.data.total + ' 条');
|
||
chk(r.data.unread >= 1, '未读数统计正确', 'unread=' + r.data.unread);
|
||
|
||
const msg = r.data.list[0];
|
||
r = await api('/message/read', { method: 'POST', token: token, data: { id: msg.id } });
|
||
chk(r.code === 1, '单条标记已读');
|
||
|
||
r = await api('/message/unread', { token: token });
|
||
chk(r.code === 1 && r.data.unread === 0, '未读数归零', 'unread=' + r.data.unread);
|
||
|
||
r = await api('/message/index?category=order', { token: token });
|
||
chk(r.code === 1, '按分类筛选可用', '订单类 ' + r.data.total + ' 条');
|
||
|
||
r = await api('/message/readAll', { method: 'POST', token: token, data: {} });
|
||
chk(r.code === 1, '全部标记已读接口可用', '影响 ' + r.data.count + ' 条');
|
||
|
||
r = await api('/message/index', { token: '', silent: true });
|
||
chk(r.code === 401 || r.code !== 1, '未登录访问消息被拦截', r.msg);
|
||
|
||
console.log('\n═══════════════════════════════════════════════════');
|
||
console.log(` 结果:通过 ${pass} 项 / 失败 ${fail} 项`);
|
||
if (fail) { console.log(' 失败明细:'); failures.forEach(f => console.log(' - ' + f)); process.exitCode = 1; }
|
||
else console.log(' 🎉 优惠券与消息中心链路通过');
|
||
console.log('═══════════════════════════════════════════════════');
|
||
})().catch(e => { console.error('\n💥 中断: ' + e.message); process.exitCode = 1; });
|