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,287 @@
|
||||
<template>
|
||||
<view class="login-container">
|
||||
<view class="logo-section">
|
||||
<text class="logo">TradeMate</text>
|
||||
<text class="subtitle">外贸小助手</text>
|
||||
</view>
|
||||
|
||||
<view class="form-section">
|
||||
<text class="form-title">{{ isRegister ? '注册' : '登录' }}</text>
|
||||
|
||||
<view class="input-group">
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="手机号"
|
||||
v-model="phone"
|
||||
@input="onPhoneInput"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-group" v-if="isRegister">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="用户名"
|
||||
v-model="username"
|
||||
@input="onUsernameInput"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-group">
|
||||
<input
|
||||
class="input"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
v-model="password"
|
||||
@input="onPasswordInput"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="error" v-if="error">{{ error }}</view>
|
||||
|
||||
<button
|
||||
class="submit-btn"
|
||||
@click="handleSubmit"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ loading ? '处理中...' : (isRegister ? '注册' : '登录') }}
|
||||
</button>
|
||||
|
||||
<text class="toggle-mode" @click="toggleMode">
|
||||
{{ isRegister ? '已有账号?立即登录' : '没有账号?立即注册' }}
|
||||
</text>
|
||||
|
||||
<view class="divider">
|
||||
<view class="line"></view>
|
||||
<text class="text">或</text>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
|
||||
<button class="wechat-btn" @click="handleWechatLogin">
|
||||
<text class="wechat-icon">W</text>
|
||||
微信一键登录
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<text class="agreement">登录即表示同意</text>
|
||||
<text class="link">《用户协议》</text>
|
||||
<text class="agreement">和</text>
|
||||
<text class="link">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { authApi } from '@/utils/api.js'
|
||||
|
||||
const phone = ref('')
|
||||
const password = ref('')
|
||||
const username = ref('')
|
||||
const isRegister = ref(false)
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
const toggleMode = () => {
|
||||
isRegister.value = !isRegister.value
|
||||
error.value = ''
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!phone.value || !password.value) {
|
||||
error.value = '请输入手机号和密码'
|
||||
return
|
||||
}
|
||||
|
||||
if (isRegister.value && !username.value) {
|
||||
error.value = '请输入用户名'
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
if (isRegister.value) {
|
||||
await authApi.register(phone.value, password.value, username.value)
|
||||
uni.showToast({ title: '注册成功,请登录', icon: 'success' })
|
||||
isRegister.value = false
|
||||
} else {
|
||||
const res = await authApi.login(phone.value, password.value)
|
||||
uni.setStorageSync('token', res.access_token)
|
||||
uni.setStorageSync('userInfo', res.user)
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}, 1000)
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message || '操作失败,请重试'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleWechatLogin = () => {
|
||||
uni.getUserProfile({
|
||||
desc: '用于完善用户资料',
|
||||
success: (res) => {
|
||||
console.log('微信登录', res.userInfo)
|
||||
uni.showToast({ title: '微信登录开发中', icon: 'none' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('微信登录失败', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onPhoneInput = (e) => { phone.value = e.detail.value }
|
||||
const onPasswordInput = (e) => { password.value = e.detail.value }
|
||||
const onUsernameInput = (e) => { username.value = e.detail.value }
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #1890ff 0%, #e6f7ff 100%);
|
||||
padding: 120rpx 60rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
text-align: center;
|
||||
margin-bottom: 80rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 48rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff4d4f;
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.submit-btn[disabled] {
|
||||
background: #a0cfff;
|
||||
}
|
||||
|
||||
.toggle-mode {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 32rpx;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 48rpx 0;
|
||||
}
|
||||
|
||||
.divider .line {
|
||||
flex: 1;
|
||||
height: 1rpx;
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.divider .text {
|
||||
padding: 0 24rpx;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.wechat-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wechat-icon {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 60rpx;
|
||||
}
|
||||
|
||||
.agreement {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #1890ff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user