You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

639 lines
21 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div>
<el-dialog :visible.sync="dialogVisible" title="员工参与导入" width="60%" top="5vh">
<div class="title">模板下载</div>
<el-button style="margin-top: 10px" size="small" type="primary"
@click="exportExcel"></el-button>
<div style="color: red; margin-top: 10px">
类型请填入元禾员工参与或干部培训如参与的是系统内课程请确保课程名称与系统内课程名称一致
</div>
<el-upload
style="margin-top: 10px"
drag
action="#"
:http-request="handleCustomUpload"
:file-list="fileList"
accept=".xls, .xlsx"
:on-remove="handleRemove">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传xls/xlsx文件</div>
</el-upload>
<div class="title" style="margin-top: 20px;">数据预览可编辑</div>
<div v-if="tableList.length > 0" style="margin-top: 10px;">
<div style="margin-bottom: 10px;">
<el-button
size="small"
type="primary"
@click="showBatchEditDialog"
:disabled="selectedRows.length === 0">
批量更改(已选择 {{ selectedRows.length }} 条)
</el-button>
<el-button
size="small"
@click="handleSelectAll"
style="margin-left: 10px;"
:disabled="tableList.length === 0">
{{ isAllSelected ? '取消全选' : '全选' }}
</el-button>
</div>
<el-table
ref="table"
:data="tableList"
border
style="width: 100%;"
max-height="500"
size="small"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55"
align="center">
</el-table-column>
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
<el-table-column prop="type" label="类型" width="180" align="center">
<template slot-scope="scope">
<el-select
v-model="scope.row.type"
placeholder="请选择类型"
clearable
size="small"
style="width: 100%;"
@change="handleTypeChange(scope.row)">
<el-option label="元禾员工参与" :value="1"></el-option>
<el-option label="干部培训" :value="2"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="course_name" label="课程名称" min-width="200" align="center">
<template slot-scope="scope">
<el-input
v-model="scope.row.course_name"
placeholder="请输入课程名称"
size="small"
clearable
:disabled="scope.row.course_id !== null && scope.row.course_id !== undefined">
</el-input>
</template>
</el-table-column>
<el-table-column prop="start_date" label="开始日期" width="150" align="center">
<template slot-scope="scope">
<el-date-picker
v-model="scope.row.start_date"
type="date"
placeholder="请选择开始日期"
size="small"
style="width: 100%;"
value-format="yyyy-MM-dd"
:disabled="scope.row.course_id !== null && scope.row.course_id !== undefined">
</el-date-picker>
</template>
</el-table-column>
<el-table-column prop="end_date" label="结束日期" width="150" align="center">
<template slot-scope="scope">
<el-date-picker
v-model="scope.row.end_date"
type="date"
placeholder="请选择结束日期"
size="small"
style="width: 100%;"
value-format="yyyy-MM-dd"
:disabled="scope.row.course_id !== null && scope.row.course_id !== undefined">
</el-date-picker>
</template>
</el-table-column>
<el-table-column prop="company_name" label="公司名称" min-width="200" align="center">
<template slot-scope="scope">
<el-input
v-model="scope.row.company_name"
placeholder="请输入公司名称"
size="small"
clearable
:disabled="scope.row.course_id !== null && scope.row.course_id !== undefined">
</el-input>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" width="150" align="center">
<template slot-scope="scope">
<el-input
v-model="scope.row.name"
placeholder="请输入姓名"
size="small"
clearable
:disabled="scope.row.course_id !== null && scope.row.course_id !== undefined">
</el-input>
</template>
</el-table-column>
<el-table-column prop="department" label="部门" width="150" align="center">
<template slot-scope="scope">
<el-input
v-model="scope.row.department"
placeholder="请输入部门"
size="small"
clearable
:disabled="scope.row.course_id !== null && scope.row.course_id !== undefined">
</el-input>
</template>
</el-table-column>
</el-table>
<div style="font-size: 12px; margin-top: 10px;">总共数据:{{ tableList.length }}条</div>
</div>
<div v-else style="text-align: center; padding: 40px; color: #909399;">
请先上传文件进行预览
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmImport" :disabled="tableList.length === 0">确认导入</el-button>
</div>
</el-dialog>
<!-- 批量编辑对话框 -->
<el-dialog
:visible.sync="batchEditDialogVisible"
title="批量更改"
width="600px">
<el-alert
type="info"
:closable="false"
style="margin-bottom: 20px;">
<div slot="title">
<div>提示:若不填写则保留原值</div>
<div style="margin-top: 5px;">类型会应用到全部选中行;已匹配系统课程的行不会改课程名称和日期</div>
<div style="margin-top: 5px;">将应用到选中的数据(共 {{ selectedRows.length }} 条)</div>
</div>
</el-alert>
<el-form :model="batchEditForm" label-width="120px" size="small">
<el-form-item label="类型">
<el-select
v-model="batchEditForm.type"
placeholder="请选择类型(不填则保留原值)"
clearable
style="width: 100%;">
<el-option label="元禾员工参与" :value="1"></el-option>
<el-option label="干部培训" :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="课程名称">
<el-input
v-model="batchEditForm.course_name"
placeholder="请输入课程名称(不填则保留原值)"
clearable>
</el-input>
</el-form-item>
<el-form-item label="开始日期">
<el-date-picker
v-model="batchEditForm.start_date"
type="date"
placeholder="请选择开始日期(不填则保留原值)"
style="width: 100%;"
value-format="yyyy-MM-dd"
clearable>
</el-date-picker>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker
v-model="batchEditForm.end_date"
type="date"
placeholder="请选择结束日期(不填则保留原值)"
style="width: 100%;"
value-format="yyyy-MM-dd"
clearable>
</el-date-picker>
</el-form-item>
<el-form-item label="公司名称">
<el-input
v-model="batchEditForm.company_name"
placeholder="请输入公司名称(不填则保留原值)"
clearable>
</el-input>
</el-form-item>
<el-form-item label="姓名">
<el-input
v-model="batchEditForm.name"
placeholder="请输入姓名(不填则保留原值)"
clearable>
</el-input>
</el-form-item>
<el-form-item label="部门">
<el-input
v-model="batchEditForm.department"
placeholder="请输入部门(不填则保留原值)"
clearable>
</el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="batchEditDialogVisible = false">取消</el-button>
<el-button type="primary" @click="applyBatchEdit">确认应用</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as XLSX from "xlsx";
import { saveAs } from "file-saver";
import { excelShow, importEmployee } from "@/api/employee/index.js";
export default {
name: 'ImportEmployee',
data() {
return {
dialogVisible: false,
tableList: [],
fileList: [],
selectedRows: [], // 选中的行
batchEditDialogVisible: false,
batchEditForm: {
type: null,
course_name: '',
start_date: '',
end_date: '',
company_name: '',
name: '',
department: ''
}
};
},
computed: {
isAllSelected() {
return this.tableList.length > 0 && this.selectedRows.length === this.tableList.length;
}
},
methods: {
show() {
this.dialogVisible = true;
},
hidden() {
this.dialogVisible = false;
},
// 处理类型变化
handleTypeChange(row) {
// 同步更新 type_name
if (row.type === 1) {
row.type_name = '元禾员工参与';
} else if (row.type === 2) {
row.type_name = '干部培训';
} else {
row.type_name = '';
}
},
isCourseLocked(row) {
return row.course_id !== null && row.course_id !== undefined;
},
handleSelectionChange(selection) {
this.selectedRows = selection;
},
handleSelectAll() {
if (this.isAllSelected) {
this.$refs.table.clearSelection();
return;
}
this.tableList.forEach(row => {
this.$refs.table.toggleRowSelection(row, true);
});
},
// 显示批量编辑对话框
showBatchEditDialog() {
if (this.selectedRows.length === 0) {
this.$message({
message: '请先选择要批量编辑的数据',
type: 'warning'
});
return;
}
// 重置表单
this.batchEditForm = {
type: null,
course_name: '',
start_date: '',
end_date: '',
company_name: '',
name: '',
department: ''
};
this.batchEditDialogVisible = true;
},
applyBatchEdit() {
if (this.selectedRows.length === 0) {
this.$message({
message: '请先选择要批量编辑的数据',
type: 'warning'
});
return;
}
let updateCount = 0;
this.selectedRows.forEach(row => {
let hasUpdate = false;
const locked = this.isCourseLocked(row);
if (this.batchEditForm.type !== null && this.batchEditForm.type !== undefined) {
row.type = this.batchEditForm.type;
this.handleTypeChange(row);
hasUpdate = true;
}
if (!locked) {
if (this.batchEditForm.course_name !== null &&
this.batchEditForm.course_name !== undefined &&
String(this.batchEditForm.course_name).trim() !== '') {
row.course_name = String(this.batchEditForm.course_name).trim();
hasUpdate = true;
}
if (this.batchEditForm.start_date !== null &&
this.batchEditForm.start_date !== undefined &&
String(this.batchEditForm.start_date).trim() !== '') {
row.start_date = String(this.batchEditForm.start_date).trim();
hasUpdate = true;
}
if (this.batchEditForm.end_date !== null &&
this.batchEditForm.end_date !== undefined &&
String(this.batchEditForm.end_date).trim() !== '') {
row.end_date = String(this.batchEditForm.end_date).trim();
hasUpdate = true;
}
if (this.batchEditForm.company_name !== null &&
this.batchEditForm.company_name !== undefined &&
String(this.batchEditForm.company_name).trim() !== '') {
row.company_name = String(this.batchEditForm.company_name).trim();
hasUpdate = true;
}
if (this.batchEditForm.name !== null &&
this.batchEditForm.name !== undefined &&
String(this.batchEditForm.name).trim() !== '') {
row.name = String(this.batchEditForm.name).trim();
hasUpdate = true;
}
if (this.batchEditForm.department !== null &&
this.batchEditForm.department !== undefined &&
String(this.batchEditForm.department).trim() !== '') {
row.department = String(this.batchEditForm.department).trim();
hasUpdate = true;
}
}
if (hasUpdate) {
updateCount++;
}
});
this.$message({
message: `成功批量更新 ${updateCount} 条数据`,
type: 'success'
});
this.batchEditDialogVisible = false;
},
normalizePreviewRow(item) {
const typeMap = {
'元禾员工参与': 1,
'元和员工参与': 1,
'员工参与数': 1,
'员工参与': 1,
'1': 1,
'干部培训': 2,
'干部培训数': 2,
'2': 2
};
const typeNameMap = {
1: '元禾员工参与',
2: '干部培训'
};
const newItem = { ...item };
let typeValue = newItem.type;
if (typeof typeValue === 'string') {
typeValue = typeMap[typeValue.trim()] ?? null;
} else if (typeValue === 1 || typeValue === 2 || typeValue === '1' || typeValue === '2') {
typeValue = Number(typeValue);
} else if (typeValue != null && typeValue !== '') {
const numeric = Number(typeValue);
typeValue = numeric === 1 || numeric === 2 ? numeric : null;
} else {
typeValue = null;
}
newItem.type = typeValue;
newItem.type_name = typeNameMap[typeValue] || '';
return newItem;
},
// 模板下载
exportExcel() {
const headers = ['类型', '课程名称', '公司名称', '姓名', '部门'];
// 说明写在页面上,不要放进类型列,否则预览会把说明当成类型值导致该列空白
const data = [headers];
const wb = XLSX.utils.book_new();
const ws = XLSX.utils.aoa_to_sheet(data);
XLSX.utils.book_append_sheet(wb, ws, '员工参与导入模板');
const wbout = XLSX.write(wb, {
bookType: "xlsx",
bookSST: true,
type: "array",
});
saveAs(
new Blob([wbout], {
type: "application/octet-stream"
}),
`员工参与导入模板.xlsx`
);
},
// 自定义上传
async handleCustomUpload(options) {
const { file, onSuccess, onError } = options;
try {
const formData = new FormData();
formData.append('file', file);
// 调用预览接口
const res = await excelShow(formData);
// 检查返回结果
if (res && res.hasOwnProperty("errcode") && res.errcode !== 0) {
this.$message({
message: res.errmsg || "上传失败",
type: "error",
duration: 2000
});
onError(new Error(res.errmsg || "上传失败"));
return;
}
// 处理返回的数据:预览接口返回什么就保留什么
let dataList = [];
if (Array.isArray(res)) {
dataList = res;
} else if (res && res.data && Array.isArray(res.data)) {
dataList = res.data;
}
dataList = dataList.map(item => this.normalizePreviewRow(item));
// 排序course_id 为 null 的放在前面
dataList.sort((a, b) => {
const aHasCourseId = a.course_id !== null && a.course_id !== undefined;
const bHasCourseId = b.course_id !== null && b.course_id !== undefined;
if (aHasCourseId && !bHasCourseId) return 1;
if (!aHasCourseId && bHasCourseId) return -1;
return 0;
});
this.tableList = dataList;
this.fileList = [file];
// 清空选中状态
this.$nextTick(() => {
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
this.selectedRows = [];
});
this.$message({
message: `上传成功,共${this.tableList.length}条数据`,
type: "success"
});
// 通知 el-upload 成功
onSuccess(res, file);
} catch (error) {
console.error('上传失败:', error);
this.$message({
message: error.message || "上传失败",
type: "error",
duration: 2000
});
onError(error);
}
},
// 移除文件
handleRemove(file, fileList) {
this.fileList = fileList;
this.tableList = [];
this.selectedRows = [];
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
},
// 确认导入
async confirmImport() {
if (this.tableList.length === 0) {
this.$message({
message: "请先上传文件并预览数据",
type: "warning"
});
return;
}
// 验证数据:确保每一行的每一个字段都有值
const requiredFields = ['type', 'course_name', 'start_date', 'end_date', 'company_name', 'name', 'department'];
const fieldNames = {
type: '类型',
course_name: '课程名称',
start_date: '开始日期',
end_date: '结束日期',
company_name: '公司名称',
name: '姓名',
department: '部门'
};
for (let i = 0; i < this.tableList.length; i++) {
const row = this.tableList[i];
for (let j = 0; j < requiredFields.length; j++) {
const field = requiredFields[j];
const value = row[field];
if (value === null || value === undefined || value === '') {
this.$message({
message: `第 ${i + 1} 行:${fieldNames[field]}不能为空`,
type: "warning"
});
return;
}
}
}
try {
// 预览接口返回什么就提交什么,不做转换
const res = await importEmployee({ data: this.tableList });
this.$message({
type: 'success',
message: `成功导入${res.total || this.tableList.length}条`
});
this.hidden();
this.$emit('refresh');
} catch (error) {
console.error('导入失败:', error);
this.$message({
message: error.message || "导入失败",
type: "error"
});
}
}
},
watch: {
dialogVisible(newVal) {
if (!newVal) {
// 关闭时清空数据
this.tableList = [];
this.fileList = [];
this.selectedRows = [];
this.batchEditDialogVisible = false;
this.batchEditForm = {
type: null,
course_name: '',
start_date: '',
end_date: '',
company_name: '',
name: '',
department: ''
};
// 清空表格选中状态
this.$nextTick(() => {
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
});
}
},
batchEditDialogVisible(newVal) {
if (!newVal) {
// 关闭批量编辑对话框时重置表单
this.batchEditForm = {
type: null,
course_name: '',
start_date: '',
end_date: '',
company_name: '',
name: '',
department: ''
};
}
}
}
};
</script>
<style scoped lang="scss">
.title {
font-size: 15px;
font-weight: 600;
padding: 8px 4px;
position: relative;
&::before {
content: '';
width: 4px;
background: $primaryColor;
position: absolute;
top: 8px;
bottom: 8px;
left: -8px;
}
}
</style>