feat: 修复 H5 底部导航覆盖 + 更新项目进度文档
## H5 底部导航修复 (Bug #10) - 精简 App.vue,移除重复 tabbar,仅保留全局样式 - uni-page 设置 height: calc(100% - 50px) + overflow-y: auto - 内容区域精确停在底部导航上方,独立滚动不再叠加 - 恢复 custom-tab-bar 组件 ## 项目进度文档 - PROGRESS.md 更新至 10 个 Bug 修复 - 新增 H5 底部导航修复记录 - 新增历史变更条目
This commit is contained in:
@@ -4,21 +4,21 @@
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'copy' }"
|
||||
@click="activeTab = 'copy'"
|
||||
@click="activeTab = 'copy'; activeTab === 'copy' && loadStats()"
|
||||
>
|
||||
开发信
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'whatsapp' }"
|
||||
@click="activeTab = 'whatsapp'"
|
||||
@click="activeTab = 'whatsapp'; activeTab === 'whatsapp' && loadStats()"
|
||||
>
|
||||
WhatsApp话术
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'product' }"
|
||||
@click="activeTab = 'product'"
|
||||
@click="activeTab = 'product'; activeTab === 'product' && loadStats()"
|
||||
>
|
||||
产品描述
|
||||
</view>
|
||||
@@ -31,6 +31,21 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-section" v-if="stats">
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{ stats.today_copy || 0 }}</text>
|
||||
<text class="stat-label">今日复制</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{ stats.today_send || 0 }}</text>
|
||||
<text class="stat-label">今日发送</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{ stats.weekly_total || 0 }}</text>
|
||||
<text class="stat-label">本周共</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-section">
|
||||
<view class="form-item">
|
||||
<text class="form-label">产品名称</text>
|
||||
@@ -77,10 +92,11 @@
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="results-section" v-if="results.length > 0">
|
||||
<view class="results-section" v-if="results.length > 0 && activeTab !== 'keywords'">
|
||||
<view class="results-header">
|
||||
<text class="results-title">生成的文案</text>
|
||||
<text class="refresh-btn" @click="generateContent">换一批</text>
|
||||
<text class="export-btn" @click="exportCsv">导出CSV</text>
|
||||
</view>
|
||||
<view class="results-list">
|
||||
<view class="result-item" v-for="(item, index) in results" :key="index">
|
||||
@@ -88,6 +104,7 @@
|
||||
<view class="result-actions">
|
||||
<text class="copy-btn" @click="copyText(item)">复制</text>
|
||||
<text class="send-btn" @click="sendToWhatsapp(item)">发送</text>
|
||||
<text class="competitor-btn" @click="runCompetitorAnalysis">竞品分析</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -104,6 +121,16 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="competitor-section" v-if="competitorResult">
|
||||
<view class="competitor-header">
|
||||
<text class="competitor-title">竞品分析结果</text>
|
||||
<text class="competitor-close" @click="competitorResult = null">×</text>
|
||||
</view>
|
||||
<view class="competitor-content">
|
||||
<text class="competitor-text">{{ competitorResult }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty" v-if="!loading && results.length === 0 && activeTab !== 'keywords'">
|
||||
<text>输入产品信息,点击生成文案</text>
|
||||
</view>
|
||||
@@ -112,12 +139,14 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { marketingApi } from '@/utils/api.js'
|
||||
import { marketingApi, interactionApi } from '@/utils/api.js'
|
||||
|
||||
const activeTab = ref('copy')
|
||||
const loading = ref(false)
|
||||
const results = ref([])
|
||||
const keywords = ref([])
|
||||
const competitorResult = ref(null)
|
||||
const stats = ref(null)
|
||||
|
||||
const formData = ref({
|
||||
product_name: '',
|
||||
@@ -138,6 +167,15 @@ const onTargetChange = (e) => {
|
||||
formData.value.target = targetMarkets.value[e.detail.value]
|
||||
}
|
||||
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
const res = await interactionApi.getMarketingEffectStats()
|
||||
stats.value = res
|
||||
} catch (err) {
|
||||
console.error('加载统计失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
const generateContent = async () => {
|
||||
if (!formData.value.product_name) {
|
||||
uni.showToast({ title: '请输入产品名称', icon: 'none' })
|
||||
@@ -163,6 +201,7 @@ const generateContent = async () => {
|
||||
formData.value.style
|
||||
)
|
||||
results.value = res.results || []
|
||||
loadStats()
|
||||
}
|
||||
} catch (err) {
|
||||
uni.showToast({ title: err.message || '生成失败', icon: 'none' })
|
||||
@@ -175,14 +214,51 @@ const copyText = (text) => {
|
||||
uni.setClipboardData({
|
||||
data: text,
|
||||
success: () => {
|
||||
interactionApi.trackMarketingEffect({ action: 'copy', content_preview: text.slice(0, 100) }).catch(() => {})
|
||||
loadStats()
|
||||
uni.showToast({ title: '已复制', icon: 'success' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const exportCsv = () => {
|
||||
if (results.value.length === 0) return
|
||||
let csv = 'Content\n'
|
||||
results.value.forEach(r => { csv += `"${r.replace(/"/g, '""')}"\n` })
|
||||
const blob = new Blob([csv], { type: 'text/csv' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
uni.downloadFile({
|
||||
url,
|
||||
success: (res) => {
|
||||
uni.saveFile({ tempFilePath: res.tempFilePath })
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
},
|
||||
fail: () => { uni.showToast({ title: '导出失败', icon: 'none' }) },
|
||||
})
|
||||
}
|
||||
|
||||
const sendToWhatsapp = (text) => {
|
||||
interactionApi.trackMarketingEffect({ action: 'send', content_preview: text.slice(0, 100) }).catch(() => {})
|
||||
loadStats()
|
||||
uni.showToast({ title: '请先选择客户', icon: 'none' })
|
||||
}
|
||||
|
||||
const runCompetitorAnalysis = async () => {
|
||||
try {
|
||||
uni.showLoading({ title: '分析中...' })
|
||||
const res = await marketingApi.competitorAnalysis(
|
||||
formData.value.product_name,
|
||||
formData.value.description,
|
||||
'',
|
||||
formData.value.target
|
||||
)
|
||||
uni.hideLoading()
|
||||
competitorResult.value = typeof res.analysis === 'string' ? res.analysis : JSON.stringify(res.analysis, null, 2)
|
||||
} catch (err) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: err.message || '分析失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -215,6 +291,33 @@ const sendToWhatsapp = (text) => {
|
||||
border-bottom: 4rpx solid #1890ff;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #1890ff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
@@ -316,6 +419,12 @@ const sendToWhatsapp = (text) => {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 24rpx;
|
||||
color: #52c41a;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.results-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -340,7 +449,7 @@ const sendToWhatsapp = (text) => {
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.copy-btn, .send-btn {
|
||||
.copy-btn, .send-btn, .competitor-btn {
|
||||
font-size: 24rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 6rpx;
|
||||
@@ -356,6 +465,11 @@ const sendToWhatsapp = (text) => {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.competitor-btn {
|
||||
background: #f9f0ff;
|
||||
color: #722ed1;
|
||||
}
|
||||
|
||||
.history-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
@@ -383,9 +497,46 @@ const sendToWhatsapp = (text) => {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.competitor-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.competitor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.competitor-title {
|
||||
font-size: 26rpx;
|
||||
color: #722ed1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.competitor-close {
|
||||
font-size: 36rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.competitor-content {
|
||||
padding: 16rpx;
|
||||
background: #f9f0ff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.competitor-text {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 100rpx;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user