v0.2.14 前端代码优化

v0.2o
bruce 2025-03-24 14:30:47 +08:00
parent 260a85d8f3
commit 6522a9cf05
14 changed files with 2101 additions and 42 deletions

20
doc/README.md 100644
View File

@ -0,0 +1,20 @@
# SAMS系统文档
本目录包含SAMS系统的相关文档。
## 文档结构
- `api.md`: API接口文档包含所有后端接口的详细说明
- 认证接口
- 系统管理接口
- 活动管理接口
- 社团管理接口
- 通用说明
## 文档更新
本文档会随着系统开发持续更新。如果您发现文档中的任何问题或有改进建议,请及时反馈。
## 文档格式
所有文档均使用Markdown格式编写可以使用任何支持Markdown的编辑器或查看器阅读。

961
doc/api.md 100644
View File

@ -0,0 +1,961 @@
# SAMS系统接口文档
## 1. 认证接口 (AuthController)
### 1.1 用户登录
- **接口路径**: `/login`
- **请求方式**: POST
- **请求参数**:
```json
{
"username": "string", // 用户名/学号/邮箱
"password": "string" // 密码
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "登录成功",
"data": "JWT令牌"
}
```
## 2. 系统管理接口 (sys)
### 2.1 用户管理 (UserController)
- **接口路径**: `/api/user`
- **功能**: 普通用户管理相关接口
#### 2.1.1 修改密码
- **接口路径**: `/api/user/change-password`
- **请求方式**: PUT
- **请求参数**:
```json
{
"userId": "number", // 用户ID
"oldPassword": "string", // 旧密码
"newPassword": "string" // 新密码
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "密码修改成功",
"data": null
}
```
#### 2.1.2 更新个人信息
- **接口路径**: `/api/user/profile`
- **请求方式**: PUT
- **请求参数**:
```json
{
"userId": "number", // 用户ID
"nickname": "string", // 昵称
"email": "string", // 邮箱
"avatar": "string" // 头像URL
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "个人信息更新成功",
"data": null
}
```
#### 2.1.3 获取用户详情
- **接口路径**: `/api/user/{userId}`
- **请求方式**: GET
- **路径参数**:
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": {
"userId": "number",
"username": "string",
"nickname": "string",
"email": "string",
"avatar": "string"
}
}
```
#### 2.1.4 上传头像
- **接口路径**: `/api/user/avatar`
- **请求方式**: POST
- **请求参数**:
- file: 头像文件MultipartFile
- **响应结果**: 返回上传后的头像URL字符串
### 2.2 管理员用户管理 (AdminUserController)
- **接口路径**: `/api/admin/user`
- **功能**: 管理员用户管理相关接口
#### 2.2.1 批量导入用户
- **接口路径**: `/api/admin/user/import`
- **请求方式**: POST
- **请求参数**:
```json
[
{
"username": "string",
"password": "string",
"email": "string",
"role": "string"
}
]
```
- **响应结果**:
```json
{
"code": 200,
"msg": "用户导入成功",
"data": null
}
```
#### 2.2.2 查询用户列表
- **接口路径**: `/api/admin/user/list`
- **请求方式**: GET
- **请求参数**:
- keyword: 关键字(可选,用于搜索用户名、学号、邮箱)
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"userId": "number",
"username": "string",
"email": "string",
"role": "string",
"status": "string"
}
]
}
```
#### 2.2.3 更新用户信息
- **接口路径**: `/api/admin/user/update`
- **请求方式**: PUT
- **请求参数**:
```json
{
"userId": "number",
"role": "string",
"status": "string",
"password": "string"
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "用户信息更新成功",
"data": null
}
```
#### 2.2.4 重置用户密码
- **接口路径**: `/api/admin/user/reset-password/{userId}`
- **请求方式**: PUT
- **路径参数**:
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "密码已重置为 123456",
"data": null
}
```
#### 2.2.5 封禁用户
- **接口路径**: `/api/admin/user/ban/{userId}`
- **请求方式**: PUT
- **路径参数**:
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "用户已封禁",
"data": null
}
```
#### 2.2.6 删除用户
- **接口路径**: `/api/admin/user/{userId}`
- **请求方式**: DELETE
- **路径参数**:
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "用户已删除",
"data": null
}
```
### 2.3 文件管理 (FileController)
- **接口路径**: `/api/file`
- **功能**: 文件上传下载相关接口
- **说明**: 该模块目前为空,待实现
## 3. 活动管理接口 (ams)
### 3.1 活动管理 (ActivityController)
- **接口路径**: `/api/admin/activity`
- **功能**: 活动创建、修改、查询等
- **权限要求**: 需要相应角色权限
#### 3.1.1 创建活动
- **接口路径**: `/api/admin/activity/create`
- **请求方式**: POST
- **权限要求**: USER, CLUB_ADMIN
- **请求参数**:
```json
{
"title": "string", // 活动标题
"description": "string", // 活动描述
"startTime": "string", // 开始时间
"endTime": "string", // 结束时间
"location": "string", // 活动地点
"maxParticipants": "number", // 最大参与人数
"clubId": "number" // 主办社团ID
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "活动创建成功,等待审批",
"data": null
}
```
#### 3.1.2 取消活动
- **接口路径**: `/api/admin/activity/cancel`
- **请求方式**: PUT
- **权限要求**: CLUB_ADMIN
- **请求参数**:
- actId: 活动ID
- **响应结果**:
```json
{
"code": 200,
"msg": "活动已取消",
"data": null
}
```
### 3.2 审批管理 (ApprovalController)
- **接口路径**: `/api/admin/approval`
- **功能**: 活动审批相关接口
- **权限要求**: 需要相应角色权限
#### 3.2.1 审批通过
- **接口路径**: `/api/admin/approval/approve`
- **请求方式**: POST
- **权限要求**: CLUB_ADMIN, DEPARTMENT_ADMIN, COLLEGE_ADMIN
- **请求参数**:
- apprId: 审批ID
- approverId: 审批人ID
- **响应结果**:
```json
{
"code": 200,
"msg": "审批已通过",
"data": null
}
```
#### 3.2.2 审批拒绝
- **接口路径**: `/api/admin/approval/reject`
- **请求方式**: POST
- **权限要求**: CLUB_ADMIN, DEPARTMENT_ADMIN, COLLEGE_ADMIN
- **请求参数**:
- apprId: 审批ID
- reason: 拒绝原因
- approverId: 审批人ID
- **响应结果**:
```json
{
"code": 200,
"msg": "审批已拒绝",
"data": null
}
```
### 3.3 评论管理 (CommentController)
- **接口路径**: `/api/comment`
- **功能**: 活动评论相关接口
- **权限要求**: 需要相应角色权限
#### 3.3.1 发表评论
- **接口路径**: `/api/comment/add`
- **请求方式**: POST
- **权限要求**: USER
- **请求参数**:
- userId: 用户ID
- actId: 活动ID
- content: 评论内容
- parentCommentId: 父评论ID可选用于回复
- **响应结果**:
```json
{
"code": 200,
"msg": "评论成功",
"data": null
}
```
#### 3.3.2 删除评论
- **接口路径**: `/api/comment/delete`
- **请求方式**: DELETE
- **权限要求**: USER, ADMIN
- **请求参数**:
- commentId: 评论ID
- userId: 用户ID
- isAdmin: 是否为管理员
- **响应结果**:
```json
{
"code": 200,
"msg": "评论已删除",
"data": null
}
```
#### 3.3.3 获取活动评论列表
- **接口路径**: `/api/comment/list`
- **请求方式**: GET
- **请求参数**:
- actId: 活动ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"commentId": "number",
"userId": "number",
"content": "string",
"createTime": "string"
}
]
}
```
#### 3.3.4 获取评论回复
- **接口路径**: `/api/comment/replies`
- **请求方式**: GET
- **请求参数**:
- parentCommentId: 父评论ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"commentId": "number",
"userId": "number",
"content": "string",
"createTime": "string"
}
]
}
```
### 3.4 反应管理 (ReactionController)
- **接口路径**: `/api/reaction`
- **功能**: 活动反应(点赞等)相关接口
- **权限要求**: 需要相应角色权限
#### 3.4.1 添加反应
- **接口路径**: `/api/reaction/add`
- **请求方式**: POST
- **权限要求**: USER
- **请求参数**:
- actId: 活动ID
- userId: 用户ID
- reactionType: 反应类型LIKE/DISLIKE
- **响应结果**:
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
#### 3.4.2 取消反应
- **接口路径**: `/api/reaction/remove`
- **请求方式**: DELETE
- **权限要求**: USER
- **请求参数**:
- actId: 活动ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "已取消点赞/点踩",
"data": null
}
```
#### 3.4.3 获取反应统计
- **接口路径**: `/api/reaction/stats`
- **请求方式**: GET
- **请求参数**:
- actId: 活动ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": {
"likes": "number",
"dislikes": "number"
}
}
```
#### 3.4.4 获取活动反应列表
- **接口路径**: `/api/reaction/list`
- **请求方式**: GET
- **权限要求**: ADMIN
- **请求参数**:
- actId: 活动ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"reactionId": "number",
"userId": "number",
"reactionType": "string",
"createTime": "string"
}
]
}
```
### 3.5 报名管理 (RegistrationController)
- **接口路径**: `/api/registration`
- **功能**: 活动报名相关接口
- **权限要求**: 需要相应角色权限
#### 3.5.1 报名活动
- **接口路径**: `/api/registration/register`
- **请求方式**: POST
- **权限要求**: USER
- **请求参数**:
- actId: 活动ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "报名成功",
"data": null
}
```
#### 3.5.2 取消报名
- **接口路径**: `/api/registration/cancel`
- **请求方式**: PUT
- **权限要求**: USER
- **请求参数**:
- actId: 活动ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "报名已取消",
"data": null
}
```
#### 3.5.3 活动签到
- **接口路径**: `/api/registration/attend`
- **请求方式**: PUT
- **权限要求**: USER
- **请求参数**:
- actId: 活动ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "签到成功",
"data": null
}
```
#### 3.5.4 获取我的报名列表
- **接口路径**: `/api/registration/my-registrations`
- **请求方式**: GET
- **权限要求**: USER
- **请求参数**:
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"registrationId": "number",
"actId": "number",
"userId": "number",
"status": "string",
"createTime": "string"
}
]
}
```
#### 3.5.5 获取活动报名列表
- **接口路径**: `/api/registration/list`
- **请求方式**: GET
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN
- **请求参数**:
- actId: 活动ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"registrationId": "number",
"actId": "number",
"userId": "number",
"status": "string",
"createTime": "string"
}
]
}
```
## 4. 社团管理接口 (sms)
### 4.1 社团管理 (ClubController)
- **接口路径**: `/api/admin/club`
- **功能**: 社团基本信息管理
- **权限要求**: 需要相应角色权限
#### 4.1.1 添加社团
- **接口路径**: `/api/admin/club/add`
- **请求方式**: POST
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN
- **请求参数**:
```json
{
"name": "string", // 社团名称
"description": "string", // 社团描述
"collegeId": "number", // 所属学院ID
"type": "string" // 社团类型
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "社团添加成功",
"data": null
}
```
#### 4.1.2 更新社团信息
- **接口路径**: `/api/admin/club/update`
- **请求方式**: PUT
- **权限要求**: ADMIN, CLUB_ADMIN
- **请求参数**:
```json
{
"clubId": "number", // 社团ID
"name": "string", // 社团名称
"description": "string", // 社团描述
"type": "string" // 社团类型
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "社团信息更新成功",
"data": null
}
```
#### 4.1.3 删除社团
- **接口路径**: `/api/admin/club/delete/{clubId}`
- **请求方式**: DELETE
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN
- **路径参数**:
- clubId: 社团ID
- **响应结果**:
```json
{
"code": 200,
"msg": "社团删除成功",
"data": null
}
```
#### 4.1.4 获取社团详情
- **接口路径**: `/api/admin/club/{clubId}`
- **请求方式**: GET
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN, CLUB_ADMIN
- **路径参数**:
- clubId: 社团ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": {
"clubId": "number",
"name": "string",
"description": "string",
"collegeId": "number",
"type": "string"
}
}
```
#### 4.1.5 查询社团列表
- **接口路径**: `/api/admin/club/list`
- **请求方式**: GET
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN, CLUB_ADMIN
- **请求参数**:
- keyword: 关键字(可选,用于搜索)
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"clubId": "number",
"name": "string",
"description": "string",
"collegeId": "number",
"type": "string"
}
]
}
```
### 4.2 社团成员管理 (ClubUserController)
- **接口路径**: `/api/admin/club/user`
- **功能**: 社团成员管理相关接口
- **权限要求**: 需要相应角色权限
#### 4.2.1 添加社团成员
- **接口路径**: `/api/admin/club/user/add`
- **请求方式**: POST
- **权限要求**: CLUB_ADMIN
- **请求参数**:
- clubId: 社团ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "成员已加入社团",
"data": null
}
```
#### 4.2.2 移除社团成员
- **接口路径**: `/api/admin/club/user/remove`
- **请求方式**: DELETE
- **权限要求**: CLUB_ADMIN, COLLEGE_ADMIN
- **请求参数**:
- clubId: 社团ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "成员已移除",
"data": null
}
```
#### 4.2.3 查询社团成员列表
- **接口路径**: `/api/admin/club/user/list`
- **请求方式**: GET
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN
- **请求参数**:
- clubId: 社团ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"userId": "number",
"username": "string",
"role": "string"
}
]
}
```
#### 4.2.4 查看我的社团成员
- **接口路径**: `/api/admin/club/user/my-club-members`
- **请求方式**: GET
- **权限要求**: CLUB_ADMIN
- **请求参数**:
- clubId: 社团ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"userId": "number",
"username": "string",
"role": "string"
}
]
}
```
### 4.3 学院管理 (CollegeController)
- **接口路径**: `/api/admin/college`
- **功能**: 学院信息管理相关接口
- **权限要求**: 需要相应角色权限
#### 4.3.1 添加学院
- **接口路径**: `/api/admin/college/add`
- **请求方式**: POST
- **权限要求**: ADMIN, COLLEGE_ADMIN
- **请求参数**:
```json
{
"name": "string", // 学院名称
"description": "string", // 学院描述
"code": "string" // 学院代码
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "学院添加成功",
"data": null
}
```
#### 4.3.2 更新学院信息
- **接口路径**: `/api/admin/college/update`
- **请求方式**: PUT
- **权限要求**: ADMIN, COLLEGE_ADMIN
- **请求参数**:
```json
{
"collegeId": "number", // 学院ID
"name": "string", // 学院名称
"description": "string", // 学院描述
"code": "string" // 学院代码
}
```
- **响应结果**:
```json
{
"code": 200,
"msg": "学院更新成功",
"data": null
}
```
#### 4.3.3 删除学院
- **接口路径**: `/api/admin/college/delete/{collegeId}`
- **请求方式**: DELETE
- **权限要求**: ADMIN, COLLEGE_ADMIN
- **路径参数**:
- collegeId: 学院ID
- **响应结果**:
```json
{
"code": 200,
"msg": "学院删除成功",
"data": null
}
```
#### 4.3.4 获取学院详情
- **接口路径**: `/api/admin/college/{collegeId}`
- **请求方式**: GET
- **权限要求**: ADMIN, COLLEGE_ADMIN
- **路径参数**:
- collegeId: 学院ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": {
"collegeId": "number",
"name": "string",
"description": "string",
"code": "string"
}
}
```
#### 4.3.5 获取我的学院信息
- **接口路径**: `/api/admin/college/my-college`
- **请求方式**: GET
- **权限要求**: DEPARTMENT_ADMIN
- **请求参数**:
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": {
"collegeId": "number",
"name": "string",
"description": "string",
"code": "string"
}
}
```
#### 4.3.6 查询学院列表
- **接口路径**: `/api/admin/college/list`
- **请求方式**: GET
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN
- **请求参数**:
- keyword: 关键字(可选,用于搜索)
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": [
{
"collegeId": "number",
"name": "string",
"description": "string",
"code": "string"
}
]
}
```
#### 4.3.7 指派学院负责人
- **接口路径**: `/api/admin/college/assign`
- **请求方式**: POST
- **权限要求**: ADMIN, COLLEGE_ADMIN
- **请求参数**:
- collegeId: 学院ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "负责人指派成功",
"data": null
}
```
#### 4.3.8 移除学院负责人
- **接口路径**: `/api/admin/college/remove`
- **请求方式**: DELETE
- **权限要求**: ADMIN, COLLEGE_ADMIN
- **请求参数**:
- collegeId: 学院ID
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "负责人移除成功",
"data": null
}
```
#### 4.3.9 获取学院负责人
- **接口路径**: `/api/admin/college/getleader/{collegeId}`
- **请求方式**: GET
- **权限要求**: ADMIN, COLLEGE_ADMIN, DEPARTMENT_ADMIN
- **路径参数**:
- collegeId: 学院ID
- **请求参数**:
- userId: 用户ID
- **响应结果**:
```json
{
"code": 200,
"msg": "success",
"data": {
"userId": "number",
"username": "string",
"role": "string"
}
}
```
## 通用说明
1. **认证方式**
- 除登录接口外,所有接口都需要在请求头中携带 JWT Token
- Token 格式:`Authorization: Bearer <token>`
2. **响应格式**
- 所有接口统一使用 `AjaxResult` 封装响应
- 成功响应示例:
```json
{
"code": 200,
"msg": "操作成功",
"data": {}
}
```
- 失败响应示例:
```json
{
"code": 500,
"msg": "操作失败",
"data": null
}
```
3. **请求方式**
- 查询类接口使用 GET
- 创建类接口使用 POST
- 更新类接口使用 PUT
- 删除类接口使用 DELETE
4. **数据格式**
- 请求和响应均使用 JSON 格式
- 日期时间格式统一使用 ISO 8601 标准

