封装一个通用的工具栏

**目标**:封装一个通用的工具栏供大家使用

通用工具栏的组件结构

在后续的业务开发中,经常会用到一个类似下图的工具栏,作为公共组件,进行一下封装

image-20200723223704401

组件 src/components/PageTools/index.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template>
<el-card class="page-tools">
<el-row type="flex" justify="space-between" align="middle">
<el-col>
<div v-if="showBefore" class="before">
<i class="el-icon-info" />
<!-- 定义前面得插槽 -->
<slot name="before" />
</div>
</el-col>
<el-col>
<el-row type="flex" justify="end">
<!-- 定义后面的插槽 -->
<slot name="after" />
</el-row>
</el-col>
</el-row>
</el-card>
</template>

<script>
export default {
props: {
showBefore: {
type: Boolean,
default: false
}

}
}
</script>

<style lang='scss'>
.page-tools {
margin: 10px 0;
.before {
line-height: 34px;
i {
margin-right: 5px;
color: #409eff;
}
display: inline-block;
padding: 0px 10px;
border-radius: 3px;
border: 1px solid rgba(145, 213, 255, 1);
background: rgba(230, 247, 255, 1);
}
}
</style>

组件统一注册

为了方便所有的页面都可以不用引用该组件,可以进行全局注册

提供注册入口 src/componets/index.js

1
2
3
4
5
6
7
8
9
// 该文件负责所有的公共的组件的全局注册   Vue.use
import PageTools from './PageTools'
export default {
install(Vue) {
// 注册全局的通用栏组件对象
Vue.component('PageTools', PageTools)
}
}

在入口处进行注册 src/main.js

1
2
import Component from '@/components'
Vue.use(Component) // 注册自己的插件

提交代码

**本节任务**: 封装一个通用的工具栏

员工列表页面的基本布局和结构

**目标**:实现员工列表页面的基本布局和结构

结构代码 src/employees/index.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div class="dashboard-container">
<div class="app-container">
<page-tools :show-before="true">
<span slot="before">共166条记录</span>
<template slot="after">
<el-button size="small" type="warning">导入</el-button>
<el-button size="small" type="danger">导出</el-button>
<el-button size="small" type="primary">新增员工</el-button>
</template>
</page-tools>
<!-- 放置表格和分页 -->
<el-card>
<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>
<!-- 分页组件 -->
<el-row type="flex" justify="center" align="middle" style="height: 60px">
<el-pagination layout="prev, pager, next" />
</el-row>
</el-card>
</div>
</div>
</template>

image-20200831162222975

提交代码

**本节任务**:员工列表页面的基本布局和结构

员工列表数据请求和分页加载

**目标**实现员工数据的加载和分页请求

首先,封装员工的加载请求 src/api/employees.js

1
2
3
4
5
6
7
8
9
10
/**
* 获取员工的综合列表数据
* ***/
export function getEmployeeList(params) {
return request({
url: '/sys/user',
params
})
}

然后,实现加载数据和分页的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { getEmployeeList } from '@/api/employees'
export default {
data() {
return {
loading: false,
list: [], // 接数据的
page: {
page: 1, // 当前页码
size: 10,
total: 0 // 总数
}

}
},
created() {
this.getEmployeeList()
},
methods: {
changePage(newPage) {
this.page.page = newPage
this.getEmployeeList()
},
async getEmployeeList() {
this.loading = true
const { total, rows } = await getEmployeeList(this.page)
this.page.total = total
this.list = rows
this.loading = false
}
}
}

绑定表格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<el-card v-loading="loading">
<el-table border :data="list">
<el-table-column label="序号" sortable="" type="index" />
<el-table-column label="姓名" sortable="" prop="username" />
<el-table-column label="工号" sortable="" prop="workNumber" />
<el-table-column label="聘用形式" sortable="" prop="formOfEmployment" />
<el-table-column label="部门" sortable="" prop="departmentName" />
<el-table-column label="入职时间" sortable="" prop="timeOfEntry" />
<el-table-column label="账户状态" sortable="" prop="enableState" />
<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>
<!-- 分页组件 -->
<el-row type="flex" justify="center" align="middle" style="height: 60px">
<el-pagination
layout="prev, pager, next"
:page-size="page.size"
:current-page="page.page"
:total="page.total"
@current-change="changePage"
/>
</el-row>
</el-card>

提交代码

**本节任务**员工列表数据请求和分页加载

员工列表中的数据进行格式化

**目标**:将列表中的内容进行格式化

利用列格式化属性处理聘用形式

image-20200724001315166

上小节中,列表中的聘用形式/入职时间账户状态需要进行显示内容的处理

那么聘用形式中1代表什么含义,这实际上是我们需要的枚举数据,该数据的存放文件位于我们提供的**资源/枚举中,可以将枚举下的文件夹放于src/api**文件夹下

针对聘用形式,可以使用el-table-columnformatter属性进行设置

1
2
3
4
5
6
7
8
9
 import    EmployeeEnum from '@/api/constant/employees'
<!-- 格式化聘用形式 -->
<el-table-column label="聘用形式" sortable :formatter="formatEmployment" />
// 格式化聘用形式
formatEmployment(row, column, cellValue, index) {
// 要去找 1所对应的值
const obj = EmployeeEnum.hireType.find(item => item.id === cellValue)
return obj ? obj.value : '未知'
}

过滤器解决时间格式的处理

针对入职时间,我们可以采用作用域插槽进行处理

1
2
3
4
5
6
7
<el-table-column label="入职时间" sortable prop="timeOfEntry">
<template slot-scope="obj">
{{
obj.row.timeOfEntry | 过滤器
}}
</template>
</el-table-column>

问题来了,过滤器从哪里呢?

在**资源/过滤器中,我们提供了若干工具方法,我们可以将其转化成过滤器,首先将其拷贝到src**

在**main.js**中将工具方法转化成过滤器

1
2
3
4
5
6
import * as filters from '@/filters' // 引入工具类
// 注册全局的过滤器
Object.keys(filters).forEach(key => {
// 注册过滤器
Vue.filter(key, filters[key])
})

好了,现在可以愉快的用过滤器的方式使用工具类的方法了

1
2
3
4
<el-table-column label="入职时间" sortable="" align="center">
<!-- 作用域插槽 -->
<template slot-scope="{ row }">{{ row.timeOfEntry | formatDate }}</template>
</el-table-column>

最后一项,账户状态,可以用开关组件switch进行显示

1
2
3
4
5
6
<el-table-column label="账户状态" align="center" sortable="" prop="enableState">
<template slot-scope="{ row }">
<!-- 根据当前状态来确定 是否打开开关 -->
<el-switch :value="row.enableState === 1" />
</template>
</el-table-column>

提交代码

本节任务 员工列表中的数据进行格式化

删除员工功能

**目标**实现删除员工的功能

首先封装 删除员工的请求

1
2
3
4
5
6
7
8
9
10
/**
* 删除员工接口
* ****/

export function delEmployee(id) {
return request({
url: `/sys/user/${id}`,
method: 'delete'
})
}

删除功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <template slot-scope="{ 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="deleteEmployee(row.id)">删除</el-button>
</template>
// 删除员工
async deleteEmployee(id) {
try {
await this.$confirm('您确定删除该员工吗')
await delEmployee(id)
this.getEmployeeList()
this.$message.success('删
除员工成功')
} catch (error) {
console.log(error)
}
}

提交代码

**本节任务**: 删除员工功能

新增员工功能-弹层-校验-部门

**目标**:实现新增员工的功能

新建员工弹层组件

当我们点击新增员工时,我们需要一个类似的弹层

image-20200917110516195

类似**组织架构**的组件,同样新建一个弹层组件 src/views/employees/components/add-employee.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<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-row type="flex" justify="center">
<el-col :span="6">
<el-button size="small">取消</el-button>
<el-button type="primary" size="small">确定</el-button>
</el-col>
</el-row>
</template>
</el-dialog>
</template>

<script>
export default {
props: {
showDialog: {
type: Boolean,
default: false
}
}
}
</script>

<style>

</style>

引用弹出层,点击弹出

父组件中引用,弹出层

1
2
3
import AddDemployee from './components/add-employee'
<!-- 放置新增组件 -->
<add-employee :show-dialog.sync="showDialog" />
1
2
3
4
5
<!-- 放置新增组件 -->
<add-employee :show-dialog.sync="showDialog" />

<el-button icon="plus" type="primary" size="small" @click="showDialog = true">新增员工</el-button>

新增员工的表单校验

封装新增员工api src/api/employees.js

1
2
3
4
5
6
7
8
9
10
11
/** **
* 新增员工的接口
* **/
export function addEmployee(data) {
return request({
method: 'post',
url: '/sys/user',
data
})
}

针对员工属性,添加校验规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import EmployeeEnum from '@/api/constant/employees'

data() {
return {
EmployeeEnum, // 在data中定义数据
// 表单数据
treeData: [], // 定义数组接收树形数据
showTree: false, // 控制树形的显示或者隐藏
loading: false, // 控制树的显示或者隐藏进度条
formData: {
username: '',
mobile: '',
formOfEmployment: '',
workNumber: '',
departmentName: '',
timeOfEntry: '',
correctionTime: ''
},
rules: {
username: [{ required: true, message: '用户姓名不能为空', trigger: 'blur' }, {
min: 1, max: 4, message: '用户姓名为1-4位'
}],
mobile: [{ required: true, message: '手机号不能为空', trigger: 'blur' }, {
pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur'
}],
formOfEmployment: [{ required: true, message: '聘用形式不能为空', trigger: 'blur' }],
workNumber: [{ required: true, message: '工号不能为空', trigger: 'blur' }],
departmentName: [{ required: true, message: '部门不能为空', trigger: 'change' }],
timeOfEntry: [{ required: true, message: '入职时间', trigger: 'blur' }]
}
}
}

绑定数据和规则校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<el-form :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>

加载部门数据转化树形

聘用形式和选择部门的处理

员工的部门是从树形部门中选择一个部门

获取部门数据,转化树形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { getDepartments } from '@/api/departments'
import { transListToTreeData } from '@/utils'
data () {
return {
// 表单数据
treeData: [], // 定义数组接收树形数据
showTree: false, // 控制树形的显示或者隐藏
loading: false, // 控制树的显示或者隐藏进度条
}
},
methods: {
async getDepartments() {
this.showTree = true
this.loading = true
const { depts } = await getDepartments()
// depts是数组 但不是树形
this.treeData = transListToTreeData(depts, '')
this.loading = false
},
}

点击部门赋值表单数据

选择部门,赋值表单数据

1
2
3
4
5
6
7
8
9
10
11
12
<el-form-item label="部门" prop="departmentName">
<el-input v-model="formData.departmentName" style="width:50%" placeholder="请选择部门" @focus="getDepartments" />
<!-- 放置一个tree组件 -->
<el-tree
v-if="showTree"
v-loading="loading"
:data="treeData"
:default-expand-all="true"
:props="{ label: 'name' }"
@node-click="selectNode"
/>
</el-form-item>

点击部门时触发

1
2
3
4
selectNode(node) {
this.formData.departmentName = node.name
this.showTree = false
}

聘用形式

1
2
3
4
5
6
<el-form-item label="聘用形式" prop="formOfEmployment">
<el-select v-model="formData.formOfEmployment" style="width:50%" placeholder="请选择">
<!-- 遍历只能遍历组件的数据 -->
<el-option v-for="item in EmployeeEnum.hireType" :key="item.id" :label="item.value" :value="item.id" />
</el-select>
</el-form-item>

新增员工功能-确定-取消

调用新增接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 点击确定时 校验整个表单
async btnOK() {
try {
await this.$refs.addEmployee.validate()
// 调用新增接口
await addEmployee(this.formData) // 新增员工
// 告诉父组件更新数据
// this.$parent 可以直接调用到父组件的实例 实际上就是父组件this
// this.$emit
this.$parent.getEmployeeList()
this.$parent.showDialog = false
} catch (error) {
console.log(error)
}
},
btnCancel() {
// 重置原来的数据
this.formData = {
username: '',
mobile: '',
formOfEmployment: '',
workNumber: '',
departmentName: '',
timeOfEntry: '',
correctionTime: ''
}
this.$refs.addEmployee.resetFields() // 重置校验结果
this.$emit('update:showDialog', false)
}

新增员工的功能和组织架构的功能极其类似,这里不做过多阐述

提交代码

本节任务 新增员工功能和弹层

员工导入组件封装

**目标**:封装一个导入excel数据的文件

image-20200726005308648

首先封装一个类似的组件,首先需要注意的是,类似功能,vue-element-admin已经提供了,我们只需要改造即可 代码地址

类似功能性的组件,我们只需要会使用和封装即可

excel导入功能需要使用npm包**xlsx,所以需要安装xlsx**插件

1
$ npm i xlsx

将vue-element-admin提供的导入功能新建一个组件,位置: src/components/UploadExcel

注册全局的导入excel组件

1
2
3
4
5
6
7
8
import PageTools from './PageTools'
import UploadExcel from './UploadExcel'
export default {
install(Vue) {
Vue.component('PageTools', PageTools) // 注册工具栏组件
Vue.component('UploadExcel', UploadExcel) // 注册导入excel组件
}
}

修改样式和布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<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{
line-height: 80px;
color: #bbb;
i {
font-size: 60px;
display: block;
}
}
}
</style>

