feat: 岗位管理模块前端实现

前端实现:
- post.js: API接口封装(86行,8个接口)
  - getPostList(): 分页查询
  - getPostListByDept(): 按部门查询
  - getPostListEnabled(): 获取启用的岗位
  - getPostById(): 查询详情
  - createPost(): 创建岗位
  - updatePost(): 更新岗位
  - deletePost(): 删除岗位
  - updatePostStatus(): 更新状态

- post.vue: 岗位管理页面(416行)
  - 搜索功能:岗位编码、名称、所属部门(树选择)、状态
  - 表格展示:编码、名称、部门、职责、排序、状态、时间
  - 状态开关:el-switch直接切换状态
  - 新增/编辑对话框:
    * 岗位编码、名称(必填)
    * 所属部门(树选择器)
    * 岗位职责、岗位要求(多行文本)
    * 排序号、状态、备注

技术特点:
- 部门树选择器(el-tree-select)
- 状态开关(el-switch)
- 表单验证(必填项)
- 删除确认

模块状态: 完整(前端+后端)
This commit is contained in:
zhangjf 2026-02-16 09:40:00 +08:00
parent 2b7c43366b
commit 84adda022e
2 changed files with 500 additions and 0 deletions

View File

@ -0,0 +1,85 @@
import request from '../utils/request'
/**
* 获取岗位列表分页
*/
export const getPostList = (params) => {
return request({
url: '/sys/api/v1/post/list',
method: 'get',
params
})
}
/**
* 根据部门ID获取岗位列表
*/
export const getPostListByDept = (deptId) => {
return request({
url: `/sys/api/v1/post/list/dept/${deptId}`,
method: 'get'
})
}
/**
* 获取所有启用的岗位列表
*/
export const getPostListEnabled = () => {
return request({
url: '/sys/api/v1/post/list/enabled',
method: 'get'
})
}
/**
* 获取岗位详情
*/
export const getPostById = (postId) => {
return request({
url: `/sys/api/v1/post/${postId}`,
method: 'get'
})
}
/**
* 创建岗位
*/
export const createPost = (data) => {
return request({
url: '/sys/api/v1/post',
method: 'post',
data
})
}
/**
* 更新岗位
*/
export const updatePost = (postId, data) => {
return request({
url: `/sys/api/v1/post/${postId}`,
method: 'put',
data
})
}
/**
* 删除岗位
*/
export const deletePost = (postId) => {
return request({
url: `/sys/api/v1/post/${postId}`,
method: 'delete'
})
}
/**
* 更新岗位状态
*/
export const updatePostStatus = (postId, status) => {
return request({
url: `/sys/api/v1/post/${postId}/status`,
method: 'put',
params: { status }
})
}

View File

