# 员工管理模块
需求:管理员工相关的信息(员工信息的增删改查,支持批量导入,支持角色授权等)
# 通用工具栏的组件结构
在后续的业务开发中,经常会用到一个类似下图的工具栏,作为公共组件,进行一下封装

组件 src/components/PageTools/index.vue
<template>
<el-card>
<div class="page-tools">
<!-- 左侧 -->
<div class="left">
<div class="tips">
<i class="el-icon-info" />
<span>本月: 社保在缴 公积金在缴</span>
</div>
</div>
<div class="right">
<!-- 右侧 -->
<slot name="right" />
</div>
</div>
</el-card>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
.page-tools {
display: flex;
justify-content: space-between;
align-items: center;
.tips {
line-height: 34px;
padding: 0px 15px;
border-radius: 5px;
border: 1px solid rgba(145, 213, 255, 1);
background: rgba(230, 247, 255, 1);
i {
margin-right: 10px;
color: #409eff;
}
}
}
</style>
- 通过全局插件的方式扩展全局组件(这样的话,其他组件中可以直接使用这个全局组件)
/*
封装Vue插件
*/
import PageTools from '@/components/PageTools/index.vue'
export default {
install(Vue, options) {
// 扩展一个自定义指令,处理图片加载失败的情况
// <img v-imgerror='default.png' src="a.png" alt=""/>
Vue.directive('imgerror', {
// bindings包含指令相关的参数信息
inserted(el, bindings) {
// console.dir(bindings)
// 如何知道img标签图片加载失败了?
el.onerror = () => {
// 加载失败后触发该函数
el.src = bindings.value || options.defaultImg
}
}
})
// 扩展全局组件
Vue.component('page-tools', PageTools)
}
}
效果图:

