创建uniapp基础形态

This commit is contained in:
2026-07-15 17:39:10 +08:00
parent 743c5cb4eb
commit 11e0763766
22 changed files with 5311 additions and 43 deletions
+342
View File
@@ -0,0 +1,342 @@
<template>
<view class="page">
<view class="header">
<view class="header-row">
<view class="user-info">
<text class="greeting">{{ greeting }}</text>
<text class="username">{{ user?.nickname || '云泽用户' }}</text>
</view>
<view class="avatar">
<text class="avatar-text">{{ avatarText }}</text>
</view>
</view>
<view class="search-bar">
<FaIcon name="magnifying-glass" color="#9acafc" :size="18" />
<text class="search-text">搜索功能数据...</text>
</view>
</view>
<view class="main-scroll">
<view class="stats-grid">
<view class="stat-card" v-for="item in stats" :key="item.label">
<text class="stat-value">{{ item.value }}</text>
<text class="stat-label">{{ item.label }}</text>
<text class="stat-trend" :class="item.up ? 'up' : 'down'">
{{ item.up ? '↑' : '↓' }} {{ item.trend }}
</text>
</view>
</view>
<view class="section">
<text class="section-title">快捷入口</text>
<view class="quick-grid">
<view
class="quick-item"
v-for="item in quickActions"
:key="item.name"
@tap="onQuickTap(item)"
>
<view class="quick-icon">
<FaIcon :name="item.icon" color="#3c9cff" :size="22" />
</view>
<text class="quick-name">{{ item.name }}</text>
</view>
</view>
</view>
<view class="section">
<view class="section-header">
<text class="section-title">最近动态</text>
<text class="section-more">查看全部</text>
</view>
<view class="activity-card">
<view class="activity-item" v-for="(item, index) in activities" :key="index">
<view class="activity-dot" />
<view class="activity-body">
<text class="activity-title">{{ item.title }}</text>
<text class="activity-time">{{ item.time }}</text>
</view>
</view>
</view>
</view>
<view class="bottom-space" />
</view>
<AppTabbar current="dashboard" />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getUser, isLoggedIn } from '@/utils/auth.js'
import AppTabbar from '@/components/AppTabbar.vue'
const user = ref(null)
const greeting = computed(() => {
const hour = new Date().getHours()
if (hour < 12) return '早上好'
if (hour < 18) return '下午好'
return '晚上好'
})
const avatarText = computed(() => {
const name = user.value?.nickname || '云'
return name.charAt(0).toUpperCase()
})
const stats = ref([
{ label: '今日访问', value: '2,847', trend: '12%', up: true },
{ label: '活跃用户', value: '1,256', trend: '8%', up: true },
{ label: '转化率', value: '68.5%', trend: '3%', up: false },
{ label: '总收入', value: '¥8.2k', trend: '15%', up: true }
])
const quickActions = ref([
{ name: '数据分析', icon: 'chart-line' },
{ name: '消息中心', icon: 'bell' },
{ name: '订单管理', icon: 'clipboard-list' },
{ name: '设置', icon: 'gear' }
])
const activities = ref([
{ title: '新用户注册 +128', time: '5 分钟前' },
{ title: '系统更新完成 v2.1', time: '1 小时前' },
{ title: '订单 #8821 已发货', time: '2 小时前' },
{ title: '数据备份成功', time: '昨天 23:00' }
])
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
return
}
user.value = getUser()
})
function onQuickTap(item) {
uni.showToast({ title: item.name, icon: 'none' })
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background: #ffffff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 24rpx) 40rpx 32rpx;
background: #ffffff;
}
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
}
.greeting {
display: block;
font-size: 26rpx;
color: $u-primary-disabled;
}
.username {
display: block;
font-size: 40rpx;
font-weight: 600;
color: $u-primary;
margin-top: 6rpx;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-text {
font-size: 32rpx;
font-weight: 600;
color: $u-primary;
}
.search-bar {
display: flex;
align-items: center;
gap: 16rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 22rpx 28rpx;
}
.search-text {
font-size: 26rpx;
color: $u-primary-disabled;
}
.main-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 8rpx 40rpx 0;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
margin-bottom: 48rpx;
}
.stat-card {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 28rpx;
}
.stat-value {
display: block;
font-size: 36rpx;
font-weight: 600;
color: $u-primary;
}
.stat-label {
display: block;
font-size: 24rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.stat-trend {
display: block;
margin-top: 12rpx;
font-size: 22rpx;
&.up {
color: $u-primary;
}
&.down {
color: $u-primary-disabled;
}
}
.section {
margin-bottom: 48rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: $u-primary;
}
.section-more {
font-size: 24rpx;
color: $u-primary-disabled;
}
.quick-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
}
.quick-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
}
.quick-icon {
width: 96rpx;
height: 96rpx;
border-radius: 24rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
}
}
.quick-name {
font-size: 22rpx;
color: $u-primary-disabled;
}
.activity-card {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
overflow: hidden;
}
.activity-item {
display: flex;
align-items: center;
padding: 28rpx;
gap: 20rpx;
& + & {
border-top: 2rpx solid #f0f6fb;
}
}
.activity-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: $u-primary;
flex-shrink: 0;
}
.activity-body {
flex: 1;
}
.activity-title {
display: block;
font-size: 28rpx;
color: $u-primary;
}
.activity-time {
display: block;
font-size: 22rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.bottom-space {
height: 40rpx;
}
</style>
+451
View File
@@ -0,0 +1,451 @@
<template>
<view class="page">
<view class="page-header">
<text class="page-title">功能中心</text>
<text class="page-desc">探索更多精彩功能</text>
</view>
<view class="main-scroll">
<view class="feature-banner" @tap="onBannerTap">
<view class="banner-content">
<text class="banner-title">新功能上线</text>
<text class="banner-desc">AI 智能助手效率翻倍</text>
</view>
<view class="banner-btn">立即体验</view>
</view>
<view class="category" v-for="cat in categories" :key="cat.title">
<text class="category-title">{{ cat.title }}</text>
<view class="feature-grid">
<view
class="feature-card"
v-for="item in cat.items"
:key="item.name"
@tap="onFeatureTap(item)"
>
<view class="feature-icon">
<FaIcon :name="item.icon" color="#3c9cff" :size="22" />
</view>
<text class="feature-name">{{ item.name }}</text>
<text class="feature-desc">{{ item.desc }}</text>
</view>
</view>
</view>
<view class="bottom-space" />
</view>
<AppTabbar current="features" />
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { isLoggedIn } from '@/utils/auth.js'
import AppTabbar from '@/components/AppTabbar.vue'
const categories = ref([
{
title: '常用工具',
items: [
{ name: '扫一扫', desc: '快速识别', icon: 'qrcode' },
{ name: '收付款', desc: '便捷支付', icon: 'coins' },
{ name: '卡包', desc: '优惠券', icon: 'ticket' },
{ name: '出行', desc: '交通服务', icon: 'car' }
]
},
{
title: '生活服务',
items: [
{ name: '外卖', desc: '美食到家', icon: 'bag-shopping' },
{ name: '电影', desc: '在线购票', icon: 'film' },
{ name: '酒店', desc: '预订住宿', icon: 'hotel' },
{ name: '更多', desc: '发现更多', icon: 'ellipsis' }
]
},
{
title: '办公效率',
items: [
{ name: '文档', desc: '在线协作', icon: 'file-lines' },
{ name: '日历', desc: '日程管理', icon: 'calendar-days' },
{ name: '云盘', desc: '文件存储', icon: 'folder' },
{ name: '会议', desc: '视频会议', icon: 'video' }
]
}
])
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
}
})
function onBannerTap() {
uni.showToast({ title: 'AI 智能助手', icon: 'none' })
}
function onFeatureTap(item) {
uni.showToast({ title: item.name, icon: 'none' })
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background: #ffffff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.page-header {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 24rpx) 40rpx 24rpx;
}
.page-title {
display: block;
font-size: 40rpx;
font-weight: 600;
color: $u-primary;
}
.page-desc {
display: block;
font-size: 26rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.main-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 0 40rpx;
}
.feature-banner {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 32rpx 28rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 48rpx;
&:active {
border-color: $u-primary-disabled;
}
}
.banner-title {
display: block;
font-size: 30rpx;
font-weight: 600;
color: $u-primary;
}
.banner-desc {
display: block;
font-size: 24rpx;
color: $u-primary-disabled;
margin-top: 8rpx;
}
.banner-btn {
padding: 14rpx 28rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
font-size: 24rpx;
color: $u-primary;
font-weight: 500;
}
.category {
margin-bottom: 48rpx;
}
.category-title {
display: block;
font-size: 30rpx;
font-weight: 600;
color: $u-primary;
margin-bottom: 24rpx;
}
.feature-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.feature-card {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 28rpx;
&:active {
border-color: $u-primary-disabled;
}
}
.feature-icon {
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.feature-name {
display: block;
font-size: 28rpx;
font-weight: 600;
color: $u-primary;
}
.feature-desc {
display: block;
font-size: 22rpx;
color: $u-primary-disabled;
margin-top: 4rpx;
}
.bottom-space {
height: 40rpx;
}
</style>
+446
View File
@@ -0,0 +1,446 @@
<template>
<view class="login-page">
<view class="page-inner">
<!-- 品牌区 -->
<view class="header">
<text class="title">欢迎回来</text>
<text class="subtitle">登录云泽开启你的旅程</text>
</view>
<!-- 登录方式切换 -->
<view class="tabs">
<view
class="tab"
:class="{ active: loginType === 'phone' }"
@tap="loginType = 'phone'"
>手机登录</view>
<view
class="tab"
:class="{ active: loginType === 'account' }"
@tap="loginType = 'account'"
>账号登录</view>
</view>
<!-- 表单 -->
<view class="form">
<template v-if="loginType === 'phone'">
<view class="field">
<u-input
v-model="phone"
placeholder="请输入手机号"
type="number"
maxlength="11"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
>
<template #prefix>
<text class="prefix">+86</text>
</template>
</u-input>
</view>
<view class="field field-row">
<u-input
v-model="code"
placeholder="请输入验证码"
type="number"
maxlength="6"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
<text
class="code-link"
:class="{ disabled: countdown > 0 }"
@tap="sendCode"
>{{ countdown > 0 ? `${countdown}s 后重发` : '获取验证码' }}</text>
</view>
</template>
<template v-else>
<view class="field">
<u-input
v-model="username"
placeholder="请输入账号"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
</view>
<view class="field">
<u-input
v-model="password"
placeholder="请输入密码"
type="password"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
</view>
</template>
<view class="btn-primary" @tap="handleLogin"> </view>
<view class="btn-ghost" @tap="handleTestLogin">测试登录</view>
</view>
<!-- 协议 -->
<view class="agreement" @tap="agreed = !agreed">
<view class="agree-dot" :class="{ on: agreed }">
<text v-if="agreed" class="agree-check"></text>
</view>
<text class="agree-text">
登录即表示同意
<text class="agree-link">用户协议</text>
<text class="agree-link">隐私政策</text>
</text>
</view>
<!-- 第三方 -->
<view class="oauth">
<text class="oauth-label">其他方式</text>
<view class="oauth-icons">
<view class="oauth-btn" @tap="socialLogin('wechat')">
<FaIcon name="weixin" type="brands" color="#3c9cff" :size="22" />
</view>
<view class="oauth-btn" @tap="socialLogin('qq')">
<FaIcon name="qq" type="brands" color="#3c9cff" :size="22" />
</view>
<view class="oauth-btn" @tap="socialLogin('alipay')">
<FaIcon name="alipay" type="brands" color="#3c9cff" :size="22" />
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { loginSuccess, isLoggedIn } from '@/utils/auth.js'
const loginType = ref('phone')
const phone = ref('')
const code = ref('')
const username = ref('')
const password = ref('')
const agreed = ref(true)
const countdown = ref(0)
let timer = null
const inputStyle = {
backgroundColor: 'transparent',
padding: '0 8rpx',
height: '96rpx',
fontSize: '28rpx'
}
const placeholderStyle = 'color: #9acafc; font-size: 28rpx'
onMounted(() => {
if (isLoggedIn()) {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}
})
function sendCode() {
if (countdown.value > 0) return
if (!/^1\d{10}$/.test(phone.value)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
countdown.value = 60
timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) clearInterval(timer)
}, 1000)
uni.showToast({ title: '验证码已发送', icon: 'success' })
}
function navigateAfterLogin() {
uni.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}, 500)
}
function handleTestLogin() {
loginSuccess({ nickname: '测试用户', phone: '13800000000' })
navigateAfterLogin()
}
function handleLogin() {
if (!agreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
if (loginType.value === 'phone') {
if (!/^1\d{10}$/.test(phone.value)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
if (!code.value || code.value.length < 4) {
uni.showToast({ title: '请输入验证码', icon: 'none' })
return
}
loginSuccess({ phone: phone.value, nickname: '用户' + phone.value.slice(-4) })
} else {
if (!username.value.trim()) {
uni.showToast({ title: '请输入账号', icon: 'none' })
return
}
if (!password.value.trim()) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
loginSuccess({ nickname: username.value })
}
navigateAfterLogin()
}
function socialLogin(type) {
if (!agreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
const names = { wechat: '微信', qq: 'QQ', alipay: '支付宝' }
loginSuccess({ nickname: names[type] + '用户' })
uni.showToast({ title: `${names[type]}登录成功`, icon: 'success' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}, 500)
}
</script>
<style lang="scss" scoped>
.login-page {
min-height: 100vh;
background: #ffffff;
}
.page-inner {
padding: 0 56rpx;
padding-top: calc(var(--status-bar-height, 44px) + 80rpx);
}
/* ---- 品牌 ---- */
.header {
margin-bottom: 72rpx;
}
.logo {
width: 88rpx;
height: 88rpx;
border-radius: 22rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 36rpx;
}
.logo-char {
font-size: 44rpx;
font-weight: 600;
color: $u-primary;
}
.title {
display: block;
font-size: 52rpx;
font-weight: 600;
color: $u-primary;
letter-spacing: 2rpx;
}
.subtitle {
display: block;
font-size: 26rpx;
color: $u-primary-disabled;
margin-top: 12rpx;
}
/* ---- Tab ---- */
.tabs {
display: flex;
gap: 48rpx;
margin-bottom: 48rpx;
}
.tab {
font-size: 30rpx;
color: $u-primary-disabled;
padding-bottom: 12rpx;
position: relative;
&.active {
color: $u-primary;
font-weight: 600;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 4rpx;
background: $u-primary;
border-radius: 2rpx;
}
}
}
/* ---- 表单 ---- */
.form {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.field {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 0 28rpx;
overflow: hidden;
}
.field-row {
display: flex;
align-items: center;
}
.prefix {
font-size: 28rpx;
color: $u-primary;
padding-right: 20rpx;
margin-right: 20rpx;
border-right: 1rpx solid $u-primary-disabled;
}
.code-link {
flex-shrink: 0;
font-size: 26rpx;
color: $u-primary;
padding-left: 16rpx;
white-space: nowrap;
&.disabled {
color: $u-primary-disabled;
}
}
.btn-primary {
height: 96rpx;
background: $u-primary;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 600;
color: #fff;
letter-spacing: 6rpx;
margin-top: 16rpx;
&:active {
background: $u-primary-dark;
}
}
.btn-ghost {
height: 96rpx;
background: #ffffff;
border: 2rpx solid $u-primary;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: $u-primary;
&:active {
background: #f8fbff;
}
}
/* ---- 协议 ---- */
.agreement {
display: flex;
align-items: flex-start;
gap: 12rpx;
margin-top: 36rpx;
}
.agree-dot {
width: 30rpx;
height: 30rpx;
border-radius: 50%;
border: 2rpx solid $u-primary-disabled;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 2rpx;
&.on {
background: $u-primary;
border-color: $u-primary;
}
}
.agree-check {
font-size: 18rpx;
color: #fff;
}
.agree-text {
font-size: 22rpx;
color: $u-primary-disabled;
line-height: 1.7;
}
.agree-link {
color: $u-primary;
}
/* ---- 第三方 ---- */
.oauth {
margin-top: 80rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 28rpx;
padding-bottom: 60rpx;
}
.oauth-label {
font-size: 24rpx;
color: $u-primary-disabled;
}
.oauth-icons {
display: flex;
gap: 40rpx;
}
.oauth-btn {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
}
}
</style>
+649
View File
@@ -0,0 +1,649 @@
<template>
<view class="page">
<view class="profile-header">
<view class="profile-info">
<view class="profile-avatar">
<text class="avatar-text">{{ avatarText }}</text>
</view>
<view class="profile-detail">
<text class="profile-name">{{ user?.nickname || '云泽用户' }}</text>
<text class="profile-id">ID: {{ userId }}</text>
</view>
<view class="profile-edit" @tap="onEdit">
<FaIcon name="pen-to-square" color="#3c9cff" :size="18" />
</view>
</view>
<view class="profile-stats">
<view class="profile-stat" v-for="item in profileStats" :key="item.label">
<text class="stat-num">{{ item.value }}</text>
<text class="stat-label">{{ item.label }}</text>
</view>
</view>
</view>
<view class="main-scroll">
<view class="menu-group" v-for="(group, gIndex) in menuGroups" :key="gIndex">
<view
class="menu-item"
v-for="item in group"
:key="item.title"
@tap="onMenuTap(item)"
>
<view class="menu-left">
<view class="menu-icon">
<FaIcon :name="item.icon" color="#3c9cff" :size="18" />
</view>
<text class="menu-title">{{ item.title }}</text>
</view>
<view class="menu-right">
<text v-if="item.badge" class="menu-badge">{{ item.badge }}</text>
<FaIcon name="chevron-right" color="#9acafc" :size="14" />
</view>
</view>
</view>
<view class="logout-btn" @tap="handleLogout">
<text class="logout-text">退出登录</text>
</view>
<view class="bottom-space" />
</view>
<AppTabbar current="profile" />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getUser, logout, isLoggedIn } from '@/utils/auth.js'
import AppTabbar from '@/components/AppTabbar.vue'
const user = ref(null)
const avatarText = computed(() => {
const name = user.value?.nickname || '云'
return name.charAt(0).toUpperCase()
})
const userId = computed(() => {
return user.value?.phone
? user.value.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
: '10086'
})
const profileStats = ref([
{ label: '关注', value: '128' },
{ label: '粉丝', value: '256' },
{ label: '获赞', value: '1.2k' }
])
const menuGroups = ref([
[
{ title: '我的订单', icon: 'clipboard-list' },
{ title: '我的收藏', icon: 'star' },
{ title: '浏览历史', icon: 'clock-rotate-left' }
],
[
{ title: '消息通知', icon: 'bell', badge: '3' },
{ title: '账号安全', icon: 'lock' },
{ title: '隐私设置', icon: 'eye' }
],
[
{ title: '帮助中心', icon: 'circle-question' },
{ title: '关于我们', icon: 'circle-info' },
{ title: '设置', icon: 'gear' }
]
])
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
return
}
user.value = getUser()
})
function onEdit() {
uni.showToast({ title: '编辑资料', icon: 'none' })
}
function onMenuTap(item) {
uni.showToast({ title: item.title, icon: 'none' })
}
function handleLogout() {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success(res) {
if (res.confirm) {
logout()
uni.reLaunch({ url: '/pages/login/login' })
}
}
})
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background: #ffffff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.profile-header {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 24rpx) 40rpx 32rpx;
background: #ffffff;
}
.profile-info {
display: flex;
align-items: center;
gap: 24rpx;
}
.profile-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.avatar-text {
font-size: 40rpx;
font-weight: 600;
color: $u-primary;
}
.profile-detail {
flex: 1;
}
.profile-name {
display: block;
font-size: 36rpx;
font-weight: 600;
color: $u-primary;
}
.profile-id {
display: block;
font-size: 24rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.profile-edit {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
}
}
.profile-stats {
display: flex;
justify-content: space-around;
margin-top: 32rpx;
padding: 28rpx 0;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
}
.profile-stat {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-num {
font-size: 32rpx;
font-weight: 600;
color: $u-primary;
}
.stat-label {
font-size: 22rpx;
color: $u-primary-disabled;
margin-top: 4rpx;
}
.main-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 8rpx 40rpx 0;
}
.menu-group {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
margin-bottom: 24rpx;
overflow: hidden;
}
.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 28rpx;
border-bottom: 2rpx solid #f0f6fb;
&:last-child {
border-bottom: none;
}
&:active {
background: #f8fafc;
}
}
.menu-left {
display: flex;
align-items: center;
gap: 20rpx;
}
.menu-icon {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
}
.menu-title {
font-size: 28rpx;
color: $u-primary;
}
.menu-right {
display: flex;
align-items: center;
gap: 12rpx;
}
.menu-badge {
font-size: 20rpx;
color: #ffffff;
background: $u-primary;
padding: 2rpx 12rpx;
border-radius: 999rpx;
min-width: 32rpx;
text-align: center;
}
.logout-btn {
margin-top: 8rpx;
height: 96rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
background: #f8fafc;
}
}
.logout-text {
font-size: 30rpx;
color: $u-primary-disabled;
font-weight: 500;
}
.bottom-space {
height: 40rpx;
}
</style>