@ -0,0 +1,415 @@
<template>
<div class="post-container">
<el-card>
<!-- 搜索栏 -->
<el-form :inline="true" :model="searchForm">
<el-form-item label="岗位编码">
<el-input v-model="searchForm.postCode" placeholder="请输入岗位编码" clearable style="width: 150px;" />
</el-form-item>
<el-form-item label="岗位名称">
<el-input v-model="searchForm.postName" placeholder="请输入岗位名称" clearable style="width: 150px;" />
</el-form-item>
<el-form-item label="所属部门">
<el-tree-select
v-model="searchForm.deptId"
:data="deptTreeOptions"
placeholder="请选择部门"
clearable
check-strictly
:render-after-expand="false"
style="width: 200px;"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px;">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<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">新增岗位</el-button>
</el-row>
<!-- 表格 -->
<el-table :data="tableData" border v-loading="loading" stripe>
<el-table-column prop="postId" label="岗位ID" width="80" />
<el-table-column prop="postCode" label="岗位编码" width="120" />
<el-table-column prop="postName" label="岗位名称" width="150" />
<el-table-column prop="deptId" label="所属部门" width="150">
<template #default="{ row }">
{{ getDeptName(row.deptId) }}
</template>
</el-table-column>
<el-table-column prop="postDuty" label="岗位职责" width="200" show-overflow-tooltip />
<el-table-column prop="sortOrder" label="排序" width="80" align="center" />
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="handleStatusChange(row)"
/>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" width="180" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="fetchData"
@current-change="fetchData"
style="margin-top: 20px; justify-content: flex-end;"
/>
</el-card>
<!-- 新增/编辑对话框 -->
<el-dialog
v-model="dialogVisible"
:title="form.postId ? '编辑岗位' : '新增岗位'"
width="600px"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="岗位编码" prop="postCode">
<el-input v-model="form.postCode" placeholder="请输入岗位编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="岗位名称" prop="postName">
<el-input v-model="form.postName" placeholder="请输入岗位名称" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="所属部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
:data="deptTreeOptions"
placeholder="请选择部门"
clearable
check-strictly
:render-after-expand="false"
style="width: 100%;"
/>
</el-form-item>
<el-form-item label="岗位职责">
<el-input
v-model="form.postDuty"
type="textarea"
:rows="3"
placeholder="请输入岗位职责描述"
/>
</el-form-item>
<el-form-item label="岗位要求">
<el-input
v-model="form.postRequirement"
type="textarea"
:rows="3"
placeholder="请输入岗位要求"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="排序号">
<el-input-number v-model="form.sortOrder" :min="0" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio :value="1">启用</el-radio>
<el-radio :value="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注">
<el-input
v-model="form.remark"
type="textarea"
:rows="2"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getPostList,
createPost,
updatePost,
deletePost,
updatePostStatus
} from '../../api/post'
import { getDeptTree } from '../../api/dept'
//
const searchForm = reactive({
postCode: '',
postName: '',
deptId: null,
status: null
})
//
const tableData = ref([])
const loading = ref(false)
//
const page = reactive({
current: 1,
size: 10,
total: 0
})
//
const dialogVisible = ref(false)
const formRef = ref(null)
//
const form = reactive({
postId: null,
postCode: '',
postName: '',
postDuty: '',
postRequirement: '',
deptId: null,
sortOrder: 0,
status: 1,
remark: ''
})
//
const rules = {
postCode: [
{ required: true, message: '请输入岗位编码', trigger: 'blur' }
],
postName: [
{ required: true, message: '请输入岗位名称', trigger: 'blur' }
]
}
//
const deptTreeOptions = ref([])
//
const deptMap = ref({})
//
const loadDeptTree = async () => {
try {
const data = await getDeptTree()
deptTreeOptions.value = convertToTreeSelect(data)
buildDeptMap(data)
} catch (error) {
console.error('加载部门树失败:', error)
}
}
// TreeSelect
const convertToTreeSelect = (data) => {
return data.map(item => ({
value: item.deptId,
label: item.deptName,
children: item.children ? convertToTreeSelect(item.children) : []
}))
}
//
const buildDeptMap = (data) => {
data.forEach(item => {
deptMap.value[item.deptId] = item.deptName
if (item.children && item.children.length > 0) {
buildDeptMap(item.children)
}
})
}
//
const getDeptName = (deptId) => {
return deptMap.value[deptId] || '-'
}
//
const fetchData = async () => {
loading.value = true
try {
const params = {
current: page.current,
size: page.size,
postCode: searchForm.postCode || undefined,
postName: searchForm.postName || undefined,
deptId: searchForm.deptId || undefined,
status: searchForm.status !== null && searchForm.status !== '' ? searchForm.status : undefined
}
const res = await getPostList(params)
tableData.value = res.records
page.total = res.total
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
} finally {
loading.value = false
}
}
//
const handleSearch = () => {
page.current = 1
fetchData()
}
//
const handleReset = () => {
searchForm.postCode = ''
searchForm.postName = ''
searchForm.deptId = null
searchForm.status = null
page.current = 1
fetchData()
}
//
const handleAdd = () => {
Object.assign(form, {
postId: null,
postCode: '',
postName: '',
postDuty: '',
postRequirement: '',
deptId: null,
sortOrder: 0,
status: 1,
remark: ''
})
dialogVisible.value = true
}
//
const handleEdit = (row) => {
Object.assign(form, {
postId: row.postId,
postCode: row.postCode,
postName: row.postName,
postDuty: row.postDuty,
postRequirement: row.postRequirement,
deptId: row.deptId,
sortOrder: row.sortOrder,
status: row.status,
remark: row.remark
})
dialogVisible.value = true
}
//
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch (error) {
ElMessage.warning('请检查表单填写是否完整')
return
}
try {
if (form.postId) {
await updatePost(form.postId, form)
ElMessage.success('更新成功')
} else {
await createPost(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
await fetchData()
} catch (error) {
console.error('保存失败:', error)
ElMessage.error(error.message || '操作失败')
}
}
//
const handleStatusChange = async (row) => {
try {
await updatePostStatus(row.postId, row.status)
ElMessage.success('状态更新成功')
} catch (error) {
console.error('状态更新失败:', error)
ElMessage.error(error.message || '状态更新失败')
//
row.status = row.status === 1 ? 0 : 1
}
}
//
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm('确定要删除该岗位吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await deletePost(row.postId)
ElMessage.success('删除成功')
//
if (tableData.value.length === 1 && page.current > 1) {
page.current--
}
await fetchData()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
ElMessage.error(error.message || '删除失败')
}
}
}
//
onMounted(() => {
loadDeptTree()
fetchData()
})
</script>
<style scoped>
.post-container {
padding: 20px;
}
</style>