# 具名插槽优化
左边右边的内容不是写死的, 而是通过插槽定制的, 且如果左侧插槽没有传, 那么左边的tips就没有必要显示了
tips: 通过 $slots 可以拿到所有的插槽列表,$slot.插槽名可以用于判断该插槽是否传值
<template>
<el-card>
<div class="page-tools">
<!-- 左侧 -->
<div class="left">
<div v-if="$slots.left" class="tips">
<i class="el-icon-info" />
<slot name="left" />
</div>
</div>
<!-- 右侧 -->
<div class="right">
<slot name="right" />
</div>
</div>
</el-card>
</template>
- 组件中使用示例
<template>
<div class="employees-container">
<div class="app-container">
<page-tools>
<template #left>
<span>本月: 社保在缴 公积金在缴</span>
</template>
<template #right>
<el-button type="primary" size="small">历史归档</el-button>
<el-button type="primary" size="small">导出</el-button>
</template>
</page-tools>
</div>
</div>
</template>
总结:封装组件时,可以通过插槽定制组件的默认行为
- 普通默认插槽
- 具名插槽
- 作用域插槽
# 员工列表页面
目标:实现员工列表页面的基本布局和结构组件文件路径
src/employees/index.vue
<template>
<div class="employees-container">
<div class="app-container">
<page-tools>
<template #left>
<span>总记录数: 16 条</span>
</template>
<template #right>
<el-button type="warning" size="small">excel导入</el-button>
<el-button type="danger" size="small">excel导出</el-button>
<el-button type="primary" size="small">新增员工</el-button>
</template>
</page-tools>
<el-card style="margin-top: 10px;">
<el-table border>
<el-table-column label="序号" sortable="" />
<el-table-column label="姓名" sortable="" />
<el-table-column label="工号" sortable="" />
<el-table-column label="聘用形式" sortable="" />
<el-table-column label="部门" sortable="" />
<el-table-column label="入职时间" sortable="" />
<el-table-column label="账户状态" sortable="" />
<el-table-column label="操作" sortable="" fixed="right" width="280">
<template>
<el-button type="text" size="small">查看</el-button>
<el-button type="text" size="small">转正</el-button>
<el-button type="text" size="small">调岗</el-button>
<el-button type="text" size="small">离职</el-button>
<el-button type="text" size="small">角色</el-button>
<el-button type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div style="height: 60px; margin-top: 10px">
<el-pagination layout="prev, pager, next" />
</div>
</el-card>
</div>
</div>
</template>
总结:顶部基于封装的PageTools组件实现导航;下面是员工列表布局(表格和分页布局)
# 获取员工列表数据
**
目标**实现员工的数据加载渲染
- 封装获取员工数据的接口
- 获取之后填充表格数据
- 首先,封装员工的加载请求
src/api/employees.js
// 获取员工列表数据
export function reqGetEmployeeList(options) {
return request({
methods: 'get',
url: '/sys/user',
params: options
})
}
- 然后,实现加载数据和分页的逻辑
import { reqGetEmployeeList } from '@/api/employees'
export default {
name: 'Employees',
data () {
return {
// 员工列表数据
list: [],
// 列表总数
total: 0,
// 查询参数
filterParams: {
page: 1,
size: 10
}
}
},
created () {
this.loadEmployeeList()
},
methods: {
// 获取员工列表数据
async loadEmployeeList () {
try {
const ret = await reqGetEmployeeList(this.filterParams)
this.list = ret.data.rows
this.total = ret.data.total
if (!ret.success) {
this.$message.error(ret.message)
}
} catch {
this.$message.error('获取员工列表失败')
}
}
}
}
- 把数据绑定到表格
<el-card style="margin-top: 10px;">
<el-table v-loading="loading" border :data="list">
<el-table-column label="序号" type="index" sortable="" />
<el-table-column label="姓名" prop="username" sortable="" />
<el-table-column label="工号" prop="workNumber" sortable="" />
<el-table-column label="聘用形式" prop="formOfEmployment" sortable="" />
<el-table-column label="部门" prop="departmentName" sortable="" />
<el-table-column label="入职时间" prop="timeOfEntry" sortable="" />
<el-table-column label="账户状态" prop="enableState" sortable="" />
<el-table-column label="操作" sortable="" fixed="right" width="280">
<template>
<el-button type="text" size="small">查看</el-button>
<el-button type="text" size="small">转正</el-button>
<el-button type="text" size="small">调岗</el-button>
<el-button type="text" size="small">离职</el-button>
<el-button type="text" size="small">角色</el-button>
<el-button type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div style="height: 60px; margin-top: 10px">
<el-pagination layout="prev, pager, next" />
</div>
</el-card>
总结:调用接口;获取数据;填充页面
# 员工列表的分页处理
目标: 处理分页数据
- 分页结构
<div style="height: 60px; margin-top: 10px">
<el-pagination
:total="total"
:current-page="page"
:page-size="size"
layout="prev, pager, next"
@current-change="handleCurrentChange"
/>
</div>
- 代码完善
handleCurrentChange(index) {
this.filterParams.page = index
this.loadEmployeeList()
}
# 员工列表数据格式化
目标:将列表中的内容进行格式化列表中的 聘用形式 / 入职时间 和 账户状态 需要进行显示内容的处理
# 列格式化-处理聘用形式
该数据的存放文件位于我们提供的**
资源/枚举中,可以将枚举下的文件夹放于src/api**文件夹下@/api/constant/employees.js文件1 表示正式;2 表示非正式
- 聘用形式数据
@/api/constant/employees.js
// 聘用形式
hireType: [{
id: 1,
value: '正式'
}, {
id: 2,
value: '非正式'
}],
- 基于列的formatter定制列表数据
<el-table-column label="聘用形式" prop="formOfEmployment" sortable="" :formatter='formatHireType'/>
import Types from '@/api/constant/employees'
// 格式化聘用形式
formatHireType (row) {
// find的返回值是其中一项数据
const obj = Types.hireType.find(item => {
return item.id === row.formOfEmployment
})
return obj.value
},
- 基于过滤器方式实现
<el-table-column label="聘用形式" prop="formOfEmployment" sortable="">
<template v-slot='scope'>
{{scope.row.formOfEmployment|formatType}}
</template>
</el-table-column>
filters: {
// 定义过滤器格式化聘用形式
formatType (value) {
// find的返回值是其中一项数据
const obj = Types.hireType.find(item => {
return item.id === value
})
return obj.value
}
},
总结:
- 基于formatter属性实现,这是由ElementUI提供的规则
- 基于Vue的过滤器实现(作用域插槽)
# 过滤器-处理时间格式
针对入职时间,我们可以采用 作用域插槽 用过滤器进行处理
<el-table-column label="入职时间" prop="timeOfEntry" sortable="">
<template #default="{ row }">
{{ row.timeOfEntry | formatTime }}
</template>
</el-table-column>
- 通过插件机制扩展过滤器
/*
封装Vue插件
*/
import PageTools from '@/components/PageTools/index.vue'
import moment from 'moment'
export default {
install(Vue, options) {
// 扩展一个自定义指令,处理图片加载失败的情况
// <img v-imgerror='default.png' src="a.png" alt=""/>
Vue.directive('imgerror', {
// bindings包含指令相关的参数信息
inserted(el, bindings) {
// console.dir(bindings)
// 如何知道img标签图片加载失败了?
el.onerror = () => {
// 加载失败后触发该函数
el.src = bindings.value || options.defaultImg
}
}
})
// 扩展全局组件
Vue.component('page-tools', PageTools)
// 扩展过滤器
Vue.filter('formatTime', (value) => {
return moment(value).format('yyyy-MM-DD')
})
}
}
总结:
- 过滤器的定义方式
- 插件的用法
- 日期处理第三方包moment用法
# 账户状态-switch开关
账户状态,可以用开关组件switch进行显示, 这里用 :value, 将来发送请求, 请求成功了再更新状态
<el-table-column label="账户状态" prop="enableState" sortable="">
<template #default="{ row }">
<el-switch
:value="row.enableState === 1"
active-color="#13ce66"
inactive-color="#ff4949"
/>
</template>
</el-table-column>
总结:
- 作用域插槽
- el-switch组件的基本使用
# 删除员工
**
目标**实现删除员工的功能(接口文档中没有体现, 但是实际有这个接口)
- 封装接口
- 绑定事件
- 调用接口删除
- 刷新列表
- 首先封装 删除员工的请求
export function reqDelEmployee(id) {
return request({
method: 'delete',
url: `/sys/user/${id}`
})
}
- 删除事件绑定
<el-table-column label="操作" sortable="" fixed="right" width="280">
<template #default="{ row }">
<el-button type="text" size="small">查看</el-button>
<el-button type="text" size="small">转正</el-button>
<el-button type="text" size="small">调岗</el-button>
<el-button type="text" size="small">离职</el-button>
<el-button type="text" size="small">角色</el-button>
<el-button type="text" size="small" @click="handleDelete(row.id)">删除</el-button>
</template>
</el-table-column>
// 删除员工
handleDelete (id) {
// 删除部门
this.$confirm('确认要删除员工吗?, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
// 确认删除
const ret = await reqDelEmployee(id)
if (!ret.success) {
this.$message.error(ret.message)
} else {
// 刷新列表
if (this.total % this.filterParams.size === 1 && this.filterParams.page > 1) {
this.filterParams.page -= 1
}
this.loadEmployeeList()
}
}).catch((e) => {
if (e !== 'cancel') {
this.$message.error('删除员工失败')
}
})
},
总结:封装接口;绑定事件;调用接口删除;刷新列表
# 新增员工
# 新建员工弹层组件
类似**
组织架构**的组件,同样新建一个弹层组件src/views/employees/components/add-employee.vue
<template>
<el-dialog title="新增员工" :visible="showDialog">
<!-- 表单 -->
<el-form label-width="120px">
<el-form-item label="姓名">
<el-input style="width:50%" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="手机">
<el-input style="width:50%" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="入职时间">
<el-date-picker style="width:50%" placeholder="请选择入职时间" />
</el-form-item>
<el-form-item label="聘用形式">
<el-select style="width:50%" placeholder="请选择" />
</el-form-item>
<el-form-item label="工号">
<el-input style="width:50%" placeholder="请输入工号" />
</el-form-item>
<el-form-item label="部门">
<el-input style="width:50%" placeholder="请选择部门" />
</el-form-item>
<el-form-item label="转正时间">
<el-date-picker style="width:50%" placeholder="请选择转正时间" />
</el-form-item>
</el-form>
<!-- footer插槽 -->
<template v-slot:footer>
<el-button>取消</el-button>
<el-button type="primary">确定</el-button>
</template>
</el-dialog>
</template>
<script>
export default {
props: {
showDialog: {
type: Boolean,
default: false
}
}
}
</script>
<style>
</style>
# 控制弹窗的显示
- 父组件中引用,弹出层
import AddEmployee from './components/add-employee'
components: {
AddEmployee
},
<add-employee :show-dialog.sync="showDialog" />
- 点击按钮, 显示弹层
<el-button icon="plus" type="primary" size="small" @click="showDialog = true">新增员工</el-button>
- top 属性, 配置弹层位置(top是dialog组件的属性,值vh是相对单位)
<el-dialog title="新增员工" :visible="showDialog" top="8vh">
# 添加 dialog 的关闭功能
- 给父组件传递的prop时, 加上 .sync 修饰符
<add-employee :show-dialog.sync="showDialog" />
- 注册事件, 提供方法, 关闭弹层
<el-dialog title="新增员工" :visible="showDialog" top="8vh" @close="handleClose">
<el-button @click="handleClose">取消</el-button>
closeDialog() {
this.$emit('update:showDialog', false)
}
总结:控制弹窗的显示和隐藏
- 父组件向子组件传值
- 子组件向父组件传值
- 父和子之间传值的简化写法 sync修饰符用法
add-employee.vue准备数据
data() {
return {
formData: {
username: '', // 用户名
mobile: '', // 手机号
formOfEmployment: '', // 聘用形式
workNumber: '', // 工号
departmentName: '', // 部门
timeOfEntry: '', // 入职时间
correctionTime: '' // 转正时间
}
}
},
- 绑定数据, 绑定校验
<el-form ref="addForm" :model="formData" :rules="rules" label-width="120px">
<el-form-item label="姓名" prop="username">
<el-input v-model="formData.username" style="width:50%" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="手机" prop="mobile">
<el-input v-model="formData.mobile" style="width:50%" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="入职时间" prop="timeOfEntry">
<el-date-picker v-model="formData.timeOfEntry" style="width:50%" placeholder="请选择入职时间" />
</el-form-item>
<el-form-item label="聘用形式" prop="formOfEmployment">
<el-select v-model="formData.formOfEmployment" style="width:50%" placeholder="请选择" />
</el-form-item>
<el-form-item label="工号" prop="workNumber">
<el-input v-model="formData.workNumber" style="width:50%" placeholder="请输入工号" />
</el-form-item>
<el-form-item label="部门" prop="departmentName">
<el-input v-model="formData.departmentName" style="width:50%" placeholder="请选择部门" />
</el-form-item>
<el-form-item label="转正时间" prop="correctionTime">
<el-date-picker v-model="formData.correctionTime" style="width:50%" placeholder="请选择转正时间" />
</el-form-item>
</el-form>
- 指定规则
rules: {
username: [
{ required: true, message: '用户姓名不能为空', trigger: ['blur', 'change'] },
{ min: 1, max: 4, message: '用户姓名为1-4位', trigger: ['blur', 'change'] }
],
mobile: [
{ required: true, message: '手机号不能为空', trigger: ['blur', 'change'] },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: ['blur', 'change'] }
],
formOfEmployment: [
{ required: true, message: '聘用形式不能为空', trigger: ['blur', 'change'] }
],
workNumber: [
{ required: true, message: '工号不能为空', trigger: ['blur', 'change'] }
],
departmentName: [
{ required: true, message: '部门不能为空', trigger: ['blur', 'change'] }
],
timeOfEntry: [
{ required: true, message: '请选择入职时间', trigger: ['blur', 'change'] }
]
}
总结:表单验证基本配置
# 聘用形式数据填充
- 导入常量数据
import Types from '@/api/constant/employees.js'
- 配置data数据
hireType: Types.hireType,
- 模板中使用数据
<el-form-item label="聘用形式" prop="formOfEmployment">
<el-select v-model="formData.formOfEmployment" style="width:50%" placeholder="请选择">
<el-option
v-for="item in hireType"
:key="item.id"
:label="item.value"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
# 部门数据转化树形格式
员工的部门是从树形部门中选择一个部门
<el-form-item label="部门" prop="departmentName">
<el-input @focus="getDepartments" v-model="formData.departmentName" style="width:50%" placeholder="请选择部门" />
</el-form-item>
import { reqGetDepartments } from '@/api/departments'
data() {
return {
...,
depts: [], // 定义数组接收树形数据
}
},
methods: {
// 把列表数据转换为树形结构
translateListToTreeData (list, id) {
// 遍历每一个元素,并且判断每一项pid是否可以参数的pid一致,这样可以找到参数pid所属的下属部门
const arr = []
list.forEach(item => {
if (item.pid === id) {
// 如果当前部门和父级部门匹配,再去找当前部门的下级部门
// children就是item的下级所有部门
const children = this.translateListToTreeData(list, item.id)
if (children.length > 0) {
// 证明找到了当前部门的子部门
item.children = children
}
arr.push(item)
}
})
return arr
},
async getDepartments () {
// 这里获取的原始部门数据是数组
const ret = await reqGetDepartments()
// 把列表数据转换为树形数据
this.depts = this.translateListToTreeData(ret.data.depts, '')
// 获取数据后显示tree组件
this.showTree = true
},
closeDialog() {
this.$emit('update:showDialog', false)
},
}
总结:部门输入框获取焦点时,触发接口调用,获取部门列表数据并转换为树形结构
# 结合 el-tree 组件展示
- 准备tree结构
<el-form-item label="部门" prop="departmentName">
<el-input @focus="getDepartments" v-model="formData.departmentName" style="width:50%" placeholder="请选择部门" />
<el-tree
v-if="showTree"
v-loading="loading"
:data="depts"
:props="{ label: 'name' }"
/>
</el-form-item>
- 控制显示隐藏
showTree: false, // 是否显示tree
async getDepartments() {
this.showTree = true
const { data } = await reqGetDepartments()
this.treeData = tranListToTreeData(data.depts, '')
}
总结:通过状态位showTree控制树形结构的显示和隐藏
# 点击选择部门表单数据填充
- 注册 node-click 事件
<el-tree
v-if="showTree"
v-loading="loading"
:data="treeData"
:props="{ label: 'name' }"
@node-click="selectNode"
/>
- 添加事件处理
// 选中树形部门其中一个节点
selectNode (dept) {
// 控制仅仅可以选择叶子节点(最下面一层部门)
if (dept.children && dept.children.length > 0) return
// 获取部门的名称
this.formData.departmentName = dept.name
// 隐藏树形结构
this.showTree = false
},
- 优化样式:
<div v-if="showTree" class="tree-box">
<el-tree
v-loading="loading"
:data="treeData"
:props="{ label: 'name' }"
@node-click="selectNode"
/>
</div>
<style lang="scss" scoped>
.tree-box {
position: absolute;
width: 50%;
min-height: 50px;
left: 0;
top: 45px;
z-index: 100;
border: 1px solid #ccc;
border-radius: 4px;
padding-right: 5px;
overflow: hidden;
background-color: #fff;
}
</style>
总结:
- 选中部门名称
- 美化样式
# 完成新增功能
添加流程:(注意:添加的聘用形式是字符串类型的)
- 封装一个接口提交表单
- 调用接口提交表单
- 关闭弹窗并刷新列表
- 封装新增员工**api **
src/api/employees.js
export function reqAddEmployee(data) {
return request({
method: 'post',
url: '/sys/user',
data
})
}
- 注册点击事件
<el-button type="primary" @click="handleSubmit">确定</el-button>
import { reqAddEmployee } from '@/api/employees'
// 提交表单
handleSubmit () {
this.$refs.formData.validate(async valid => {
if (!valid) return
// 调用新增接口
const ret = await reqAddEmployee(this.formData)
if (!ret.success) {
this.$message.error(ret.message)
}
this.$emit('update:showDialog', false)
// 通知到父组件更新数据
this.$parent.loadEmployeeList()
})
},
- 重置数据(点击取消按钮)
// 关闭弹窗
handleClose () {
// 关闭弹窗
this.$emit('update:showDialog', false)
// 重置表单(只能重置需要验证的输入域)
this.$refs.formData.resetFields() // 保证验证提示默认不显示
this.formData = this.$options.data().formData
}
总结:this.$parent代表当前组件的父组件,通过它可以访问父组件的实例方法
注意:重置表单的简化写法:
this.formData = this.$options.data().formDatathis.formData = this.$options.data() 可以获取原始的表单数据
# 回顾
- 角色管理
- 删除角色
- 控制弹窗的显示和隐藏
- 添加角色
- 编辑角色
- 公司信息的动态渲染
- 员工管理
- 熟悉员工管理的基本业务流程
- 封装顶部的导航组件(具名插槽)
- 员工列表的动态渲染
- 定制列表的列效果(作用域插槽)
- 聘用形式
- 入职日期
- 用户的状态
- 删除员工
- 添加员工
- 控制弹窗的显示与隐藏
- 表单验证
- 聘用形式的数据填充(和列表展示的聘用形式对应的)
- 获取部门的名称
- 点击部门输入域,需要显示部门树形列表
- 动态获取部门数据并转成树形结构然后展示
- 点中其中一个部门后,获取对应的部门名称,隐藏树形列表
# 员工导入
功能描述:
刚才我们完成的员工添加是一个一个进行的,实际情况中有时候需要我们一次性添加多个员工信息,这个时候就需要我们开发一个批量导入的功能,点击
excel导入按钮,选择准备好要导入的excel表格文件,进行批量添加