View File

@ -0,0 +1,53 @@
import request from '@/utils/request'
// 创建活动
export function createActivity(data) {
return request({
url: '/api/admin/activity/create',
method: 'post',
data
})
}
// 取消活动
export function cancelActivity(data) {
return request({
url: '/api/admin/activity/cancel',
method: 'put',
data
})
}
// 获取活动列表
export function getActivityList(params) {
return request({
url: '/api/admin/activity/list',
method: 'get',
params
})
}
// 获取活动详情
export function getActivityDetail(actId) {
return request({
url: `/api/admin/activity/${actId}`,
method: 'get'
})
}
// 更新活动信息
export function updateActivity(data) {
return request({
url: '/api/admin/activity/update',
method: 'put',
data
})
}
// 删除活动
export function deleteActivity(actId) {
return request({
url: `/api/admin/activity/delete/${actId}`,
method: 'delete'
})
}

View File

@ -0,0 +1,69 @@
import request from '@/utils/request'
// 添加社团
export function addClub(data) {
return request({
url: '/api/admin/club/add',
method: 'post',
data
})
}
// 更新社团信息
export function updateClub(data) {
return request({
url: '/api/admin/club/update',
method: 'put',
data
})
}
// 删除社团
export function deleteClub(clubId) {
return request({
url: `/api/admin/club/delete/${clubId}`,
method: 'delete'
})
}
// 获取社团详情
export function getClubDetail(clubId) {
return request({
url: `/api/admin/club/${clubId}`,
method: 'get'
})
}
// 获取社团列表
export function getClubList(params) {
return request({
url: '/api/admin/club/list',
method: 'get',
params
})
}
// 获取社团成员列表
export function getClubMembers(clubId) {
return request({
url: `/api/admin/club/${clubId}/members`,
method: 'get'
})
}
// 添加社团成员
export function addClubMember(data) {
return request({
url: '/api/admin/club/member/add',
method: 'post',
data
})
}
// 移除社团成员
export function removeClubMember(clubId, userId) {
return request({
url: `/api/admin/club/${clubId}/member/${userId}`,
method: 'delete'
})
}

