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,101 @@
|
||||
const { translateApi } = require('../../utils/api');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
tab: 'translate',
|
||||
sourceText: '',
|
||||
translatedText: '',
|
||||
targetLang: 'en',
|
||||
langOptions: [
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'zh', label: '中文' },
|
||||
{ value: 'es', label: 'Español' },
|
||||
{ value: 'fr', label: 'Français' },
|
||||
{ value: 'de', label: 'Deutsch' },
|
||||
{ value: 'ja', label: '日本語' },
|
||||
{ value: 'pt', label: 'Português' },
|
||||
],
|
||||
replyInquiry: '',
|
||||
replySuggestions: [],
|
||||
loading: false,
|
||||
},
|
||||
|
||||
onLoad() {},
|
||||
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab;
|
||||
this.setData({ tab });
|
||||
},
|
||||
|
||||
onSourceInput(e) {
|
||||
this.setData({ sourceText: e.detail.value });
|
||||
},
|
||||
|
||||
onReplyInput(e) {
|
||||
this.setData({ replyInquiry: e.detail.value });
|
||||
},
|
||||
|
||||
onLangChange(e) {
|
||||
this.setData({ targetLang: e.detail.value });
|
||||
},
|
||||
|
||||
async doTranslate() {
|
||||
if (!this.data.sourceText.trim()) {
|
||||
wx.showToast({ title: '请输入要翻译的内容', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
const result = await translateApi.translate(
|
||||
this.data.sourceText,
|
||||
this.data.targetLang
|
||||
);
|
||||
this.setData({
|
||||
translatedText: result.translated_text,
|
||||
loading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err.message || '翻译失败', icon: 'none' });
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
async doGetReply() {
|
||||
if (!this.data.replyInquiry.trim()) {
|
||||
wx.showToast({ title: '请输入客户询盘内容', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
const result = await translateApi.getReply(this.data.replyInquiry);
|
||||
this.setData({
|
||||
replySuggestions: result.suggestions,
|
||||
loading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err.message || '生成回复失败', icon: 'none' });
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
copyText(e) {
|
||||
const text = e.currentTarget.dataset.text;
|
||||
wx.setClipboardData({
|
||||
data: text,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已复制', icon: 'success' });
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
clearInput() {
|
||||
this.setData({
|
||||
sourceText: '',
|
||||
translatedText: '',
|
||||
replyInquiry: '',
|
||||
replySuggestions: [],
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "智能翻译",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<view class="container">
|
||||
<view class="tabs">
|
||||
<view class="tab {{tab === 'translate' ? 'active' : ''}}" data-tab="translate" bindtap="switchTab">翻译</view>
|
||||
<view class="tab {{tab === 'reply' ? 'active' : ''}}" data-tab="reply" bindtap="switchTab">回复建议</view>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view wx:if="{{tab === 'translate'}}">
|
||||
<view class="input-area">
|
||||
<text class="input-label">输入要翻译的内容</text>
|
||||
<textarea class="textarea" placeholder="请输入中文或英文..." value="{{sourceText}}" bindinput="onSourceInput" auto-height />
|
||||
</view>
|
||||
|
||||
<view class="lang-select">
|
||||
<text>目标语言</text>
|
||||
<picker value="{{targetLang}}" range="{{langOptions}}" range-key="label" bindchange="onLangChange">
|
||||
<text class="picker">{{langOptions[langOptions.findIndex(function(item) { return item.value === targetLang })].label}}</text>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<button class="btn-primary btn-translate" bindtap="doTranslate" loading="{{loading}}">翻译</button>
|
||||
|
||||
<view class="result-area" wx:if="{{translatedText}}">
|
||||
<view class="result-header">
|
||||
<text class="input-label">翻译结果</text>
|
||||
<text class="copy-btn" bindtap="copyText" data-text="{{translatedText}}">复制</text>
|
||||
</view>
|
||||
<text class="result-text">{{translatedText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{tab === 'reply'}}">
|
||||
<view class="input-area">
|
||||
<text class="input-label">输入客户询盘内容</text>
|
||||
<textarea class="textarea" placeholder="粘贴客户的英文询盘..." value="{{replyInquiry}}" bindinput="onReplyInput" auto-height />
|
||||
</view>
|
||||
|
||||
<button class="btn-primary btn-translate" bindtap="doGetReply" loading="{{loading}}">生成回复建议</button>
|
||||
|
||||
<view class="suggestions" wx:if="{{replySuggestions.length > 0}}">
|
||||
<view class="section-title">回复建议</view>
|
||||
<view class="suggestion-item" wx:for="{{replySuggestions}}" wx:key="tone">
|
||||
<text class="suggestion-tone">{{item.tone}}</text>
|
||||
<text class="suggestion-content">{{item.reply}}</text>
|
||||
<view style="margin-top: 10rpx;">
|
||||
<text class="copy-btn" bindtap="copyText" data-text="{{item.reply}}">复制</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,114 @@
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
border-bottom: 4rpx solid transparent;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #1890ff;
|
||||
border-bottom-color: #1890ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
color: #1890ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-translate {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.result-area {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
color: #1890ff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.suggestion-tone {
|
||||
font-size: 24rpx;
|
||||
color: #1890ff;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
}
|
||||
Reference in New Issue
Block a user