Unify frontend config, fix marketing tracking field mismatch, expose customer notes in API

Centralizes all hardcoded page paths, storage keys, external URLs, and branding into a single uni-app/src/config.js. Fixes trackMarketingEffect sending wrong field names (action/content_preview -> event_type/content) that silently dropped tracking data. Adds notes, estimated_value, next_followup_at to Customer response. Removes '翻译' from bottom tab nav (5 tabs now), adds quick translate card on home page. Makes profile page header color consistent with app theme (#1890ff).
This commit is contained in:
TradeMate Dev
2026-05-20 14:30:50 +08:00
parent f8a23855d2
commit a60aac4638
12 changed files with 689 additions and 67 deletions
+114 -35
View File
@@ -105,49 +105,68 @@
<view class="section" v-if="hasLogin && followupStats.pending > 0">
<view class="section-title">
<text>待跟进提醒</text>
<text class="section-more" @click="goToPage('/pages/followup/followup')">查看全部 ></text>
<text class="section-more" @click="goToPage(PAGES.FOLLOWUP)">查看全部 ></text>
</view>
<view class="followup-card" @click="goToPage('/pages/followup/followup')">
<view class="followup-card" @click="goToPage(PAGES.FOLLOWUP)">
<text class="followup-count">{{ followupStats.pending }}</text>
<text class="followup-label">个客户需要跟进</text>
<text class="followup-hint">{{ followupStats.sent }} 已发送 · {{ followupStats.replied }} 已回复</text>
</view>
</view>
<view class="section" v-if="hasLogin">
<view class="section-title">
<text>快捷翻译</text>
<text class="section-more" @click="goToPage(PAGES.TRANSLATE)">去翻译 ></text>
</view>
<view class="translate-mini">
<textarea class="translate-mini-input" v-model="quickTranslateText" placeholder="输入文本,快速翻译..." />
<view class="translate-mini-actions">
<button class="translate-mini-btn" @click="doQuickTranslate" :disabled="!quickTranslateText.trim()">翻译</button>
<button class="translate-mini-btn extract" @click="doQuickExtract" :disabled="!quickTranslateText.trim()">提取信息</button>
</view>
<text class="translate-mini-result" v-if="quickTranslateResult">{{ quickTranslateResult }}</text>
</view>
</view>
<view class="more-section">
<view class="section-title">功能矩阵</view>
<view class="more-grid">
<view class="more-item" @click="hasLogin ? goToPage('/pages/product/product') : goToLogin()">
<view class="more-item" @click="goToPage(PAGES.TRANSLATE)">
<text class="more-icon">🔤</text>
<text class="more-text">翻译</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage(PAGES.PRODUCT) : goToLogin()">
<text class="more-icon">📦</text>
<text class="more-text">产品库</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage('/pages/followup/followup') : goToLogin()">
<view class="more-item" @click="hasLogin ? goToPage(PAGES.FOLLOWUP) : goToLogin()">
<text class="more-icon">📋</text>
<text class="more-text">跟进</text>
<text class="notif-badge" v-if="hasLogin && followupStats.pending > 0">{{ followupStats.pending > 99 ? '99+' : followupStats.pending }}</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage('/pages/notification/notification') : goToLogin()">
<view class="more-item" @click="hasLogin ? goToPage(PAGES.NOTIFICATION) : goToLogin()">
<text class="more-icon">🔔</text>
<text class="more-text">通知</text>
<text class="notif-badge" v-if="hasLogin && unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage('/pages/analytics/analytics') : goToLogin()">
<view class="more-item" @click="hasLogin ? goToPage(PAGES.ANALYTICS) : goToLogin()">
<text class="more-icon">📊</text>
<text class="more-text">分析</text>
</view>
<view class="more-item" @click="goToPage('/pages/upgrade/upgrade')">
<view class="more-item" @click="goToPage(PAGES.UPGRADE)">
<text class="more-icon">💎</text>
<text class="more-text">升级</text>
</view>
<view class="more-item" @click="goToPage('/pages/feedback/feedback')">
<view class="more-item" @click="goToPage(PAGES.FEEDBACK)">
<text class="more-icon">💬</text>
<text class="more-text">反馈</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage('/pages/team/team') : goToLogin()">
<view class="more-item" @click="hasLogin ? goToPage(PAGES.TEAM) : goToLogin()">
<text class="more-icon">👨👩👧👦</text>
<text class="more-text">团队</text>
</view>
<view class="more-item" v-if="isAdmin" @click="goToPage('/pages/admin/admin')">
<view class="more-item" v-if="isAdmin" @click="goToPage(PAGES.ADMIN)">
<text class="more-icon"></text>
<text class="more-text">管理</text>
</view>
@@ -243,15 +262,15 @@
<view class="footer">
<view class="footer-links">
<text class="footer-link" @click="goToPage('/pages/agreement/privacy')">隐私政策</text>
<text class="footer-link" @click="goToPage(PAGES.AGREEMENT_PRIVACY)">隐私政策</text>
<text class="footer-divider">|</text>
<text class="footer-link" @click="goToPage('/pages/agreement/terms')">用户协议</text>
<text class="footer-link" @click="goToPage(PAGES.AGREEMENT_TERMS)">用户协议</text>
</view>
<view class="footer-beian">
<a class="footer-beian-link" href="https://beian.miit.gov.cn" target="_blank">京ICP备2026007249号-1</a>
<a class="footer-beian-link" href="https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545" target="_blank">京公网安备11011502039545号</a>
<a class="footer-beian-link" :href="EXTERNAL_URLS.BEIAN" target="_blank">{{ APP_INFO.ICP }}</a>
<a class="footer-beian-link" :href="EXTERNAL_URLS.BEIAN_PSB" target="_blank">{{ APP_INFO.PSB }}</a>
</view>
<text class="footer-copyright">© 2026 北京宇之然科技中心. 保留所有权利.</text>
<text class="footer-copyright">© {{ APP_INFO.COPYRIGHT }}. 保留所有权利.</text>
</view>
<AiAssistant />
</view>
@@ -262,6 +281,7 @@ import { ref, computed, onUnmounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { authApi, customerApi, analyticsApi, onboardingApi, notificationApi, followupApi, translateApi, BASE_URL } from '@/utils/api.js'
import AiAssistant from '@/components/ai-assistant.vue'
import { STORAGE_KEYS, PAGES, EXTERNAL_URLS, APP_INFO, EXTRACT_FIELD_LABELS } from '@/config.js'
const showAnnouncement = ref(false)
const currentAnnouncement = ref(0)
@@ -298,15 +318,12 @@ const productDesc = ref('')
const targetMarket = ref('US importers')
const generatedContent = ref([])
const quickTranslateText = ref('')
const quickTranslateResult = ref('')
const tryText = ref('')
const tryResult = ref('')
const tryExtracted = ref(null)
const extractFieldLabels = {
product_name: '产品名称', quantity: '数量', price: '价格',
currency: '货币', delivery_terms: '交货条款', target_country: '目标国家',
intent: '意图', product_interest: '感兴趣产品', budget: '预算',
urgency: '紧迫程度', contact_info: '联系方式',
}
const extractFieldLabels = EXTRACT_FIELD_LABELS
const tryLoading = ref(false)
onShow(() => {
@@ -317,7 +334,7 @@ onShow(() => {
const token = uni.getStorageSync('token')
const isGuest = uni.getStorageSync('isGuest')
if (token && !isGuest) {
uni.setStorageSync('isGuest', false)
uni.setStorageSync(STORAGE_KEYS.IS_GUEST, false)
loadData()
checkOnboarding()
loadUnread()
@@ -380,7 +397,7 @@ const loadFollowupStats = async () => {
}
const finishOnboarding = () => {
uni.setStorageSync('onboarded', true)
uni.setStorageSync(STORAGE_KEYS.ONBOARDED, true)
showOnboarding.value = false
onboardingStep.value = 1
productName.value = ''
@@ -410,7 +427,7 @@ const loadData = async () => {
}
}
const tabbarPages = ['/pages/index/index', '/pages/translate/translate', '/pages/customers/customers', '/pages/marketing/marketing', '/pages/quotation/quotation']
const tabbarPages = [PAGES.INDEX, PAGES.TRANSLATE, PAGES.CUSTOMERS, PAGES.MARKETING, PAGES.QUOTATION]
const goToPage = (url) => {
if (tabbarPages.includes(url)) {
@@ -421,22 +438,41 @@ const goToPage = (url) => {
}
const goToLogin = () => {
uni.reLaunch({ url: '/pages/login/login' })
}
const handleLogout = () => {
uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.clearStorageSync()
uni.reLaunch({ url: '/pages/login/login' })
uni.showModal({
title: '提示',
content: '请先登录',
success: (res) => {
if (res.confirm) {
uni.reLaunch({ url: PAGES.LOGIN })
}
},
})
}
const doQuickTranslate = async () => {
if (!quickTranslateText.value.trim()) return
try {
const chinesePattern = /[\u4e00-\u9fa5]/
const targetLang = chinesePattern.test(quickTranslateText.value) ? 'en' : 'zh'
const res = await translateApi.translate(quickTranslateText.value, targetLang, 'auto')
quickTranslateResult.value = res.translated_text || res.translated || '翻译成功'
} catch {
quickTranslateResult.value = '翻译失败'
}
}
const doQuickExtract = async () => {
if (!quickTranslateText.value.trim()) return
try {
const res = await translateApi.extract(quickTranslateText.value, 'auto')
const extracted = res.extracted || {}
const obj = typeof extracted === 'string' ? { raw: extracted } : extracted
quickTranslateResult.value = Object.entries(obj).map(([k, v]) => `${k}: ${v}`).join('\n')
} catch {
quickTranslateResult.value = '提取失败'
}
}
const handleTryTranslate = async () => {
if (!tryText.value.trim()) {
uni.showToast({ title: '请输入内容', icon: 'none' })
@@ -941,6 +977,49 @@ const playTryResult = () => {
margin-top: 12rpx;
}
.translate-mini {
padding: 24rpx;
}
.translate-mini-input {
width: 100%;
height: 120rpx;
background: #f5f5f5;
border-radius: 12rpx;
padding: 16rpx 20rpx;
font-size: 26rpx;
box-sizing: border-box;
resize: none;
}
.translate-mini-actions {
display: flex;
gap: 16rpx;
margin-top: 16rpx;
}
.translate-mini-btn {
flex: 1;
height: 64rpx;
line-height: 64rpx;
text-align: center;
background: #1890ff;
color: #fff;
border-radius: 10rpx;
font-size: 26rpx;
}
.translate-mini-btn.extract {
background: #f5f5f5;
color: #333;
}
.translate-mini-result {
display: block;
margin-top: 16rpx;
padding: 16rpx 20rpx;
background: #f0f5ff;
border-radius: 10rpx;
font-size: 26rpx;
color: #333;
white-space: pre-wrap;
}
.more-section {
background: #fff;
border-radius: 16rpx;