View File

@ -0,0 +1,48 @@
import request from '@/utils/request'
// 用户登录
export function login(data) {
return request({
url: '/api/login',
method: 'post',
data
})
}
// 获取用户信息
export function getUserInfo(userId) {
return request({
url: `/api/user/${userId}`,
method: 'get'
})
}
// 修改密码
export function changePassword(data) {
return request({
url: '/api/user/change-password',
method: 'put',
data
})
}
// 更新用户信息
export function updateProfile(data) {
return request({
url: '/api/user/profile',
method: 'put',
data
})
}
// 上传头像
export function uploadAvatar(data) {
return request({
url: '/api/user/avatar',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data
})
}

View File

@ -0,0 +1,10 @@
export default {
development: {
baseUrl: 'http://localhost:8080',
uploadUrl: 'http://localhost:8080/api/file/upload'
},
production: {
baseUrl: 'http://your-production-domain',
uploadUrl: 'http://your-production-domain/api/file/upload'
}
}

View File

@ -0,0 +1,9 @@
import env from './env'
import settings from './settings'
const config = {
...env[process.env.NODE_ENV || 'development'],
...settings
}
export default config

View File

@ -0,0 +1,83 @@
export default {
/**
* 系统标题
*/
title: 'SAMS学生社团管理系统',
/**
* 系统logo
*/
logo: '/logo.png',
/**
* 是否显示设置右面板
*/
showSettings: true,
/**
* 是否显示标签视图
*/
tagsView: true,
/**
* 是否固定头部
*/
fixedHeader: true,
/**
* 是否显示侧边栏Logo
*/
sidebarLogo: true,
/**
* 是否显示动态标题
*/
dynamicTitle: true,
/**
* 主题色
*/
theme: '#409EFF',
/**
* 导航模式 vertical / horizontal
*/
navMode: 'vertical',
/**
* 内容区域宽度 fluid / fixed
*/
contentWidth: 'fluid',
/**
* 侧边栏主题 dark / light
*/
sidebarTheme: 'dark',
/**
* 侧边栏折叠
*/
sidebarCollapse: false,
/**
* 布局模式
* default / classic / transverse / columns
*/
layout: 'default',
/**
* 是否开启水印
*/
watermark: false,
/**
* 水印配置
*/
watermarkConfig: {
text: 'SAMS',
fontColor: '#333',
fontSize: 16,
rotate: -30,
zIndex: 1000
}
}

