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 次删除

查看文件

@@ -0,0 +1,166 @@
// 已读取AGENTS.md
import { ContentSecurityService } from '../security/content-security.service';
export interface BannerItem {
id: number;
title: string;
subtitle?: string;
tag?: string;
badge?: string;
badgeType?: string;
bg?: string;
image?: string;
link?: string;
sortOrder?: number;
status: number; // 1: 启用上架, 0: 停用下架
createdAt?: string;
updatedAt?: string;
}
export class BannerService {
private banners: BannerItem[] = [
{
id: 1,
title: '同城聚会狂欢 | 网红火锅探店与量贩KTV嗨唱',
subtitle: 'AA制铜锅涮肉 · 8人热烈局',
tag: '热门陪伴',
badge: 'HOT 热门组局',
badgeType: 'badge-hot',
bg: 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)',
image: 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=800',
link: '/pages/activity/detail?id=act_101',
sortOrder: 1,
status: 1
},
{
id: 2,
title: '量贩KTV麦霸沉浸欢唱 | 音乐达人歌声试听',
subtitle: '工体纯K 888豪华包厢',
tag: 'K歌对唱',
badge: '同城陪唱',
badgeType: 'badge-ktv',
bg: 'linear-gradient(135deg, #7C3AED 0%, #6366F1 100%)',
image: 'https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?w=800',
link: '/pages/activity/detail?id=act_102',
sortOrder: 2,
status: 1
},
{
id: 3,
title: '精酿清吧微醺畅聊 | 线下陪吃陪喝立减30元',
subtitle: '望京跳海酒馆 · 慢调交友',
tag: '同城陪喝',
badge: '同城陪喝',
badgeType: 'badge-bar',
bg: 'linear-gradient(135deg, #059669 0%, #10B981 100%)',
image: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800',
link: '/pages/activity/detail?id=act_103',
sortOrder: 3,
status: 1
}
];
private nextId = 4;
private secService = new ContentSecurityService();
// 获取移动端展示的轮播图 (仅 status = 1,按 sortOrder 升序)
getActiveBanners(): BannerItem[] {
return this.banners
.filter((b) => b.status === 1)
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
}
// 获取后台管理的全部轮播图
getAllBanners(): BannerItem[] {
return [...this.banners].sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
}
// 创建新轮播图
createBanner(payload: {
title: string;
subtitle?: string;
tag?: string;
badge?: string;
badgeType?: string;
bg?: string;
image?: string;
link?: string;
sortOrder?: number;
status?: number;
}): BannerItem {
if (!payload.title || !payload.title.trim()) {
throw new Error('轮播图标题不能为空');
}
// 微信内容安全检测
const secResult = this.secService.checkText(payload.title + ' ' + (payload.subtitle || ''));
if (!secResult.pass) {
const err = new Error(secResult.message);
(err as any).code = secResult.code;
throw err;
}
const newBanner: BannerItem = {
id: this.nextId++,
title: payload.title.trim(),
subtitle: payload.subtitle ? payload.subtitle.trim() : '',
tag: payload.tag || '热门活动',
badge: payload.badge || 'HOT 热门组局',
badgeType: payload.badgeType || 'badge-hot',
bg: payload.bg || 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)',
image: payload.image || '',
link: payload.link || '/pages/activity/list',
sortOrder: payload.sortOrder !== undefined ? Number(payload.sortOrder) : 0,
status: payload.status !== undefined ? Number(payload.status) : 1,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.banners.push(newBanner);
return newBanner;
}
// 修改轮播图
updateBanner(id: number, payload: Partial<Omit<BannerItem, 'id'>>): BannerItem {
const index = this.banners.findIndex((b) => b.id === id);
if (index === -1) {
throw new Error('轮播图不存在或已被删除');
}
if (payload.title) {
const secResult = this.secService.checkText(payload.title + ' ' + (payload.subtitle || ''));
if (!secResult.pass) {
const err = new Error(secResult.message);
(err as any).code = secResult.code;
throw err;
}
}
const current = this.banners[index];
const updated: BannerItem = {
...current,
...payload,
updatedAt: new Date().toISOString()
};
this.banners[index] = updated;
return updated;
}
// 快速上下线切换
toggleStatus(id: number, status: number): BannerItem {
if (status !== 0 && status !== 1) {
throw new Error('状态参数必须为 0 或 1');
}
return this.updateBanner(id, { status });
}
// 删除轮播图
deleteBanner(id: number): boolean {
const initialLength = this.banners.length;
this.banners = this.banners.filter((b) => b.id !== id);
if (this.banners.length === initialLength) {
throw new Error('轮播图不存在或已被删除');
}
return true;
}
}