feat(system): implement dynamic platform configs for customer service, safety notice, and service agreement
这个提交包含在:
@@ -372,6 +372,21 @@ export async function initDatabase(): Promise<void> {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// 20. 平台官方客服、公告及服务协议通用配置表
|
||||
await conn.query(`
|
||||
CREATE TABLE IF NOT EXISTS platform_configs (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
config_key VARCHAR(64) UNIQUE NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
value_json JSON NOT NULL,
|
||||
description VARCHAR(255) DEFAULT '',
|
||||
status TINYINT DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_key (config_key)
|
||||
) 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) {
|
||||
@@ -626,4 +641,40 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
|
||||
(4, '电竞开黑连胜冲星券', 10.00, 30.00, 30, 5000, 340, 'ALL', '线上游戏陪玩下单立减', 1);
|
||||
`);
|
||||
}
|
||||
|
||||
// 17. 种子平台通用配置(官方客服、履约公告指引、规范协议)
|
||||
const [cfgItemRows]: any = await conn.query('SELECT COUNT(*) as count FROM platform_configs');
|
||||
if (cfgItemRows[0].count === 0) {
|
||||
const customerServiceVal = JSON.stringify({
|
||||
hotline: '400-888-6666',
|
||||
hotlineDesc: '7x24h 官方客服服务热线',
|
||||
wechat: 'peiwan_vip_kefu',
|
||||
wechatDesc: '官方 VIP 客服微信号',
|
||||
workHours: '周一至周日 00:00 - 24:00'
|
||||
});
|
||||
|
||||
const safetyNoticeVal = JSON.stringify({
|
||||
title: '平台官方公告与指引',
|
||||
points: [
|
||||
'【安全履约须知】为保障搭子与陪伴师人身与财产安全,线下约局须在合规公开商业街区进行。',
|
||||
'【现场核销】现场碰面后请出示 4 位数字核销码进行签到确认。',
|
||||
'【平台兜底】平台提供 7x24 小时争议仲裁与资金托管保障服务,严禁脱离平台私下交易。'
|
||||
],
|
||||
noticePopup: false
|
||||
});
|
||||
|
||||
const protocolVal = JSON.stringify({
|
||||
title: '用户服务协议与隐私规范',
|
||||
summary: '严禁发布任何违规言论及低俗诈骗行为,平台全面遵循微信内容安全风控与用户隐私保护标准。',
|
||||
content: '1. 账号使用规范:用户注册与使用陪玩服务须符合国家法律法规,严禁利用平台从事涉黄、赌博、欺诈等违规行为。\n2. 资金与交易保障:平台提供全额担保交易,订单完成前款项由官方账户托管,核销确认后自动结算至大神钱包。\n3. 争议与售后:若遇服务不符或搭子违约,可随时提交争议仲裁由运营平台人工介入裁决。'
|
||||
});
|
||||
|
||||
await conn.query(`
|
||||
INSERT INTO platform_configs (config_key, name, value_json, description, status)
|
||||
VALUES
|
||||
('customer_service', '官方在线客服与服务热线', ?, '小程序端个人中心客服联系方式', 1),
|
||||
('safety_notice', '安全履约须知与官方公告', ?, '小程序端官方指引与安全红线弹窗', 1),
|
||||
('platform_protocol', '平台服务协议与合规规范', ?, '小程序端服务协议与隐私政策详情', 1);
|
||||
`, [customerServiceVal, safetyNoticeVal, protocolVal]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,6 +1247,91 @@ export class MysqlRepository {
|
||||
return res.affectedRows > 0;
|
||||
}
|
||||
|
||||
// 3. 平台通用配置与指引 (Platform Configs)
|
||||
async getPlatformConfigs(): Promise<{ [key: string]: any }> {
|
||||
const [rows]: any = await pool.query(`
|
||||
SELECT config_key as configKey, name, value_json as valueJson, description, status
|
||||
FROM platform_configs
|
||||
WHERE status = 1
|
||||
`);
|
||||
const result: { [key: string]: any } = {};
|
||||
for (const r of rows) {
|
||||
let val = r.valueJson;
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
val = JSON.parse(val);
|
||||
} catch (_) {}
|
||||
}
|
||||
result[r.configKey] = val;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async getAdminPlatformConfigs(): Promise<any[]> {
|
||||
const [rows]: any = await pool.query(`
|
||||
SELECT id, config_key as configKey, name, value_json as valueJson, description, status,
|
||||
created_at as createdAt, updated_at as updatedAt
|
||||
FROM platform_configs
|
||||
ORDER BY id ASC
|
||||
`);
|
||||
return rows.map((r: any) => {
|
||||
let val = r.valueJson;
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
val = JSON.parse(val);
|
||||
} catch (_) {}
|
||||
}
|
||||
return {
|
||||
...r,
|
||||
valueJson: val
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async updatePlatformConfig(configKey: string, data: { name?: string; valueJson?: any; description?: string; status?: number }): Promise<any> {
|
||||
const fields: string[] = [];
|
||||
const values: any[] = [];
|
||||
|
||||
if (data.name !== undefined) {
|
||||
fields.push('name = ?');
|
||||
values.push(data.name);
|
||||
}
|
||||
if (data.valueJson !== undefined) {
|
||||
fields.push('value_json = ?');
|
||||
values.push(JSON.stringify(data.valueJson));
|
||||
}
|
||||
if (data.description !== undefined) {
|
||||
fields.push('description = ?');
|
||||
values.push(data.description);
|
||||
}
|
||||
if (data.status !== undefined) {
|
||||
fields.push('status = ?');
|
||||
values.push(Number(data.status));
|
||||
}
|
||||
|
||||
if (fields.length > 0) {
|
||||
values.push(configKey);
|
||||
await pool.query(`UPDATE platform_configs SET ${fields.join(', ')} WHERE config_key = ?`, values);
|
||||
}
|
||||
|
||||
const [rows]: any = await pool.query(`
|
||||
SELECT id, config_key as configKey, name, value_json as valueJson, description, status,
|
||||
created_at as createdAt, updated_at as updatedAt
|
||||
FROM platform_configs
|
||||
WHERE config_key = ?
|
||||
`, [configKey]);
|
||||
|
||||
if (!rows || rows.length === 0) return null;
|
||||
const r = rows[0];
|
||||
let val = r.valueJson;
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
val = JSON.parse(val);
|
||||
} catch (_) {}
|
||||
}
|
||||
return { ...r, valueJson: val };
|
||||
}
|
||||
|
||||
|
||||
|
||||
async getArbitrations(): Promise<any[]> {
|
||||
|
||||
@@ -1051,6 +1051,57 @@ app.delete('/api/admin/coupons/:id', async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 平台官方客服、履约公告与规范协议配置接口
|
||||
// 移动端:获取全部有效系统通用配置
|
||||
app.get('/api/system/configs', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const configs = await repo.getPlatformConfigs();
|
||||
res.json({ code: 20000, data: configs });
|
||||
} catch (err: any) {
|
||||
res.status(500).json({ code: 50000, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 管理后台:获取全部配置项列表
|
||||
app.get('/api/admin/system/configs', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const list = await repo.getAdminPlatformConfigs();
|
||||
res.json({ code: 20000, data: list });
|
||||
} catch (err: any) {
|
||||
res.status(500).json({ code: 50000, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 管理后台:更新指定配置项
|
||||
app.put('/api/admin/system/configs/:key', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const configKey = String(req.params.key);
|
||||
const { name, valueJson, description, status } = req.body;
|
||||
|
||||
// 微信内容安全审查(检查文本内容或字符串化后的配置)
|
||||
const textToCheck = `${name || ''} ${description || ''} ${JSON.stringify(valueJson || '')}`;
|
||||
const sec = contentSecurityService.checkText(textToCheck);
|
||||
if (!sec.pass) {
|
||||
return res.status(400).json({ code: sec.code, message: sec.message });
|
||||
}
|
||||
|
||||
const updated = await repo.updatePlatformConfig(configKey, {
|
||||
name,
|
||||
valueJson,
|
||||
description,
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
// 创建新订单 (落盘 MySQL)
|
||||
app.post('/api/orders', async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
||||
在新工单中引用
屏蔽一个用户