fix: onboarding passes product_info dict; marketing service template fallback when no AI; frontend style-switching tabs

This commit is contained in:
TradeMate Dev
2026-05-14 19:23:45 +08:00
parent 93f6ad306a
commit 566f59f0e4
3 changed files with 128 additions and 12 deletions
+72 -6
View File
@@ -100,12 +100,23 @@
<text class="export-btn" @click="exportCsv" v-if="activeTab !== 'keywords'">导出CSV</text>
</view>
</view>
<view class="style-tabs" v-if="activeTab !== 'keywords' && availableStyles.length > 1">
<view
class="style-tab"
v-for="s in availableStyles"
:key="s"
:class="{ active: selectedStyle === s }"
@click="selectedStyle = s"
>
{{ styleLabels[s] || s }}
</view>
</view>
<view class="results-list" v-if="activeTab !== 'keywords'">
<view class="result-item" v-for="(item, index) in resultsMap[activeTab]" :key="index">
<text class="result-text">{{ item }}</text>
<view class="result-item" v-for="(item, index) in filteredResults" :key="index">
<text class="result-text">{{ item.content || item }}</text>
<view class="result-actions">
<text class="copy-btn" @click="copyText(item)">复制</text>
<text class="send-btn" @click="sendToWhatsapp(item)" v-if="activeTab !== 'product'">发送</text>
<text class="copy-btn" @click="copyText(item.content || item)">复制</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>
</view>
</view>
@@ -134,7 +145,7 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import { marketingApi, interactionApi } from '@/utils/api.js'
const tabConfig = {
@@ -193,6 +204,31 @@ const loading = ref(false)
const resultsMap = reactive({ copy: [], whatsapp: [], product: [], keywords: [] })
const competitorResult = ref(null)
const stats = ref(null)
const selectedStyle = ref('professional')
const styleLabels = {
professional: '专业正式',
friendly: '亲切友好',
urgent: '紧急催促',
benefit_focused: '利益导向',
storytelling: '故事叙述',
}
const availableStyles = computed(() => {
const items = resultsMap[activeTab.value]
if (!items || items.length === 0 || typeof items[0] === 'string') return []
const styles = [...new Set(items.map(i => i.style).filter(Boolean))]
return styles
})
const filteredResults = computed(() => {
const items = resultsMap[activeTab.value]
if (!items || items.length === 0) return []
if (typeof items[0] === 'string') return items
const style = selectedStyle.value
const filtered = items.filter(i => i.style === style)
return filtered.length > 0 ? filtered : items
})
const formData = ref({
product_name: '',
@@ -254,6 +290,9 @@ const generateContent = async () => {
formData.value.style
)
resultsMap[tab] = res.results || []
if (res.results && res.results.length > 0) {
selectedStyle.value = res.results[0].style || formData.value.style
}
loadStats()
}
} catch (err) {
@@ -278,7 +317,10 @@ const exportCsv = () => {
const items = resultsMap[activeTab.value]
if (!items || items.length === 0) return
let csv = 'Content\n'
items.forEach(r => { csv += `"${r.replace(/"/g, '""')}"\n` })
items.forEach(r => {
const text = typeof r === 'string' ? r : (r.content || '')
csv += `"${text.replace(/"/g, '""')}"\n`
})
const blob = new Blob([csv], { type: 'text/csv' })
const url = URL.createObjectURL(blob)
uni.downloadFile({
@@ -439,6 +481,30 @@ const runCompetitorAnalysis = async () => {
border: 2rpx solid #1890ff;
}
.style-tabs {
display: flex;
gap: 12rpx;
margin-bottom: 20rpx;
overflow-x: auto;
padding-bottom: 4rpx;
}
.style-tab {
flex-shrink: 0;
padding: 12rpx 24rpx;
background: #f5f5f5;
border-radius: 8rpx;
font-size: 24rpx;
color: #666;
white-space: nowrap;
}
.style-tab.active {
background: #e6f7ff;
color: #1890ff;
border: 2rpx solid #1890ff;
}
.generate-btn {
width: 100%;
height: 88rpx;