feat(banner): support dynamic banner management in admin and sync with miniprogram
这个提交包含在:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
在新工单中引用
屏蔽一个用户