提交代码

**本节任务**:员工导入组件封装

员工的导入

**目标**:实现员工的导入

建立公共导入的页面路由

新建一个公共的导入页面,挂载路由 src/router/index.js

1
2
3
4
5
6
7
8
9
{
path: '/import',
component: Layout,
hidden: true, // 隐藏在左侧菜单中
children: [{
path: '', // 二级路由path什么都不写 表示二级默认路由
component: () => import('@/views/import')
}]
},

创建import路由组件 src/views/import/index.vue

1
2
3
4
5
<template>
<!-- 公共导入组件 -->
<upload-excel :on-success="success" />
</template>

分析excel导入代码,封装接口

封装导入员工的api接口

1
2
3
4
5
6
7
8
9
10
11
12
/** *
* 封装一个导入员工的接口
*
* ***/

export function importEmployee(data) {
return request({
url: '/sys/user/batch',
method: 'post',
data
})
}

实现excel导入

获取导入的excel数据, 导入excel接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async  success({ header, results }) {
// 如果是导入员工
const userRelations = {
'入职日期': 'timeOfEntry',
'手机号': 'mobile',
'姓名': 'username',
'转正日期': 'correctionTime',
'工号': 'workNumber'
}
const arr = []
results.forEach(item => {
const userInfo = {}
Object.keys(item).forEach(key => {
userInfo[userRelations[key]] = item[key]
})
arr.push(userInfo)
})
await importEmployee(arr) // 调用导入接口
this.$router.back()
}

为了让这个页面可以服务更多的导入功能,我们可以在页面中用参数来判断,是否是导入员工

1
2
3
4
5
data() {
return {
type: this.$route.query.type
}
},

当excel中有日期格式的时候,实际转化的值为一个数字,我们需要一个方法进行转化

1
2
3
4
5
6
7
8
9
10
11
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 + format + date
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}

需要注意,**导入的手机号不能和之前的存在的手机号重复**

逻辑判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
async  success({ header, results }) {
if (this.type === 'user') {
const userRelations = {
'入职日期': 'timeOfEntry',
'手机号': 'mobile',
'姓名': 'username',
'转正日期': 'correctionTime',
'工号': 'workNumber'
}
const arr = []
// 遍历所有的数组
results.forEach(item => {
// 需要将每一个条数据里面的中文都换成英文
const userInfo = {}
Object.keys(item).forEach(key => {
// key是当前的中文名 找到对应的英文名
if (userRelations[key] === 'timeOfEntry' || userRelations[key] === 'correctionTime') {
userInfo[userRelations[key]] = new Date(this.formatDate(item[key], '/')) // 只有这样, 才能入库
return
}
userInfo[userRelations[key]] = item[key]
})
// 最终userInfo变成了全是英文
arr.push(userInfo)
})
await importEmployee(arr)
this.$message.success('导入成功')
}
this.$router.back() // 回到上一页
},
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 + format + date
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}

员工页面跳转

1
2
<el-button type="warning" size="small" @click="$router.push('/import?type=user')">导入</el-button>

**目标**: 实现员工的导入

员工导出excel功能

目标: 实现将员工数据导出功能

日常业务中,我们经常遇到excel导出功能, 怎么使用呢

Excel 的导入导出都是依赖于js-xlsx来实现的。

js-xlsx的基础上又封装了Export2Excel.js来方便导出数据。

安装excel所需依赖和按需加载

由于 Export2Excel不仅依赖js-xlsx还依赖file-saverscript-loader

所以你先需要安装如下命令:

1
2
npm install xlsx file-saver -S
npm install script-loader -S -D

由于js-xlsx体积还是很大的,导出功能也不是一个非常常用的功能,所以使用的时候建议使用懒加载。使用方法如下:

1
2
3
4
5
6
7
8
9
import('@/vendor/Export2Excel').then(excel => {
excel.export_json_to_excel({
header: tHeader, //表头 必填
data, //具体数据 必填
filename: 'excel-list', //非必填
autoWidth: true, //非必填
bookType: 'xlsx' //非必填
})
})

excel导出参数的介绍

vue-element-admin提供了导出的功能模块,在课程资源/excel导出目录下,放置到src目录下

参数

参数 说明 类型 可选值 默认值
header 导出数据的表头 Array / []
data 导出的具体数据 Array / [[]]
filename 导出文件名 String / excel-list
autoWidth 单元格是否要自适应宽度 Boolean true / false true
bookType 导出文件类型 String xlsx, csv, txt, more xlsx

excel导出基本的结构

我们最重要的一件事,就是把表头和数据进行相应的对应

因为数据中的key是英文,想要导出的表头是中文的话,需要将中文和英文做对应

1
2
3
4
5
6
7
8
9
const headers = {
'手机号': 'mobile',
'姓名': 'username',
'入职日期': 'timeOfEntry',
'聘用形式': 'formOfEmployment',
'转正日期': 'correctionTime',
'工号': 'workNumber',
'部门': 'departmentName'
}

然后,完成导出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 导出excel数据
exportData() {
// 做操作
// 表头对应关系
const headers = {
'姓名': 'username',
'手机号': 'mobile',
'入职日期': 'timeOfEntry',
'聘用形式': 'formOfEmployment',
'转正日期': 'correctionTime',
'工号': 'workNumber',
'部门': 'departmentName'
}
// 懒加载
import('@/vendor/Export2Excel').then(async excel => {
const { rows } = await getEmployeeList({ page: 1, size: this.page.total })
const data = this.formatJson(headers, rows)

excel.export_json_to_excel({
header: Object.keys(headers),
data,
filename: '员工信息表',
autoWidth: true,
bookType: 'xlsx'

})
// 获取所有的数据

// excel.export_json_to_excel({
// header: ['姓名', '薪资'],
// data: [['张三', 12000], ['李四', 5000]],
// filename: '员工薪资表',
// autoWidth: true,
// bookType: 'csv'
// })
})
},
// 该方法负责将数组转化成二维数组
formatJson(headers, rows) {
// 首先遍历数组
// [{ username: '张三'},{},{}] => [[’张三'],[],[]]
return rows.map(item => {
return Object.keys(headers).map(key => {
if (headers[key] === 'timeOfEntry' || headers[key] === 'correctionTime') {
return formatDate(item[headers[key]]) // 返回格式化之前的时间
} else if (headers[key] === 'formOfEmployment') {
var en = EmployeeEnum.hireType.find(obj => obj.id === item[headers[key]])
return en ? en.value : '未知'
}
return item[headers[key]]
}) // => ["张三", "13811","2018","1", "2018", "10002"]
})
// return data
// return rows.map(item => {
// // item是对象 => 转化成只有值的数组 => 数组值的顺序依赖headers {username: '张三' }
// // Object.keys(headers) => ["姓名", "手机号",...]
// return Object.keys(headers).map(key => {
// return item[headers[key]]
// }) // / 得到 ['张三',’129‘,’dd‘,'dd']
// })
}

