v0.2o
zhanghongyu04 2025-03-25 19:36:31 +08:00
parent 336f062650
commit b5e4d4b477
17 changed files with 885 additions and 828 deletions

View File

@ -21,5 +21,6 @@
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.2.0"
}
},
"packageManager": "pnpm@10.6.5+sha512.cdf928fca20832cd59ec53826492b7dc25dc524d4370b6b4adbf65803d32efaa6c1c88147c0ae4e8d579a6c9eec715757b50d4fa35eea179d868eada4ed043af"
}

View File

@ -20,7 +20,7 @@ export function getActivity(id) {
// 创建活动
export function createActivity(data) {
return request({
url: '/activity',
url: '/activity/create',
method: 'post',
data
})

View File

@ -52,6 +52,11 @@ export const studentRoutes = [
path: 'notice',
component: () => import('@/views/notice/index.vue'),
meta: { title: '公告', roles: ['PARTICIPANT'] }
},
{
path: 'profile',
component: () => import('@/views/profile/index.vue'),
meta: { title: '个人中心', roles: ['PARTICIPANT'] }
}
]
}
@ -87,6 +92,11 @@ export const adminRoutes = [
path: 'edit/:id',
component: () => import('@/views/admin/activity/form.vue'),
meta: { title: '编辑活动' }
},
{
path: 'approval',
component: () => import('@/views/admin/activity/approval.vue'),
meta: { title: '活动审批' }
}
]
},
@ -109,6 +119,11 @@ export const adminRoutes = [
path: 'edit/:id',
component: () => import('@/views/admin/club/form.vue'),
meta: { title: '编辑社团' }
},
{
path: 'members',
component: () => import('@/views/admin/club/members.vue'),
meta: { title: '成员管理' }
}
]
},
@ -117,6 +132,11 @@ export const adminRoutes = [
component: () => import('@/views/admin/user/index.vue'),
meta: { title: '人员管理', roles: ['ADMIN', 'COLLEGE_ADMIN'] },
children: [
{
path: 'list',
component: () => import('@/views/admin/user/list.vue'),
meta: { title: '用户列表' }
},
{
path: 'password',
component: () => import('@/views/profile/password.vue'),

View File

@ -1,114 +1,64 @@
<!-- 社团成员管理页面 -->
<template>
<div class="club-members">
<!-- 社团信息卡片 -->
<el-card class="club-card">
<div class="club-info">
<div class="club-name">{{ clubInfo.name }}</div>
<div class="club-type">
<el-tag :type="getClubTypeTag(clubInfo.type)">
{{ getClubTypeLabel(clubInfo.type) }}
</el-tag>
</div>
<div class="club-leader">负责人{{ clubInfo.leader }}</div>
<div class="club-count">成员数量{{ clubInfo.memberCount }}</div>
</div>
</el-card>
<!-- 成员列表卡片 -->
<el-card class="member-card">
<div class="app-container">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>成员列表</span>
<div class="header-operations">
<el-button type="primary" :icon="Plus" @click="handleAddMember"></el-button>
<el-button
type="danger"
:icon="Delete"
:disabled="!selectedMembers.length"
@click="handleBatchRemove"
>
批量移除
</el-button>
</div>
<span>成员管理</span>
<el-button type="primary" @click="handleAdd"></el-button>
</div>
</template>
<!-- 搜索工具栏 -->
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="姓名">
<el-input
v-model="queryParams.name"
placeholder="请输入姓名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<!-- 搜索栏 -->
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
<el-form-item label="学号">
<el-input
v-model="queryParams.studentId"
placeholder="请输入学号"
clearable
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.schoolId" placeholder="请输入学号" clearable />
</el-form-item>
<el-form-item label="角色">
<el-select v-model="queryParams.role" placeholder="请选择角色" clearable>
<el-option label="普通成员" value="member" />
<el-option label="管理员" value="admin" />
<el-option label="负责人" value="leader" />
<el-form-item label="姓名">
<el-input v-model="queryParams.name" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="活跃" value="active" />
<el-option label="非活跃" value="inactive" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"></el-button>
<el-button :icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" @click="handleQuery"></el-button>
<el-button @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<!-- 成员列表 -->
<el-table
v-loading="loading"
:data="memberList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="姓名" prop="name" width="120" />
<el-table-column label="学号" prop="studentId" width="120" />
<el-table-column label="性别" prop="gender" width="80">
<template #default="{ row }">
{{ row.gender === 'male' ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column label="学院" prop="college" width="150" />
<el-table-column label="专业" prop="major" width="150" />
<el-table-column label="角色" prop="role" width="100">
<template #default="{ row }">
<el-tag :type="getRoleType(row.role)">
{{ getRoleLabel(row.role) }}
<!-- 表格 -->
<el-table :data="memberList" style="width: 100%" v-loading="loading">
<el-table-column prop="schoolId" label="学号" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="joinDate" label="加入时间" />
<el-table-column prop="status" label="状态">
<template #default="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
{{ scope.row.status === 'active' ? '活跃' : '非活跃' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="加入时间" prop="joinTime" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button
type="primary"
link
:icon="Edit"
@click="handleUpdateRole(row)"
>修改角色</el-button>
size="small"
@click="handleEdit(scope.row)"
>编辑</el-button>
<el-button
type="danger"
link
:icon="Delete"
@click="handleRemove(row)"
size="small"
@click="handleRemove(scope.row)"
>移除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<div class="pagination-container">
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
@ -121,94 +71,31 @@
</div>
</el-card>
<!-- 添加成员对话框 -->
<!-- 添加/编辑成员对话框 -->
<el-dialog
v-model="addDialogVisible"
title="添加成员"
v-model="dialogVisible"
:title="dialogType === 'add' ? '添加成员' : '编辑成员'"
width="500px"
append-to-body
>
<el-form
ref="addFormRef"
:model="addForm"
:rules="addFormRules"
label-width="100px"
>
<el-form-item label="学号" prop="studentId">
<el-input v-model="addForm.studentId" placeholder="请输入学号" />
<el-form :model="memberForm" label-width="100px">
<el-form-item label="学号" v-if="dialogType === 'add'">
<el-input v-model="memberForm.schoolId" placeholder="请输入学号" />
</el-form-item>
<el-form-item label="角色" prop="role">
<el-select v-model="addForm.role" placeholder="请选择角色" style="width: 100%">
<el-option label="普通成员" value="member" />
<el-option label="管理员" value="admin" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="addLoading" @click="confirmAdd">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 修改角色对话框 -->
<el-dialog
v-model="roleDialogVisible"
title="修改角色"
width="500px"
append-to-body
>
<el-form
ref="roleFormRef"
:model="roleForm"
:rules="roleFormRules"
label-width="100px"
>
<el-form-item label="姓名">
<span>{{ roleForm.name }}</span>
<el-input v-model="memberForm.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="学号">
<span>{{ roleForm.studentId }}</span>
</el-form-item>
<el-form-item label="角色" prop="role">
<el-select v-model="roleForm.role" placeholder="请选择角色" style="width: 100%">
<el-option label="普通成员" value="member" />
<el-option label="管理员" value="admin" />
<el-option label="负责人" value="leader" />
<el-form-item label="状态">
<el-select v-model="memberForm.status" placeholder="请选择状态">
<el-option label="活跃" value="active" />
<el-option label="非活跃" value="inactive" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="roleDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="roleLoading" @click="confirmUpdateRole">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 批量移除确认框 -->
<el-dialog
v-model="removeDialogVisible"
title="确认移除"
width="400px"
append-to-body
>
<div class="dialog-content">
<el-icon class="warning-icon"><Warning /></el-icon>
<span>确定要移除选中的 {{ selectedMembers.length }} 个成员吗</span>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="removeDialogVisible = false">取消</el-button>
<el-button type="danger" :loading="removeLoading" @click="confirmBatchRemove">
确定
</el-button>
</div>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm"></el-button>
</span>
</template>
</el-dialog>
</div>
@ -216,96 +103,38 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
Search,
Refresh,
Plus,
Delete,
Edit,
Warning
} from '@element-plus/icons-vue'
import {
getClubDetail,
getClubMembers,
addClubMember,
removeClubMember,
updateClubMemberRole
} from '@/api/club'
const router = useRouter()
const route = useRoute()
const loading = ref(false)
const addLoading = ref(false)
const roleLoading = ref(false)
const removeLoading = ref(false)
const clubInfo = ref({})
const memberList = ref([])
const total = ref(0)
const selectedMembers = ref([])
const addDialogVisible = ref(false)
const roleDialogVisible = ref(false)
const removeDialogVisible = ref(false)
const addFormRef = ref(null)
const roleFormRef = ref(null)
import { getClubMembers, addClubMember, updateClubMember, removeClubMember } from '@/api/club'
//
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
schoolId: '',
name: '',
studentId: '',
role: ''
status: ''
})
//
const addForm = reactive({
studentId: '',
role: 'member'
})
//
const memberList = ref([])
const total = ref(0)
const loading = ref(false)
//
const roleForm = reactive({
memberId: '',
//
const dialogVisible = ref(false)
const dialogType = ref('')
const memberForm = reactive({
schoolId: '',
name: '',
studentId: '',
role: ''
status: 'active'
})
//
const addFormRules = {
studentId: [
{ required: true, message: '请输入学号', trigger: 'blur' },
{ pattern: /^\d{8}$/, message: '请输入8位数字学号', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
]
}
const roleFormRules = {
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
]
}
//
const getDetail = async () => {
try {
const res = await getClubDetail(route.params.id)
clubInfo.value = res.data
} catch (error) {
console.error('获取社团详情失败:', error)
}
}
//
const getList = async () => {
loading.value = true
try {
const res = await getClubMembers(route.params.id, queryParams)
memberList.value = res.data.list
const res = await getClubMembers(queryParams)
memberList.value = res.data.records
total.value = res.data.total
} catch (error) {
console.error('获取成员列表失败:', error)
@ -314,264 +143,101 @@ const getList = async () => {
}
}
//
//
const handleQuery = () => {
queryParams.pageNum = 1
getList()
}
//
//
const resetQuery = () => {
queryParams.schoolId = ''
queryParams.name = ''
queryParams.studentId = ''
queryParams.role = ''
queryParams.status = ''
handleQuery()
}
//
const handleSelectionChange = (selection) => {
selectedMembers.value = selection
}
//
const handleAddMember = () => {
addForm.studentId = ''
addForm.role = 'member'
addDialogVisible.value = true
}
//
const confirmAdd = async () => {
if (!addFormRef.value) return
await addFormRef.value.validate(async (valid) => {
if (!valid) return
addLoading.value = true
try {
await addClubMember(route.params.id, addForm)
ElMessage.success('添加成功')
addDialogVisible.value = false
getList()
} catch (error) {
console.error('添加成员失败:', error)
} finally {
addLoading.value = false
}
})
}
//
const handleUpdateRole = (row) => {
roleForm.memberId = row.id
roleForm.name = row.name
roleForm.studentId = row.studentId
roleForm.role = row.role
roleDialogVisible.value = true
}
//
const confirmUpdateRole = async () => {
if (!roleFormRef.value) return
await roleFormRef.value.validate(async (valid) => {
if (!valid) return
roleLoading.value = true
try {
await updateClubMemberRole(route.params.id, roleForm.memberId, {
role: roleForm.role
})
ElMessage.success('修改成功')
roleDialogVisible.value = false
getList()
} catch (error) {
console.error('修改角色失败:', error)
} finally {
roleLoading.value = false
}
})
}
//
const handleRemove = (row) => {
ElMessageBox.confirm(
`确定要移除成员"${row.name}"吗?`,
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(async () => {
try {
await removeClubMember(route.params.id, row.id)
ElMessage.success('移除成功')
getList()
} catch (error) {
console.error('移除成员失败:', error)
}
})
}
//
const handleBatchRemove = () => {
removeDialogVisible.value = true
}
//
const confirmBatchRemove = async () => {
if (!selectedMembers.value.length) return
removeLoading.value = true
try {
const memberIds = selectedMembers.value.map(item => item.id)
await Promise.all(
memberIds.map(id => removeClubMember(route.params.id, id))
)
ElMessage.success('移除成功')
removeDialogVisible.value = false
getList()
} catch (error) {
console.error('批量移除成员失败:', error)
} finally {
removeLoading.value = false
}
}
//
//
const handleSizeChange = (val) => {
queryParams.pageSize = val
getList()
}
//
//
const handleCurrentChange = (val) => {
queryParams.pageNum = val
getList()
}
//
const getClubTypeTag = (type) => {
const map = {
academic: 'success',
sports: 'warning',
art: 'info',
charity: 'danger',
other: ''
}
return map[type] || ''
//
const handleAdd = () => {
dialogType.value = 'add'
memberForm.schoolId = ''
memberForm.name = ''
memberForm.status = 'active'
dialogVisible.value = true
}
//
const getClubTypeLabel = (type) => {
const map = {
academic: '学术类',
sports: '体育类',
art: '艺术类',
charity: '公益类',
other: '其他'
}
return map[type] || '其他'
//
const handleEdit = (row) => {
dialogType.value = 'edit'
Object.assign(memberForm, row)
dialogVisible.value = true
}
//
const getRoleType = (role) => {
const map = {
leader: 'danger',
admin: 'warning',
member: 'info'
}
return map[role] || ''
//
const handleRemove = (row) => {
ElMessageBox.confirm('确认移除该成员吗?', '提示', {
type: 'warning'
}).then(async () => {
try {
await removeClubMember(row.id)
ElMessage.success('移除成功')
getList()
} catch (error) {
console.error('移除成员失败:', error)
ElMessage.error('移除失败')
}
})
}
//
const getRoleLabel = (role) => {
const map = {
leader: '负责人',
admin: '管理员',
member: '普通成员'
//
const submitForm = async () => {
try {
if (dialogType.value === 'add') {
await addClubMember(memberForm)
ElMessage.success('添加成功')
} else {
await updateClubMember(memberForm)
ElMessage.success('更新成功')
}
dialogVisible.value = false
getList()
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
return map[role] || '未知'
}
onMounted(() => {
getDetail()
getList()
})
</script>
<style scoped>
.club-members {
.app-container {
padding: 20px;
}
.club-card {
margin-bottom: 20px;
}
.club-info {
display: flex;
align-items: center;
gap: 20px;
}
.club-name {
font-size: 18px;
font-weight: bold;
}
.club-type {
margin-left: 10px;
}
.club-leader,
.club-count {
color: #606266;
}
.member-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-operations {
display: flex;
gap: 10px;
}
.search-form {
margin-bottom: 20px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.pagination {
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.dialog-content {
display: flex;
align-items: center;
gap: 10px;
}
.warning-icon {
font-size: 24px;
color: #e6a23c;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@ -1,366 +1,306 @@
<!-- 个人中心页面 -->
<template>
<div class="profile">
<!-- 个人信息卡片 -->
<div class="app-container">
<el-row :gutter="20">
<el-col :span="6">
<!-- 左侧个人信息卡片 -->
<el-col :span="8">
<el-card class="profile-card">
<div class="profile-header">
<el-avatar :size="100" :src="userInfo.avatar" />
<h3>{{ userInfo.name }}</h3>
<p>{{ userInfo.studentId }}</p>
<h2 class="username">{{ userInfo.nickName }}</h2>
<p class="school-id">学号{{ userInfo.schoolId }}</p>
</div>
<div class="profile-info">
<div class="info-item">
<label>学院</label>
<span>{{ userInfo.college }}</span>
<i class="el-icon-user"></i>
<span>姓名{{ userInfo.userName }}</span>
</div>
<div class="info-item">
<label>专业</label>
<span>{{ userInfo.major }}</span>
<i class="el-icon-college"></i>
<span>学院{{ userInfo.collegeName }}</span>
</div>
<div class="info-item">
<label>年级</label>
<span>{{ userInfo.grade }}</span>
<i class="el-icon-major"></i>
<span>专业{{ userInfo.major }}</span>
</div>
<div class="info-item">
<label>手机</label>
<span>{{ userInfo.phone }}</span>
</div>
<div class="info-item">
<label>邮箱</label>
<span>{{ userInfo.email }}</span>
<i class="el-icon-email"></i>
<span>邮箱{{ userInfo.email }}</span>
</div>
</div>
<div class="profile-actions">
<el-button type="primary" @click="handleEdit"></el-button>
<el-button type="primary" @click="handleEditProfile"></el-button>
<el-button @click="handleChangePassword"></el-button>
</div>
</el-card>
</el-col>
<el-col :span="18">
<el-card>
<el-tabs v-model="activeTab">
<!-- 我的社团 -->
<el-tab-pane label="我的社团" name="clubs">
<div class="my-clubs">
<el-empty v-if="myClubs.length === 0" description="暂无加入的社团" />
<el-row v-else :gutter="20">
<el-col :span="8" v-for="club in myClubs" :key="club.id">
<el-card class="club-card" shadow="hover">
<div class="club-cover">
<el-image :src="club.cover" fit="cover">
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
</div>
<div class="club-info">
<h4>{{ club.name }}</h4>
<p class="club-role">{{ club.role }}</p>
<p class="club-join-time">加入时间{{ club.joinTime }}</p>
<div class="club-actions">
<el-button type="primary" link @click="viewClubDetail(club.id)">
查看详情
</el-button>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-tab-pane>
<!-- 右侧活动记录 -->
<el-col :span="16">
<el-card class="activity-card">
<template #header>
<div class="card-header">
<span>我的活动</span>
<el-radio-group v-model="activityType" size="small">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="registered">已报名</el-radio-button>
<el-radio-button label="attended">已参加</el-radio-button>
</el-radio-group>
</div>
</template>
<!-- 我的活动 -->
<el-tab-pane label="我的活动" name="activities">
<div class="my-activities">
<el-empty v-if="myActivities.length === 0" description="暂无参与的活动" />
<el-timeline v-else>
<el-timeline-item
v-for="activity in myActivities"
:key="activity.id"
:timestamp="activity.startTime"
:type="getActivityType(activity.status)"
>
<el-card class="activity-card">
<div class="activity-info">
<h4>{{ activity.name }}</h4>
<p>{{ activity.description }}</p>
<div class="activity-meta">
<span>
<el-icon><Location /></el-icon>
{{ activity.location }}
</span>
<el-tag size="small" :type="getActivityStatusType(activity.status)">
{{ activity.status }}
</el-tag>
</div>
</div>
<div class="activity-actions">
<el-button type="primary" link @click="viewActivityDetail(activity.id)">
查看详情
</el-button>
<el-button
type="danger"
link
@click="cancelActivity(activity.id)"
v-if="activity.status === '报名中'"
>
取消报名
</el-button>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
</el-tabs>
<el-table :data="activityList" style="width: 100%" v-loading="loading">
<el-table-column prop="title" label="活动名称" />
<el-table-column prop="clubName" label="主办社团" />
<el-table-column prop="startTime" label="开始时间" />
<el-table-column prop="status" label="状态">
<template #default="scope">
<el-tag :type="getActivityStatusType(scope.row.status)">
{{ getActivityStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button
type="primary"
link
@click="handleViewActivity(scope.row)"
>查看</el-button>
<el-button
v-if="scope.row.status === 'REGISTERED'"
type="danger"
link
@click="handleCancelRegistration(scope.row)"
>取消报名</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[5, 10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
</el-col>
</el-row>
<!-- 编辑资料对话框 -->
<el-dialog
v-model="editDialog.visible"
title="编辑个人资料"
v-model="editDialogVisible"
title="编辑资料"
width="500px"
append-to-body
>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="80px">
<el-form :model="editForm" label-width="100px">
<el-form-item label="头像">
<el-upload
class="avatar-uploader"
action="/api/file/upload"
action="/api/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<el-avatar v-if="editForm.avatar" :src="editForm.avatar" :size="100" />
<img v-if="editForm.avatar" :src="editForm.avatar" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="editForm.name" />
<el-form-item label="昵称">
<el-input v-model="editForm.nickName" />
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="editForm.phone" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-form-item label="邮箱">
<el-input v-model="editForm.email" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelEdit"> </el-button>
<el-button type="primary" @click="submitEdit"> </el-button>
</div>
<span class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitEditProfile"></el-button>
</span>
</template>
</el-dialog>
<!-- 修改密码对话框 -->
<change-password v-model:visible="passwordDialogVisible" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ref, reactive, onMounted, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Picture, Location, Plus } from '@element-plus/icons-vue'
import { getUserInfo, updateUserInfo } from '@/api/user'
import { getMyClubs } from '@/api/club/club'
import { getMyActivities, cancelActivitySignUp } from '@/api/activity/activity'
const router = useRouter()
//
const activeTab = ref('clubs')
import { Plus } from '@element-plus/icons-vue'
import { getUserProfile, updateUserProfile, getUserActivities, cancelActivityRegistration } from '@/api/user'
import ChangePassword from './components/ChangePassword.vue'
//
const userInfo = ref({
id: null,
name: '',
studentId: '',
college: '',
major: '',
grade: '',
phone: '',
email: '',
avatar: ''
const userInfo = ref({})
//
const activityList = ref([])
const total = ref(0)
const loading = ref(false)
const activityType = ref('all')
//
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
type: 'all'
})
//
const myClubs = ref([])
//
const myActivities = ref([])
//
const editDialog = reactive({
visible: false
})
//
const editDialogVisible = ref(false)
const editForm = reactive({
name: '',
phone: '',
email: '',
avatar: ''
avatar: '',
nickName: '',
email: ''
})
//
const editRules = {
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号码', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
]
}
//
const getActivityStatusType = (status) => {
const statusMap = {
'未开始': 'info',
'报名中': 'success',
'进行中': 'warning',
'已结束': 'info',
'已取消': 'danger'
}
return statusMap[status] || 'info'
}
// 线
const getActivityType = (status) => {
const typeMap = {
'未开始': 'primary',
'报名中': 'success',
'进行中': 'warning',
'已结束': 'info'
}
return typeMap[status] || 'info'
}
//
const passwordDialogVisible = ref(false)
//
const fetchUserInfo = async () => {
const getUserInfo = async () => {
try {
const response = await getUserInfo()
userInfo.value = response.data
const res = await getUserProfile()
userInfo.value = res.data
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
//
const fetchMyClubs = async () => {
//
const getList = async () => {
loading.value = true
try {
const response = await getMyClubs()
myClubs.value = response.data
const res = await getUserActivities(queryParams)
activityList.value = res.data.records
total.value = res.data.total
} catch (error) {
console.error('获取我的社团列表失败:', error)
console.error('获取活动列表失败:', error)
} finally {
loading.value = false
}
}
//
const fetchMyActivities = async () => {
//
const handleSizeChange = (val) => {
queryParams.pageSize = val
getList()
}
//
const handleCurrentChange = (val) => {
queryParams.pageNum = val
getList()
}
//
const getActivityStatusType = (status) => {
const statusMap = {
REGISTERED: 'warning',
ATTENDED: 'success',
CANCELLED: 'info'
}
return statusMap[status] || 'info'
}
//
const getActivityStatusText = (status) => {
const statusMap = {
REGISTERED: '已报名',
ATTENDED: '已参加',
CANCELLED: '已取消'
}
return statusMap[status] || '未知'
}
//
const handleEditProfile = () => {
Object.assign(editForm, userInfo.value)
editDialogVisible.value = true
}
//
const submitEditProfile = async () => {
try {
const response = await getMyActivities()
myActivities.value = response.data
await updateUserProfile(editForm)
ElMessage.success('更新成功')
editDialogVisible.value = false
getUserInfo()
} catch (error) {
console.error('获取我的活动列表失败:', error)
console.error('更新用户信息失败:', error)
ElMessage.error('更新失败')
}
}
//
const viewClubDetail = (clubId) => {
router.push(`/club/detail/${clubId}`)
//
const handleChangePassword = () => {
passwordDialogVisible.value = true
}
//
const viewActivityDetail = (activityId) => {
router.push(`/activity/detail/${activityId}`)
//
const handleViewActivity = (row) => {
// TODO:
}
//
const cancelActivity = async (activityId) => {
try {
await ElMessageBox.confirm('确定要取消报名该活动吗?', '提示', {
type: 'warning'
})
await cancelActivitySignUp(activityId)
ElMessage.success('取消报名成功')
fetchMyActivities()
} catch (error) {
if (error !== 'cancel') {
//
const handleCancelRegistration = (row) => {
ElMessageBox.confirm('确认取消报名该活动吗?', '提示', {
type: 'warning'
}).then(async () => {
try {
await cancelActivityRegistration(row.id)
ElMessage.success('取消报名成功')
getList()
} catch (error) {
console.error('取消报名失败:', error)
ElMessage.error('取消报名失败')
}
}
}
//
const handleEdit = () => {
editDialog.visible = true
Object.assign(editForm, {
name: userInfo.value.name,
phone: userInfo.value.phone,
email: userInfo.value.email,
avatar: userInfo.value.avatar
})
}
//
const handleAvatarSuccess = (response) => {
editForm.avatar = response.data
const handleAvatarSuccess = (res) => {
editForm.avatar = res.data
}
//
//
const beforeAvatarUpload = (file) => {
const isJPG = file.type === 'image/jpeg'
const isPNG = file.type === 'image/png'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG && !isPNG) {
ElMessage.error('头像只能是 JPG 或 PNG 格式!')
return false
if (!isJPG) {
ElMessage.error('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
ElMessage.error('头像大小不能超过 2MB!')
return false
ElMessage.error('上传头像图片大小不能超过 2MB!')
}
return true
return isJPG && isLt2M
}
//
const submitEdit = async () => {
try {
await updateUserInfo(editForm)
ElMessage.success('更新成功')
editDialog.visible = false
fetchUserInfo()
} catch (error) {
console.error('更新失败:', error)
}
}
//
const cancelEdit = () => {
editDialog.visible = false
}
//
watch(activityType, (val) => {
queryParams.type = val
queryParams.pageNum = 1
getList()
})
onMounted(() => {
fetchUserInfo()
fetchMyClubs()
fetchMyActivities()
getUserInfo()
getList()
})
</script>
<style scoped>
.profile {
.app-container {
padding: 20px;
}
@ -369,126 +309,82 @@ onMounted(() => {
}
.profile-header {
padding: 20px 0;
margin-bottom: 20px;
}
.profile-header h3 {
margin: 10px 0 5px;
font-size: 18px;
color: #303133;
.username {
margin: 10px 0;
font-size: 20px;
}
.profile-header p {
margin: 0;
font-size: 14px;
color: #909399;
.school-id {
color: #666;
margin: 5px 0;
}
.profile-info {
text-align: left;
padding: 20px;
}
.info-item {
margin-bottom: 10px;
font-size: 14px;
}
.info-item label {
color: #909399;
margin-right: 5px;
}
.info-item span {
color: #606266;
}
.profile-actions {
padding: 0 20px 20px;
}
.club-card {
margin-bottom: 20px;
}
.club-cover {
height: 150px;
overflow: hidden;
.info-item {
margin: 10px 0;
display: flex;
align-items: center;
}
.club-cover .el-image {
width: 100%;
height: 100%;
.info-item i {
margin-right: 10px;
color: #409EFF;
}
.club-info {
padding: 15px;
.profile-actions {
display: flex;
flex-direction: column;
gap: 10px;
}
.club-info h4 {
margin: 0 0 10px;
font-size: 16px;
color: #303133;
}
.club-role {
margin: 0 0 5px;
font-size: 14px;
color: #606266;
}
.club-join-time {
margin: 0 0 10px;
font-size: 12px;
color: #909399;
}
.activity-card {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.activity-info {
flex: 1;
}
.activity-info h4 {
margin: 0 0 10px;
font-size: 16px;
color: #303133;
}
.activity-info p {
margin: 0 0 10px;
font-size: 14px;
color: #606266;
}
.activity-meta {
.pagination-container {
margin-top: 20px;
display: flex;
align-items: center;
gap: 20px;
font-size: 14px;
color: #909399;
justify-content: flex-end;
}
.avatar-uploader {
text-align: center;
}
.avatar-uploader .avatar {
width: 100px;
height: 100px;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
border: 1px dashed #d9d9d9;
border-radius: 50%;
}
.avatar-uploader-icon:hover {
border-color: #409eff;
line-height: 100px;
}
</style>

View File

@ -1,6 +1,7 @@
package com.bruce.sams.controller.ams;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bruce.sams.domain.ams.Activity;
import com.bruce.sams.domain.entity.AjaxResult;
import com.bruce.sams.service.ActivityService;
@ -18,6 +19,54 @@ public class ActivityController {
@Autowired
private ActivityService activityService;
/**
*
*/
@GetMapping("/list")
public AjaxResult listActivities(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String title,
@RequestParam(required = false) String status,
@RequestParam(required = false) Long collegeId,
@RequestParam(required = false) Long clubId) {
Page<Activity> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Activity> queryWrapper = new LambdaQueryWrapper<>();
// 添加查询条件
if (title != null && !title.isEmpty()) {
queryWrapper.like(Activity::getTitle, title);
}
if (status != null && !status.isEmpty()) {
queryWrapper.eq(Activity::getStatus, status);
}
if (collegeId != null) {
queryWrapper.eq(Activity::getCollegeId, collegeId);
}
if (clubId != null) {
queryWrapper.eq(Activity::getClubId, clubId);
}
// 按创建时间降序排序
queryWrapper.orderByDesc(Activity::getCreatedAt);
Page<Activity> result = activityService.page(page, queryWrapper);
return AjaxResult.success(result);
}
/**
*
*/
@GetMapping("/{actId}")
public AjaxResult getActivityDetail(@PathVariable Long actId) {
Activity activity = activityService.getActivityById(actId);
if (activity == null) {
return AjaxResult.error("活动不存在");
}
return AjaxResult.success(activity);
}
/**
*
*/
@ -28,6 +77,16 @@ public class ActivityController {
return AjaxResult.success("活动创建成功,等待审批");
}
/**
*
*/
@PreAuthorize("hasRole('CLUB_ADMIN')")
@PutMapping("/update")
public AjaxResult updateActivity(@RequestBody Activity activity) {
activityService.updateActivity(activity);
return AjaxResult.success("活动信息已更新");
}
/**
*
*/
@ -37,4 +96,12 @@ public class ActivityController {
activityService.cancelActivity(actId);
return AjaxResult.success("活动已取消");
}
/**
*
*/
@GetMapping("/stats")
public AjaxResult getActivityStats() {
return AjaxResult.success(activityService.getActivityStats());
}
}

View File

@ -1,5 +1,7 @@
package com.bruce.sams.controller.sms;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bruce.sams.domain.entity.AjaxResult;
import com.bruce.sams.domain.sms.Club;
import com.bruce.sams.service.ClubService;
@ -8,7 +10,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
*
*
*/
@RestController
@RequestMapping("/api/admin/club")
@ -18,50 +20,85 @@ public class ClubController {
private ClubService clubService;
/**
* &
*
*/
@PreAuthorize("hasRole('ADMIN') or hasRole('COLLEGE_ADMIN') or hasRole('DEPARTMENT_ADMIN')")
@PostMapping("/add")
public AjaxResult addClub(@RequestBody Club club) {
clubService.addClub(club);
return AjaxResult.success("社团添加成功");
@GetMapping("/list")
public AjaxResult listClubs(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String clubName,
@RequestParam(required = false) String category,
@RequestParam(required = false) Long collegeId) {
Page<Club> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Club> queryWrapper = new LambdaQueryWrapper<>();
// 添加查询条件
if (clubName != null && !clubName.isEmpty()) {
queryWrapper.like(Club::getClubName, clubName);
}
if (category != null && !category.isEmpty()) {
queryWrapper.eq(Club::getCategory, category);
}
if (collegeId != null) {
queryWrapper.eq(Club::getCollegeId, collegeId);
}
// 按创建时间降序排序
queryWrapper.orderByDesc(Club::getCreatedAt);
Page<Club> result = clubService.page(page, queryWrapper);
return AjaxResult.success(result);
}
/**
*
*
*/
@PreAuthorize("hasRole('ADMIN') or hasRole('CLUB_ADMIN')")
@GetMapping("/{clubId}")
public AjaxResult getClubDetail(@PathVariable Long clubId) {
Club club = clubService.getClubById(clubId);
if (club == null) {
return AjaxResult.error("社团不存在");
}
return AjaxResult.success(club);
}
/**
*
*/
@PreAuthorize("hasRole('ADMIN') or hasRole('COLLEGE_ADMIN')")
@PostMapping("/create")
public AjaxResult createClub(@RequestBody Club club) {
clubService.createClub(club);
return AjaxResult.success("社团创建成功");
}
/**
*
*/
@PreAuthorize("hasRole('ADMIN') or hasRole('COLLEGE_ADMIN')")
@PutMapping("/update")
public AjaxResult updateClub(@RequestBody Club club) {
clubService.updateClub(club);
return AjaxResult.success("社团信息更新成功");
return AjaxResult.success("社团信息更新");
}
/**
*
*
*/
@PreAuthorize("hasRole('ADMIN') or hasRole('COLLEGE_ADMIN') or hasRole('DEPARTMENT_ADMIN')")
@DeleteMapping("/delete/{clubId}")
public AjaxResult deleteClub(@PathVariable Long clubId) {
clubService.deleteClub(clubId);
return AjaxResult.success("社团删除成功");
@GetMapping("/{clubId}/members")
public AjaxResult getClubMembers(
@PathVariable Long clubId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return AjaxResult.success(clubService.getClubMembers(clubId, pageNum, pageSize));
}
/**
*
*
*/
@PreAuthorize("hasRole('ADMIN') or hasRole('COLLEGE_ADMIN') or hasRole('DEPARTMENT_ADMIN') or hasRole('CLUB_ADMIN')")
@GetMapping("/{clubId}")
public AjaxResult getClubById(@PathVariable Long clubId) {
return AjaxResult.success(clubService.getClubById(clubId));
}
/**
*
*/
@PreAuthorize("hasRole('ADMIN') or hasRole('COLLEGE_ADMIN') or hasRole('DEPARTMENT_ADMIN') or hasRole('CLUB_ADMIN')")
@GetMapping("/list")
public AjaxResult listClubs(@RequestParam(required = false) String keyword, @RequestParam Long userId) {
return AjaxResult.success(clubService.listClubs(keyword, userId));
@GetMapping("/stats")
public AjaxResult getClubStats() {
return AjaxResult.success(clubService.getClubStats());
}
}

View File

@ -1,5 +1,7 @@
package com.bruce.sams.controller.sys;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bruce.sams.common.utils.FileUploadUtil;
import com.bruce.sams.domain.entity.AjaxResult;
import com.bruce.sams.domain.sys.User;
@ -7,20 +9,130 @@ import com.bruce.sams.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* -
*
*/
@RestController
@RequestMapping("/api/user")
@RequestMapping("/api/admin/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
/**
*
*/
@GetMapping("/list")
public AjaxResult listUsers(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String userName,
@RequestParam(required = false) String schoolId,
@RequestParam(required = false) Long collegeId) {
Page<User> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
// 添加查询条件
if (userName != null && !userName.isEmpty()) {
queryWrapper.like(User::getUserName, userName);
}
if (schoolId != null && !schoolId.isEmpty()) {
queryWrapper.eq(User::getSchoolId, schoolId);
}
if (collegeId != null) {
queryWrapper.eq(User::getCollegeId, collegeId);
}
// 按创建时间降序排序
queryWrapper.orderByDesc(User::getCreatedAt);
Page<User> result = userService.page(page, queryWrapper);
return AjaxResult.success(result);
}
/**
*
*/
@GetMapping("/{userId}")
public AjaxResult getUserDetail(@PathVariable Long userId) {
User user = userService.getUserById(userId);
if (user == null) {
return AjaxResult.error("用户不存在");
}
return AjaxResult.success(user);
}
/**
*
*/
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/update")
public AjaxResult updateUser(@RequestBody User user) {
userService.updateUser(user);
return AjaxResult.success("用户信息已更新");
}
/**
*
*/
@PutMapping("/password")
public AjaxResult updatePassword(
@RequestParam Long userId,
@RequestParam String oldPassword,
@RequestParam String newPassword) {
User user = userService.getUserById(userId);
if (user == null) {
return AjaxResult.error("用户不存在");
}
// 验证旧密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
return AjaxResult.error("原密码错误");
}
// 更新密码
user.setPassword(passwordEncoder.encode(newPassword));
userService.updateUser(user);
return AjaxResult.success("密码修改成功");
}
/**
*
*/
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/reset-password/{userId}")
public AjaxResult resetPassword(@PathVariable Long userId) {
User user = userService.getUserById(userId);
if (user == null) {
return AjaxResult.error("用户不存在");
}
// 重置为默认密码
user.setPassword(passwordEncoder.encode("123456"));
userService.updateUser(user);
return AjaxResult.success("密码已重置为默认密码");
}
/**
*
*/
@GetMapping("/stats")
public AjaxResult getUserStats() {
return AjaxResult.success(userService.getUserStats());
}
/**
*
*
@ -86,5 +198,4 @@ public class UserController {
return avatarUrl;
}
}

View File

@ -1,8 +1,13 @@
package com.bruce.sams.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bruce.sams.domain.sms.ClubUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Map;
/**
* @author bruce
@ -13,6 +18,20 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ClubUserMapper extends BaseMapper<ClubUser> {
/**
*
*
* @param page
* @param clubId ID
* @return
*/
@Select("SELECT u.user_id, u.user_name, u.nick_name, u.avatar, u.email, " +
"cu.join_date, cu.is_active " +
"FROM sys_user u " +
"JOIN sms_club_user cu ON u.user_id = cu.user_id " +
"WHERE cu.club_id = #{clubId} " +
"ORDER BY cu.join_date DESC")
Page<Map<String, Object>> selectClubMembers(Page<Map<String, Object>> page, @Param("clubId") Long clubId);
}

View File

@ -1,7 +1,10 @@
package com.bruce.sams.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bruce.sams.domain.sys.User;
import com.bruce.sams.entity.User;
import com.bruce.sams.vo.ActivityVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@ -43,6 +46,21 @@ public interface UserMapper extends BaseMapper<User> {
@Update("UPDATE sys_user SET avatar = #{avatarUrl} WHERE user_id = #{userId}")
void updateAvatar(Long userId, String avatarUrl);
/**
*
*/
Page<ActivityVO> selectUserActivities(Page<ActivityVO> page, @Param("userId") Long userId, @Param("type") String type);
/**
*
*/
boolean checkActivityRegistration(@Param("userId") Long userId, @Param("activityId") Long activityId);
/**
*
*/
void cancelActivityRegistration(@Param("userId") Long userId, @Param("activityId") Long activityId);
}

View File

@ -3,6 +3,8 @@ package com.bruce.sams.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bruce.sams.domain.ams.Activity;
import java.util.Map;
/**
* @author bruce
* @description ams_activity()Service
@ -36,4 +38,11 @@ public interface ActivityService extends IService<Activity> {
* @return
*/
Activity getActivityById(Long actId);
/**
*
*
* @return
*/
Map<String, Long> getActivityStats();
}

View File

@ -1,9 +1,11 @@
package com.bruce.sams.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bruce.sams.domain.sms.Club;
import java.util.List;
import java.util.Map;
/**
* *
@ -23,4 +25,28 @@ public interface ClubService extends IService<Club> {
Club getClubById(Long clubId);
List<Club> listClubs(String keyword, Long userId);
/**
*
*
* @param club
*/
void createClub(Club club);
/**
*
*
* @param clubId ID
* @param pageNum
* @param pageSize
* @return
*/
Page<Map<String, Object>> getClubMembers(Long clubId, Integer pageNum, Integer pageSize);
/**
*
*
* @return
*/
Map<String, Long> getClubStats();
}

View File

@ -1,11 +1,18 @@
package com.bruce.sams.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bruce.sams.domain.sys.Role;
import com.bruce.sams.domain.sys.User;
import java.util.List;
import java.util.Map;
public interface UserService {
/**
*
*/
public interface UserService extends IService<User> {
/**
*
@ -91,4 +98,18 @@ public interface UserService {
*/
void updateUserAvatar(Long userId, String avatarUrl);
/**
*
*
* @param userId ID
* @return
*/
User getUserById(Long userId);
/**
*
*
* @return
*/
Map<String, Long> getUserStats();
}

View File

@ -1,5 +1,6 @@
package com.bruce.sams.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.sams.common.enums.ActivityStatus;
import com.bruce.sams.domain.ams.Activity;
@ -7,6 +8,9 @@ import com.bruce.sams.mapper.ActivityMapper;
import com.bruce.sams.service.ActivityService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
*
*
@ -71,4 +75,31 @@ public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> i
public Activity getActivityById(Long actId) {
return this.getById(actId);
}
/**
*
*
* @return
*/
@Override
public Map<String, Long> getActivityStats() {
Map<String, Long> stats = new HashMap<>();
// 获取各种状态的活动数量
stats.put("total", this.count());
stats.put("pending", this.count(new LambdaQueryWrapper<Activity>()
.eq(Activity::getStatus, ActivityStatus.PENDING_CLUB)
.or()
.eq(Activity::getStatus, ActivityStatus.PENDING_DEPARTMENT)
.or()
.eq(Activity::getStatus, ActivityStatus.PENDING_COLLEGE)));
stats.put("ongoing", this.count(new LambdaQueryWrapper<Activity>()
.eq(Activity::getStatus, ActivityStatus.ONGOING)));
stats.put("completed", this.count(new LambdaQueryWrapper<Activity>()
.eq(Activity::getStatus, ActivityStatus.COMPLETED)));
stats.put("cancelled", this.count(new LambdaQueryWrapper<Activity>()
.eq(Activity::getStatus, ActivityStatus.CANCELLED)));
return stats;
}
}

View File

@ -1,11 +1,14 @@
package com.bruce.sams.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.sams.domain.sms.Club;
import com.bruce.sams.domain.sms.ClubUser;
import com.bruce.sams.domain.sys.Role;
import com.bruce.sams.domain.sys.User;
import com.bruce.sams.mapper.ClubMapper;
import com.bruce.sams.mapper.ClubUserMapper;
import com.bruce.sams.mapper.UserMapper;
import com.bruce.sams.service.ClubService;
import com.bruce.sams.service.UserService;
@ -13,7 +16,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
@ -32,6 +37,9 @@ public class ClubServiceImpl extends ServiceImpl<ClubMapper, Club>
@Autowired
private UserService userService;
@Autowired
private ClubUserMapper clubUserMapper;
/**
*
*/
@ -122,6 +130,55 @@ public class ClubServiceImpl extends ServiceImpl<ClubMapper, Club>
query.eq(Club::getLeaderId, userId); // 仅查询自己负责的社团
return this.list(query);
}
/**
*
*
* @param club
*/
@Override
public void createClub(Club club) {
this.save(club);
}
/**
*
*
* @param clubId ID
* @param pageNum
* @param pageSize
* @return
*/
@Override
public Page<Map<String, Object>> getClubMembers(Long clubId, Integer pageNum, Integer pageSize) {
Page<Map<String, Object>> page = new Page<>(pageNum, pageSize);
return clubUserMapper.selectClubMembers(page, clubId);
}
/**
*
*
* @return
*/
@Override
public Map<String, Long> getClubStats() {
Map<String, Long> stats = new HashMap<>();
// 获取社团总数
stats.put("total", this.count());
// 获取各类别社团数量
stats.put("academic", this.count(new LambdaQueryWrapper<Club>()
.eq(Club::getCategory, "学术科技")));
stats.put("art", this.count(new LambdaQueryWrapper<Club>()
.eq(Club::getCategory, "文化艺术")));
stats.put("public", this.count(new LambdaQueryWrapper<Club>()
.eq(Club::getCategory, "社会公益")));
stats.put("other", this.count(new LambdaQueryWrapper<Club>()
.eq(Club::getCategory, "其他")));
return stats;
}
}

View File

@ -1,6 +1,7 @@
package com.bruce.sams.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.sams.common.enums.UserStatus;
import com.bruce.sams.common.exception.PasswordIncorrectException;
@ -12,10 +13,15 @@ import com.bruce.sams.mapper.RoleMapper;
import com.bruce.sams.mapper.UserMapper;
import com.bruce.sams.mapper.UserRoleMapper;
import com.bruce.sams.service.UserService;
import com.bruce.sams.vo.ActivityVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
@ -30,6 +36,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
@Autowired
private RoleMapper roleMapper;
@Autowired
private PasswordEncoder passwordEncoder;
/**
*
*
@ -207,4 +216,72 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
userMapper.updateAvatar(userId, avatarUrl);
}
/**
*
*
* @param userId ID
* @return
*/
@Override
public User getUserById(Long userId) {
return this.getById(userId);
}
/**
*
*
* @return
*/
@Override
public Map<String, Long> getUserStats() {
Map<String, Long> stats = new HashMap<>();
// 获取用户总数
stats.put("total", this.count());
// 获取各状态用户数量
stats.put("active", this.count(new LambdaQueryWrapper<User>()
.eq(User::getStatus, "active")));
stats.put("inactive", this.count(new LambdaQueryWrapper<User>()
.eq(User::getStatus, "inactive")));
stats.put("banned", this.count(new LambdaQueryWrapper<User>()
.eq(User::getStatus, "banned")));
return stats;
}
@Override
public Page<ActivityVO> getUserActivities(Long userId, Integer pageNum, Integer pageSize, String type) {
Page<ActivityVO> page = new Page<>(pageNum, pageSize);
return userMapper.selectUserActivities(page, userId, type);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelActivityRegistration(Long userId, Long activityId) {
// 检查用户是否已报名该活动
if (!userMapper.checkActivityRegistration(userId, activityId)) {
throw new RuntimeException("您未报名该活动");
}
// 取消报名
userMapper.cancelActivityRegistration(userId, activityId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updatePassword(Long userId, String oldPassword, String newPassword) {
// 获取用户信息
User user = getUserById(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 验证原密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new RuntimeException("原密码错误");
}
// 更新密码
user.setPassword(passwordEncoder.encode(newPassword));
userMapper.updateById(user);
}
}

View File

@ -3,6 +3,7 @@ server:
spring:
application:
name: SAMS
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/SAMS