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'
|
import request from '../utils/request'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取项目成员列表
|
* 获取项目成员列表(按项目ID)
|
||||||
*/
|
*/
|
||||||
export const getProjectMembers = (projectId) => {
|
export const getMemberListByProject = (projectId) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/proj/api/v1/project-member/list/project/${projectId}`,
|
url: `/proj/api/v1/project-member/list/project/${projectId}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
@ -11,9 +11,9 @@ export const getProjectMembers = (projectId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户参与的项目列表
|
* 获取用户的项目列表(按用户ID)
|
||||||
*/
|
*/
|
||||||
export const getUserProjects = (userId) => {
|
export const getMemberListByUser = (userId) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/proj/api/v1/project-member/list/user/${userId}`,
|
url: `/proj/api/v1/project-member/list/user/${userId}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
@ -21,7 +21,7 @@ export const getUserProjects = (userId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取成员详情
|
* 获取项目成员详情
|
||||||
*/
|
*/
|
||||||
export const getMemberById = (memberId) => {
|
export const getMemberById = (memberId) => {
|
||||||
return request({
|
return request({
|
||||||
@ -33,7 +33,7 @@ export const getMemberById = (memberId) => {
|
|||||||
/**
|
/**
|
||||||
* 添加项目成员
|
* 添加项目成员
|
||||||
*/
|
*/
|
||||||
export const addProjectMember = (data) => {
|
export const addMember = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/proj/api/v1/project-member',
|
url: '/proj/api/v1/project-member',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -44,7 +44,7 @@ export const addProjectMember = (data) => {
|
|||||||
/**
|
/**
|
||||||
* 更新项目成员
|
* 更新项目成员
|
||||||
*/
|
*/
|
||||||
export const updateProjectMember = (memberId, data) => {
|
export const updateMember = (memberId, data) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/proj/api/v1/project-member/${memberId}`,
|
url: `/proj/api/v1/project-member/${memberId}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
@ -55,7 +55,7 @@ export const updateProjectMember = (memberId, data) => {
|
|||||||
/**
|
/**
|
||||||
* 移除项目成员
|
* 移除项目成员
|
||||||
*/
|
*/
|
||||||
export const removeProjectMember = (memberId) => {
|
export const removeMember = (memberId) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/proj/api/v1/project-member/${memberId}`,
|
url: `/proj/api/v1/project-member/${memberId}`,
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="project-member-container">
|
<div class="member-container">
|
||||||
<el-card>
|
<el-card>
|
||||||
<!-- 搜索栏 -->
|
<!-- 搜索栏 -->
|
||||||
<el-form :inline="true" :model="searchForm">
|
<el-form :inline="true" :model="searchForm">
|
||||||
<el-form-item label="项目">
|
<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
|
<el-option
|
||||||
v-for="project in projectOptions"
|
v-for="project in projectOptions"
|
||||||
:key="project.projectId"
|
:key="project.projectId"
|
||||||
@ -13,25 +13,53 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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-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-item>
|
||||||
</el-form>
|
</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 :data="tableData" border v-loading="loading">
|
||||||
<el-table-column prop="userId" label="用户ID" width="100" />
|
<el-table-column prop="userId" label="用户ID" width="100" />
|
||||||
<el-table-column prop="role" label="项目角色" width="120">
|
<el-table-column prop="role" label="项目角色" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag v-if="row.role === 'pm'" type="danger">项目经理</el-tag>
|
{{ getRoleText(row.role) }}
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="joinDate" label="加入日期" width="120" />
|
<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">
|
<el-table-column prop="workload" label="工作量占比" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.workload }}%
|
{{ row.workload }}%
|
||||||
@ -43,11 +71,23 @@
|
|||||||
<el-tag v-else type="info">已离开</el-tag>
|
<el-tag v-else type="info">已离开</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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 prop="createdTime" label="创建时间" width="180" />
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
<el-table-column label="操作" width="250" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
<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>
|
<el-button type="danger" size="small" @click="handleRemove(row)">移除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -57,12 +97,12 @@
|
|||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="form.memberId ? '编辑成员' : '添加成员'"
|
:title="form.memberId ? '编辑项目成员' : '添加项目成员'"
|
||||||
width="600px"
|
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-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
|
<el-option
|
||||||
v-for="project in projectOptions"
|
v-for="project in projectOptions"
|
||||||
:key="project.projectId"
|
:key="project.projectId"
|
||||||
@ -73,7 +113,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="用户ID" prop="userId">
|
<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>
|
||||||
|
|
||||||
<el-form-item label="项目角色" prop="role">
|
<el-form-item label="项目角色" prop="role">
|
||||||
@ -98,28 +138,12 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="离开日期">
|
<el-form-item label="工作量占比">
|
||||||
<el-date-picker
|
<el-input-number v-model="form.workload" :min="0" :max="100" style="width: 100%;" />
|
||||||
v-model="form.leaveDate"
|
|
||||||
type="date"
|
|
||||||
placeholder="选择日期"
|
|
||||||
style="width: 100%;"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</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-form-item label="备注说明">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.remark"
|
v-model="form.remark"
|
||||||
@ -142,16 +166,19 @@
|
|||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
getProjectMembers,
|
getMemberListByProject,
|
||||||
addProjectMember,
|
addMember,
|
||||||
updateProjectMember,
|
updateMember,
|
||||||
removeProjectMember
|
removeMember,
|
||||||
|
updateMemberStatus
|
||||||
} from '../../api/projectMember'
|
} from '../../api/projectMember'
|
||||||
import { getProjectList } from '../../api/project'
|
import { getProjectList } from '../../api/project'
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
projectId: null
|
projectId: null,
|
||||||
|
role: '',
|
||||||
|
status: null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表格数据
|
// 表格数据
|
||||||
@ -172,9 +199,7 @@ const form = reactive({
|
|||||||
userId: null,
|
userId: null,
|
||||||
role: 'member',
|
role: 'member',
|
||||||
joinDate: null,
|
joinDate: null,
|
||||||
leaveDate: null,
|
|
||||||
workload: 0,
|
workload: 0,
|
||||||
status: 1,
|
|
||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -187,7 +212,7 @@ const rules = {
|
|||||||
{ required: true, message: '请输入用户ID', trigger: 'blur' }
|
{ required: true, message: '请输入用户ID', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
role: [
|
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 () => {
|
const fetchData = async () => {
|
||||||
if (!searchForm.projectId) {
|
if (!searchForm.projectId) {
|
||||||
ElMessage.warning('请选择项目')
|
tableData.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getProjectMembers(searchForm.projectId)
|
let data = await getMemberListByProject(searchForm.projectId)
|
||||||
tableData.value = res
|
|
||||||
|
// 前端过滤
|
||||||
|
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) {
|
} catch (error) {
|
||||||
console.error('加载数据失败:', error)
|
console.error('加载数据失败:', error)
|
||||||
ElMessage.error(error.message || '加载数据失败')
|
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 = () => {
|
const handleAdd = () => {
|
||||||
if (!searchForm.projectId) {
|
if (!searchForm.projectId) {
|
||||||
@ -233,9 +292,7 @@ const handleAdd = () => {
|
|||||||
userId: null,
|
userId: null,
|
||||||
role: 'member',
|
role: 'member',
|
||||||
joinDate: null,
|
joinDate: null,
|
||||||
leaveDate: null,
|
|
||||||
workload: 0,
|
workload: 0,
|
||||||
status: 1,
|
|
||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@ -249,9 +306,7 @@ const handleEdit = (row) => {
|
|||||||
userId: row.userId,
|
userId: row.userId,
|
||||||
role: row.role,
|
role: row.role,
|
||||||
joinDate: row.joinDate,
|
joinDate: row.joinDate,
|
||||||
leaveDate: row.leaveDate,
|
|
||||||
workload: row.workload,
|
workload: row.workload,
|
||||||
status: row.status,
|
|
||||||
remark: row.remark
|
remark: row.remark
|
||||||
})
|
})
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@ -270,10 +325,10 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (form.memberId) {
|
if (form.memberId) {
|
||||||
await updateProjectMember(form.memberId, form)
|
await updateMember(form.memberId, form)
|
||||||
ElMessage.success('更新成功')
|
ElMessage.success('更新成功')
|
||||||
} else {
|
} else {
|
||||||
await addProjectMember(form)
|
await addMember(form)
|
||||||
ElMessage.success('添加成功')
|
ElMessage.success('添加成功')
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
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) => {
|
const handleRemove = async (row) => {
|
||||||
try {
|
try {
|
||||||
@ -293,7 +369,7 @@ const handleRemove = async (row) => {
|
|||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
|
|
||||||
await removeProjectMember(row.memberId)
|
await removeMember(row.memberId)
|
||||||
ElMessage.success('移除成功')
|
ElMessage.success('移除成功')
|
||||||
await fetchData()
|
await fetchData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -311,7 +387,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.project-member-container {
|
.member-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user