This commit is contained in:
那些花儿
2025-04-24 19:05:25 +08:00
parent bdaea43795
commit 4ad01ea72d
10 changed files with 1090 additions and 75 deletions
@@ -11,6 +11,5 @@ public class ClientActivitySignVO {
private String contactName;
private String contactPhone;
}
+10
View File
@@ -24,6 +24,7 @@
"@dcloudio/uni-mp-weixin": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-xhs": "3.0.0-4050720250324001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4050720250324001",
"moment": "^2.30.1",
"tdesign-mobile-vue": "^1.8.3",
"vue": "^3.4.21",
"vue-i18n": "^9.1.9"
@@ -8773,6 +8774,15 @@
"integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==",
"license": "MIT"
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+1
View File
@@ -52,6 +52,7 @@
"@dcloudio/uni-mp-weixin": "3.0.0-4050720250324001",
"@dcloudio/uni-mp-xhs": "3.0.0-4050720250324001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4050720250324001",
"moment": "^2.30.1",
"tdesign-mobile-vue": "^1.8.3",
"vue": "^3.4.21",
"vue-i18n": "^9.1.9"
+5
View File
@@ -8,6 +8,11 @@ import 'tdesign-mobile-vue/es/style/index.css';
// 自定义组件库样式
import './theme.css'
// 引入moment
import moment from 'moment';
import 'moment/dist/locale/zh-cn'
moment.locale('zh-cn')
export function createApp() {
const app = createSSRApp(App);
app.use(TDesign); // 使用TDesign组件库
+39 -8
View File
@@ -6,10 +6,20 @@
"navigationBarTitleText": "uni-app"
}
},
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom",
"disableScroll": true
}
},
{
"path": "pages/activity/list/index",
"style": {
"navigationBarTitleText": "赛事活动"
"navigationBarTitleText": "赛事活动",
"enablePullDownRefresh": true,
"onReachBottomDistance": 100
}
},
{
@@ -29,31 +39,52 @@
"style": {
"navigationBarTitleText": "我的团队"
}
},
{
"path": "pages/profile/index",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "#FFFFFF",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#ff5050",
"navigationBarBackgroundColor": "#f45a5a",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#000",
"selectedColor": "#ff5050",
"borderStyle": "black",
"selectedColor": "#f45a5a",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"height": "60px",
"fontSize": "12px",
"fontSize": "14px",
"iconWidth": "24px",
"spacing": "3px",
"iconfontSrc": "static/fonts/iconfont.ttf",
"list": [
{
"pagePath": "pages/index/index",
"text": "组件"
"text": "组件",
"iconfont": {
"text": "\ue64d",
"selectedText": "\ue64d",
"fontSize": "17px",
"color": "#333",
"selectedColor": "#f45a5a"
}
},
{
"pagePath": "pages/my/index",
"text": "接口"
"pagePath": "pages/profile/index",
"text": "我的",
"iconfont": {
"text": "\ue600",
"selectedText": "\ue600",
"fontSize": "17px",
"color": "#333",
"selectedColor": "#f45a5a"
}
}
]
}
+21 -40
View File
@@ -1,12 +1,10 @@
<template>
<view class="page-container">
<!-- 顶部搜索区域 -->
<view class="header">
<view class="search-block">
<t-search
v-model="pageForm.searchKeyword"
placeholder="搜索活动"
shape="round"
class="search"
>
<template #action>
<t-button variant="text" @click="filterVisible = true">筛选</t-button>
@@ -57,7 +55,7 @@
@click="navigateToDetail(item.id)"
>
<view class="card-head">
<image :src="item.image" mode="aspectFill" class="card-img"/>
<image :src="item.cover" mode="aspectFill" class="card-img"/>
<text class="card-tag" :class="getStatusClass(item.status)">
{{ getStatusText(item.status) }}
</text>
@@ -68,33 +66,27 @@
<view class="card-info">
<view class="info-item">
<t-icon name="alarm" size="12px"/>
<text>{{ item.date }}</text>
报名时间
{{moment(item.signStartTime).format('YYYY-MM-DD HH:mm:ss')}}
{{moment(item.signEndTime).format('YYYY-MM-DD HH:mm:ss')}}
</view>
<view class="info-item">
<t-icon name="location" size="12px"/>
<text>{{ item.location }}</text>
</view>
<view class="info-item">
<t-icon name="user" size="12px"/>
<text>{{ item.participants }}人已报名</text>
活动时间
{{moment(item.startTime).format('YYYY-MM-DD HH:mm:ss')}}
{{moment(item.endTime).format('YYYY-MM-DD HH:mm:ss')}}
</view>
</view>
<view class="card-footer">
<view class="price">
<text class="price-symbol">¥</text>
<text class="price-value">{{ item.price }}</text>
</view>
<t-button
size="small"
shape="round"
:theme="item.status === 2 ? 'default' : 'primary'"
:disabled="item.status === 2"
>
{{ item.status === 1 ? '立即报名' : item.status === 3 ? '预约提醒' : '已结束' }}
</t-button>
</view>
<!-- <view class="card-footer">-->
<!-- <t-button-->
<!-- size="small"-->
<!-- shape="round"-->
<!-- :theme="item.status === 2 ? 'default' : 'primary'"-->
<!-- :disabled="item.status === 2"-->
<!-- >-->
<!-- {{ item.status === 1 ? '立即报名' : item.status === 3 ? '预约提醒' : '已结束' }}-->
<!-- </t-button>-->
<!-- </view>-->
</view>
</view>
@@ -117,6 +109,7 @@
import {ref, reactive, onMounted} from 'vue';
import {Toast} from 'tdesign-mobile-vue';
import {useRequest} from "@/util/request";
import moment from "moment";
// 筛选选项
const statusOptions = [
@@ -141,12 +134,6 @@ const hasMore = ref(true);
const refreshIng = ref(false);
const filterVisible = ref(false);
// 搜索表单
const searchForm = reactive({
keyword: '',
status: ''
});
// 获取状态样式类
const getStatusClass = (status) => {
return {
@@ -187,6 +174,7 @@ const getListData = async (isRefresh = false) => {
} else {
dataList.value = [...dataList.value, ...data.list];
}
pageForm.totalCount = data.totalCount;
hasMore.value = dataList.value.length < data.totalCount;
pageForm.pageNumber++;
console.log(hasMore.value)
@@ -237,19 +225,12 @@ onMounted(() => {
</script>
<style scoped>
.header {
.search-block {
background-color: #fff;
padding: 10px 16px;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05);
position: sticky;
top: 0;
z-index: 10;
}
.search {
--td-search-bg-color: #f0f0f0;
}
.filter {
background-color: #fff;
border-radius: 0 0 16px 16px;
+163 -14
View File
@@ -59,7 +59,6 @@
<div class="member-name">
{{ member.name }}
<span class="member-role" v-if="member.role === 'leader'">队长</span>
<span class="member-role" v-else-if="member.role === 'admin'" style="background-color: #ff9a5a;">管理员</span>
</div>
<div class="member-detail">
<div class="member-detail-item">
@@ -80,17 +79,18 @@
<!-- 当前角色与申请队长 -->
<div v-if="currentUserRole !== 'leader'">
<div class="role-notice" v-if="currentUserRole === 'admin'">
<i class="bi bi-info-circle"></i>
您当前是管理员身份,拥有团队管理权限
</div>
<div class="role-notice" v-else>
<div class="role-notice">
<i class="bi bi-info-circle"></i>
您当前是普通成员身份
</div>
<button class="apply-leader-btn" @click="showLeaderConfirm">
<t-button block
theme="primary"
variant="outline"
size="large"
shape="round"
@click="leaderConfirmVisible = true">
<i class="bi bi-award"></i>申请成为队长
</button>
</t-button>
</div>
<div class="role-notice" v-else>
<i class="bi bi-check-circle"></i>
@@ -100,15 +100,32 @@
<!-- 项目报名按钮 -->
<div class="team-section">
<button class="register-btn" @click="goToEventRegistration">
<t-button block
theme="primary"
size="large"
shape="round" @click="goToEventRegistration">
<i class="bi bi-calendar-check"></i>
项目报名
</button>
</t-button>
</div>
</div>
</view>
<t-dialog
v-model:visible="leaderConfirmVisible"
title="申请成为队长"
cancel-btn="取消"
confirm-btn="确认"
@confirm="onLeaderConfirm"
@cancel="leaderConfirmVisible = false"
>
<template #content>
<p>申请成为队长前,请确认您已与团队其他成员商议并达成一致意见。</p>
<p>一个团队仅能有一名队长,作为队长,您可以帮助队员一键报名项目。</p>
</template>
</t-dialog>
</template>
<script setup>
@@ -124,7 +141,7 @@ const teamId = ''
const team = reactive({
id: teamId,
name: '江苏健康跑团',
name: 'xx队',
logoUrl: null,
createdDate: '2023-10-15',
eventId: '',
@@ -132,7 +149,7 @@ const team = reactive({
memberCount: 12,
rank: 5,
score: 256,
description: '我们是一支来自江苏省的健康跑团队,致力于推广健康跑步文化,鼓励大家积极参与体育活动,共同展现江苏的活力风采。',
description: 'xxxxxxxxxxxxxxxxxxx',
// 团队成员
members: [
@@ -179,6 +196,9 @@ const team = reactive({
]
});
const currentUserRole = ref('')
// 申请成为队长弹框提示
const leaderConfirmVisible = ref(false)
function handleMenuOpened(){
@@ -190,6 +210,12 @@ function actChange(val){
console.log(val)
}
// 申请成为队长
function onLeaderConfirm(){
}
function goToEventRegistration(){}
</script>
@@ -310,4 +336,127 @@ function actChange(val){
font-size: 13px;
color: #666;
}
/* 团队成员列表 */
.team-section {
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.section-title {
font-size: 17px;
font-weight: 600;
color: #333;
display: flex;
align-items: center;
}
.section-title i {
margin-right: 8px;
color: #e74c3c;
font-size: 20px;
}
.member-list {
background: white;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.member-item {
display: flex;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
align-items: center;
}
.member-item:last-child {
border-bottom: none;
}
.member-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #f0f0f0;
margin-right: 15px;
overflow: hidden;
flex-shrink: 0;
}
.member-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.member-info {
flex: 1;
}
.member-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 5px;
display: flex;
align-items: center;
}
.member-role {
display: inline-block;
font-size: 12px;
background-color: #e74c3c;
color: white;
padding: 1px 6px;
border-radius: 4px;
margin-left: 8px;
}
.member-detail {
display: flex;
font-size: 13px;
color: #666;
}
.member-detail-item {
display: flex;
align-items: center;
margin-right: 15px;
}
.member-detail-item i {
margin-right: 4px;
font-size: 14px;
color: #999;
}
.member-actions {
color: #999;
padding: 5px;
border-radius: 5px;
}
.role-notice {
background-color: #f8f9fa;
padding: 12px 15px;
border-radius: 8px;
margin-top: 15px;
font-size: 13px;
color: #666;
display: flex;
align-items: center;
}
.role-notice i {
color: #ffa63f;
margin-right: 8px;
font-size: 16px;
}
</style>
+239
View File
@@ -0,0 +1,239 @@
<template>
<view class="login-container">
<image
class="banner-image"
src="https://res.firstui.cn/template/loginB/login/img_banner_3x.png"
mode="aspectFill"
/>
<view class="login-form">
<text class="title">登录</text>
<t-input
class="input-item"
v-model="loginForm.phone"
placeholder="请输入手机号码"
>
</t-input>
<t-input
class="input-item"
v-model="loginForm.smsCode"
placeholder="请输入验证码"
>
<template #suffix>
<t-button variant="text" class="verify-btn" @click="getSmsCode" :disabled="isCountingDown">
{{ countDownText }}
</t-button>
</template>
</t-input>
<!-- <view class="login-options">-->
<!-- <text>账号登录</text>-->
<!-- <text>注册</text>-->
<!-- </view>-->
<t-button block theme="primary" size="large" class="login-btn" @click="handleLogin">
登录
</t-button>
<!-- <view class="other-login">-->
<!-- <text class="other-text">其他登录方式</text>-->
<!-- <view class="login-methods">-->
<!-- <t-button shape="circle" variant="outline" icon="qq" class="method-btn" />-->
<!-- <t-button shape="circle" variant="outline" icon="wechat" class="method-btn" />-->
<!-- <t-button shape="circle" variant="outline" icon="image" class="method-btn" />-->
<!-- </view>-->
<!-- </view>-->
</view>
</view>
</template>
<script setup>
import {ref, reactive, computed, onMounted} from 'vue'
import {Toast} from 'tdesign-mobile-vue';
import {useRequest} from "@/util/request";
const request = useRequest()
const loginForm = reactive({
phone: '',
smsCode: ''
})
const countdown = ref(0)
const timer = ref(null)
// 是否在倒计时中
const isCountingDown = computed(() => countdown.value > 0)
// 验证码按钮文字
const countDownText = computed(() => {
return isCountingDown.value ? `${countdown.value}秒后重试` : '获取验证码'
})
// 开始倒计时
const startCountDown = () => {
countdown.value = 60
timer.value = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer.value)
timer.value = null
}
}, 1000)
}
// 获取验证码
async function getSmsCode() {
if (!loginForm.phone) {
Toast.error('请输入手机号码');
return
}
if (isCountingDown.value) return
try {
const {code, data, msg} = await request.post('/client/auth/send-sms-code/' + loginForm.phone)
if (code === 0) {
Toast.success('验证码发送成功')
// 开始倒计时
startCountDown()
} else {
Toast.error(msg)
}
} catch (e) {
Toast.error(e.message)
} finally {
}
}
async function handleLogin() {
if (!loginForm.phone) {
Toast.error('请输入手机号码');
return
}
if (!loginForm.smsCode) {
Toast.error('请输入验证码');
return
}
try {
const {data, code, msg} = await useRequest().post('/client/auth/sms-login', loginForm)
if (code === 0) {
Toast.success('登录成功')
uni.switchTab({
url: '/pages/index/index'
})
} else {
Toast.error(msg)
}
} catch (e) {
console.error(e)
Toast.error(e.message)
}
}
onMounted(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
</script>
<style lang="less" scoped>
.login-container {
min-height: 100vh;
background-color: #fff;
position: relative;
}
.banner-image {
width: 100%;
height: 260px;
background-color: #4080ff;
}
.login-form {
padding: 20px;
.title {
font-size: 24px;
font-weight: bold;
margin: 20px 0;
display: block;
color: var(--td-brand-color-5);
}
.input-item {
margin-bottom: 20px;
}
.country-code {
color: #333;
font-weight: bold;
}
.verify-btn {
color: var(--td-brand-color-5);
}
.login-options {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
font-size: 14px;
color: #666;
}
.login-btn {
margin: 20px 0;
}
.other-login {
margin-top: 40px;
text-align: center;
.other-text {
color: #999;
font-size: 14px;
position: relative;
display: inline-block;
padding: 0 10px;
background: #fff;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
width: 60px;
height: 1px;
background: #eee;
}
&::before {
left: -70px;
}
&::after {
right: -70px;
}
}
.login-methods {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 20px;
.method-btn {
width: 40px;
height: 40px;
}
}
}
}
</style>
-11
View File
@@ -1,11 +0,0 @@
<script setup>
</script>
<template>
我的页面
</template>
<style scoped>
</style>
+611
View File
@@ -0,0 +1,611 @@
<template>
<div class="container">
<!-- 头部个人信息 -->
<div class="profile-header">
<div class="back-button" @click="goBack">
<i class="bi bi-chevron-left"></i>
</div>
<div class="profile-avatar">
<img src="https://erp-bucket.pek3b.qingstor.com/assistant/avatar.jpg" alt="用户头像"
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMjAwIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2U5ZWNlZiIvPjxwYXRoIGQ9Ik0xMDAgNTVhNDAgNDAgMCAxIDAgMCA4MCw0MCA0MCAwIDEgMCAwLTgwem0wIDE5MGM0MiAwIDgwLTM0IDgwLTc1IDAtMTcuNS05LjMtMzMuMi0yNC0zOS44YTYwIDYwIDAgMCAxLTExMiAwQzI5LjMgMTM3IDIwIDE1Mi41IDIwIDE3MGMwIDQxIDM4IDc1IDgwIDc1eiIgZmlsbD0iI2I5YmVjNyIvPjwvc3ZnPg=='">
</div>
<div class="profile-name">{{ userData.name }}</div>
<div class="profile-bio">{{ userData.bio }}</div>
<div class="profile-stats">
<div class="stat-item">
<div class="stat-value">{{ userData.events }}</div>
<div class="stat-label">参加活动</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ userData.points }}</div>
<div class="stat-label">活力值</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ userData.teams }}</div>
<div class="stat-label">加入团队</div>
</div>
</div>
</div>
<!-- 基本功能区 -->
<div class="profile-functions">
<div class="function-section">
<div class="section-header">
<div class="section-title">
<i class="bi bi-gear-fill"></i>
<span>基本功能</span>
</div>
</div>
<div class="function-item" @click="navigateTo('personal-info')">
<div class="item-icon">
<i class="bi bi-person-badge"></i>
</div>
<div class="item-content">
<div class="item-title">个人资料</div>
<div class="item-description">查看和编辑个人信息</div>
</div>
<div class="item-arrow">
<i class="bi bi-chevron-right"></i>
</div>
</div>
<div class="function-item" @click="navigateTo('certification')">
<div class="item-icon" style="background-color: #f5f0ff;">
<i class="bi bi-shield-check" style="color: #7e57c2;"></i>
</div>
<div class="item-content">
<div class="item-title">实名认证</div>
<div class="item-description">已认证</div>
</div>
<div class="item-arrow">
<i class="bi bi-chevron-right"></i>
</div>
</div>
</div>
<!-- 我参加的活动 -->
<div class="function-section">
<div class="function-item" @click="navigateTo('all-events')">
<div class="item-icon" style="background-color: #fff4e5;">
<i class="bi bi-calendar-event" style="color: #ff9800;"></i>
</div>
<div class="item-content">
<div class="item-title">我参加的活动</div>
<div class="item-description">已参加 {{ userData.events }} 个活动</div>
</div>
<div class="item-arrow">
<i class="bi bi-chevron-right"></i>
</div>
</div>
</div>
<!-- 我的团队 -->
<div class="function-section">
<div class="function-item" @click="navigateTo('all-teams')">
<div class="item-icon" style="background-color: #e8f5e9;">
<i class="bi bi-people-fill" style="color: #4caf50;"></i>
</div>
<div class="item-content">
<div class="item-title">我的团队</div>
<div class="item-description">已加入 {{ userData.teams }} 个团队</div>
</div>
<div class="item-arrow">
<i class="bi bi-chevron-right"></i>
</div>
</div>
</div>
</div>
<!-- 底部填充 -->
<div class="bottom-padding"></div>
<!-- 底部导航栏 -->
<div class="footer-nav">
<div class="footer-item" @click="navigateTo('home')">
<div class="footer-icon-container">
<div class="footer-icon">
<i class="bi bi-house-fill"></i>
</div>
</div>
<div class="footer-text">首页</div>
</div>
<div class="footer-item" @click="navigateTo('sports')">
<div class="footer-icon-container">
<div class="footer-icon">
<i class="bi bi-lightning-fill"></i>
</div>
</div>
<div class="footer-text">运动</div>
</div>
<div class="footer-item" @click="navigateTo('community')">
<div class="footer-icon-container">
<div class="footer-icon">
<i class="bi bi-people-fill"></i>
</div>
</div>
<div class="footer-text">社区</div>
</div>
<div class="footer-item active" @click="navigateTo('profile')">
<div class="footer-icon-container">
<div class="footer-icon">
<i class="bi bi-person-fill"></i>
</div>
</div>
<div class="footer-text">我的</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, reactive} from "vue";
// 用户数据
const userData = reactive({
name: '张三',
bio: '热爱运动,热爱生活',
events: 8,
points: 1650,
teams: 2
});
// 参加的活动数据
const events = ref([
{
id: 1,
title: '2023年江苏省青少年体育锦标赛',
image: 'https://erp-bucket.pek3b.qingstor.com/assistant/premium-event-1.jpg',
date: '2023.12.15-12.20',
location: '南京奥体中心',
status: 'completed'
},
{
id: 2,
title: '2023年冬季室内马拉松挑战赛',
image: 'https://erp-bucket.pek3b.qingstor.com/assistant/premium-event-2.jpg',
date: '2024.01.10-01.15',
location: '苏州国际博览中心',
status: 'upcoming'
}
]);
// 团队数据
const teams = ref([
{
id: 1,
name: '南京跑团',
image: 'https://erp-bucket.pek3b.qingstor.com/assistant/team-1.jpg',
members: 128,
level: 5,
role: '普通成员',
currentEvent: { // 当前参与的活动
id: 2,
title: '2023年冬季室内马拉松挑战赛'
},
events: [1, 2] // 参与过的所有活动ID
},
{
id: 2,
name: '江苏健走协会',
image: 'https://erp-bucket.pek3b.qingstor.com/assistant/team-2.jpg',
members: 256,
level: 8,
role: '团队管理员',
currentEvent: {
id: 1,
title: '2023年江苏省青少年体育锦标赛'
},
events: [1]
}
]);
// 活动状态文本
const getStatusText = (status) => {
switch (status) {
case 'completed':
return '已完成';
case 'upcoming':
return '即将开始';
case 'in-progress':
return '进行中';
default:
return '';
}
};
</script>
<style scoped lang="less">
.container {
width: 100%;
max-width: 750px;
margin: 0 auto;
background-color: #fff;
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
/* 头部区域 */
.profile-header {
background: linear-gradient(135deg, #ff5050 0%, #ff9a5a 100%);
padding: 30px 20px 20px;
color: white;
position: relative;
}
.back-button {
position: absolute;
top: 15px;
left: 15px;
color: white;
font-size: 24px;
z-index: 10;
}
.profile-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
margin: 0 auto 15px;
border: 3px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.profile-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.profile-name {
text-align: center;
font-size: 20px;
font-weight: 600;
margin-bottom: 5px;
}
.profile-bio {
text-align: center;
font-size: 14px;
opacity: 0.8;
margin-bottom: 20px;
}
.profile-stats {
display: flex;
justify-content: space-around;
margin-top: 15px;
padding-bottom: 10px;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 18px;
font-weight: bold;
}
.stat-label {
font-size: 12px;
opacity: 0.8;
}
/* 功能区域 */
.profile-functions {
padding: 20px 15px;
}
.function-section {
background: #fff;
border-radius: 10px;
margin-bottom: 15px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.section-header {
padding: 15px;
border-bottom: 1px solid #f2f2f2;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title {
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.section-title i {
font-size: 18px;
color: #ff5050;
}
.view-all {
font-size: 12px;
color: #999;
display: flex;
align-items: center;
}
.function-item {
padding: 15px;
display: flex;
align-items: center;
border-bottom: 1px solid #f2f2f2;
}
.function-item:last-child {
border-bottom: none;
}
.item-icon {
width: 40px;
height: 40px;
background-color: #f5f8ff;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 15px;
}
.item-icon i {
font-size: 20px;
color: #3f7eef;
}
.item-content {
flex: 1;
}
.item-title {
font-size: 15px;
font-weight: 500;
margin-bottom: 3px;
}
.item-description {
font-size: 12px;
color: #999;
}
.item-arrow {
color: #ccc;
font-size: 14px;
}
/* 我的活动 */
.events-list, .teams-list {
padding: 10px 15px;
}
.event-card, .team-card {
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
margin-bottom: 15px;
overflow: hidden;
display: flex;
}
.event-image, .team-image {
width: 80px;
height: 80px;
overflow: hidden;
}
.event-image img, .team-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.event-info, .team-info {
flex: 1;
padding: 10px 15px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.event-title, .team-title {
font-size: 15px;
font-weight: 500;
margin-bottom: 5px;
line-height: 1.3;
}
.event-meta, .team-meta {
display: flex;
align-items: center;
gap: 10px;
}
.meta-info {
display: flex;
align-items: center;
font-size: 12px;
color: #666;
}
.meta-info i {
font-size: 14px;
margin-right: 3px;
color: #ff5050;
}
.event-status, .team-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
color: white;
align-self: flex-start;
}
.status-completed {
background-color: #8e8e8e;
}
.status-upcoming {
background-color: #4caf50;
}
.status-in-progress {
background-color: #ff9800;
}
/* 团队活动标签 */
.team-event-tag {
background-color: #e3f2fd;
color: #0277bd;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
display: inline-flex;
align-items: center;
margin-top: 5px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.team-event-tag i {
font-size: 12px;
margin-right: 4px;
color: #0277bd;
}
/* 底部导航栏 */
.footer-nav {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 55px;
background-color: #fff;
display: flex;
justify-content: space-around;
align-items: center;
border-top: 1px solid #eee;
z-index: 100;
padding: 5px 0 5px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
padding-bottom: env(safe-area-inset-bottom);
}
.footer-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
}
.footer-icon-container {
position: relative;
height: 24px;
width: 24px;
display: flex;
justify-content: center;
align-items: center;
transition: transform 0.3s ease;
margin-bottom: 2px;
}
.footer-icon {
display: flex;
justify-content: center;
align-items: center;
position: relative;
z-index: 2;
}
.active .footer-icon-container {
transform: translateY(-8px);
}
.active .footer-icon-container::before {
content: '';
position: absolute;
width: 36px;
height: 36px;
background-color: #ff5050;
border-radius: 50%;
box-shadow: 0 2px 6px rgba(255, 80, 80, 0.3);
z-index: 1;
display: block;
}
.footer-text {
font-size: 12px;
margin-top: 1px;
color: #666;
}
.active .footer-text {
color: #ff5050;
margin-top: -2px;
}
.active i {
color: #ffffff;
}
/* 底部填充 */
.bottom-padding {
height: 60px;
}
/* 加载动画 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
transition: opacity 0.5s, visibility 0.5s;
}
.loading-overlay.hidden {
opacity: 0;
visibility: hidden;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #ff5050;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>