Compare commits

...

10 Commits

Author SHA1 Message Date
9a20760490 excel导出 2025-03-29 10:40:13 +08:00
jiutianzhiyu
00099ed8c2 员工excel导入 2021-03-31 12:46:30 +08:00
jiutianzhiyu
4c5b831c22 新增员工 2021-03-30 12:14:23 +08:00
jiutianzhiyu
3a566de9d3 删除员工 2021-03-30 09:51:24 +08:00
jiutianzhiyu
180027be76 自定义列不需要prop 2021-03-30 09:41:33 +08:00
jiutianzhiyu
7421fd70bb 过滤器格式化时间 2021-03-30 06:18:49 +08:00
jiutianzhiyu
40facf16f0 导入过滤器 2021-03-30 06:02:54 +08:00
jiutianzhiyu
568f95dd5b formatter格式化内容 2021-03-30 05:46:58 +08:00
jiutianzhiyu
4919782303 枚举资源 2021-03-30 05:38:22 +08:00
jiutianzhiyu
ff74112724 员工列表渲染和分页功能 2021-03-30 05:21:24 +08:00
18 changed files with 2217 additions and 8 deletions

View File

@ -17,13 +17,15 @@
"axios": "0.18.1", "axios": "0.18.1",
"core-js": "3.6.5", "core-js": "3.6.5",
"element-ui": "2.13.2", "element-ui": "2.13.2",
"file-saver": "^2.0.5",
"js-cookie": "2.2.0", "js-cookie": "2.2.0",
"normalize.css": "7.0.0", "normalize.css": "7.0.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"path-to-regexp": "2.4.0", "path-to-regexp": "2.4.0",
"vue": "2.6.10", "vue": "2.6.10",
"vue-router": "3.0.6", "vue-router": "3.0.6",
"vuex": "3.1.0" "vuex": "3.1.0",
"xlsx": "^0.16.9"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "4.4.4", "@vue/cli-plugin-babel": "4.4.4",
@ -45,6 +47,7 @@
"sass": "1.26.8", "sass": "1.26.8",
"sass-loader": "8.0.2", "sass-loader": "8.0.2",
"script-ext-html-webpack-plugin": "2.1.3", "script-ext-html-webpack-plugin": "2.1.3",
"script-loader": "^0.7.2",
"serve-static": "1.13.2", "serve-static": "1.13.2",
"svg-sprite-loader": "4.1.3", "svg-sprite-loader": "4.1.3",
"svgo": "1.2.2", "svgo": "1.2.2",

View File

@ -0,0 +1,94 @@
// 审批
export default {
// 审批类型
approvalType: [
{
id: '1',
value: '转正'
},
{
id: '2',
value: '调岗'
},
{
id: '3',
value: '离职'
},
{
id: '4',
value: '员工信息审核'
},
{
id: '5',
value: '调薪'
},
{
id: '6',
value: '工资审核'
},
{
id: '7',
value: '请假'
},
{
id: '8',
value: '销假'
},
{
id: '9',
value: '外出'
},
{
id: '10',
value: '销外出'
},
{
id: '11',
value: '出差'
},
{
id: '12',
value: '销出差'
},
{
id: '13',
value: '外勤打卡'
},
{
id: '14',
value: '补打卡'
},
{
id: '15',
value: '加班'
},
{
id: '16',
value: '招聘'
},
{
id: '17',
value: '录用'
}
],
// 审批状态
approvalState: [
{
id: '1',
value: '审批中'
},
{
id: '2',
value: '审批驳回'
},
{
id: '3',
value: '已撤销'
},
{
id: '4',
value: '审批通过'
}
]
}

View File

@ -0,0 +1,254 @@
// 员工端
export default {
// 假期类型
holidayType: [{
id: '1',
value: '正常',
isEnable: false
},
{
id: '2',
value: '旷工',
isEnable: false
},
{
id: '3',
value: '事假',
isEnable: false
},
{
id: '4',
value: '调休',
isEnable: false
},
{
id: '5',
value: '迟到',
isEnable: false
},
{
id: '6',
value: '早退',
isEnable: false
}
],
vacationtype: [{
id: '1',
name: '正常'
}, {
id: '2',
name: '旷工'
}, {
id: '3',
name: '迟到'
}, {
id: '4',
name: '早退'
}, {
id: '5',
name: '外出'
}, {
id: '6',
name: '出差'
}, {
id: '7',
name: '年假'
}, {
id: '8',
name: '事假'
}, {
id: '9',
name: '病假'
}, {
id: '10',
name: '婚假'
}, {
id: '11',
name: '丧假'
}, {
id: '12',
name: '产假'
}, {
id: '13',
name: '奖励产假'
}, {
id: '14',
name: '陪产假'
}, {
id: '15',
name: '探亲假'
}, {
id: '16',
name: '工伤假'
}, {
id: '17',
name: '调休'
}, {
id: '18',
name: '产检假'
}, {
id: '19',
name: '流产假'
}, {
id: '20',
name: '长期病假'
}, {
id: '21',
name: '测试假'
}, {
id: '22',
name: '补签'
}
],
type: [{
leaveType: '60000',
name: '年假',
isEnable: false
},
{
leaveType: '60100',
name: '事假',
isEnable: false
},
{
leaveType: '60200',
name: '病假',
isEnable: false
},
{
leaveType: '60300',
name: '婚假',
isEnable: false
},
{
leaveType: '60400',
name: '丧假',
isEnable: false
},
{
leaveType: '60500',
name: '产假',
isEnable: false
},
{
leaveType: '60600',
name: '奖励产假',
isEnable: false
},
{
leaveType: '60700',
name: '陪产假',
isEnable: false
},
{
leaveType: '60800',
name: '探亲假',
isEnable: false
},
{
leaveType: '60900',
name: '工伤假',
isEnable: false
},
{
leaveType: '61000',
name: '调休假',
isEnable: false
},
{
leaveType: '61100',
name: '产检假',
isEnable: false
},
{
leaveType: '61200',
name: '流产假',
isEnable: false
},
{
leaveType: '61300',
name: '长期病假',
isEnable: false
},
{
leaveType: '61400',
name: '测试假',
isEnable: false
}
],
departmentType: [{
dedTypeCode: '51000',
name: '迟到扣款',
isEnable: false,
departmentId: '',
periodLowerLimit: '30', // 时间段下限
periodUpperLimit: '30', // 时间段上限
timesLowerLimit: '2', // 次数下限
timesUpperLimit: '2', // 次数上限
dedAmonutLowerLimit: '30', // 扣款金额下限
dedAmonutUpperLimit: '0', // 扣款金额上限
absenceDays: '0.5', // 旷工天数
fineSalaryMultiples: '2', // 罚款工资倍数
absenceTimesUpperLimt: '0' // 旷工次数上限
},
{
dedTypeCode: '52000',
name: '早退扣款',
isEnable: false,
departmentId: '',
periodLowerLimit: '30', // 时间段下限
periodUpperLimit: '30', // 时间段上限
timesLowerLimit: '2', // 次数下限
timesUpperLimit: '2', // 次数上限
dedAmonutLowerLimit: '30', // 扣款金额下限
dedAmonutUpperLimit: '0', // 扣款金额上限
absenceDays: '0.5', // 旷工天数
fineSalaryMultiples: '2', // 罚款工资倍数
absenceTimesUpperLimt: '0' // 旷工次数上限
},
{
dedTypeCode: '53000',
name: '旷工扣款',
isEnable: false,
departmentId: '',
periodLowerLimit: '30', // 时间段下限
periodUpperLimit: '30', // 时间段上限
timesLowerLimit: '2', // 次数下限
timesUpperLimit: '2', // 次数上限
dedAmonutLowerLimit: '30', // 扣款金额下限
dedAmonutUpperLimit: '0', // 扣款金额上限
absenceDays: '0.5', // 旷工天数
fineSalaryMultiples: '2', // 罚款工资倍数
absenceTimesUpperLimt: '0' // 旷工次数上限
}
],
overtimeType: [{
// id: '1',
departmentId: '', // 部门ID
rule: '工作日可申请加班', // 规则内容
ruleStartTime: '', // 规则生效每日开始时间
ruleEndTime: '', // 规则生效每日结束时间
isTimeOff: false, // 是否调休
isEnable: false // 是否可用
},
{
// id: '2',
departmentId: '', // 部门ID
rule: '休息日可申请加班', // 规则内容
ruleStartTime: '', // 规则生效每日开始时间
ruleEndTime: '', // 规则生效每日结束时间
isTimeOff: false, // 是否调休
isEnable: false // 是否可用
},
{
// id: '3',
departmentId: '', // 部门ID
rule: '法定节假日可申请加班', // 规则内容
ruleStartTime: '', // 规则生效每日开始时间
ruleEndTime: '', // 规则生效每日结束时间
isTimeOff: false, // 是否调休
isEnable: false // 是否可用
}
]
}

View File

@ -0,0 +1,26 @@
// 通用
export default {
// 启用状态
enableState: [
{
id: '1',
value: '启用'
},
{
id: '0',
value: '禁用'
}
],
// 有无
hasState: [
{
id: '1',
value: '有'
},
{
id: '0',
value: '无'
}
]
}

View File

@ -0,0 +1,420 @@
// 员工
export default {
// 聘用形式
hireType: [
{
id: 1,
value: '正式'
},
{
id: 2,
value: '非正式'
}
],
// 管理形式
subjection: [
{
id: '1',
value: '总部'
},
{
id: '2',
value: '分城市'
}
],
// 在职状态
workingState: [
{
id: '1',
value: '在职'
},
{
id: '2',
value: '离职'
}
],
// 离职类型
leaveType: [
{
id: '1',
value: '主动离职'
},
{
id: '2',
value: '被动离职'
},
{
id: '3',
value: '退休'
}
],
// 减员月
attritionMonth: [
{
id: '1',
value: '离职日本月'
},
{
id: '2',
value: '离职日次月'
}
],
// 聘用形式
informaltype: [
{
id: '2',
value: '实习'
},
{
id: '3',
value: '劳务'
},
{
id: '4',
value: '顾问'
},
{
id: '5',
value: '返聘'
},
{
id: '6',
value: '外包'
}
],
// 最高学历
highestDegree: [
{
id: '1',
value: '初中'
},
{
id: '2',
value: '高中'
},
{
id: '3',
value: '中专'
},
{
id: '4',
value: '大专'
},
{
id: '5',
value: '本科'
},
{
id: '6',
value: '硕士'
},
{
id: '7',
value: '博士'
},
{
id: '8',
value: '其他'
}
],
// 国家/地区
isOverseas: [
{
id: '1',
value: '中国大陆'
},
{
id: '2',
value: '港澳台国外'
}
],
// 性别
gender: [
{
id: '1',
value: '男'
},
{
id: '2',
value: '女'
}
],
// 婚姻状况
maritaStatus: [
{
id: '1',
value: '未婚'
},
{
id: '2',
value: '已婚'
},
{
id: '3',
value: '离异'
}
],
// 生肖
animalSymbol: [
{
id: '1',
value: '鼠'
},
{
id: '2',
value: '牛'
},
{
id: '3',
value: '虎'
},
{
id: '4',
value: '兔'
},
{
id: '5',
value: '龙'
},
{
id: '6',
value: '蛇'
},
{
id: '7',
value: '马'
},
{
id: '8',
value: '羊'
},
{
id: '9',
value: '猴'
},
{
id: '10',
value: '鸡'
},
{
id: '11',
value: '狗'
},
{
id: '12',
value: '猪'
}
],
// 星座
constellation: [
{
code: 1,
value: '水瓶座'
},
{
code: 2,
value: '双鱼座'
},
{
code: 3,
value: '白羊座'
},
{
code: 4,
value: '金牛座'
},
{
code: 5,
value: '双子座'
},
{
code: 6,
value: '巨蟹座'
},
{
code: 7,
value: '狮子座'
},
{
code: 8,
value: '处女座'
},
{
code: 9,
value: '天秤座'
},
{
code: 10,
value: '天蝎座'
},
{
code: 11,
value: '射手座'
},
{
code: 12,
value: '摩羯座'
}
],
// 血型
bloodType: [
{
id: '1',
value: 'A型'
},
{
id: '2',
value: 'B型'
},
{
id: '3',
value: 'O型'
},
{
id: '4',
value: 'AB型'
}
],
// 学历
educationType: [
{
id: '1',
value: '统招'
},
{
id: '2',
value: '自考'
},
{
id: '3',
value: '成考'
}
],
// 转正
positiveType: [
{
id: '1',
value: '已转正'
},
{
id: '2',
value: '未转正'
}
],
// 合同期限
contractPeriod: [
{
id: '1',
value: '6月'
},
{
id: '2',
value: '12月'
},
{
id: '3',
value: '24月'
},
{
id: '4',
value: '36月'
},
{
id: '5',
value: '其他'
}
],
// 签约次数
renewalCount: [
{
id: 1,
value: '0次'
},
{
id: 2,
value: '1次'
},
{
id: 3,
value: '2次'
},
{
id: 4,
value: '3次'
},
{
id: 5,
value: '4次或以上'
}
],
// 简历来源
resumeSource: [
{
id: '1',
value: '智联招聘'
},
{
id: '2',
value: '拉勾网'
},
{
id: '3',
value: '前程无忧'
},
{
id: '4',
value: '猎聘网'
},
{
id: '5',
value: '校园宣讲'
},
{
id: '6',
value: '猎头'
},
{
id: '7',
value: '内部推荐'
}
],
// 社招/校招
hireSourceType: [
{
id: '1',
value: '社招'
},
{
id: '2',
value: '校招'
}
],
// 新加
// 部门
departments: [
{
id: '1',
value: '总裁办'
},
{
id: '2',
value: '研究院'
}
],
// 职位状态
stausInfos: [
{
id: '1',
value: '在职'
},
{
id: '2',
value: '入职'
},
{
id: '3',
value: '离职'
}
]
}

View File

@ -0,0 +1,206 @@
// 公司设置
export default {
// 所属行业
industryKind: [{
id: '1',
value: '互联网'
},
{
id: '2',
value: '游戏'
},
{
id: '3',
value: '软件'
},
{
id: '4',
value: '电子'
},
{
id: '5',
value: '通信'
},
{
id: '6',
value: '硬件'
},
{
id: '7',
value: '房地产'
},
{
id: '8',
value: '建筑'
},
{
id: '9',
value: '物业'
},
{
id: '10',
value: '金融'
},
{
id: '11',
value: '消费品'
},
{
id: '12',
value: '汽车'
},
{
id: '13',
value: '机械'
},
{
id: '14',
value: '制造'
},
{
id: '15',
value: '服务'
},
{
id: '16',
value: '外包'
},
{
id: '17',
value: '中介'
},
{
id: '18',
value: '广告'
},
{
id: '19',
value: '传媒'
},
{
id: '20',
value: '教育'
},
{
id: '21',
value: '文化'
},
{
id: '22',
value: '交通'
},
{
id: '23',
value: '贸易'
},
{
id: '24',
value: '物流'
},
{
id: '25',
value: '制药'
},
{
id: '26',
value: '医疗'
},
{
id: '27',
value: '能源'
},
{
id: '28',
value: '化工'
},
{
id: '29',
value: '环保'
},
{
id: '30',
value: '政府'
},
{
id: '31',
value: '农林牧渔'
},
{
id: '32',
value: '其他'
}
],
// 系统模块表
systemModules: [{
id: 'organizations',
value: '组织架构'
},
{
id: 'accounts',
value: '账户'
},
{
id: 'settings',
value: '公司设置'
},
{
id: 'employees',
value: '员工'
},
{
id: 'salarys',
value: '工资'
},
{
id: 'social_securitys',
value: '社保'
},
{
id: 'attendances',
value: '考勤'
},
{
id: 'recruits',
value: '招聘'
},
{
id: 'approvals',
value: '审批'
},
{
id: 'notices',
value: '公告'
}
],
// 公司规模
companySize: [{
id: '1',
value: '10人以下'
},
{
id: '2',
value: '10-20人'
},
{
id: '3',
value: '20-50人'
},
{
id: '4',
value: '50-100人'
},
{
id: '5',
value: '100-200人'
},
{
id: '6',
value: '200-500人'
},
{
id: '7',
value: '500人以上'
}
]
}

37
src/api/constant/user.js Normal file
View File

@ -0,0 +1,37 @@
// 员工端
export default {
// 假期类型
holidayType: [
{
id: '1',
value: '事假'
},
{
id: '0',
value: '调休'
}
],
// 假期类型
leaveType: [
{
id: '1',
value: '请假'
},
{
id: '0',
value: '调休'
}
],
// 假期类型
applyType: [
{
id: 3,
value: '离职'
},
{
id: 15,
value: '加班'
}
]
}

View File

@ -5,3 +5,46 @@ export function getEmployeeSimple() {
url: '/sys/user/simple' url: '/sys/user/simple'
}) })
} }
/**
* 获取员工的综合列表数据
* ***/
export function getEmployeeList(params) {
return request({
url: '/sys/user',
params
})
}
/**
* 删除员工接口
* ****/
export function delEmployee(id) {
return request({
url: `/sys/user/${id}`,
method: 'delete'
})
}
/** **
* 新增员工的接口
* **/
export function addEmployee(data) {
return request({
method: 'post',
url: '/sys/user',
data
})
}
/** *
* 批量导入员工的接口
*
* ***/
export function importEmployee(data) {
return request({
url: '/sys/user/batch',
method: 'post',
data
})
}

View File

@ -0,0 +1,147 @@
<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>
<script>
import XLSX from 'xlsx'
export default {
props: {
beforeUpload: Function, // eslint-disable-line
onSuccess: Function// eslint-disable-line
},
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.onSuccess && this.onSuccess(this.excelData)
},
handleDrop(e) {
e.stopPropagation()
e.preventDefault()
if (this.loading) return
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$message.error('Only support uploading one file!')
return
}
const rawFile = files[0] // only use files[0]
if (!this.isExcel(rawFile)) {
this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
return false
}
this.upload(rawFile)
e.stopPropagation()
e.preventDefault()
},
handleDragover(e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleUpload() {
this.$refs['excel-upload-input'].click()
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0] // only use files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
if (!this.beforeUpload) {
this.readerData(rawFile)
return
}
const before = this.beforeUpload(rawFile)
if (before) {
this.readerData(rawFile)
}
},
readerData(rawFile) {
this.loading = true
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateData({ header, results })
this.loading = false
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
},
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
},
isExcel(file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
}
}
</script>
<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>

View File

@ -1,10 +1,12 @@
// 该文件负责所有的公共的组件的全局注册 Vue.use // 该文件负责所有的公共的组件的全局注册 Vue.use
import PageTools from './PageTools' import PageTools from './PageTools'
import UploadExcel from './UploadExcel'
export default { export default {
// 为vue准备的第三方包, 必须有install方法 // 为vue准备的第三方包, 必须有install方法
// 这里方法可以自动接收一个形参, 就是Vue包 // 这里方法可以自动接收一个形参, 就是Vue包
install(Vue) { install(Vue) {
// 注册全局的通用栏组件对象 // 注册全局的通用栏组件对象
Vue.component('PageTools', PageTools) Vue.component('PageTools', PageTools)
Vue.component('UploadExcel', UploadExcel)
} }
} }

399
src/filters/index.js Normal file
View File

@ -0,0 +1,399 @@
// import parseTime, formatTime and set to filter
/**
* Show plural label if time is plural number
* @param {number} time
* @param {string} label
* @return {string}
*/
function pluralize(time, label) {
if (time === 1) {
return time + label
}
return time + label + 's'
}
/**
* @param {number} time
*/
export function timeAgo(time) {
const between = Date.now() / 1000 - Number(time)
if (between < 3600) {
return pluralize(~~(between / 60), ' minute')
} else if (between < 86400) {
return pluralize(~~(between / 3600), ' hour')
} else {
return pluralize(~~(between / 86400), ' day')
}
}
/**
* Number formatting
* like 10000 => 10k
* @param {number} num
* @param {number} digits
*/
export function numberFormatter(num, digits) {
const si = [
{ value: 1E18, symbol: 'E' },
{ value: 1E15, symbol: 'P' },
{ value: 1E12, symbol: 'T' },
{ value: 1E9, symbol: 'G' },
{ value: 1E6, symbol: 'M' },
{ value: 1E3, symbol: 'k' }
]
for (let i = 0; i < si.length; i++) {
if (num >= si[i].value) {
return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
}
}
return num.toString()
}
/**
* 10000 => "10,000"
* @param {number} num
*/
export function toThousandFilter(num) {
return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
}
/**
* Upper case first char
* @param {String} string
*/
export function uppercaseFirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
export function parseTime(time, cFormat) {
if (arguments.length === 0) {
return null
}
if ((time + '').length === 10) {
time = +time * 1000
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
date = new Date(parseInt(time))
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
if (key === 'a') {
return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
}
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return timeStr
}
export function formatTime(time, option) {
time = +time * 1000
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
)
}
}
export function getNowFormatDate() {
var date = new Date()
var seperator1 = '-'
var year = date.getFullYear()
var month = date.getMonth() + 1
var strDate = date.getDate()
if (month >= 1 && month <= 9) {
month = '0' + month
}
if (strDate >= 0 && strDate <= 9) {
strDate = '0' + strDate
}
var currentdate = year + seperator1 + month + seperator1 + strDate
return currentdate
}
/* 数字 格式化 */
export function nFormatter(num, digits) {
const si = [{
value: 1e18,
symbol: 'E'
},
{
value: 1e15,
symbol: 'P'
},
{
value: 1e12,
symbol: 'T'
},
{
value: 1e9,
symbol: 'G'
},
{
value: 1e6,
symbol: 'M'
},
{
value: 1e3,
symbol: 'k'
}
]
for (let i = 0; i < si.length; i++) {
if (num >= si[i].value) {
return (
(num / si[i].value + 0.1)
.toFixed(digits)
.replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
)
}
}
return num.toString()
}
export function html2Text(val) {
const div = document.createElement('div')
div.innerHTML = val
return div.textContent || div.innerText
}
export function toThousandslsFilter(num) {
return (+num || 0)
.toString()
.replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
}
// 验证手机号
export function checkPhone(rule, value, callback) {
if (!value) {
return callback(new Error('手机号不能为空'))
} else {
const reg = /^1[3|4|5|7|8][0-9]\d{8}$/
if (reg.test(value)) {
callback()
} else {
return callback(new Error('请输入正确的手机号'))
}
}
}
export function checkPassword(rule, value, callback) {
if (!value) {
return callback(new Error('密码不能为空'))
} else if (value.length < 6) {
callback(new Error('请至少输入 6 个字符。请不要使用容易被猜到的密码'))
} else {
callback()
}
}
// 手机号证验证
export function checkTel(value, callback) {
var reg = /^1[3|4|5|7|8][0-9]\d{8}$/
return reg.test(value)
}
// 身份证验证
export function checkiDNumber(value, callback) {
var reg = /\d{17}[\d|x]|\d{15}/
return reg.test(value)
}
// 身份证验证
export function checkEmails(value, callback) {
var reg = /^[A-Za-zd]+([-_.][A-Za-zd]+)*@([A-Za-zd]+[-.])+[A-Za-zd]{2,5}$/
return reg.test(value)
}
// 邮箱验证
export function checkEmail(rule, value, callback) {
if (!value) {
return callback(new Error('邮箱不能为空'))
} else {
var reg = /^[A-Za-zd]+([-_.][A-Za-zd]+)*@([A-Za-zd]+[-.])+[A-Za-zd]{2,5}$/
if (reg.test(value)) {
callback()
} else {
return callback(new Error('请输入正确的邮箱'))
}
}
}
// 英文验证
export function checkCode(value, callback) {
var reg = /^[A-Za-z]+$/g
return reg.test(value)
}
// qq验证
export function checkQq(value, callback) {
var reg = /^[0-9]+$/g
return reg.test(value)
}
// 银行卡号
export function formatBankNo(BankNo, callback) {
var strBin = '10,18,30,35,37,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,58,60,62,65,68,69,84,87,88,94,95,98,99'
return strBin
}
export function getStrleng(str, max) {
var myLen = 0
for (var i = 0; i < str.length && myLen <= max * 2; i++) {
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
myLen++
} else myLen += 2
}
return myLen
}
// 上传图片格式控制
export function updatedImg(file, obj, callback, func) {
if (file.size < 10100000) {
var fileName = file.name
var suffix = fileName
.substring(fileName.lastIndexOf('.') + 1)
.toUpperCase()
if (
suffix === 'PDF' ||
suffix === 'JPG' ||
suffix === 'JPEG' ||
suffix === 'PNG' ||
suffix === 'GIF'
) {
return true
} else {
var tipType = '文件类型不正确,请重新上传'
callback(tipType)
return false
}
} else {
var tipSize = '文件大小超过5M,请重新上传'
callback(tipSize)
return false
}
}
// 上传文档格式控制
export function updatedFile(file, obj, callback, func) {
if (file.size < 10100000) {
var fileName = file.name
var suffix = fileName
.substring(fileName.lastIndexOf('.') + 1)
.toUpperCase()
if (
suffix === 'DOC' ||
suffix === 'DOCX' ||
suffix === 'XLS' ||
suffix === 'XLSX' ||
suffix === 'PDF' ||
suffix === 'ZIP' ||
suffix === 'RAR'
) {
return true
} else {
var tipType = '文件类型不正确,请重新上传'
callback(tipType)
return false
}
} else {
var tipSize = '文件大小超过5M,请重新上传'
callback(tipSize)
return false
}
}
export function importFile(file, obj, callback, func) {
if (file.size < 10100000) {
var fileName = file.name
var suffix = fileName
.substring(fileName.lastIndexOf('.') + 1)
.toUpperCase()
if (
suffix === 'XLS' ||
suffix === 'XLSX'
) {
return true
} else {
var tipType = '文件类型不正确,请重新上传'
callback(tipType)
return false
}
} else {
var tipSize = '文件大小超过10M,请重新上传'
callback(tipSize)
return false
}
}
export function minHeight(resfile) {
return document.body.clientHeight - 180 + 'px'
}
export function formatDate(date, fmt = 'yyyy-MM-dd') {
if (!(date instanceof Array)) {
date = new Date(date)
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
}
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
}
for (const k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
const str = o[k] + ''
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str))
}
}
return fmt
}
function padLeftZero(str) {
return ('00' + str).substr(str.length)
}
export function getBlob(response) {
const blob = new Blob([response.data], {
type: 'application/vnd.ms-excel'
})
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
var filename = decodeURI(response.headers.filename)
// link.download = filename + '.xls'
link.download = filename
link.click()
}
// 图片 blob 流转化为可用 src
export function imgHandle(obj) {
return window.URL.createObjectURL(obj)
}

View File

@ -16,9 +16,17 @@ import router from './router'
import '@/icons' // icon import '@/icons' // icon
import '@/permission' // permission control import '@/permission' // permission control
// 第三方组件全局注册
import Component from '@/components' import Component from '@/components'
Vue.use(Component) Vue.use(Component)
// 批量注册
import * as myfilter from '@/filters'
// 遍历注册
for (const key in myfilter) {
Vue.filter(key, myfilter[key])
}
/** /**
* If you don't want to use mock-server * If you don't want to use mock-server
* you want to use MockJs for mock api * you want to use MockJs for mock api

View File

@ -40,6 +40,16 @@ export const constantRoutes = [
hidden: true hidden: true
}, },
{
path: '/import',
component: Layout,
hidden: true, // 隐藏在左侧菜单中
children: [{
path: '', // 二级路由path什么都不写 表示二级默认路由
component: () => import('@/views/import')
}]
},
// 404 page must be placed at the end !!! // 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true } { path: '*', redirect: '/404', hidden: true }
] ]
@ -57,9 +67,9 @@ export const asyncRoutes = [
] ]
const createRouter = () => new Router({ const createRouter = () => new Router({
// mode: 'history', // require service support // mode: 'history', // 需要服务器支持, 默认使用hash模式
scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部 scrollBehavior: () => ({ y: 0 }), // 切换路由时, 让页面回到顶部
routes: [...constantRoutes, ...asyncRoutes] routes: [...constantRoutes, ...asyncRoutes] // 合并静态路由和动态路由
}) })
const router = createRouter() const router = createRouter()

220
src/vendor/Export2Excel.js vendored Normal file
View File

@ -0,0 +1,220 @@
/* eslint-disable */
import { saveAs } from 'file-saver'
import XLSX from 'xlsx'
function generateArray(table) {
var out = [];
var rows = table.querySelectorAll('tr');
var ranges = [];
for (var R = 0; R < rows.length; ++R) {
var outRow = [];
var row = rows[R];
var columns = row.querySelectorAll('td');
for (var C = 0; C < columns.length; ++C) {
var cell = columns[C];
var colspan = cell.getAttribute('colspan');
var rowspan = cell.getAttribute('rowspan');
var cellValue = cell.innerText;
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
//Skip ranges
ranges.forEach(function (range) {
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
}
});
//Handle Row Span
if (rowspan || colspan) {
rowspan = rowspan || 1;
colspan = colspan || 1;
ranges.push({
s: {
r: R,
c: outRow.length
},
e: {
r: R + rowspan - 1,
c: outRow.length + colspan - 1
}
});
};
//Handle Value
outRow.push(cellValue !== "" ? cellValue : null);
//Handle Colspan
if (colspan)
for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
}
out.push(outRow);
}
return [out, ranges];
};
function datenum(v, date1904) {
if (date1904) v += 1462;
var epoch = Date.parse(v);
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {};
var range = {
s: {
c: 10000000,
r: 10000000
},
e: {
c: 0,
r: 0
}
};
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R;
if (range.s.c > C) range.s.c = C;
if (range.e.r < R) range.e.r = R;
if (range.e.c < C) range.e.c = C;
var cell = {
v: data[R][C]
};
if (cell.v == null) continue;
var cell_ref = XLSX.utils.encode_cell({
c: C,
r: R
});
if (typeof cell.v === 'number') cell.t = 'n';
else if (typeof cell.v === 'boolean') cell.t = 'b';
else if (cell.v instanceof Date) {
cell.t = 'n';
cell.z = XLSX.SSF._table[14];
cell.v = datenum(cell.v);
} else cell.t = 's';
ws[cell_ref] = cell;
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
return ws;
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook();
this.SheetNames = [];
this.Sheets = {};
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
export function export_table_to_excel(id) {
var theTable = document.getElementById(id);
var oo = generateArray(theTable);
var ranges = oo[1];
/* original data */
var data = oo[0];
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
/* add ranges to worksheet */
// ws['!cols'] = ['apple', 'banan'];
ws['!merges'] = ranges;
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
});
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), "test.xlsx")
}
export function export_json_to_excel({
multiHeader = [],
header,
data,
filename,
merges = [],
autoWidth = true,
bookType = 'xlsx'
} = {}) {
/* original data */
filename = filename || 'excel-list'
data = [...data]
data.unshift(header);
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader[i])
}
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
if (merges.length > 0) {
if (!ws['!merges']) ws['!merges'] = [];
merges.forEach(item => {
ws['!merges'].push(XLSX.utils.decode_range(item))
})
}
if (autoWidth) {
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row => row.map(val => {
/*先判断是否为null/undefined*/
if (val == null) {
return {
'wch': 10
};
}
/*再判断是否为中文*/
else if (val.toString().charCodeAt(0) > 255) {
return {
'wch': val.toString().length * 2
};
} else {
return {
'wch': val.toString().length
};
}
}))
/*以第一行为初始值*/
let result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
}
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
});
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), `${filename}.${bookType}`);
}

24
src/vendor/Export2Zip.js vendored Normal file
View File

@ -0,0 +1,24 @@
/* eslint-disable */
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
export function export_txt_to_zip(th, jsonData, txtName, zipName) {
const zip = new JSZip()
const txt_name = txtName || 'file'
const zip_name = zipName || 'file'
const data = jsonData
let txtData = `${th}\r\n`
data.forEach((row) => {
let tempStr = ''
tempStr = row.toString()
txtData += `${tempStr}\r\n`
})
zip.file(`${txt_name}.txt`, txtData)
zip.generateAsync({
type: "blob"
}).then((blob) => {
saveAs(blob, `${zip_name}.zip`)
}, (err) => {
alert('导出失败')
})
}

View File

@ -0,0 +1,136 @@
<template>
<el-dialog title="新增员工" :visible="showDialog" @close="btnCancel">
<!-- 表单 -->
<el-form ref="addEmployee" label-width="120px" :model="formData" :rules="rules">
<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-option v-for="item in EmployeeEnum.hireType" :key="item.id" :label="item.value" :value="item.id" />
</el-select>
</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="请选择部门" @focus="getDepartments" />
<!-- 树形控件 -->
<el-tree v-if="treeData.length > 0 " :data="treeData" :props="{ label: 'name' }" :default-expand-all="true" @node-click="selectNode" />
</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>
<!-- footer插槽 -->
<template v-slot:footer>
<el-row type="flex" justify="center">
<el-col :span="6">
<el-button size="small" @click="btnCancel">取消</el-button>
<el-button type="primary" size="small" @click="btnOK">确定</el-button>
</el-col>
</el-row>
</template>
</el-dialog>
</template>
<script>
import { addEmployee } from '@/api/employees'
import { getDepartments } from '@/api/departments'
import { listToTreeData } from '@/utils/list-to-treedata'
import EmployeeEnum from '@/api/constant/employees'
export default {
props: {
showDialog: {
type: Boolean,
default: false
}
},
data() {
return {
EmployeeEnum, //
loading: false,
formData: {
username: '',
mobile: '',
formOfEmployment: '',
workNumber: '',
departmentName: '',
timeOfEntry: '',
correctionTime: ''
},
treeData: [], //
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' }]
}
}
},
methods: {
async getDepartments() {
this.showTree = true
this.loading = true
const { depts } = await getDepartments()
this.treeData = listToTreeData(depts, '')
this.loading = false
},
//
selectNode(node) {
this.formData.departmentName = node.name
this.treeData = {}
},
//
async btnOK() {
try {
await this.$refs.addEmployee.validate()
//
await addEmployee(this.formData) //
//
// this.$parent this
// this.$emit
this.$parent.getEmployeeList()
this.$parent.showDialog = false
this.$message.success('添加员工成功')
} catch (error) {
console.log(error)
}
},
btnCancel() {
//
this.formData = {
username: '',
mobile: '',
formOfEmployment: '',
workNumber: '',
departmentName: '',
timeOfEntry: '',
correctionTime: ''
}
this.$refs.addEmployee.resetFields() //
this.$emit('update:showDialog', false)
}
}
}
</script>
<style>
</style>

View File

@ -1,16 +1,124 @@
<template> <template>
<div class="dashboard-container"> <div class="dashboard-container">
<div class="app-container"> <div class="app-container">
<h2> <page-tools :show-before="true">
员工 <span slot="before">共166条记录</span>
</h2> <template slot="after">
<el-button size="small" type="warning" @click="$router.push('/import?type=employees')">导入</el-button>
<el-button size="small" type="danger" @click="exportExcel">导出</el-button>
<el-button size="small" type="primary" @click="showDialog = true">新增员工</el-button>
</template>
</page-tools>
<!-- 放置表格和分页 -->
<el-card v-loading="loading">
<el-table border :data="list" :default-sort="{prop:'workNumber',order:'ascending'}">
<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="mobile" />
<el-table-column label="聘用形式" sortable="" prop="formOfEmployment" :formatter="formatEmployment" />
<el-table-column label="部门" sortable="" prop="departmentName" />
<el-table-column label="入职时间" sortable="">
<template slot-scope="{row}">
{{ row.timeOfEntry | formatDate }}
</template>
</el-table-column>
<el-table-column label="账户状态" sortable="">
<template slot-scope="{row}">
<el-switch :value="row.enableState === 1" />
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="280">
<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="delEmployee(row.id)">删除</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>
</div> </div>
<!-- 新增/编辑 弹窗 -->
<add-employee :show-dialog.sync="showDialog" />
</div> </div>
</template> </template>
<script> <script>
import { getEmployeeList, delEmployee } from '@/api/employees'
import EmployeeEnum from '@/api/constant/employees'
import AddEmployee from './components/add-employee'
export default { export default {
components: { AddEmployee },
data() {
return {
list: [], //
page: {
page: 1, //
size: 10, //
total: 0 //
},
showDialog: false //
}
},
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
},
//
formatEmployment(row, column, cellValue, index) {
// 1
const obj = EmployeeEnum.hireType.find(item => item.id === cellValue)
// return
return obj ? obj.value : '未知'
},
//
async delEmployee(id) {
try {
await this.$confirm('确定删除该员工吗?')
await delEmployee(id)
// ,
if (this.list.length === 1 && this.page.page > 1) {
this.page.page -= 1
}
this.getEmployeeList()
this.$message.success('删除员工成功')
} catch (err) {
console.log(err)
}
},
//
exportExcel() {
import('@/vendor/Export2Excel').then(excel => {
excel.export_json_to_excel({
header: tHeader, //
data, //
filename: 'excel-list', //
autoWidth: true, //
bookType: 'xlsx' //
})
})
}
}
} }
</script> </script>

View File

@ -0,0 +1,72 @@
<template>
<!-- 公共导入组件 -->
<upload-excel :on-success="onSuccess" />
</template>
<script>
import { importEmployee } from '@/api/employees'
export default {
data() {
return {
}
},
methods: {
async onSuccess(data) {
try {
console.log(data)
if (this.$route.query.type === 'employees') {
const oldUserDate = data.results
//
const dict = {
'入职日期': 'timeOfEntry',
'手机号': 'mobile',
'姓名': 'username',
'转正日期': 'correctionTime',
'工号': 'workNumber'
}
// ,
const userData = oldUserDate.map(user => {
//
const obj = {}
// , key
for (const cnKey in user) {
const enKey = dict[cnKey]
let value = user[cnKey]
// :
if (enKey === 'timeOfEntry' || enKey === 'correctionTime') {
value = new Date(this.formatDate(value, '/'))
}
obj[enKey] = value
}
return obj
})
console.log(userData)
await importEmployee(userData)
this.$message.success('导入成功')
this.$router.back()
}
} catch (error) {
console.log(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 + format + date
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
}
}
}
</script>
<style scoped lang="scss">
</style>