添加登录和后端校验

完成后端设计(未在本仓库体现),通过安全的手段完成了登录鉴权
This commit is contained in:
2026-08-06 16:27:36 +08:00
commit b19a56003f
286 changed files with 51650 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
export default definePageConfig({
navigationStyle: 'custom',
navigationBarTitleText: '设置'
})
+144
View File
@@ -0,0 +1,144 @@
.settings-page {
padding: 0 24px 24px;
}
.settings-area {
padding-top: calc(env(safe-area-inset-top, 0px));
}
.settings-user {
padding: 24px;
}
.settings-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
border: 3px dashed var(--line-star);
}
.settings-avatar-placeholder {
width: 100px;
height: 100px;
border-radius: 50%;
background: var(--bg-input);
border: 3px dashed var(--line-star);
display: flex;
align-items: center;
justify-content: center;
padding: 30px;
box-sizing: border-box;
}
.settings-name {
font-size: 32px;
font-weight: 700;
color: var(--text-primary);
display: block;
margin-bottom: 8px;
}
.settings-id {
font-size: 24px;
color: var(--text-secondary);
}
.arrow {
font-size: 36px;
color: var(--text-secondary);
}
.settings-list {
padding: 0 24px;
}
.settings-item {
display: flex;
align-items: center;
padding: 28px 0;
}
.settings-item.bordered {
border-bottom: 1px dashed rgba(0,0,0,0.06);
}
.settings-item-icon-img {
width: 32px;
height: 32px;
margin-right: 16px;
flex-shrink: 0;
}
.settings-item-label {
flex: 1;
font-size: 30px;
color: var(--text-primary);
}
.logout-btn {
text-align: center;
padding: 24px 0;
color: #ff6b6b;
border: 3px dashed #ff6b6b;
}
/* --- 主题选择 --- */
.settings-section {
padding: 32px;
}
.section-label {
font-size: 30px;
font-weight: 600;
color: var(--text-primary);
display: block;
margin-bottom: 24px;
}
.theme-options {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.theme-option {
flex: 1;
min-width: 160px;
padding: 24px 16px;
border-radius: 20px;
background: var(--bg-input);
border: 3px dashed transparent;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
transition: all 0.2s ease;
}
.theme-option.active {
border-color: var(--accent-pink);
background: rgba(255, 154, 158, 0.08);
}
.theme-option-text {
font-size: 28px;
color: var(--text-primary);
font-weight: 500;
}
.theme-check {
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--accent-pink);
}
.theme-hint {
font-size: 24px;
color: var(--text-secondary);
margin-top: 20px;
display: block;
text-align: center;
}
/* 弹窗复用 address 弹窗和 profile 弹窗样式 */
+175
View File
@@ -0,0 +1,175 @@
import { View, Text, Image, Input, Button } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useState, useEffect, useCallback } from 'react'
import './index.scss'
import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import { getUserInfo, setUserInfo, clearUserInfo, getAddressList, getDefaultAddress, type AddressItem } from '../../utils/store'
const THEME_OPTIONS: { label: string; value: 'light' | 'dark' | 'auto' }[] = [
{ label: '浅色模式', value: 'light' },
{ label: '深色模式', value: 'dark' },
{ label: '跟随系统', value: 'auto' }
]
const MENU = [
{ label: '个人信息', icon: '/icon/个人.png', action: (s: any) => s.setShowEdit(true) },
{ label: '账号管理', icon: '/icon/账号管理.png', action: () => Taro.navigateTo({ url: '/pages/userDatabase/index' }) },
{ label: '定制协议', icon: '/icon/定制协议.png', action: () => Taro.navigateTo({ url: '/pages/agreement/index' }) },
{ label: '关于我们', icon: '/icon/关于我们.png', action: () => Taro.showToast({ title: '智绘微刻 v1.0', icon: 'none' }) }
]
export default function SettingsPage() {
const { theme, setTheme, resolvedTheme } = useThemeContext()
const safe = useSafeArea()
useStatusBar(resolvedTheme)
const [user, setUser] = useState<any>({})
const [editName, setEditName] = useState('')
const [editAvatar, setEditAvatar] = useState('')
const [showEdit, setShowEdit] = useState(false)
const loadUser = () => {
const u = getUserInfo() || {}
setUser(u)
setEditName(u.nickName || '')
setEditAvatar(u.avatarUrl || '')
}
useEffect(() => {
loadUser()
}, [])
const handleSaveInfo = () => {
setUserInfo({ ...user, nickName: editName, avatarUrl: editAvatar })
Taro.showToast({ title: '修改成功', icon: 'success' })
setShowEdit(false)
loadUser()
}
const handleLogout = () => {
Taro.showModal({
title: '确认退出',
content: '退出后将清空当前登录状态,确定吗?',
success: (res) => {
if (res.confirm) {
clearUserInfo()
Taro.switchTab({ url: '/pages/index/index' })
}
}
})
}
const onChooseAvatar = (e: any) => {
setEditAvatar(e.detail.avatarUrl || '')
}
return (
<View className={`theme-${resolvedTheme}`} style={{ minHeight: '100vh' }}>
<View className='settings-page'>
<View className='settings-area'>
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='flex-between' style={{ width: '100%' }}>
<Text className='back-btn' onTap={() => Taro.navigateBack()}></Text>
<Text className='page-title'></Text>
<View style={{ width: '60px' }} />
</View>
</View>
{/* 用户卡片 */}
<View className='settings-user dashed-card mt-20'>
<View className='flex-between' onTap={() => setShowEdit(true)}>
<View className='flex-center'>
{user.avatarUrl ? (
<Image className='settings-avatar' src={user.avatarUrl} mode='aspectFill' />
) : (
<View className='settings-avatar-placeholder'>
<Image className='settings-avatar-icon' src='/icon/个人.png' mode='aspectFit' />
</View>
)}
<View style={{ marginLeft: '20px' }}>
<Text className='settings-name'>{user.nickName || '未登录'}</Text>
<Text className='settings-id'>ID: {user.openid ? user.openid.slice(-8) : '---'}</Text>
</View>
</View>
<Text className='arrow'></Text>
</View>
</View>
{/* 主题设置 */}
<View className='settings-section dashed-card mt-20'>
<Text className='section-label'></Text>
<View className='theme-options'>
{THEME_OPTIONS.map(opt => (
<View
key={opt.value}
className={`theme-option ${theme === opt.value ? 'active' : ''}`}
onTap={() => setTheme(opt.value)}
>
<Text className='theme-option-text'>{opt.label}</Text>
{theme === opt.value && <View className='theme-check' />}
</View>
))}
</View>
{theme === 'auto' && (
<Text className='theme-hint'>{resolvedTheme === 'dark' ? '深色模式' : '浅色模式'}</Text>
)}
</View>
{/* 菜单列表 */}
<View className='settings-list dashed-card mt-20'>
{MENU.map((item, idx) => (
<View key={idx} className={`settings-item ${idx < MENU.length - 1 ? 'bordered' : ''}`} onTap={() => item.action({ setShowEdit })}>
<Image className='settings-item-icon-img' src={item.icon} mode='aspectFit' />
<Text className='settings-item-label'>{item.label}</Text>
<Text className='arrow'></Text>
</View>
))}
</View>
{/* 退出登录 */}
{user?.openid && (
<View className='mt-20'>
<View className='btn-outline logout-btn' onTap={handleLogout}>
<Text>退</Text>
</View>
</View>
)}
<View style={{ height: '40px' }} />
{/* 编辑弹窗 */}
{showEdit && (
<View className='modal-overlay'>
<View className='modal-card dashed-card'>
<Text className='modal-title'></Text>
<Button className='avatar-btn' openType='chooseAvatar' onChooseAvatar={onChooseAvatar}>
{editAvatar ? (
<Image className='avatar-img' src={editAvatar} mode='aspectFill' />
) : (
<Image className='avatar-placeholder-img' src='/icon/个人.png' mode='aspectFit' />
)}
</Button>
<Input
className='nickname-input'
placeholder='请输入昵称'
value={editName}
onInput={(e: any) => setEditName(e.detail.value)}
/>
<View className='form-actions'>
<View className='btn-outline' onTap={() => setShowEdit(false)}>
<Text></Text>
</View>
<View className='btn-gradient' onTap={handleSaveInfo}>
<Text></Text>
</View>
</View>
</View>
</View>
)}
</View>
</View>
</View>
)
}