iHRM人力资源 - 员工管理 - 新增员工、员工详情
文章目录
一、新增员工页面
当我们点击新增按钮的时候,会路由到一个新的页面
我们之前使用的是弹层编辑或者是行内编辑,下面又是一种新的方式
1.1 页面结构
src/views/employee/detail.vue
1.2 路由
import layout from '@/layout'
export default {
path: '/employee',
component: layout,
children: [{
path: '',
name: 'employee',
component: () => import('@/views/employee'),
meta: {
title: '员工',
icon: 'people'
}
}, {
// 员工详情地址
path: '/employee/detail',
component: () => import('@/views/employee/detail.vue')
}
]
}
可以直接在路径上输入试一下
1.3 跳转到“新增员工”界面
这么跳转
按钮如下所示,便可以跳转
<el-button size="mini" type="primary" @click="$router.push('employee/detail')">添加员工</el-button>
但是出现了下面的问题,是框架的问题,改一下
import layout from '@/layout'
export default {
path: '/employee',
component: layout,
children: [{
path: '',
name: 'employee',
component: () => import('@/views/employee'),
meta: {
title: '员工',
icon: 'people'
}
}, {
// 员工详情地址
path: '/employee/detail',
component: () => import('@/views/employee/detail.vue'),
// 隐藏这个路由
hidden: true,
// 这个其实就是一个标题和图标而已
meta: {
title: '员工详情',
icon: 'people'
}
}
]
}
1.4 数据和校验
具体的规则
<template>
<div class="dashboard-container">
<div class="app-container">
<div class="edit-form">
<!--表单-->
<el-form ref="userForm" :model="userInfo" :rules="rules" label-width="220px">
<!-- 姓名 -->
<el-row>
<el-col :span="12">
<el-form-item label="姓名" prop="username">
<el-input v-model="userInfo.username" size="mini" class="inputW" />
</el-form-item>
</el-col>
</el-row>
<!-- 工号 -->
<el-row>
<el-col :span="12">
<el-form-item label="工号" prop="workNumber">
<!-- 工号是系统生成的 禁用这个组件-->
<el-input v-model="userInfo.workNumber" disabled size="mini" class="inputW" />
</el-form-item>
</el-col>
</el-row>
<!--手机 -->
<el-row>
<el-col :span="12">
<el-form-item label="手机" prop="mobile">
<el-input
v-model="userInfo.mobile"
size="mini"
class="inputW"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="部门" prop="departmentId">
<!-- 放置及联部门组件 会单独封装-->
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="聘用形式" prop="formOfEmployment">
<el-select v-model="userInfo.formOfEmployment" size="mini" class="inputW">
<el-option label="正式" :value="1" />
<el-option label="非正式" :value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="入职时间" prop="timeOfEntry">
<el-date-picker
v-model="userInfo.timeOfEntry"
size="mini"
type="date"
value-format="yyyy-MM-dd"
class="inputW"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="转正时间" prop="correctionTime">
<el-date-picker
v-model="userInfo.correctionTime"
size="mini"
type="date"
class="inputW"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 员工照片 -->
<el-row>
<el-col :span="12">
<el-form-item label="员工头像">
<!-- 放置上传图片 -->
</el-form-item>
</el-col>
</el-row>
<!-- 保存个人信息 -->
<el-row type="flex">
<el-col :span="12" style="margin-left:220px">
<el-button size="mini" type="primary" @click="saveData">保存更新</el-button>
</el-col>
</el-row>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
userInfo: {
username: '', // 用户名
mobile: '', // 手机号
workNumber: '', // 工号
formOfEmployment: null, // 聘用形式
departmentId: null, // 部门id
timeOfEntry: '', // 入职时间
correctionTime: '' // 转正时间
},
rules: {
username: [{ required: true, message: '请输入姓名', trigger: 'blur' }, {
min: 1, max: 4, message: '姓名为1-4位'
}],
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' }, {
// pattern 正则表达式
pattern: /^1[3-9]\d{9}$/,
message: '手机号格式不正确',
trigger: 'blur'
}],
formOfEmployment: [{ required: true, message: '请选择聘用形式', trigger: 'blur' }],
departmentId: [{ required: true, message: '请选择部门', trigger: 'blur' }],
timeOfEntry: [{ required: true, message: '请选择入职时间', trigger: 'blur' }],
correctionTime: [{ required: true, message: '请选择转正时间', trigger: 'blur' }, {
validator: (rule, value, callback) => {
if (this.userInfo.timeOfEntry) {
if (new Date(this.userInfo.timeOfEntry) > new Date(value)) {
// 如果失败就callback一个错误
callback(new Error('转正时间不能小于入职时间'))
return
}
}
// 如果成功就callback即可
callback()
}
}]
}
}
},
methods: {
saveData() {
this.$refs.userForm.validate()
}
}
}
</script>
二、级联组件封装
2.1 实现思路
如下图所示的内容
整体思路如下图所示
级联组件如下图所示
当一个数据集合有清晰的层级结构时,可通过级联选择器逐级查看并选择
2.2 级联组件
2.2.1 创建组件
然后再detail.vue中引入此组件
import SelectTree from '@/views/employee/select-tree.vue'
export default {
// 组件注册
components: { SelectTree }
...
}
引入使用
<el-row>
<el-col :span="12">
<el-form-item label="部门" prop="departmentId">
<!-- 放置及联部门组件 会单独封装-->
<select-tree/>
</el-form-item>
</el-col>
</el-row>
效果图
2.2.2 向级联组件赋值数据
工具类的api
/**
* 列表数据转树形数据
* rootValue: 其实就是pid(父id)
*/
export function transListToTreeData(list, rootValue) {
const arr = []
list.forEach(item => {
if (item.pid === rootValue) {
// 找到了匹配的节点
arr.push(item)
// 当前节点的id和当前节点的字节点的pid相等
// 下面的方法其实就是找当前节点的子节点
const children = transListToTreeData(list, item.id) // 找到的节点的子节点
item.children = children // 将子节点赋值给当前节点
// 我们先push再赋值childern也没关系,因为是一个对象,地址是一样的
}
})
return arr
}
级联组件代码
<template>
<!--防止element-ui的级联组件-->
<el-cascader size="mini" :options="treeData" :props="props"></el-cascader>
</template>
<script>
import { getDepartment } from '@/api/department'
import { transListToTreeData } from '@/utils'
export default {
data() {
return {
// 赋值给我们的级联组件options属性的
treeData: [],
// 声明绑定的对象
props: {
// 要展示的字段
label: 'name',
// 要存储的字段
value: 'id'
}
}
},
created() {
this.getDepartment()
},
methods: {
async getDepartment() {
const result = await getDepartment()
this.treeData = transListToTreeData(result, 0)
}
}
}
</script>
效果图
我们发现某个部门下没有数据了,但是选择不上,后面会修复这个bug
因为我们将列表数据转成树形结构的时候,如果一个部门没有子部门的话,我们也给了一个children属性,导致出现了这个情况
改的话,当每个部门没有子部门的时候,就不给children属性了
2.2.3 修改bug
/**
* 列表数据转树形数据
* rootValue: 其实就是pid(父id)
*/
export function transListToTreeData(list, rootValue) {
const arr = []
list.forEach(item => {
if (item.pid === rootValue) {
// 找到了匹配的节点
arr.push(item)
// 当前节点的id和当前节点的字节点的pid相等
// 下面的方法其实就是找当前节点的子节点
const children = transListToTreeData(list, item.id) // 找到的节点的子节点
if (children.length) {
item.children = children // 将子节点赋值给当前节点
}
// 我们先push再赋值childern也没关系,因为是一个对象,地址是一样的
}
})
return arr
}
效果图
相当的完美
2.2.4 设置分割符为横线
<template>
<!--防止element-ui的级联组件-->
<el-cascader
size="mini"
:options="treeData"
:props="props"
separator="-"
></el-cascader>
</template>
2.2.5 级联组件双向绑定
- 父组件修改后,子组件也修改
<el-cascader
size="mini"
:options="treeData"
:props="props"
separator="-"
:value="value"
></el-cascader>
props: {
// v-model接收的属性值必须是value
// 要把这个属性绑定给级联属性
value: {
// 存储的是部门的id
type: Number,
default: null
}
}
在父组件中绑定
<el-col :span="12">
<el-form-item label="部门" prop="departmentId">
<!-- 放置及联部门组件 会单独封装-->
<!--inputW样式会给到select-tree组件中的template第一层的组件-->
<select-tree class="inputW" v-model="userInfo.departmentId"/>
</el-form-item>
</el-col>
data() {
return {
userInfo: {
username: '', // 用户名
mobile: '', // 手机号
workNumber: '', // 工号
formOfEmployment: null, // 聘用形式
departmentId: null, // 部门id
timeOfEntry: '', // 入职时间
correctionTime: '' // 转正时间
},
}
}
当我们把detail的组件的departmentId改成2后,如下图所示,会有一个双向绑定
相当于我们把部门的主键给级联组件后,级联组件就会帮我们找
- 子组件修改后,父组件也修改
我们上面完成了一条线,下面再来完成另一条线
当我们选择某个部门后,id发生了变化,那我们应该去触发input的事件
<!--element-ui的级联组件-->
<el-cascader
size="mini"
:options="treeData"
:props="props"
separator="-"
:value="value"
@change="changeValue"
></el-cascader>
// 参数1:数组
changeValue(list) {
// 取到数组的最后一位
if (list.length > 0) {
// 将最后一位的id取出,传给了select-tree组件上的v-model属性
// 而v-model属性又监听了input,所以把id赋值到了userInfo.departmentId
this.$emit('input', list[list.length - 1])
} else {
// 如果没有内容,我们就把值设置为空即可
this.$emit('input', null)
}
}
三、新增员工
流程图如下所示
- api请求
/**
* 新增员工
*/
export function addEmployee(data) {
return request({
url: '/sys/user',
method: 'post',
data
})
}
- 按钮
<!-- 保存个人信息 -->
<el-row type="flex">
<el-col :span="12" style="margin-left:220px">
<el-button
size="mini"
type="primary"
@click="saveData"
>保存更新
</el-button>
</el-col>
</el-row>
- 方法
methods: {
saveData() {
this.$refs.userForm.validate(async isOK => {
if (isOK) {
// 校验通过
await addEmployee(this.userInfo)
this.$message.success('新增成功')
// 跳转到列表也
this.$router.push('/employee')
}
})
}
}
- 添加完成后的效果图
四、编辑员工
4.1 数据回显
点击查看按钮,跳转到详情页面的时候,要写到这条数据的id(当前点击行数据的id),有了id之后我们才能完成数据的回显
"查看"按钮在这个组件里面
- 点击“查看”按钮,跳转到详情页面,并且携带数据id
加了一个路由参数
<!--路由后面加了一个参数,也就是路由的参数-->
<el-button size="mini" type="text" @click="$router.push(`/employee/detail/${row.id}`)">查看</el-button>
修改一下路由
{
// 员工详情地址
// path: '/employee/detail',
// 这样后就是一个动态的路由参数,此路由参数的字段名是id
// “?”的意思id这个参数可能有,也可能没有,
path: '/employee/detail/:id',
component: () => import('@/views/employee/detail.vue'),
// 隐藏这个路由
hidden: true,
// 这个其实就是一个标题和图标而已
meta: {
title: '员工详情',
icon: 'people'
}
}
2.在详情页获取路由参数id
- api请求
/**
* 查询员工
*/
export function getEmployeeDetail(id) {
return request({
url: `/sys/user/${id}`,
})
}
- 方法
created() {
// 如何获取路由参数中的id
if (this.$route.params.id) {
// 有id的情况下崽调用
this.getEmployeeDetail()
}
}
methods: {
async getEmployeeDetail() {
this.userInfo = await getEmployeeDetail(this.$route.params.id)
}
...
}
- 效果图
4.2 保存
其实就是在新增的基础上,加了一个判断是不是编辑模式,如果是的话,就调用编辑api方法
- 按钮
<!-- 保存个人信息 -->
<el-row type="flex">
<el-col :span="12" style="margin-left:220px">
<el-button
size="mini"
type="primary"
@click="saveData"
>保存更新
</el-button>
</el-col>
</el-row>
- api
/**
* 编辑接口
*/
export function updateEmployee(data) {
return request({
url: `/sys/user/${data.id}`,
method: 'post',
data
})
}
- 方法
saveData() {
this.$refs.userForm.validate(async isOK => {
// 校验通过
if (isOK) {
// 编辑模式
if (this.$route.params.id) {
await updateEmployee(this.userInfo)
this.$message.success('更新员工成功')
} else {
// 新增模式
await addEmployee(this.userInfo)
this.$message.success('新增成功')
}
// 跳转到列表
this.$router.push('/employee')
}
})
}
- 修改模式下不让修改手机号
<el-col :span="12">
<!--修改模式下不让修改手机号,两个叹号,第一个,将值取反成布尔类型的值,第二个,再取回来-->
<el-form-item label="手机" prop="mobile">
<el-input
:disabled="!!$route.params.id"
v-model="userInfo.mobile"
size="mini"
class="inputW"
/>
</el-form-item>
</el-col>
五、员工上传头像组件
效果及流程图
5.1 封装组件
创建新组件
<template>
<!--上传的组件-->
<!--action是上传的地址,但是我们的项目不用,因为action属于自动上传,而我们目前的项目是手动上传-->
<!--虽然我们不使用action,但是也要留着此属性,否则控制台报错-->
<!--show-file-list表示展示文件的上传列表,我们目前只上传一个头像而已,不需要列表-->
<!--before-upload 上传文件之前,此方法主要是为了上传文件前进行检查-->
<el-upload
class="avatar-uploader"
action=""
:show-file-list="false"
:before-upload="beforeAvatarUpload"
>
<!-- (自动上传)action是上传地址 人资项目不需要 人资项目(手动上传) -->
<!-- show-file-list不展示列表 -->
<img v-if="value" :src="value" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"/>
</el-upload>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
methods: {
// 检查函数 判断文件的类型还有大小 return true(继续上传)/false(停止上传)
beforeAvatarUpload(file) {
// jpeg png gif bmp
const isJPG = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp'].includes(file.type)
// 由byte转换成MB
const isLt2M = file.size / 1024 / 1024 < 5 // 5M
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG PNG GIF BMP 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 5MB!')
}
return isJPG && isLt2M
}
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
在下面这个组件中应用
<!-- 员工照片 -->
<el-row>
<el-col :span="12">
<el-form-item label="员工头像">
<!-- 放置上传图片 -->
<image-upload/>
</el-form-item>
</el-col>
</el-row>
效果图
5.2 图片回显
- 父组件向子组件传输
父组件
<!-- 员工照片 -->
<el-row>
<el-col :span="12">
<el-form-item label="员工头像">
<!-- 放置上传图片 -->
<image-upload v-model="userInfo.staffPhoto"/>
</el-form-item>
</el-col>
</el-row>
子组件 - 员工头像上传组件
<el-upload
class="avatar-uploader"
action=""
:show-file-list="false"
:before-upload="beforeAvatarUpload"
>
<!--下面是,如果有图片的话,就显示图片,没有图片的话,就显示一个图标-->
<!--下面的value值是父组件传输过来的一个值-->
<img v-if="value" :src="value" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"/>
</el-upload>
props: {
// 接收传输过来的value值
value: {
type: String,
default: ''
}
}
此时的效果图
目前只实现了蓝色的部分,没有实现绿色部分
子组件向父组件传输这部分没有完成
当我们使用组件修改完头像的时候,头像也要形成一个回显操作
5.3 自定义上传图片
要完成定义上传图片功能,我们要上传到腾讯云服务器,我们上传后会给我们返回一个地址,地址执行我们的图片
我们要通过input把这个图片传出去
默认有个action自动上传,但是我们制成了空字符串,因为我们要采用一个自定义上传的方式(手动上传)
需要自己注册一个账号
- 增加http-request属性
翻阅文档,有如下Attribute(是一个属性,不是一个事件),我们监听他需要给他传一个函数,不是用@修饰,而是用冒号修饰
如下所示
<el-upload
class="avatar-uploader"
action=""
:show-file-list="false"
:before-upload="beforeAvatarUpload"
:http-request="uploadImage"
>
- 安装腾讯云上传SDK
npm i cos-js-sdk-v5
import COS from 'cos-js-sdk-v5'
- 上传方法
// 选择图片之后上传,上传的内容是params
uploadImage(params) {
// 完成COS对象的初始化
const cos = new COS({
SecretId: 'AKIDDSdjgnjT1NZ3a7VjkfVIwOdfv9IH2b8e',
SecretKey: 'WEwe9WJ9vLeq1BHNLLKF5Up10ndUDk24'
})
// 参数1:对象, 参数2:回调函数,err表示报错信息,data表示要返回的数据
cos.putObject({
Bucket: 'heimachengxuyuan-1302806742', // 存储桶名称
Region: 'ap-nanjing', // 地域名称
Key: params.file.name, // 文件名称
StorageClass: 'STANDARD', // 固定值
Body: params.file // 文件对象
}, (err, data) => {
if (data.statusCode === 200 && data.Location) {
// 拿到了腾讯云返回的地址
// 通过input自定义事件将地址传出去
this.$emit('input', 'http://' + data.Location) // 将地址返回了
} else {
this.$message.error(err.Message) // 上传失败提示消息
}
})
}
方法中的params参数,我们需要里面的file
this.$emit(‘input’, ‘http://’ + data.Location)
正好和父组件对应
子组件向父组件传输完成!!!niubi!!!