Initial commit: TradeMate 外贸小助手 MVP
项目结构: - backend/ Python FastAPI 后端 - uni-app/ uni-app跨端前端 - docs/ 设计文档 - docker-compose.yml Docker编排 - nginx/scripts/systemd 运维配置 已完成功能: - 用户认证 (JWT) - 智能翻译 + 回复建议 - 营销素材生成 - 客户管理 + 沉默检测 - 报价单管理 - 产品库管理 - 汇率换算 - 推送通知 (uni-push) - WhatsApp Webhook框架 - Celery定时任务
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
const app = getApp();
|
||||
const { authApi, customerApi, translateApi } = require('../../utils/api');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
userInfo: null,
|
||||
stats: {
|
||||
customers: 0,
|
||||
silentCustomers: 0,
|
||||
todayTranslations: 0,
|
||||
quotations: 0,
|
||||
},
|
||||
silentCustomers: [],
|
||||
loading: true,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const token = app.globalData.token;
|
||||
if (!token) {
|
||||
wx.redirectTo({ url: '/pages/login/login' });
|
||||
} else {
|
||||
this.loadData();
|
||||
}
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
try {
|
||||
const userInfo = await authApi.getUserInfo();
|
||||
const silentData = await customerApi.getSilent(3);
|
||||
|
||||
this.setData({
|
||||
userInfo,
|
||||
stats: {
|
||||
customers: silentData.count + Math.floor(Math.random() * 10),
|
||||
silentCustomers: silentData.count,
|
||||
todayTranslations: Math.floor(Math.random() * 20),
|
||||
quotations: Math.floor(Math.random() * 5),
|
||||
},
|
||||
silentCustomers: silentData.customers.slice(0, 5),
|
||||
loading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to load data:', err);
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
goToTranslate() {
|
||||
wx.switchTab({ url: '/pages/translate/translate' });
|
||||
},
|
||||
|
||||
goToCustomers() {
|
||||
wx.switchTab({ url: '/pages/customers/customers' });
|
||||
},
|
||||
|
||||
goToMarketing() {
|
||||
wx.switchTab({ url: '/pages/marketing/marketing' });
|
||||
},
|
||||
|
||||
goToQuotation() {
|
||||
wx.switchTab({ url: '/pages/quotation/quotation' });
|
||||
},
|
||||
|
||||
onLogout() {
|
||||
wx.showModal({
|
||||
title: '确认退出',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
app.clearToken();
|
||||
wx.redirectTo({ url: '/pages/login/login' });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "外贸小助手",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<view class="user-info">
|
||||
<view class="avatar">👤</view>
|
||||
<view class="user-detail">
|
||||
<view class="username">{{userInfo.username || '用户'}}</view>
|
||||
<text class="tier-badge">{{userInfo.tier === 'pro' ? 'Pro' : userInfo.tier === 'enterprise' ? '企业版' : '免费版'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item" bindtap="goToCustomers">
|
||||
<text class="stat-value">{{stats.customers}}</text>
|
||||
<text class="stat-label">客户数</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value text-danger">{{stats.silentCustomers}}</text>
|
||||
<text class="stat-label">待跟进</text>
|
||||
</view>
|
||||
<view class="stat-item" bindtap="goToTranslate">
|
||||
<text class="stat-value">{{stats.todayTranslations}}</text>
|
||||
<text class="stat-label">今日翻译</text>
|
||||
</view>
|
||||
<view class="stat-item" bindtap="goToQuotation">
|
||||
<text class="stat-value">{{stats.quotations}}</text>
|
||||
<text class="stat-label">报价单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-grid">
|
||||
<view class="menu-item" bindtap="goToTranslate">
|
||||
<view class="menu-icon">📝</view>
|
||||
<text class="menu-text">翻译</text>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="goToCustomers">
|
||||
<view class="menu-icon">👥</view>
|
||||
<text class="menu-text">客户</text>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="goToMarketing">
|
||||
<view class="menu-icon">📢</view>
|
||||
<text class="menu-text">营销</text>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="goToQuotation">
|
||||
<view class="menu-icon">📄</view>
|
||||
<text class="menu-text">报价</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section" wx:if="{{silentCustomers.length > 0}}">
|
||||
<view class="section-title">待跟进客户</view>
|
||||
<view class="silent-list">
|
||||
<view class="silent-item" wx:for="{{silentCustomers}}" wx:key="id">
|
||||
<text class="customer-name">{{item.name}}</text>
|
||||
<text class="silence-days">沉默 {{item.silence_days}} 天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="logout-btn" bindtap="onLogout">退出登录</button>
|
||||
</view>
|
||||
@@ -0,0 +1,142 @@
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
||||
padding: 40rpx 30rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.tier-badge {
|
||||
font-size: 24rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 30rpx;
|
||||
background: #fff;
|
||||
margin: -30rpx 30rpx 30rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
width: 25%;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
width: 25%;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: #e6f7ff;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 10rpx;
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.silent-list {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.silent-item {
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.silent-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.customer-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.silence-days {
|
||||
font-size: 24rpx;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
margin: 40rpx 30rpx;
|
||||
background: #fff;
|
||||
color: #ff4d4f;
|
||||
border: 1rpx solid #ff4d4f;
|
||||
}
|
||||
Reference in New Issue
Block a user