Files
school-developer/src/views/professional/outercompanyemployee/indexSecond.vue
guochunsi e1cb334fbf ren
2026-01-06 19:23:18 +08:00

914 lines
26 KiB
Vue
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="handleFilter">
<el-form-item label="单位名称" prop="companyId">
<el-select
v-model="state.queryForm.companyId"
filterable
clearable
placeholder="请选择单位"
style="width: 200px"
>
<el-option
v-for="item in companyList"
:key="item.id"
:label="item.companyName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="职员编号" prop="employeeNo">
<el-input
v-model="state.queryForm.employeeNo"
placeholder="请输入职员编号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="state.queryForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="身份证" prop="idCard">
<el-input
v-model="state.queryForm.idCard"
placeholder="请输入身份证"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="手机" prop="mobile">
<el-input
v-model="state.queryForm.mobile"
placeholder="请输入手机"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="允许进出" prop="inoutFlag">
<el-select
v-model="state.queryForm.inoutFlag"
clearable
placeholder="请选择"
style="width: 200px"
>
<el-option
v-for="item in yesNoDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="handleFilter">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_outercompanyemployee_add">
</el-button>
<el-button
type="primary"
@click="handleExportIn"
v-if="permission.scope == '1'"
>
</el-button>
<el-button
type="warning"
@click="handleExportScore"
:loading="exportLoading"
icon="Download">导出
</el-button>
<el-button
type="primary"
@click="batchDelect">批量删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="companyName" label="单位名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="employeeNo" label="职员编号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" min-width="100" align="center" show-overflow-tooltip />
<el-table-column prop="idCard" label="身份证" min-width="180" align="center" show-overflow-tooltip />
<el-table-column prop="mobile" label="手机" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="inoutFlag" label="允许进出" width="100" align="center">
<template #default="scope">
<el-tag v-if="scope.row.inoutFlag">{{ getDictLabel(scope.row.inoutFlag) }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="头像" width="100" align="center">
<template #default="scope">
<img
width="50px"
height="50px"
@click="handlePictureCardPreview(scope.row.employeeNo)"
:src="getImageView(scope.row.employeeNo)"
@error="handleImageError"
style="cursor: pointer; object-fit: cover; border-radius: 4px;"
/>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_outercompanyemployee_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_outercompanyemployee_reset_pw"
icon="RefreshLeft"
link
type="primary"
style="margin-left: 12px"
@click="resetPassword(scope.row)">重置密码
</el-button>
<el-button
v-if="permissions.professional_outercompanyemployee_del"
icon="delete"
link
type="primary"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
<!-- 新增/编辑弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="form.id ? '编辑' : '新增'"
width="800px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="单位名称" prop="companyId">
<el-select
v-model="form.companyId"
filterable
clearable
placeholder="请选择单位"
style="width: 100%"
>
<el-option
v-for="item in companyList"
:key="item.id"
:label="item.companyName"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职员编号" prop="employeeNo">
<el-input
v-model="form.employeeNo"
placeholder="系统自动生成"
disabled
clearable
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名" prop="realName">
<el-input
v-model="form.realName"
placeholder="请输入姓名"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证" prop="idCard">
<el-input
v-model="form.idCard"
placeholder="请输入身份证号"
clearable
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手机" prop="mobile">
<el-input
v-model="form.mobile"
placeholder="请输入手机号"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位" prop="position">
<el-input
v-model="form.position"
placeholder="请输入职位"
clearable
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="家庭地址" prop="address">
<el-input
v-model="form.address"
placeholder="请输入家庭地址"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="允许进出" prop="inoutFlag">
<el-select
v-model="form.inoutFlag"
clearable
placeholder="请选择"
style="width: 100%"
>
<el-option
v-for="item in yesNoDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 头像预览对话框 -->
<el-dialog v-model="dialogUploadVisible" title="头像预览" append-to-body>
<img width="100%" :src="dialogImageUrl" alt="" style="max-height: 600px; object-fit: contain;" />
</el-dialog>
<!-- 上传头像对话框 -->
<el-dialog v-model="dialogAvatarVisible" title="上传头像" width="50%" append-to-body>
<el-upload
action="/basic/basicstudent/uploadAvatarBase64"
list-type="picture-card"
name="file"
:headers="headers"
:limit="1"
:data="uploadData"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="removeHandler"
:http-request="httpRequest"
:on-success="uploadSuccess"
>
<el-icon><Plus /></el-icon>
<template #tip>
<div class="el-upload__tip">上传头像人脸识别用</div>
</template>
</el-upload>
</el-dialog>
<!-- 导入对话框 -->
<el-dialog v-model="dialogViewVisible" title="导入文件" append-to-body>
<el-upload
class="upload-container"
ref="uploadFormRef"
action="doUpload"
:limit="1"
:file-list="filesList"
:before-upload="fileUpload"
:auto-upload="false"
>
<template #trigger>
<el-button type="primary">选取文件</el-button>
</template>
<a href="outercomanyemployee.xlsx" rel="external nofollow" download="模板" style="margin-left: 20px">
<el-button type="success">下载模板</el-button>
</a>
<template #tip>
<div class="el-upload__tip">只能上传excel文件且不超过5MB</div>
<div class="el-upload__tip">{{ fileName }}</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogViewVisible = false">取消</el-button>
<el-button type="primary" @click="submitUpload">导入</el-button>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { useMessage } from '/@/hooks/message'
import { useMessageBox } from '/@/hooks/message'
import { useDict } from '/@/hooks/dict'
import { Session } from '/@/utils/storage'
import { validateNull } from '/@/utils/validate'
import axios from 'axios'
import request from '/@/utils/request'
import { Plus } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
import {
fetchList,
getObj,
saveSecond,
putObj,
delObj,
batchDel,
resetPassWord
} from '/@/api/professional/stayschool/outercompanyemployee'
import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany'
// 使用 Pinia store
const userInfoStore = useUserInfo()
const { userInfos } = storeToRefs(userInfoStore)
// 创建权限对象
const permissions = computed(() => {
const perms: Record<string, boolean> = {}
userInfos.value.authBtnList.forEach((perm: string) => {
perms[perm] = true
})
return perms
})
// 消息提示 hooks
const message = useMessage()
const messageBox = useMessageBox()
// 字典数据
const { yes_no: yesNoDict } = useDict('yes_no')
// 获取字典标签的辅助函数
const getDictLabel = (value: string | number) => {
const item = yesNoDict.value.find((i: any) => i.value === value)
return item ? item.label : ''
}
// 表格引用
const tableRef = ref()
const formRef = ref()
const queryRef = ref()
const uploadFormRef = ref()
// 搜索显示
const showSearch = ref(true)
// 弹窗状态
const dialogVisible = ref(false)
const dialogUploadVisible = ref(false)
const dialogAvatarVisible = ref(false)
const dialogViewVisible = ref(false)
const submitLoading = ref(false)
const exportLoading = ref(false)
// 选中的行数据
const selectList = ref<any[]>([])
const permission = reactive({
hasPermission: "0",
scope: "0"
})
// 单位列表
const companyList = ref<any[]>([])
// 表单数据
const form = reactive({
id: '',
companyId: '',
companyName: '',
employeeNo: '',
realName: '',
idCard: '',
mobile: '',
position: '',
address: '',
inoutFlag: '',
remarks: ''
})
// 表单验证规则
const formRules = {
companyId: [
{ required: true, message: '请选择单位', trigger: 'change' }
],
realName: [
{ required: true, message: '请填写姓名', trigger: 'blur' }
],
idCard: [
{ required: true, message: '请填写身份证号', trigger: 'blur' }
],
mobile: [
{ required: true, message: '请填写手机号', trigger: 'blur' }
],
address: [
{ required: true, message: '请填写家庭住址', trigger: 'blur' }
],
inoutFlag: [
{ required: true, message: '请选择是否允许进出', trigger: 'change' }
]
}
// 头像相关
const dialogImageUrl = ref('')
const fileList = ref<any[]>([])
const rowData = ref<any>({})
const fileReader = ref<FileReader | null>(null)
// 上传相关
const uploadData = reactive({
bucketName: "base",
module: "basic",
username: ""
})
// 导入相关
const fileName = ref('')
const filesList = ref<any[]>([])
let files: File | null = null
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 配置 useTable - 注意这个 API 返回的数据结构特殊
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList({
...params,
companyType: '2' // 二期单位
})
// 特殊处理API 返回的是 response.data.data.dataList.records
const dataList = response.data?.data?.dataList || response.data?.dataList || {}
permission.hasPermission = response.data?.data?.permission?.hasPermission || "0"
permission.scope = response.data?.data?.permission?.scope || "0"
return {
data: {
records: dataList.records || [],
total: dataList.total || 0
}
}
},
queryForm: {
companyId: '',
employeeNo: '',
realName: '',
idCard: '',
mobile: '',
inoutFlag: ''
}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 获取单位列表
const loadCompanyList = async () => {
try {
const response = await getCompanyList({ companyType: '2' })
companyList.value = response.data || []
} catch (error) {
// 获取单位列表失败
}
}
// 重置查询
const resetQuery = () => {
queryRef.value?.resetFields()
getDataList()
}
// 处理搜索
const handleFilter = () => {
getDataList()
}
// 多选变化
const handleSelectionChange = (selection: any[]) => {
selectList.value = selection
}
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
companyId: '',
companyName: '',
employeeNo: '',
realName: '',
idCard: '',
mobile: '',
position: '',
address: '',
inoutFlag: '',
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
try {
const response = await getObj(row.id)
const data = response.data
Object.assign(form, {
id: data.id,
companyId: data.companyId || '',
companyName: data.companyName || '',
employeeNo: data.employeeNo || '',
realName: data.realName || '',
idCard: data.idCard || '',
mobile: data.mobile || '',
position: data.position || '',
address: data.address || '',
inoutFlag: data.inoutFlag || '',
remarks: data.remarks || ''
})
dialogVisible.value = true
} catch (error) {
// 获取详情失败
}
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => {
await delObj(row.id)
message.success('删除成功')
// 如果当前页只剩一条数据,且不是第一页,则跳转到上一页
if (state.pagination && state.dataList && state.dataList.length === 1 && state.pagination.current && state.pagination.current > 1) {
state.pagination.current = state.pagination.current - 1
}
getDataList()
}).catch(() => {})
}
// 批量删除
const batchDelect = () => {
if (selectList.value.length === 0) {
message.warning('请至少选择一条数据')
return
}
messageBox.confirm('是否确认删除').then(async () => {
await batchDel(selectList.value)
message.success('删除成功')
selectList.value = []
getDataList()
}).catch(() => {})
}
// 重置密码
const resetPassword = (row: any) => {
messageBox.confirm('是否确定重置密码?').then(async () => {
try {
const response = await resetPassWord(row)
const pw = response.data?.data
if (!validateNull(pw)) {
ElMessageBox.alert('重置后密码为: ' + pw, '重置密码')
} else {
ElMessageBox.alert('系统繁忙,请重试', '重置密码')
}
} catch (error) {
// 重置密码失败
}
}).catch(() => {})
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
// 设置单位名称
const selectedCompany = companyList.value.find(item => item.id === form.companyId)
if (selectedCompany) {
form.companyName = selectedCompany.companyName
}
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await saveSecond(form) // 使用 saveSecond API
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
// 获取图片 URL
const getImageView = (employeeNo: string) => {
const timestamp = Date.parse(new Date().toString())
const baseUrl = import.meta.env.VITE_API_URL || ''
return `${baseUrl}/admin/user/photo/${employeeNo}?${timestamp}`
}
// 图片加载错误处理
const handleImageError = (event: Event) => {
const img = event.target as HTMLImageElement
img.src = '/img/default/no_pic.png'
}
// 预览头像
const handlePictureCardPreview = (employeeNo: string) => {
dialogImageUrl.value = getImageView(employeeNo)
dialogUploadVisible.value = true
}
// 上传前验证
const beforeUpload = (file: File) => {
const isLt5M = file.size < 1 * 1024 * 1024
if (fileList.value.length >= 1) {
message.warning('只能上传一张头像')
return false
}
if (!isLt5M) {
message.warning('文件大小不能超过1M')
return false
}
return true
}
// 自定义上传
const httpRequest = (options: any) => {
const file = options.file
if (!fileReader.value) {
fileReader.value = new FileReader()
}
if (file) {
fileReader.value.readAsDataURL(file)
}
fileReader.value.onload = () => {
const base64Str = fileReader.value?.result as string
const config = {
url: '/basic/basicstudent/uploadAvatarBase64',
method: 'post',
data: {
base64Str: base64Str.split(',')[1],
username: rowData.value.employeeNo
},
timeout: 10000,
onUploadProgress: (progressEvent: any) => {
progressEvent.percent = progressEvent.loaded / progressEvent.total * 100
options.onProgress(progressEvent, file)
}
}
axios(config)
.then(res => {
options.onSuccess(res, file)
getDataList()
})
.catch(err => {
options.onError(err)
})
}
}
// 移除文件
const removeHandler = (file: any) => {
const index = fileList.value.findIndex(f => f.uid === file.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
}
}
// 上传成功
const uploadSuccess = (res: any, file: any) => {
if (res.data) {
const data = res.data
file.key = data.key
fileList.value.push(file)
message.success('上传成功')
dialogAvatarVisible.value = false
getDataList()
}
}
// 导入
const handleExportIn = () => {
fileName.value = ""
filesList.value = []
files = null
dialogViewVisible.value = true
}
// 文件上传验证
const fileUpload = (file: File) => {
const fileLast = file.name.split('.')
const extension = fileLast[fileLast.length - 1] === 'xls'
const extension2 = fileLast[fileLast.length - 1] === 'xlsx'
const isLt2M = file.size / 1024 / 1024 < 5
if (!extension && !extension2) {
message.warning('上传模板只能是 xls、xlsx格式!')
return false
}
if (!isLt2M) {
message.warning('上传模板大小不能超过 5MB!')
return false
}
fileName.value = file.name
files = file
return false // 返回false不会自动上传
}
// 提交导入
const submitUpload = async () => {
if (!fileName.value || !files) {
message.warning('请选择要上传的文件!')
return
}
const fileFormData = new FormData()
fileFormData.append('file', files, fileName.value)
try {
const response = await request({
url: `/professional/file/exportOuterCompanyEmployee?companyType=2`,
method: 'post',
data: fileFormData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (response.code === 0) {
message.success('操作成功')
dialogViewVisible.value = false
getDataList()
} else {
message.error(response.msg || '导入失败')
}
} catch (error: any) {
message.error(error?.msg || '导入失败')
}
}
// 导出
const handleExportScore = async () => {
if (!state.queryForm || Object.keys(state.queryForm).length === 0) {
message.warning('请选择导出条件')
return
}
exportLoading.value = true
try {
const params = {
...state.queryForm,
companyType: "2"
}
const response = await axios({
method: 'post',
url: '/professional/outercompanyemployee/export',
data: params,
responseType: 'blob',
headers: {
'Content-Type': 'application/json'
}
})
const blob = new Blob([response.data])
const fileName = '二期单位人员导出表.xls'
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
message.success('导出成功')
} catch (error: any) {
message.error(error?.msg || '导出失败')
} finally {
exportLoading.value = false
}
}
// 初始化
onMounted(() => {
if (!window.FileReader) {
// eslint-disable-next-line no-console
console.error('Your browser does not support FileReader API!')
} else {
fileReader.value = new FileReader()
}
loadCompanyList()
})
</script>
<style lang="scss" scoped>
.upload-container {
:deep(.el-upload) {
width: 100%;
}
}
</style>