feat(banner): support dynamic banner management in admin and sync with miniprogram

这个提交包含在:
xcbyy
2026-10-10 08:51:34 +08:00
父节点 d23975aa3a
当前提交 f2c6f98962
共有 9 个文件被更改,包括 1119 次插入 和 36 次删除

查看文件

@@ -615,7 +615,7 @@ app.get('/api/expert/stats/:id', async (req: Request, res: Response) => {
});
// 首页运营 Banner 列表 (配置化接口)
// 首页运营 Banner 列表 (小程序端读取启用状态)
app.get('/api/banners', async (_req: Request, res: Response) => {
try {
const banners = await repo.getBanners();
@@ -625,6 +625,120 @@ app.get('/api/banners', async (_req: Request, res: Response) => {
}
});
// 管理后台: 获取全部 Banner 列表 (含停用状态)
app.get('/api/admin/banners', async (_req: Request, res: Response) => {
try {
const banners = await repo.getAdminBanners();
res.json({ code: 20000, data: banners });
} catch (err: any) {
res.status(500).json({ code: 50000, message: err.message });
}
});
// 管理后台: 新增 Banner
app.post('/api/admin/banners', async (req: Request, res: Response) => {
try {
const { title, subtitle, tag, badge, badgeType, bg, image, link, sortOrder, status } = req.body;
if (!title || !title.trim()) {
return res.status(400).json({ code: 40000, message: 'Banner 标题不能为空' });
}
// 微信内容安全检测
const secCheck = contentSecurityService.checkText(title + ' ' + (subtitle || ''));
if (!secCheck.pass) {
return res.status(400).json({ code: secCheck.code, message: secCheck.message });
}
const banner = await repo.createBanner({
title: title.trim(),
subtitle: subtitle ? subtitle.trim() : '',
tag,
badge,
badgeType,
bg,
image,
link,
sortOrder: sortOrder !== undefined ? Number(sortOrder) : 0,
status: status !== undefined ? Number(status) : 1
});
res.json({ code: 20000, data: banner, message: '轮播图创建成功' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 管理后台: 修改 Banner
app.put('/api/admin/banners/:id', async (req: Request, res: Response) => {
try {
const bannerId = req.params.id;
const { title, subtitle, tag, badge, badgeType, bg, image, link, sortOrder, status } = req.body;
if (title) {
const secCheck = contentSecurityService.checkText(title + ' ' + (subtitle || ''));
if (!secCheck.pass) {
return res.status(400).json({ code: secCheck.code, message: secCheck.message });
}
}
const updated = await repo.updateBanner(bannerId, {
title,
subtitle,
tag,
badge,
badgeType,
bg,
image,
link,
sortOrder: sortOrder !== undefined ? Number(sortOrder) : undefined,
status: status !== undefined ? Number(status) : undefined
});
if (!updated) {
return res.status(404).json({ code: 40400, message: '轮播图不存在或已被删除' });
}
res.json({ code: 20000, data: updated, message: '轮播图修改成功' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 管理后台: 切换 Banner 上架/下线状态
app.post('/api/admin/banners/:id/status', async (req: Request, res: Response) => {
try {
const bannerId = req.params.id;
const { status } = req.body;
if (status === undefined || (status !== 0 && status !== 1)) {
return res.status(400).json({ code: 40000, message: '状态参数必须为 0 或 1' });
}
const updated = await repo.updateBannerStatus(bannerId, Number(status));
if (!updated) {
return res.status(404).json({ code: 40400, message: '轮播图不存在' });
}
res.json({ code: 20000, data: updated, message: status === 1 ? '轮播图已上架生效' : '轮播图已下线隐藏' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 管理后台: 删除 Banner
app.delete('/api/admin/banners/:id', async (req: Request, res: Response) => {
try {
const bannerId = req.params.id;
const ok = await repo.deleteBanner(bannerId);
if (!ok) {
return res.status(404).json({ code: 40400, message: '轮播图不存在或已被删除' });
}
res.json({ code: 20000, data: null, message: '轮播图已删除' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 创建新订单 (落盘 MySQL)
app.post('/api/orders', async (req: Request, res: Response) => {
try {