feat(admin): complete post management, finance csv export, and wechat message template config

这个提交包含在:
xcbyy
2026-10-13 11:11:40 +08:00
父节点 3bb6f5a6be
当前提交 736450973e
共有 11 个文件被更改,包括 951 次插入 和 4 次删除

查看文件

@@ -436,6 +436,14 @@ export async function initDatabase(): Promise<void> {
`);
} catch (_) {}
// 扩展 posts 表结构: is_top (置顶), status (正常/隐藏)
try {
await conn.query('ALTER TABLE posts ADD COLUMN is_top TINYINT DEFAULT 0');
} catch (_) {}
try {
await conn.query("ALTER TABLE posts ADD COLUMN status VARCHAR(32) DEFAULT '正常'");
} catch (_) {}
// 21. 用户举报工单表 (涉黄/涉赌/线下欺诈等不良陪玩举报)
await conn.query(`
CREATE TABLE IF NOT EXISTS reports (
@@ -814,6 +822,15 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
defaultAaFee: 50
});
const messageTemplateRulesVal = JSON.stringify({
orderStatusTemplateId: 'tmpl_order_status_update_01',
activityJoinTemplateId: 'tmpl_activity_joined_02',
withdrawSuccessTemplateId: 'tmpl_withdraw_success_03',
verifyCodeTemplateId: 'tmpl_verify_code_notice_04',
enablePush: 1,
remark: '微信小程序订阅消息与服务通知模版ID配置'
});
const [cfgItemRows]: any = await conn.query('SELECT COUNT(*) as count FROM platform_configs');
if (cfgItemRows[0].count === 0) {
const customerServiceVal = JSON.stringify({
@@ -916,6 +933,12 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
VALUES ('activity_rules', '同城组局发单与预算预设规范', ?, '招募席位上下限、快捷单人赏金预设与包场预算梯队', 1)
`, [activityRulesVal]);
}
if (!existingKeys.has('message_template_rules')) {
await conn.query(`
INSERT INTO platform_configs (config_key, name, value_json, description, status)
VALUES ('message_template_rules', '微信服务通知与模板消息配置', ?, '微信订阅消息通知与状态模板配置', 1)
`, [messageTemplateRulesVal]);
}
if (existingKeys.has('platform_protocol')) {
const [protoRows]: any = await conn.query("SELECT value_json FROM platform_configs WHERE config_key = 'platform_protocol'");
if (protoRows && protoRows.length > 0) {

查看文件

@@ -2194,20 +2194,34 @@ export class MysqlRepository {
// 友圈动态相关
async getPosts(category?: string): Promise<any[]> {
async getPosts(category?: string, keyword?: string, status?: string): Promise<any[]> {
let sql = `
SELECT
id, user_id as userId, author, avatar, tag, tag_type as tagType,
location, distance, content, topics, images, action_text as actionText,
likes, comments, views, category, created_at as createdAt
likes, comments, views, category, is_top as isTop, status, created_at as createdAt
FROM posts
`;
const params: any[] = [];
const where: string[] = [];
if (category && category !== 'all') {
sql += ' WHERE category = ?';
where.push('category = ?');
params.push(category);
}
sql += ' ORDER BY created_at DESC';
if (keyword) {
where.push('(content LIKE ? OR author LIKE ? OR location LIKE ?)');
params.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`);
}
if (status && status !== 'all') {
where.push('status = ?');
params.push(status);
}
if (where.length > 0) {
sql += ' WHERE ' + where.join(' AND ');
}
sql += ' ORDER BY is_top DESC, created_at DESC';
const [rows]: any = await pool.query(sql, params);
return rows.map((r: any) => {
@@ -2222,12 +2236,45 @@ export class MysqlRepository {
return {
...r,
isTop: Boolean(r.isTop),
status: r.status || '正常',
topics: topicsArr,
images: imagesArr,
};
});
}
// 管理端切换置顶
async togglePostTop(postId: string, isTop: boolean): Promise<boolean> {
const [result]: any = await pool.query(`
UPDATE posts SET is_top = ? WHERE id = ?
`, [isTop ? 1 : 0, postId]);
return result.affectedRows > 0;
}
// 管理端切换状态 (正常 / 隐藏)
async updatePostStatus(postId: string, status: string): Promise<boolean> {
const [result]: any = await pool.query(`
UPDATE posts SET status = ? WHERE id = ?
`, [status, postId]);
return result.affectedRows > 0;
}
// 管理端彻底删除动态及下属评论
async adminDeletePost(postId: string): Promise<boolean> {
try {
await pool.query('DELETE FROM post_comments WHERE post_id = ?', [postId]);
} catch (_) {}
const [result]: any = await pool.query('DELETE FROM posts WHERE id = ?', [postId]);
return result.affectedRows > 0;
}
// 管理端删除特定违规评论
async adminDeletePostComment(commentId: string): Promise<boolean> {
const [result]: any = await pool.query('DELETE FROM post_comments WHERE id = ?', [commentId]);
return result.affectedRows > 0;
}
async createPost(data: {
userId?: number;
author?: string;

查看文件

@@ -1686,6 +1686,84 @@ app.get('/api/posts', async (req: Request, res: Response) => {
}
});
// 管理端:全量动态管理与检索
app.get('/api/admin/posts', async (req: Request, res: Response) => {
try {
const category = req.query.category ? String(req.query.category) : undefined;
const keyword = req.query.keyword ? String(req.query.keyword) : undefined;
const status = req.query.status ? String(req.query.status) : undefined;
const posts = await repo.getPosts(category, keyword, status);
res.json({ code: 20000, data: posts });
} catch (err: any) {
res.status(500).json({ code: 50000, message: err.message });
}
});
// 管理端:切换置顶状态
app.post('/api/admin/posts/:id/top', async (req: Request, res: Response) => {
try {
const postId = String(req.params.id);
const { isTop } = req.body;
await repo.togglePostTop(postId, Boolean(isTop));
res.json({ code: 20000, message: isTop ? '置顶成功' : '已取消置顶' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 管理端:切换显示/隐藏状态
app.post('/api/admin/posts/:id/status', async (req: Request, res: Response) => {
try {
const postId = String(req.params.id);
const { status } = req.body;
await repo.updatePostStatus(postId, String(status));
res.json({ code: 20000, message: '动态状态更新成功' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 管理端:违规下架彻底删除
app.delete('/api/admin/posts/:id', async (req: Request, res: Response) => {
try {
const postId = String(req.params.id);
await repo.adminDeletePost(postId);
res.json({ code: 20000, message: '违规动态及关联评论已彻底删除' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 管理端:删除单条不当评论
app.delete('/api/admin/posts/comments/:commentId', async (req: Request, res: Response) => {
try {
const commentId = String(req.params.commentId);
await repo.adminDeletePostComment(commentId);
res.json({ code: 20000, message: '违规评论已删除' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 管理端:微信订阅消息与服务通知模拟测试推送接口
app.post('/api/admin/message/test-send', async (req: Request, res: Response) => {
try {
const { templateType, targetUserId, previewData } = req.body;
// 模拟微信服务器发送模版消息
const result = {
templateType: templateType || 'order_status',
targetUserId: targetUserId || 1001,
sendTime: new Date().toISOString(),
status: 'SUCCESS',
wechatMsgId: `wx_msg_${Date.now()}`,
previewData: previewData || { title: '订单履约提醒', detail: '您的同城生活陪伴订单已开始服务' }
};
res.json({ code: 20000, data: result, message: '微信模板消息模拟发送成功' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
app.post('/api/posts', async (req: Request, res: Response) => {
try {
const { content } = req.body || {};