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

查看文件

@@ -296,6 +296,27 @@ export async function initDatabase(): Promise<void> {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// 16. 首页轮播图运营配置表
await conn.query(`
CREATE TABLE IF NOT EXISTS banners (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(128) NOT NULL,
subtitle VARCHAR(255) DEFAULT '',
tag VARCHAR(64) DEFAULT '热门活动',
badge VARCHAR(64) DEFAULT 'HOT 热门组局',
badge_type VARCHAR(64) DEFAULT 'badge-hot',
bg VARCHAR(255) DEFAULT 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)',
image TEXT,
link VARCHAR(255) DEFAULT '/pages/activity/list',
sort_order INT DEFAULT 0,
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_sort (sort_order),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// 初始化配置单条记录
const [cfgRows]: any = await conn.query('SELECT COUNT(*) as count FROM finance_config WHERE id = 1');
if (cfgRows[0].count === 0) {
@@ -492,4 +513,16 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
('cmt_3', 'post_2', 2001, '涮肉小行家', 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=120', '国服李白太强了!主玩大乔和张飞,晚上可以一起开黑吗?', NOW() - INTERVAL 25 MINUTE);
`);
}
// 13. 种子轮播图
const [bnrRows]: any = await conn.query('SELECT COUNT(*) as count FROM banners');
if (bnrRows[0].count === 0) {
await conn.query(`
INSERT INTO banners (id, title, subtitle, tag, badge, badge_type, bg, image, link, sort_order, status)
VALUES
(1, '同城聚会狂欢 | 网红火锅探店与量贩KTV嗨唱', 'AA制铜锅涮肉 · 8人热烈局', '热门陪伴', 'HOT 热门组局', 'badge-hot', 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)', 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=800', '/pages/activity/detail?id=act_101', 1, 1),
(2, '量贩KTV麦霸沉浸欢唱 | 音乐达人歌声试听', '工体纯K 888豪华包厢', 'K歌对唱', '同城陪唱', 'badge-ktv', 'linear-gradient(135deg, #7C3AED 0%, #6366F1 100%)', 'https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?w=800', '/pages/activity/detail?id=act_102', 2, 1),
(3, '精酿清吧微醺畅聊 | 线下陪吃陪喝立减30元', '望京跳海酒馆 · 慢调交友', '同城陪喝', '同城陪喝', 'badge-bar', 'linear-gradient(135deg, #059669 0%, #10B981 100%)', 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800', '/pages/activity/detail?id=act_103', 3, 1);
`);
}
}

查看文件

@@ -697,41 +697,158 @@ export class MysqlRepository {
}
async getBanners(): Promise<any[]> {
return [
{
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'
},
{
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'
},
{
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'
}
];
const [rows]: any = await pool.query(`
SELECT
id, title, subtitle, tag, badge, badge_type as badgeType,
bg, image, link, sort_order as sortOrder, status,
created_at as createdAt, updated_at as updatedAt
FROM banners
WHERE status = 1
ORDER BY sort_order ASC, id DESC
`);
return rows;
}
async getAdminBanners(): Promise<any[]> {
const [rows]: any = await pool.query(`
SELECT
id, title, subtitle, tag, badge, badge_type as badgeType,
bg, image, link, sort_order as sortOrder, status,
created_at as createdAt, updated_at as updatedAt
FROM banners
ORDER BY sort_order ASC, id DESC
`);
return rows;
}
async createBanner(data: {
title: string;
subtitle?: string;
tag?: string;
badge?: string;
badgeType?: string;
bg?: string;
image?: string;
link?: string;
sortOrder?: number;
status?: number;
}): Promise<any> {
const title = data.title;
const subtitle = data.subtitle || '';
const tag = data.tag || '热门活动';
const badge = data.badge || 'HOT 热门组局';
const badgeType = data.badgeType || 'badge-hot';
const bg = data.bg || 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)';
const image = data.image || '';
const link = data.link || '/pages/activity/list';
const sortOrder = Number(data.sortOrder) || 0;
const status = data.status !== undefined ? Number(data.status) : 1;
const [res]: any = await pool.query(`
INSERT INTO banners (title, subtitle, tag, badge, badge_type, bg, image, link, sort_order, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [title, subtitle, tag, badge, badgeType, bg, image, link, sortOrder, status]);
const newId = res.insertId;
const [rows]: any = await pool.query(`
SELECT
id, title, subtitle, tag, badge, badge_type as badgeType,
bg, image, link, sort_order as sortOrder, status,
created_at as createdAt, updated_at as updatedAt
FROM banners
WHERE id = ?
`, [newId]);
return rows[0];
}
async updateBanner(id: number | string, data: Partial<{
title: string;
subtitle: string;
tag: string;
badge: string;
badgeType: string;
bg: string;
image: string;
link: string;
sortOrder: number;
status: number;
}>): Promise<any> {
const fields: string[] = [];
const values: any[] = [];
if (data.title !== undefined) {
fields.push('title = ?');
values.push(data.title);
}
if (data.subtitle !== undefined) {
fields.push('subtitle = ?');
values.push(data.subtitle);
}
if (data.tag !== undefined) {
fields.push('tag = ?');
values.push(data.tag);
}
if (data.badge !== undefined) {
fields.push('badge = ?');
values.push(data.badge);
}
if (data.badgeType !== undefined) {
fields.push('badge_type = ?');
values.push(data.badgeType);
}
if (data.bg !== undefined) {
fields.push('bg = ?');
values.push(data.bg);
}
if (data.image !== undefined) {
fields.push('image = ?');
values.push(data.image);
}
if (data.link !== undefined) {
fields.push('link = ?');
values.push(data.link);
}
if (data.sortOrder !== undefined) {
fields.push('sort_order = ?');
values.push(Number(data.sortOrder));
}
if (data.status !== undefined) {
fields.push('status = ?');
values.push(Number(data.status));
}
if (fields.length > 0) {
values.push(id);
await pool.query(`UPDATE banners SET ${fields.join(', ')} WHERE id = ?`, values);
}
const [rows]: any = await pool.query(`
SELECT
id, title, subtitle, tag, badge, badge_type as badgeType,
bg, image, link, sort_order as sortOrder, status,
created_at as createdAt, updated_at as updatedAt
FROM banners
WHERE id = ?
`, [id]);
return rows[0] || null;
}
async updateBannerStatus(id: number | string, status: number): Promise<any> {
await pool.query('UPDATE banners SET status = ? WHERE id = ?', [Number(status), id]);
const [rows]: any = await pool.query(`
SELECT
id, title, subtitle, tag, badge, badge_type as badgeType,
bg, image, link, sort_order as sortOrder, status,
created_at as createdAt, updated_at as updatedAt
FROM banners
WHERE id = ?
`, [id]);
return rows[0] || null;
}
async deleteBanner(id: number | string): Promise<boolean> {
const [res]: any = await pool.query('DELETE FROM banners WHERE id = ?', [id]);
return res.affectedRows > 0;
}