导出时间格式的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
formatJson(headers, rows) {
return rows.map(item => {
// item是一个对象 { mobile: 132111,username: '张三' }
// ["手机号", "姓名", "入职日期" 。。]
return Object.keys(headers).map(key => {
// 需要判断 字段
if (headers[key] === 'timeOfEntry' || headers[key] === 'correctionTime') {
// 格式化日期
return formatDate(item[headers[key]])
} else if (headers[key] === 'formOfEmployment') {
const obj = EmployeeEnum.hireType.find(obj => obj.id === item[headers[key]])
return obj ? obj.value : '未知'
}
return item[headers[key]]
})
// ["132", '张三’, ‘’,‘’,‘’d]
})
// return rows.map(item => Object.keys(headers).map(key => item[headers[key]]))
// 需要处理时间格式问题
}

扩展 复杂表头的导出

当需要导出复杂表头的时候,vue-element-admin同样支持该类操作

vue-element-admin 提供的导出方法中有 multiHeadermerges 的参数

参数 说明 类型 可选值 默认值
multiHeader 复杂表头的部分 Array / [[]]
merges 需要合并的部分 Array / []

multiHeader里面是一个二维数组,里面的一个元素是一行表头,假设你想得到一个如图的结构

image-20201014152302636

mutiHeader应该这样定义

1
const multiHeader = [['姓名', '主要信息', '', '', '', '', '部门']]

multiHeader中的一行表头中的字段的个数需要和真正的列数相等,假设想要跨列,多余的空间需要定义成空串

它主要对应的是标准的表头

1
const header = ['姓名', '手机号', '入职日期', '聘用形式', '转正日期', '工号', '部门']

如果,我们要实现其合并的效果, 需要设定merges选项

1
const merges = ['A1:A2', 'B1:F1', 'G1:G2']

merges的顺序是没关系的,只要配置这两个属性,就可以导出复杂表头的excel了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
exportData() {
const headers = {
'姓名': 'username',
'手机号': 'mobile',
'入职日期': 'timeOfEntry',
'聘用形式': 'formOfEmployment',
'转正日期': 'correctionTime',
'工号': 'workNumber',
'部门': 'departmentName'
}
// 导出excel
import('@/vendor/Export2Excel').then(async excel => {
// excel是引入文件的导出对象
// 导出 header从哪里来
// data从哪里来
// 现在没有一个接口获取所有的数据
// 获取员工的接口 页码 每页条数 100 1 10000
const { rows } = await getEmployeeList({ page: 1, size: this.page.total })
const data = this.formatJson(headers, rows) // 返回的data就是 要导出的结构
const multiHeader = [['姓名', '主要信息', '', '', '', '', '部门']]
const merges = ['A1:A2', 'B1:F1', 'G1:G2']
excel.export_json_to_excel({
header: Object.keys(headers),
data,
filename: '员工资料表',
multiHeader, // 复杂表头
merges // 合并选项
})

// excel.export_json_to_excel({
// header: ['姓名', '工资'],
// data: [['张三', 3000], ['李四', 5000]],
// filename: '员工工资表'
// })
// [{ username: '张三',mobile: 13112345678 }] => [[]]
// 要转化 数据结构 还要和表头的顺序对应上
// 要求转出的标题是中文
})
},
// 将表头数据和数据进行对应
// [{}] => [[]]
formatJson(headers, rows) {
return rows.map(item => {
// item是一个对象 { mobile: 132111,username: '张三' }
// ["手机号", "姓名", "入职日期" 。。]
return Object.keys(headers).map(key => {
// 需要判断 字段
if (headers[key] === 'timeOfEntry' || headers[key] === 'correctionTime') {
// 格式化日期
return formatDate(item[headers[key]])
} else if (headers[key] === 'formOfEmployment') {
const obj = EmployeeEnum.hireType.find(obj => obj.id === item[headers[key]])
return obj ? obj.value : '未知'
}
return item[headers[key]]
})
// ["132", '张三’, ‘’,‘’,‘’d]
})
// return rows.map(item => Object.keys(headers).map(key => item[headers[key]]))
// 需要处理时间格式问题
}

提交代码

**本节任务**实现将员工数据导出功能

员工详情页创建和布局

**目标**:创建员工详情的主要布局页面和基本布局

详情页的基本布局和路由

image-20200726130305221

建立详情页路由

1
2
3
4
5
6
7
8
{
path: 'detail/:id', // query传参 动态路由传参
component: () => import('@/views/employees/detail'),
hidden: true, // 不在左侧菜单显示
meta: {
title: '员工详情' // 标记当前路由规则的中文名称 后续在做左侧菜单时 使用
}
}

建立基本架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="dashboard-container">
<div class="app-container">
<el-card>
<el-tabs>
<el-tab-pane label="登录账户设置">

<!-- 放置表单 -->
<el-form label-width="120px" style="margin-left: 120px; margin-top:30px">
<el-form-item label="姓名:">
<el-input style="width:300px" />
</el-form-item>
<el-form-item label="密码:">
<el-input style="width:300px" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary">更新</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="个人详情" />
<el-tab-pane label="岗位信息" />
</el-tabs>
</el-card>
</div>
</div>

列表跳转到详情

1
2
<el-button type="text" size="small" @click="$router.push(`/employees/detail/${obj.row.id}`)">查看</el-button>

读取和保存用户信息的接口

加载个人基本信息 > 该接口已经在之前提供过了 src/api/user.js

1
2
3
4
5
6
7
8
9
10
/** *
* 获取某个用户的基本信息
*
* ***/
export function getUserDetailById(id) {
return request({
url: `/sys/user/${id}`
})
}

保存个人基本信息 src/api/employees.js

1
2
3
4
5
6
7
8
9
10
11
12
/** *
*
* 保存员工的基本信息
* **/
export function saveUserDetailById(data) {
return request({
url: `/sys/user/${data.id}`,
method: 'put',
data
})
}

实现用户名和密码的修改

注意:这里有个缺陷,接口中读取的是后端的密文,我们并不能解密,所以当我们没有任何修改就保存时,会校验失败,因为密文超过了规定的12位长度,为了真的修改密码,我们设定了一个临时的字段 password2,用它来存储我们的修改值,最后保存的时候,把password2传给password

用户名和密码的修改 src/views/employees/detail.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { getUserDetailById } from '@/api/user'
import { saveUserDetailById } from '@/api/employees'
export default {
data() {
return {
userId: this.$route.params.id, // 这样可以后面直接通过 this.userId进行获取数据
userInfo: {
// 专门存放基本信息
username: '',
password2: ''
},
rules: {
username: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
password2: [{ required: true, message: '密码不能为空', trigger: 'blur' },
{ min: 6, max: 9, message: '密码长度6-9位', trigger: 'blur' }]
}
}
},
created() {
this.getUserDetailById()
},
methods: {
async getUserDetailById() {
this.userInfo = await getUserDetailById(this.userId)
},
async saveUser() {
try {
// 校验
await this.$refs.userForm.validate()
await saveUserDetailById({ ...this.userInfo, password: this.userInfo.password2 }) // 将新密码的值替换原密码的值
this.$message.success('保存成功')
} catch (error) {
console.log(error)
}
}
}
}

绑定表单数据

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 放置表单 -->
<el-form ref="userForm" :model="userInfo" :rules="rules" label-width="120px" style="margin-left: 120px; margin-top:30px">
<el-form-item label="姓名:" prop="username">
<el-input v-model="userInfo.username" style="width:300px" />
</el-form-item>
<el-form-item label="新密码:" prop="password2">
<el-input v-model="userInfo.password2" style="width:300px" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveUser">更新</el-button>
</el-form-item>
</el-form>

提交代码

个人组件和岗位组件封装

封装个人详情组件

我们将员工个人信息分为三部分,账户,个人, 岗位,这个小节我们对个人组件和岗位组件进行封装

image-20200901105323305

封装个人组件 src/views/employees/components/user-info.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
<template>
<div class="user-info">
<!-- 个人信息 -->
<el-form label-width="220px">
<!-- 工号 入职时间 -->
<el-row class="inline-info">
<el-col :span="12">
<el-form-item label="工号">
<el-input v-model="userInfo.workNumber" class="inputW" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入职时间">
<el-date-picker
v-model="userInfo.timeOfEntry"
type="date"
class="inputW"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 姓名 部门 -->
<el-row class="inline-info">
<el-col :span="12">
<el-form-item label="姓名">
<el-input v-model="userInfo.username" class="inputW" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门">
<el-input v-model="userInfo.departmentName" class="inputW" />
</el-form-item>
</el-col>
</el-row>
<!--手机 聘用形式 -->
<el-row class="inline-info">
<el-col :span="12">
<el-form-item label="手机">
<el-input v-model="userInfo.mobile" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="聘用形式">
<el-select v-model="userInfo.formOfEmployment" class="inputW">
<el-option
v-for="item in EmployeeEnum.hireType"
:key="item.id"
:label="item.value"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 员工照片 -->
<el-row class="inline-info">
<el-col :span="12">
<el-form-item label="员工头像">
<!-- 放置上传图片 -->

</el-form-item>
</el-col>
</el-row>
<!-- 保存个人信息 -->
<el-row class="inline-info" type="flex" justify="center">
<el-col :span="12">
<el-button type="primary" @click="saveUser">保存更新</el-button>
<el-button @click="$router.back()">返回</el-button>

</el-col>
</el-row>
</el-form>
<!-- 基础信息 -->
<el-form label-width="220px">
<div class="block">
<div class="title">基础信息</div>
<el-form-item label="最高学历">
<el-select v-model="formData.theHighestDegreeOfEducation" class="inputW2">
<el-option
v-for="item in EmployeeEnum.highestDegree"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!-- 个人头像 -->
<!-- 员工照片 -->

