This commit is contained in:
guochunsi
2025-12-31 17:40:01 +08:00
parent 6d94e91b70
commit 74c06bb8a0
713 changed files with 115034 additions and 46 deletions

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalacademicqualificationsconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="qualificationName" label="学历名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalacademicqualificationsconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalacademicqualificationsconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="学历名称" prop="qualificationName">
<el-input
v-model="form.qualificationName"
placeholder="请输入学历名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/academicqualificationsconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
qualificationName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
qualificationName: [
{ required: true, message: '请输入学历名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
qualificationName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
qualificationName: row.qualificationName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,52 @@
<template>
<div>
<multi-upload
ref="commonUploadPicRef"
@pushListData="pushListData"
@delListData="delListData"
:params="params"
:fileList="fileList"
:limitNums="limitNums"
:accept-file="acceptFile"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// @ts-ignore - Vue 3 script setup component
import multiUpload from './multiUpload.vue'
// Props
const props = defineProps<{
fileList: any[]
type?: string
params?: any
limitNums?: number
acceptFile?: string
}>()
// 组件引用
const commonUploadPicRef = ref()
// 推送列表数据
const pushListData = (data: { name: string; url: string }) => {
props.fileList.push(data)
}
// 删除列表数据
const delListData = (file: string) => {
const index = props.fileList.findIndex((item: any) => item.url === file)
if (index > -1) {
props.fileList.splice(index, 1)
}
}
// 暴露方法
defineExpose({
commonUploadPic: commonUploadPicRef
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,29 @@
<template>
<div>
<el-button type="success" icon="view" size="small" @click="handlePdfPreview">预览</el-button>
<el-dialog v-model="visible" width="90%" append-to-body destroy-on-close>
<auth-img :authSrc="url" style="height:1000px;" v-if="visible" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import authImg from '@/components/tools/auth-img.vue'
// Props
defineProps<{
url: string
}>()
// 对话框显示状态
const visible = ref(false)
// 预览PDF
const handlePdfPreview = () => {
visible.value = true
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div>
<el-upload
class="upload-demo"
action="/professional/file/teacherAboutInfoUpload"
:data="params"
:headers="headers"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:file-list="fileList"
:accept="realAcceptFile"
:limit="realLimitNums"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<el-tag>仅支持{{realAcceptFile}}后缀的文件上传,且文件大小不能超过5M!最大上传数量限制为{{realLimitNums}}.</el-tag>
</el-upload>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
// Props
const props = defineProps<{
params?: any
fileList?: any[]
limitNums?: number
acceptFile?: string
}>()
// Emits
const emit = defineEmits<{
delListData: [url: string]
pushListData: [obj: { name: string; url: string }]
}>()
// 消息提示 hooks
const message = useMessage()
// 响应式数据
const realLimitNums = ref(0)
const realAcceptFile = ref('.jpg,.jpeg,.png,.pdf')
const fileName = ref('')
// Computed
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 生命周期
onMounted(() => {
if (props.limitNums) {
realLimitNums.value = props.limitNums
} else {
realLimitNums.value = 5
}
if (props.acceptFile) {
realAcceptFile.value = props.acceptFile
} else {
realAcceptFile.value = ".jpg,.jpeg,.png,.pdf"
}
})
// 方法
// const handleUploadLimit = () => {
// message.error('最多只能上传1张图片')
// }
const beforeUpload = (file: any) => {
let fileNameStr = file.name
const pos = fileNameStr.lastIndexOf(".")
fileNameStr = fileNameStr.substring(pos + 1, fileNameStr.length)
fileNameStr = fileNameStr.toLowerCase()
const extension = fileNameStr === 'jpg'
const extension2 = fileNameStr === 'png'
const extension3 = fileNameStr === 'jpeg'
const extension4 = fileNameStr === 'bmp'
const extension5 = fileNameStr === 'gif'
const extension6 = fileNameStr === 'pdf'
const isLt2M = file.size / 1024 / 1024 < 5
if (!extension && !extension2 && !extension3 && !extension4 && !extension5 && !extension6) {
message.warning('请检查文件上传格式!')
return false
}
if (!isLt2M) {
message.warning('上传图片大小不能超过 5MB!')
return false
}
fileName.value = file.name
return true // 返回false不会自动上传
}
const handleRemove = (file: any) => {
emit("delListData", file.url)
}
// const handleUploadError = () => {
// message.error('文件上传失败,请检查文件上传格式与大小')
// }
const handleUploadSuccess = (res: any) => {
message.success('文件上传成功')
const obj = { name: res.data.name, url: res.data.url }
emit("pushListData", obj)
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,161 @@
<template>
<el-dialog v-model="visible" title="驳回" width="600px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="150px">
<el-form-item label="姓名:">
<el-tag>{{ dataForm.name }}</el-tag>
</el-form-item>
<el-form-item label="工号:">
<el-tag>{{ dataForm.teacherNo }}</el-tag>
</el-form-item>
<el-form-item label="类型:">
<el-tag>{{ typeName }}</el-tag>
</el-form-item>
<el-form-item label="驳回理由">
<el-input
type="textarea"
v-model="dataForm.backReason"
:rows="4"
placeholder="请输入驳回理由"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button @click="saveData" :loading="loading" type="primary"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { putObj as editTitle } from '/@/api/professional/professionaluser/professionaltitlerelation'
import { putObj as editQua } from '/@/api/professional/professionaluser/professionalqualificationrelation'
import { putObj as editCer } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
import { putObj as editEducation } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
import { putObj as editHonor } from '/@/api/professional/professionaluser/professionalteacherhonor'
import { putObj as editPaper } from '/@/api/professional/professionalteacherpaper'
import { putObj as editMaterial } from '/@/api/professional/professionalteachingmaterial'
import { putObj as editTopic } from '/@/api/professional/professionaltopiclist'
// Emits
const emit = defineEmits<{
(e: 'refreshData'): void
}>()
// 消息提示
const message = useMessage()
const messageBox = useMessageBox()
// 对话框显示状态
const visible = ref(false)
// 加载状态
const loading = ref(false)
// 表单数据
const dataForm = reactive({
name: '',
id: null as string | number | null,
backReason: '',
teacherNo: ''
})
// 类型名称
const typeName = ref('')
// 类型
const type = ref('')
// 类型映射
const typeMap: Record<string, string> = {
title: '职称',
qua: '职业资格',
cer: '教师资格证',
education: '学历学位',
honor: '综合表彰',
paper: '教师论文',
material: '教材',
topic: '课题'
}
// API 方法映射
const apiMethodMap: Record<string, any> = {
title: editTitle,
qua: editQua,
cer: editCer,
education: editEducation,
honor: editHonor,
paper: editPaper,
material: editMaterial,
topic: editTopic
}
// 初始化
const init = (row: any, typeValue: string) => {
type.value = typeValue
typeName.value = typeMap[typeValue] || ''
dataForm.name = row.realName || ''
dataForm.teacherNo = row.teacherNo || ''
dataForm.id = row.id || null
dataForm.backReason = ''
visible.value = true
}
// 保存数据
const saveData = async () => {
if (!dataForm.backReason.trim()) {
message.warning('请填写驳回理由')
return
}
const apiMethod = apiMethodMap[type.value]
if (!apiMethod) {
message.error('未知的类型')
return
}
await saveDataMethod(apiMethod)
}
// 保存数据方法
const saveDataMethod = async (method: any) => {
const data = {
id: dataForm.id,
state: '-2',
backReason: dataForm.backReason
}
try {
await messageBox.confirm(`确认驳回, ${dataForm.name}${typeName.value}申请?`)
loading.value = true
await method(data)
message.success('操作成功')
visible.value = false
emit('refreshData')
} catch (error: any) {
// 用户取消或请求失败
if (error !== 'cancel') {
message.error(error?.msg || '操作失败')
}
} finally {
loading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped lang="scss">
.dialog-footer {
text-align: right;
}
</style>

View File

@@ -0,0 +1,155 @@
<template>
<el-dialog append-to-body v-model="visible" width="90%" height="700px" :title="title">
<el-tabs v-model="activeName" type="border-card" tab-position="left">
<el-tab-pane label="获奖证书" name="awardImg" v-if="imageData.awardImg">
<auth-img v-for="src in imageData.awardImg" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="知网截图" name="knowdgeImg" v-if="imageData.knowdgeImg">
<auth-img v-for="src in imageData.knowdgeImg" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="刊物封面" name="pubCover" v-if="imageData.pubCover">
<auth-img v-for="src in imageData.pubCover" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="目录页" name="cateImg" v-if="imageData.cateImg">
<auth-img v-for="src in imageData.cateImg" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="内容页" name="contentImg" v-if="imageData.contentImg">
<auth-img v-for="src in imageData.contentImg" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="教材封面" name="mateCover" v-if="imageData.mateCover">
<auth-img v-for="src in imageData.mateCover" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="出版页" name="pubImg" v-if="imageData.pubImg">
<auth-img v-for="src in imageData.pubImg" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="立项申报书" name="projectApp" v-if="imageData.projectApp">
<auth-img v-for="src in imageData.projectApp" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="结题证书" name="conclusionBook" v-if="imageData.conclusionBook">
<auth-img v-for="src in imageData.conclusionBook" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="结题报告" name="conclusionReport" v-if="imageData.conclusionReport">
<auth-img v-for="src in imageData.conclusionReport" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
<el-tab-pane label="其他材料" name="otherImg" v-if="imageData.otherImg">
<auth-img v-for="src in imageData.otherImg" :authSrc="src.url" :key="src.url" style="height:1000px;"></auth-img>
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
<script setup lang="ts" name="showEvidence">
import { ref, reactive, nextTick } from 'vue';
import authImg from '/@/components/tools/auth-img.vue';
interface ImageItem {
url: string;
}
interface ImageData {
awardImg?: ImageItem[];
knowdgeImg?: ImageItem[];
pubCover?: ImageItem[];
cateImg?: ImageItem[];
contentImg?: ImageItem[];
mateCover?: ImageItem[];
pubImg?: ImageItem[];
projectApp?: ImageItem[];
conclusionBook?: ImageItem[];
conclusionReport?: ImageItem[];
otherImg?: ImageItem[];
}
const props = defineProps<{
row?: any;
title?: string;
}>();
const visible = ref(false);
const imageData = reactive<ImageData>({});
const activeName = ref('');
/**
* 安全解析JSON字符串
*/
const safeParseJson = (value: any): ImageItem[] | undefined => {
if (!value) return undefined;
if (typeof value === 'string') {
try {
return JSON.parse(value);
} catch {
return undefined;
}
}
if (Array.isArray(value)) {
return value;
}
return undefined;
};
/**
* 初始化数据
*/
const init = () => {
Object.assign(imageData, JSON.parse(JSON.stringify(props.row)));
if (imageData.awardImg) {
activeName.value = 'awardImg';
imageData.awardImg = safeParseJson(imageData.awardImg);
}
if (imageData.knowdgeImg) {
if (!activeName.value) activeName.value = 'knowdgeImg';
imageData.knowdgeImg = safeParseJson(imageData.knowdgeImg);
}
if (imageData.pubCover) {
imageData.pubCover = safeParseJson(imageData.pubCover);
}
if (imageData.cateImg) {
imageData.cateImg = safeParseJson(imageData.cateImg);
}
if (imageData.contentImg) {
imageData.contentImg = safeParseJson(imageData.contentImg);
}
if (imageData.mateCover) {
if (!activeName.value) activeName.value = 'mateCover';
imageData.mateCover = safeParseJson(imageData.mateCover);
}
if (imageData.pubImg) {
imageData.pubImg = safeParseJson(imageData.pubImg);
}
if (imageData.projectApp) {
if (!activeName.value) activeName.value = 'projectApp';
imageData.projectApp = safeParseJson(imageData.projectApp);
}
if (imageData.conclusionBook) {
imageData.conclusionBook = safeParseJson(imageData.conclusionBook);
}
if (imageData.conclusionReport) {
imageData.conclusionReport = safeParseJson(imageData.conclusionReport);
}
if (imageData.otherImg) {
imageData.otherImg = safeParseJson(imageData.otherImg);
}
nextTick(() => {
visible.value = true;
});
};
// 暴露方法供父组件调用
defineExpose({
init,
});
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,74 @@
<template>
<el-dialog v-model="visible" width="90%" append-to-body title="综合表彰材料">
<auth-img v-for="src in imgUrl" :key="src.title" :authSrc="src.url" style="height:1000px;"></auth-img>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import authImg from '/@/components/tools/auth-img.vue'
interface ImageItem {
title: string
url: string
}
// 响应式数据
const visible = ref(false)
const row = ref<any>({})
const pdfSrc = ref('')
const imgUrl = ref<ImageItem[]>([])
// 方法(保留以备将来使用)
// const checkIsImg = (url: string) => {
// if ((url.indexOf('.jpg') == -1)
// && (url.indexOf('.jpeg') == -1)
// && (url.indexOf('.png') == -1)
// && (url.indexOf('.JPG') == -1)
// && (url.indexOf('.JPEG') == -1)
// && (url.indexOf('.PNG') == -1)) {
// return false
// }
// return true
// }
// const downLoadFileForLook = (fileUrl: string, teacherNo: string) => {
// const index = fileUrl.indexOf('fileName')
// const firstIndex = index + 'fileName'.length + 1
// const lastIndex = fileUrl.indexOf('&')
// const fileName = fileUrl.substr(firstIndex, lastIndex - firstIndex)
// pdfSrc.value = "/professional/file/showPdf?fileName=" + fileName + "&type=attachment&teacherNo=" + teacherNo
// }
const init = (rowData: any) => {
row.value = rowData
pdfSrc.value = ''
imgUrl.value = []
nextTick(() => {
const obj: ImageItem = {
title: "",
url: row.value.attachment
}
imgUrl.value.push(obj)
// if(checkIsImg(rowData.attachment)){
// const obj: ImageItem = {
// title: "",
// url: row.value.attachment
// }
// imgUrl.value.push(obj)
// }else{
// downLoadFileForLook(rowData.attachment, rowData.teacherNo)
// }
visible.value = true
})
}
// 暴露方法给父组件
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,27 @@
<!--
- Copyright (c) 2018-2025, cyweb All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- Neither the name of the pig4cloud.com developer nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-->
<template>
<div></div>
</template>
<script setup lang="ts">
// 空组件,保留用于未来扩展
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalacademicdegreeconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="degreeName" label="学位名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalacademicdegreeconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalacademicdegreeconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="学位名称" prop="degreeName">
<el-input
v-model="form.degreeName"
placeholder="请输入学位名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
degreeName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
degreeName: [
{ required: true, message: '请输入学位名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
degreeName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
degreeName: row.degreeName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalacademiceducationtypeconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="name" label="类型名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalacademiceducationtypeconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalacademiceducationtypeconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="类型名称" prop="name">
<el-input
v-model="form.name"
placeholder="请输入类型名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
name: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
name: [
{ required: true, message: '请输入类型名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
name: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
name: row.name || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row :gutter="24">
<el-col :span="4">
<el-collapse v-model="activeNames">
<el-collapse-item title="汇总" name="1">
<div v-for="(item, index) in infoList" :key="index">
{{ item }}
</div>
</el-collapse-item>
</el-collapse>
</el-col>
<el-col :span="20">
<el-tabs v-model="tabsActiveName" @tab-click="handleClick">
<el-tab-pane name="1" label="学历"></el-tab-pane>
<el-tab-pane name="2" label="职称"></el-tab-pane>
<el-tab-pane name="3" label="职业资格"></el-tab-pane>
<el-tab-pane name="4" label="教师资格"></el-tab-pane>
</el-tabs>
<el-table :data="tableData" show-summary style="height: 800px; overflow-y: auto;" border>
<el-table-column prop="deptName" label="部门/资质(人数)" min-width="150" align="center" />
<el-table-column
v-for="(item, index) in tableHead"
:key="index"
:prop="item"
:label="item"
min-width="120"
align="center">
<template #default="scope">
{{ scope.row[item] ? scope.row[item] : 0 }}
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { countInfo } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
import { titleCountInfo } from '/@/api/professional/professionaluser/professionaltitlerelation'
import { quaCountInfo } from '/@/api/professional/professionaluser/professionalqualificationrelation'
import { cerCountInfo } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
const activeNames = ref(['1'])
const tabsActiveName = ref('1')
const tableData = ref<any[]>([])
const tableHead = ref<string[]>([])
const infoList = ref<string[]>([])
const init = async () => {
try {
const res = await countInfo()
tableData.value = res.data.data.tableData || []
tableHead.value = res.data.data.tableHead || []
infoList.value = res.data.data.infoList || []
} catch (error) {
console.error('Failed to load data:', error)
}
}
const handleClick = (tab: any) => {
tableData.value = []
tableHead.value = []
infoList.value = []
if (tab.name === '1') {
init()
} else if (tab.name === '2') {
titleCountInfo().then(res => {
tableData.value = res.data.data.tableData || []
tableHead.value = res.data.data.tableHead || []
infoList.value = res.data.data.infoList || []
})
} else if (tab.name === '3') {
quaCountInfo().then(res => {
tableData.value = res.data.data.tableData || []
tableHead.value = res.data.data.tableHead || []
infoList.value = res.data.data.infoList || []
})
} else if (tab.name === '4') {
cerCountInfo().then(res => {
tableData.value = res.data.data.tableData || []
tableHead.value = res.data.data.tableHead || []
infoList.value = res.data.data.infoList || []
})
}
}
onMounted(() => {
init()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalatstation_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="atStationName" label="类型名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalatstation_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalatstation_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="类型名称" prop="atStationName">
<el-input
v-model="form.atStationName"
placeholder="请输入类型名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalatstation'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
atStationName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
atStationName: [
{ required: true, message: '请输入类型名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
atStationName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
atStationName: row.atStationName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalemploymentnature_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="employmentNatureName" label="用工性质" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalemploymentnature_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalemploymentnature_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="用工性质" prop="employmentNatureName">
<el-input
v-model="form.employmentNatureName"
placeholder="请输入用工性质"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalemploymentnature'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
employmentNatureName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
employmentNatureName: [
{ required: true, message: '请输入用工性质', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
employmentNatureName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
employmentNatureName: row.employmentNatureName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,249 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalmajorstation_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="majorStationName" label="职称职务" min-width="180" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalmajorstation_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalmajorstation_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="职称职务" prop="majorStationName">
<el-input
v-model="form.majorStationName"
placeholder="请输入职称职务"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalmajorstation'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
majorStationName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
majorStationName: [
{ required: true, message: '请输入职称职务', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
majorStationName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
majorStationName: row.majorStationName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalpaperconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="typeName" label="论文类型" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalpaperconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalpaperconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="论文类型" prop="typeName">
<el-input
v-model="form.typeName"
placeholder="请输入论文类型"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalpaperconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
typeName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
typeName: [
{ required: true, message: '请输入论文类型', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
typeName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
typeName: row.typeName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,247 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalpartybranch_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="name" label="类别名字" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注信息" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
icon="Edit"
v-if="permissions.professional_professionalpartybranch_edit"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-button
type="danger"
link
icon="Delete"
v-if="permissions.professional_professionalpartybranch_del"
@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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="类别名字" prop="name">
<el-input
v-model="form.name"
placeholder="请输入类别名字"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalpartybranch'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
name: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
name: [
{ required: true, message: '请输入类别名字', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
name: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
name: row.name || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,137 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter(search)"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="用户名" prop="realName">
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入用户名"
/>
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter(search)" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realName" label="用户名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="oldBranchName" label="原支部名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="branchName" label="现支部名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="partyFee" label="党费" width="120" align="center">
<template #default="scope">
{{ scope.row.partyFee ? Number(scope.row.partyFee).toFixed(2) : '-' }}
</template>
</el-table-column>
<el-table-column prop="changeTime" label="变动时间" width="180" align="center" />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/professionaluser/professionalpartychange'
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
teacherNo: '',
realName: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 查询
const handleFilter = () => {
getDataList()
}
// 重置查询
const resetQuery = () => {
Object.assign(search, {
teacherNo: '',
realName: ''
})
getDataList()
}
// 初始化
onMounted(() => {
getDataList()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalqualificationconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="levelName" label="等级名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalqualificationconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalqualificationconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="等级名称" prop="levelName">
<el-input
v-model="form.levelName"
placeholder="请输入等级名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalqualificationconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
levelName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
levelName: [
{ required: true, message: '请输入等级名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
levelName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
levelName: row.levelName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,283 @@
<template>
<el-dialog v-model="visible" title="编辑职业资格" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
:model="dataForm"
:rules="formRules"
label-width="120px"
>
<el-form-item label="职业工种" prop="worker">
<el-select
v-model="dataForm.worker"
filterable
clearable
placeholder="请选择职业工种"
style="width: 100%"
>
<el-option
v-for="item in workTypeList"
:key="item.id"
:label="item.workName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="等级" prop="qualificationConfigId">
<el-select
v-model="dataForm.qualificationConfigId"
filterable
clearable
placeholder="请选择等级"
style="width: 100%"
>
<el-option
v-for="item in qualificationList"
:key="item.id"
:label="item.levelName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="取证时间" prop="certificateTime">
<el-date-picker
v-model="dataForm.certificateTime"
type="date"
placeholder="请选择取证时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="证书编号" prop="certificateNumber">
<el-input
v-model="dataForm.certificateNumber"
placeholder="请输入证书编号"
clearable
/>
</el-form-item>
<el-form-item label="材料1" prop="materialA">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileList"
:on-success="materiaUploadSuccess"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</template>
</el-upload>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { Session } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
import { putObj } from '/@/api/professional/professionaluser/professionalqualificationrelation'
// Props
const props = defineProps<{
visible?: boolean
}>()
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
// 消息提示
const message = useMessage()
// 表单引用
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 表单数据
const dataForm = reactive({
worker: '',
qualificationConfigId: '',
certificateTime: '',
certificateNumber: '',
materialA: '',
evidenceA: '',
state: '',
teacherNo: '',
id: ''
})
// 表单验证规则
const formRules = {
worker: [
{ required: true, message: '请选择职业工种', trigger: 'change' }
],
qualificationConfigId: [
{ required: true, message: '请选择等级', trigger: 'change' }
],
certificateTime: [
{ required: true, message: '请选择取证时间', trigger: 'change' }
],
certificateNumber: [
{ required: true, message: '请输入证书编号', trigger: 'blur' }
]
}
// 基础信息
const baseInfoAbout = reactive<{
stationTypeList: any[]
atStationList: any[]
teacherTypeList: any[]
employmentNatureList: any[]
stationLevelList: any[]
stationDutyLevelList: any[]
workTypeList: any[]
proTitleList: any[]
majorStationList: any[]
qualificationList: any[]
partBranchList: any[]
}>({
stationTypeList: [],
atStationList: [],
teacherTypeList: [],
employmentNatureList: [],
stationLevelList: [],
stationDutyLevelList: [],
workTypeList: [],
proTitleList: [],
majorStationList: [],
qualificationList: [],
partBranchList: []
})
// 上传相关
const url = ref('')
const fileList = ref<any[]>([])
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 显示表单标志
const showForm = ref(false)
// 计算属性:职业工种列表
const workTypeList = computed(() => baseInfoAbout.workTypeList as any[])
// 计算属性:资格列表
const qualificationList = computed(() => baseInfoAbout.qualificationList as any[])
// 初始化字典数据
const initDicData = async () => {
try {
const response = await getAllInfoAboutList()
const map = response.data.data
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
baseInfoAbout.atStationList = map['atStationList'] || []
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
baseInfoAbout.workTypeList = map['workTypeList'] || []
baseInfoAbout.proTitleList = map['proTitleList'] || []
baseInfoAbout.majorStationList = map['majorStationList'] || []
baseInfoAbout.qualificationList = map['qualificationList'] || []
baseInfoAbout.partBranchList = map['partBranchList'] || []
visible.value = true
} catch (error) {
// 获取字典数据失败
}
}
// 文件上传成功
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
dataForm.evidenceA = response.data.url
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=3`
fileList.value = []
Object.assign(dataForm, {
worker: row.worker || '',
qualificationConfigId: row.qualificationConfigId || '',
certificateTime: row.certificateTime || '',
certificateNumber: row.certificateNumber || '',
materialA: row.materialA || '',
evidenceA: row.evidenceA || '',
state: row.state || '',
teacherNo: row.teacherNo || '',
id: row.id || ''
})
showForm.value = true
initDicData()
}
// 提交表单
const dialogSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
// 初始化方法(保持兼容性)
const init = () => {
// 新版本不再需要初始化配置
}
// 暴露方法
defineExpose({
openDialog,
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,488 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 图表统计 -->
<div style="text-align: right; margin-bottom: 20px;">
<el-collapse accordion @change="initChartOption">
<el-collapse-item title="资格等级统计">
<div style="width: 100%">
<el-row :gutter="24">
<el-col :span="12">
<v-chart ref="chartRef" style="width: 100%; height: 400px;" :option="chartOption" />
</el-col>
<el-col :span="12">
<el-table :data="chartData" border show-summary style="width: 100%">
<el-table-column prop="name" label="等级" width="180" align="center" />
<el-table-column prop="value" label="人数" width="180" align="center" />
<el-table-column prop="rate" label="占比" width="180" align="center" />
</el-table>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</div>
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="审核状态" prop="state">
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
v-for="item in professionalState"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalqualificationrelation_add">
</el-button>
<el-button
type="success"
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="state" label="审核状态" width="120" align="center">
<template #default="scope">
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="qualificationConfigId" label="资格等级" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getQualificationLevelName(scope.row.qualificationConfigId) }}
</template>
</el-table-column>
<el-table-column prop="worker" label="工种" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getWorkTypeName(scope.row.worker) }}
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column label="证明材料" width="120" align="center">
<template #default="scope">
<el-button
v-if="scope.row.evidenceA && scope.row.evidenceA !== ''"
type="primary"
link
@click="handlePreview(scope.row.srcList)">查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
icon="Edit"
v-if="permissions.professional_professionalqualificationrelation_edit && (scope.row.state === '0' || scope.row.state === '-2')"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-button
type="success"
link
icon="Check"
v-if="permissions.professional_professionalqualificationrelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
v-if="permissions.professional_professionalqualificationrelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
@click="changeState(scope.row, -2)">驳回
</el-button>
<el-button
type="danger"
link
icon="Delete"
v-if="permissions.professional_professionalqualificationrelation_del"
@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="职业资格材料" append-to-body width="90%">
<auth-img
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
style="height:1000px;"
/>
</el-dialog>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } 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 VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { PieChart } from 'echarts/charts'
import { TooltipComponent, LegendComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import {
fetchList,
putObj,
delObj,
getChartOption,
exportExcel
} from '/@/api/professional/professionaluser/professionalqualificationrelation'
import { getLevelList } from '/@/api/professional/rsbase/professionalqualificationconfig'
import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype'
import { defineAsyncComponent } from 'vue'
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
import DataForm from './form.vue'
import ProfessionalBackResaon from '/@/views/professional/common/professional-back-resaon.vue'
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 使用 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 { professional_state: professionalState } = useDict('professional_state')
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const multiDialogRef = ref()
const dataFormRef = ref()
const backReasonRef = ref()
const chartRef = ref()
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
state: '',
teacherNo: '',
realName: ''
})
// 图表数据
const chartOption = ref<any>({})
const chartData = ref<any[]>([])
// 材料预览
const dialogVisible = ref(false)
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
// 资格等级和工种列表
const qualificationLevelList = ref<any[]>([])
const workTypeList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
const records = response.data.records || []
// 处理证明材料列表
records.forEach((v: any) => {
v.srcList = []
if (v.evidenceA != null) {
v.srcList.push(v.evidenceA)
}
})
return {
data: {
records,
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 初始化图表
const initChartOption = async () => {
try {
const response = await getChartOption()
chartOption.value = response.data.data.option || {}
let total = 0
if (chartOption.value.series && chartOption.value.series[0] && chartOption.value.series[0].data) {
chartOption.value.series[0].data.forEach((item: any) => {
total += item.value || 0
})
chartData.value = []
chartOption.value.series[0].data.forEach((item: any) => {
const rate = total > 0 ? Number((item.value / total * 100).toFixed(1)) : 0
chartData.value.push({
name: item.name,
value: item.value,
rate: `${rate}%`
})
})
}
} catch (error) {
// Failed to load chart data
}
}
// 预览材料
const handlePreview = (list: string[]) => {
imgUrl.value = []
nextTick(() => {
list.forEach(v => {
imgUrl.value.push({
title: '',
url: v
})
})
nextTick(() => {
dialogVisible.value = true
})
})
}
// 审核状态变更
const changeState = (row: any, val: number) => {
if (val === 1) {
// 通过
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
}
}).catch(() => {})
} else if (val === -2) {
// 驳回
backReasonRef.value?.init(row, 'qua')
}
}
// 查询
const handleFilter = () => {
getDataList()
}
// 重置查询
const resetQuery = () => {
Object.assign(search, {
state: '',
teacherNo: '',
realName: ''
})
getDataList()
}
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(3)
}
// 打开编辑窗口
const handleEdit = (row: any) => {
dataFormRef.value?.openDialog(row)
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => {
try {
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 (error) {
// Failed to delete
}
}).catch(() => {})
}
// 导出
const handleDownLoadWord = async () => {
exportLoading.value = true
try {
const response: any = await exportExcel(search)
const blob = new Blob([response as BlobPart])
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) {
message.error('导出失败')
} finally {
exportLoading.value = false
}
}
// 获取资格等级名称
const getQualificationLevelName = (id: string | number) => {
const item = qualificationLevelList.value.find((item: any) => item.id === id)
return item ? item.levelName : '-'
}
// 获取工种名称
const getWorkTypeName = (id: string | number) => {
const item = workTypeList.value.find((item: any) => item.id === id)
return item ? item.workName : '-'
}
// 加载字典数据
const loadDictData = async () => {
try {
// 加载资格等级列表
const levelRes = await getLevelList()
if (levelRes && levelRes.data) {
qualificationLevelList.value = levelRes.data
}
// 加载工种列表
const workRes = await getWorkTypeList()
if (workRes && workRes.data) {
workTypeList.value = workRes.data
}
} catch (error) {
// Failed to load dict data
}
}
// 初始化
onMounted(async () => {
await loadDictData()
getDataList()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalstationdutylevel_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stationDutyLevelName" label="职务级别名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalstationdutylevel_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalstationdutylevel_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="职务级别名称" prop="stationDutyLevelName">
<el-input
v-model="form.stationDutyLevelName"
placeholder="请输入职务级别名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalstationdutylevel'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
stationDutyLevelName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
stationDutyLevelName: [
{ required: true, message: '请输入职务级别名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
stationDutyLevelName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
stationDutyLevelName: row.stationDutyLevelName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalstationtype_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="typeName" label="岗位类别名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalstationtype_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalstationtype_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="岗位类别名称" prop="typeName">
<el-input
v-model="form.typeName"
placeholder="请输入岗位类别名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalstationtype'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
typeName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
typeName: [
{ required: true, message: '请输入岗位类别名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
typeName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
typeName: row.typeName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,387 @@
<template>
<el-dialog v-model="visible" title="编辑学历学位" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
:model="dataForm"
:rules="formRules"
label-width="120px"
>
<el-form-item label="毕业时间" prop="graduateTime">
<el-date-picker
v-model="dataForm.graduateTime"
type="date"
placeholder="请选择毕业时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="教育类型" prop="type">
<el-select
v-model="dataForm.type"
filterable
clearable
placeholder="请选择教育类型"
style="width: 100%"
>
<el-option
v-for="item in educationTypeList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="学历" prop="qualificationConfigId">
<el-select
v-model="dataForm.qualificationConfigId"
filterable
clearable
placeholder="请选择学历"
style="width: 100%"
>
<el-option
v-for="item in qualificationList"
:key="item.id"
:label="item.qualificationName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="学位" prop="degreeConfigId">
<el-select
v-model="dataForm.degreeConfigId"
filterable
clearable
placeholder="请选择学位"
style="width: 100%"
>
<el-option
v-for="item in degreeList"
:key="item.id"
:label="item.degreeName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="毕业学校" prop="graduateSchool">
<el-input
v-model="dataForm.graduateSchool"
placeholder="请输入毕业学校"
clearable
/>
</el-form-item>
<el-form-item label="所学专业" prop="major">
<el-input
v-model="dataForm.major"
placeholder="请输入所学专业"
clearable
/>
</el-form-item>
<el-form-item label="证书编码" prop="certificateNumber">
<el-input
v-model="dataForm.certificateNumber"
placeholder="请输入证书编码"
clearable
/>
</el-form-item>
<el-form-item label="学历证书" prop="materialA">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileList"
:on-success="materiaUploadSuccess"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</template>
</el-upload>
</el-form-item>
<el-form-item label="学位证书" prop="materialB">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileListB"
:on-success="materiaUploadSuccessB"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</template>
</el-upload>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { Session } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
import { putObj } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
import { getAllTypeList } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig'
import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig'
import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
// Props
const props = defineProps<{
visible?: boolean
}>()
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
// 消息提示
const message = useMessage()
// 表单引用
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 表单数据
const dataForm = reactive({
graduateTime: '',
type: '',
qualificationConfigId: '',
degreeConfigId: '',
graduateSchool: '',
major: '',
certificateNumber: '',
materialA: '',
materialB: '',
qualificationImg: '',
degreeImg: '',
state: '',
teacherNo: '',
id: ''
})
// 表单验证规则
const formRules = {
graduateTime: [
{ required: true, message: '请选择毕业时间', trigger: 'change' }
],
type: [
{ required: true, message: '请选择教育类型', trigger: 'change' }
],
graduateSchool: [
{ required: true, message: '请输入毕业学校', trigger: 'blur' }
],
major: [
{ required: true, message: '请输入所学专业', trigger: 'blur' }
],
certificateNumber: [
{ required: true, message: '请输入证书编码', trigger: 'blur' }
]
}
// 下拉列表数据
const educationTypeList = ref<any[]>([])
const qualificationList = ref<any[]>([])
const degreeList = ref<any[]>([])
// 基础信息
const baseInfoAbout = reactive<{
stationTypeList: any[]
atStationList: any[]
teacherTypeList: any[]
employmentNatureList: any[]
stationLevelList: any[]
stationDutyLevelList: any[]
workTypeList: any[]
proTitleList: any[]
majorStationList: any[]
qualificationList: any[]
partBranchList: any[]
}>({
stationTypeList: [],
atStationList: [],
teacherTypeList: [],
employmentNatureList: [],
stationLevelList: [],
stationDutyLevelList: [],
workTypeList: [],
proTitleList: [],
majorStationList: [],
qualificationList: [],
partBranchList: []
})
// 上传相关
const url = ref('')
const fileList = ref<any[]>([])
const fileListB = ref<any[]>([])
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 显示表单标志
const showForm = ref(false)
// 初始化字典数据
const initDicData = async () => {
try {
const [response, eduRes, quaRes, degRes] = await Promise.all([
getAllInfoAboutList(),
getAllTypeList(),
getQualificationList(),
getDegreeList()
])
const map = response.data.data
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
baseInfoAbout.atStationList = map['atStationList'] || []
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
baseInfoAbout.workTypeList = map['workTypeList'] || []
baseInfoAbout.proTitleList = map['proTitleList'] || []
baseInfoAbout.majorStationList = map['majorStationList'] || []
baseInfoAbout.qualificationList = map['qualificationList'] || []
baseInfoAbout.partBranchList = map['partBranchList'] || []
// 获取教育类型列表
if (eduRes && eduRes.data) {
educationTypeList.value = Array.isArray(eduRes.data)
? eduRes.data
: (eduRes.data.records || eduRes.data.list || [])
}
// 获取学历列表
if (quaRes && quaRes.data) {
qualificationList.value = Array.isArray(quaRes.data)
? quaRes.data
: (quaRes.data.records || quaRes.data.list || [])
}
// 获取学位列表
if (degRes && degRes.data) {
degreeList.value = Array.isArray(degRes.data)
? degRes.data
: (degRes.data.records || degRes.data.list || [])
}
visible.value = true
} catch (error) {
// 获取字典数据失败
}
}
// 文件上传成功 - 学历证书
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
dataForm.qualificationImg = response.data.url
}
// 文件上传成功 - 学位证书
const materiaUploadSuccessB = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
dataForm.degreeImg = response.data.url
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=1`
fileList.value = []
fileListB.value = []
Object.assign(dataForm, {
graduateTime: row.graduateTime || '',
type: row.type || '',
qualificationConfigId: row.qualificationConfigId || '',
degreeConfigId: row.degreeConfigId || '',
graduateSchool: row.graduateSchool || '',
major: row.major || '',
certificateNumber: row.certificateNumber || '',
materialA: row.materialA || '',
materialB: row.materialB || '',
qualificationImg: row.qualificationImg || '',
degreeImg: row.degreeImg || '',
state: row.state || '',
teacherNo: row.teacherNo || '',
id: row.id || ''
})
showForm.value = true
initDicData()
}
// 提交表单
const dialogSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
// 初始化方法(保持兼容性)
const init = () => {
// 新版本不再需要初始化配置
}
// 暴露方法
defineExpose({
openDialog,
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,516 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 图表统计 -->
<div style="text-align: right; margin-bottom: 20px;">
<el-collapse accordion @change="initChartOption">
<el-collapse-item title="学历统计">
<div style="width: 100%">
<el-row :gutter="24">
<el-col :span="12">
<v-chart ref="chartRef" style="width: 100%; height: 400px;" :option="chartOption" />
</el-col>
<el-col :span="12">
<el-table :data="chartData" border show-summary style="width: 100%">
<el-table-column prop="xl" label="学历" width="180" align="center" />
<el-table-column prop="total" label="人数" width="180" align="center" />
<el-table-column prop="rate" label="占比" width="180" align="center" />
</el-table>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</div>
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="审核状态" prop="state">
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
v-for="item in professionalState"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalteacheracademicrelation_add">
</el-button>
<el-button
type="success"
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="state" label="审核状态" width="120" align="center">
<template #default="scope">
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="graduateTime" label="毕业时间" width="180" align="center" />
<el-table-column prop="degreeConfigId" label="学位" min-width="120" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getDegreeName(scope.row.degreeConfigId) }}
</template>
</el-table-column>
<el-table-column prop="qualificationConfigId" label="学历" min-width="120" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getQualificationName(scope.row.qualificationConfigId) }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="学历证书附件" width="130" align="center">
<template #default="scope">
<el-button
v-if="scope.row.qualificationImg && scope.row.qualificationImg !== ''"
type="primary"
link
@click="handlePreview(scope.row.qiList, 1)">查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="学位证书附件" width="130" align="center">
<template #default="scope">
<el-button
v-if="scope.row.degreeImg && scope.row.degreeImg !== ''"
type="primary"
link
@click="handlePreview(scope.row.deList, 2)">查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
icon="Edit"
v-if="permissions.professional_professionalteacheracademicrelation_edit && (scope.row.state === '0' || scope.row.state === '-2')"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-button
type="success"
link
icon="Check"
v-if="permissions.professional_professionalteacheracademicrelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
v-if="permissions.professional_professionalteacheracademicrelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
@click="changeState(scope.row, -2)">驳回
</el-button>
<el-button
type="danger"
link
icon="Delete"
v-if="permissions.professional_professionalteacheracademicrelation_del"
@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="dialogTitle" append-to-body width="90%">
<auth-img
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
style="height:1000px;"
/>
</el-dialog>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } 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 VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { PieChart } from 'echarts/charts'
import { TooltipComponent, LegendComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import {
fetchList,
putObj,
delObj,
getChartOption,
exportExcel
} from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig'
import { defineAsyncComponent } from 'vue'
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
import DataForm from './form.vue'
import ProfessionalBackResaon from '/@/views/professional/common/professional-back-resaon.vue'
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 使用 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 { professional_state: professionalState } = useDict('professional_state')
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const multiDialogRef = ref()
const dataFormRef = ref()
const backReasonRef = ref()
const chartRef = ref()
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
state: '',
teacherNo: '',
realName: ''
})
// 图表数据
const chartOption = ref<any>({})
const chartData = ref<any[]>([])
// 材料预览
const dialogVisible = ref(false)
const dialogTitle = ref('')
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
// 学位和学历列表
const degreeList = ref<any[]>([])
const qualificationList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
const records = response.data.records || []
// 处理证明材料列表
records.forEach((v: any) => {
if (v.qualificationImg != null) {
v.qiList = [v.qualificationImg]
} else {
v.qiList = []
}
if (v.degreeImg != null) {
v.deList = [v.degreeImg]
} else {
v.deList = []
}
})
return {
data: {
records,
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 初始化图表
const initChartOption = async () => {
try {
const response = await getChartOption()
chartOption.value = response.data.data || {}
// 处理图表数据(如果有的话)
if (chartOption.value.series && chartOption.value.series[0] && chartOption.value.series[0].data) {
let total = 0
chartOption.value.series[0].data.forEach((item: any) => {
total += item.value || 0
})
chartData.value = []
chartOption.value.series[0].data.forEach((item: any) => {
const rate = total > 0 ? Number((item.value / total * 100).toFixed(1)) : 0
chartData.value.push({
xl: item.name,
total: item.value,
rate: `${rate}%`
})
})
}
} catch (error) {
// Failed to load chart data
}
}
// 预览材料
const handlePreview = (list: string[], type: number) => {
imgUrl.value = []
nextTick(() => {
list.forEach(v => {
imgUrl.value.push({
title: '',
url: v
})
})
if (type === 1) {
dialogTitle.value = '学历证书附件'
} else if (type === 2) {
dialogTitle.value = '学位证书附件'
}
nextTick(() => {
dialogVisible.value = true
})
})
}
// 审核状态变更
const changeState = (row: any, val: number) => {
if (val === 1) {
// 通过
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
}
}).catch(() => {})
} else if (val === -2) {
// 驳回
backReasonRef.value?.init(row, 'education')
}
}
// 查询
const handleFilter = () => {
getDataList()
}
// 重置查询
const resetQuery = () => {
Object.assign(search, {
state: '',
teacherNo: '',
realName: ''
})
getDataList()
}
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(1)
}
// 打开编辑窗口
const handleEdit = (row: any) => {
dataFormRef.value?.openDialog(row)
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => {
try {
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 (error) {
// Failed to delete
}
}).catch(() => {})
}
// 导出
const handleDownLoadWord = async () => {
exportLoading.value = true
try {
const response: any = await exportExcel(search)
const blob = new Blob([response as BlobPart])
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) {
message.error('导出失败')
} finally {
exportLoading.value = false
}
}
// 获取学位名称
const getDegreeName = (id: string | number) => {
const item = degreeList.value.find((item: any) => item.id === id)
return item ? item.degreeName : '-'
}
// 获取学历名称
const getQualificationName = (id: string | number) => {
const item = qualificationList.value.find((item: any) => item.id === id)
return item ? item.qualificationName : '-'
}
// 加载字典数据
const loadDictData = async () => {
try {
// 加载学位列表
const degreeRes = await getDegreeList()
if (degreeRes && degreeRes.data) {
degreeList.value = degreeRes.data
}
// 加载学历列表
const qualRes = await getQualificationList()
if (qualRes && qualRes.data) {
qualificationList.value = qualRes.data
}
} catch (error) {
// Failed to load dict data
}
}
// 初始化
onMounted(async () => {
await loadDictData()
getDataList()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalteachercertificateconf_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="cretificateName" label="资格证名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalteachercertificateconf_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalteachercertificateconf_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="资格证名称" prop="cretificateName">
<el-input
v-model="form.cretificateName"
placeholder="请输入资格证名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalteachercertificateconf'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
cretificateName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
cretificateName: [
{ required: true, message: '请输入资格证名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
cretificateName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
cretificateName: row.cretificateName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,303 @@
<template>
<el-dialog v-model="visible" title="编辑教师资格证" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
:model="dataForm"
:rules="formRules"
label-width="120px"
>
<el-form-item label="类型" prop="certificateConfId">
<el-select
v-model="dataForm.certificateConfId"
filterable
clearable
placeholder="请选择类型"
style="width: 100%"
>
<el-option
v-for="item in teacherCertificateList"
:key="item.id"
:label="item.cretificateName || item.certificateName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="取证时间" prop="certificateTime">
<el-date-picker
v-model="dataForm.certificateTime"
type="date"
placeholder="请选择取证时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="证书编号" prop="certificateNumber">
<el-input
v-model="dataForm.certificateNumber"
placeholder="请输入证书编号"
clearable
/>
</el-form-item>
<el-form-item label="材料1" prop="materialA">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileList"
:on-success="materiaUploadSuccess"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</template>
</el-upload>
</el-form-item>
<el-form-item label="材料2" prop="materialB">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileListB"
:on-success="materiaUploadSuccessB"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</template>
</el-upload>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { Session } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
import { putObj } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
import { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf'
// Props
const props = defineProps<{
visible?: boolean
}>()
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
// 消息提示
const message = useMessage()
// 表单引用
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 表单数据
const dataForm = reactive({
certificateConfId: '',
certificateTime: '',
certificateNumber: '',
materialA: '',
materialB: '',
evidenceA: '',
evidenceB: '',
state: '',
teacherNo: '',
id: ''
})
// 表单验证规则
const formRules = {
certificateConfId: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
certificateTime: [
{ required: true, message: '请选择取证时间', trigger: 'change' }
],
certificateNumber: [
{ required: true, message: '请输入证书编号', trigger: 'blur' }
]
}
// 教师资格证列表
const teacherCertificateList = ref<any[]>([])
// 基础信息
const baseInfoAbout = reactive<{
stationTypeList: any[]
atStationList: any[]
teacherTypeList: any[]
employmentNatureList: any[]
stationLevelList: any[]
stationDutyLevelList: any[]
workTypeList: any[]
proTitleList: any[]
majorStationList: any[]
qualificationList: any[]
partBranchList: any[]
}>({
stationTypeList: [],
atStationList: [],
teacherTypeList: [],
employmentNatureList: [],
stationLevelList: [],
stationDutyLevelList: [],
workTypeList: [],
proTitleList: [],
majorStationList: [],
qualificationList: [],
partBranchList: []
})
// 上传相关
const url = ref('')
const fileList = ref<any[]>([])
const fileListB = ref<any[]>([])
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 显示表单标志
const showForm = ref(false)
// 初始化字典数据
const initDicData = async () => {
try {
const [response, certResponse] = await Promise.all([
getAllInfoAboutList(),
getTeacherCertificateList()
])
const map = response.data.data
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
baseInfoAbout.atStationList = map['atStationList'] || []
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
baseInfoAbout.workTypeList = map['workTypeList'] || []
baseInfoAbout.proTitleList = map['proTitleList'] || []
baseInfoAbout.majorStationList = map['majorStationList'] || []
baseInfoAbout.qualificationList = map['qualificationList'] || []
baseInfoAbout.partBranchList = map['partBranchList'] || []
// 获取教师资格证列表
if (certResponse && certResponse.data) {
teacherCertificateList.value = Array.isArray(certResponse.data)
? certResponse.data
: (certResponse.data.records || certResponse.data.list || [])
}
visible.value = true
} catch (error) {
// 获取字典数据失败
}
}
// 文件上传成功 - 材料A
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
dataForm.evidenceA = response.data.url
}
// 文件上传成功 - 材料B
const materiaUploadSuccessB = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
dataForm.evidenceB = response.data.url
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=0`
fileList.value = []
fileListB.value = []
Object.assign(dataForm, {
certificateConfId: row.certificateConfId || '',
certificateTime: row.certificateTime || '',
certificateNumber: row.certificateNumber || '',
materialA: row.materialA || '',
materialB: row.materialB || '',
evidenceA: row.evidenceA || '',
evidenceB: row.evidenceB || '',
state: row.state || '',
teacherNo: row.teacherNo || '',
id: row.id || ''
})
showForm.value = true
initDicData()
}
// 提交表单
const dialogSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
// 初始化方法(保持兼容性)
const init = () => {
// 新版本不再需要初始化配置
}
// 暴露方法
defineExpose({
openDialog,
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,406 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="审核状态" prop="state">
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
v-for="item in professionalState"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalteachercertificaterelation_add">
</el-button>
<el-button
type="success"
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="state" label="审核状态" width="120" align="center">
<template #default="scope">
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="certificateConfId" label="关联资格证书" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getCertificateName(scope.row.certificateConfId) }}
</template>
</el-table-column>
<el-table-column prop="certificateNumber" label="证书编号" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="证明材料" width="120" align="center">
<template #default="scope">
<el-button
v-if="scope.row.evidenceA && scope.row.evidenceA !== ''"
type="primary"
link
@click="handlePreview(scope.row.srcList)">查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
icon="Edit"
v-if="permissions.professional_professionalteachercertificaterelation_edit && (scope.row.state === '0' || scope.row.state === '-2')"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-button
type="success"
link
icon="Check"
v-if="permissions.professional_professionalteachercertificaterelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
v-if="permissions.professional_professionalteachercertificaterelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
@click="changeState(scope.row, -2)">驳回
</el-button>
<el-button
type="danger"
link
icon="Delete"
v-if="permissions.professional_professionalteachercertificaterelation_del"
@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="教师资格材料" append-to-body width="90%">
<auth-img
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
style="height:1000px;"
/>
</el-dialog>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } 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 { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf'
import {
fetchList,
putObj,
delObj,
exportExcel
} from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
import { defineAsyncComponent } from 'vue'
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
// 使用 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 { professional_state: professionalState } = useDict('professional_state')
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const multiDialogRef = ref()
const dataFormRef = ref()
const backReasonRef = ref()
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
state: '',
teacherNo: '',
realName: ''
})
// 材料预览
const dialogVisible = ref(false)
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
// 证书列表
const certificateList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
const records = response.data.records || []
// 处理证明材料列表
records.forEach((v: any) => {
v.srcList = []
if (v.evidenceA != null) {
v.srcList.push(v.evidenceA)
}
})
return {
data: {
records,
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 预览材料
const handlePreview = (list: string[]) => {
imgUrl.value = []
nextTick(() => {
list.forEach(v => {
imgUrl.value.push({
title: '',
url: v
})
})
nextTick(() => {
dialogVisible.value = true
})
})
}
// 审核状态变更
const changeState = (row: any, val: number) => {
if (val === 1) {
// 通过
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
}
}).catch(() => {})
} else if (val === -2) {
// 驳回
backReasonRef.value?.init(row, 'cer')
}
}
// 查询
const handleFilter = () => {
getDataList()
}
// 重置查询
const resetQuery = () => {
Object.assign(search, {
state: '',
teacherNo: '',
realName: ''
})
getDataList()
}
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(0)
}
// 打开编辑窗口
const handleEdit = (row: any) => {
dataFormRef.value?.openDialog(row)
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => {
try {
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 (error) {
// Failed to delete
}
}).catch(() => {})
}
// 导出
const handleDownLoadWord = async () => {
exportLoading.value = true
try {
const response: any = await exportExcel(search)
const blob = new Blob([response as BlobPart])
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) {
message.error('导出失败')
} finally {
exportLoading.value = false
}
}
// 获取证书名称
const getCertificateName = (id: string | number) => {
const item = certificateList.value.find((item: any) => item.id === id)
return item ? item.cretificateName : '-'
}
// 加载字典数据
const loadDictData = async () => {
try {
// 加载证书列表
const certRes = await getTeacherCertificateList()
if (certRes && certRes.data) {
certificateList.value = certRes.data
}
} catch (error) {
// Failed to load dict data
}
}
// 初始化
onMounted(async () => {
await loadDictData()
getDataList()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,252 @@
<template>
<el-dialog v-model="visible" :title="title" width="80%" append-to-body :close-on-click-modal="false" destroy-on-close>
<el-row>
<el-form
ref="formRef"
:model="dataForm"
:rules="dataRules"
label-width="120px"
>
<el-col :span="12">
<el-form-item label="荣誉" prop="honor">
<el-input v-model="dataForm.honor" placeholder="请输入荣誉" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="表彰单位" prop="honorCompany">
<el-input v-model="dataForm.honorCompany" placeholder="请输入表彰单位" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年份" prop="year">
<el-input-number
v-model="dataForm.year"
:controls="false"
:min="1900"
:max="2100"
placeholder="请输入年份"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="证明材料" prop="materialA">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:on-success="materiaUploadSuccess"
:file-list="fileList"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</template>
</el-upload>
</el-form-item>
</el-col>
</el-form>
</el-row>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button @click="dialogSubmit" type="primary" :loading="submitLoading"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { Session } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
import { putObj } from '/@/api/professional/professionaluser/professionalteacherhonor'
// Props
const props = defineProps<{
visible?: boolean
}>()
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
// 消息提示
const message = useMessage()
// 表单引用
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 表单数据
const dataForm = reactive({
honor: '',
honorCompany: '',
year: null as number | null,
materialA: '',
attachment: '',
state: '',
teacherNo: '',
teacherName: '',
id: ''
})
// 表单验证规则
const dataRules = {
honor: [
{ required: true, message: '请填写荣誉', trigger: 'change' }
],
honorCompany: [
{ required: true, message: '请填写表彰单位', trigger: 'change' }
],
year: [
{ required: true, message: '请填写年份', trigger: 'change' }
]
}
// 基础信息
const baseInfoAbout = reactive<{
stationTypeList: any[]
atStationList: any[]
teacherTypeList: any[]
employmentNatureList: any[]
stationLevelList: any[]
stationDutyLevelList: any[]
workTypeList: any[]
proTitleList: any[]
majorStationList: any[]
qualificationList: any[]
partBranchList: any[]
}>({
stationTypeList: [],
atStationList: [],
teacherTypeList: [],
employmentNatureList: [],
stationLevelList: [],
stationDutyLevelList: [],
workTypeList: [],
proTitleList: [],
majorStationList: [],
qualificationList: [],
partBranchList: []
})
// 上传相关
const url = ref('')
const fileList = ref<any[]>([])
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 显示表单标志
const showForm = ref(false)
// 标题
const title = ref('')
// 初始化字典数据
const initDicData = async () => {
try {
const response = await getAllInfoAboutList()
const map = response.data.data
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
baseInfoAbout.atStationList = map['atStationList'] || []
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
baseInfoAbout.workTypeList = map['workTypeList'] || []
baseInfoAbout.proTitleList = map['proTitleList'] || []
baseInfoAbout.majorStationList = map['majorStationList'] || []
baseInfoAbout.qualificationList = map['qualificationList'] || []
baseInfoAbout.partBranchList = map['partBranchList'] || []
visible.value = true
} catch (error) {
// 获取字典数据失败
}
}
// 文件上传成功
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
dataForm.attachment = response.data.url
}
// 打开对话框
const openDialog = (row: any) => {
title.value = row.teacherName || ''
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=4`
fileList.value = []
Object.assign(dataForm, {
honor: row.honor || '',
honorCompany: row.honorCompany || '',
year: row.year || null,
materialA: row.materialA || '',
attachment: row.attachment || '',
state: row.state || '',
teacherNo: row.teacherNo || '',
teacherName: row.teacherName || '',
id: row.id || ''
})
showForm.value = true
initDicData()
}
// 提交表单
const dialogSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
// 初始化方法(保持兼容性)
const init = () => {
// 新版本不再需要初始化配置
}
// 暴露方法
defineExpose({
openDialog,
init
})
</script>
<style scoped lang="scss">
.dialog-footer {
text-align: right;
}
</style>

View File

@@ -0,0 +1,350 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="审核状态" prop="state">
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
v-for="item in professionalState"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="姓名" prop="teacherName">
<el-input
v-model="search.teacherName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalteacherhonor_add">
</el-button>
<el-button
type="success"
icon="Download"
@click="handleDownLoadWord"
:loading="exportLoading">导出
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="state" label="审核状态" width="120" align="center">
<template #default="scope">
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="teacherName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="honor" label="荣誉" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="honorCompany" label="表彰单位" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column label="证明材料" width="120" align="center">
<template #default="scope">
<el-button
v-if="scope.row.attachment && scope.row.attachment !== ''"
type="primary"
link
@click="showEdvince(scope.row)">
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
icon="Edit"
v-if="permissions.professional_professionalteacherhonor_edit && (scope.row.state === '0' || scope.row.state === '-2')"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-button
type="success"
link
icon="Check"
v-if="permissions.professional_professionalteacherhonor_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
v-if="permissions.professional_professionalteacherhonor_exam && (scope.row.state === '0' || scope.row.state === '1')"
@click="changeState(scope.row, -2)">驳回
</el-button>
<el-button
type="danger"
link
icon="Delete"
v-if="permissions.professional_professionalteacherhonor_del"
@click="handleDel(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<ShowHonorEdvince ref="showHonorEdvinceRef" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
<DataForm ref="dataFormRef" />
</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 {
fetchList,
putObj,
delObj
} from '/@/api/professional/professionaluser/professionalteacherhonor'
import { defineAsyncComponent } from 'vue'
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
import ShowHonorEdvince from './showHonorEdvince.vue'
import ProfessionalBackResaon from '/@/views/professional/common/professional-back-resaon.vue'
import DataForm from './form.vue'
// 使用 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 { professional_state: professionalState } = useDict('professional_state')
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const multiDialogRef = ref()
const showHonorEdvinceRef = ref()
const backReasonRef = ref()
const dataFormRef = ref()
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
state: '',
teacherNo: '',
teacherName: ''
})
// 导出加载状态
const exportLoading = ref(false)
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 查看证明材料
const showEdvince = (row: any) => {
showHonorEdvinceRef.value?.init(row)
}
// 审核状态变更
const changeState = (row: any, val: number) => {
if (val === 1) {
// 通过
const str = '通过'
messageBox.confirm(`是否确认${str}${row.teacherName || row.realName}的申请`).then(async () => {
try {
await putObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
}
}).catch(() => {})
} else if (val === -2) {
// 驳回
const newRow = JSON.parse(JSON.stringify(row))
newRow.realName = newRow.teacherName
backReasonRef.value?.init(newRow, 'honor')
}
}
// 查询
const handleFilter = () => {
getDataList()
}
// 重置查询
const resetQuery = () => {
Object.assign(search, {
state: '',
teacherNo: '',
teacherName: ''
})
getDataList()
}
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(4)
}
// 打开编辑窗口
const handleEdit = (row: any) => {
dataFormRef.value?.openDialog(row)
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => {
try {
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 (error) {
// Failed to delete
}
}).catch(() => {})
}
// 导出
const handleDownLoadWord = async () => {
exportLoading.value = true
try {
const response = await fetchList({
current: 1,
size: 999999,
...search
})
const data = response.data.records || []
const tHeader = ['工号', '姓名', '荣誉', '表彰单位', '年份']
const filterVal = ['teacherNo', 'teacherName', 'honor', 'honorCompany', 'year']
// 动态导入导出工具
const { export_json_to_excel } = await import('/@/excel/Export2Excel.js')
const exportData = data.map((v: any) => filterVal.map((j: string) => v[j]))
export_json_to_excel(tHeader, exportData, '综合表彰.xls')
message.success('导出成功')
} catch (error) {
message.error('导出失败')
} finally {
exportLoading.value = false
}
}
// 初始化
onMounted(() => {
getDataList()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,44 @@
<template>
<el-dialog v-model="visible" width="90%" append-to-body title="综合表彰材料">
<auth-img
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
style="height:1000px;"
/>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import authImg from '/@/components/tools/auth-img.vue'
interface ImageItem {
title: string
url: string
}
const visible = ref(false)
const row = ref<any>({})
const imgUrl = ref<ImageItem[]>([])
const init = (rowData: any) => {
row.value = rowData
imgUrl.value = []
nextTick(() => {
const obj: ImageItem = {
title: '',
url: row.value.attachment
}
imgUrl.value.push(obj)
visible.value = true
})
}
defineExpose({
init
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,143 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter(search)"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="姓名" prop="teacherName">
<el-input
v-model="search.teacherName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter(search)" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="teacherName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="oldDeptName" label="原部门名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="newDeptName" label="现部门名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="changeDate" label="调令日期" width="120" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
icon="Printer"
@click="doPrint(scope.row)">打印部门调令
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/professionaluser/professionalteacherstationchange'
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
teacherNo: '',
teacherName: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 查询
const handleFilter = () => {
getDataList()
}
// 重置查询
const resetQuery = () => {
Object.assign(search, {
teacherNo: '',
teacherName: ''
})
getDataList()
}
// 打印部门调令
const doPrint = (row: any) => {
window.open(`/professional/professionalteacherstationchange/print/${row.id}`)
}
// 初始化
onMounted(() => {
getDataList()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalteachertype_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="typeName" label="类型名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalteachertype_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalteachertype_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="类型名称" prop="typeName">
<el-input
v-model="form.typeName"
placeholder="请输入类型名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalteachertype'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
typeName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
typeName: [
{ required: true, message: '请输入类型名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
typeName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
typeName: row.typeName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalteachingmaterialconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="typeName" label="类型名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalteachingmaterialconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalteachingmaterialconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="类型名称" prop="typeName">
<el-input
v-model="form.typeName"
placeholder="请输入类型名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalteachingmaterialconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
typeName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
typeName: [
{ required: true, message: '请输入类型名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
typeName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
typeName: row.typeName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionaltitlelevelconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="professionalTitle" label="职称等级名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionaltitlelevelconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionaltitlelevelconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="职称等级名称" prop="professionalTitle">
<el-input
v-model="form.professionalTitle"
placeholder="请输入职称等级名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionaltitlelevelconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
professionalTitle: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
professionalTitle: [
{ required: true, message: '请输入职称等级名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
professionalTitle: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
professionalTitle: row.professionalTitle || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,296 @@
<template>
<el-dialog v-model="visible" title="编辑职称" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
:model="dataForm"
:rules="formRules"
label-width="120px"
>
<el-form-item label="职称等级" prop="professionalTitleConfigId">
<el-select
v-model="dataForm.professionalTitleConfigId"
filterable
clearable
placeholder="请选择职称等级"
style="width: 100%"
>
<el-option
v-for="item in proTitleList"
:key="item.id"
:label="item.professionalTitle"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="专业技术职务" prop="majorStation">
<el-select
v-model="dataForm.majorStation"
filterable
clearable
placeholder="请选择专业技术职务"
style="width: 100%"
>
<el-option
v-for="item in majorStationList"
:key="item.id"
:label="item.majorStationName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="取证时间" prop="certificateTime">
<el-date-picker
v-model="dataForm.certificateTime"
type="date"
placeholder="请选择取证时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="职称任职时间" prop="inOfficeDate">
<el-date-picker
v-model="dataForm.inOfficeDate"
type="date"
placeholder="请选择职称任职时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="证书编号" prop="certificateNumber">
<el-input
v-model="dataForm.certificateNumber"
placeholder="请输入证书编号"
clearable
/>
</el-form-item>
<el-form-item label="证明材料" prop="materialA">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileList"
:on-success="materiaUploadSuccess"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</template>
</el-upload>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { Session } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
import { putObj } from '/@/api/professional/professionaluser/professionaltitlerelation'
// Props
const props = defineProps<{
visible?: boolean
}>()
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
// 消息提示
const message = useMessage()
// 表单引用
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 表单数据
const dataForm = reactive({
professionalTitleConfigId: '',
majorStation: '',
certificateTime: '',
inOfficeDate: '',
certificateNumber: '',
materialA: '',
evidence: '',
state: '',
teacherNo: '',
id: ''
})
// 表单验证规则
const formRules = {
professionalTitleConfigId: [
{ required: true, message: '请选择职称等级', trigger: 'change' }
],
majorStation: [
{ required: true, message: '请选择专业技术职务', trigger: 'change' }
],
certificateTime: [
{ required: true, message: '请选择取证时间', trigger: 'change' }
],
certificateNumber: [
{ required: true, message: '请输入证书编号', trigger: 'blur' }
]
}
// 基础信息
const baseInfoAbout = reactive<{
stationTypeList: any[]
atStationList: any[]
teacherTypeList: any[]
employmentNatureList: any[]
stationLevelList: any[]
stationDutyLevelList: any[]
workTypeList: any[]
proTitleList: any[]
majorStationList: any[]
qualificationList: any[]
partBranchList: any[]
}>({
stationTypeList: [],
atStationList: [],
teacherTypeList: [],
employmentNatureList: [],
stationLevelList: [],
stationDutyLevelList: [],
workTypeList: [],
proTitleList: [],
majorStationList: [],
qualificationList: [],
partBranchList: []
})
// 上传相关
const url = ref('')
const fileList = ref<any[]>([])
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 显示表单标志
const showForm = ref(false)
// 计算属性:职称列表
const proTitleList = computed(() => baseInfoAbout.proTitleList as any[])
// 计算属性:专业技术职务列表
const majorStationList = computed(() => baseInfoAbout.majorStationList as any[])
// 初始化字典数据
const initDicData = async () => {
try {
const response = await getAllInfoAboutList()
const map = response.data.data
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
baseInfoAbout.atStationList = map['atStationList'] || []
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
baseInfoAbout.workTypeList = map['workTypeList'] || []
baseInfoAbout.proTitleList = map['proTitleList'] || []
baseInfoAbout.majorStationList = map['majorStationList'] || []
baseInfoAbout.qualificationList = map['qualificationList'] || []
baseInfoAbout.partBranchList = map['partBranchList'] || []
visible.value = true
} catch (error) {
// 获取字典数据失败
}
}
// 文件上传成功
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
dataForm.evidence = response.data.url
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=2`
fileList.value = []
Object.assign(dataForm, {
professionalTitleConfigId: row.professionalTitleConfigId || '',
majorStation: row.majorStation || '',
certificateTime: row.certificateTime || '',
inOfficeDate: row.inOfficeDate || '',
certificateNumber: row.certificateNumber || '',
materialA: row.materialA || '',
evidence: row.evidence || '',
state: row.state || '',
teacherNo: row.teacherNo || '',
id: row.id || ''
})
showForm.value = true
initDicData()
}
// 提交表单
const dialogSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
// 初始化方法(保持兼容性)
const init = () => {
// 新版本不再需要初始化配置
}
// 暴露方法
defineExpose({
openDialog,
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,569 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 图表统计 -->
<div style="text-align: right; margin-bottom: 20px;">
<el-collapse accordion @change="initChartOption">
<el-collapse-item title="职称统计">
<div style="width: 100%">
<el-row :gutter="24">
<el-col :span="12">
<v-chart ref="titleChartRef" style="width: 100%; height: 400px;" :option="titleChartOption" />
</el-col>
<el-col :span="12">
<el-table :data="titleChartTableData" border show-summary style="width: 100%">
<el-table-column prop="name" label="职称" width="180" align="center" />
<el-table-column prop="value" label="人数" width="180" align="center" />
<el-table-column prop="rate" label="比例" width="180" align="center" />
</el-table>
</el-col>
</el-row>
<el-row :gutter="24" style="margin-top: 20px;">
<el-col :span="12">
<v-chart ref="techChartRef" style="width: 100%; height: 400px;" :option="techChartOption" />
</el-col>
<el-col :span="12">
<el-table :data="techChartTableData" border show-summary style="width: 100%">
<el-table-column prop="name" label="技术职务" width="180" align="center" />
<el-table-column prop="value" label="人数" width="180" align="center" />
<el-table-column prop="rate" label="比例" width="180" align="center" />
</el-table>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</div>
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="审核状态" prop="state">
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
v-for="item in professionalState"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
<el-form-item label="职称" prop="professionalTitleConfigId">
<el-select
v-model="search.professionalTitleConfigId"
clearable
filterable
style="width: 200px"
placeholder="请选择职称"
>
<el-option
v-for="item in professionalTitleList"
:key="item.id"
:label="item.professionalTitle"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="专业技术职务" prop="majorStation">
<el-select
v-model="search.majorStation"
clearable
filterable
style="width: 200px"
placeholder="请选择专业技术职务"
>
<el-option
v-for="item in majorStationList"
:key="item.id"
:label="item.majorStationName"
:value="item.id"
/>
</el-select>
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionaltitlerelation_add">
</el-button>
<el-button
type="success"
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="state" label="审核状态" width="120" align="center">
<template #default="scope">
<AuditState :state="scope.row.state" />
</template>
</el-table-column>
<el-table-column label="姓名/工号" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="professionalTitleConfigId" label="职称" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getProfessionalTitleName(scope.row.professionalTitleConfigId) }}
</template>
</el-table-column>
<el-table-column prop="majorStation" label="专业技术职务" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getMajorStationName(scope.row.majorStation) }}
</template>
</el-table-column>
<!-- <el-table-column prop="changedTime" label="变动时间" width="180" align="center" />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" /> -->
<el-table-column label="证明材料" width="120" align="center">
<template #default="scope">
<el-button
v-if="scope.row.evidence && scope.row.evidence !== ''"
type="primary"
link
icon="View"
@click="handlePreview(scope.row.srcList)">查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
icon="edit-pen"
v-if="permissions.professional_professionaltitlerelation_edit && (scope.row.state === '0' || scope.row.state === '-2')"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-button
type="success"
link
icon="check"
v-if="permissions.professional_professionaltitlerelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="close"
v-if="permissions.professional_professionaltitlerelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
@click="changeState(scope.row, -2)">驳回
</el-button>
<el-button
type="primary"
link
icon="delete"
v-if="permissions.professional_professionaltitlerelation_del"
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="职称材料" append-to-body width="90%">
<auth-img
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
style="height:1000px;"
/>
</el-dialog>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } 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 VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { PieChart } from 'echarts/charts'
import { TooltipComponent, LegendComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import {
fetchList,
putObj,
delObj,
getChartOption,
exportRelation
} from '/@/api/professional/professionaluser/professionaltitlerelation'
import { getProfessionalTitleList } from '/@/api/professional/rsbase/professionaltitlelevelconfig'
import { getMajorStationList } from '/@/api/professional/rsbase/professionalmajorstation'
import { defineAsyncComponent } from 'vue'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 使用 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 { professional_state: professionalState } = useDict('professional_state')
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const multiDialogRef = ref()
const dataFormRef = ref()
const backReasonRef = ref()
const titleChartRef = ref()
const techChartRef = ref()
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
state: '',
teacherNo: '',
realName: '',
professionalTitleConfigId: '',
majorStation: ''
})
// 图表数据
const titleChartOption = ref<any>({})
const titleChartTableData = ref<any[]>([])
const techChartOption = ref<any>({})
const techChartTableData = ref<any[]>([])
// 材料预览
const dialogVisible = ref(false)
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
// 职称和专业技术职务列表
const professionalTitleList = ref<any[]>([])
const majorStationList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
const records = response.data.records || []
// 处理证明材料列表
records.forEach((v: any) => {
if (v.evidence != null) {
v.srcList = [v.evidence]
} else {
v.srcList = []
}
})
return {
data: {
records,
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 初始化图表
const initChartOption = async () => {
try {
const response = await getChartOption()
const data = response.data.data || {}
// 职称图表
titleChartOption.value = data.titleOption || {}
let titleTotal = 0
if (titleChartOption.value.series && titleChartOption.value.series[0] && titleChartOption.value.series[0].data) {
titleChartOption.value.series[0].data.forEach((item: any) => {
titleTotal += item.value || 0
})
titleChartTableData.value = []
titleChartOption.value.series[0].data.forEach((item: any) => {
const rate = titleTotal > 0 ? Number((item.value / titleTotal * 100).toFixed(1)) : 0
titleChartTableData.value.push({
name: item.name,
value: item.value,
rate: `${rate}%`
})
})
}
// 技术职务图表
techChartOption.value = data.techOption || {}
let techTotal = 0
if (techChartOption.value.series && techChartOption.value.series[0] && techChartOption.value.series[0].data) {
techChartOption.value.series[0].data.forEach((item: any) => {
techTotal += item.value || 0
})
techChartTableData.value = []
techChartOption.value.series[0].data.forEach((item: any) => {
const rate = techTotal > 0 ? Number((item.value / techTotal * 100).toFixed(1)) : 0
techChartTableData.value.push({
name: item.name,
value: item.value,
rate: `${rate}%`
})
})
}
} catch (error) {
// Failed to load chart data
}
}
// 预览材料
const handlePreview = (list: string[]) => {
imgUrl.value = []
nextTick(() => {
list.forEach(v => {
imgUrl.value.push({
title: '',
url: v
})
})
nextTick(() => {
dialogVisible.value = true
})
})
}
// 审核状态变更
const changeState = (row: any, val: number) => {
if (val === 1) {
// 通过
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
}
}).catch(() => {})
} else if (val === -2) {
// 驳回
backReasonRef.value?.init(row, 'title')
}
}
// 查询
const handleFilter = () => {
getDataList()
}
// 重置查询
const resetQuery = () => {
Object.assign(search, {
state: '',
teacherNo: '',
realName: '',
professionalTitleConfigId: '',
majorStation: ''
})
getDataList()
}
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(2)
}
// 打开编辑窗口
const handleEdit = (row: any) => {
dataFormRef.value?.openDialog(row)
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => {
try {
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 (error) {
// Failed to delete
}
}).catch(() => {})
}
// 导出
const handleDownLoadWord = async () => {
exportLoading.value = true
try {
const response: any = await exportRelation(search)
const blob = new Blob([response as BlobPart])
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) {
message.error('导出失败')
} finally {
exportLoading.value = false
}
}
// 获取职称名称
const getProfessionalTitleName = (id: string | number) => {
const item = professionalTitleList.value.find((item: any) => item.id === id)
return item ? item.professionalTitle : '-'
}
// 获取专业技术职务名称
const getMajorStationName = (id: string | number) => {
const item = majorStationList.value.find((item: any) => item.id === id)
return item ? item.majorStationName : '-'
}
// 加载字典数据
const loadDictData = async () => {
try {
// 加载职称列表
const titleRes = await getProfessionalTitleList()
if (titleRes && titleRes.data) {
professionalTitleList.value = titleRes.data
}
// 加载专业技术职务列表
const majorRes = await getMajorStationList()
if (majorRes && majorRes.data) {
majorStationList.value = majorRes.data
}
} catch (error) {
// Failed to load dict data
}
}
// 初始化
onMounted(async () => {
await loadDictData()
dataFormRef.value?.init()
getDataList()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionaltopiclevelconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="levelName" label="课题等级名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionaltopiclevelconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionaltopiclevelconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="课题等级名称" prop="levelName">
<el-input
v-model="form.levelName"
placeholder="请输入课题等级名称"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionaltopiclevelconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
levelName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
levelName: [
{ required: true, message: '请输入课题等级名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
levelName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
levelName: row.levelName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionaltopicsourceconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="sourceName" label="课题来源" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionaltopicsourceconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionaltopicsourceconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="课题来源" prop="sourceName">
<el-input
v-model="form.sourceName"
placeholder="请输入课题来源"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionaltopicsourceconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
sourceName: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
sourceName: [
{ required: true, message: '请输入课题来源', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
sourceName: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
sourceName: row.sourceName || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,216 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_worktype_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="workName" label="工种名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_worktype_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_worktype_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="工种名称" prop="workName">
<el-input
v-model="form.workName"
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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/professionalworktype'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
workName: ''
})
// 表单验证规则
const formRules = {
workName: [
{ required: true, message: '请输入工种名称', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
workName: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
workName: row.workName || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,328 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_professionalyearbounds_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="data-table"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="teacherName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="salary" label="13月工资" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="assessmentTarget" label="重点考核目标" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="specialPerformance1" label="专项绩效1" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="specialPerformance2" label="专项绩效2" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="meritpay" label="年终奖励性绩效" min-width="150" align="center" show-overflow-tooltip />
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_professionalyearbounds_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_professionalyearbounds_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-form-item label="工号" prop="teacherNo">
<el-input
v-model="form.teacherNo"
placeholder="请输入工号"
clearable
/>
</el-form-item>
<el-form-item label="姓名" prop="teacherName">
<el-input
v-model="form.teacherName"
placeholder="请输入姓名"
clearable
/>
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker
v-model="form.year"
type="year"
placeholder="请选择年份"
format="YYYY"
value-format="YYYY"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="13月工资" prop="salary">
<el-input-number
v-model="form.salary"
:min="0"
:precision="2"
placeholder="请输入13月工资"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="重点考核目标" prop="assessmentTarget">
<el-input-number
v-model="form.assessmentTarget"
:min="0"
:precision="2"
placeholder="请输入重点考核目标"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="专项绩效1" prop="specialPerformance1">
<el-input-number
v-model="form.specialPerformance1"
:min="0"
:precision="2"
placeholder="请输入专项绩效1"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="专项绩效2" prop="specialPerformance2">
<el-input-number
v-model="form.specialPerformance2"
:min="0"
:precision="2"
placeholder="请输入专项绩效2"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="年终奖励性绩效" prop="meritpay">
<el-input-number
v-model="form.meritpay"
:min="0"
:precision="2"
placeholder="请输入年终奖励性绩效"
style="width: 100%"
/>
</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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { fetchList, addObj, putObj, delObj } from '/@/api/professional/professionalyearbounds'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
teacherNo: '',
teacherName: '',
year: '',
salary: null as number | null,
assessmentTarget: null as number | null,
specialPerformance1: null as number | null,
specialPerformance2: null as number | null,
meritpay: null as number | null
})
// 表单验证规则
const formRules = {
teacherNo: [
{ required: true, message: '请输入工号', trigger: 'blur' }
],
teacherName: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
year: [
{ required: true, message: '请选择年份', trigger: 'change' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
teacherNo: '',
teacherName: '',
year: '',
salary: null,
assessmentTarget: null,
specialPerformance1: null,
specialPerformance2: null,
meritpay: null
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id || '',
teacherNo: row.teacherNo || '',
teacherName: row.teacherName || '',
year: row.year || '',
salary: row.salary || null,
assessmentTarget: row.assessmentTarget || null,
specialPerformance1: row.specialPerformance1 || null,
specialPerformance2: row.specialPerformance2 || null,
meritpay: row.meritpay || null
})
dialogVisible.value = true
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => {
try {
await delObj(row.id)
message.success('删除成功')
getDataList(false) // 删除后保持当前页
} catch (error: any) {
message.error(error?.msg || '删除失败')
}
}).catch(() => {
// 用户取消
})
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList(false) // 提交后保持当前页
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
.data-table {
width: 100% !important;
}
</style>

View File

@@ -0,0 +1,168 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="薪资年份" prop="salaryYear">
<el-date-picker
v-model="search.salaryYear"
type="year"
format="YYYY"
value-format="YYYY"
placeholder="请选择薪资年份"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="薪资月份" prop="salaryMonth">
<el-date-picker
v-model="search.salaryMonth"
type="month"
format="M"
value-format="M"
placeholder="请选择薪资月份"
clearable
style="width: 200px"
/>
</el-form-item>
</template>
</template>
</search-form>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="data-table"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="createTime" label="导出时间" width="180" align="center" />
<el-table-column prop="salaryYear" label="薪资年份" width="120" align="center" />
<el-table-column prop="salaryMonth" label="薪资月份" width="120" align="center" />
<el-table-column prop="confirm" label="劳务日期锁定" width="150" align="center">
<template #default="scope">
<el-tag :type="scope.row.confirm === '1' ? 'success' : 'info'">
{{ scope.row.confirm === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_salaryexportrecord_del"
icon="delete"
link
type="primary"
@click="handleDel(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { fetchList, delObj } from '/@/api/professional/salaryexportrecord'
// 使用 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 tableRef = ref()
const searchFormRef = ref()
// 搜索表单显示状态
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
salaryYear: '',
salaryMonth: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 查询
const handleFilter = () => {
getDataList() // 查询后跳转到第一页
}
// 删除
const handleDel = (row: any) => {
messageBox.confirm('确认删除?').then(async () => {
try {
await delObj(row.id)
message.success('删除成功')
getDataList(false) // 删除后保持当前页
} catch (error: any) {
message.error(error?.msg || '删除失败')
}
}).catch(() => {
// 用户取消
})
}
</script>
<style lang="scss" scoped>
.data-table {
width: 100% !important;
}
</style>

View File

@@ -0,0 +1,184 @@
<template>
<el-dialog v-model="visible" title="薪资导入" width="600px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px">
<el-form-item label="年份">
<el-date-picker
v-model="chooseDate"
type="year"
format="YYYY"
value-format="YYYY"
placeholder="请选择年份"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item>
<el-upload
v-if="showUpload"
:headers="headers"
class="upload-demo"
:action="url"
:limit="1"
:with-credentials="true"
:before-upload="beforeUpload"
>
<template #tip>
<div class="el-upload-list__item-name">{{ fileName }}</div>
</template>
<el-button
size="small"
v-if="permissions.professional_salary_import"
type="primary">选择文件
</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitUpload" :loading="btnLoading">导入</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import request from '/@/utils/request'
// Emits
const emit = defineEmits<{
(e: 'refreshData'): void
}>()
// 使用 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
})
// 消息提示
const message = useMessage()
// 对话框显示状态
const visible = ref(false)
// 表单数据
const chooseDate = ref('')
const url = ref('')
const showUpload = ref(false)
const fileList = ref<any[]>([])
const btnLoading = ref(false)
const fileName = ref('')
const files = ref<File | null>(null)
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 年份改变
const handleChange = () => {
if (chooseDate.value) {
showUpload.value = true
url.value = `/professional/file/importAwardTax?chooseDate=${chooseDate.value}`
} else {
showUpload.value = false
}
}
// 初始化
const init = () => {
fileList.value = []
files.value = null
fileName.value = ''
btnLoading.value = false
chooseDate.value = ''
showUpload.value = false
visible.value = true
}
// 上传前验证
const beforeUpload = (file: File) => {
files.value = 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 < 20
if (!extension && !extension2) {
message.warning('上传模板只能是 xls、xlsx格式!')
return false
}
if (!isLt2M) {
message.warning('上传模板大小不能超过 20MB!')
return false
}
fileName.value = file.name
return false // 返回false不会自动上传
}
// 导入提交
const submitUpload = async () => {
btnLoading.value = true
if (fileName.value === "") {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
if (!files.value) {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
const fileFormData = new FormData()
fileFormData.append('file', files.value, fileName.value)
try {
const res = await request({
url: url.value,
method: 'post',
data: fileFormData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.data.code == 200) {
message.success("导入成功")
visible.value = false
emit('refreshData')
} else {
message.error(res.data.msg || '导入失败')
}
} catch (error: any) {
message.error(error?.msg || '导入失败')
} finally {
btnLoading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,169 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
size="small"
v-if="permissions.teacher_award_import"
type="primary"
@click="handleImportBaseSalary">绩效导入
</el-button>
</div>
</el-row>
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
placeholder="请输入姓名"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
placeholder="请输入工号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker
v-model="search.year"
type="year"
format="YYYY"
value-format="YYYY"
placeholder="请选择年份"
clearable
style="width: 200px"
/>
</el-form-item>
</template>
</template>
</search-form>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="data-table"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="startDate" label="开始日期" width="120" align="center" />
<el-table-column prop="endDate" label="结束日期" width="120" align="center" />
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="income" label="累计应纳税所得额" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="taxRate" label="税率" width="100" align="center" />
<el-table-column prop="deductMoney" label="速算扣除" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realMoney" label="应补(退)税额" min-width="130" align="center" show-overflow-tooltip />
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
<!-- 导入组件 -->
<import-award-tax ref="importAwardTaxRef" @refreshData="handleFilter" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/teacherawardtax'
import ImportAwardTax from './importAwardTax.vue'
// 使用 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
})
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const importAwardTaxRef = ref()
// 搜索表单显示状态
const showSearch = ref(true)
// 搜索表单数据
const search = reactive({
realName: '',
teacherNo: '',
year: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 查询
const handleFilter = () => {
getDataList() // 查询后跳转到第一页
}
// 导入
const handleImportBaseSalary = () => {
importAwardTaxRef.value?.init()
}
</script>
<style lang="scss" scoped>
.data-table {
width: 100% !important;
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<el-dialog v-model="visible" title="薪资导出" width="500px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="120px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
type="month"
format="YYYY-M"
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="劳务日期锁定">
<el-radio-group v-model="lockedLw">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-tag type="warning">选择是则本月其他造单批次无法指定到当前月份如需解锁请前往薪资导出记录删除记录即可</el-tag>
</el-form-item>
</el-form>
<template #footer>
<span v-if="showBtn" class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="exportSalaryData" :loading="btnLoading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import request from '/@/utils/request'
// 消息提示
const message = useMessage()
// 对话框显示状态
const visible = ref(false)
// 表单数据
const chooseDate = ref('')
const showBtn = ref(false)
const btnLoading = ref(false)
const lockedLw = ref(1)
// 初始化
const init = () => {
chooseDate.value = ''
showBtn.value = false
btnLoading.value = false
lockedLw.value = 1
visible.value = true
}
// 日期改变
const handleChange = () => {
if (chooseDate.value) {
showBtn.value = true
} else {
showBtn.value = false
}
}
// 下载文件
const downLoadFile = async (url: string) => {
return request({
method: 'get',
url: url,
responseType: 'blob',
headers: {
'Content-Type': 'application/json',
"Authorization": 'Bearer ' + Session.getToken()
}
})
}
// 导出薪资数据
const exportSalaryData = async () => {
if (!chooseDate.value) {
message.warning('请选择日期')
return
}
btnLoading.value = true
try {
const res = await downLoadFile(`/professional/file/exportSalary?chooseDate=${chooseDate.value}&state=${lockedLw.value}`)
// 处理返回的文件流
const blob = new Blob([res.data])
const elink = document.createElement('a')
elink.download = "薪资表.xls"
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
message.success('导出成功')
visible.value = false
} catch (error: any) {
message.error(error?.msg || '导出失败')
} finally {
btnLoading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,183 @@
<template>
<el-dialog v-model="visible" title="薪资导入" width="600px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
type="month"
format="YYYY-M"
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item>
<el-upload
v-if="showUpload"
:headers="headers"
class="upload-demo"
:action="url"
:limit="1"
:with-credentials="true"
:before-upload="beforeUpload"
>
<template #tip>
<div class="el-upload-list__item-name">{{ fileName }}</div>
</template>
<el-button
size="small"
v-if="permissions.professional_salary_import"
type="primary">选择文件
</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitUpload" :loading="btnLoading">导入</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import request from '/@/utils/request'
// Emits
const emit = defineEmits<{
(e: 'refreshData'): void
}>()
// 使用 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
})
// 消息提示
const message = useMessage()
// 对话框显示状态
const visible = ref(false)
// 表单数据
const chooseDate = ref('')
const url = ref('')
const showUpload = ref(false)
const fileList = ref<any[]>([])
const btnLoading = ref(false)
const fileName = ref('')
const files = ref<File | null>(null)
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 日期改变
const handleChange = () => {
if (chooseDate.value) {
showUpload.value = true
url.value = `/professional/file/importTeacherPayslpi?chooseDate=${chooseDate.value}`
} else {
showUpload.value = false
}
}
// 初始化
const init = () => {
fileList.value = []
files.value = null
fileName.value = ''
chooseDate.value = ''
showUpload.value = false
visible.value = true
}
// 上传前验证
const beforeUpload = (file: File) => {
files.value = 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 < 20
if (!extension && !extension2) {
message.warning('上传模板只能是 xls、xlsx格式!')
return false
}
if (!isLt2M) {
message.warning('上传模板大小不能超过 20MB!')
return false
}
fileName.value = file.name
return false // 返回false不会自动上传
}
// 导入提交
const submitUpload = async () => {
btnLoading.value = true
if (fileName.value === "") {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
if (!files.value) {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
const fileFormData = new FormData()
fileFormData.append('file', files.value, fileName.value)
try {
const res = await request({
url: url.value,
method: 'post',
data: fileFormData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.data.data && res.data.data.code == 1) {
message.error(`导入失败,${res.data.data.title}: ${res.data.data.msg}`)
} else {
message.success("导入成功")
visible.value = false
emit('refreshData')
}
} catch (error: any) {
message.error(error?.msg || '导入失败')
} finally {
btnLoading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,183 @@
<template>
<el-dialog v-model="visible" title="税金导入" width="600px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
type="month"
format="YYYY-M"
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item>
<el-upload
v-if="showUpload"
:headers="headers"
class="upload-demo"
:action="url"
:limit="1"
:with-credentials="true"
:before-upload="beforeUpload"
>
<template #tip>
<div class="el-upload-list__item-name">{{ fileName }}</div>
</template>
<el-button
size="small"
v-if="permissions.professional_salary_import"
type="primary">选择文件
</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitUpload" :loading="btnLoading">导入</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import request from '/@/utils/request'
// Emits
const emit = defineEmits<{
(e: 'refreshData'): void
}>()
// 使用 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
})
// 消息提示
const message = useMessage()
// 对话框显示状态
const visible = ref(false)
// 表单数据
const chooseDate = ref('')
const url = ref('')
const showUpload = ref(false)
const fileList = ref<any[]>([])
const btnLoading = ref(false)
const fileName = ref('')
const files = ref<File | null>(null)
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 日期改变
const handleChange = () => {
if (chooseDate.value) {
showUpload.value = true
url.value = `/professional/file/importTaxSalary?chooseDate=${chooseDate.value}`
} else {
showUpload.value = false
}
}
// 初始化
const init = () => {
fileList.value = []
files.value = null
fileName.value = ''
chooseDate.value = ''
showUpload.value = false
visible.value = true
}
// 上传前验证
const beforeUpload = (file: File) => {
files.value = 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 < 20
if (!extension && !extension2) {
message.warning('上传模板只能是 xls、xlsx格式!')
return false
}
if (!isLt2M) {
message.warning('上传模板大小不能超过 20MB!')
return false
}
fileName.value = file.name
return false // 返回false不会自动上传
}
// 导入提交
const submitUpload = async () => {
btnLoading.value = true
if (fileName.value === "") {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
if (!files.value) {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
const fileFormData = new FormData()
fileFormData.append('file', files.value, fileName.value)
try {
const res = await request({
url: url.value,
method: 'post',
data: fileFormData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.data.data && res.data.data.code == 1) {
message.error(`导入失败,${res.data.data.title}: ${res.data.data.msg}`)
} else {
message.success("导入成功")
visible.value = false
emit('refreshData')
}
} catch (error: any) {
message.error(error?.msg || '导入失败')
} finally {
btnLoading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,354 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
size="small"
v-if="permissions.professional_salary_import"
type="primary"
@click="handleImportBaseSalary">工资条导入
</el-button>
<el-button
type="primary"
size="small"
v-if="permissions.professional_seach_auth"
@click="canSearch(1)">设置可查询
</el-button>
<el-button
type="primary"
size="small"
v-if="permissions.professional_seach_auth"
@click="canSearch(0)">设置不可查询
</el-button>
<el-button
type="primary"
size="small"
v-if="permissions.professional_professionalsalaries_del"
@click="delbatch">批量删除
</el-button>
</div>
</el-row>
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
placeholder="请输入工号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
placeholder="请输入姓名"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input
v-model="search.idCard"
placeholder="请输入身份证号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="年份" prop="nf">
<el-date-picker
v-model="search.nf"
type="year"
format="YYYY"
value-format="YYYY"
placeholder="请选择年份"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="月份" prop="yf">
<el-date-picker
v-model="search.yf"
type="month"
format="M"
value-format="M"
placeholder="请选择月份"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="岗位类别" prop="stationTypeId">
<el-select
v-model="search.stationTypeId"
filterable
clearable
placeholder="请选择岗位类别"
style="width: 200px"
>
<el-option
v-for="item in stationLevelList"
:key="item.id"
:label="item.levelName"
:value="item.id"
/>
</el-select>
</el-form-item>
</template>
</template>
</search-form>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="data-table"
@selection-change="selectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="numId" label="编号" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="postSalary" label="岗位工资" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="nf" label="年份" width="100" align="center" />
<el-table-column prop="yf" label="月份" width="100" align="center" />
<el-table-column prop="idCard" label="身份证号" min-width="180" align="center" show-overflow-tooltip />
<el-table-column prop="shouldPay" label="应发工资" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realWage" label="实发工资" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="normalView" label="职工查看" width="120" align="center">
<template #default="scope">
<el-tag :type="scope.row.normalView === '1' ? 'success' : 'info'">
{{ scope.row.normalView === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="document"
link
type="primary"
@click="handleEdit(scope.row)">查看
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
<!-- 子组件 -->
<salary-info ref="salaryInfoRef" />
<import-base-salary ref="importBaseSalaryRef" @refreshData="handleFilter" />
<export-base-salary ref="exportBaseSalaryRef" />
</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, useMessageBox } from '/@/hooks/message'
import { fetchList, delBatch, setCanSearch } from '/@/api/professional/teacherpayslip'
import { checkAuth } from '/@/api/professional/teachersalary'
import { getStationLevelList } from '/@/api/professional/professionalstationlevelconfig'
import SalaryInfo from './salaryInfo.vue'
import ImportBaseSalary from './importBaseSalary.vue'
import ExportBaseSalary from './exportBaseSalary.vue'
// 使用 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 tableRef = ref()
const searchFormRef = ref()
const salaryInfoRef = ref()
const importBaseSalaryRef = ref()
const exportBaseSalaryRef = ref()
// 搜索表单显示状态
const showSearch = ref(true)
// 岗位类别列表
const stationLevelList = ref<any[]>([])
// 选中的行
const selectList = ref<any[]>([])
// 搜索表单数据
const search = reactive({
teacherNo: '',
realName: '',
idCard: '',
nf: '',
yf: '',
stationTypeId: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 初始化
onMounted(() => {
init()
})
// 初始化数据
const init = async () => {
try {
const [stationRes, authRes] = await Promise.all([
getStationLevelList(),
checkAuth()
])
stationLevelList.value = stationRes.data || []
// 根据权限设置表格选项(如果需要)
// if (authRes.data.data === false) {
// // 设置普通表格选项
// }
} catch (error) {
// 初始化失败
}
}
// 查询
const handleFilter = () => {
getDataList() // 查询后跳转到第一页
}
// 选择变化
const selectionChange = (selection: any[]) => {
selectList.value = selection
}
// 查看
const handleEdit = (row: any) => {
salaryInfoRef.value?.init(row)
}
// 导入工资条
const handleImportBaseSalary = () => {
importBaseSalaryRef.value?.init()
}
// 批量删除
const delbatch = () => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
return
}
messageBox.confirm(`确认删除当前${selectList.value.length}条记录`).then(async () => {
try {
const data = { selectList: selectList.value }
const response = await delBatch(data)
if (response.data.code == -1) {
message.error(response.data.data)
} else {
message.info("删除成功")
getDataList(false) // 删除后保持当前页
}
} catch (error: any) {
message.error(error?.msg || '删除失败')
}
}).catch(() => {
// 用户取消
})
}
// 设置可查询/不可查询
const canSearch = (val: number) => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
return
}
const params = {
canSearch: val,
selectList: selectList.value
}
messageBox.confirm('确认设置?').then(async () => {
try {
await setCanSearch(params)
message.success("设置成功")
getDataList(false) // 设置后保持当前页
} catch (error: any) {
message.error(error?.msg || '设置失败')
}
}).catch(() => {
// 用户取消
})
}
</script>
<style lang="scss" scoped>
.data-table {
width: 100% !important;
}
</style>

View File

@@ -0,0 +1,323 @@
<template>
<el-dialog v-model="visible" width="100%" v-loading="baseLoading" top="0" :close-on-click-modal="false" destroy-on-close>
<!--基本信息-->
<el-card shadow="hover">
<el-row :span="24">
<el-col>
<el-table
:data="salaryData.baseInfo"
style="width: 100%">
<el-table-column
prop="realName"
label="姓名"
width="80">
</el-table-column>
<el-table-column
prop="idCard"
label="身份证号"
width="200">
</el-table-column>
<el-table-column
label="年份"
width="300">
<template #default>
<el-date-picker
v-model="nowUser.nf"
type="year"
format="YYYY"
value-format="YYYY"
style="width: 200px"
placeholder="选择年">
</el-date-picker>
</template>
</el-table-column>
<el-table-column
label="月份"
width="300">
<template #default>
<el-date-picker
v-model="nowUser.yff"
type="month"
format="M"
value-format="M"
style="width: 200px"
placeholder="选择月">
</el-date-picker>
</template>
</el-table-column>
<el-table-column
label="操作"
width="200">
<template #default>
<el-button @click="searchUserInfo" type="primary" size="small">搜索</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-card>
<el-card shadow="hover">
<!--应发部分-->
<el-row>
<el-col :span="24">
<el-table
:data="salaryData.baseInfo"
size="small"
border
>
<el-table-column label="应发部分">
<el-table-column prop="baseSalary" label="基础专项绩效" min-width="60" align="center"></el-table-column>
<el-table-column prop="postSalary" label="岗位工资" min-width="60" align="center"></el-table-column>
<el-table-column prop="payWage" label="薪级工资" min-width="60" align="center"></el-table-column>
<el-table-column label="见习期工资" prop="studentPay" min-width="60" align="center"></el-table-column>
<el-table-column prop="liveAllowance" label="生活补贴" min-width="60" align="center"></el-table-column>
<el-table-column prop="postAllowance" label="岗位津贴" min-width="60" align="center"></el-table-column>
<el-table-column label="住房(租金)补贴" prop="houseSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="新职工住房补贴" prop="newHouseSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="回民补贴" prop="huiSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="养老保险补贴" prop="oldSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="教龄津贴" prop="ageAllowance" min-width="60" align="center"></el-table-column>
<el-table-column label="特教补贴" prop="specialSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="特级教师津贴" prop="teacherAllowance" min-width="60" align="center"></el-table-column>
<el-table-column label="特岗津贴(一)" prop="sPostAllowance1" min-width="60" align="center"></el-table-column>
<el-table-column label="特岗津贴(二)" prop="sPostAllowance2" min-width="60" align="center"></el-table-column>
<el-table-column label="其他" prop="other" min-width="60" align="center"></el-table-column>
<el-table-column label="奖励性绩效工资" prop="meritPay" min-width="60" align="center"></el-table-column>
<el-table-column label="乡镇工作补贴" prop="villageSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="临时性补贴" prop="temporarySubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="上下班交通补贴" prop="trafficSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="保留津贴" prop="keepAllowance" min-width="60" align="center"></el-table-column>
<el-table-column label="补发工资" prop="retroactivePay" min-width="60" align="center"></el-table-column>
<el-table-column label="应休未休假补贴" prop="vacationMoney" min-width="60" align="center"></el-table-column>
<el-table-column label="应发工资" prop="shouldPay" min-width="60" align="center"></el-table-column>
<el-table-column label="基础专项绩效" prop="baseSalary" min-width="60" align="center"></el-table-column>
<el-table-column label="基础工资应税收入" prop="shouldTaxMoney" min-width="60" align="center"></el-table-column>
</el-table-column>
</el-table>
<el-tag type="danger">基础工资应税收入= 岗位工资 +薪级工资+见习期工资+生活补贴+岗位津贴+教龄津贴+特教补贴+特级教师津贴+特岗津贴1+特岗津贴2+奖励绩效性工资+乡镇工作补贴+临时补贴+保留津贴+应休未休假-个人补缴-其他扣款-医疗救助金 </el-tag>
</el-col>
</el-row>
<!--应扣部分-->
<el-row>
<el-col :span="24">
<el-table
size="small"
border
:data="salaryData.baseInfo"
style="width: 100%">
<el-table-column label="应扣部分">
<el-table-column label="住房公积金" width="90" prop="houseFund"></el-table-column>
<el-table-column label="医疗保险金" width="90" prop="medicalInsurance"></el-table-column>
<el-table-column label="失业保险金" width="90" prop="unemployInsurance"></el-table-column>
<el-table-column label="养老保险金" width="90" prop="endowInsurance"></el-table-column>
<el-table-column label="工会费" width="60" prop="unionFee"></el-table-column>
<el-table-column label="儿童统筹" width="90" prop="childrenWhole"></el-table-column>
<el-table-column label="个人所得税" width="90" prop="personalTax"></el-table-column>
<el-table-column label="其他扣款" width="90" prop="otherDeduction"></el-table-column>
<el-table-column label="病事假扣款" width="90" prop="sickDeduction"></el-table-column>
<el-table-column label="医疗救助基金" width="90" prop="medicalFund"></el-table-column>
<el-table-column label="工伤保险" width="90" prop="inductrialInjury"></el-table-column>
<el-table-column label="个人补缴" width="90" prop="personalPay"></el-table-column>
<el-table-column label="应扣合计" width="90" prop="withhold"></el-table-column>
</el-table-column>
</el-table>
<el-tag>个人所得税 = 个税计算数据中的 累计应补(退)税额 </el-tag>
</el-col>
</el-row>
<!--劳务费-->
<el-row v-if="showAllContent">
<el-col :span="24">
<el-table size="small" border :data="allProjectData">
<el-table-column label="造单收入清单">
<el-table-column label="造单部门" prop="deptName"></el-table-column>
<el-table-column label="造单人" prop="createName"></el-table-column>
<el-table-column label="项目编号" prop="projectNo"></el-table-column>
<el-table-column label="项目名" prop="title"></el-table-column>
<el-table-column label="金额" prop="realMoney"></el-table-column>
<el-table-column label="付讫时间" prop="payTime"></el-table-column>
<el-table-column label="免税或暂不交税" prop="freeTax">
<template #default="scope">
<span v-if="scope.row.freeTax=='1'"></span>
</template>
</el-table-column>
</el-table-column>
</el-table>
</el-col>
</el-row>
<!--专项扣除-->
<el-row v-if="showAllContent">
<el-col :span="24">
<el-table
size="small"
border
:data="salaryExtendData"
style="width: 100%">
<el-table-column label="个税计算数据">
<el-table-column prop="currentIncome" label="应税收入" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalIncome" label="累计收入额" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalDeduction" label="累计减除费用" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalSpecialDecution" label="累计专项扣除" min-width="60" align="center"></el-table-column>
<el-table-column label="累计专项扣除附加" align="center">
<el-table-column prop="totalChildEdu" label="累计子女教育" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalConEdu" label="累计继续教育" min-width="60" align="center"> </el-table-column>
<el-table-column prop="totalHouseInterest" label="累计住房贷款利息" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalHouse" label="累计住房租金" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalSupportOld" label="累计赡养老人" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalBabyMoney" label="累计婴幼儿照护费用" min-width="60" align="center"></el-table-column>
</el-table-column>
<el-table-column prop="totalTaxMoney" label="累计应纳税所得额" min-width="60" align="center"></el-table-column>
<el-table-column prop="taxRate" label="税率" min-width="60" align="center"></el-table-column>
<el-table-column prop="quickDecution" label="速算扣除数" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalRealTaxPay" label="累计应扣缴税额" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalPrePayTax" label="累计已预缴税额" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalRetrieveTax" label="累计应补(退)税额" min-width="60" align="center"></el-table-column>
</el-table-column>
</el-table>
<el-tag type="warning">应税收入= 基础工资应税收入+造单收入 </el-tag> &nbsp;&nbsp;
<el-tag type="success">累计专项扣除=当年累计个人承担的住房公积金+医疗保险金+失业保险金+养老保险金 </el-tag>
</el-col>
</el-row>
<!--实发合计-->
<el-row>
<el-col :span="24">
<el-table
size="small"
border
:data="[staticsData]"
empty-text=" ">
<el-table-column label="小计">
<el-table-column label="应发工资" prop="shouldPay"></el-table-column>
<el-table-column label="应扣合计" prop="shouldDedu"></el-table-column>
<el-table-column label="实发工资" prop="realWage"></el-table-column>
</el-table-column>
</el-table>
<el-tag type="primary">实发工资= 应发工资-应扣合计 </el-tag>
</el-col>
</el-row>
</el-card>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { queryUserInfo, queryExtendSalaryInfo } from '/@/api/professional/teacherpayslip'
import { checkAuth } from '/@/api/professional/teachersalary'
// 对话框显示状态
const visible = ref(false)
// 数据
const salaryData = reactive({
baseInfo: [] as any[]
})
const nowUser = ref<any>({})
const baseLoading = ref(false)
const salaryExtendData = ref<any[]>([])
const allProjectData = ref<any[]>([])
const staticsData = reactive({
shouldPay: 0,
shouldDedu: 0,
realWage: 0,
orderMoney: 0,
personTax: 0
})
const showAllContent = ref(false)
// 检查权限
const checkAuthMethod = async () => {
try {
const res = await checkAuth()
showAllContent.value = res.data.data
} catch (error) {
// 检查权限失败
}
}
// 构建用户信息
const makeUserInfo = (data: any) => {
const row = JSON.parse(JSON.stringify(data))
salaryData.baseInfo = []
row.realName = (row.realName == "" || row.realName == null ? row.userName : row.realName)
salaryData.baseInfo.push(row)
staticsData.shouldPay = row.shouldPay || 0
staticsData.shouldDedu = row.withhold || 0
staticsData.realWage = row.realWage || 0
staticsData.personTax = (row.personalTax as number) || 0
}
// 查询扩展薪资信息
const queryExtendSalaryInfoMethod = async (row: any) => {
salaryExtendData.value = []
const params = { nf: row.nf, yf: row.yf, teacherNo: row.teacherNo }
try {
const res = await queryExtendSalaryInfo(params)
salaryExtendData.value.push(res.data.data.salaryTax)
allProjectData.value = res.data.data.allProject
staticsData.orderMoney = res.data.data.totalMoney
} catch (error) {
// 查询失败
}
}
// 搜索用户信息
const searchUserInfo = async () => {
baseLoading.value = true
const data = { idCard: nowUser.value.idCard, nf: nowUser.value.nf, yf: nowUser.value.yff }
try {
const response = await queryUserInfo(data)
if (response.data.data == null) {
salaryData.baseInfo = []
const obj: any = {}
obj.realName = nowUser.value.realName
obj.idCard = nowUser.value.idCard
salaryData.baseInfo.push(obj)
salaryExtendData.value = []
Object.assign(staticsData, {
shouldPay: 0,
shouldDedu: 0,
realWage: 0,
orderMoney: 0,
personTax: 0
})
} else {
const resData = response.data.data
resData.realName = nowUser.value.realName
makeUserInfo(resData)
await queryExtendSalaryInfoMethod(resData)
}
} catch (error) {
// 查询失败
} finally {
baseLoading.value = false
}
}
// 初始化
const init = (row: any) => {
visible.value = true
nextTick(() => {
checkAuthMethod()
nowUser.value = JSON.parse(JSON.stringify(row))
nowUser.value.yff = row.yf
makeUserInfo(row)
queryExtendSalaryInfoMethod(row)
})
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,122 @@
<template>
<el-dialog v-model="visible" title="薪资导出" width="500px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="120px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
type="month"
format="YYYY-M"
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="劳务日期锁定">
<el-radio-group v-model="lockedLw">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-tag type="warning">选择是则本月其他造单批次无法指定到当前月份如需解锁请前往薪资导出记录删除记录即可</el-tag>
</el-form-item>
</el-form>
<template #footer>
<span v-if="showBtn" class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="exportSalaryData" :loading="btnLoading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import request from '/@/utils/request'
// 消息提示
const message = useMessage()
// 对话框显示状态
const visible = ref(false)
// 表单数据
const chooseDate = ref('')
const showBtn = ref(false)
const btnLoading = ref(false)
const lockedLw = ref(1)
// 初始化
const init = () => {
chooseDate.value = ''
showBtn.value = false
btnLoading.value = false
lockedLw.value = 1
visible.value = true
}
// 日期改变
const handleChange = () => {
if (chooseDate.value) {
showBtn.value = true
} else {
showBtn.value = false
}
}
// 下载文件
const downLoadFile = async (url: string) => {
return request({
method: 'get',
url: url,
responseType: 'blob',
headers: {
'Content-Type': 'application/json',
"Authorization": 'Bearer ' + Session.getToken()
}
})
}
// 导出薪资数据
const exportSalaryData = async () => {
if (!chooseDate.value) {
message.warning('请选择日期')
return
}
btnLoading.value = true
try {
const res = await downLoadFile(`/professional/file/exportSalary?chooseDate=${chooseDate.value}&state=${lockedLw.value}`)
// 处理返回的文件流
const blob = new Blob([res.data])
const elink = document.createElement('a')
elink.download = "薪资表.xls"
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
message.success('导出成功')
visible.value = false
} catch (error: any) {
message.error(error?.msg || '导出失败')
} finally {
btnLoading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,183 @@
<template>
<el-dialog v-model="visible" title="薪资导入" width="600px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
type="month"
format="YYYY-M"
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item>
<el-upload
v-if="showUpload"
:headers="headers"
class="upload-demo"
:action="url"
:limit="1"
:with-credentials="true"
:before-upload="beforeUpload"
>
<template #tip>
<div class="el-upload-list__item-name">{{ fileName }}</div>
</template>
<el-button
size="small"
v-if="permissions.professional_salary_import"
type="primary">选择文件
</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitUpload" :loading="btnLoading">导入</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import request from '/@/utils/request'
// Emits
const emit = defineEmits<{
(e: 'refreshData'): void
}>()
// 使用 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
})
// 消息提示
const message = useMessage()
// 对话框显示状态
const visible = ref(false)
// 表单数据
const chooseDate = ref('')
const url = ref('')
const showUpload = ref(false)
const fileList = ref<any[]>([])
const btnLoading = ref(false)
const fileName = ref('')
const files = ref<File | null>(null)
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 日期改变
const handleChange = () => {
if (chooseDate.value) {
showUpload.value = true
url.value = `/professional/file/importSalary?chooseDate=${chooseDate.value}`
} else {
showUpload.value = false
}
}
// 初始化
const init = () => {
fileList.value = []
files.value = null
fileName.value = ''
chooseDate.value = ''
showUpload.value = false
visible.value = true
}
// 上传前验证
const beforeUpload = (file: File) => {
files.value = 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 < 20
if (!extension && !extension2) {
message.warning('上传模板只能是 xls、xlsx格式!')
return false
}
if (!isLt2M) {
message.warning('上传模板大小不能超过 20MB!')
return false
}
fileName.value = file.name
return false // 返回false不会自动上传
}
// 导入提交
const submitUpload = async () => {
btnLoading.value = true
if (fileName.value === "") {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
if (!files.value) {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
const fileFormData = new FormData()
fileFormData.append('file', files.value, fileName.value)
try {
const res = await request({
url: url.value,
method: 'post',
data: fileFormData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.data.data && res.data.data.code == 1) {
message.error(`导入失败,${res.data.data.title}: ${res.data.data.msg}`)
} else {
message.success("导入成功")
visible.value = false
emit('refreshData')
}
} catch (error: any) {
message.error(error?.msg || '导入失败')
} finally {
btnLoading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,183 @@
<template>
<el-dialog v-model="visible" title="税金导入" width="600px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
type="month"
format="YYYY-M"
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item>
<el-upload
v-if="showUpload"
:headers="headers"
class="upload-demo"
:action="url"
:limit="1"
:with-credentials="true"
:before-upload="beforeUpload"
>
<template #tip>
<div class="el-upload-list__item-name">{{ fileName }}</div>
</template>
<el-button
size="small"
v-if="permissions.professional_salary_import"
type="primary">选择文件
</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitUpload" :loading="btnLoading">导入</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import request from '/@/utils/request'
// Emits
const emit = defineEmits<{
(e: 'refreshData'): void
}>()
// 使用 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
})
// 消息提示
const message = useMessage()
// 对话框显示状态
const visible = ref(false)
// 表单数据
const chooseDate = ref('')
const url = ref('')
const showUpload = ref(false)
const fileList = ref<any[]>([])
const btnLoading = ref(false)
const fileName = ref('')
const files = ref<File | null>(null)
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 日期改变
const handleChange = () => {
if (chooseDate.value) {
showUpload.value = true
url.value = `/professional/file/importTaxSalary?chooseDate=${chooseDate.value}`
} else {
showUpload.value = false
}
}
// 初始化
const init = () => {
fileList.value = []
files.value = null
fileName.value = ''
chooseDate.value = ''
showUpload.value = false
visible.value = true
}
// 上传前验证
const beforeUpload = (file: File) => {
files.value = 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 < 20
if (!extension && !extension2) {
message.warning('上传模板只能是 xls、xlsx格式!')
return false
}
if (!isLt2M) {
message.warning('上传模板大小不能超过 20MB!')
return false
}
fileName.value = file.name
return false // 返回false不会自动上传
}
// 导入提交
const submitUpload = async () => {
btnLoading.value = true
if (fileName.value === "") {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
if (!files.value) {
message.warning('请选择要上传的文件!')
btnLoading.value = false
return
}
const fileFormData = new FormData()
fileFormData.append('file', files.value, fileName.value)
try {
const res = await request({
url: url.value,
method: 'post',
data: fileFormData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.data.data && res.data.data.code == 1) {
message.error(`导入失败,${res.data.data.title}: ${res.data.data.msg}`)
} else {
message.success("导入成功")
visible.value = false
emit('refreshData')
}
} catch (error: any) {
message.error(error?.msg || '导入失败')
} finally {
btnLoading.value = false
}
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,378 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
size="small"
v-if="permissions.professional_salary_import"
type="primary"
@click="handleImportBaseSalary">人事薪资导入
</el-button>
<el-button
size="small"
v-if="permissions.professional_salary_finance_import"
type="primary"
@click="handleExportSalart">薪资导出
</el-button>
<el-button
size="small"
v-if="permissions.professional_salary_finance_import"
type="primary"
@click="handleImportTaxSalary">税金导入
</el-button>
<el-button
type="primary"
size="small"
v-if="permissions.professional_seach_auth"
@click="canSearch(1)">设置可查询
</el-button>
<el-button
type="primary"
size="small"
v-if="permissions.professional_seach_auth"
@click="canSearch(0)">设置不可查询
</el-button>
<el-button
type="primary"
size="small"
v-if="permissions.professional_professionalsalaries_del"
@click="delbatch">批量删除
</el-button>
</div>
</el-row>
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
placeholder="请输入工号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
placeholder="请输入姓名"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input
v-model="search.idCard"
placeholder="请输入身份证号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="年份" prop="nf">
<el-date-picker
v-model="search.nf"
type="year"
format="YYYY"
value-format="YYYY"
placeholder="请选择年份"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="月份" prop="yf">
<el-date-picker
v-model="search.yf"
type="month"
format="M"
value-format="M"
placeholder="请选择月份"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="岗位类别" prop="stationTypeId">
<el-select
v-model="search.stationTypeId"
filterable
clearable
placeholder="请选择岗位类别"
style="width: 200px"
>
<el-option
v-for="item in stationLevelList"
:key="item.id"
:label="item.levelName"
:value="item.id"
/>
</el-select>
</el-form-item>
</template>
</template>
</search-form>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="data-table"
@selection-change="selectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="numId" label="编号" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="postSalary" label="岗位工资" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="nf" label="年份" width="100" align="center" />
<el-table-column prop="yf" label="月份" width="100" align="center" />
<el-table-column prop="idCard" label="身份证号" min-width="180" align="center" show-overflow-tooltip />
<el-table-column prop="shouldPay" label="应发工资" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="realWage" label="实发工资" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="normalView" label="职工查看" width="120" align="center">
<template #default="scope">
<el-tag :type="scope.row.normalView === '1' ? 'success' : 'info'">
{{ scope.row.normalView === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="document"
link
type="primary"
@click="handleEdit(scope.row)">查看
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
<!-- 子组件 -->
<salary-info ref="salaryInfoRef" />
<import-base-salary ref="importBaseSalaryRef" @refreshData="handleFilter" />
<export-base-salary ref="exportBaseSalaryRef" />
<import-tax-salary ref="importTaxSalaryRef" @refreshData="handleFilter" />
</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, useMessageBox } from '/@/hooks/message'
import { fetchList, delBatch, setCanSearch, checkAuth } from '/@/api/professional/teachersalary'
import { getStationLevelList } from '/@/api/professional/professionalstationlevelconfig'
import SalaryInfo from './salaryInfo.vue'
import ImportBaseSalary from './importBaseSalary.vue'
import ExportBaseSalary from './exportBaseSalary.vue'
import ImportTaxSalary from './importTaxSalary.vue'
// 使用 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 tableRef = ref()
const searchFormRef = ref()
const salaryInfoRef = ref()
const importBaseSalaryRef = ref()
const exportBaseSalaryRef = ref()
const importTaxSalaryRef = ref()
// 搜索表单显示状态
const showSearch = ref(true)
// 岗位类别列表
const stationLevelList = ref<any[]>([])
// 选中的行
const selectList = ref<any[]>([])
// 搜索表单数据
const search = reactive({
teacherNo: '',
realName: '',
idCard: '',
nf: '',
yf: '',
stationTypeId: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: search
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 初始化
onMounted(() => {
init()
})
// 初始化数据
const init = async () => {
try {
const [stationRes, authRes] = await Promise.all([
getStationLevelList(),
checkAuth()
])
stationLevelList.value = stationRes.data || []
// 根据权限设置表格选项(如果需要)
// if (authRes.data.data === false) {
// // 设置普通表格选项
// }
} catch (error) {
// 初始化失败
}
}
// 查询
const handleFilter = () => {
getDataList() // 查询后跳转到第一页
}
// 选择变化
const selectionChange = (selection: any[]) => {
selectList.value = selection
}
// 查看
const handleEdit = (row: any) => {
salaryInfoRef.value?.init(row)
}
// 导入人事薪资
const handleImportBaseSalary = () => {
importBaseSalaryRef.value?.init()
}
// 导入税金
const handleImportTaxSalary = () => {
importTaxSalaryRef.value?.init()
}
// 导出薪资
const handleExportSalart = () => {
exportBaseSalaryRef.value?.init()
}
// 批量删除
const delbatch = () => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
return
}
messageBox.confirm(`确认删除当前${selectList.value.length}条记录`).then(async () => {
try {
const data = { selectList: selectList.value }
const response = await delBatch(data)
if (response.data.code == -1) {
message.error(response.data.data)
} else {
message.info("删除成功")
getDataList(false) // 删除后保持当前页
}
} catch (error: any) {
message.error(error?.msg || '删除失败')
}
}).catch(() => {
// 用户取消
})
}
// 设置可查询/不可查询
const canSearch = (val: number) => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
return
}
const params = {
canSearch: val,
selectList: selectList.value
}
messageBox.confirm('确认设置?').then(async () => {
try {
await setCanSearch(params)
message.success("设置成功")
getDataList(false) // 设置后保持当前页
} catch (error: any) {
message.error(error?.msg || '设置失败')
}
}).catch(() => {
// 用户取消
})
}
</script>
<style lang="scss" scoped>
.data-table {
width: 100% !important;
}
</style>

View File

@@ -0,0 +1,320 @@
<template>
<el-dialog v-model="visible" width="100%" v-loading="baseLoading" top="0" :close-on-click-modal="false" destroy-on-close>
<!--基本信息-->
<el-card shadow="hover">
<el-row :span="24">
<el-col>
<el-table
:data="salaryData.baseInfo"
style="width: 100%">
<el-table-column
prop="realName"
label="姓名"
width="80">
</el-table-column>
<el-table-column
prop="idCard"
label="身份证号"
width="200">
</el-table-column>
<el-table-column
label="年份"
width="300">
<template #default>
<el-date-picker
v-model="nowUser.nf"
type="year"
format="YYYY"
value-format="YYYY"
style="width: 200px"
placeholder="选择年">
</el-date-picker>
</template>
</el-table-column>
<el-table-column
label="月份"
width="300">
<template #default>
<el-date-picker
v-model="nowUser.yff"
type="month"
format="M"
value-format="M"
style="width: 200px"
placeholder="选择月">
</el-date-picker>
</template>
</el-table-column>
<el-table-column
label="操作"
width="200">
<template #default>
<el-button @click="searchUserInfo" type="primary" size="small">搜索</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-card>
<el-card shadow="hover">
<!--应发部分-->
<el-row v-if="showAllContent">
<el-col :span="24">
<el-table
:data="salaryData.baseInfo"
size="small"
border
>
<el-table-column label="应发部分">
<el-table-column prop="postSalary" label="岗位工资" min-width="60" align="center"></el-table-column>
<el-table-column prop="payWage" label="薪级工资" min-width="60" align="center"></el-table-column>
<el-table-column label="见习期工资" prop="studentPay" min-width="60" align="center"></el-table-column>
<el-table-column prop="liveAllowance" label="生活补贴" min-width="60" align="center"></el-table-column>
<el-table-column prop="postAllowance" label="岗位津贴" min-width="60" align="center"></el-table-column>
<el-table-column label="住房(租金)补贴" prop="houseSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="新职工住房补贴" prop="newHouseSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="回民补贴" prop="huiSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="养老保险补贴" prop="oldSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="教龄津贴" prop="ageAllowance" min-width="60" align="center"></el-table-column>
<el-table-column label="特教补贴" prop="specialSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="特级教师津贴" prop="teacherAllowance" min-width="60" align="center"></el-table-column>
<el-table-column label="特岗津贴(一)" prop="sPostAllowance1" min-width="60" align="center"></el-table-column>
<el-table-column label="特岗津贴(二)" prop="sPostAllowance2" min-width="60" align="center"></el-table-column>
<el-table-column label="其他" prop="other" min-width="60" align="center"></el-table-column>
<el-table-column label="奖励性绩效工资" prop="meritPay" min-width="60" align="center"></el-table-column>
<el-table-column label="乡镇工作补贴" prop="villageSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="临时性补贴" prop="temporarySubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="上下班交通补贴" prop="trafficSubsidies" min-width="60" align="center"></el-table-column>
<el-table-column label="保留津贴" prop="keepAllowance" min-width="60" align="center"></el-table-column>
<el-table-column label="补发工资" prop="retroactivePay" min-width="60" align="center"></el-table-column>
<el-table-column label="应休未休假补贴" prop="vacationMoney" min-width="60" align="center"></el-table-column>
<el-table-column label="应发工资" prop="shouldPay" min-width="60" align="center"></el-table-column>
<el-table-column label="基础专项绩效" prop="baseSalary" min-width="60" align="center"></el-table-column>
<el-table-column label="基础工资应税收入" prop="shouldTaxMoney" min-width="60" align="center"></el-table-column>
</el-table-column>
</el-table>
<el-tag type="danger">基础工资应税收入= 岗位工资 +薪级工资+见习期工资+生活补贴+岗位津贴+教龄津贴+特教补贴+特级教师津贴+特岗津贴1+特岗津贴2+奖励绩效性工资+乡镇工作补贴+临时补贴+保留津贴+应休未休假-个人补缴-其他扣款-医疗救助金 </el-tag>
</el-col>
</el-row>
<!--应扣部分-->
<el-row v-if="showAllContent">
<el-col :span="24">
<el-table
size="small"
border
:data="salaryData.baseInfo"
style="width: 100%">
<el-table-column label="应扣部分">
<el-table-column label="住房公积金" width="90" prop="houseFund"></el-table-column>
<el-table-column label="医疗保险金" width="90" prop="medicalInsurance"></el-table-column>
<el-table-column label="失业保险金" width="90" prop="unemployInsurance"></el-table-column>
<el-table-column label="养老保险金" width="90" prop="endowInsurance"></el-table-column>
<el-table-column label="工会费" width="60" prop="unionFee"></el-table-column>
<el-table-column label="儿童统筹" width="90" prop="childrenWhole"></el-table-column>
<el-table-column label="个人所得税" width="90" prop="personalTax"></el-table-column>
<el-table-column label="其他扣款" width="90" prop="otherDeduction"></el-table-column>
<el-table-column label="病事假扣款" width="90" prop="sickDeduction"></el-table-column>
<el-table-column label="医疗救助基金" width="90" prop="medicalFund"></el-table-column>
<el-table-column label="工伤保险" width="90" prop="inductrialInjury"></el-table-column>
<el-table-column label="个人补缴" width="90" prop="personalPay"></el-table-column>
<el-table-column label="应扣合计" width="90" prop="withhold"></el-table-column>
</el-table-column>
</el-table>
<el-tag>个人所得税 = 个税计算数据中的 累计应补(退)税额 </el-tag>
</el-col>
</el-row>
<!--劳务费-->
<el-row>
<el-col :span="24">
<el-table size="small" border :data="allProjectData" show-summary>
<el-table-column label="造单收入清单">
<el-table-column label="造单部门" prop="deptName"></el-table-column>
<el-table-column label="造单人" prop="createName"></el-table-column>
<el-table-column label="项目编号" prop="projectNo"></el-table-column>
<el-table-column label="项目名" prop="title"></el-table-column>
<el-table-column label="金额" prop="realMoney"></el-table-column>
<el-table-column label="付讫时间" prop="payTime"></el-table-column>
<el-table-column label="免税或暂不交税" prop="freeTax">
<template #default="scope">
<span v-if="scope.row.freeTax=='1'"></span>
</template>
</el-table-column>
</el-table-column>
</el-table>
</el-col>
</el-row>
<!--专项扣除-->
<el-row>
<el-col :span="24">
<el-table
size="small"
border
:data="salaryExtendData"
style="width: 100%">
<el-table-column label="个税计算数据">
<el-table-column prop="currentIncome" label="应税收入" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalIncome" label="累计收入额" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalDeduction" label="累计减除费用" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalSpecialDecution" label="累计专项扣除" min-width="60" align="center"></el-table-column>
<el-table-column label="累计专项扣除附加" align="center">
<el-table-column prop="totalChildEdu" label="累计子女教育" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalConEdu" label="累计继续教育" min-width="60" align="center"> </el-table-column>
<el-table-column prop="totalHouseInterest" label="累计住房贷款利息" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalHouse" label="累计住房租金" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalSupportOld" label="累计赡养老人" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalBabyMoney" label="累计婴幼儿照护费用" min-width="60" align="center"></el-table-column>
</el-table-column>
<el-table-column prop="totalTaxMoney" label="累计应纳税所得额" min-width="60" align="center"></el-table-column>
<el-table-column prop="taxRate" label="税率" min-width="60" align="center"></el-table-column>
<el-table-column prop="quickDecution" label="速算扣除数" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalRealTaxPay" label="累计应扣缴税额" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalPrePayTax" label="累计已预缴税额" min-width="60" align="center"></el-table-column>
<el-table-column prop="totalRetrieveTax" label="累计应补(退)税额" min-width="60" align="center"></el-table-column>
</el-table-column>
</el-table>
</el-col>
</el-row>
<!--实发合计-->
<el-row v-if="showAllContent">
<el-col :span="24">
<el-table
size="small"
border
:data="[staticsData]"
empty-text=" ">
<el-table-column label="小计">
<el-table-column label="应发工资" prop="shouldPay"></el-table-column>
<el-table-column label="应扣合计" prop="shouldDedu"></el-table-column>
<el-table-column label="实发工资" prop="realWage"></el-table-column>
<el-table-column label="造单收入" prop="orderMoney"></el-table-column>
</el-table-column>
</el-table>
<el-tag type="primary">实发工资= 应发工资-应扣合计 </el-tag>
</el-col>
</el-row>
</el-card>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { queryUserInfo, queryExtendSalaryInfo, checkAuth } from '/@/api/professional/teachersalary'
// 对话框显示状态
const visible = ref(false)
// 数据
const salaryData = reactive({
baseInfo: [] as any[]
})
const nowUser = ref<any>({})
const baseLoading = ref(false)
const salaryExtendData = ref<any[]>([])
const allProjectData = ref<any[]>([])
const staticsData = reactive({
shouldPay: 0,
shouldDedu: 0,
realWage: 0,
orderMoney: 0,
personTax: 0
})
const showAllContent = ref(false)
// 检查权限
const checkAuthMethod = async () => {
try {
const res = await checkAuth()
showAllContent.value = res.data.data
} catch (error) {
// 检查权限失败
}
}
// 构建用户信息
const makeUserInfo = (data: any) => {
const row = JSON.parse(JSON.stringify(data))
salaryData.baseInfo = []
row.realName = (row.realName == "" || row.realName == null ? row.userName : row.realName)
salaryData.baseInfo.push(row)
staticsData.shouldPay = row.shouldPay || 0
staticsData.shouldDedu = row.withhold || 0
staticsData.realWage = row.realWage || 0
staticsData.personTax = (row.personalTax as number) || 0
}
// 查询扩展薪资信息
const queryExtendSalaryInfoMethod = async (row: any) => {
salaryExtendData.value = []
const params = { nf: row.nf, yf: row.yf, teacherNo: row.teacherNo }
try {
const res = await queryExtendSalaryInfo(params)
salaryExtendData.value.push(res.data.data.salaryTax)
allProjectData.value = res.data.data.allProject
staticsData.orderMoney = res.data.data.totalMoney
} catch (error) {
// 查询失败
}
}
// 搜索用户信息
const searchUserInfo = async () => {
baseLoading.value = true
const data = { idCard: nowUser.value.idCard, nf: nowUser.value.nf, yf: nowUser.value.yff }
try {
const response = await queryUserInfo(data)
if (response.data.data == null) {
salaryData.baseInfo = []
const obj: any = {}
obj.realName = nowUser.value.realName
obj.idCard = nowUser.value.idCard
salaryData.baseInfo.push(obj)
salaryExtendData.value = []
Object.assign(staticsData, {
shouldPay: 0,
shouldDedu: 0,
realWage: 0,
orderMoney: 0,
personTax: 0
})
} else {
const resData = response.data.data
resData.realName = nowUser.value.realName
makeUserInfo(resData)
await queryExtendSalaryInfoMethod(resData)
}
} catch (error) {
// 查询失败
} finally {
baseLoading.value = false
}
}
// 初始化
const init = (row: any) => {
visible.value = true
nextTick(() => {
checkAuthMethod()
nowUser.value = JSON.parse(JSON.stringify(row))
nowUser.value.yff = row.yf
makeUserInfo(row)
queryExtendSalaryInfoMethod(row)
})
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,300 @@
<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="getDataList">
<el-form-item label="单位名称" prop="companyName">
<el-input
v-model="state.queryForm.companyName"
placeholder="请输入单位名称"
clearable
/>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
查询
</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_outercompany_add">
</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"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="companyName" label="单位名称" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="允许时段" min-width="250" align="center">
<template #default="scope">
<span v-if="scope.row.allowStartTime && scope.row.allowEndTime">
{{ scope.row.allowStartTime }} - {{ scope.row.allowEndTime }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_outercompany_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_outercompany_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="单位名称" prop="companyName">
<el-input
v-model="form.companyName"
placeholder="请输入单位名称"
clearable
/>
</el-form-item>
<el-form-item label="开始时段" prop="allowStartTime">
<el-date-picker
v-model="form.allowStartTime"
type="date"
placeholder="请选择开始时段"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="截止时段" prop="allowEndTime">
<el-date-picker
v-model="form.allowEndTime"
type="date"
placeholder="请选择截止时段"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/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 tableRef = ref()
const formRef = ref()
const queryRef = ref()
// 搜索显示
const showSearch = ref(true)
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
companyName: '',
allowStartTime: '',
allowEndTime: '',
companyType: '0'
})
// 表单验证规则
const formRules = {
companyName: [
{ required: true, message: '请输入单位名称', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList({
...params,
companyType: '0'
})
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {
companyName: ''
}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 重置查询
const resetQuery = () => {
queryRef.value?.resetFields()
getDataList()
}
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
companyName: '',
allowStartTime: '',
allowEndTime: '',
companyType: '0'
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
try {
const response = await getObj(row.id)
Object.assign(form, {
id: response.data.id,
companyName: response.data.companyName || '',
allowStartTime: response.data.allowStartTime || '',
allowEndTime: response.data.allowEndTime || '',
companyType: response.data.companyType || '0'
})
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,300 @@
<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="getDataList">
<el-form-item label="单位名称" prop="companyName">
<el-input
v-model="state.queryForm.companyName"
placeholder="请输入单位名称"
clearable
/>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
查询
</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_outercompany_add">
</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"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="companyName" label="单位名称" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="允许时段" min-width="250" align="center">
<template #default="scope">
<span v-if="scope.row.allowStartTime && scope.row.allowEndTime">
{{ scope.row.allowStartTime }} - {{ scope.row.allowEndTime }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_outercompany_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_outercompany_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="单位名称" prop="companyName">
<el-input
v-model="form.companyName"
placeholder="请输入单位名称"
clearable
/>
</el-form-item>
<el-form-item label="开始时段" prop="allowStartTime">
<el-date-picker
v-model="form.allowStartTime"
type="date"
placeholder="请选择开始时段"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="截止时段" prop="allowEndTime">
<el-date-picker
v-model="form.allowEndTime"
type="date"
placeholder="请选择截止时段"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/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 tableRef = ref()
const formRef = ref()
const queryRef = ref()
// 搜索显示
const showSearch = ref(true)
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
companyName: '',
allowStartTime: '',
allowEndTime: '',
companyType: '2'
})
// 表单验证规则
const formRules = {
companyName: [
{ required: true, message: '请输入单位名称', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList({
...params,
companyType: '2'
})
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {
companyName: ''
}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 重置查询
const resetQuery = () => {
queryRef.value?.resetFields()
getDataList()
}
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
companyName: '',
allowStartTime: '',
allowEndTime: '',
companyType: '2'
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
try {
const response = await getObj(row.id)
Object.assign(form, {
id: response.data.id,
companyName: response.data.companyName || '',
allowStartTime: response.data.allowStartTime || '',
allowEndTime: response.data.allowEndTime || '',
companyType: response.data.companyType || '2'
})
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,154 @@
<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="getDataList">
<el-form-item label="单位名称" prop="companyName">
<el-input
v-model="state.queryForm.companyName"
placeholder="请输入单位名称"
clearable
/>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
查询
</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_outercompany_add">
</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"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="companyName" label="单位名称" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="允许时段" min-width="250" align="center">
<template #default="scope">
<span v-if="scope.row.allowStartTime && scope.row.allowEndTime">
{{ scope.row.allowStartTime }} - {{ scope.row.allowEndTime }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<!-- 操作列已注释此页面为只读 -->
<!-- <el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_outercompany_edit"
icon="edit-pen"
text
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_outercompany_del"
icon="delete"
text
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"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/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
})
// 表格引用
const tableRef = ref()
const queryRef = ref()
// 搜索显示
const showSearch = ref(true)
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList({
...params,
companyType: '1'
})
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {
companyName: ''
}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 重置查询
const resetQuery = () => {
queryRef.value?.resetFields()
getDataList()
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,919 @@
<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 {
fetchList,
getObj,
addObj,
putObj,
delObj,
batchDel,
resetPassWord
} from '/@/api/professional/outercompanyemployee'
import { getList as getCompanyList } from '/@/api/professional/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: '0'
})
// 特殊处理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: '0' })
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)) {
messageBox.alert('重置后密码为: ' + pw, '重置密码')
} else {
messageBox.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 addObj(form)
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 uploadAvatarDialog = (row: any) => {
rowData.value = row
fileList.value = []
uploadData.username = row.employeeNo
dialogAvatarVisible.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=0`,
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: "0"
}
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) {
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>

View File

@@ -0,0 +1,913 @@
<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/outercompanyemployee'
import { getList as getCompanyList } from '/@/api/professional/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>

View File

@@ -0,0 +1,871 @@
<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="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/outercompanyemployee'
import { getList as getCompanyList } from '/@/api/professional/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: '',
inoutFlag: '',
remarks: ''
})
// 表单验证规则 - 培训单位没有必填验证
const formRules = {
companyId: [
{ 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: '1' // 培训单位
})
// 特殊处理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: '1' })
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: '',
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 || '',
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=1`,
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: "1"
}
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>

View File

@@ -0,0 +1,254 @@
<template>
<el-dialog v-model="dialogEmptyFromVisible" title="教职工信息导出" width="80%">
<el-form style="margin-left: 7px" :inline="true">
<el-form-item>
<el-tag>导出教职工人数,依据'职工信息'页面检索条件,如部门,职称等级等条件</el-tag>
<el-tag type="warning">*请在'职工信息'页面点击搜索后,再点开当前导出页面</el-tag>
</el-form-item>
<el-form-item label="是否退休">
<el-select
v-model="tiedTag"
filterable
reserve-keyword
clearable
>
<el-option
v-for="item in YES_OR_NO"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="exportTeacherInfo"
:loading="exportLoading"
size="small">导出</el-button>
</el-form-item>
</el-form>
<!-- 基本信息 -->
<div class="div_stuData" style="margin-bottom: 5px;margin-top: 5px;">基础信息
<el-checkbox v-model="allCheckTeacherBaseInfo" style="margin-left:20px">全选</el-checkbox>
</div>
<el-tabs type="border-card" tab-position="left">
<el-form ref="baseForm" label-width="120px">
<el-row>
<el-checkbox-group v-model="checkedTeacherBaseInfo">
<el-checkbox v-for="(item,index) in teacherBasicCheckData" :label="item.value" :key="index">{{item.label}}</el-checkbox>
</el-checkbox-group>
</el-row>
</el-form>
</el-tabs>
<!-- 岗位信息 -->
<div class="div_stuData" style="margin-bottom: 5px;margin-top: 5px;">岗位信息
<el-checkbox v-model="allCheckStationInfo">全选</el-checkbox>
</div>
<el-tabs type="border-card" tab-position="left">
<el-form ref="baseForm" label-width="120px">
<el-row>
<el-checkbox-group v-model="checkedTeacherStationInfo">
<el-checkbox v-for="(item, index) in teacherStationData" :key="index" :label="item.value">{{item.label}}</el-checkbox>
</el-checkbox-group>
</el-row>
</el-form>
</el-tabs>
<!-- 其他 -->
<div class="div_stuData" style="margin-bottom: 5px;margin-top: 5px;">其他
<el-checkbox v-model="allCheckedother" style="margin-left: 20px">全选</el-checkbox>
</div>
<el-tabs type="border-card" tab-position="left">
<el-form ref="baseForm" label-width="120px">
<el-row>
<el-checkbox-group v-model="checkedTeacherOtherInfo">
<el-checkbox v-for="(item, index) in teacherOtherData" :key="index" :label="item.value">{{item.label}}</el-checkbox>
</el-checkbox-group>
</el-row>
</el-form>
</el-tabs>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElNotification } from 'element-plus'
import global from '/@/components/tools/commondict.vue'
import request from '/@/utils/request'
// Props
const props = defineProps<{
search: any
}>()
// 响应式数据
const tiedTag = ref('')
const YES_OR_NO = global.YES_OR_NO
const exportLoading = ref(false)
const dialogEmptyFromVisible = ref(false)
const allCheckTeacherBaseInfo = ref(false)
const allCheckStationInfo = ref(false)
const allCheckedother = ref(false)
const checkedTeacherBaseInfo = ref<string[]>([])
const checkedTeacherStationInfo = ref<string[]>([])
const checkedTeacherOtherInfo = ref<string[]>([])
const teacherBasicCheckData = [
{label:'姓名',value:'realName_姓名'},
{label:'工号',value:'teacherNo_工号'},
{label:'部门',value:'deptCode_部门'},
{label:'性别',value:'sex_性别'},
{label:'身份证',value:'idCard_身份证'},
{label:'出生年月',value:'birthday_出生年月'},
{label:'年龄',value:'age_年龄'},
{label:'手机',value:'telPhone_手机'},
{label:'手机2',value:'telPhoneTwo_手机2'},
{label:'宅电',value:'homePhone_宅电'},
{label:'民族',value:'national_民族'},
{label:'籍贯',value:'nativePlace_籍贯'},
{label:'出生地',value:'birthPlace_出生地'},
{label:'健康状况',value:'health_健康状况'},
{label:'特长',value:'speciality_特长'},
{label:'银行卡号',value:'bankNo_银行卡号'},
{label:'开户行',value:'bankOpen_开户行'},
{label:'家庭住址',value:'homeAddress_家庭住址'},
{label:'授课类型',value:'teacherCate_授课类型'},
{label:'允许进出',value:'inoutFlag_允许进出'},
{label:'进出情况备注',value:'inoutRemarks_进出情况备注'},
]
const teacherStationData = [
{label:'教师类型',value:'teacherTypeName_教师类型'},
{label:'党支部',value:'branchName_党支部'},
{label:'用工性质',value:'employmentNatureName_用工性质'},
{label:'岗位类别',value:'stationTypeName_岗位类别'},
{label:'岗位级别',value:'stationLevelName_岗位级别'},
{label:'职务级别',value:'stationDutyLevelName_职务级别'},
{label:'任现岗位职级时间',value:'stationDate_任现岗位职级时间'},
{label:'在职情况',value:'atStationName_在职情况'},
{label:'职务',value:'dutyDesc_职务'},
{label:'参加工作时间',value:'workDate_参加工作时间'},
{label:'退休时间',value:'retireDate_退休时间'},
{label:'职位类别',value:'teacherClassify_职位类别'},
{label:'任现职务时间',value:'dutyDate_任现职务时间'},
{label:'进编时间',value:'entryDutyDate_进编时间'},
{label:'进校时间',value:'entrySchoolDate_进校时间'},
]
const teacherOtherData = [
{label:'职业资格工种',value:'zyzg_职业资格工种'},
{label:'职业资格等级',value:'zyzgLevel_职业资格等级'},
{label:'政治面貌',value:'zzmm_政治面貌'},
{label:'学历',value:'xl_学历'},
{label:'学位',value:'xw_学位'},
{label:'职称',value:'zc_职称'},
{label:'高校教师资格证',value:'highCer_高校教师资格证'},
{label:'中等教师资格证',value:'midCer_中等教师资格证'},
{label:'教师上岗证',value:'priCer_教师上岗证'},
{label:'共同居住人',value:'livewith_共同居住人'},
]
// 导出文件函数
const exportForModel = async (params: any, fileNameStr: string, url: string) => {
try {
const res = await request({
method: 'post',
url: url,
data: params,
responseType: 'blob',
headers: {
'Content-Type': 'application/json'
}
})
const blob = new Blob([res.data])
const fileName = fileNameStr
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)
} catch (err) {
ElNotification.error({
title: '错误',
message: '导出失败',
})
}
}
// Watch
watch(allCheckTeacherBaseInfo, (newVal) => {
if (newVal) {
checkedTeacherBaseInfo.value = teacherBasicCheckData.map(item => item.value)
} else {
checkedTeacherBaseInfo.value = []
}
})
watch(allCheckStationInfo, (newVal) => {
if (newVal) {
checkedTeacherStationInfo.value = teacherStationData.map(item => item.value)
} else {
checkedTeacherStationInfo.value = []
}
})
watch(allCheckedother, (newVal) => {
if (newVal) {
checkedTeacherOtherInfo.value = teacherOtherData.map(item => item.value)
} else {
checkedTeacherOtherInfo.value = []
}
})
// 方法
const init = () => {
tiedTag.value = props.search.tied
dialogEmptyFromVisible.value = true
}
const exportTeacherInfo = async () => {
if (checkedTeacherBaseInfo.value.length == 0 && checkedTeacherStationInfo.value.length == 0 && checkedTeacherOtherInfo.value.length == 0) {
ElNotification({
title: '警告',
message: '请选择要导出的字段',
type: 'warning',
duration: 3000
})
return
}
exportLoading.value = true
const searchData = JSON.parse(JSON.stringify(props.search))
searchData.tied = tiedTag.value
const params = {
"checkedTeacherBaseInfo": checkedTeacherBaseInfo.value,
"checkedTeacherStationInfo": checkedTeacherStationInfo.value,
"checkedTeacherOtherInfo": checkedTeacherOtherInfo.value,
"teacherBaseDTO": searchData
}
await exportForModel(params, "教职工信息.xls", "/professional/teacherbase/exportTeacherInfoBySelf")
setTimeout(() => {
exportLoading.value = false
}, 3000)
}
// 暴露方法给父组件
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,82 @@
<template>
<el-dialog v-model="visible" title="导入" width="80%" append-to-body>
<el-row>
<el-col>
<a href="excel/dictlist.xlsx" rel="external nofollow" download="职工信息字典下载">
<el-button style="margin-left: 20px" size="small" type="success">职工信息字典下载</el-button>
</a>
</el-col>
</el-row>
<el-row>
<el-col>
<el-tag>导入时部分字段需严格按照字典值填写</el-tag>
</el-col>
</el-row>
<el-row>
<el-upload
class="upload-demo"
action="/professional/file/importTeacherInfoSimple"
:headers="headers"
:accept="'.xls,.xlsx'"
:on-success="handleUploadSuccess"
:on-error="handleAvatarError"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">只能上传后缀为xls,xlsx的文件</div>
</template>
</el-upload>
</el-row>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ElNotification } from 'element-plus'
import { Session } from '/@/utils/storage'
// 响应式数据
const visible = ref(false)
// 计算属性
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 方法
const init = () => {
visible.value = true
}
const handleUploadSuccess = () => {
visible.value = false
ElNotification({
title: '成功',
message: '导入成功',
type: 'success',
})
}
const handleAvatarError = (err: any) => {
const result = JSON.parse(err.message)
if (result.code == "1") {
ElNotification.error({
title: '错误',
message: result.msg,
duration: 30000
})
}
}
// 暴露方法给父组件
defineExpose({
init
})
</script>
<style scoped>
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,842 @@
<template>
<el-dialog v-model="educationDialogFromVisible" width="80%" :title="waitShenheForm.title" append-to-body>
<!--2.1 教师资格证-->
<el-form v-if="waitShenheForm.a" ref="teacherCertificateFormRef" :model="waitShenheForm.form" :rules="teacherCertificateRules" label-width="120px">
<el-form-item label="类型" prop="certificateConfId">
<el-select v-model="waitShenheForm.form.certificateConfId" placeholder="请选择类型" style="width: 100%">
<el-option
v-for="item in certificateTypeList"
:key="item.id"
:label="item.cretificateName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="证书编号" prop="certificateNumber">
<el-input v-model="waitShenheForm.form.certificateNumber" placeholder="请输入证书编号" />
</el-form-item>
<el-form-item label="证明材料">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="materialUrlFrom.url"
:file-list="materialUrlFrom.fileListA"
:on-success="materiaUploadSuccessA"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(0)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
</el-form-item>
</el-form>
<!--2.2 学历-->
<el-form v-if="waitShenheForm.b" ref="educationFormRef" :model="waitShenheForm.form" :rules="educationRules" label-width="120px">
<el-form-item label="毕业时间" prop="graduateTime">
<el-date-picker
v-model="waitShenheForm.form.graduateTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="教育类型" prop="type">
<el-select v-model="waitShenheForm.form.type" placeholder="请选择教育类型" style="width: 100%">
<el-option
v-for="item in educationTypeList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学历" prop="qualificationConfigId">
<el-select v-model="waitShenheForm.form.qualificationConfigId" placeholder="请选择学历" style="width: 100%">
<el-option
v-for="item in qualificationList"
:key="item.id"
:label="item.qualificationName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学位" prop="degreeConfigId">
<el-select v-model="waitShenheForm.form.degreeConfigId" placeholder="请选择学位" style="width: 100%">
<el-option
v-for="item in degreeList"
:key="item.id"
:label="item.degreeName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="毕业学校" prop="graduateSchool">
<el-input v-model="waitShenheForm.form.graduateSchool" placeholder="请输入毕业学校" />
</el-form-item>
<el-form-item label="所学专业" prop="major">
<el-input v-model="waitShenheForm.form.major" placeholder="请输入所学专业" />
</el-form-item>
<el-form-item label="证书编码" prop="certificateNumber">
<el-input v-model="waitShenheForm.form.certificateNumber" placeholder="请输入证书编码" />
</el-form-item>
<el-form-item label="学历证书">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="materialUrlFrom.url"
:file-list="materialUrlFrom.fileListA"
:on-success="materiaUploadSuccessA"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</el-upload>
</el-form-item>
<el-form-item label="学位证书">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="materialUrlFrom.url"
:on-success="materiaUploadSuccessB"
:file-list="materialUrlFrom.fileListB"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(1)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
</el-form-item>
</el-form>
<!--2.3 职称-->
<el-form v-if="waitShenheForm.c" ref="proFormRef" :model="waitShenheForm.form" :rules="proRules" label-width="120px">
<el-form-item label="职称等级" prop="professionalTitleConfigId">
<el-select v-model="waitShenheForm.form.professionalTitleConfigId" placeholder="请选择职称等级" style="width: 100%">
<el-option
v-for="item in baseInfoAbout.proTitleList"
:key="item.id"
:label="item.professionalTitle"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="专业技术职务" prop="majorStation">
<el-select v-model="waitShenheForm.form.majorStation" placeholder="请选择专业技术职务" style="width: 100%">
<el-option
v-for="item in baseInfoAbout.majorStationList"
:key="item.id"
:label="item.majorStationName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="取证时间" prop="certificateTime">
<el-date-picker
v-model="waitShenheForm.form.certificateTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="职称任职时间" prop="inOfficeDate">
<el-date-picker
v-model="waitShenheForm.form.inOfficeDate"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="证书编号" prop="certificateNumber">
<el-input v-model="waitShenheForm.form.certificateNumber" placeholder="请输入证书编号" />
</el-form-item>
<el-form-item label="证明材料">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="materialUrlFrom.url"
:file-list="materialUrlFrom.fileListA"
:on-success="materiaUploadSuccessA"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(2)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
</el-form-item>
</el-form>
<!--2.4 职业-->
<el-form v-if="waitShenheForm.d" ref="workFormRef" :model="waitShenheForm.form" :rules="workRules" label-width="120px">
<el-form-item label="职业工种" prop="worker">
<el-select v-model="waitShenheForm.form.worker" filterable placeholder="请选择职业工种" style="width: 100%">
<el-option
v-for="item in baseInfoAbout.workTypeList"
:key="item.id"
:label="item.workName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="等级" prop="qualificationConfigId">
<el-select v-model="waitShenheForm.form.qualificationConfigId" placeholder="请选择等级" style="width: 100%">
<el-option
v-for="item in baseInfoAbout.qualificationList"
:key="item.id"
:label="item.levelName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="取证时间" prop="certificateTime">
<el-date-picker
v-model="waitShenheForm.form.certificateTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="证书编号" prop="certificateNumber">
<el-input v-model="waitShenheForm.form.certificateNumber" placeholder="请输入证书编号" />
</el-form-item>
<el-form-item label="材料1">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="materialUrlFrom.url"
:file-list="materialUrlFrom.fileListA"
:on-success="materiaUploadSuccessA"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(3)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
</el-form-item>
</el-form>
<!--2.5 综合表彰-->
<el-form v-if="waitShenheForm.e" ref="honorFormRef" :model="waitShenheForm.form" :rules="honorRules" label-width="120px">
<el-form-item label="荣誉" prop="honor">
<el-input v-model="waitShenheForm.form.honor" placeholder="请填写荣誉" />
</el-form-item>
<el-form-item label="表彰单位" prop="honorCompany">
<el-input v-model="waitShenheForm.form.honorCompany" placeholder="请填写表彰单位" />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker
v-model="waitShenheForm.form.year"
type="year"
placeholder="选择年份"
format="yyyy"
value-format="yyyy"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="证明材料">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="materialUrlFrom.url"
:on-success="materiaUploadSuccessA"
:file-list="materialUrlFrom.fileListA"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(4)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
</el-form-item>
</el-form>
<!--2.6 人员调动-->
<el-form v-if="waitShenheForm.f" ref="stationChangeFormRef" :model="waitShenheForm.form" :rules="stationChangeRules" label-width="120px">
<el-form-item label="工号">
<el-tag>{{waitShenheForm.form.teacherNo}}</el-tag>
</el-form-item>
<el-form-item label="姓名">
<el-tag>{{waitShenheForm.form.realName}}</el-tag>
</el-form-item>
<el-form-item label="原部门名称">
<el-tag disabled>{{waitShenheForm.form.deptName}}</el-tag>
</el-form-item>
<el-form-item label="现二级部门*">
<el-select v-model="newSecDeptCode" @change="getDeptListByParent" placeholder="请选择二级部门" style="width: 100%">
<el-option v-for="item in secDeptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="现子级部门">
<el-select v-model="newSecChildDeptCode" placeholder="请选择下级部门" clearable style="width: 100%">
<el-option v-for="item in newSecChildDeptCodeList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="调令日期" prop="changeDate">
<el-date-picker
v-model="waitShenheForm.form.changeDate"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="岗位类型" prop="pos">
<el-select v-model="waitShenheForm.form.pos" placeholder="请选择岗位类型" style="width: 100%">
<el-option
v-for="item in global.STATION_TYPE"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="waitShenheForm.form.remarks" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(5)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
</el-form-item>
</el-form>
<!--2.7 党员调动-->
<el-form v-if="waitShenheForm.g" ref="partChangeFormRef" :model="waitShenheForm.form" :rules="partChangeRules" label-width="120px">
<el-form-item label="工号">
<el-tag>{{waitShenheForm.form.teacherNo}}</el-tag>
</el-form-item>
<el-form-item label="用户名">
<el-tag>{{waitShenheForm.form.realName}}</el-tag>
</el-form-item>
<el-form-item label="原支部名">
<el-select disabled v-model="waitShenheForm.form.oldBranchName" style="width: 100%">
<el-option
v-for="item in baseInfoAbout.partBranchList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="现支部名" prop="branchName">
<el-select clearable v-model="waitShenheForm.form.branchName" placeholder="请选择现支部" style="width: 100%">
<el-option
v-for="item in baseInfoAbout.partBranchList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="党费" prop="partyFee">
<el-input-number v-model="waitShenheForm.form.partyFee" :precision="2" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="变动时间" prop="changeTime">
<el-date-picker
v-model="waitShenheForm.form.changeTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="waitShenheForm.form.remarks" placeholder="请输入备注" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(6)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { updateOtherInfo, getMyTeacherNo, getAllInfoAboutList } from '/@/api/professional/teacherbase'
import { checkLocked } from '/@/api/professional/professionalstatuslock'
import { getDeptListByLevelTwo, getDeptListByParent as getDeptListByParentApi } from '/@/api/basic/basicdept'
import { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf'
import { getAllTypeList } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig'
import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig'
import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
import global from '/@/components/tools/commondict.vue'
import { Session } from '/@/utils/storage'
// 字典数据
const certificateTypeList = ref<any[]>([])
const educationTypeList = ref<any[]>([])
const qualificationList = ref<any[]>([])
const degreeList = ref<any[]>([])
// 表单 ref
const teacherCertificateFormRef = ref()
const educationFormRef = ref()
const proFormRef = ref()
const workFormRef = ref()
const honorFormRef = ref()
const stationChangeFormRef = ref()
const partChangeFormRef = ref()
// 表单验证规则
const teacherCertificateRules = {
certificateConfId: [{ required: true, message: '请选择类型', trigger: 'change' }],
certificateNumber: [{ required: true, message: '请输入证书编号', trigger: 'blur' }]
}
const educationRules = {
graduateTime: [{ required: true, message: '请输入毕业时间', trigger: 'change' }],
type: [{ required: true, message: '请选择教育类型', trigger: 'change' }],
graduateSchool: [{ required: true, message: '请输入毕业学校', trigger: 'blur' }],
major: [{ required: true, message: '请输入所学专业', trigger: 'blur' }],
certificateNumber: [{ required: true, message: '请输入证书编码', trigger: 'blur' }]
}
const proRules = {
professionalTitleConfigId: [{ required: true, message: '请选择职称等级', trigger: 'change' }],
majorStation: [{ required: true, message: '请选择专业技术职务', trigger: 'change' }],
certificateTime: [{ required: true, message: '请选择取证时间', trigger: 'change' }],
certificateNumber: [{ required: true, message: '请输入证书编号', trigger: 'blur' }]
}
const workRules = {
worker: [{ required: true, message: '请选择职业工种', trigger: 'change' }],
qualificationConfigId: [{ required: true, message: '请选择等级', trigger: 'change' }],
certificateTime: [{ required: true, message: '请选择取证时间', trigger: 'change' }],
certificateNumber: [{ required: true, message: '请输入证书编号', trigger: 'blur' }]
}
const honorRules = {
honor: [{ required: true, message: '请填写荣誉', trigger: 'blur' }],
honorCompany: [{ required: true, message: '请填写表彰单位', trigger: 'blur' }],
year: [{ required: true, message: '请输入年份', trigger: 'change' }]
}
const stationChangeRules = {
changeDate: [{ required: true, message: '请选择日期', trigger: 'change' }],
pos: [{ required: true, message: '请选择岗位类型', trigger: 'change' }]
}
const partChangeRules = {
branchName: [{ required: true, message: '请选择现支部', trigger: 'change' }],
changeTime: [{ required: true, message: '请选择变动时间', trigger: 'change' }]
}
// 获取字典数据
const loadCertificateTypeList = async () => {
try {
const res = await getTeacherCertificateList()
certificateTypeList.value = res.data || []
} catch (err) {
// 获取证书类型列表失败
}
}
const loadEducationTypeList = async () => {
try {
const res = await getAllTypeList()
educationTypeList.value = res.data || []
} catch (err) {
// 获取教育类型列表失败
}
}
const loadQualificationList = async () => {
try {
const res = await getQualificationList()
qualificationList.value = res.data || []
} catch (err) {
// 获取学历列表失败
}
}
const loadDegreeList = async () => {
try {
const res = await getDegreeList()
degreeList.value = res.data || []
} catch (err) {
// 获取学位列表失败
}
}
// Props
const props = defineProps<{
page: any
nowRow: any
}>()
// Emits
const emit = defineEmits(['getList'])
// 消息提示 hooks
const message = useMessage()
const messageBox = useMessageBox()
// 响应式数据
const educationDialogFromVisible = ref(false)
const waitShenheForm = reactive({
form: {
newDeptCodeList: [] as any[],
deptCodeList: [] as any[]
} as any,
title: '',
a: false,
b: false,
c: false,
d: false,
e: false,
f: false,
g: false
})
const materialUrlFrom = reactive({
url: '/professional/file/teacherAboutInfoUpload',
fileListA: [] as any[],
fileListB: [] as any[],
fileListC: [] as any[]
})
const baseInfoAbout = reactive({
stationTypeList: [] as any[],
atStationList: [] as any[],
teacherTypeList: [] as any[],
partBranchList: [] as any[],
employmentNatureList: [] as any[],
stationLevelList: [] as any[],
stationDutyLevelList: [] as any[],
workTypeList: [] as any[],
proTitleList: [] as any[],
majorStationList: [] as any[],
qualificationList: [] as any[]
})
const secDeptList = ref<any[]>([])
const newSecChildDeptCodeList = ref<any[]>([])
const newSecDeptCode = ref('')
const newSecChildDeptCode = ref('')
const teacherNo = ref('')
// Computed
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
// 方法定义
const initDicData = () => {
getAllInfoAboutList().then((response: any) => {
const map = response.data.data
baseInfoAbout.stationTypeList = map['stationTypeList']
baseInfoAbout.atStationList = map['atStationList']
baseInfoAbout.teacherTypeList = map['teacherTypeList']
baseInfoAbout.employmentNatureList = map['employmentNatureList']
baseInfoAbout.stationLevelList = map['stationLevelList']
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList']
baseInfoAbout.workTypeList = map['workTypeList']
baseInfoAbout.proTitleList = map['proTitleList']
baseInfoAbout.majorStationList = map['majorStationList']
baseInfoAbout.qualificationList = map['qualificationList']
baseInfoAbout.partBranchList = map['partBranchList']
})
// 加载字典数据
loadCertificateTypeList()
loadEducationTypeList()
loadQualificationList()
loadDegreeList()
}
const init = (val: number) => {
initDicData()
if (val == 5 || val == 6) {
teacherNo.value = props.nowRow.teacherNo
handleWaitExam(val)
} else {
for (let i in waitShenheForm.form) {
if (i !== 'newDeptCodeList' && i !== 'deptCodeList') {
(waitShenheForm.form as any)[i] = ''
}
}
waitShenheForm.form.newDeptCodeList = []
waitShenheForm.form.deptCodeList = []
materialUrlFrom.fileListA = []
materialUrlFrom.fileListB = []
materialUrlFrom.fileListC = []
materialUrlFrom.url = '/professional/file/teacherAboutInfoUpload'
let statusCode = ""
if (val == 0) {
statusCode = "teacherTitle"
}
if (val == 1) {
statusCode = "acade"
}
if (val == 2) {
statusCode = "title"
}
if (val == 3) {
statusCode = "job"
}
if (val == 4) {
statusCode = "remix"
}
checkLocked(statusCode).then((res: any) => {
if (!res.data.data) {
getMyTeacherNo().then((res: any) => {
teacherNo.value = res.data.data
handleWaitExam(val)
})
} else {
message.warning("新增功能已锁定,暂不允许操作")
}
})
}
}
const handleWaitExam = (val: number) => {
materialUrlFrom.url = '/professional/file/teacherAboutInfoUpload'
waitShenheForm.a = false
waitShenheForm.b = false
waitShenheForm.c = false
waitShenheForm.d = false
waitShenheForm.e = false
waitShenheForm.f = false
waitShenheForm.g = false
switch (val) {
case 0:
waitShenheForm.title = "教师资格证"
waitShenheForm.a = true
break
case 1:
waitShenheForm.title = "学历更新"
waitShenheForm.b = true
break
case 2:
waitShenheForm.title = "职称更新"
waitShenheForm.c = true
break
case 3:
waitShenheForm.title = "职业更新"
waitShenheForm.d = true
break
case 4:
waitShenheForm.title = "综合表彰"
materialUrlFrom.fileListA = []
waitShenheForm.form.honor = ''
waitShenheForm.form.honorCompany = ''
waitShenheForm.form.year = ''
waitShenheForm.form.attachment = ''
waitShenheForm.e = true
break
case 5:
waitShenheForm.title = "人员调动"
waitShenheForm.f = true
waitShenheForm.form = { ...props.nowRow }
waitShenheForm.form.newDeptCodeList = []
waitShenheForm.form.deptCodeList = []
newSecDeptCode.value = ''
newSecChildDeptCode.value = ''
getDeptListByLevelTwo().then((res: any) => {
secDeptList.value = res.data.data
educationDialogFromVisible.value = true
})
break
case 6:
waitShenheForm.title = "党员调动"
waitShenheForm.form = { ...props.nowRow }
waitShenheForm.form.oldBranchName = waitShenheForm.form.oldBranchId
waitShenheForm.g = true
break
}
materialUrlFrom.url = materialUrlFrom.url + "?teacherNo=" + teacherNo.value + "&type=" + val
if (val != 5) {
educationDialogFromVisible.value = true
}
}
const materiaUploadSuccessA = (response: any, file: any, fileList: any) => {
if (response.data.code == "-1") {
message.error("当前不允许上传文件")
return
}
waitShenheForm.form.mateA = response.data.url
}
const materiaUploadSuccessB = (response: any, file: any, fileList: any) => {
if (response.data.code == "-1") {
message.error("当前不允许上传文件")
return
}
waitShenheForm.form.mateB = response.data.url
}
const materiaUploadSuccessC = (response: any, file: any, fileList: any) => {
if (response.data.code == "-1") {
message.error("当前不允许上传文件")
return
}
waitShenheForm.form.mateC = response.data.url
}
const dialogSubmit = async (val: number) => {
waitShenheForm.form.type = val
waitShenheForm.form.teacherNo = teacherNo.value
// 表单验证
let formRef: any = null
if (val == 0) {
formRef = teacherCertificateFormRef.value
if (undefined == waitShenheForm.form.mateA || waitShenheForm.form.mateA == "") {
message.info("请上传资料")
return
}
} else if (val == 1) {
formRef = educationFormRef.value
if ((undefined == waitShenheForm.form.mateA || waitShenheForm.form.mateA == "") && (undefined == waitShenheForm.form.mateB || waitShenheForm.form.mateB == "")) {
message.info("请上传学历或学位证书")
return
}
} else if (val == 2) {
formRef = proFormRef.value
if (undefined == waitShenheForm.form.mateA || waitShenheForm.form.mateA == "") {
message.info("请上传证明材料")
return
}
} else if (val == 3) {
formRef = workFormRef.value
if (undefined == waitShenheForm.form.mateA || waitShenheForm.form.mateA == "") {
message.info("请上传资料")
return
}
} else if (val == 4) {
formRef = honorFormRef.value
if (undefined == waitShenheForm.form.mateA || waitShenheForm.form.mateA == "") {
message.info("请上传证明材料")
return
}
} else if (val == 5) {
formRef = stationChangeFormRef.value
if (!newSecDeptCode.value) {
message.info("请选择要调入的部门")
return
}
waitShenheForm.form.newDeptCode = newSecDeptCode.value
waitShenheForm.form.newSecDeptCode = newSecChildDeptCode.value ? newSecChildDeptCode.value : newSecDeptCode.value
} else if (val == 6) {
formRef = partChangeFormRef.value
}
// 验证表单
if (formRef) {
try {
await formRef.validate()
} catch (err) {
return
}
}
try {
await messageBox.confirm('确认提交?')
const res = await updateOtherInfo(waitShenheForm.form)
if (res.data.data == '-1') {
message.success("当前不允许提交")
} else {
message.success("提交成功")
}
emit("getList", props.page)
educationDialogFromVisible.value = false
} catch (err) {
// 用户取消
}
}
const getDeptListByParent = () => {
newSecChildDeptCode.value = ''
newSecChildDeptCodeList.value = []
getDeptListByParentApi(newSecDeptCode.value).then((res: any) => {
newSecChildDeptCodeList.value = res.data.data
})
}
// 暴露方法
defineExpose({
init
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,92 @@
<template>
<el-dialog v-model="visible" title="政治面貌" width="600px" @close="handleClose">
<el-form :model="form" label-width="120px" ref="formRef">
<el-form-item label="政治面貌" required>
<el-select v-model="form.politicsStatusId" placeholder="请选择政治面貌" style="width: 100%">
<el-option
v-for="item in politicsStatusList"
:key="item.id"
:label="item.politicsStatus"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="加入时间" required>
<el-date-picker
v-model="form.joinTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="转正时间" required>
<el-date-picker
v-model="form.correctionTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
interface Props {
modelValue: boolean
formData?: any
politicsStatusList?: any[]
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'submit', data: any): void
}
const props = withDefaults(defineProps<Props>(), {
formData: () => ({}),
politicsStatusList: () => []
})
const emit = defineEmits<Emits>()
const visible = ref(false)
const formRef = ref()
const form = ref<any>({})
watch(() => props.modelValue, (val) => {
visible.value = val
if (val && props.formData) {
form.value = { ...props.formData }
} else {
form.value = {}
}
})
watch(visible, (val) => {
emit('update:modelValue', val)
})
const handleClose = () => {
visible.value = false
form.value = {}
}
const handleSubmit = () => {
emit('submit', { ...form.value })
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,113 @@
<template>
<el-dialog v-model="visible" title="社会关系" width="600px" @close="handleClose">
<el-form :model="form" label-width="120px" ref="formRef">
<el-form-item label="称谓" required>
<el-select v-model="form.title" placeholder="请选择称谓" style="width: 100%">
<el-option
v-for="item in titleOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="姓名" required>
<el-input v-model="form.realName" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="出生年月" required>
<el-date-picker
v-model="form.birthday"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="政治面貌">
<el-select v-model="form.politicsStatusId" placeholder="请选择政治面貌" style="width: 100%">
<el-option
v-for="item in politicsStatusList"
:key="item.id"
:label="item.politicsStatus"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="工作单位及职务">
<el-input
v-model="form.workStation"
type="textarea"
:rows="2"
placeholder="请输入工作单位及职务"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
interface Props {
modelValue: boolean
formData?: any
politicsStatusList?: any[]
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'submit', data: any): void
}
const props = withDefaults(defineProps<Props>(), {
formData: () => ({}),
politicsStatusList: () => []
})
const emit = defineEmits<Emits>()
const visible = ref(false)
const formRef = ref()
const form = ref<any>({})
// 称谓选项
const titleOptions = [
{ label: '父亲', value: '父亲' },
{ label: '母亲', value: '母亲' },
{ label: '配偶', value: '配偶' },
{ label: '子女', value: '子女' },
{ label: '兄弟姐妹', value: '兄弟姐妹' },
{ label: '其他', value: '其他' }
]
watch(() => props.modelValue, (val) => {
visible.value = val
if (val && props.formData) {
form.value = { ...props.formData }
} else {
form.value = {}
}
})
watch(visible, (val) => {
emit('update:modelValue', val)
})
const handleClose = () => {
visible.value = false
form.value = {}
}
const handleSubmit = () => {
emit('submit', { ...form.value })
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,214 @@
<template>
<el-dialog
v-model="visible"
title="锁定状态管理"
width="600px"
class="status-lock-dialog"
@close="handleClose">
<div class="status-lock-content">
<div class="status-lock-description">
<el-icon class="status-lock-icon"><Lock /></el-icon>
<span>请选择需要锁定的模块锁定后将无法进行编辑操作</span>
</div>
<el-form :model="form" label-width="120px" class="status-lock-form">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="职称管理">
<el-switch
inline-prompt
v-model="form.title"
active-text="已锁定"
inactive-text="未锁定"
active-color="#f56c6c"
inactive-color="#67c23a">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职业资格">
<el-switch
inline-prompt
v-model="form.job"
active-text="已锁定"
inactive-text="未锁定"
active-color="#f56c6c"
inactive-color="#67c23a">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教师资格证">
<el-switch
inline-prompt
v-model="form.teacherTitle"
active-text="已锁定"
inactive-text="未锁定"
active-color="#f56c6c"
inactive-color="#67c23a">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学历学位">
<el-switch
inline-prompt
v-model="form.acade"
active-text="已锁定"
inactive-text="未锁定"
active-color="#f56c6c"
inactive-color="#67c23a">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="综合表彰">
<el-switch
inline-prompt
v-model="form.remix"
active-text="已锁定"
inactive-text="未锁定"
active-color="#f56c6c"
inactive-color="#67c23a">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="岗位晋升">
<el-switch
inline-prompt
v-model="form.work"
active-text="已锁定"
inactive-text="未锁定"
active-color="#f56c6c"
inactive-color="#67c23a">
</el-switch>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<template #footer>
<div class="status-lock-footer">
<el-button @click="handleClose">取消</el-button>
<el-button
type="primary"
v-if="canEdit"
@click="handleSubmit">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Lock } from '@element-plus/icons-vue'
interface Props {
modelValue: boolean
formData?: any
canEdit?: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'submit', data: any): void
}
const props = withDefaults(defineProps<Props>(), {
formData: () => ({
title: false,
job: true,
teacherTitle: true,
acade: true,
remix: true,
work: true
}),
canEdit: false
})
const emit = defineEmits<Emits>()
const visible = ref(false)
const form = ref<any>({
title: false,
job: true,
teacherTitle: true,
acade: true,
remix: true,
work: true
})
watch(() => props.modelValue, (val) => {
visible.value = val
if (val && props.formData) {
form.value = { ...props.formData }
} else {
form.value = {
title: false,
job: true,
teacherTitle: true,
acade: true,
remix: true,
work: true
}
}
})
watch(visible, (val) => {
emit('update:modelValue', val)
})
const handleClose = () => {
visible.value = false
}
const handleSubmit = () => {
emit('submit', { ...form.value })
}
</script>
<style scoped lang="scss">
.status-lock-dialog {
.status-lock-content {
padding: 20px 0;
}
.status-lock-description {
display: flex;
align-items: center;
padding: 16px;
margin-bottom: 24px;
background: #fef0f0;
border: 1px solid #fbc4c4;
border-radius: 6px;
color: #f56c6c;
font-size: 14px;
.status-lock-icon {
margin-right: 8px;
font-size: 18px;
color: #f56c6c;
}
}
.status-lock-form {
:deep(.el-form-item) {
margin-bottom: 24px;
}
}
.status-lock-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
}
</style>

View File

@@ -0,0 +1,248 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_typeofworkconfig_add">
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="workTitle" label="工种名字" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_typeofworkconfig_edit"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="permissions.professional_typeofworkconfig_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="600px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="工种名字" prop="workTitle">
<el-input
v-model="form.workTitle"
placeholder="请输入工种名字"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%"
/>
</el-form-item>
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } 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 { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/typeofworkconfig'
// 使用 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 tableRef = ref()
const formRef = ref()
// 弹窗状态
const dialogVisible = ref(false)
const submitLoading = ref(false)
// 表单数据
const form = reactive({
id: '',
workTitle: '',
sort: 0,
remarks: ''
})
// 表单验证规则
const formRules = {
workTitle: [
{ required: true, message: '请输入工种名字', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
queryForm: {}
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 打开新增窗口
const handleAdd = () => {
Object.assign(form, {
id: '',
workTitle: '',
sort: 0,
remarks: ''
})
dialogVisible.value = true
}
// 打开编辑窗口
const handleEdit = async (row: any) => {
Object.assign(form, {
id: row.id,
workTitle: row.workTitle || '',
sort: row.sort || 0,
remarks: row.remarks || ''
})
dialogVisible.value = true
}
// 删除
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 handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
</style>