399 lines
15 KiB
TypeScript
399 lines
15 KiB
TypeScript
import { View, Text, Image, Button, Checkbox, CheckboxGroup, RichText } from '@tarojs/components'
|
||
import Taro, { useDidShow } from '@tarojs/taro'
|
||
import { useState } from 'react'
|
||
import './index.scss'
|
||
|
||
export default function UserIndex() {
|
||
const [userInfo, setUserInfo] = useState<any>(null)
|
||
const [showLoginModal, setShowLoginModal] = useState(false)
|
||
const [isAgreed, setIsAgreed] = useState(false)
|
||
const [showAgreement, setShowAgreement] = useState(false) // For showing agreement content
|
||
|
||
useDidShow(() => {
|
||
const info = Taro.getStorageSync('userInfo')
|
||
if (info) setUserInfo(info)
|
||
})
|
||
|
||
const goOrders = () => Taro.navigateTo({ url: '/pages/order/list' })
|
||
const goDistributor = () => Taro.navigateTo({ url: '/subpackages/distributor/index' })
|
||
const goInvite = () => Taro.navigateTo({ url: '/subpackages/distributor/invite' })
|
||
const goWithdraw = () => Taro.navigateTo({ url: '/subpackages/distributor/withdraw' })
|
||
|
||
const handleAddress = async () => {
|
||
try {
|
||
const res = await Taro.chooseAddress()
|
||
// 同步地址信息到后端
|
||
const token = Taro.getStorageSync('token')
|
||
if (token) {
|
||
await Taro.request({
|
||
url: 'https://market.quant-speed.com/api/wechat/update/',
|
||
method: 'POST',
|
||
header: {
|
||
'Authorization': `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
data: {
|
||
province: res.provinceName,
|
||
city: res.cityName,
|
||
country: '中国' // 默认中国,chooseAddress通常返回国内地址
|
||
}
|
||
})
|
||
// 更新本地 userInfo
|
||
const updatedInfo = { ...userInfo, province: res.provinceName, city: res.cityName, country: '中国' }
|
||
setUserInfo(updatedInfo)
|
||
Taro.setStorageSync('userInfo', updatedInfo)
|
||
Taro.showToast({ title: '地址信息已同步', icon: 'success' })
|
||
}
|
||
} catch(e) {
|
||
// 用户取消或其他错误,忽略
|
||
}
|
||
}
|
||
|
||
const handleAvatarClick = async () => {
|
||
if (!userInfo) return
|
||
|
||
try {
|
||
const { tempFilePaths } = await Taro.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'] })
|
||
if (!tempFilePaths.length) return
|
||
|
||
Taro.showLoading({ title: '上传中...' })
|
||
|
||
const token = Taro.getStorageSync('token')
|
||
const uploadRes = await Taro.uploadFile({
|
||
url: 'https://market.quant-speed.com/api/upload/image/',
|
||
filePath: tempFilePaths[0],
|
||
name: 'file',
|
||
header: {
|
||
'Authorization': `Bearer ${token}`
|
||
}
|
||
})
|
||
|
||
if (uploadRes.statusCode !== 200) {
|
||
throw new Error('上传失败')
|
||
}
|
||
|
||
const data = JSON.parse(uploadRes.data)
|
||
const newAvatarUrl = data.url
|
||
|
||
// 更新后端用户信息
|
||
await Taro.request({
|
||
url: 'https://market.quant-speed.com/api/wechat/update/',
|
||
method: 'POST',
|
||
header: {
|
||
'Authorization': `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
data: {
|
||
avatar_url: newAvatarUrl
|
||
}
|
||
})
|
||
|
||
// 更新本地 userInfo
|
||
const updatedInfo = { ...userInfo, avatar_url: newAvatarUrl }
|
||
setUserInfo(updatedInfo)
|
||
Taro.setStorageSync('userInfo', updatedInfo)
|
||
|
||
Taro.hideLoading()
|
||
Taro.showToast({ title: '头像更新成功', icon: 'success' })
|
||
|
||
} catch (e) {
|
||
Taro.hideLoading()
|
||
Taro.showToast({ title: '头像更新失败', icon: 'none' })
|
||
console.error(e)
|
||
}
|
||
}
|
||
|
||
const handleNicknameClick = () => {
|
||
if (!userInfo) return
|
||
|
||
Taro.showModal({
|
||
title: '修改昵称',
|
||
content: userInfo.nickname || '',
|
||
// @ts-ignore
|
||
editable: true,
|
||
placeholderText: '请输入新昵称',
|
||
success: async function (res) {
|
||
if (res.confirm && (res as any).content) {
|
||
const newNickname = (res as any).content
|
||
if (newNickname === userInfo.nickname) return
|
||
|
||
try {
|
||
Taro.showLoading({ title: '更新中...' })
|
||
const token = Taro.getStorageSync('token')
|
||
|
||
await Taro.request({
|
||
url: 'https://market.quant-speed.com/api/wechat/update/',
|
||
method: 'POST',
|
||
header: {
|
||
'Authorization': `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
data: {
|
||
nickname: newNickname
|
||
}
|
||
})
|
||
|
||
// 更新本地 userInfo
|
||
const updatedInfo = { ...userInfo, nickname: newNickname }
|
||
setUserInfo(updatedInfo)
|
||
Taro.setStorageSync('userInfo', updatedInfo)
|
||
|
||
Taro.hideLoading()
|
||
Taro.showToast({ title: '昵称已更新', icon: 'success' })
|
||
} catch (e) {
|
||
Taro.hideLoading()
|
||
Taro.showToast({ title: '更新失败', icon: 'none' })
|
||
console.error(e)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleLogout = () => {
|
||
Taro.showModal({
|
||
title: '提示',
|
||
content: '确定要退出登录吗?',
|
||
success: function (res) {
|
||
if (res.confirm) {
|
||
Taro.removeStorageSync('token')
|
||
Taro.removeStorageSync('userInfo')
|
||
setUserInfo(null)
|
||
Taro.showToast({ title: '已退出登录', icon: 'success' })
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
|
||
const login = async () => {
|
||
try {
|
||
// 1. 获取微信登录 Code
|
||
const { code } = await Taro.login()
|
||
if (!code) throw new Error('登录失败:无法获取 Code')
|
||
|
||
// 2. 调用后端登录 (仅 Code)
|
||
const res = await Taro.request({
|
||
url: 'https://market.quant-speed.com/api/wechat/login/',
|
||
method: 'POST',
|
||
data: { code }
|
||
})
|
||
|
||
console.log('code:', code)
|
||
|
||
if (res.statusCode === 200 && res.data.token) {
|
||
Taro.setStorageSync('token', res.data.token)
|
||
Taro.setStorageSync('userInfo', res.data)
|
||
setUserInfo(res.data)
|
||
Taro.showToast({ title: '登录成功', icon: 'success' })
|
||
} else {
|
||
throw new Error(res.data.error || '登录请求失败')
|
||
}
|
||
} catch (e) {
|
||
Taro.showToast({ title: e.message || '登录失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const getPhoneNumber = async (e) => {
|
||
const { code: phoneCode, errMsg } = e.detail
|
||
if (errMsg !== "getPhoneNumber:ok") {
|
||
Taro.showToast({ title: '获取手机号失败', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
try {
|
||
Taro.showLoading({ title: '登录中...' })
|
||
// 1. 获取登录 Code
|
||
const { code: loginCode } = await Taro.login()
|
||
|
||
// 2. 调用后端登录 (Code + PhoneCode)
|
||
console.log('loginCode:', loginCode)
|
||
console.log('phoneCode:', phoneCode)
|
||
const res = await Taro.request({
|
||
url: 'https://market.quant-speed.com/api/wechat/login/',
|
||
method: 'POST',
|
||
data: {
|
||
code: loginCode,
|
||
phone_code: phoneCode
|
||
}
|
||
})
|
||
|
||
Taro.hideLoading()
|
||
|
||
if (res.statusCode === 200 && res.data.token) {
|
||
Taro.setStorageSync('token', res.data.token)
|
||
Taro.setStorageSync('userInfo', res.data)
|
||
setUserInfo(res.data)
|
||
setShowLoginModal(false) // Close modal on success
|
||
Taro.showToast({ title: '授权登录成功', icon: 'success' })
|
||
} else {
|
||
throw new Error(res.data.error || '登录失败')
|
||
}
|
||
} catch(err) {
|
||
Taro.hideLoading()
|
||
Taro.showToast({ title: err.message || '系统异常', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const serviceGroups = [
|
||
{
|
||
title: '基础服务',
|
||
items: [
|
||
{ title: '我的订单', icon: '📦', action: goOrders },
|
||
{ title: '地址管理', icon: '📍', action: handleAddress },
|
||
{ title: '新增地址', icon: '📝', action: handleAddress },
|
||
]
|
||
},
|
||
{
|
||
title: '分销中心',
|
||
items: [
|
||
{ title: '分销首页', icon: '⚡', action: goDistributor },
|
||
{ title: '推广邀请', icon: '🤝', action: goInvite },
|
||
{ title: '佣金提现', icon: '💰', action: goWithdraw },
|
||
]
|
||
},
|
||
{
|
||
title: '其他',
|
||
items: [
|
||
{ title: '联系客服', icon: '🎧', isContact: true },
|
||
...(userInfo ? [{ title: '退出登录', icon: '🚪', action: handleLogout }] : [])
|
||
]
|
||
}
|
||
]
|
||
|
||
const stats = [
|
||
{ label: '余额', value: '0.00' },
|
||
{ label: '积分', value: '0' },
|
||
{ label: '优惠券', value: '0' }
|
||
]
|
||
|
||
const handleAgreementCheck = (e) => {
|
||
setIsAgreed(!!e.detail.value.length)
|
||
}
|
||
|
||
const handleShowAgreement = (e) => {
|
||
e.stopPropagation()
|
||
setShowAgreement(true)
|
||
}
|
||
|
||
const handleLoginBtnClick = () => {
|
||
if (!isAgreed) {
|
||
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' })
|
||
return
|
||
}
|
||
// If agreed, the button openType='getPhoneNumber' handles it.
|
||
}
|
||
|
||
return (
|
||
<View className='page-container'>
|
||
{/* Profile Card */}
|
||
<View className='profile-card'>
|
||
<View className='avatar-container' onClick={handleAvatarClick}>
|
||
<Image src={userInfo?.avatar_url || 'https://via.placeholder.com/150/00b96b/FFFFFF?text=USER'} className='avatar' />
|
||
{userInfo && <View className='online-dot' />}
|
||
</View>
|
||
<View className='info-col'>
|
||
<Text className='nickname' onClick={handleNicknameClick}>{userInfo?.nickname || '未登录用户'}</Text>
|
||
<Text className='uid'>ID: {userInfo ? (userInfo.phone_number || userInfo.id || '----') : '----'}</Text>
|
||
{!userInfo && (
|
||
<View className='login-btns'>
|
||
<Button
|
||
className='btn-login primary'
|
||
onClick={() => setShowLoginModal(true)}
|
||
>
|
||
立即登录
|
||
</Button>
|
||
</View>
|
||
)}
|
||
</View>
|
||
<View className='card-bg-effect' />
|
||
</View>
|
||
|
||
{/* Stats Row */}
|
||
<View className='stats-row'>
|
||
{stats.map((item, idx) => (
|
||
<View key={idx} className='stat-item'>
|
||
<Text className='stat-val'>{item.value}</Text>
|
||
<Text className='stat-lbl'>{item.label}</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
|
||
{/* Service Groups */}
|
||
<View className='service-container'>
|
||
{serviceGroups.map((group, gIdx) => (
|
||
<View key={gIdx} className='service-group'>
|
||
<Text className='group-title'>{group.title}</Text>
|
||
<View className='grid-layout'>
|
||
{group.items.map((item, idx) => (
|
||
<View key={idx} className='grid-item' onClick={item.action}>
|
||
<View className='icon-box'>
|
||
<Text className='icon'>{item.icon}</Text>
|
||
</View>
|
||
<Text className='item-title'>{item.title}</Text>
|
||
{item.isContact && <Button openType='contact' className='contact-overlay' />}
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
))}
|
||
</View>
|
||
|
||
<View className='version-info'>
|
||
<Text>Quant Speed Market v1.0.0</Text>
|
||
<Text>Powered by Taro & React</Text>
|
||
</View>
|
||
|
||
{/* Login Modal */}
|
||
{showLoginModal && (
|
||
<View className='login-modal-mask' onClick={() => setShowLoginModal(false)}>
|
||
<View className='login-modal-content' onClick={e => e.stopPropagation()}>
|
||
<View className='modal-header'>
|
||
<Text className='modal-title'>欢迎登录 Quant Speed</Text>
|
||
<Text className='modal-subtitle'>登录后享受更多权益</Text>
|
||
</View>
|
||
|
||
<View className='modal-body'>
|
||
<Button
|
||
className={`btn-modal-login ${isAgreed ? 'primary' : 'disabled'}`}
|
||
openType={isAgreed ? 'getPhoneNumber' : undefined}
|
||
onGetPhoneNumber={getPhoneNumber}
|
||
onClick={handleLoginBtnClick}
|
||
>
|
||
微信一键登录
|
||
</Button>
|
||
|
||
<View className='agreement-box'>
|
||
<CheckboxGroup onChange={handleAgreementCheck}>
|
||
<Checkbox value='agree' checked={isAgreed} color='#00b96b' className='agreement-checkbox' />
|
||
</CheckboxGroup>
|
||
<Text className='agreement-text'>
|
||
我已阅读并同意 <Text className='link' onClick={handleShowAgreement}>《用户协议》</Text> 与 <Text className='link' onClick={handleShowAgreement}>《隐私政策》</Text>
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* Agreement Detail Modal */}
|
||
{showAgreement && (
|
||
<View className='agreement-modal-mask'>
|
||
<View className='agreement-content'>
|
||
<Text className='agreement-title'>用户协议与隐私政策</Text>
|
||
<View className='agreement-scroll'>
|
||
<View className='p'><Text>1. 特别提示</Text></View>
|
||
<View className='p'><Text>在此特别提醒您(用户)在注册成为用户之前,请认真阅读本《用户协议》(以下简称“协议”),确保您充分理解本协议中各条款。请您审慎阅读并选择接受或不接受本协议。除非您接受本协议所有条款,否则您无权注册、登录或使用本协议所涉服务。您的注册、登录、使用等行为将视为对本协议的接受,并同意接受本协议各项条款的约束。</Text></View>
|
||
<View className='p'><Text>2. 账号注册</Text></View>
|
||
<View className='p'><Text>2.1 鉴于“Quant Speed”账号的绑定注册方式,您同意在注册时将您的手机号码及微信账号信息提供给“Quant Speed”用于注册。</Text></View>
|
||
<View className='p'><Text>3. 隐私保护</Text></View>
|
||
<View className='p'><Text>3.1 本小程序将严格保护您的个人信息安全。我们使用各种安全技术和程序来保护您的个人信息不被未经授权的访问、使用或泄漏。</Text></View>
|
||
</View>
|
||
<Button className='btn-close' onClick={() => setShowAgreement(false)}>我知道了</Button>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|