<el-form-item label="员工照片">
<!-- 放置上传图片 -->
</el-form-item>
<el-form-item label="国家/地区">
<el-select v-model="formData.nationalArea" class="inputW2">
<el-option
v-for="item in EmployeeEnum.isOverseas"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="护照号">
<el-input v-model="formData.passportNo" placeholder="正规护照格式" class="inputW" />
</el-form-item>
<el-form-item label="身份证号">
<el-input v-model="formData.idNumber" placeholder="正规身份证格式" class="inputW" />
</el-form-item>
<el-form-item label="籍贯">
<el-input v-model="formData.nativePlace" placeholder="籍贯地址" class="inputW5" />
</el-form-item>
<el-form-item label="民族">
<el-input v-model="formData.nation" placeholder="请输入民族" class="inputW2" />
</el-form-item>
<el-form-item label="婚姻状况">
<el-select v-model="formData.maritalStatus" class="inputW2">
<el-option
v-for="item in EmployeeEnum.maritaStatus"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="生日">
<el-input v-model="formData.birthday" placeholder="示例 0323" class="inputW" />
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="formData.age" type="number" class="inputW2" />
</el-form-item>
<el-form-item label="星座">
<el-select v-model="formData.constellation" class="inputW2">
<el-option
v-for="item in EmployeeEnum.constellation"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="血型">
<el-select v-model="formData.bloodType" class="inputW2">
<el-option
v-for="item in EmployeeEnum.bloodType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="户籍所在地">
<el-input v-model="formData.domicile" class="inputW5" />
</el-form-item>
<el-form-item label="政治面貌">
<el-input v-model="formData.politicalOutlook" class="inputW2" />
</el-form-item>
<el-form-item label="入党时间">
<el-date-picker
v-model="formData.timeToJoinTheParty"
type="date"
placeholder="选择日期"
class="inputW"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="存档机构">
<el-input v-model="formData.archivingOrganization" placeholder="请输入" />
</el-form-item>
<el-form-item label="子女状态">
<el-input v-model="formData.stateOfChildren" placeholder="请输入" />
</el-form-item>
<el-form-item label="子女有无商业险">
<el-radio-group v-model="formData.doChildrenHaveCommercialInsurance">
<el-radio label="1">有</el-radio>
<el-radio label="2">无</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="有无违法违纪状态">
<el-input v-model="formData.isThereAnyViolationOfLawOrDiscipline" placeholder="请输入" />
</el-form-item>
<el-form-item label="有无重大病史">
<el-input v-model="formData.areThereAnyMajorMedicalHistories" placeholder="请输入" />
</el-form-item>
</div>
<!-- 通讯信息 -->
<div class="block">
<div class="title">通讯信息</div>
<el-form-item label="QQ">
<el-input v-model="formData.qq" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="微信">
<el-input v-model="formData.wechat" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="现居住地">
<el-input v-model="formData.placeOfResidence" placeholder="请输入" />
</el-form-item>
<el-form-item label="通讯地址">
<el-input v-model="formData.postalAddress" placeholder="请输入" />
</el-form-item>
<el-form-item label="联系手机">
<el-input v-model="formData.contactTheMobilePhone" placeholder="11位字符" maxlength="11" class="inputW" @change.native="handlePhone(2)" />
</el-form-item>
<el-form-item label="个人邮箱">
<el-input v-model="formData.personalMailbox" placeholder="请输入" type="mail" class="inputW" />
</el-form-item>
<el-form-item label="紧急联系人">
<el-input v-model="formData.emergencyContact" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="紧急联系电话">
<el-input v-model="formData.emergencyContactNumber" placeholder="11位字符" class="inputW" />
</el-form-item>
</div>
<!-- 账号信息 -->
<div class="block">
<div class="title">账号信息</div>
<el-form-item label="社保电脑号">
<el-input v-model="formData.socialSecurityComputerNumber" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="公积金账号">
<el-input v-model="formData.providentFundAccount" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="银行卡号">
<el-input v-model="formData.bankCardNumber" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="开户行">
<el-input v-model="formData.openingBank" placeholder="请输入" class="inputW" />
</el-form-item>
</div>
<!-- 教育信息 -->
<div class="block">
<div class="title">教育信息</div>
<el-form-item label="学历类型">
<el-select v-model="formData.educationalType" placeholder="请选择">
<el-option
v-for="item in EmployeeEnum.educationType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="毕业学校">
<el-input v-model="formData.graduateSchool" placeholder="请输入" class="inputW2" />
</el-form-item>
<el-form-item label="入学时间">
<el-date-picker v-model="formData.enrolmentTime" type="data" placeholder="请输入时间" class="inputW" value-format="yyyy-MM-dd" />
</el-form-item>
<el-form-item label="毕业时间">
<el-date-picker v-model="formData.graduationTime" type="data" placeholder="请输入时间" class="inputW" value-format="yyyy-MM-dd" />
</el-form-item>
<el-form-item label="专业">
<el-input v-model="formData.major" placeholder="请输入" class="inputW" />
</el-form-item>
</div>
<!-- 从业信息 -->
<div class="block">
<div class="title">从业信息</div>
<el-form-item label="上家公司">
<el-input v-model="formData.homeCompany" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="职称">
<el-input v-model="formData.title" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="有无竞业限制">
<el-input v-model="formData.isThereAnyCompetitionRestriction" placeholder="请输入" style="width:80%" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="formData.remarks" type="textarea" placeholder="请输入备注" style="width:80%" />
</el-form-item>
<!-- 保存员工信息 -->
<el-row class="inline-info" type="flex" justify="center">
<el-col :span="12">
<el-button type="primary" @click="savePersonal">保存更新</el-button>
<el-button @click="$router.back()">返回</el-button>
</el-col>
</el-row>
</div>
</el-form>

</div>

</template>

本章节个人数据过于**繁杂,庞大**,同学们在开发期间,拷贝代码即可,我们只写关键部位的代码

定义user-info的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import EmployeeEnum from '@/api/constant/employees'

export default {
data() {
return {
userId: this.$route.params.id,
EmployeeEnum, // 员工枚举数据
userInfo: {},
formData: {
userId: '',
username: '', // 用户名
sex: '', // 性别
mobile: '', // 手机
companyId: '', // 公司id
departmentName: '', // 部门名称
// onTheJobStatus: '', // 在职状态 no
dateOfBirth: '', // 出生日期
timeOfEntry: '', // 入职时间
theHighestDegreeOfEducation: '', // 最高学历
nationalArea: '', // 国家
passportNo: '', // 护照号
idNumber: '', // 身份证号
idCardPhotoPositive: '', // 身份证照正
idCardPhotoBack: '', // 身份证照正
nativePlace: '', // 籍贯
nation: '', // 民族
englishName: '', // 英文名字
maritalStatus: '', // 婚姻状况
staffPhoto: '', // 员工照片
birthday: '', // 生日
zodiac: '', // 属相
age: '', // 年龄
constellation: '', // 星座
bloodType: '', // 血型
domicile: '', // 户籍所在地
politicalOutlook: '', // 政治面貌
timeToJoinTheParty: '', // 入党时间
archivingOrganization: '', // 存档机构
stateOfChildren: '', // 子女状态
doChildrenHaveCommercialInsurance: '1', // 保险状态
isThereAnyViolationOfLawOrDiscipline: '', // 违法违纪状态
areThereAnyMajorMedicalHistories: '', // 重大病史
qq: '', // QQ
wechat: '', // 微信
residenceCardCity: '', // 居住证城市
dateOfResidencePermit: '', // 居住证办理日期
residencePermitDeadline: '', // 居住证截止日期
placeOfResidence: '', // 现居住地
postalAddress: '', // 通讯地址
contactTheMobilePhone: '', // 联系手机
personalMailbox: '', // 个人邮箱
emergencyContact: '', // 紧急联系人
emergencyContactNumber: '', // 紧急联系电话
socialSecurityComputerNumber: '', // 社保电脑号
providentFundAccount: '', // 公积金账号
bankCardNumber: '', // 银行卡号
openingBank: '', // 开户行
educationalType: '', // 学历类型
graduateSchool: '', // 毕业学校
enrolmentTime: '', // 入学时间
graduationTime: '', // 毕业时间
major: '', // 专业
graduationCertificate: '', // 毕业证书
certificateOfAcademicDegree: '', // 学位证书
homeCompany: '', // 上家公司
title: '', // 职称
resume: '', // 简历
isThereAnyCompetitionRestriction: '', // 有无竞业限制
proofOfDepartureOfFormerCompany: '', // 前公司离职证明
remarks: '' // 备注
}
}
}
}

在detail.vue组件中,注册并使用

1
2
3
4
5
<el-tab-pane label="个人详情">
<!-- 放置个人详情 -->
<component :is="userComponent" />
<!-- <user-info /> -->
</el-tab-pane>

在以上代码中,我们使用了动态组件component,它通过 **is属性来绑定需要显示在该位置的组件,is属性可以直接为注册组件**的组件名称即可

封装岗位组件

同理,封装岗位组件

封装岗位组件 src/views/employee/components/job-info.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<template>
<div class="job-info">
<!-- 基础信息 -->
<el-form label-width="220px">
<div class="block">
<div class="title">基础信息</div>
<el-form-item label="岗位">
<el-input v-model="formData.post" placeholder="请输入" class="inputW" />
</el-form-item>
<!-- <el-form-item label="转正日期">
<el-date-picker
v-model="formData.dateOfCorrection"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
/>
</el-form-item> -->
<el-form-item label="转正状态">
<el-select v-model="formData.stateOfCorrection" placeholder="请选择" disabled>
<el-option
v-for="item in EmployeeEnum.stateOfCorrection"
:key="item.value"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="职级">
<el-input v-model="formData.rank" class="inputW" />
</el-form-item>
<el-form-item label="转正评价">
<el-input v-model="formData.correctionEvaluation" type="textarea" placeholder="1-300位字符" />
</el-form-item>
<el-form-item label="汇报对象">
<el-select v-model="formData.reportId" filterable placeholder="请选择" class="inputW">
<el-option v-for="item in depts" :key="item.id" :label="item.username" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="HRBP">
<el-select v-model="formData.hrbp" filterable placeholder="请选择" class="inputW">
<el-option v-for="item in depts" :key="item.id" :label="item.username" :value="item.id" class="inputW" />
</el-select>
</el-form-item>
<el-form-item class="formInfo" label="调整司龄(天):">
<el-input v-model="formData.adjustmentAgedays" type="number" placeholder="请输入" class="inputW" />
</el-form-item>
<el-form-item label="首次参加工作时间">
<el-date-picker
v-model="formData.workingTimeForTheFirstTime"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="调整工龄">
<el-input v-model="formData.adjustmentOfLengthOfService" placeholder="0.00年" class="inputW" disabled />
</el-form-item>
</div>
<!-- 合同信息 -->
<div class="block">
<div class="title">合同信息</div>
<el-form-item class="formInfo" label="首次合同开始时间:">
<el-date-picker
v-model="formData.initialContractStartTime"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="首次合同结束时间">
<el-date-picker
v-model="formData.firstContractTerminationTime"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="现合同开始时间">
<el-date-picker
v-model="formData.currentContractStartTime"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="现合同结束时间">
<el-date-picker
v-model="formData.closingTimeOfCurrentContract "
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="合同期限">
<el-select v-model="formData.contractPeriod" class="filter-item">
<el-option
v-for="item in EmployeeEnum.contractPeriod"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="续签次数">
<el-select v-model="formData.renewalNumber" class="filter-item">
<el-option
v-for="item in EmployeeEnum.renewalCount"
:key="item.id"
:label="item.value"
:value="item.id"
/>
</el-select>
</el-form-item>
</div>
<!-- 招聘信息 -->
<div class="block">
<div class="title">招聘信息</div>
<el-form-item label="其他招聘渠道">
<el-select v-model="formData.otherRecruitmentChannels" placeholder="请选择">
<el-option
v-for="item in EmployeeEnum.resumeSource"
:key="item.id"
:label="item.value"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="招聘渠道">
<el-select v-model="formData.recruitmentChannels" placeholder="请选择">
<el-option
v-for="item in EmployeeEnum.resumeSource"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社招/校招">
<el-select v-model="formData.socialRecruitment" placeholder="请选择">
<el-option
v-for="item in EmployeeEnum.hireSourceType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="推荐企业/人">
<el-input v-model="formData.recommenderBusinessPeople" placeholder="请输入" class="infoPosition inputW" />
</el-form-item>
</div>
<!-- 从业信息 -->
<el-form-item>
<el-button type="primary" @click="saveJob">保存更新</el-button>
<el-button @click="$router.back()">返回</el-button>
</el-form-item>
</el-form>
</div>

