feat: silent wechat login, marketing tab optimization, admin page foundation

- Add silent WeChat login for MP/browser environments
- Fix Python 3.6 compatibility (remove typing.Annotated usage)
- Marketing page: tab-based content generation with category support
- Translate page: add auto-detect language default
- Homepage: add TTS playback, announcement ticker, remove redundant quick-actions
- Fix FAB button overlap with custom tabbar on customers/quotation pages
- Make openai/anthropic imports lazy for Python 3.6 compat
This commit is contained in:
TradeMate Dev
2026-05-14 00:30:48 +08:00
parent f70dd24c7d
commit 23a31f7c00
30 changed files with 485 additions and 269 deletions
+87 -13
View File
@@ -1,5 +1,9 @@
<template>
<view class="login-container">
<view class="silent-loading" v-if="silentLoading">
<text class="silent-loading-text">正在自动登录...</text>
</view>
<view class="welcome-section">
<text class="logo">TradeMate</text>
<text class="subtitle">外贸小助手</text>
@@ -110,7 +114,7 @@
</template>
<script setup>
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { authApi } from '@/utils/api.js'
const phone = ref('')
@@ -118,13 +122,73 @@ const password = ref('')
const username = ref('')
const isRegister = ref(false)
const loading = ref(false)
const silentLoading = ref(true)
const error = ref('')
const showForm = ref(false)
const isWechatAvailable = ref(false)
// #ifdef MP-WEIXIN
isWechatAvailable.value = true
// #endif
const doWechatLogin = async (code) => {
const res = await authApi.wechatLogin(code)
uni.setStorageSync('token', res.access_token)
uni.setStorageSync('userInfo', res.user)
uni.setStorageSync('hasLogin', true)
uni.setStorageSync('isGuest', false)
uni.switchTab({ url: '/pages/index/index' })
}
onMounted(async () => {
// #ifdef MP-WEIXIN
// 微信小程序:静默登录
isWechatAvailable.value = true
try {
const loginRes = await new Promise((resolve, reject) => {
uni.login({ provider: 'weixin', success: resolve, fail: reject })
})
await doWechatLogin(loginRes.code)
} catch (_) {
silentLoading.value = false
}
// #endif
// #ifdef H5
// H5 微信内置浏览器:OAuth 静默登录
const isWechatBrowser = /MicroMessenger/i.test(navigator.userAgent)
if (isWechatBrowser) {
try {
const cfg = await authApi.wechatConfig()
if (cfg.available && cfg.app_id) {
const params = new URLSearchParams(window.location.search)
const code = params.get('code')
if (code) {
// OAuth 回调,携带 code
await doWechatLogin(code)
// 清除 URL 中的 code 参数
window.history.replaceState({}, '', window.location.pathname)
return
} else {
// 跳转微信 OAuth 授权
const redirectUri = encodeURIComponent(window.location.href.split('?')[0])
window.location.href =
`https://open.weixin.qq.com/connect/oauth2/authorize` +
`?appid=${cfg.app_id}` +
`&redirect_uri=${redirectUri}` +
`&response_type=code` +
`&scope=snsapi_base` +
`&state=STATE#wechat_redirect`
return
}
}
} catch (_) {}
}
silentLoading.value = false
// #endif
// #ifndef MP-WEIXIN
if (!/MicroMessenger/i.test(navigator.userAgent)) {
silentLoading.value = false
}
// #endif
})
const toggleMode = () => {
isRegister.value = !isRegister.value
@@ -178,15 +242,7 @@ const handleWechatLogin = () => {
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)
await doWechatLogin(loginRes.code)
} catch (err) {
error.value = err.message || '微信登录失败'
} finally {
@@ -438,6 +494,24 @@ const goToQuickTry = async () => {
margin-right: 12rpx;
}
.silent-loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.silent-loading-text {
font-size: 28rpx;
color: #999;
}
.footer {
text-align: center;
margin-top: 40rpx;