feat: 修复 H5 底部导航覆盖 + 更新项目进度文档
## H5 底部导航修复 (Bug #10) - 精简 App.vue,移除重复 tabbar,仅保留全局样式 - uni-page 设置 height: calc(100% - 50px) + overflow-y: auto - 内容区域精确停在底部导航上方,独立滚动不再叠加 - 恢复 custom-tab-bar 组件 ## 项目进度文档 - PROGRESS.md 更新至 10 个 Bug 修复 - 新增 H5 底部导航修复记录 - 新增历史变更条目
This commit is contained in:
+274
-110
@@ -1,74 +1,110 @@
|
||||
<template>
|
||||
<view class="login-container">
|
||||
<view class="logo-section">
|
||||
<view class="welcome-section">
|
||||
<text class="logo">TradeMate</text>
|
||||
<text class="subtitle">外贸小助手</text>
|
||||
<text class="slogan">让外贸更简单 · 让沟通更高效</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 class="features-section">
|
||||
<view class="feature-card">
|
||||
<text class="feature-icon">🌐</text>
|
||||
<text class="feature-title">智能翻译</text>
|
||||
<text class="feature-desc">支持中英双语商务翻译,精准理解外贸术语</text>
|
||||
</view>
|
||||
|
||||
<view class="input-group" v-if="isRegister">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="用户名"
|
||||
v-model="username"
|
||||
@input="onUsernameInput"
|
||||
/>
|
||||
<view class="feature-card">
|
||||
<text class="feature-icon">💬</text>
|
||||
<text class="feature-title">智能回复</text>
|
||||
<text class="feature-desc">AI 生成专业商务回复,多种风格可选</text>
|
||||
</view>
|
||||
|
||||
<view class="input-group">
|
||||
<input
|
||||
class="input"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
v-model="password"
|
||||
@input="onPasswordInput"
|
||||
/>
|
||||
<view class="feature-card">
|
||||
<text class="feature-icon">📊</text>
|
||||
<text class="feature-title">客户管理</text>
|
||||
<text class="feature-desc">客户信息、跟进提醒、数据分析一体化</text>
|
||||
</view>
|
||||
<view class="feature-card">
|
||||
<text class="feature-icon">💰</text>
|
||||
<text class="feature-title">报价单生成</text>
|
||||
<text class="feature-desc">快速生成专业报价单,支持导出 PDF</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="error" v-if="error">{{ error }}</view>
|
||||
|
||||
<button
|
||||
class="submit-btn"
|
||||
@click="handleSubmit"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ loading ? '处理中...' : (isRegister ? '注册' : '登录') }}
|
||||
<view class="actions-section">
|
||||
<button class="try-btn" @click="goToQuickTry">
|
||||
<text class="try-icon">🎯</text>
|
||||
<text class="try-text">快速体验</text>
|
||||
</button>
|
||||
|
||||
<text class="toggle-mode" @click="toggleMode">
|
||||
{{ isRegister ? '已有账号?立即登录' : '没有账号?立即注册' }}
|
||||
</text>
|
||||
|
||||
<view class="divider">
|
||||
<view class="line"></view>
|
||||
<text class="text">或</text>
|
||||
<text class="text">已有账号?登录继续</text>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
|
||||
<button class="wechat-btn" @click="handleWechatLogin">
|
||||
<text class="wechat-icon">W</text>
|
||||
微信一键登录
|
||||
<view class="form-section" v-if="showForm">
|
||||
<view class="input-group">
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="手机号"
|
||||
v-model="phone"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-group" v-if="isRegister">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="用户名"
|
||||
v-model="username"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-group">
|
||||
<input
|
||||
class="input"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
v-model="password"
|
||||
/>
|
||||
</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" v-if="false">
|
||||
<view class="line"></view>
|
||||
<text class="text">或</text>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
|
||||
<button class="wechat-btn" @click="handleWechatLogin" v-if="false">
|
||||
<text class="wechat-icon">W</text>
|
||||
微信一键登录
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<button class="show-login-btn" @click="toggleShowForm" v-if="!showForm">
|
||||
登录 / 注册
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<text class="agreement">登录即表示同意</text>
|
||||
<text class="link">《用户协议》</text>
|
||||
<text class="link" @click="goToAgreement('terms')">《用户协议》</text>
|
||||
<text class="agreement">和</text>
|
||||
<text class="link">《隐私政策》</text>
|
||||
<text class="link" @click="goToAgreement('privacy')">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -83,12 +119,17 @@ const username = ref('')
|
||||
const isRegister = ref(false)
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const showForm = ref(false)
|
||||
|
||||
const toggleMode = () => {
|
||||
isRegister.value = !isRegister.value
|
||||
error.value = ''
|
||||
}
|
||||
|
||||
const toggleShowForm = () => {
|
||||
showForm.value = !showForm.value
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!phone.value || !password.value) {
|
||||
error.value = '请输入手机号和密码'
|
||||
@@ -108,15 +149,17 @@ const handleSubmit = async () => {
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
const res = await authApi.login(phone.value, password.value)
|
||||
uni.setStorageSync('token', res.access_token)
|
||||
uni.setStorageSync('userInfo', res.user)
|
||||
uni.setStorageSync('hasLogin', true)
|
||||
uni.setStorageSync('isGuest', false)
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}, 1000)
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message || '操作失败,请重试'
|
||||
} finally {
|
||||
@@ -125,83 +168,220 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
const handleWechatLogin = () => {
|
||||
uni.getUserProfile({
|
||||
desc: '用于完善用户资料',
|
||||
success: (res) => {
|
||||
console.log('微信登录', res.userInfo)
|
||||
uni.showToast({ title: '微信登录开发中', icon: 'none' })
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: async (loginRes) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await authApi.wechatLogin(loginRes.code)
|
||||
uni.setStorageSync('token', res.access_token)
|
||||
uni.setStorageSync('userInfo', res.user)
|
||||
uni.setStorageSync('hasLogin', true)
|
||||
uni.setStorageSync('isGuest', false)
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}, 1000)
|
||||
} catch (err) {
|
||||
error.value = err.message || '微信登录失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('微信登录失败', err)
|
||||
error.value = '微信登录取消或失败'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onPhoneInput = (e) => { phone.value = e.detail.value }
|
||||
const onPasswordInput = (e) => { password.value = e.detail.value }
|
||||
const onUsernameInput = (e) => { username.value = e.detail.value }
|
||||
const goToAgreement = (type) => {
|
||||
uni.navigateTo({ url: `/pages/agreement/${type}` })
|
||||
}
|
||||
|
||||
const goToQuickTry = async () => {
|
||||
uni.setStorageSync('isGuest', true)
|
||||
try {
|
||||
const res = await authApi.guestLogin()
|
||||
if (res.access_token) {
|
||||
uni.setStorageSync('token', res.access_token)
|
||||
if (res.user) {
|
||||
uni.setStorageSync('userInfo', res.user)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Guest login failed, continuing without token:', e)
|
||||
}
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #1890ff 0%, #e6f7ff 100%);
|
||||
padding: 120rpx 60rpx 60rpx;
|
||||
background: linear-gradient(180deg, #f0f7ff 0%, #ffffff 50%);
|
||||
padding: 40rpx 40rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
.welcome-section {
|
||||
text-align: center;
|
||||
margin-bottom: 80rpx;
|
||||
margin-bottom: 40rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 60rpx;
|
||||
font-size: 56rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
color: #1890ff;
|
||||
letter-spacing: 4rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: #666;
|
||||
margin-top: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slogan {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.features-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx 24rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 20rpx rgba(24, 144, 255, 0.08);
|
||||
border: 2rpx solid #e6f7ff;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 48rpx;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.actions-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.try-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, #1890ff 0%, #69c0ff 100%);
|
||||
color: #fff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.3);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.try-icon {
|
||||
font-size: 36rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.try-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 24rpx 0;
|
||||
}
|
||||
|
||||
.divider .line {
|
||||
flex: 1;
|
||||
height: 1rpx;
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.divider .text {
|
||||
padding: 0 24rpx;
|
||||
color: #bbb;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 40rpx 32rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 48rpx;
|
||||
display: block;
|
||||
.show-login-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #fff;
|
||||
color: #1890ff;
|
||||
border: 2rpx solid #1890ff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 32rpx;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: #f5f5f5;
|
||||
background: #fafafa;
|
||||
border-radius: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
padding: 0 28rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
border: 2rpx solid transparent;
|
||||
|
||||
&:focus {
|
||||
border-color: #1890ff;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff4d4f;
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
@@ -227,29 +407,11 @@ const onUsernameInput = (e) => { username.value = e.detail.value }
|
||||
.toggle-mode {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 32rpx;
|
||||
color: #666;
|
||||
margin-top: 28rpx;
|
||||
color: #1890ff;
|
||||
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;
|
||||
@@ -262,26 +424,28 @@ const onUsernameInput = (e) => { username.value = e.detail.value }
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.wechat-icon {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-right: 16rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 60rpx;
|
||||
margin-top: 40rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.agreement {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #1890ff;
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user