</template>

定义岗位数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import EmployeeEnum from '@/api/constant/employees'

export default {
data() {
return {
userId: this.$route.params.id,
depts: [],
EmployeeEnum,
formData: {
adjustmentAgedays: '', // 调整司龄天
adjustmentOfLengthOfService: '', // 调整工龄天
closingTimeOfCurrentContract: '', // 现合同结束时间
companyId: '', // 公司ID
contractDocuments: '', // 合同文件
contractPeriod: '', // 合同期限
correctionEvaluation: '', // 转正评价
currentContractStartTime: '', // 现合同开始时间
firstContractTerminationTime: '', // 首次合同结束时间
hrbp: '', // HRBP
initialContractStartTime: '', // 首次合同开始时间
otherRecruitmentChannels: '', // 其他招聘渠道
post: '', // 岗位
rank: null, // 职级
recommenderBusinessPeople: '', // 推荐企业人
recruitmentChannels: '', // 招聘渠道
renewalNumber: '', // 续签次数
reportId: '', // 汇报对象
reportName: null, // 汇报对象
socialRecruitment: '', // 社招校招
stateOfCorrection: '', // 转正状态
taxableCity: '', // 纳税城市
userId: '', // 员工ID
workMailbox: '', // 工作邮箱
workingCity: '', // 工作城市
workingTimeForTheFirstTime: '' // 首次参加工作时间
}
}
}
}

在detail.vue组件中,注册并使用

1
2
3
4
<el-tab-pane label="岗位详情">
<!-- 放置岗位详情 -->
<component :is="JobInfo" />
</el-tab-pane>

**本节任务**:完成个人组件和岗位组件封装

员工个人信息和岗位信息-读取-保存

**目标**:实现个人信息的岗位信息的读取和校验,保存

读取个人保存个人信息

这个环节里面大部分都是繁杂的属性和重复的过程,所以该环节直接将过程代码拷贝到项目中即可

封装 读取个人信息 保存个人信息 读取岗位信息 保存岗位信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/** *
* 读取用户详情的基础信息
* **/
export function getPersonalDetail(id) {
return request({
url: `/employees/${id}/personalInfo`
})
}

/** *
* 更新用户详情的基础信息
* **/
export function updatePersonal(data) {
return request({
url: `/employees/${data.userId}/personalInfo`,
method: 'put',
data
})
}


/** **
* 获取用户的岗位信息
*
* ****/
export function getJobDetail(id) {
return request({
url: `/employees/${id}/jobs`
})
}


/**
* 保存岗位信息
* ****/
export function updateJob(data) {
return request({
url: `/employees/${data.userId}/jobs`,
method: 'put',
data
})
}

读取,保存个人信息 user-info 需要注意:这里的保存实际上分成了两个接口,这是接口的设计,我们只能遵守

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { getPersonalDetail, updatePersonal, saveUserDetailById } from '@/api/employees'
import { getUserDetailById } from '@/api/user'
created() {
this.getPersonalDetail()
this.getUserDetailById()
},
methods: {
async getPersonalDetail() {
this.formData = await getPersonalDetail(this.userId) // 获取员工数据
},
async savePersonal() {
await updatePersonal({ ...this.formData, id: this.userId })
this.$message.success('保存成功')
},
async saveUser() {
// 调用父组件
await saveUserDetailById(this.userInfo)
this.$message.success('保存成功')
},
async getUserDetailById() {
this.userInfo = await getUserDetailById(this.userId)
}
}

读取保存岗位信息

读取,保存岗位信息 job-info

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { getEmployeeSimple, updateJob, getJobDetail } from '@/api/employees'

created() {
this.getJobDetail()
this.getEmployeeSimple()
},
methods: {
async getJobDetail() {
this.formData = await getJobDetail(this.userId)
},
// 获取员工列表
async getEmployeeSimple() {
this.depts = await getEmployeeSimple()
},
// 保存岗位信息
async saveJob() {
await updateJob(this.formData)
this.$message.success('保存岗位信息成功')
}
}

提交代码

本节任务 实现个人信息的岗位信息的读取和校验,保存

配置腾讯云Cos

**目标**: 配置一个腾讯云cos

由于上课的开发的特殊性,我们不希望把所有的图片都上传到我们自己的官方服务器上,这里我们可以采用一个腾讯云的图片方案

image-20200805200831035

上边图的意思就是说,我们找一个可以免费上传图片的服务器,帮我们**代管图片,我们在自己的数据库里只保存一个地址就行, 这其实也是很多项目的处理方案,会有一个公共的文件服务器**

第一步,我们必须先拥有一个腾迅云的开发者账号(小心腾讯云的广告电话)

请按照腾讯云的注册方式,注册自己的账号

第二步,实名认证

选择个人账户

image-20200805201013078

填写个人身份信息

image-20200805201230001

下一步,扫描二维码授权

image-20200805201318977

手机端授权

image-20200805201422567

点击领取免费产品

image-20200805201513338

选择对象存储COS

image-20200805201559233

我们免费拥有**6个月的50G流量**的对象存储空间使用权限,足够我们上传用户头像的使用了

点击0元试用,开通服务

image-20200805201750662

到这一步,账号的部分就操作完毕,接下来,我们需要来创建一个存储图片的存储桶

登录 对象存储控制台创建存储桶。设置存储桶的权限为 公有读,私有写

image-20200806135409338

image-20200916231600661

设置cors规则

image-20200916231949174

AllowHeader 需配成*,如下图所示。

image-20200805211749522

因为我们本身没有域名,所以这里设置成*****,仅限于测试,正式环境的话,这里需要配置真实的域名地址

到这里,我们的腾讯云存储桶就设置好了。

封装上传图片组件-上传组件需求分析

目标 梳理整个的上传过程

初始化cos对象参数

名称 描述
SecretId 开发者拥有的项目身份识别 ID,用以身份认证,可在 API 密钥管理 页面获取
SecretKey 开发者拥有的项目身份密钥,可在 API 密钥管理 页面获取

注意,上述的参数我们在本次开发过程中,直接将参数放置在前端代码中存储,但是腾讯云本身是不建议这么做的,因为**敏感信息**放在前端很容易被捕获,由于我们本次是测试研发,所以这个过程可以忽略

正确的做法应该是,通过网站调用接口换取敏感信息

相关文档

实例化 上传sdk

1
2
3
4
var cos = new COS({
SecretId: 'COS_SECRETID', // 身份识别 ID
SecretKey: 'COS_SECRETKEY', // 身份密钥
});

到目前为止,我们上传图片准备的内容就已经OK,接下来,我们在**src/componets** 新建一个**ImageUpload** 组件

该组件需要满足什么要求呢?

  1. 可以显示传入的图片地址
  2. 可以删除传入的图片地址
  3. 可以上传图片到云服务器
  4. 上传到腾讯云之后,可以返回图片地址,显示
  5. 上传成功之后,可以回调成功函数

这个上传组件简单吗?

no ! ! !

看似需求很明确,但是它真正的实现很复杂,我们通过一个图来看一下

image-20200806174643390

从上图中,我们可以看到,实际上是有两种场景的,本地场景和已经上传的场景

下个章节,针对这个场景我们进行开发

封装上传组件-代码实现

**目标**实现上传组件的代码部分

JavaScript SDK 需浏览器支持基本的 HTML5 特性(支持 IE10 以上浏览器),以便支持 ajax 上传文件和计算文件 MD5 值。

新建文件上传组件

安装JavaScript SDK

1
$ npm i cos-js-sdk-v5 --save

新建上传图片组件 src/components/ImageUpload/index.vue

上传组件,我们可以沿用element的el-upload组件,并且采用照片墙的模式 list-type="picture-card"

放置el-upload组件

1
2
3
4
5
<template>
<el-upload list-type="picture-card">
<i class="el-icon-plus" />
</el-upload>
</template>

全局注册组件

1
2
3
4
5
6
7
8
9
10
11
import PageTools from './PageTools'
import UploadExcel from './UploadExcel'
import ImageUpload from './ImageUpload'
export default {
install(Vue) {
Vue.component('PageTools', PageTools) // 注册工具栏组件
Vue.component('UploadExcel', UploadExcel) // 注册导入excel组件
Vue.component('ImageUpload', ImageUpload) // 注册导入上传组件
}
}

点击图片进行预览

限定上传的图片数量和action

1
2
3
4
<template>
<el-upload list-type="picture-card" :limit="1" action="#">
</el-upload>
</template>

