目录
1.员工管理-导出excel
2.员工管理-excel组件封装
3.员工管理-下载导入模板
4.员工管理-员工导入-上传excel
5.员工管理-删除员工
6.员工详情和路由
7.员工详情-表单数据校验
8.员工详情-封装部门级联组件
9.员工详情-级联组件-双向绑定
10.员工详情-新增员工
11.员工详情-编辑员工-查看员工
12.员工详情-编辑员工-保存
1.员工管理-导出excel
-
导出员工接口返回的是二进制流
-
axios配置responseType为blob接收二进制流文件为Blob格式
-
安装file-saver包,实现下载Blob文件 yarn add file-saver
-
封装导出员工excel的API-代码位置(src/api/employee.js)
/**
* 导出员工的excel
* **/
export function exportEmployee() {
return request({
url: '/sys/user/export',
// 改变接收数据的类型
responseType: 'blob' // 使用blob接收二进制文件流
})
}
- 拦截器判断是不是blob类型,如果是直接返回数据,不再进行解构-代码位置(src/utils/request.js)
// 响应拦截器
service.interceptors.response.use((response) => {
// axios默认包裹了data
// 判断是不是Blob
if (response.data instanceof Blob) return response.data // 返回了Blob对象
const { data, message, success } = response.data // 默认json格式
if (success) {
return data
} else {
Message({ type: 'error', message })
return Promise.reject(new Error(message))
}
},
- 安装file-saver
$ npm i file-saver
$ yarn add file-saver
- 点击按钮调用接口,使用file-saver将blob转化成文件下载-代码位置(src/views/employee/index.vue)
<el-button size="mini" @click="exportEmployee">excel导出</el-button>
import FileSaver from 'file-saver'
import { exportEmployee } from '@/api/employee'
async exportEmployee() {
const result = await exportEmployee() // 导出所有的员工接口
// console.log(result) // 使用一个npm包 直接将blob文件下载到本地 file-saver
// FileSaver.saveAs(blob对象,文件名称)
FileSaver.saveAs(result, '员工信息表.xlsx') // 下载文件
}
2.员工管理-excel组件封装
- 创建员工导入组件-代码位置(src/views/employee/components/import-excel.vue)
<template>
<el-dialog
width="500px"
title="员工导入"
:visible="showExcelDialog"
@close="$emit('update:showExcelDialog', false)"
>
<el-row type="flex" justify="center">
<div class="upload-excel">
<input
ref="excel-upload-input"
class="excel-upload-input"
type="file"
accept=".xlsx, .xls"
>
<div class="drop">
<i class="el-icon-upload" />
<el-button type="text">下载导入模板</el-button>
<span>将文件拖到此处或
<el-button type="text">点击上传</el-button>
</span>
</div>
</div>
</el-row>
<el-row type="flex" justify="end">
<!-- update:props属性名,值 直接修改 .sync修饰符的属性值 -->
<el-button size="mini" type="primary" @click="$emit('update:showExcelDialog', false)">取消</el-button>
</el-row>
</el-dialog>
</template>
<script>
export default {
props: {
showExcelDialog: {
type: Boolean,
default: false
}
},
methods: {
}
}
</script>
<style scoped lang="scss">
.upload-excel {
display: flex;
justify-content: center;
margin: 20px;
width: 360px;
height: 180px;
align-items: center;
color: #697086;
.excel-upload-input {
display: none;
z-index: -9999;
}
.btn-upload,
.drop {
border: 1px dashed #dcdfe6;
width: 100%;
height: 100%;
text-align: center;
line-height: 160px;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
}
.drop {
line-height: 40px;
color: #bbb;
i {
font-size: 60px;
display: block;
color: #c0c4cc;
}
}
}
</style>
- 在员工管理页面-导入该组件并注册使用-代码位置(src/views/employee/index.vue)
import ImportExcel from './components/import-excel.vue'
export default {
components: {
ImportExcel
}
}
}
- 声明一个控制该弹层的变量-代码位置(src/views/employee/index.vue)
data () {
return {
showExcelDialog: false // 控制excel的弹层显示和隐藏
}
}
- 使用该组件,并且应用变量-代码位置(src/views/employee/index.vue)
<import-excel :show-excel-dialog.sync="showExcelDialog" />
- 点击员工导入弹出层-代码位置(src/views/employee/index.vue)
<el-button size="mini" @click="showExcelDialog = true">excel导入</el-button>
3.员工管理-下载导入模板
- 封装下载模板的API-代码位置(src/api/employee.js)
/**
* 下载员工导入模版
* **/
export function getExportTemplate() {
return request({
url: '/sys/user/import/template',
responseType: 'blob' // 二进制文件流
})
}
- 点击按钮进行下载模板-代码位置(src/views/employee/components/import-excel.vue)
<el-button type="text" @click="getTemplate">下载导入模板</el-button>
async getTemplate() {
const data = await getExportTemplate()
FileSaver.saveAs(data, '员工导入模版.xlsx')
}
4.员工管理-员工导入-上传excel
- 封装上传excel的API-代码位置(src/api/employee.js)
/**
* 上传用户的excel
*
*/
export function uploadExcel(data) {
return request({
url: '/sys/user/import',
method: 'post',
data // form-data类型 因为要上传文件类型
})
}
- 点击上传-弹出文件选择框-代码位置(src/views/employee/components/import-excel.vue)
<el-button type="text" @click="handleUpload">点击上传</el-button>
handleUpload() {
this.$refs['excel-upload-input'].click() // this.$refs.属性名 和 this.$refs[属性名] 等价
},
- 监听文件改变-上传excel-关闭弹层-代码位置(src/views/employee/components/import-excel.vue)
<input
ref="excel-upload-input"
class="excel-upload-input"
type="file"
accept=".xlsx, .xls"
@change="uploadChange"
>
async uploadChange(event) {
console.log(event.target.files)
// 调用上传接口
// uploadExcel() // 参数 form-data 需要文件file
const files = event.target.files // input的文件列表
if (files.length > 0) {
// 大于0 说明有文件要上传
const data = new FormData()
// file: file类型
data.append('file', files[0]) // 将文件参数加入到formData中
try {
await uploadExcel(data)
// 成功
this.$emit('uploadSuccess') // 通知父组件 我上传成功
this.$emit('update:showExcelDialog', false) // 关闭弹层
// this.$refs['excel-upload-input'].value = ''
} catch (error) {
// 捕获失败
// this.$refs['excel-upload-input'].value = ''
} finally {
// 不论成功或者失败都会执行finally
this.$refs['excel-upload-input'].value = ''
}
}
}
这里为什么不管成功或者失败都要清空文件选择器中的内容呢? 因为不论成功或者失败,再点击上传都会去选择一个新的excel,所以这里使用finally等到最后,将内容清空。
- 父组件需要监听上传成功的事件-代码位置(src/views/employee/index.vue)
<import-excel :show-excel-dialog.sync="showExcelDialog" @uploadSuccess="getEmployeeList" />
5.员工管理-删除员工
- 封装删除员工的API-代码位置(src/api/employee.js)
/**
* 删除员工
* **/
export function delEmployee(id) {
return request({
method: 'delete',
url: `/sys/user/${id}`
})
}
- 放置气泡框确认框-代码位置(src/views/employee/index.vue)
<el-popconfirm title="确认删除该行数据吗?" @onConfirm="confirmDel(row.id)">
<el-button slot="reference" style="margin-left:10px" size="mini" type="text">删除</el-button>
</el-popconfirm>
- 删除方法实现-代码位置(src/views/employee/index.vue)
// 删除员工方法
async confirmDel(id) {
await delEmployee(id)
if (this.list.length === 1 && this.queryParams.page > 1) this.queryParams.page--
this.getEmployeeList()
this.$message.success('删除员工成功')
}
6.员工详情和路由
- (拷贝)创建一个员工详情页面-代码位置(src/views/employee/detail.vue)
<template>
<div class="dashboard-container">
<div class="app-container">
<div class="edit-form">
<el-form ref="userForm" label-width="220px">
<!-- 姓名 部门 -->
<el-row>
<el-col :span="12">
<el-form-item label="姓名" prop="username">
<el-input 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 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
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 size="mini" class="inputW" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="入职时间" prop="timeOfEntry">
<el-date-picker
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="转正时间">
<el-date-picker
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">保存更新</el-button>
</el-col>
</el-row>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style scoped lang="scss">
.edit-form {
background: #fff;
padding: 20px;
.inputW {
width: 380px
}
}
</style>
- 配置员工详情的路由信息-代码位置(src/router/modules/employee.js)
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: '员工详情' // 显示在导航的文本
}
}]
}
- 点击添加员工跳转到详情页-代码位置(src/views/employee/index.vue)
<el-button size="mini" type="primary" @click="$router.push('/employee/detail')">添加员工</el-button>
7.员工详情-表单数据校验
表单校验规则
-
姓名-必填-1-4个字符
-
手机号-必填-格式校验
-
部门-必填
-
聘用形式-必填
-
入职时间-必填
-
转正时间-必填-不能小于入职时间
-
定义数据和规则-代码位置(src/views/employee/detail.vue)
<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(new Error('转正时间不能小于入职时间'))
return
}
}
callback()
}
}]
}
}
},
methods: {
saveData() {
this.$refs.userForm.validate()
}
}
}
</script>
- 绑定表单数据和属性-代码位置(src/views/employee/detail.vue)
<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>
8.员工详情-封装部门级联组件
Cascader级联组件的特性
- options为一个树形结构的数据源
- props可以设置数据源中的字段如 label (展示) value(存取)
- separator为展示的分隔符
- 创建select-tree组件-代码位置(src/views/employee/components/select-tree.vue)
<template>
<!-- element-ui级联组件 -->
<el-cascader
size="mini"
:options="treeData"
:props="props"
separator="-"
/>
</template>
<script>
import { getDepartment } from '@/api/department'
import { transListToTreeData } from '@/utils'
export default {
data() {
return {
treeData: [], // 赋值给 级联组件的options
props: {
label: 'name', // 要展示的字段
value: 'id' // 要存储的字段
}
}
},
created() {
this.getDepartment()
},
methods: {
async getDepartment() {
this.treeData = transListToTreeData(await getDepartment(), 0) // 将组织架构的数据 转化树形赋值给treeData
}
}
}
</script>
- 解决转化树形中的问题-代码位置(src/utils/index.js)
/**
*
* 列表型数据转化树形
*/
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 } // 将子节点赋值给当前节点
}
})
return arr
}
这里我们加了一个判断,就是只有当前节点有子节点时才添加children属性,否则会造成级联组件本身就已经是最末端了,但是发现它的children属性存在,就会呈现不同的表现形式。
- 使用select-tree组件-代码位置(src/views/employee/detail.vue)
不要忘记引入注册
<el-form-item label="部门" prop="departmentId">
<!-- 放置及联部门组件 会单独封装-->
<!-- inputW样式会给到selectTree中 template第一层的组件 -->
<select-tree class="inputW" />
</el-form-item>
9.员工详情-级联组件-双向绑定
- 接收value属性-代码位置(src/views/employee/components/select-tree.vue)
export default {
props: {
value: {
type: Number, // 存储的是部门的id 3 4 5
default: null
}
}
}
- 级联改变触发input事件-代码位置(src/views/employee/components/select-tree.vue)
<el-cascader
:value="value"
size="mini"
:options="treeData"
:props="props"
separator="-"
@change="changeValue"
/>
changeValue(list) {
// 取到数组的最后一次
if (list.length > 0) {
this.$emit('input', list[list.length - 1]) // 将最后一位的id取出来 传出去
} else {
this.$emit('input', null) // 如果长度为0 说明值为空
}
}
- 在父组件使用v-model双向绑定-代码位置(src/views/employee/detail.vue)
<select-tree v-model="userInfo.departmentId" class="inputW" />
10.员工详情-新增员工
- 封装新增员工API-代码位置(src/api/employee.js)
export function addEmployee(data) {
return request({
url: '/sys/user',
method: 'post',
data
})
}
- 点击保存按钮进行新增-代码位置(src/views/employee/detail.vue)
saveData() {
this.$refs.userForm.validate(async isOK => {
if (isOK) {
// 校验通过
await addEmployee(this.userInfo)
this.$message.success('新增员工成功')
this.$router.push('/employee')
}
})
}
11.员工详情-编辑员工-查看员工
- 封装获取员工详情的API-代码位置(src/api/employee.js)
/**
* 获取员工详情
* **/
export function getEmployeeDetail(id) {
return request({
url: `/sys/user/${id}`
})
}
- 点击查看时跳转到详情携带id-代码位置(src/views/employee/index.vue)
<el-button size="mini" type="text" @click="$router.push(`/employee/detail/${row.id}`)">查看</el-button>
- 配置详情的路由支持新增模式和编辑模式-代码位置(src/router/modules/employee.js)
? 标识可有可无,可以传id也可以选择不传,页面都能正确显示
export default {
path: '/employee',
component: layout,
children: [{
path: '',
name: 'employee',
component: () => import('@/views/employee'),
meta: {
title: '员工',
icon: 'people'
}
}, {
path: '/employee/detail/:id?', // 员工详情的地址
component: () => import('@/views/employee/detail.vue'),
hidden: true, // 表示隐藏在左侧菜单
meta: {
title: '员工详情' // 显示在导航的文本
}
}]
}
- 员工详情页判断是否有id,有id就查询详情数据-代码位置(src/views/employee/detail.vue)
created() {
// 如何获取路由参数的中id
// if (this.$route.params.id) { this.getEmployeeDetail() }
this.$route.params.id && this.getEmployeeDetail()
},
methods: {
async getEmployeeDetail() {
this.userInfo = await getEmployeeDetail(this.$route.params.id)
}
}
12.员工详情-编辑员工-保存
- 封装更新员工的API-代码位置(src/api/employee.js)
export function updateEmployee(data) {
return request({
url: `/sys/user/${data.id}`,
method: 'put',
data
})
}
- 保存时区分保存和新增场景-代码位置(src/views/employee/detail.vue)
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')
}
})
}
- 在当编辑模式时,让手机号不可编辑-代码位置(src/views/employee/detail.vue)
因为新增时手机号已经注册成功,手机号如果修改了,用户就不能再成功登录。所以编辑时,用户的手机号允许修改。
<el-input
v-model="userInfo.mobile"
:disabled="!!$route.params.id"
size="mini"
class="inputW"
/>