feat: 后端补齐前端所需模块 + 后台登录页品牌化重构

后端(补齐前端已入口但无接口的模块):
- 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
这个提交包含在:
linda
2026-10-28 16:51:45 +08:00
父节点 2af4cf72b5
当前提交 4734724ed2
共有 12 个文件被更改,包括 890 次插入 和 125 次删除

查看文件

@@ -0,0 +1,85 @@
/**
* 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; });