Files
market_page/miniprogram/src/pages/user/index.tsx
jeremygan2021 1f1516ae20 forum
2026-02-12 19:40:59 +08:00

399 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 SpeedQuant 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>
)
}