feat: 新增本月经营态势弹窗+千问TTS语音播报

- 新增 BusinessReportDialog 组件,展示整体经营/成本费用/风险态势/利润机会四部分文字报告
- BossPage 标题栏新增「本月经营态势」按钮
- 后端新增 /api/tts 路由,调用千问 CosyVoice TTS API 合成语音
- 前端按句分段合成、逐句顺序播放,边播边合成
- 报告含待确认门店数量
This commit is contained in:
freedakgmail
2026-08-03 22:55:50 +08:00
parent c15ca07b7c
commit 6bc2f43da7
5 changed files with 329 additions and 2 deletions
+2
View File
@@ -13,6 +13,7 @@ import smartSchedulingRoutes from './routes/smart-scheduling.js'
import situationalAwarenessRoutes from './routes/situational-awareness.js'
import analyticsEnhancedRoutes from './routes/analytics-enhanced.js'
import adminRoutes from './routes/admin.js'
import ttsRoutes from './routes/tts.js'
const app = express()
const PORT = parseInt(process.env.PORT || '3333')
@@ -59,6 +60,7 @@ app.use('/api/store-expense', storeExpenseRoutes)
app.use('/api/smart-scheduling', smartSchedulingRoutes)
app.use('/api/situational-awareness', situationalAwarenessRoutes)
app.use('/api/analytics-enhanced', analyticsEnhancedRoutes)
app.use('/api/tts', ttsRoutes)
app.use(notFoundHandler)
app.use(errorHandler)
+61
View File
@@ -0,0 +1,61 @@
import { Router } from 'express'
import { sendSuccess, sendError } from '../middleware/error.js'
import type { AuthRequest } from '../middleware/auth.js'
const router = Router()
const TTS_API_KEY = process.env.TTS_API_KEY || 'sk-ws-H.EHYXPRE.Gv4a.MEQCIDOofoXP-VBQILl0c_kspJZhSUj8fyYIZupigveHnpRrAiAsjWXuJ3dpyTBDCUF1ZLdcvLz8GJoan0ObIkCwfE06kQ'
const TTS_API_HOST = process.env.TTS_API_HOST || 'llm-znfsxsdp6uwik7ad.cn-beijing.maas.aliyuncs.com'
const TTS_URL = `https://${TTS_API_HOST}/api/v1/services/audio/tts/SpeechSynthesizer`
router.post('/', async (req: AuthRequest, res) => {
try {
const { text } = req.body
if (!text || typeof text !== 'string') {
return sendError(res, 'text is required')
}
if (text.length > 3000) {
return sendError(res, 'text too long (max 3000 characters)')
}
const response = await fetch(TTS_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TTS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'cosyvoice-v3-flash',
input: {
text,
voice: 'longanyang',
format: 'mp3',
sample_rate: 22050,
volume: 50,
rate: 1.0,
},
}),
})
if (!response.ok) {
const errText = await response.text()
console.error('TTS API error:', response.status, errText)
return sendError(res, `TTS API error: ${response.status}`, 502)
}
const result: any = await response.json()
if (result.output?.audio?.url) {
sendSuccess(res, { url: result.output.audio.url })
} else {
console.error('TTS API unexpected response:', JSON.stringify(result))
sendError(res, 'TTS API returned no audio URL', 502)
}
} catch (err: any) {
console.error('TTS route error:', err.message)
sendError(res, err.message, 500)
}
})
export default router