669 lines
15 KiB
Vue
669 lines
15 KiB
Vue
<template>
|
||
<view class="page-container">
|
||
<view v-if="team">
|
||
<t-sticky>
|
||
<t-dropdown-menu class="top-dropdown-menu">
|
||
<t-dropdown-item :options="actOptions" :value="pageForm.actId" @change="actChange"/>
|
||
</t-dropdown-menu>
|
||
</t-sticky>
|
||
<!-- 团队卡片 -->
|
||
<div class="content-area">
|
||
<div class="team-card">
|
||
<div class="team-header">
|
||
<div class="team-logo">
|
||
<i class="bi bi-people-fill" v-if="!team.logoUrl"></i>
|
||
<img :src="team.logoUrl" alt="团队logo" v-if="team.logoUrl">
|
||
</div>
|
||
|
||
<div class="team-basic-info">
|
||
<div class="team-name">{{ team.groupName }}</div>
|
||
<div class="team-meta">创建于 {{ moment(team.createdAt).format("YYYY-MM-DD HH:mm") }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="team-stats">
|
||
<div class="stat-item">
|
||
<div class="stat-icon"><i class="bi bi-people"></i></div>
|
||
<div class="stat-content">
|
||
<div class="stat-value">成员:{{ team.groupUsers?.length }}</div>
|
||
<!-- <div class="stat-label">成员</div>-->
|
||
</div>
|
||
</div>
|
||
<div class="stat-divider"></div>
|
||
<div class="stat-item" @click="queryRank">
|
||
<div class="stat-icon"><i class="bi bi-trophy"></i></div>
|
||
<div class="stat-content">
|
||
<div class="stat-value">排名:{{ team.totalRank ? team.totalRank : '--' }}</div>
|
||
<div class="stat-label">点击查看排行榜</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 项目报名按钮 -->
|
||
<div class="action-section">
|
||
<t-button block theme="danger" size="large" class="register-btn" @click="goToEventRegistration"
|
||
:disabled="!team.teamIsLeader">
|
||
<div v-if="team.teamIsLeader"> {{ team.isSelfLeader ? '参与项目报名' : '查看项目报名' }}</div>
|
||
<div v-else>暂无队长无法报名</div>
|
||
</t-button>
|
||
</div>
|
||
|
||
<!-- 团队成员列表 -->
|
||
<div class="members-section">
|
||
<div class="section-header">
|
||
<div class="section-title">
|
||
<i class="bi bi-people-fill"></i>
|
||
团队成员
|
||
</div>
|
||
<div class="section-badge">{{ team.groupUsers.length }}</div>
|
||
</div>
|
||
<div class="member-grid">
|
||
<div class="member-grid-item" v-for="member in team.groupUsers" :key="member.id"
|
||
@click="openUserDetail(member)">
|
||
<div class="member-name">
|
||
{{ member.userName }}
|
||
<t-tag theme="danger" size="small" v-if="member.isLeader">队长</t-tag>
|
||
</div>
|
||
<div class="member-details">
|
||
<span><i class="bi bi-gender-ambiguous"></i>{{ member.sex }}</span>
|
||
<span><i class="bi bi-building"></i>{{ member.unitName }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 当前角色与申请队长 -->
|
||
<div class="role-section">
|
||
<div class="role-card" v-if="team.isSelfLeader">
|
||
<div class="role-icon">
|
||
<i class="bi bi-trophy"></i>
|
||
</div>
|
||
<div class="role-info">
|
||
<div class="role-title">您是队长</div>
|
||
<div class="role-desc">拥有团队所有管理权限</div>
|
||
</div>
|
||
</div>
|
||
<div v-if="team.teamIsLeader&&!team.isSelfLeader">
|
||
<div class="role-card">
|
||
<div class="role-icon">
|
||
<i class="bi bi-person"></i>
|
||
</div>
|
||
<div class="role-info">
|
||
<div class="role-title">您是普通成员</div>
|
||
<div class="role-desc">项目报名请联系队长</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="!team.teamIsLeader&&!team.isSelfLeader">
|
||
<div class="role-card">
|
||
<div class="role-icon">
|
||
<i class="bi bi-person"></i>
|
||
</div>
|
||
<div class="role-info">
|
||
<div class="role-title">您是普通成员</div>
|
||
<div class="role-desc">申请成为队长可获得更多权限</div>
|
||
</div>
|
||
</div>
|
||
<t-button block
|
||
class="leader-apply-btn"
|
||
theme="danger"
|
||
variant="outline"
|
||
@click="leaderConfirmVisible = true">
|
||
申请成为队长
|
||
</t-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</view>
|
||
<view v-else style="flex: 1;display: flex;justify-content: center;align-items: center">
|
||
<t-empty v-if="actOptions && actOptions.length === 0"
|
||
image="/static/img/empty.png"
|
||
description="您没有任何团队信息"
|
||
style="flex: 1">
|
||
</t-empty>
|
||
</view>
|
||
</view>
|
||
<t-dialog
|
||
v-model:visible="leaderConfirmVisible"
|
||
title="申请成为队长"
|
||
cancel-btn="取消"
|
||
confirm-btn="确认"
|
||
@confirm="onLeaderConfirm"
|
||
@cancel="leaderConfirmVisible = false"
|
||
>
|
||
<template #content>
|
||
<div class="dialog-content">
|
||
<div class="dialog-icon">
|
||
<i class="bi bi-info-circle"></i>
|
||
</div>
|
||
<div class="dialog-text">
|
||
<p>申请成为队长前,请确认您已与团队其他成员商议并达成一致意见。</p>
|
||
<p>一个团队仅能有一名队长,作为队长,您可以帮助队员一键报名项目。</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</t-dialog>
|
||
|
||
<t-popup v-model:visible="showUserDetailPicker" placement="bottom" destroy-on-close style="height: 50%">
|
||
<view class="member-picker">
|
||
<view class="picker-header">
|
||
<text class="picker-title">成员信息</text>
|
||
<t-icon name="close" @click="showUserDetailPicker = false"/>
|
||
</view>
|
||
|
||
<view class="member-list">
|
||
<t-cell-group bordered>
|
||
<t-cell title="姓名" hover>{{ userInfo.userName }}</t-cell>
|
||
<t-cell title="性别" hover>{{userInfo.sex}}</t-cell>
|
||
<t-cell title="联系方式" hover>{{userInfo.phone}}</t-cell>
|
||
<t-cell title="职务" hover>{{userInfo.position}}</t-cell>
|
||
<t-cell title="所属单位" hover>{{userInfo.unitName}}</t-cell>
|
||
</t-cell-group>
|
||
</view>
|
||
</view>
|
||
</t-popup>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {ref, reactive, onMounted, computed} from 'vue';
|
||
import {Dialog, Toast} from 'tdesign-mobile-vue';
|
||
import {useRequest} from "@/utils/request";
|
||
import moment from "moment";
|
||
import {onLoad} from "@dcloudio/uni-app";
|
||
|
||
const actList = ref([])
|
||
const showDesc = ref(false)
|
||
|
||
const actOptions = computed(() => {
|
||
return actList.value.map(item => {
|
||
return {
|
||
value: item.id,
|
||
label: item.title
|
||
}
|
||
})
|
||
})
|
||
|
||
const pageForm = reactive({
|
||
actId: null,
|
||
})
|
||
|
||
const teamId = ''
|
||
|
||
let team = ref(null);
|
||
// 申请成为队长弹框提示
|
||
const leaderConfirmVisible = ref(false)
|
||
|
||
// 用户详情弹框
|
||
const showUserDetailPicker = ref(false)
|
||
const userInfo = ref({})
|
||
// 打开用户详情
|
||
const openUserDetail = (val) => {
|
||
showUserDetailPicker.value = true
|
||
userInfo.value = val
|
||
console.log(val)
|
||
}
|
||
|
||
const actChange = (val) => {
|
||
getListAct()
|
||
}
|
||
|
||
// 申请成为队长
|
||
const onLeaderConfirm = async () => {
|
||
try {
|
||
pageForm.groupId = team.value.id
|
||
const {code, msg} = await useRequest().post('/client/group/apply-leader/' + team.value.id);
|
||
if (code === 0) {
|
||
await getTeamInfo();
|
||
Toast({message: '申请成功', theme: 'success'});
|
||
leaderConfirmVisible.value = false
|
||
} else {
|
||
Toast({message: msg, theme: 'error'});
|
||
}
|
||
} catch (e) {
|
||
Toast({message: e, theme: 'error'});
|
||
}
|
||
}
|
||
|
||
// 点击参与项目报名
|
||
const goToEventRegistration = () => {
|
||
uni.navigateTo({url: '/pages/activity/team-form/index?id=' + pageForm.actId})
|
||
};
|
||
|
||
// 根据活动查询队伍信息
|
||
const getTeamInfo = async () => {
|
||
try {
|
||
const {code, msg, data} = await useRequest().post('/client/group/info/' + pageForm.actId);
|
||
if (code === 0) {
|
||
team.value = data
|
||
team.value.teamIsLeader = !!team.value.groupUsers.find(t => t.isLeader)
|
||
} else {
|
||
Toast({message: msg, theme: 'error'});
|
||
}
|
||
} catch (e) {
|
||
Toast({message: e, theme: 'error'});
|
||
}
|
||
};
|
||
|
||
// 查询自己已经签到的活动
|
||
const getListAct = async () => {
|
||
try {
|
||
const {code, msg, data} = await useRequest().post('/client/group/list-act');
|
||
if (code === 0) {
|
||
actList.value = data;
|
||
if (data.length > 0) {
|
||
pageForm.actId = data[0].id;
|
||
await getTeamInfo();
|
||
}
|
||
}
|
||
} catch (error) {
|
||
Toast({message: error, theme: 'error'});
|
||
}
|
||
|
||
};
|
||
|
||
// 点击查看成绩排名
|
||
const queryRank = () => {
|
||
uni.navigateTo({url: '/pages/activity/rank/index?id=' + pageForm.actId})
|
||
}
|
||
|
||
// 初始化
|
||
onLoad(() => {
|
||
getListAct();
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
:deep(.top-dropdown-menu) {
|
||
background-color: #fff;
|
||
padding: 10px 0;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
z-index: 999;
|
||
}
|
||
|
||
.content-area {
|
||
padding: 0 16px 24px;
|
||
}
|
||
|
||
/*用户弹框*/
|
||
.member-picker {
|
||
background-color: #fff;
|
||
border-radius: 16px 16px 0 0;
|
||
padding: 20px;
|
||
|
||
.picker-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
|
||
.picker-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.picker-subtitle {
|
||
font-size: 14px;
|
||
color: #ff5f5c;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.member-list {
|
||
max-height: 50vh;
|
||
overflow-y: auto;
|
||
margin-bottom: 20px;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
.member-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 0;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
&--disabled {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.member-status {
|
||
font-size: 12px;
|
||
color: #52c41a;
|
||
}
|
||
|
||
:deep(.t-checkbox) {
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.picker-footer {
|
||
padding-top: 12px;
|
||
}
|
||
}
|
||
|
||
/* 团队卡片 */
|
||
.team-card {
|
||
background-color: #fff;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
box-shadow: 0 4px 16px rgba(255, 95, 92, 0.08);
|
||
margin: 12px 0 20px;
|
||
}
|
||
|
||
.team-header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px;
|
||
background: linear-gradient(to right, rgba(255, 95, 92, 0.05), rgba(255, 95, 92, 0.02));
|
||
border-bottom: 1px solid rgba(255, 95, 92, 0.1);
|
||
}
|
||
|
||
.team-logo {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
background-color: white;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border: 2px solid #fff;
|
||
box-shadow: 0 2px 8px rgba(255, 95, 92, 0.15);
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.team-logo i {
|
||
font-size: 24px;
|
||
color: #ff5f5c;
|
||
}
|
||
|
||
.team-logo img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.team-basic-info {
|
||
margin-left: 12px;
|
||
flex: 1;
|
||
}
|
||
|
||
.team-name {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.team-meta {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.team-stats {
|
||
display: flex;
|
||
padding: 12px 16px;
|
||
}
|
||
|
||
.stat-item {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.stat-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 18px;
|
||
background-color: rgba(255, 95, 92, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 10px;
|
||
color: #ff5f5c;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.stat-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.stat-divider {
|
||
width: 1px;
|
||
height: 36px;
|
||
background-color: #f0f0f0;
|
||
margin: 0 16px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.team-desc-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 16px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
background-color: #f9f9f9;
|
||
border-top: 1px solid #f0f0f0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.team-desc-toggle i {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.team-desc {
|
||
padding: 12px 16px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
line-height: 1.5;
|
||
position: relative;
|
||
background-color: #fff8f8;
|
||
border-top: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.team-desc i {
|
||
color: #ffdddd;
|
||
font-size: 18px;
|
||
position: absolute;
|
||
top: 8px;
|
||
left: 10px;
|
||
}
|
||
|
||
/* 操作按钮 */
|
||
.action-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.register-btn {
|
||
height: 48px;
|
||
font-size: 16px;
|
||
background-color: #ff5f5c;
|
||
border-color: #ff5f5c;
|
||
box-shadow: 0 6px 16px rgba(255, 95, 92, 0.2);
|
||
}
|
||
|
||
/* 成员列表 */
|
||
.members-section {
|
||
background-color: #fff;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.section-title i {
|
||
margin-right: 8px;
|
||
color: #ff5f5c;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.section-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 24px;
|
||
height: 24px;
|
||
background-color: #fff0f0;
|
||
color: #ff5f5c;
|
||
border-radius: 12px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
padding: 0 8px;
|
||
}
|
||
|
||
.member-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 8px;
|
||
padding: 12px;
|
||
}
|
||
|
||
.member-grid-item {
|
||
background-color: #f9f9f9;
|
||
border-radius: 8px;
|
||
padding: 10px;
|
||
}
|
||
|
||
.member-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
margin-bottom: 2px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.member-details {
|
||
display: flex;
|
||
flex-direction: column;
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.member-details span {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.member-details i {
|
||
margin-right: 4px;
|
||
font-size: 12px;
|
||
color: #ccc;
|
||
}
|
||
|
||
/* 角色相关 */
|
||
.role-section {
|
||
padding: 16px;
|
||
}
|
||
|
||
.role-card {
|
||
display: flex;
|
||
align-items: center;
|
||
background-color: #fff8f8;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 16px;
|
||
box-shadow: 0 4px 12px rgba(255, 95, 92, 0.05);
|
||
}
|
||
|
||
.role-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 20px;
|
||
background: linear-gradient(120deg, #ff5f5c, #ff8c8a);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 12px;
|
||
box-shadow: 0 4px 12px rgba(255, 95, 92, 0.2);
|
||
}
|
||
|
||
.role-icon i {
|
||
font-size: 20px;
|
||
color: white;
|
||
}
|
||
|
||
.role-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.role-title {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.role-desc {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
.leader-apply-btn {
|
||
margin-top: 8px;
|
||
border-radius: 12px;
|
||
height: 48px;
|
||
border-color: #ff5f5c;
|
||
color: #ff5f5c;
|
||
}
|
||
|
||
/* 弹窗内容样式 */
|
||
.dialog-content {
|
||
display: flex;
|
||
padding: 16px 0;
|
||
}
|
||
|
||
.dialog-icon {
|
||
margin-right: 16px;
|
||
font-size: 24px;
|
||
color: #ff9800;
|
||
}
|
||
|
||
.dialog-text p {
|
||
margin: 0 0 8px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
}
|
||
</style>
|