View File

@ -0,0 +1,26 @@
const TokenKey = 'Admin-Token'
export function getToken() {
return localStorage.getItem(TokenKey)
}
export function setToken(token) {
return localStorage.setItem(TokenKey, token)
}
export function removeToken() {
return localStorage.removeItem(TokenKey)
}
export function getUserInfo() {
const userInfo = localStorage.getItem('userInfo')
return userInfo ? JSON.parse(userInfo) : null
}
export function setUserInfo(userInfo) {
return localStorage.setItem('userInfo', JSON.stringify(userInfo))
}
export function removeUserInfo() {
return localStorage.removeItem('userInfo')
}

View File

@ -1,31 +1,62 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getToken } from '@/utils/auth'
import config from '@/config'
// 创建axios实例
const service = axios.create({
baseURL: '/api',
timeout: 5000
baseURL: config.baseUrl,
timeout: 10000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
// 只给非登录接口加 token
if (token && config.url !== '/login') {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
service.interceptors.response.use(
res => {
if (res.data.code === 200) return res.data
ElMessage.error(res.data.message || '请求出错')
return Promise.reject(new Error(res.data.message || 'Error'))
// 请求拦截器
service.interceptors.request.use(
config => {
const token = getToken()
if (token && config.url !== '/login') {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
ElMessage.error(error.response?.data?.message || '服务器异常')
console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage({
message: res.message || '系统错误',
type: 'error',
duration: 5 * 1000
})
// 401: 未登录或token过期
if (res.code === 401) {
ElMessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
location.reload()
})
}
return Promise.reject(new Error(res.message || '系统错误'))
} else {
return res
}
},
error => {
console.log('err' + error)
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)

View File

@ -0,0 +1,277 @@
<template>
<div class="app-container">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>活动列表</span>
<el-button type="primary" @click="handleAdd"></el-button>
</div>
</template>
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
<el-form-item label="活动名称">
<el-input v-model="queryParams.title" 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="ENDED" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
<el-form-item>
<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="activityList" style="width: 100%">
<el-table-column prop="title" label="活动名称" />
<el-table-column prop="description" label="活动描述" show-overflow-tooltip />
<el-table-column prop="startTime" label="开始时间" width="180" />
<el-table-column prop="endTime" label="结束时间" width="180" />
<el-table-column prop="location" label="活动地点" width="150" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleEdit(scope.row)"></el-button>
<el-button type="primary" link @click="handleView(scope.row)"></el-button>
<el-button type="danger" link @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.size"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 添加或修改活动对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="activityFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="活动名称" prop="title">
<el-input v-model="form.title" placeholder="请输入活动名称" />
</el-form-item>
<el-form-item label="活动描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入活动描述" />
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="选择开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="选择结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="活动地点" prop="location">
<el-input v-model="form.location" placeholder="请输入活动地点" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getActivityList, createActivity, updateActivity, deleteActivity } from '@/api/activity/activity'
const loading = ref(false)
const activityList = ref([])
const total = ref(0)
const queryParams = ref({
page: 1,
size: 10,
title: '',
status: ''
})
const dialog = ref({
visible: false,
title: ''
})
const form = ref({
id: undefined,
title: '',
description: '',
startTime: '',
endTime: '',
location: ''
})
const rules = {
title: [{ required: true, message: '请输入活动名称', trigger: 'blur' }],
description: [{ required: true, message: '请输入活动描述', trigger: 'blur' }],
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
location: [{ required: true, message: '请输入活动地点', trigger: 'blur' }]
}
const getStatusType = (status) => {
const statusMap = {
ACTIVE: 'success',
ENDED: 'info',
CANCELLED: 'danger'
}
return statusMap[status] || 'info'
}
const getStatusText = (status) => {
const statusMap = {
ACTIVE: '进行中',
ENDED: '已结束',
CANCELLED: '已取消'
}
return statusMap[status] || '未知'
}
const getList = async () => {
loading.value = true
try {
const res = await getActivityList(queryParams.value)
activityList.value = res.data.records
total.value = res.data.total
} catch (error) {
console.error('获取活动列表失败:', error)
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.value.page = 1
getList()
}
const resetQuery = () => {
queryParams.value = {
page: 1,
size: 10,
title: '',
status: ''
}
getList()
}
const handleSizeChange = (val) => {
queryParams.value.size = val
getList()
}
const handleCurrentChange = (val) => {
queryParams.value.page = val
getList()
}
const handleAdd = () => {
dialog.value = {
visible: true,
title: '添加活动'
}
form.value = {
id: undefined,
title: '',
description: '',
startTime: '',
endTime: '',
location: ''
}
}
const handleEdit = (row) => {
dialog.value = {
visible: true,
title: '修改活动'
}
form.value = { ...row }
}
const handleView = (row) => {
// TODO:
}
const handleDelete = (row) => {
ElMessageBox.confirm('确认要删除该活动吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await deleteActivity(row.id)
ElMessage.success('删除成功')
getList()
} catch (error) {
console.error('删除活动失败:', error)
}
})
}
const submitForm = async () => {
try {
if (form.value.id) {
await updateActivity(form.value)
ElMessage.success('修改成功')
} else {
await createActivity(form.value)
ElMessage.success('新增成功')
}
dialog.value.visible = false
getList()
} catch (error) {
console.error('提交表单失败:', error)
}
}
const cancel = () => {
dialog.value.visible = false
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.app-container {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
</style>

View File

@ -0,0 +1,310 @@
<template>
<div class="app-container">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>社团列表</span>
<el-button type="primary" @click="handleAdd"></el-button>
</div>
</template>
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
<el-form-item label="社团名称">
<el-input v-model="queryParams.name" placeholder="请输入社团名称" clearable />
</el-form-item>
<el-form-item label="所属学院">
<el-select v-model="queryParams.collegeId" placeholder="请选择学院" clearable>
<el-option
v-for="item in collegeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<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="clubList" style="width: 100%">
<el-table-column prop="name" label="社团名称" />
<el-table-column prop="description" label="社团描述" show-overflow-tooltip />
<el-table-column prop="collegeName" label="所属学院" width="150" />
<el-table-column prop="memberCount" label="成员数" width="100" />
<el-table-column prop="activityCount" label="活动数" width="100" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="250" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleEdit(scope.row)"></el-button>
<el-button type="primary" link @click="handleView(scope.row)"></el-button>
<el-button type="primary" link @click="handleMembers(scope.row)"></el-button>
<el-button type="danger" link @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.size"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 添加或修改社团对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="clubFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="社团名称" prop="name">
<el-input v-model="form.name" placeholder="请输入社团名称" />
</el-form-item>
<el-form-item label="社团描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入社团描述" />
</el-form-item>
<el-form-item label="所属学院" prop="collegeId">
<el-select v-model="form.collegeId" placeholder="请选择学院">
<el-option
v-for="item in collegeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 成员管理对话框 -->
<el-dialog title="成员管理" v-model="memberDialog.visible" width="800px" append-to-body>
<el-table :data="memberDialog.members" style="width: 100%">
<el-table-column prop="username" label="用户名" />
<el-table-column prop="realName" label="姓名" />
<el-table-column prop="role" label="角色" width="100">
<template #default="scope">
<el-tag :type="scope.row.role === 'ADMIN' ? 'danger' : 'info'">
{{ scope.row.role === 'ADMIN' ? '管理员' : '普通成员' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="joinTime" label="加入时间" width="180" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="scope">
<el-button type="danger" link @click="handleRemoveMember(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleAddMember"></el-button>
<el-button @click="memberDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getClubList, addClub, updateClub, deleteClub, getClubMembers, removeClubMember } from '@/api/club/club'
const loading = ref(false)
const clubList = ref([])
const total = ref(0)
const collegeOptions = ref([])
const queryParams = ref({
page: 1,
size: 10,
name: '',
collegeId: undefined
})
const dialog = ref({
visible: false,
title: ''
})
const memberDialog = ref({
visible: false,
clubId: undefined,
members: []
})
const form = ref({
id: undefined,
name: '',
description: '',
collegeId: undefined
})
const rules = {
name: [{ required: true, message: '请输入社团名称', trigger: 'blur' }],
description: [{ required: true, message: '请输入社团描述', trigger: 'blur' }],
collegeId: [{ required: true, message: '请选择所属学院', trigger: 'change' }]
}
const getList = async () => {
loading.value = true
try {
const res = await getClubList(queryParams.value)
clubList.value = res.data.records
total.value = res.data.total
} catch (error) {
console.error('获取社团列表失败:', error)
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.value.page = 1
getList()
}
const resetQuery = () => {
queryParams.value = {
page: 1,
size: 10,
name: '',
collegeId: undefined
}
getList()
}
const handleSizeChange = (val) => {
queryParams.value.size = val
getList()
}
const handleCurrentChange = (val) => {
queryParams.value.page = val
getList()
}
const handleAdd = () => {
dialog.value = {
visible: true,
title: '添加社团'
}
form.value = {
id: undefined,
name: '',
description: '',
collegeId: undefined
}
}
const handleEdit = (row) => {
dialog.value = {
visible: true,
title: '修改社团'
}
form.value = { ...row }
}
const handleView = (row) => {
// TODO:
}
const handleDelete = (row) => {
ElMessageBox.confirm('确认要删除该社团吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await deleteClub(row.id)
ElMessage.success('删除成功')
getList()
} catch (error) {
console.error('删除社团失败:', error)
}
})
}
const handleMembers = async (row) => {
memberDialog.value = {
visible: true,
clubId: row.id,
members: []
}
try {
const res = await getClubMembers(row.id)
memberDialog.value.members = res.data
} catch (error) {
console.error('获取社团成员失败:', error)
}
}
const handleAddMember = () => {
// TODO:
}
const handleRemoveMember = (row) => {
ElMessageBox.confirm('确认要移除该成员吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await removeClubMember(memberDialog.value.clubId, row.id)
ElMessage.success('移除成功')
handleMembers({ id: memberDialog.value.clubId })
} catch (error) {
console.error('移除成员失败:', error)
}
})
}
const submitForm = async () => {
try {
if (form.value.id) {
await updateClub(form.value)
ElMessage.success('修改成功')
} else {
await addClub(form.value)
ElMessage.success('新增成功')
}
dialog.value.visible = false
getList()
} catch (error) {
console.error('提交表单失败:', error)
}
}
const cancel = () => {
dialog.value.visible = false
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.app-container {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
</style>

View File

@ -0,0 +1,169 @@
<template>
<div class="home-container">
<el-row :gutter="20">
<el-col :span="6">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>社团总数</span>
</div>
</template>
<div class="card-body">
<div class="number">{{ statistics.clubCount }}</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>活动总数</span>
</div>
</template>
<div class="card-body">
<div class="number">{{ statistics.activityCount }}</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>成员总数</span>
</div>
</template>
<div class="card-body">
<div class="number">{{ statistics.memberCount }}</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>今日活动</span>
</div>
</template>
<div class="card-body">
<div class="number">{{ statistics.todayActivityCount }}</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-20">
<el-col :span="12">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>最近活动</span>
<el-button type="text" @click="$router.push('/activity/list')"></el-button>
</div>
</template>
<div class="activity-list">
<el-table :data="recentActivities" style="width: 100%">
<el-table-column prop="title" label="活动名称" />
<el-table-column prop="startTime" label="开始时间" width="180" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 'ACTIVE' ? 'success' : 'info'">
{{ scope.row.status === 'ACTIVE' ? '进行中' : '已结束' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>热门社团</span>
<el-button type="text" @click="$router.push('/club/list')"></el-button>
</div>
</template>
<div class="club-list">
<el-table :data="popularClubs" style="width: 100%">
<el-table-column prop="name" label="社团名称" />
<el-table-column prop="memberCount" label="成员数" width="100" />
<el-table-column prop="activityCount" label="活动数" width="100" />
</el-table>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getActivityList } from '@/api/activity/activity'
import { getClubList } from '@/api/club/club'
const statistics = ref({
clubCount: 0,
activityCount: 0,
memberCount: 0,
todayActivityCount: 0
})
const recentActivities = ref([])
const popularClubs = ref([])
const fetchData = async () => {
try {
//
const activityRes = await getActivityList({ page: 1, size: 5 })
recentActivities.value = activityRes.data.records
//
const clubRes = await getClubList({ page: 1, size: 5 })
popularClubs.value = clubRes.data.records
//
statistics.value = {
clubCount: clubRes.data.total,
activityCount: activityRes.data.total,
memberCount: clubRes.data.records.reduce((sum, club) => sum + club.memberCount, 0),
todayActivityCount: activityRes.data.records.filter(activity => {
const today = new Date().toISOString().split('T')[0]
return activity.startTime.startsWith(today)
}).length
}
} catch (error) {
console.error('获取数据失败:', error)
}
}
onMounted(() => {
fetchData()
})
</script>
<style scoped>
.home-container {
padding: 20px;
}
.mt-20 {
margin-top: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
text-align: center;
padding: 20px 0;
}
.number {
font-size: 36px;
font-weight: bold;
color: #409EFF;
}
.activity-list,
.club-list {
margin-top: 10px;
}
</style>

View File

@ -2,10 +2,10 @@
<div class="login-page">
<div class="login-left">
<div class="login-box">
<h2 class="title">欢迎登录</h2>
<h2 class="title">SAMS学生社团管理系统</h2>
<el-form :model="loginForm" :rules="loginRules" ref="loginRef" label-position="top" size="large">
<el-form-item label="账号登录" prop="account">
<el-input v-model="loginForm.account" placeholder="请输入用户名 / 学号 / 邮箱" />
<el-form-item label="账号登录" prop="username">
<el-input v-model="loginForm.username" placeholder="请输入用户名 / 学号 / 邮箱" />
</el-form-item>
<el-form-item label="密码" prop="password">
@ -32,20 +32,21 @@
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { login } from '@/api/auth'
import { login } from '@/api/system/user'
import { setToken, setUserInfo } from '@/utils/auth'
const router = useRouter()
const loginRef = ref(null)
const loading = ref(false)
const loginForm = ref({
account: '',
username: '',
password: '',
remember: false
})
const loginRules = {
account: [{ required: true, message: '请输入用户名/学号/邮箱', trigger: 'blur' }],
username: [{ required: true, message: '请输入用户名/学号/邮箱', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
@ -54,22 +55,13 @@ const submitLogin = () => {
if (!valid) return
loading.value = true
try {
const token = await login(loginForm.value)
localStorage.setItem('token', token)
const res = await login(loginForm.value)
setToken(res.data.token)
setUserInfo(res.data.userInfo)
ElMessage.success('登录成功')
//
const payload = jwtDecode(token)
const roles = Array.isArray(payload.role) ? payload.role : [payload.role]
if (roles.includes('PARTICIPANT')) {
router.push('/user/home')
} else if (roles.includes('COLLEGE_ADMIN') ||roles.includes('DEPARTMENT_ADMIN') ||roles.includes('CLUB_ADMIN') ) {
router.push('/admin/home')
} else {
router.push('/403') //
}
} catch (e) {
ElMessage.error(e.message || '登录失败')
router.push('/')
} catch (error) {
ElMessage.error(error.message || '登录失败')
} finally {
loading.value = false
}
@ -97,6 +89,7 @@ const submitLogin = () => {
font-weight: bold;
margin-bottom: 32px;
text-align: center;
color: #409EFF;
}
.login-btn {
width: 100%;