feat: 项目成员管理前端页面实现
前端实现:
- projectMember.js: API接口封装(7个接口)
- projectMember.vue: 项目成员管理页面
页面特点:
- 以项目为维度管理成员
- 角色类型:项目经理/开发/测试/财务/普通成员
- 状态管理:在职/已离开
- 工作量占比:0-100%
模块状态:✅ 完整(前端+后端)
This commit is contained in:
parent
6702b92a95
commit
588ef25869
@ -1,9 +1,9 @@
|
||||
import request from '../utils/request'
|
||||
|
||||
/**
|
||||
* 获取项目成员列表
|
||||
* 获取项目成员列表(按项目ID)
|
||||
*/
|
||||
export const getProjectMembers = (projectId) => {
|
||||
export const getMemberListByProject = (projectId) => {
|
||||
return request({
|
||||
url: `/proj/api/v1/project-member/list/project/${projectId}`,
|
||||
method: 'get'
|
||||
@ -11,9 +11,9 @@ export const getProjectMembers = (projectId) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户参与的项目列表
|
||||
* 获取用户的项目列表(按用户ID)
|
||||
*/
|
||||
export const getUserProjects = (userId) => {
|
||||
export const getMemberListByUser = (userId) => {
|
||||
return request({
|
||||
url: `/proj/api/v1/project-member/list/user/${userId}`,
|
||||
method: 'get'
|
||||
@ -21,7 +21,7 @@ export const getUserProjects = (userId) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成员详情
|
||||
* 获取项目成员详情
|
||||
*/
|
||||
export const getMemberById = (memberId) => {
|
||||
return request({
|
||||
@ -33,7 +33,7 @@ export const getMemberById = (memberId) => {
|
||||
/**
|
||||
* 添加项目成员
|
||||
*/
|
||||
export const addProjectMember = (data) => {
|
||||
export const addMember = (data) => {
|
||||
return request({
|
||||
url: '/proj/api/v1/project-member',
|
||||
method: 'post',
|
||||
@ -44,7 +44,7 @@ export const addProjectMember = (data) => {
|
||||
/**
|
||||
* 更新项目成员
|
||||
*/
|
||||
export const updateProjectMember = (memberId, data) => {
|
||||
export const updateMember = (memberId, data) => {
|
||||
return request({
|
||||
url: `/proj/api/v1/project-member/${memberId}`,
|
||||
method: 'put',
|
||||
@ -55,7 +55,7 @@ export const updateProjectMember = (memberId, data) => {
|
||||
/**
|
||||
* 移除项目成员
|
||||
*/
|
||||
export const removeProjectMember = (memberId) => {
|
||||
export const removeMember = (memberId) => {
|
||||
return request({
|
||||
url: `/proj/api/v1/project-member/${memberId}`,
|
||||
method: 'delete'
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="project-member-container">
|
||||
<div class="member-container">
|
||||
<el-card>
|
||||
<!-- 搜索栏 -->
|
||||
<el-form :inline="true" :model="searchForm">
|
||||
<el-form-item label="项目">
|
||||
<el-select v-model="searchForm.projectId" placeholder="请选择项目" clearable @change="fetchData">
|
||||
<el-select v-model="searchForm.projectId" placeholder="请选择项目" clearable @change="handleSearch">
|
||||
<el-option
|
||||
v-for="project in projectOptions"
|
||||
:key="project.projectId"
|
||||
@ -13,25 +13,53 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色">
|
||||
<el-select v-model="searchForm.role" placeholder="请选择角色" clearable @change="handleSearch">
|
||||
<el-option label="项目经理" value="pm" />
|
||||
<el-option label="开发" value="dev" />
|
||||
<el-option label="测试" value="test" />
|
||||
<el-option label="财务" value="finance" />
|
||||
<el-option label="普通成员" value="member" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable @change="handleSearch">
|
||||
<el-option label="在职" :value="1" />
|
||||
<el-option label="已离开" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleAdd">添加成员</el-button>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<el-row style="margin-bottom: 15px;">
|
||||
<el-button type="primary" @click="handleAdd" :disabled="!searchForm.projectId">添加成员</el-button>
|
||||
<el-alert
|
||||
v-if="!searchForm.projectId"
|
||||
title="请先选择项目"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-top: 10px;"
|
||||
/>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table :data="tableData" border v-loading="loading">
|
||||
<el-table-column prop="userId" label="用户ID" width="100" />
|
||||
<el-table-column prop="role" label="项目角色" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.role === 'pm'" type="danger">项目经理</el-tag>
|
||||
<el-tag v-else-if="row.role === 'dev'" type="primary">开发</el-tag>
|
||||
<el-tag v-else-if="row.role === 'test'" type="warning">测试</el-tag>
|
||||
<el-tag v-else-if="row.role === 'finance'" type="success">财务</el-tag>
|
||||
<el-tag v-else type="info">普通成员</el-tag>
|
||||
{{ getRoleText(row.role) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="joinDate" label="加入日期" width="120" />
|
||||
<el-table-column prop="leaveDate" label="离开日期" width="120" />
|
||||
<el-table-column prop="leaveDate" label="离开日期" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.leaveDate || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="workload" label="工作量占比" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.workload }}%
|
||||
@ -43,11 +71,23 @@
|
||||
<el-tag v-else type="info">已离开</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="remark" label="备注" width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="createdTime" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleUpdateStatus(row, 0)"
|
||||
>标记离开</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleUpdateStatus(row, 1)"
|
||||
>恢复在职</el-button>
|
||||
<el-button type="danger" size="small" @click="handleRemove(row)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -57,12 +97,12 @@
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="form.memberId ? '编辑成员' : '添加成员'"
|
||||
:title="form.memberId ? '编辑项目成员' : '添加项目成员'"
|
||||
width="600px"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="项目" prop="projectId">
|
||||
<el-select v-model="form.projectId" placeholder="请选择项目" style="width: 100%;">
|
||||
<el-select v-model="form.projectId" placeholder="请选择项目" style="width: 100%;" disabled>
|
||||
<el-option
|
||||
v-for="project in projectOptions"
|
||||
:key="project.projectId"
|
||||
@ -73,7 +113,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户ID" prop="userId">
|
||||
<el-input-number v-model="form.userId" :min="1" style="width: 100%;" />
|
||||
<el-input-number v-model="form.userId" :min="1" style="width: 100%;" placeholder="请输入用户ID" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="项目角色" prop="role">
|
||||
@ -98,28 +138,12 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="离开日期">
|
||||
<el-date-picker
|
||||
v-model="form.leaveDate"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
<el-form-item label="工作量占比">
|
||||
<el-input-number v-model="form.workload" :min="0" :max="100" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="工作量占比" prop="workload">
|
||||
<el-slider v-model="form.workload" :min="0" :max="100" show-input />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="1">在职</el-radio>
|
||||
<el-radio :label="0">已离开</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注说明">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
@ -142,16 +166,19 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getProjectMembers,
|
||||
addProjectMember,
|
||||
updateProjectMember,
|
||||
removeProjectMember
|
||||
getMemberListByProject,
|
||||
addMember,
|
||||
updateMember,
|
||||
removeMember,
|
||||
updateMemberStatus
|
||||
} from '../../api/projectMember'
|
||||
import { getProjectList } from '../../api/project'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
projectId: null
|
||||
projectId: null,
|
||||
role: '',
|
||||
status: null
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
@ -172,9 +199,7 @@ const form = reactive({
|
||||
userId: null,
|
||||
role: 'member',
|
||||
joinDate: null,
|
||||
leaveDate: null,
|
||||
workload: 0,
|
||||
status: 1,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
@ -187,7 +212,7 @@ const rules = {
|
||||
{ required: true, message: '请输入用户ID', trigger: 'blur' }
|
||||
],
|
||||
role: [
|
||||
{ required: true, message: '请选择角色', trigger: 'change' }
|
||||
{ required: true, message: '请选择项目角色', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
@ -201,17 +226,38 @@ const loadProjects = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色文本
|
||||
const getRoleText = (role) => {
|
||||
const roleMap = {
|
||||
'pm': '项目经理',
|
||||
'dev': '开发',
|
||||
'test': '测试',
|
||||
'finance': '财务',
|
||||
'member': '普通成员'
|
||||
}
|
||||
return roleMap[role] || role
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const fetchData = async () => {
|
||||
if (!searchForm.projectId) {
|
||||
ElMessage.warning('请选择项目')
|
||||
tableData.value = []
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getProjectMembers(searchForm.projectId)
|
||||
tableData.value = res
|
||||
let data = await getMemberListByProject(searchForm.projectId)
|
||||
|
||||
// 前端过滤
|
||||
if (searchForm.role) {
|
||||
data = data.filter(item => item.role === searchForm.role)
|
||||
}
|
||||
if (searchForm.status !== null && searchForm.status !== '') {
|
||||
data = data.filter(item => item.status === searchForm.status)
|
||||
}
|
||||
|
||||
tableData.value = data
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
ElMessage.error(error.message || '加载数据失败')
|
||||
@ -220,6 +266,19 @@ const fetchData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.projectId = null
|
||||
searchForm.role = ''
|
||||
searchForm.status = null
|
||||
tableData.value = []
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
if (!searchForm.projectId) {
|
||||
@ -233,9 +292,7 @@ const handleAdd = () => {
|
||||
userId: null,
|
||||
role: 'member',
|
||||
joinDate: null,
|
||||
leaveDate: null,
|
||||
workload: 0,
|
||||
status: 1,
|
||||
remark: ''
|
||||
})
|
||||
dialogVisible.value = true
|
||||
@ -249,9 +306,7 @@ const handleEdit = (row) => {
|
||||
userId: row.userId,
|
||||
role: row.role,
|
||||
joinDate: row.joinDate,
|
||||
leaveDate: row.leaveDate,
|
||||
workload: row.workload,
|
||||
status: row.status,
|
||||
remark: row.remark
|
||||
})
|
||||
dialogVisible.value = true
|
||||
@ -270,10 +325,10 @@ const handleSubmit = async () => {
|
||||
|
||||
try {
|
||||
if (form.memberId) {
|
||||
await updateProjectMember(form.memberId, form)
|
||||
await updateMember(form.memberId, form)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await addProjectMember(form)
|
||||
await addMember(form)
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
@ -284,6 +339,27 @@ const handleSubmit = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
const handleUpdateStatus = async (row, status) => {
|
||||
const statusText = status === 1 ? '恢复在职' : '标记离开'
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定要${statusText}吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await updateMemberStatus(row.memberId, status)
|
||||
ElMessage.success(`${statusText}成功`)
|
||||
await fetchData()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('状态更新失败:', error)
|
||||
ElMessage.error(error.message || '状态更新失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除
|
||||
const handleRemove = async (row) => {
|
||||
try {
|
||||
@ -293,7 +369,7 @@ const handleRemove = async (row) => {
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await removeProjectMember(row.memberId)
|
||||
await removeMember(row.memberId)
|
||||
ElMessage.success('移除成功')
|
||||
await fetchData()
|
||||
} catch (error) {
|
||||
@ -311,7 +387,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.project-member-container {
|
||||
.member-container {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user