action为什么给#, 因为前面我们讲过了,我们要上传到腾讯云,需要自定义的上传方式,action给个#防止报错

预览

1
2
3
4
5
6
7
data() {
return {
fileList: [], // 图片地址设置为数组
showDialog: false, // 控制显示弹层
imgUrl: ''
}
},
1
2
3
4
5
   preview(file) {
// 这里应该弹出一个层 层里是点击的图片地址
this.imgUrl = file.url
this.showDialog = true
},

预览弹层

1
2
3
<el-dialog title="图片" :visible.sync="showDialog">
<img :src="imgUrl" style="width:100%" alt="">
</el-dialog>

根据上传数量控制上传按钮

控制上传显示

1
2
3
4
5
6
computed: {
// 设定一个计算属性 判断是否已经上传完了一张
fileComputed() {
return this.fileList.length === 1
}
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <el-upload
:on-preview="preview"
:on-remove="handleRemove"
:on-change="changeFile"
:file-list="fileList"
list-type="picture-card"
action="#"
:limit="1"
:class="{disabled: fileComputed }"
>

<style>
.disabled .el-upload--picture-card {
display: none
}
</style>

删除图片和添加图片

删除文件

1
2
3
4
5
handleRemove(file) {
// file是点击删除的文件
// 将原来的文件给排除掉了 剩下的就是最新的数组了
this.fileList = this.fileList.filter(item => item.uid !== file.uid)
},

添加文件

1
2
3
4
5
6
7
8
// 修改文件时触发
// 此时可以用fileList 因为该方法会进来很多遍 不能每次都去push
// fileList因为fileList参数是当前传进来的最新参数 我们只需要将其转化成数组即可 需要转化成一个新的数组
// [] => [...fileList] [] => fileList.map()
// 上传成功之后 还会进来 需要实现上传代码的逻辑 这里才会成功
changeFile(file, fileList) {
this.fileList = fileList.map(item => item)
}

上传之前检查

控制上传图片的类型和上传大小, 如果不满足条件 返回false上传就会停止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
beforeUpload(file) {
// 要开始做文件上传的检查了
// 文件类型 文件大小
const types = ['image/jpeg', 'image/gif', 'image/bmp', 'image/png']
if (!types.includes(file.type)) {
this.$message.error('上传图片只能是 JPG、GIF、BMP、PNG 格式!')
return false
}
// 检查大小
const maxSize = 5 * 1024 * 1024
if (maxSize < file.size) {
this.$message.error('图片大小最大不能超过5M')
return false
}
return true
}

上传动作调用上传腾讯云

上传动作为el-upload的http-request属性

1
2
3
4
5
6
7
:http-request="upload"

// 自定义上传动作 有个参数 有个file对象,是我们需要上传到腾讯云服务器的内容
upload(params) {
console.log(params.file)
}

我们需要在该方法中,调用腾讯云的上传方法

腾讯云文档地址

身份ID和密钥可以通过腾讯云平台获取

登录 访问管理控制台 ,获取您的项目 SecretId 和 SecretKey。

image-20200805211539544

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 进行上传操作
upload(params) {
// console.log(params.file)
if (params.file) {
// 执行上传操作
cos.putObject({
Bucket: 'shuiruohanyu-106-1302806742', // 存储桶
Region: 'ap-beijing', // 地域
Key: params.file.name, // 文件名
Body: params.file, // 要上传的文件对象
StorageClass: 'STANDARD' // 上传的模式类型 直接默认 标准模式即可
// 上传到腾讯云 =》 哪个存储桶 哪个地域的存储桶 文件 格式 名称 回调
}, function(err, data) {
// data返回数据之后 应该如何处理
console.log(err || data)
})
}
}

上传成功之后处理返回数据

如何处理返回成功的返回数据

确定要上传记录id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
beforeUpload(file) {
// 先检查文件类型
const types = ['image/jpeg', 'image/gif', 'image/bmp', 'image/png']
if (!types.some(item => item === file.type)) {
// 如果不存在
this.$message.error('上传图片只能是 JPG、GIF、BMP、PNG 格式!')
return false // 上传终止
}
// 检查文件大小 5M 1M = 1024KB 1KB = 1024B
const maxSize = 5 * 1024 * 1024
if (file.size > maxSize) {
// 超过了限制的文件大小
this.$message.error('上传的图片大小不能大于5M')
return false
}
// 已经确定当前上传的就是当前的这个file了
this.currentFileUid = file.uid
return true // 最后一定要return true
},

处理返回数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 进行上传操作
upload(params) {
// console.log(params.file)
if (params.file) {
// 执行上传操作
cos.putObject({
Bucket: 'shuiruohanyu-106-1302806742', // 存储桶
Region: 'ap-beijing', // 地域
Key: params.file.name, // 文件名
Body: params.file, // 要上传的文件对象
StorageClass: 'STANDARD' // 上传的模式类型 直接默认 标准模式即可
// 上传到腾讯云 =》 哪个存储桶 哪个地域的存储桶 文件 格式 名称 回调
}, (err, data) => {
// data返回数据之后 应该如何处理
console.log(err || data)
// data中有一个statusCode === 200 的时候说明上传成功
if (!err && data.statusCode === 200) {
// 此时说明文件上传成功 要获取成功的返回地址
// fileList才能显示到上传组件上 此时我们要将fileList中的数据的url地址变成 现在上传成功的地址
// 目前虽然是一张图片 但是请注意 我们的fileList是一个数组
// 需要知道当前上传成功的是哪一张图片
this.fileList = this.fileList.map(item => {
// 去找谁的uid等于刚刚记录下来的id
if (item.uid === this.currentFileUid) {
// 将成功的地址赋值给原来的url属性
return { url: 'http://' + data.Location, upload: true }
// upload 为true 表示这张图片已经上传完毕 这个属性要为我们后期应用的时候做标记
// 保存 => 图片有大有小 => 上传速度有快又慢 =>要根据有没有upload这个标记来决定是否去保存
}
return item
})
// 将上传成功的地址 回写到了fileList中 fileList变化 =》 upload组件 就会根据fileList的变化而去渲染视图
}
})
}
}

我们在fileList中设置了属性为upload为true的属性,表示该图片已经上传成功了,如果fileList还有upload不为true的数据,那就表示该图片还没有上传完毕

上传的进度条显示

为了再上传图片过程中显示进度条,我们可以使用element-ui的进度条显示当前的上传进度

放置进度条

1
<el-progress v-if="showPercent" style="width: 180px" :percentage="percent" />

通过腾讯云sdk监听上传进度

1
2
3
4
5
6
7
8
9
10
11
12
cos.putObject({
// 配置
Bucket: 'laogao-1302806742', // 存储桶名称
Region: 'ap-guangzhou', // 存储桶地域
Key: params.file.name, // 文件名作为key
StorageClass: 'STANDARD', // 此类写死
Body: params.file, // 将本地的文件赋值给腾讯云配置
// 进度条
onProgress: (params) => {
this.percent = params.percent * 100
}
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<template>
<div>
<!-- 放置一个上传组件 -->
<!-- action这里不写上传地址 因为我们是调用腾讯云cos 不是一个地址可以搞定的 要用自定义的上传 -->
<el-upload
:on-preview="preview"
:on-remove="handleRemove"
:on-change="changeFile"
:before-upload="beforeUpload"
:file-list="fileList"
:http-request="upload"
list-type="picture-card"
action="#"
:limit="1"
:class="{disabled: fileComputed }"
>
<i class="el-icon-plus" />
</el-upload>
<!-- 进度条 -->
<el-progress v-if="showPercent" style="width: 180px" :percentage="percent" />
<!-- 放置一个弹层 -->
<!-- sync修饰符自动将弹层关闭了 -->
<el-dialog title="图片" :visible.sync="showDialog">
<img :src="imgUrl" style="width:100%" alt="">
</el-dialog>
</div>
</template>

<script>
import COS from 'cos-js-sdk-v5' // 引入腾讯云的包
// 需要实例化
const cos = new COS({
SecretId: 'SecretId',
SecretKey: 'SecretKey'
}) // 实例化的包 已经具有了上传的能力 可以上传到该账号里面的存储桶了
export default {
data() {
return {
fileList: [],
showDialog: false, // 控制图片的显示或者隐藏
imgUrl: '', // 存储点击的图片地址
currentFileUid: '', // 用一个变量 记住当前上传的图片id
percent: 0,
showPercent: false // 默认不显示进度条
}
},
computed: {
// 设定一个计算属性 判断是否已经上传完了一张
fileComputed() {
return this.fileList.length === 1
}
},
methods: {
preview(file) {
// 这里应该弹出一个层 层里是点击的图片地址
this.imgUrl = file.url
this.showDialog = true
},
handleRemove(file) {
// file是点击删除的文件
// 将原来的文件给排除掉了 剩下的就是最新的数组了
this.fileList = this.fileList.filter(item => item.uid !== file.uid)
},
// 修改文件时触发
// 此时可以用fileList 因为该方法会进来很多遍 不能每次都去push
// fileList因为fileList参数是当前传进来的最新参数 我们只需要将其转化成数组即可 需要转化成一个新的数组
// [] => [...fileList] [] => fileList.map()
// 上传成功之后 还会进来 需要实现上传代码的逻辑 这里才会成功
changeFile(file, fileList) {
this.fileList = fileList.map(item => item)
},
beforeUpload(file) {
// 要开始做文件上传的检查了
// 文件类型 文件大小
const types = ['image/jpeg', 'image/gif', 'image/bmp', 'image/png']
if (!types.includes(file.type)) {
this.$message.error('上传图片只能是 JPG、GIF、BMP、PNG 格式!')
return false
}
// 检查大小
const maxSize = 5 * 1024 * 1024
if (maxSize < file.size) {
this.$message.error('图片大小最大不能超过5M')
return false
}
// file.uid
this.currentFileUid = file.uid // 记住当前的uid
this.showPercent = true
return true
},
// 自定义上传动作 有个参数 有个file对象,是我们需要上传到腾讯云服务器的内容
upload(params) {
if (params.file) {
// 上传文件到腾讯云
cos.putObject({
// 配置
Bucket: 'laogao-1302806742', // 存储桶名称
Region: 'ap-guangzhou', // 存储桶地域
Key: params.file.name, // 文件名作为key
StorageClass: 'STANDARD', // 此类写死
Body: params.file, // 将本地的文件赋值给腾讯云配置
// 进度条
onProgress: (params) => {
this.percent = params.percent * 100
}
}, (err, data) => {
// 需要判断错误与成功
if (!err && data.statusCode === 200) {
// 如果没有失败表示成功了
// 此时认为上传成功了
// this.currentFileUid
// 仍然有个小问题, 比如此时我们正在上传,但是调用了保存,保存在上传过程中进行,
// 此时上传还没有完成 此时可以这样做 : 给所有上传成功的图片 加一个属性 upload: true
this.fileList = this.fileList.map(item => {
if (item.uid === this.currentFileUid) {
// upload为true表示 该图片已经成功上传到服务器,地址已经是腾讯云的地址了 就不可以执行保存了
return { url: 'http://' + data.Location, upload: true } // 将本地的地址换成腾讯云地址
}
return item
})
setTimeout(() => {
this.showPercent = false // 隐藏进度条
this.percent = 0 // 进度归0
}, 2000)

// 将腾讯云地址写入到fileList上 ,保存的时候 就可以从fileList中直接获取图片地址

// 此时注意,我们应该记住 当前上传的是哪个图片 上传成功之后,将图片的地址赋值回去
}
})
}
}
}
}
</script>

<style>
.disabled .el-upload--picture-card {
display: none
}
</style>

上传动作中,用到了上个小节中,我们注册的腾讯云cos的**存储桶名称地域名称**

通过上面的代码,我们会发现,我们把上传之后的图片信息都给了**fileList数据,那么在应用时,就可以直接获取该实例的fileList数据即可**

提交代码

本节任务 完成上传组件的封装

在员工详情中应用上传组件

**目标**:应用封装好的上传组件

将员工的头像和证件照赋值给上传组件

在**user-info.vue**中放置上传组件

员工头像

1
2
3
4
5
6
7
8
9
<!-- 员工照片 -->
<el-row class="inline-info">
<el-col :span="12">
<el-form-item label="员工头像">
<!-- 放置上传图片 -->
<image-upload ref="staffPhoto" />
</el-form-item>
</el-col>
</el-row>

读取时赋值头像

1
2
3
4
5
6
7
8
// 读取上半部分的内容
async getUserDetailById() {
this.userInfo = await getUserDetailById(this.userId)
if (this.userInfo.staffPhoto) {
// 这里我们赋值,同时需要给赋值的地址一个标记 upload: true
this.$refs.staffPhoto.fileList = [{ url: this.userInfo.staffPhoto, upload: true }]
}
},

员工证件照

1
2
3
4
5
<el-form-item label="员工照片">
<!-- 放置上传图片 -->
<!-- ref不要重名 -->
<image-upload ref="myStaffPhoto" />
</el-form-item>

读取时赋值照片

1
2
3
4
5
6
7
// 读取下半部分内容
async getPersonalDetail() {
this.formData = await getPersonalDetail(this.userId)
if (this.formData.staffPhoto) {
this.$refs.myStaffPhoto.fileList = [{ url: this.formData.staffPhoto, upload: true }]
}
},

保存时处理头像和证件照的保存

当点击保存更新时,获取图片的内容

1
2
3
4
5
6
7
8
9
10
11
12
async  saveUser() {
// 去读取 员工上传的头像
const fileList = this.$refs.staffPhoto.fileList // 读取上传组件的数据
if (fileList.some(item => !item.upload)) {
// 如果此时去找 upload为false的图片 找到了说明 有图片还没有上传完成
this.$message.warning('您当前还有图片没有上传完成!')
return
}
// 通过合并 得到一个新对象
await saveUserDetailById({ ...this.userInfo, staffPhoto: fileList && fileList.length ? fileList[0].url : '' })
this.$message.success('保存基本信息成功')
},

上面代码中,upload如果为true,表示该图片已经完成上传,以此来判断图片是否已经上传完成

保存时读取头像

1
2
3
4
5
6
7
8
9
10
async savePersonal() {
const fileList = this.$refs.myStaffPhoto.fileList
if (fileList.some(item => !item.upload)) {
// 如果此时去找 upload为false的图片 找到了说明 有图片还没有上传完成
this.$message.warning('您当前还有图片没有上传完成!')
return
}
await updatePersonal({ ...this.formData, staffPhoto: fileList && fileList.length ? fileList[0].url : '' })
this.$message.success('保存基础信息成功')
}

提交代码

本节任务 : 在员工详情中应用上传组件

员工列表显示图片

**目标**:在员工列表中心显示图片

员工的头像可以在列表项中添加一列来进行显示

1
2
3
4
5
6
7
8
9
10
11
<el-table-column label="头像" align="center">
<template slot-scope="{row}">
<img
slot="reference"
v-imageerror="require('@/assets/common/bigUserHeader.png')"
:src="row.staffPhoto "
style="border-radius: 50%; width: 100px; height: 100px; padding: 10px"
alt=""
>
</template>
</el-table-column>

我们尝试用之前的指令来处理图片的异常问题,但是发现只有两三张图片能显示

image-20201028172654637

这是因为有的员工的头像的地址为空,给img赋值空的src不能触发错误事件,针对这一特点,我们需要对指令进行升级

插入节点的钩子里面判断空, 然后在组件更新之后的钩子中同样判断空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export const imageerror = {
inserted(dom, options) {
// 图片异常的逻辑
// 监听img标签的错误事件 因为图片加载失败 会触发 onerror事件
dom.src = dom.src || options.value

dom.onerror = function() {
// 图片失败 赋值一个默认的图片
dom.src = options.value
}
},
componentUpdated(dom, options) {
dom.src = dom.src || options.value
}
}

这样我们可以看到每个用户的头像了,如果没有头像则显示默认图片

image-20201028173333103

**任务**:员工列表显示图片

图片地址生成二维码

目标 将图片地址生成二维码显示

我们想完成这样一个功能,当我们拥有头像地址时,将头像地址生成一个二维码,用手机扫码来访问

首先,需要安装生成二维码的插件

1
$ npm i qrcode

qrcode的用法是

1
QrCode.toCanvas(dom, info)

dom为一个canvas的dom对象, info为转化二维码的信息

我们尝试将canvas标签放到dialog的弹层中

1
2
3
4
5
<el-dialog title="二维码" :visible.sync="showCodeDialog" @opened="showQrCode" @close="imgUrl=''">
<el-row type="flex" justify="center">
<canvas ref="myCanvas" />
</el-row>
</el-dialog>

在点击员工的图片时,显示弹层,并将图片地址转化成二维码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
showQrCode(url) {
// url存在的情况下 才弹出层
if (url) {
this.showCodeDialog = true // 数据更新了 但是我的弹层会立刻出现吗 ?页面的渲染是异步的!!!!
// 有一个方法可以在上一次数据更新完毕,页面渲染完毕之后
this.$nextTick(() => {
// 此时可以确认已经有ref对象了
QrCode.toCanvas(this.$refs.myCanvas, url) // 将地址转化成二维码
// 如果转化的二维码后面信息 是一个地址的话 就会跳转到该地址 如果不是地址就会显示内容
})
} else {
this.$message.warning('该用户还未上传头像')
}
}

image-20201028180651472

打印员工信息

目标 完成个人信息和工作信息的打印功能

新建打印页面及路由

创建页面组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
<template>
<div class="dashboard-container" id="myPrint">
<div class="app-container">
<el-card>
<el-breadcrumb separator="/" class="titInfo ">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>
<router-link :to="{'path':'/employees'}">员工管理</router-link>
</el-breadcrumb-item>
<el-breadcrumb-item>打印</el-breadcrumb-item>
</el-breadcrumb>
<div v-if="type === 'personal'">
<h2 class="centInfo">员工信息表</h2>
<table cellspacing="0" width="100%" class="tableList">
<tr class="title">
<td colspan="8" class="centInfo">基本信息</td>
</tr>
<tr>
<th style="width:10%">姓名</th>
<td colspan="6" style="width:80%">{{ formData.username }}</td>
<td rowspan="5" style="width:10%"><img style="width:155px;height:218px" :src="formData.staffPhoto"></td>

</tr>
<tr>
<th>性别</th>
<td colspan="6">{{ formData.sex }}</td>
</tr>
<tr>
<th>手机</th>
<td colspan="6">{{ formData.mobile }}</td>
</tr>
<tr>
<th>出生日期</th>
<td colspan="6">{{ formData.dateOfBirth | formatDate }}</td>
</tr>
<tr>
<th>最高学历</th>
<td colspan="6">{{ formData.theHighestDegreeOfEducation }}</td>
</tr>
<tr>
<th style="width:10%">是否可编辑</th>
<td style="width:35%">{{ formData.isItEditable }}</td>
<th style="width:10%">是否隐藏号码</th>
<td colspan="5" style="width:45%">{{ formData.doYouHideNumbers }}</td>
</tr>
<tr>
<th>国家地区</th>
<td>{{ formData.nationalArea }}</td>
<th>护照号</th>
<td colspan="5">{{ formData.passportNo }}</td>
</tr>
<tr>
<th>身份证号</th>
<td>{{ formData.idNumber }}</td>
<th>身份证照片</th>
<td colspan="5">{{ formData.iDCardPhoto }}</td>
</tr>
<tr>
<th>籍贯</th>
<td>{{ formData.nativePlace }}</td>
<th>民族</th>
<td colspan="5">{{ formData.nation }}</td>
</tr>
<tr>
<th>英文名</th>
<td>{{ formData.englishName }}</td>
<th>婚姻状况</th>
<td colspan="5">{{ formData.maritalStatus }}</td>
</tr>
<tr>
<th>员工照片</th>
<td>{{ formData.staffPhoto }}</td>
<th>生日</th>
<td colspan="5">{{ formData.birthday }}</td>
</tr>
<tr>
<th>属相</th>
<td>{{ formData.zodiac }}</td>
<th>年龄</th>
<td colspan="5">{{ formData.age }}</td>
</tr>
<tr>
<th>星座</th>
<td>{{ formData.constellation }}</td>
<th>血型</th>
<td colspan="5">{{ formData.bloodType }}</td>
</tr>
<tr>
<th>户籍所在地</th>
<td>{{ formData.domicile }}</td>
<th>政治面貌</th>
<td colspan="5">{{ formData.politicalOutlook }}</td>
</tr>
<tr>
<th>入党时间</th>
<td>{{ formData.timeToJoinTheParty }}</td>
<th>存档机构</th>
<td colspan="5">{{ formData.archivingOrganization }}</td>
</tr>
<tr>
<th>子女状态</th>
<td>{{ formData.stateOfChildren }}</td>
<th>子女有无商业保险</th>
<td colspan="5">{{ formData.doChildrenHaveCommercialInsurance }}</td>
</tr>
<tr>
<th>有无违法违纪行为</th>
<td>{{ formData.isThereAnyViolationOfLawOrDiscipline }}</td>
<th>有无重大病史</th>
<td colspan="5">{{ formData.areThereAnyMajorMedicalHistories }}</td>
</tr>
<tr class="title">
<td colspan="8" class="centInfo">通讯信息</td>
</tr>
<tr>
<th>QQ</th>
<td>{{ formData.qQ }}</td>
<th>微信</th>
<td colspan="5">{{ formData.weChat }}</td>
</tr>
<tr>
<th>居住证城市</th>
<td>{{ formData.residenceCardCity }}</td>
<th>居住证办理日期</th>
<td colspan="5">{{ formData.dateOfResidencePermit }}</td>
</tr>
<tr>
<th>居住证截止日期</th>
<td>{{ formData.residencePermitDeadline }}</td>
<th>现居住地</th>
<td colspan="5">{{ formData.placeOfResidence }}</td>
</tr>
<tr>
<th>通讯地址</th>
<td>{{ formData.postalAddress }}</td>
<th>联系手机</th>
<td colspan="5">{{ formData.contactTheMobilePhone }}</td>
</tr>
<tr>
<th>个人邮箱</th>
<td>{{ formData.personalMailbox }}</td>
<th>紧急联系人</th>
<td colspan="5">{{ formData.emergencyContact }}</td>
</tr>
<tr>
<th>紧急联系电话</th>
<td colspan="7">{{ formData.emergencyContactNumber }}</td>
</tr>
<tr class="title">
<td colspan="8" class="centInfo">账号信息</td>
</tr>
<tr>
<th>社保电脑号</th>
<td>{{ formData.socialSecurityComputerNumber }}</td>
<th>公积金账号</th>
<td colspan="5">{{ formData.providentFundAccount }}</td>
</tr>
<tr>
<th>银行卡号</th>
<td>{{ formData.bankCardNumber }}</td>
<th>开户行</th>
<td colspan="5">{{ formData.openingBank }}</td>
</tr>
<tr class="title">
<td colspan="8" class="centInfo">教育信息</td>
</tr>
<tr>
<th>学历类型</th>
<td>{{ formData.educationalType }}</td>
<th>毕业学校</th>
<td colspan="5">{{ formData.graduateSchool }}</td>
</tr>
<tr>
<th>入学时间</th>
<td>{{ formData.enrolmentTime }}</td>
<th>毕业时间</th>
<td colspan="5">{{ formData.graduationTime }}</td>
</tr>
<tr>
<th>专业</th>
<td>{{ formData.major }}</td>
<th>毕业证书</th>
<td colspan="5">{{ formData.graduationCertificate }}</td>
</tr>
<tr>
<th>学位证书</th>
<td colspan="7">{{ formData.certificateOfAcademicDegree }}</td>
</tr>
<tr class="title">
<td colspan="8" class="centInfo">从业信息</td>
</tr>
<tr>
<th>上家公司</th>
<td>{{ formData.homeCompany }}</td>
<th>职称</th>
<td colspan="5">{{ formData.title }}</td>
</tr>
<tr>
<th>简历</th>
<td>{{ formData.resume }}</td>
<th>有无竞业限制</th>
<td colspan="5">{{ formData.isThereAnyCompetitionRestriction }}</td>
</tr>
<tr>
<th>前公司离职证明</th>
<td>{{ formData.proofOfDepartureOfFormerCompany }}</td>
<th>备注</th>
<td colspan="5">{{ formData.remarks }}</td>
</tr>
</table>
<div class="foot">签字:___________日期:___________</div>
</div>
<div v-else>
<h2 class="centInfo">岗位信息表</h2>
<table cellspacing="0" width="100%" class="tableList">
<tr class="title">
<td colspan="4" class="centInfo">基本信息</td>
</tr>
<tr>
<th style="width:10%">姓名</th>
<td style="width:40%">{{ formData.username }}</td>
<th style="width:10%">入职日期</th>
<td style="width:40%">{{ formData.dateOfEntry }}</td>
</tr>
<tr>
<th>部门</th>
<td>{{ formData.departmentName }}</td>
<th>岗位</th>
<td>{{ formData.post }}</td>
</tr>
<tr>
<th>工作邮箱</th>
<td>{{ formData.workMailbox }}</td>
<th>工号</th>
<td>{{ formData.workNumber }}</td>
</tr>
<tr>
<th>转正日期</th>
<td>{{ formData.dateOfCorrection }}</td>
<th>转正状态</th>
<td>{{ formData.stateOfCorrection }}</td>
</tr>
<tr>
<th>职级</th>
<td>{{ formData.rank }}</td>
<th>汇报对象</th>
<td>{{ formData.reportName }}</td>
</tr>
<tr>
<th>HRBP</th>
<td>{{ formData.hRBP }}</td>
<th>聘用形式</th>
<td>{{ formData.formOfEmployment }}</td>
</tr>

<tr>
<th>管理形式</th>
<td>{{ formData.formOfManagement }}</td>
<th>调整司龄</th>
<td>{{ formData.adjustmentAgedays }}</td>
</tr>
<tr>
<th>司龄</th>
<td>{{ formData.ageOfDivision }}</td>
<th>首次参加工作时间</th>
<td>{{ formData.workingTimeForTheFirstTime }}</td>
</tr>

<tr>
<th>调整工龄天</th>
<td>{{ formData.adjustmentOfLengthOfService }}</td>
<th>工龄</th>
<td>{{ formData.workingYears }}</td>
</tr>
<tr>
<th>纳税城市</th>
<td>{{ formData.taxableCity }}</td>
<th>转正评价</th>
<td>{{ formData.correctionEvaluation }}</td>
</tr>
<tr class="title">
<td colspan="4" class="centInfo">合同信息</td>
</tr>
<tr>
<th>首次合同开始时间</th>
<td>{{ formData.initialContractStartTime }}</td>
<th>首次合同结束时间</th>
<td>{{ formData.firstContractTerminationTime }}</td>
</tr>
<tr>
<th>现合同开始时间</th>
<td>{{ formData.currentContractStartTime }}</td>
<th>现合同结束时间</th>
<td>{{ formData.closingTimeOfCurrentContract }}</td>
</tr>

<tr>
<th>合同期限</th>
<td>{{ formData.contractPeriod }}</td>
<th>合同文件</th>
<td>{{ formData.contractDocuments }}</td>
</tr>
<tr>
<th>续签次数</th>
<td colspan="3">{{ formData.renewalNumber }}</td>
</tr>
<tr class="title">
<td colspan="4" class="centInfo">招聘信息</td>
</tr>
<tr>
<th>其他招聘渠道</th>
<td>{{ formData.otherRecruitmentChannels }}</td>
<th>招聘渠道</th>
<td>{{ formData.recruitmentChannels }}</td>
</tr>
<tr>
<th>社招校招</th>
<td>{{ formData.socialRecruitment }}</td>
<th>推荐企业人</th>
<td>{{ formData.recommenderBusinessPeople }}</td>
</tr>
</table>
<div class="foot">签字:___________日期:___________</div>
</div>
</el-card>
</div>
</div>
</template>

<script>
import { getPersonalDetail, getJobDetail } from '@/api/employees'
import { getUserDetailById } from '@/api/user'
export default {
data() {
return {
formData: {},
userId: this.$route.params.id,
type: this.$route.query.type // 打印类型
}
},
// 创建完毕状态
created() {
this.type === 'personal' ? this.getPersonalDetail() : this.getJobDetail()
},
// 组件更新
methods: {
async getPersonalDetail() {
this.formData = await getPersonalDetail(this.userId) // 获取个人基本信息
},
async getJobDetail() {
const userInfo = await getUserDetailById(this.userId)
const jobInfo = await getJobDetail(this.userId) // 获取个人基本信息
this.formData = { ...userInfo, ...jobInfo }
}
}
}
</script>

<style lang="scss">
.foot {
padding: 30px 0;
text-align: right;
}
</style>


该页面内容实际上就是读取个人和详情的接口数据,根据传入的type类型决定显示个人还是岗位

type为**personal时显示个人,为job**时显示岗位

新建打印页面路由

1
2
3
4
5
6
7
8
9
{
path: 'print/:id', // 二级默认路由
component: () => import('@/views/employees/print'), // 按需加载
hidden: true,
meta: {
title: '打印', // 标记当前路由规则的中文名称 后续在做左侧菜单时 使用
icon: 'people'
}
}

完成详情到打印的跳转

个人

1
2
3
4
5
6
7
<el-row type="flex" justify="end">
<el-tooltip content="打印个人基本信息">
<router-link :to="`/employees/print/${userId}?type=personal`">
<i class="el-icon-printer" />
</router-link>
</el-tooltip>
</el-row>

岗位

1
2
3
4
5
6
7
<el-row type="flex" justify="end">
<el-tooltip content="打印岗位信息">
<router-link :to="`/employees/print/${userId}?type=job`">
<i class="el-icon-printer" />
</router-link>
</el-tooltip>
</el-row>

利用vue-print-nb进行打印

首先,打印功能我们借助一个比较流行的插件

1
$ npm i vue-print-nb

它的用法是

首先注册该插件

1
2
import Print from 'vue-print-nb'
Vue.use(Print);

使用v-print指令的方式进行打印

1
2
3
4
5
6
<el-row type="flex" justify="end">
<el-button v-print="printObj" size="small" type="primary">打印</el-button>
</el-row>
printObj: {
id: 'myPrint'
}

最终,我们看到的效果

image-20200728013006535

提交代码

**本节任务**打印员工信息