feat: redesign uni-app mobile with consolidated 4-tab workspace

Replace 5-tab layout (首页/客户/营销/报价/我的) with 4-tab (首页/客户/业务/我的). Add workspace-customer (客户列表/挖掘新客/智能跟进) and workspace-biz (翻译/产品库/报价单/营销素材) pages. Simplify index to dashboard, update profile nav.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
TradeMate Dev
2026-06-21 23:05:29 +08:00
parent 5e1aa95969
commit b1ffb78629
6 changed files with 2528 additions and 18 deletions
+3 -1
View File
@@ -10,11 +10,13 @@ export const STORAGE_KEYS = {
export const PAGES = {
INDEX: '/pages/index/index',
WORKSPACE_CUSTOMER: '/pages/workspace-customer/workspace-customer',
WORKSPACE_BIZ: '/pages/workspace-biz/workspace-biz',
PROFILE: '/pages/profile/profile',
TRANSLATE: '/pages/translate/translate',
CUSTOMERS: '/pages/customers/customers',
MARKETING: '/pages/marketing/marketing',
QUOTATION: '/pages/quotation/quotation',
PROFILE: '/pages/profile/profile',
LOGIN: '/pages/login/login',
PRODUCT: '/pages/product/product',
UPGRADE: '/pages/upgrade/upgrade',
+17 -7
View File
@@ -18,6 +18,20 @@
"navigationBarTitleText": "客户管理"
}
},
{
"path": "pages/workspace-customer/workspace-customer",
"style": {
"navigationBarTitleText": "客户工作台",
"navigationStyle": "custom"
}
},
{
"path": "pages/workspace-biz/workspace-biz",
"style": {
"navigationBarTitleText": "业务工作台",
"navigationStyle": "custom"
}
},
{
"path": "pages/marketing/marketing",
"style": {
@@ -145,16 +159,12 @@
"text": "首页"
},
{
"pagePath": "pages/customers/customers",
"pagePath": "pages/workspace-customer/workspace-customer",
"text": "客户"
},
{
"pagePath": "pages/marketing/marketing",
"text": "营销"
},
{
"pagePath": "pages/quotation/quotation",
"text": "报价"
"pagePath": "pages/workspace-biz/workspace-biz",
"text": "业务"
},
{
"pagePath": "pages/profile/profile",
+15 -7
View File
@@ -130,25 +130,33 @@
</view>
<view class="more-section">
<view class="section-title">功能矩阵</view>
<view class="section-title">快捷入口</view>
<view class="more-grid">
<view class="more-item" @click="goToPage(PAGES.TRANSLATE)">
<view class="more-item" @click="goToPage(PAGES.WORKSPACE_BIZ)">
<text class="more-icon">🔤</text>
<text class="more-text">翻译</text>
</view>
<view class="more-item" @click="goToPage(PAGES.DISCOVERY)">
<view class="more-item" @click="goToPage(PAGES.WORKSPACE_CUSTOMER)">
<text class="more-icon">🔍</text>
<text class="more-text">挖掘新</text>
<text class="more-text"></text>
</view>
<view class="more-item" @click="hasLogin ? goToPage(PAGES.PRODUCT) : goToLogin()">
<view class="more-item" @click="hasLogin ? goToPage(PAGES.WORKSPACE_BIZ) : goToLogin()">
<text class="more-icon">📦</text>
<text class="more-text">产品</text>
<text class="more-text">产品</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage(PAGES.FOLLOWUP) : goToLogin()">
<view class="more-item" @click="hasLogin ? goToPage(PAGES.WORKSPACE_CUSTOMER) : 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.WORKSPACE_BIZ) : goToLogin()">
<text class="more-icon">💰</text>
<text class="more-text">报价</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage(PAGES.WORKSPACE_BIZ) : goToLogin()">
<text class="more-icon">📝</text>
<text class="more-text">营销</text>
</view>
<view class="more-item" @click="hasLogin ? goToPage(PAGES.NOTIFICATION) : goToLogin()">
<text class="more-icon">🔔</text>
<text class="more-text">通知</text>
+45 -3
View File
@@ -19,6 +19,26 @@
</view>
</view>
<view class="section">
<view class="section-title">工作台</view>
<view class="menu-item" @click="goNotification">
<text class="menu-icon">🔔</text>
<text class="menu-text">通知中心</text>
<text class="badge-count" v-if="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goAnalytics">
<text class="menu-icon">📊</text>
<text class="menu-text">数据分析</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goTeam">
<text class="menu-icon">👥</text>
<text class="menu-text">团队协作</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="section">
<view class="section-title">账号设置</view>
<view class="menu-item" v-if="user.tier !== 'guest'" @click="showProfileEdit = true">
@@ -59,6 +79,16 @@
<text class="menu-text">意见反馈</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goPage('/pages/upgrade/upgrade')">
<text class="menu-icon">💎</text>
<text class="menu-text">升级会员</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="showWechatModal = true">
<text class="menu-icon">💁</text>
<text class="menu-text">联系客服</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="section">
@@ -138,12 +168,13 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { authApi, creditApi } from '@/utils/api.js'
import { authApi, creditApi, notificationApi } from '@/utils/api.js'
import AiAssistant from '@/components/ai-assistant.vue'
import { STORAGE_KEYS, PAGES, TIER_LABELS } from '@/config.js'
const user = ref({})
const creditBalance = ref(0)
const unreadCount = ref(0)
const showProfileEdit = ref(false)
const showPassword = ref(false)
const editForm = ref({ username: '', email: '' })
@@ -217,6 +248,17 @@ const goFeedback = () => uni.navigateTo({ url: PAGES.FEEDBACK })
const goAgreement = (type) => uni.navigateTo({ url: `/pages/agreement/${type}` })
const goCertification = () => uni.navigateTo({ url: PAGES.CERTIFICATION })
const goInvoice = () => uni.navigateTo({ url: PAGES.INVOICE })
const goNotification = () => uni.navigateTo({ url: PAGES.NOTIFICATION })
const goAnalytics = () => uni.navigateTo({ url: PAGES.ANALYTICS })
const goTeam = () => uni.navigateTo({ url: PAGES.TEAM })
const goPage = (url) => uni.navigateTo({ url })
const loadUnread = async () => {
try {
const res = await notificationApi.unreadCount()
unreadCount.value = res.count || 0
} catch { unreadCount.value = 0 }
}
const logout = () => {
uni.showModal({
@@ -232,8 +274,8 @@ const logout = () => {
})
}
onMounted(loadUser)
onShow(loadUser)
onMounted(() => { loadUser(); loadUnread() })
onShow(() => { loadUser(); loadUnread() })
</script>
<style scoped>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff