feat: 营销页面结果旁显示中文翻译 + 朗读按钮
This commit is contained in:
@@ -114,8 +114,10 @@
|
|||||||
<view class="results-list" v-if="activeTab !== 'keywords'">
|
<view class="results-list" v-if="activeTab !== 'keywords'">
|
||||||
<view class="result-item" v-for="(item, index) in filteredResults" :key="index">
|
<view class="result-item" v-for="(item, index) in filteredResults" :key="index">
|
||||||
<text class="result-text">{{ item.content || item }}</text>
|
<text class="result-text">{{ item.content || item }}</text>
|
||||||
|
<text class="result-translation" v-if="item.translation">📖 {{ item.translation }}</text>
|
||||||
<view class="result-actions">
|
<view class="result-actions">
|
||||||
<text class="copy-btn" @click="copyText(item.content || item)">复制</text>
|
<text class="copy-btn" @click="copyText(item.content || item)">复制</text>
|
||||||
|
<text class="play-btn" @click="playTts(item.content || item)">朗读</text>
|
||||||
<text class="send-btn" @click="sendToWhatsapp(item.content || item)" v-if="activeTab !== 'product'">发送</text>
|
<text class="send-btn" @click="sendToWhatsapp(item.content || item)" v-if="activeTab !== 'product'">发送</text>
|
||||||
<text class="competitor-btn" @click="runCompetitorAnalysis" v-if="activeTab === 'copy'">竞品分析</text>
|
<text class="competitor-btn" @click="runCompetitorAnalysis" v-if="activeTab === 'copy'">竞品分析</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -146,7 +148,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed } from 'vue'
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { marketingApi, interactionApi } from '@/utils/api.js'
|
import { marketingApi, interactionApi, translateApi, BASE_URL } from '@/utils/api.js'
|
||||||
|
|
||||||
const tabConfig = {
|
const tabConfig = {
|
||||||
copy: {
|
copy: {
|
||||||
@@ -302,6 +304,15 @@ const generateContent = async () => {
|
|||||||
resultsMap[tab] = res.results || []
|
resultsMap[tab] = res.results || []
|
||||||
if (res.results && res.results.length > 0) {
|
if (res.results && res.results.length > 0) {
|
||||||
selectedStyle.value = res.results[0].style || formData.value.style
|
selectedStyle.value = res.results[0].style || formData.value.style
|
||||||
|
const items = res.results.filter(r => (r.content || r).match(/[a-zA-Z]{3,}/))
|
||||||
|
if (items.length > 0) {
|
||||||
|
Promise.all(items.map(async (r) => {
|
||||||
|
try {
|
||||||
|
const t = await translateApi.translate(r.content || r, 'zh')
|
||||||
|
r.translation = t.translated || t.translated_text || ''
|
||||||
|
} catch {}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadStats()
|
loadStats()
|
||||||
}
|
}
|
||||||
@@ -373,6 +384,43 @@ const sendToWhatsapp = (text) => {
|
|||||||
uni.showToast({ title: '请先选择客户', icon: 'none' })
|
uni.showToast({ title: '请先选择客户', icon: 'none' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const playTts = (text) => {
|
||||||
|
if (!text) return
|
||||||
|
const hasChinese = /[\u4e00-\u9fa5]/.test(text)
|
||||||
|
const lang = hasChinese ? 'zh' : 'en'
|
||||||
|
const token = uni.getStorageSync('token')
|
||||||
|
|
||||||
|
uni.showLoading({ title: '语音生成中...' })
|
||||||
|
if (typeof window !== 'undefined' && window.Audio) {
|
||||||
|
uni.request({
|
||||||
|
url: `${BASE_URL}/translate/tts?text=${encodeURIComponent(text)}&lang=${lang}`,
|
||||||
|
method: 'GET',
|
||||||
|
header: { Authorization: `Bearer ${token}` },
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
success: (res) => {
|
||||||
|
uni.hideLoading()
|
||||||
|
if (res.statusCode === 200 && res.data) {
|
||||||
|
const blob = new Blob([res.data], { type: 'audio/mpeg' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const audio = new Audio(url)
|
||||||
|
audio.onended = () => { URL.revokeObjectURL(url) }
|
||||||
|
audio.play().catch(() => {
|
||||||
|
uni.showToast({ title: '播放失败', icon: 'none' })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '语音生成失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '语音生成失败', icon: 'none' })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '当前环境不支持朗读', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const runCompetitorAnalysis = async () => {
|
const runCompetitorAnalysis = async () => {
|
||||||
if (!formData.value.product_name) {
|
if (!formData.value.product_name) {
|
||||||
uni.showToast({ title: '请先输入产品名称', icon: 'none' })
|
uni.showToast({ title: '请先输入产品名称', icon: 'none' })
|
||||||
@@ -620,7 +668,18 @@ const runCompetitorAnalysis = async () => {
|
|||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-translation {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: block;
|
||||||
margin-bottom: 16rpx;
|
margin-bottom: 16rpx;
|
||||||
|
padding: 12rpx;
|
||||||
|
background: #f0f5ff;
|
||||||
|
border-radius: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-actions {
|
.result-actions {
|
||||||
@@ -628,7 +687,7 @@ const runCompetitorAnalysis = async () => {
|
|||||||
gap: 20rpx;
|
gap: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn, .send-btn, .competitor-btn {
|
.copy-btn, .send-btn, .competitor-btn, .play-btn {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
padding: 8rpx 20rpx;
|
padding: 8rpx 20rpx;
|
||||||
border-radius: 6rpx;
|
border-radius: 6rpx;
|
||||||
@@ -639,6 +698,11 @@ const runCompetitorAnalysis = async () => {
|
|||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
background: #fff7e6;
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
.send-btn {
|
.send-btn {
|
||||||
background: #07c160;
|
background: #07c160;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|||||||
Reference in New Issue
Block a user