24cd9cef53
- uni-app Vue3 frontend with dark glassmorphism theme - 5 AI dimensions: Origin / Development / Current / Learning / Trend - AI Chat system prompt updated from geometry to AI - 23 AI knowledge articles initialized in DB - Trend API + cron job for daily news - Product pricing (Pro ¥19.9, VIP ¥39.9) - Pinia stores + API utilities - AGENTS.md documentation
364 lines
8.1 KiB
JavaScript
Executable File
364 lines
8.1 KiB
JavaScript
Executable File
const { PinyinContent } = require('../../models/pinyin');
|
|
|
|
/**
|
|
* 获取拼音内容列表
|
|
* GET /api/pinyin/contents
|
|
*/
|
|
exports.getContents = async (req, res) => {
|
|
try {
|
|
const { type, isFree, page = 1, limit = 20 } = req.query;
|
|
|
|
// 构建查询条件
|
|
const query = { status: 'active' };
|
|
if (type) query.type = type;
|
|
if (isFree !== undefined) query.isFree = isFree === 'true';
|
|
|
|
// 分页
|
|
const skip = (parseInt(page) - 1) * parseInt(limit);
|
|
|
|
// 查询数据
|
|
const contents = await PinyinContent.find(query)
|
|
.select('-__v')
|
|
.sort({ order: 1, symbol: 1 })
|
|
.skip(skip)
|
|
.limit(parseInt(limit));
|
|
|
|
// 获取总数
|
|
const total = await PinyinContent.countDocuments(query);
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: 'success',
|
|
data: {
|
|
list: contents,
|
|
total,
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
totalPages: Math.ceil(total / parseInt(limit))
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('获取拼音内容列表失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '获取拼音内容列表失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取单个拼音详情
|
|
* GET /api/pinyin/contents/:symbol
|
|
*/
|
|
exports.getContentBySymbol = async (req, res) => {
|
|
try {
|
|
const { symbol } = req.params;
|
|
|
|
const content = await PinyinContent.findOne({
|
|
symbol: symbol.toLowerCase(),
|
|
status: 'active'
|
|
}).select('-__v');
|
|
|
|
if (!content) {
|
|
return res.status(404).json({
|
|
code: 404,
|
|
message: '拼音内容不存在'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: 'success',
|
|
data: content
|
|
});
|
|
} catch (error) {
|
|
console.error('获取拼音详情失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '获取拼音详情失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取拼音音频
|
|
* GET /api/pinyin/contents/:symbol/audio
|
|
*/
|
|
exports.getContentAudio = async (req, res) => {
|
|
try {
|
|
const { symbol } = req.params;
|
|
|
|
const content = await PinyinContent.findOne({
|
|
symbol: symbol.toLowerCase(),
|
|
status: 'active'
|
|
}).select('symbol audioUrl name');
|
|
|
|
if (!content) {
|
|
return res.status(404).json({
|
|
code: 404,
|
|
message: '拼音内容不存在'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: 'success',
|
|
data: {
|
|
symbol: content.symbol,
|
|
audioUrl: content.audioUrl,
|
|
name: content.name
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('获取拼音音频失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '获取拼音音频失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 创建拼音内容(管理员)
|
|
* POST /api/pinyin/contents
|
|
*/
|
|
exports.createContent = async (req, res) => {
|
|
try {
|
|
const contentData = req.body;
|
|
|
|
// 检查是否已存在
|
|
const existing = await PinyinContent.findOne({ symbol: contentData.symbol });
|
|
if (existing) {
|
|
return res.status(400).json({
|
|
code: 400,
|
|
message: '该拼音内容已存在'
|
|
});
|
|
}
|
|
|
|
const content = new PinyinContent(contentData);
|
|
await content.save();
|
|
|
|
res.status(201).json({
|
|
code: 0,
|
|
message: '创建成功',
|
|
data: content
|
|
});
|
|
} catch (error) {
|
|
console.error('创建拼音内容失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '创建拼音内容失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 更新拼音内容(管理员)
|
|
* PUT /api/pinyin/contents/:id
|
|
*/
|
|
exports.updateContent = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const updateData = req.body;
|
|
|
|
// 不允许修改symbol
|
|
delete updateData.symbol;
|
|
|
|
const content = await PinyinContent.findByIdAndUpdate(
|
|
id,
|
|
{ $set: updateData },
|
|
{ new: true, runValidators: true }
|
|
);
|
|
|
|
if (!content) {
|
|
return res.status(404).json({
|
|
code: 404,
|
|
message: '拼音内容不存在'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: '更新成功',
|
|
data: content
|
|
});
|
|
} catch (error) {
|
|
console.error('更新拼音内容失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '更新拼音内容失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 删除拼音内容(管理员)
|
|
* DELETE /api/pinyin/contents/:id
|
|
*/
|
|
exports.deleteContent = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const content = await PinyinContent.findByIdAndDelete(id);
|
|
|
|
if (!content) {
|
|
return res.status(404).json({
|
|
code: 404,
|
|
message: '拼音内容不存在'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: '删除成功'
|
|
});
|
|
} catch (error) {
|
|
console.error('删除拼音内容失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '删除拼音内容失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 上传拼音音频
|
|
* POST /api/pinyin/contents/:symbol/audio
|
|
*/
|
|
exports.uploadAudio = async (req, res) => {
|
|
try {
|
|
const { symbol } = req.params;
|
|
|
|
if (!req.file) {
|
|
return res.status(400).json({
|
|
code: 400,
|
|
message: '请上传音频文件'
|
|
});
|
|
}
|
|
|
|
// 构建文件URL(假设使用静态文件服务)
|
|
const audioUrl = `/uploads/audio/${req.file.filename}`;
|
|
|
|
// 更新拼音内容的音频URL
|
|
const content = await PinyinContent.findOneAndUpdate(
|
|
{ symbol: symbol.toLowerCase() },
|
|
{ $set: { audioUrl } },
|
|
{ new: true }
|
|
);
|
|
|
|
if (!content) {
|
|
return res.status(404).json({
|
|
code: 404,
|
|
message: '拼音内容不存在'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: '音频上传成功',
|
|
data: { audioUrl }
|
|
});
|
|
} catch (error) {
|
|
console.error('上传音频失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '上传音频失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 上传口型图
|
|
* POST /api/pinyin/contents/:symbol/mouth-image
|
|
*/
|
|
exports.uploadMouthImage = async (req, res) => {
|
|
try {
|
|
const { symbol } = req.params;
|
|
|
|
if (!req.file) {
|
|
return res.status(400).json({
|
|
code: 400,
|
|
message: '请上传图片文件'
|
|
});
|
|
}
|
|
|
|
// 构建文件URL
|
|
const mouthImage = `/uploads/images/${req.file.filename}`;
|
|
|
|
// 更新拼音内容的口型图URL
|
|
const content = await PinyinContent.findOneAndUpdate(
|
|
{ symbol: symbol.toLowerCase() },
|
|
{ $set: { mouthImage } },
|
|
{ new: true }
|
|
);
|
|
|
|
if (!content) {
|
|
return res.status(404).json({
|
|
code: 404,
|
|
message: '拼音内容不存在'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: '口型图上传成功',
|
|
data: { mouthImage }
|
|
});
|
|
} catch (error) {
|
|
console.error('上传口型图失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '上传口型图失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 批量更新音频URL(从外部TTS服务)
|
|
* POST /api/pinyin/contents/batch-update-audio
|
|
*/
|
|
exports.batchUpdateAudio = async (req, res) => {
|
|
try {
|
|
const { audioBaseUrl } = req.body;
|
|
|
|
if (!audioBaseUrl) {
|
|
return res.status(400).json({
|
|
code: 400,
|
|
message: '请提供音频基础URL'
|
|
});
|
|
}
|
|
|
|
// 获取所有拼音内容
|
|
const contents = await PinyinContent.find({});
|
|
|
|
// 批量更新音频URL
|
|
const updatePromises = contents.map(content => {
|
|
const audioUrl = `${audioBaseUrl}/${content.symbol}.mp3`;
|
|
return PinyinContent.findByIdAndUpdate(content._id, { $set: { audioUrl } });
|
|
});
|
|
|
|
await Promise.all(updatePromises);
|
|
|
|
res.json({
|
|
code: 0,
|
|
message: `成功更新 ${contents.length} 个拼音的音频URL`,
|
|
data: { updatedCount: contents.length }
|
|
});
|
|
} catch (error) {
|
|
console.error('批量更新音频失败:', error);
|
|
res.status(500).json({
|
|
code: 500,
|
|
message: '批量更新音频失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
};
|