# 基本导入组件封装
目标:封装一个导入excel数据的组件vue-element-admin已经提供了上传Excel文件的组件,我们只需要改造即可 代码地址 (opens new window)
excel导入功能需要使用npm包**
xlsx,所以需要安装xlsx**插件npm i xlsx基于vue-element-admin提供的导入功能新建一个组件,位置:
src/components/UploadExcel/index.vue
- 点击导入进入如下的页面

- 修改样式和布局
<template>
<div class="upload-excel">
<div class="btn-upload">
<el-button :loading="loading" size="mini" type="primary" @click="handleUpload">
点击上传
</el-button>
</div>
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
<div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
<i class="el-icon-upload" />
<span>将文件拖到此处</span>
</div>
</div>
</template>
<style scoped lang="scss">
.upload-excel {
display: flex;
justify-content: center;
margin-top: 100px;
.excel-upload-input {
display: none;
z-index: -9999;
}
.btn-upload,
.drop {
border: 1px dashed #bbb;
width: 350px;
height: 160px;
text-align: center;
line-height: 160px;
}
.drop {
padding-top: 20px;
line-height: 80px;
color: #bbb;
i {
font-size: 60px;
display: block;
}
}
}
</style>
- 注册全局的导入excel组件
@/components/index.js
// 导入组件
import UploadExcel from './UploadExcel'
export default {
install(Vue) {
// 进行组件的全局注册
Vue.component('UploadExcel', UploadExcel) // 注册导入excel组件
}
}
总结:基于vue-element-admin提供的导入Excel的案例封装一个组件并且配置全局插件
# 建立公共导入的页面路由
创建路由组件实现导入效果
- 创建路由组件
- 配置路由
- 点击导入按钮实现编程式导航跳转
- 新建一个公共的导入页面, 即import路由组件
src/views/import/index.vue
<template>
<upload-excel />
</template>
<script>
export default {
name: 'Import'
}
</script>
<style>
</style>
- 挂载路由**
src/router/index.js**
{
path: '/import',
component: Layout,
hidden: true, // 隐藏在左侧菜单中
children: [{
path: '', // 二级路由path什么都不写 表示二级默认路由
component: () => import('@/views/import')
}]
},
- 分析excel导入代码
从点击按钮开始, 分析代码, 这里点击按钮, 触发了input:file 的click事件, 进行了上传
<template #right>
<el-button @click="$router.push('/import')" type="warning" size="small">excel导入</el-button>
<el-button type="danger" size="small">excel导出</el-button>
<el-button type="primary" size="small" @click="handleAdd">新增员工</el-button>
</template>
- 复制vue-element-admin官方实例script代码到 src/components/UploadExcel/index.vue文件中
<template>
<upload-excel :on-success='handleSuccess' />
</template>
注意:onSuccess函数会在选中文件后自动触发
export default {
name: 'Import',
methods: {
handleSuccess ({ header, results }) {
// header是Excel文件的表头(数组)
// results是Excel文件中的具体数据(数组--里面放的是对象)
console.log(header, results)
}
}
}
- 分析上传Excel代码结构
- 点击按钮实现文件数据解析
- 拖拽实现文件数据解析
<!-- drop 放开鼠标时触发 -->
<!-- dragover 拖拽到目标区域后一直触发 -->
<!-- dragenter 进入目标区域触发一次 -->
# 实现excel导入功能
- 封装导入员工的api接口
export function reqImportEmployee(data) {
return request({
url: '/sys/user/batch',
method: 'post',
data
})
}
- 根据文件中解析的数据转换为接口需要的数据格式
export default {
name: 'Import',
methods: {
// 转换数据格式
translateData (results) {
// 中文和属性的映射关系
const userRelations = {
'入职日期': 'timeOfEntry',
'手机号': 'mobile',
'姓名': 'username',
'转正日期': 'correctionTime',
'工号': 'workNumber'
}
// results = [{"手机号":15751786628,"姓名":"张飞1","入职日期":43535,"转正日期":43719,"工号":88088},{"手机号":15751786630,"姓名":"关羽2","入职日期":43535,"转正日期":43719,"工号":88089}]
const arr = []
results.forEach(item => {
let row = {}
for (let key in item) {
// 把每一个属性都更换为英文的
let name = userRelations[key]
let value = item[key]
row[name] = value
}
arr.push(row)
})
return arr
},
handleSuccess ({ results }) {
// header是Excel文件的表头(数组)
// results是Excel文件中的具体数据(数组--里面放的是对象)
// 接下来需要把header和results数据转换为接口需要的数据
/*
const params = [{
mobile
formOfEmployment
workNumber
departmentName
timeOfEntry
correctionTime
}]
*/
// 需要把中文的key映射为英文的属性名称
// ["手机号","姓名","入职日期","转正日期","工号"]
// console.log(JSON.stringify(header))
// [{"手机号":15751786628,"姓名":"张飞1","入职日期":43535,"转正日期":43719,"工号":88088},{"手机号":15751786630,"姓名":"关羽2","入职日期":43535,"转正日期":43719,"工号":88089}]
// console.log(JSON.stringify(results))
// 这里需要返回我们调用接口需要的参数格式即可
const params = this.translateData(results)
console.log(params)
}
}
}
- 调用接口实现数据导入
const params = this.translateData(results)
try {
const ret = await reqImportEmployee(params)
if (!ret.success) {
this.$message.error(ret.message)
} else {
// 导入成功,退回上一页
this.$router.back()
}
} catch {
this.$message.error('批量导入失败')
}
- 当excel中有日期格式的时候,实际转化的值为一个数字,我们需要一个方法进行转化 (已准备好)
formatDate(numb, format) {
const time = new Date((numb - 1) * 24 * 3600000 + 1)
time.setYear(time.getFullYear() - 70)
const year = time.getFullYear() + ''
const month = time.getMonth() + 1 + ''
const date = time.getDate() - 1 + ''
if (format && format.length === 1) {
return year + format + (month < 10 ? '0' + month : month) + format + (date < 10 ? '0' + date : date)
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}
需要注意,
导入的手机号不能和之前的存在的手机号重复
- 处理日期:
// 转换数据格式
translateData (results) {
// 中文和属性的映射关系
const userRelations = {
'入职日期': 'timeOfEntry',
'手机号': 'mobile',
'姓名': 'username',
'转正日期': 'correctionTime',
'工号': 'workNumber'
}
// results = [{"手机号":15751786628,"姓名":"张飞1","入职日期":43535,"转正日期":43719,"工号":88088},{"手机号":15751786630,"姓名":"关羽2","入职日期":43535,"转正日期":43719,"工号":88089}]
const arr = []
results.forEach(item => {
let row = {}
for (let key in item) {
// 把每一个属性都更换为英文的
let name = userRelations[key]
let value = item[key]
if (['timeOfEntry', 'correctionTime'].includes(name)) {
// 这个属性是时间,需要把数字转换为年月日格式
row[name] = this.formatDate(value)
} else {
// 非日期格式不做处理
row[name] = value
}
}
arr.push(row)
})
return arr
},
总结:
- 基于现场的案例封装通用的上传导入组件
- 准备路由组件实现导入效果
- 把选中的Excel文件的内容解析为数据
- 分析解析数据的过程(点击选中;拖拽选中)
- 把获取的数据转换为接口需要的数据
- 调用接口实现导入
- 处理导入的日期格式
# 员工导出

# 导出基本演示
导入功能基于vue-element-admin实现,同理,导出功能也是基于它实现(参考export-excel.vue (opens new window))
- 把对应文件下载下来(在课程资源/excel
导出目录下的vender,放置到src目录下) - 安装相关的依赖包
npm i xlsx file-saver script-loader
- 按需导入模块(通过import()方法导入的文件在项目打包时,不会合并到一块,而是单独打包为一个文件),方便按需进行加载(需要用到该文件的时候再去加载),这样做的好处是提升首屏渲染效率。
import('@/vendor/Export2Excel').then(excel => {
excel.export_json_to_excel({
header: ['姓名', '工资'], // 表头 必填
data: [
['刘备', 100],
['关羽', 500]
], // 具体数据 必填
filename: 'excel-list', // 非必填
autoWidth: true, // 非必填
bookType: 'xlsx' // 非必填
})
})
# excel导出参数的介绍
- 参数说明
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---|---|---|---|---|
| header | 导出数据的表头 | Array | / | [] |
| data | 导出的具体数据 | Array | / | [[]] |
| filename | 导出文件名 | String | / | excel-list |
| autoWidth | 单元格是否要自适应宽度 | Boolean | true / false | true |
| bookType | 导出文件类型 | String | xlsx, csv, txt, more (opens new window) | xlsx |
# excel导出基本的结构
我们最重要的一件事,就是把 表头 和 数据 进行相应的对应,
- 因为数据中的key是英文,想要导出的表头是中文的话,需要将中文和英文做对应
const headers = {
'姓名': 'username',
'手机号': 'mobile',
'入职日期': 'timeOfEntry',
'聘用形式': 'formOfEmployment',
'转正日期': 'correctionTime',
'工号': 'workNumber',
'部门': 'departmentName'
}
- 然后,完成导出代码
handleExport () {
import('@/vendor/Export2Excel').then(async excel => {
const headers = {
'姓名': 'username',
'手机号': 'mobile',
'入职日期': 'timeOfEntry',
'聘用形式': 'formOfEmployment',
'转正日期': 'correctionTime',
'工号': 'workNumber',
'部门': 'departmentName'
}
// 获取表头
const header = Object.keys(headers)
// 从后端查询所有员工原始数据
const ret = await reqGetEmployeeList({
page: 1,
size: this.total
})
// 把原始数据转换为二维数组
const data = this.jsonToArray(headers, ret.data.rows)
// 实现导出
excel.export_json_to_excel({
header: header,
data: data, // 具体数据 必填
filename: 'excel-list', // 非必填
autoWidth: true, // 非必填
bookType: 'xlsx' // 非必填
})
})
},
jsonToArray(headers, rows) {
return rows.map(item => {
return Object.keys(headers).map(key => {
return item[headers[key]]
})
})
}
// 将表头数据和数据进行对应
jsonToArray(headers, rows) {
const dataArr = rows.map(item => {
// item是一个对象
const arr = Object.keys(headers).map(key => {
return item[headers[key]]
})
return arr
})
return dataArr
}
// 把json格式数据转换为二维数组
jsonToArray (headers, jsonData) {
const results = []
jsonData.forEach(row => {
// row就是每一行数据
const person = []
for (const key in headers) {
// 获取属性名称
const attrName = headers[key]
// 根据属性名称获取对应数据
const attrValue = row[attrName]
// 把每一列的数据放到第二维数组中
person.push(attrValue)
}
results.push(person)
})
return results
},
总结:
- 需要把后端获取的所有员工的数据获取到
- 把原始的数据转换为Excel需要的数据(算法)
- 基于vendor提供API实现导出
# 导出时间和聘用形式的格式处理
// 把json格式数据转换为二维数组
jsonToArray (headers, jsonData) {
const results = []
jsonData.forEach(row => {
// row就是每一行数据
const person = []
for (const key in headers) {
// 获取属性名称
const attrName = headers[key]
// 根据属性名称获取对应数据
let attrValue = row[attrName]
// 转换数据格式
if (['timeOfEntry', 'correctionTime'].includes(attrName)) {
// 处理时间
attrValue = moment(attrValue).format('yyyy-MM-DD')
} else if ('formOfEmployment' === attrName) {
// 聘用形式
const obj = Types.hireType.find(item => {
return item.id === parseInt(attrValue)
})
attrValue = obj ? obj.value : ''
}
// 把每一列的数据放到第二维数组中
person.push(attrValue)
}
results.push(person)
})
return results
},
总结
- 时间相关的字段转换为年月日格式(基于moment进行格式化)
- 聘用形式需要基于常量数据转换为文字格式