This commit is contained in:
2026-01-12 19:37:18 +08:00
150 changed files with 9774 additions and 5361 deletions

View File

@@ -79,12 +79,12 @@
<el-table-column :label="$t('sysuser.index')" type="index" width="60" fixed="left" />
<el-table-column :label="$t('sysuser.username')" prop="username" fixed="left" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('sysuser.name')" prop="realName" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('sysuser.phone')" prop="phone" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('sysuser.post')" show-overflow-tooltip>
<template #default="scope">
<el-tag v-for="(item, index) in scope.row.postList" :key="index">{{ item.postName }}</el-tag>
</template>
</el-table-column>
<!-- <el-table-column :label="$t('sysuser.phone')" prop="phone" show-overflow-tooltip></el-table-column>-->
<!-- <el-table-column :label="$t('sysuser.post')" show-overflow-tooltip>-->
<!-- <template #default="scope">-->
<!-- <el-tag v-for="(item, index) in scope.row.postList" :key="index">{{ item.postName }}</el-tag>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column :label="$t('sysuser.role')" show-overflow-tooltip>
<template #default="scope">
<el-tag v-for="(item, index) in scope.row.roleList" :key="index">{{ item.roleName }}</el-tag>

View File

@@ -82,7 +82,7 @@ import { list } from '/@/api/gen/datasource';
import { useMessage } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
import { validateNull } from '/@/utils/validate';
import BatchGenDialog from './BatchGenDialog.vue';
import BatchGenDialog from './batchGenDialog.vue';
import { ElMessage } from 'element-plus';
// 定义变量内容

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -8,8 +8,8 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import authImg from '@/components/tools/auth-img.vue'
import { ref, defineAsyncComponent } from 'vue'
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
// Props
defineProps<{

View File

@@ -1,20 +1,17 @@
<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 label-width="100px">
<el-form-item label="姓名 / 工号:">
<el-tag>{{ dataForm.name }} - {{ dataForm.teacherNo }}</el-tag>
</el-form-item>
<el-form-item label="类型:">
<el-tag>{{ typeName }}</el-tag>
<el-input v-model="typeName" disabled />
</el-form-item>
<el-form-item label="驳回理由">
<el-input
type="textarea"
v-model="dataForm.backReason"
:rows="4"
:rows="3"
placeholder="请输入驳回理由"
maxlength="500"
show-word-limit
@@ -24,8 +21,8 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button @click="saveData" :loading="loading" type="primary"> </el-button>
<el-button @click="visible = false">取消</el-button>
<el-button @click="saveData" :loading="loading" type="primary">保存</el-button>
</div>
</template>
</el-dialog>
@@ -34,14 +31,14 @@
<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'
import { examObj as editTitle } from '/@/api/professional/professionaluser/professionaltitlerelation'
import { examObj as editQua } from '/@/api/professional/professionaluser/professionalqualificationrelation'
import { examObj as editCer } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
import { examObj as editEducation } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
import { examObj as editHonor } from '/@/api/professional/professionaluser/professionalteacherhonor'
import { examObj as editPaper } from '/@/api/professional/professionalteacherpaper'
import { examObj as editMaterial } from '/@/api/professional/professionalteachingmaterial'
import { examObj as editTopic } from '/@/api/professional/professionaltopiclist'
// Emits
const emit = defineEmits<{

View File

@@ -2,7 +2,7 @@
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-row shadow="hover" v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="单位名称" prop="companyName">
<el-input
@@ -29,12 +29,6 @@
@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>
@@ -60,9 +54,9 @@
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="updateTime" label="更新时间" min-width="180" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_outercompany_edit"
@@ -76,7 +70,6 @@
icon="delete"
link
type="primary"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
@@ -153,7 +146,7 @@ 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'
import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/stayschool/outercompany'
// 使 Pinia store
const userInfoStore = useUserInfo()
@@ -286,8 +279,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
//
} catch (error: any) {
//
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -2,7 +2,7 @@
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-row shadow="hover" v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="单位名称" prop="companyName">
<el-input
@@ -29,12 +29,12 @@
@click="handleAdd"
v-if="permissions.professional_outercompany_add">
</el-button>
<right-toolbar
<!-- <right-toolbar
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
></right-toolbar> -->
</div>
</el-row>
@@ -60,9 +60,9 @@
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="updateTime" label="更新时间" min-width="180" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_outercompany_edit"
@@ -76,7 +76,6 @@
icon="delete"
link
type="primary"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
@@ -153,7 +152,7 @@ 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'
import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/stayschool/outercompany'
// 使 Pinia store
const userInfoStore = useUserInfo()
@@ -286,8 +285,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
//
} catch (error: any) {
//
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -2,7 +2,7 @@
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-row shadow="hover" v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="单位名称" prop="companyName">
<el-input
@@ -20,25 +20,6 @@
</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"
@@ -61,7 +42,7 @@
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="updateTime" label="更新时间" min-width="180" align="center" />
<!-- 操作列已注释此页面为只读 -->
<!-- <el-table-column label="操作" width="150" align="center" fixed="right">
@@ -100,7 +81,7 @@ 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'
import { fetchList } from '/@/api/professional/stayschool/outercompany'
// 使 Pinia store
const userInfoStore = useUserInfo()

View File

@@ -0,0 +1,311 @@
<template>
<el-dialog
v-model="visible"
:title="formData.id ? '编辑' : '新增'"
width="800px"
:close-on-click-modal="false"
destroy-on-close
@close="handleClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
class="form-content"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="单位名称" prop="companyId">
<el-select
v-model="formData.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="formData.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="formData.realName"
placeholder="请输入姓名"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证" prop="idCard">
<el-input
v-model="formData.idCard"
placeholder="请输入身份证号"
clearable
maxlength="18"
@input="handleIdCardInput"
/>
</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="formData.mobile"
type="number"
placeholder="请输入手机号"
clearable
maxlength="11"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位" prop="position">
<el-input
v-model="formData.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="formData.address"
placeholder="请输入家庭地址"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="允许进出" prop="inoutFlag">
<el-select
v-model="formData.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="formData.remarks"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useDict } from '/@/hooks/dict'
import { useMessage } from '/@/hooks/message'
import { addObj, putObj } from '/@/api/professional/stayschool/outercompanyemployee'
// Props
interface Props {
modelValue: boolean
formData: {
id?: string
companyId?: string
companyName?: string
employeeNo?: string
realName?: string
idCard?: string
mobile?: string
position?: string
address?: string
inoutFlag?: string
remarks?: string
}
companyList: any[]
saveApi?: (data: any) => Promise<any> // 自定义保存 API 函数(用于培训单位、二期单位等)
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
formData: () => ({
id: '',
companyId: '',
companyName: '',
employeeNo: '',
realName: '',
idCard: '',
mobile: '',
position: '',
address: '',
inoutFlag: '',
remarks: ''
}),
companyList: () => [],
saveApi: undefined
})
// Emits
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'success'): void
}>()
// 字典数据
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
// 消息提示
const message = useMessage()
// 表单引用
const formRef = ref()
// 提交加载状态
const submitLoading = ref(false)
// 弹窗显示状态
const visible = ref(false)
watch(() => props.modelValue, (val) => {
visible.value = val
})
watch(visible, (val) => {
emit('update:modelValue', val)
})
// 表单验证规则
const formRules = {
companyId: [
{ required: true, message: '请选择单位', trigger: 'change' }
],
realName: [
{ required: true, message: '请填写姓名', trigger: 'blur' }
],
idCard: [
{ required: true, message: '请输入正确身份证', trigger: 'blur' },
{ min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur' },
{ pattern: /^(\d{15}|\d{17}[\dXx])$/, message: '身份证号格式不正确', trigger: 'blur' }
],
mobile: [
{ required: true, message: '请填写手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
address: [
{ required: true, message: '请填写家庭住址', trigger: 'blur' }
],
inoutFlag: [
{ required: true, message: '请选择是否允许进出', trigger: 'change' }
]
}
// 关闭弹窗
const handleClose = () => {
visible.value = false
formRef.value?.resetFields()
}
// 身份证号输入限制只允许数字和X/x最大长度18
const handleIdCardInput = (value: string) => {
// 只保留数字和X/x
const filtered = value.replace(/[^\dXx]/g, '')
if (filtered !== value) {
props.formData.idCard = filtered
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
// 设置单位名称
const selectedCompany = props.companyList.find(item => item.id === props.formData.companyId)
const submitData = {
...props.formData,
companyName: selectedCompany?.companyName || ''
}
if (props.formData.id) {
await putObj(submitData)
message.success('修改成功')
} else {
// 如果提供了自定义保存函数,使用它;否则使用默认的 addObj
if (props.saveApi) {
await props.saveApi(submitData)
} else {
await addObj(submitData)
}
message.success('添加成功')
}
handleClose()
emit('success')
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
.form-content {
:deep(.el-row) {
margin-bottom: 18px;
&:last-child {
margin-bottom: 0;
}
}
:deep(.el-row .el-form-item) {
margin-bottom: 0;
}
}
</style>

View File

@@ -0,0 +1,284 @@
<template>
<el-dialog
v-model="visible"
:title="formData.id ? '编辑' : '新增'"
width="800px"
:close-on-click-modal="false"
destroy-on-close
@close="handleClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
class="form-content"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="班级名称" prop="companyId">
<el-select
v-model="formData.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="formData.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="formData.realName"
placeholder="请输入姓名"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证" prop="idCard">
<el-input
v-model="formData.idCard"
placeholder="请输入身份证号"
clearable
maxlength="18"
@input="handleIdCardInput"
/>
</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="formData.mobile"
type="number"
placeholder="请输入手机号"
clearable
maxlength="11"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="允许进出" prop="inoutFlag">
<el-select
v-model="formData.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="formData.remarks"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useDict } from '/@/hooks/dict'
import { useMessage } from '/@/hooks/message'
import { putObj } from '/@/api/professional/stayschool/outercompanyemployee'
// Props
interface Props {
modelValue: boolean
formData: {
id?: string
companyId?: string
companyName?: string
employeeNo?: string
realName?: string
idCard?: string
mobile?: string
inoutFlag?: string
remarks?: string
}
companyList: any[]
saveApi?: (data: any) => Promise<any> // 自定义保存 API 函数
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
formData: () => ({
id: '',
companyId: '',
companyName: '',
employeeNo: '',
realName: '',
idCard: '',
mobile: '',
inoutFlag: '',
remarks: ''
}),
companyList: () => [],
saveApi: undefined
})
// Emits
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'success'): void
}>()
// 字典数据
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
// 消息提示
const message = useMessage()
// 表单引用
const formRef = ref()
// 提交加载状态
const submitLoading = ref(false)
// 弹窗显示状态
const visible = ref(false)
watch(() => props.modelValue, (val) => {
visible.value = val
})
watch(visible, (val) => {
emit('update:modelValue', val)
})
// 表单验证规则
const formRules = {
companyId: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
realName: [
{ required: true, message: '请填写姓名', trigger: 'blur' }
],
idCard: [
{ required: true, message: '请输入正确身份证', trigger: 'blur' },
{ min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur' },
{ pattern: /^(\d{15}|\d{17}[\dXx])$/, message: '身份证号格式不正确', trigger: 'blur' }
],
mobile: [
{ required: true, message: '请填写手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
inoutFlag: [
{ required: true, message: '请选择是否允许进出', trigger: 'change' }
]
}
// 关闭弹窗
const handleClose = () => {
visible.value = false
formRef.value?.resetFields()
}
// 身份证号输入限制只允许数字和X/x最大长度18
const handleIdCardInput = (value: string) => {
// 只保留数字和X/x
const filtered = value.replace(/[^\dXx]/g, '')
if (filtered !== value) {
props.formData.idCard = filtered
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
// 设置班级名称
const selectedCompany = props.companyList.find(item => item.id === props.formData.companyId)
const submitData = {
...props.formData,
companyName: selectedCompany?.companyName || ''
}
if (props.formData.id) {
await putObj(submitData)
message.success('修改成功')
} else {
// 使用自定义保存函数
if (props.saveApi) {
await props.saveApi(submitData)
} else {
message.error('请提供保存 API 函数')
return
}
message.success('添加成功')
}
handleClose()
emit('success')
} catch (error: any) {
message.error(error?.msg || '操作失败')
} finally {
submitLoading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
.form-content {
:deep(.el-row) {
margin-bottom: 18px;
&:last-child {
margin-bottom: 0;
}
}
:deep(.el-row .el-form-item) {
margin-bottom: 0;
}
}
</style>

View File

@@ -2,7 +2,7 @@
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-row shadow="hover" v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="handleFilter">
<el-form-item label="单位名称" prop="companyId">
<el-select
@@ -10,7 +10,6 @@
filterable
clearable
placeholder="请选择单位"
style="width: 200px"
>
<el-option
v-for="item in companyList"
@@ -25,7 +24,6 @@
v-model="state.queryForm.employeeNo"
placeholder="请输入职员编号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
@@ -33,7 +31,6 @@
v-model="state.queryForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="身份证" prop="idCard">
@@ -41,7 +38,6 @@
v-model="state.queryForm.idCard"
placeholder="请输入身份证"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="手机" prop="mobile">
@@ -49,7 +45,6 @@
v-model="state.queryForm.mobile"
placeholder="请输入手机"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="允许进出" prop="inoutFlag">
@@ -57,7 +52,6 @@
v-model="state.queryForm.inoutFlag"
clearable
placeholder="请选择"
style="width: 200px"
>
<el-option
v-for="item in yesNoDict"
@@ -68,15 +62,15 @@
</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-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<div class="mb15">
<el-button
type="primary"
icon="FolderAdd"
@@ -85,26 +79,28 @@
</el-button>
<el-button
type="primary"
plain
icon="UploadFilled"
class="ml10"
@click="handleExportIn"
v-if="permission.scope == '1'"
>
</el-button>
<el-button
<!-- <el-button
type="warning"
plain
icon="Download"
class="ml10"
@click="handleExportScore"
:loading="exportLoading"
icon="Download">导出
:loading="exportLoading">导出
</el-button>
<el-button
type="primary"
@click="batchDelect">批量删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
type="danger"
plain
icon="Delete"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
@click="batchDelect">批量删除
</el-button> -->
</div>
</el-row>
@@ -140,18 +136,18 @@
<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;"
/>
<el-button
type="primary"
link
icon="Picture"
@click="handlePictureCardPreview(scope.row.employeeNo)"
>
查看
</el-button>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<!-- <el-table-column prop="updateTime" label="更新时间" width="180" align="center" /> -->
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
@@ -167,17 +163,15 @@
icon="RefreshLeft"
link
type="primary"
style="margin-left: 12px"
@click="resetPassword(scope.row)">重置密码
</el-button>
<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>
</el-button> -->
</template>
</el-table-column>
</el-table>
@@ -190,167 +184,18 @@
/>
<!-- 新增/编辑弹窗 -->
<el-dialog
<form-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>
:form-data="form"
:company-list="companyList"
@success="handleFormSuccess"
/>
<!-- 头像预览对话框 -->
<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
@@ -396,17 +241,16 @@ 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 { Plus, Picture } 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'
} from '/@/api/professional/stayschool/outercompanyemployee'
import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany'
import FormDialog from './form.vue'
// 使 Pinia store
const userInfoStore = useUserInfo()
@@ -426,7 +270,7 @@ const message = useMessage()
const messageBox = useMessageBox()
//
const { yes_no: yesNoDict } = useDict('yes_no')
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
//
const getDictLabel = (value: string | number) => {
@@ -436,7 +280,6 @@ const getDictLabel = (value: string | number) => {
//
const tableRef = ref()
const formRef = ref()
const queryRef = ref()
const uploadFormRef = ref()
@@ -446,9 +289,7 @@ 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)
//
@@ -476,52 +317,14 @@ const form = reactive({
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>({
@@ -665,36 +468,9 @@ const resetPassword = (row: any) => {
}).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
}
}
})
//
const handleFormSuccess = () => {
getDataList()
}
// URL
@@ -704,98 +480,12 @@ const getImageView = (employeeNo: string) => {
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 = () => {
@@ -901,11 +591,6 @@ const handleExportScore = async () => {
//
onMounted(() => {
if (!window.FileReader) {
console.error('Your browser does not support FileReader API!')
} else {
fileReader.value = new FileReader()
}
loadCompanyList()
})
</script>

View File

@@ -2,7 +2,7 @@
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-row shadow="hover" v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="handleFilter">
<el-form-item label="单位名称" prop="companyId">
<el-select
@@ -76,35 +76,50 @@
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<div class="mb15" style="width: 100%; position: relative;">
<el-button
type="primary"
icon="FolderAdd"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_outercompanyemployee_add">
v-if="permissions.professional_outercompanyemployee_add"
>
</el-button>
<el-button
type="primary"
plain
icon="UploadFilled"
@click="handleExportIn"
v-if="permission.scope == '1'"
>
class="ml10"
>
</el-button>
<el-button
<!-- <el-button
type="warning"
plain
icon="Download"
@click="handleExportScore"
:loading="exportLoading"
icon="Download">导出
class="ml10"
>
导出
</el-button>
<el-button
type="primary"
@click="batchDelect">批量删除
</el-button>
<right-toolbar
type="danger"
plain
icon="Delete"
@click="batchDelect"
class="ml10"
>
批量删除
</el-button> -->
<!-- <right-toolbar
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
style="position: absolute; right: 0; top: 0;"
@queryTable="getDataList"
></right-toolbar>
></right-toolbar> -->
</div>
</el-row>
@@ -140,18 +155,18 @@
<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;"
/>
<el-button
type="primary"
link
icon="Picture"
@click="handlePictureCardPreview(scope.row.employeeNo)"
>
查看
</el-button>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<!-- <el-table-column prop="updateTime" label="更新时间" width="180" align="center" /> -->
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
@@ -167,17 +182,15 @@
icon="RefreshLeft"
link
type="primary"
style="margin-left: 12px"
@click="resetPassword(scope.row)">重置密码
</el-button>
<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>
</el-button> -->
</template>
</el-table-column>
</el-table>
@@ -190,167 +203,19 @@
/>
<!-- 新增/编辑弹窗 -->
<el-dialog
<form-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>
:form-data="form"
:company-list="companyList"
:save-api="saveSecond"
@success="handleFormSuccess"
/>
<!-- 头像预览对话框 -->
<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
@@ -396,18 +261,17 @@ 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'
} from '/@/api/professional/stayschool/outercompanyemployee'
import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany'
import FormDialog from './form.vue'
// 使 Pinia store
const userInfoStore = useUserInfo()
@@ -427,7 +291,7 @@ const message = useMessage()
const messageBox = useMessageBox()
//
const { yes_no: yesNoDict } = useDict('yes_no')
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
//
const getDictLabel = (value: string | number) => {
@@ -437,7 +301,6 @@ const getDictLabel = (value: string | number) => {
//
const tableRef = ref()
const formRef = ref()
const queryRef = ref()
const uploadFormRef = ref()
@@ -447,9 +310,7 @@ 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)
//
@@ -477,52 +338,15 @@ const form = reactive({
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>({
@@ -666,36 +490,9 @@ const resetPassword = (row: any) => {
}).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
}
}
})
//
const handleFormSuccess = () => {
getDataList()
}
// URL
@@ -705,90 +502,12 @@ const getImageView = (employeeNo: string) => {
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 = () => {
@@ -894,12 +613,6 @@ const handleExportScore = async () => {
//
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>

View File

@@ -2,7 +2,7 @@
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-row shadow="hover" v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="handleFilter">
<el-form-item label="班级名称" prop="companyId">
<el-select
@@ -79,32 +79,47 @@
<div class="mb15" style="width: 100%;">
<el-button
type="primary"
icon="FolderAdd"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.professional_outercompanyemployee_add">
v-if="permissions.professional_outercompanyemployee_add"
>
</el-button>
<el-button
type="primary"
plain
icon="UploadFilled"
@click="handleExportIn"
v-if="permission.scope == '1'"
>
class="ml10"
>
</el-button>
<el-button
<!-- <el-button
type="warning"
plain
icon="Download"
@click="handleExportScore"
:loading="exportLoading"
icon="Download">导出
class="ml10"
>
导出
</el-button>
<el-button
type="primary"
@click="batchDelect">批量删除
type="danger"
plain
icon="Delete"
@click="batchDelect"
class="ml10"
>
批量删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
></right-toolbar> -->
</div>
</el-row>
@@ -140,18 +155,18 @@
<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;"
/>
<el-button
type="primary"
link
icon="Picture"
@click="handlePictureCardPreview(scope.row.employeeNo)"
>
查看
</el-button>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<!-- <el-table-column prop="updateTime" label="更新时间" width="180" align="center" /> -->
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
@@ -167,17 +182,15 @@
icon="RefreshLeft"
link
type="primary"
style="margin-left: 12px"
@click="resetPassword(scope.row)">重置密码
</el-button>
<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>
</el-button> -->
</template>
</el-table-column>
</el-table>
@@ -190,146 +203,19 @@
/>
<!-- 新增/编辑弹窗 -->
<el-dialog
<form-train
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>
:form-data="form"
:company-list="companyList"
:save-api="saveSecond"
@success="handleFormSuccess"
/>
<!-- 头像预览对话框 -->
<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
@@ -375,18 +261,18 @@ 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 { Plus, UploadFilled, Download, Delete, Picture } 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'
} from '/@/api/professional/stayschool/outercompanyemployee'
import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany'
import FormTrain from './formTrain.vue'
// 使 Pinia store
const userInfoStore = useUserInfo()
@@ -406,7 +292,7 @@ const message = useMessage()
const messageBox = useMessageBox()
//
const { yes_no: yesNoDict } = useDict('yes_no')
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
//
const getDictLabel = (value: string | number) => {
@@ -416,7 +302,6 @@ const getDictLabel = (value: string | number) => {
//
const tableRef = ref()
const formRef = ref()
const queryRef = ref()
const uploadFormRef = ref()
@@ -426,9 +311,7 @@ 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)
//
@@ -454,37 +337,15 @@ const form = reactive({
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>({
@@ -624,36 +485,9 @@ const resetPassword = (row: any) => {
}).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
}
}
})
//
const handleFormSuccess = () => {
getDataList()
}
// URL
@@ -663,90 +497,12 @@ const getImageView = (employeeNo: string) => {
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 = () => {
@@ -852,12 +608,6 @@ const handleExportScore = async () => {
//
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>

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -42,7 +42,7 @@
@click="handleEdit(scope.row)">编辑
</el-button>
<el-button
type="danger"
type="primary"
link
icon="Delete"
v-if="permissions.professional_professionalpartybranch_del"
@@ -232,8 +232,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -6,7 +6,7 @@
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter(search)"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
@@ -14,7 +14,6 @@
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
@@ -23,7 +22,6 @@
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入用户名"
/>
</el-form-item>
@@ -33,7 +31,7 @@
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter(search)" icon="Search">查询</el-button>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
@@ -50,9 +48,11 @@
>
<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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="oldBranchName" label="原支部名称" min-width="150" align="center" show-overflow-tooltip />
@@ -66,7 +66,7 @@
<el-table-column prop="changeTime" label="变动时间" width="180" align="center" />
<el-table-column prop="createTime" 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>
@@ -83,9 +83,12 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/professionaluser/professionalpartychange'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
// 表格引用
const tableRef = ref()
const searchFormRef = ref()

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -1,5 +1,5 @@
<template>
<el-dialog v-model="visible" title="编辑职业资格" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<el-dialog v-model="dialogVisible" title="编辑职业资格" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
@@ -55,15 +55,17 @@
<el-form-item label="证书编号" prop="certificateNumber">
<el-input
v-model="dataForm.certificateNumber"
placeholder="请输入证书编号"
placeholder="请输入证书编号(仅支持英文和数字)"
clearable
show-word-limit
maxlength="32"
@input="handleCertificateNumberInput"
/>
</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"
@@ -72,7 +74,9 @@
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
<div style="margin-top: 8px;">
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</div>
</template>
</el-upload>
</el-form-item>
@@ -81,7 +85,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
@@ -92,17 +96,13 @@
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
}>()
import { addObj } from '/@/api/professional/professionaluser/professionalqualificationrelation'
import { checkLocked } from '/@/api/professional/professionalstatuslock'
import { getLevelList } from '/@/api/professional/rsbase/professionalqualificationconfig'
import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype'
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
@@ -113,11 +113,8 @@ const message = useMessage()
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 对话框显示状态(内部管理)
const dialogVisible = ref(false)
// 表单数据
const dataForm = reactive({
@@ -128,7 +125,6 @@ const dataForm = reactive({
materialA: '',
evidenceA: '',
state: '',
teacherNo: '',
id: ''
})
@@ -144,35 +140,21 @@ const formRules = {
{ required: true, message: '请选择取证时间', trigger: 'change' }
],
certificateNumber: [
{ required: true, message: '请输入证书编号', trigger: 'blur' }
{ required: true, message: '请输入证书编号', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
],
materialA: [
{ 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: []
qualificationList: []
})
// 上传相关
@@ -198,25 +180,31 @@ 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
// 使用专门的 API 获取数据(与 index.vue 保持一致)
const [levelRes, workRes] = await Promise.all([
getLevelList(),
getWorkTypeList()
])
// 处理资格等级列表(与 index.vue 保持一致)
if (levelRes && levelRes.data) {
baseInfoAbout.qualificationList = levelRes.data
}
// 处理工种列表(与 index.vue 保持一致)
if (workRes && workRes.data) {
baseInfoAbout.workTypeList = workRes.data
}
} catch (error) {
// 获取字典数据失败
}
}
// 证书编号输入处理(只允许英文和数字)
const handleCertificateNumberInput = (value: string) => {
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
}
// 文件上传成功
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
@@ -224,25 +212,62 @@ const materiaUploadSuccess = (response: any) => {
return
}
dataForm.evidenceA = response.data.url
dataForm.materialA = response.data.url
// 上传成功后清除该字段的验证错误
formRef.value?.clearValidate('materialA')
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=3`
const openDialog = async (row?: any) => {
// 新增模式:先检查是否锁定
if (!row || !row.id) {
try {
const lockResponse = await checkLocked('job')
if (lockResponse.data) {
message.warning("新增功能已锁定,暂不允许操作")
return
}
} catch (error) {
// 错误处理已在数据请求层统一处理,此处不需要提示
return
}
}
// 公共设置
url.value = `/professional/file/teacherAboutInfoUpload?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 || ''
})
// 根据是否有 row 设置不同的表单数据
if (row && row.id) {
// 编辑模式:使用传入的数据
Object.assign(dataForm, {
worker: row.worker || '',
qualificationConfigId: row.qualificationConfigId || '',
certificateTime: row.certificateTime || '',
certificateNumber: row.certificateNumber || '',
materialA: row.materialA || '',
evidenceA: row.evidenceA || '',
state: row.state || '',
id: row.id || ''
})
} else {
// 新增模式:重置为空
Object.assign(dataForm, {
worker: '',
qualificationConfigId: '',
certificateTime: '',
certificateNumber: '',
materialA: '',
evidenceA: '',
state: '',
id: ''
})
}
// 公共操作:加载字典数据并显示对话框
await initDicData()
showForm.value = true
initDicData()
dialogVisible.value = true
}
// 提交表单
@@ -253,13 +278,18 @@ const dialogSubmit = async () => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
// 统一使用 addObj 接口(新增和编辑都使用同一个接口)
// 确保 evidenceA 或 materialA 有值
if (!dataForm.evidenceA && dataForm.materialA) {
dataForm.evidenceA = dataForm.materialA
}
await addObj(dataForm)
message.success(dataForm.id ? "修改成功" : "提交成功")
dialogVisible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
// 错误处理已在数据请求层统一处理,此处不需要提示
} finally {
submitLoading.value = false
}

View File

@@ -1,28 +1,6 @@
<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"
@@ -36,7 +14,6 @@
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
@@ -52,7 +29,6 @@
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
@@ -61,7 +37,6 @@
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
@@ -87,7 +62,8 @@
v-if="permissions.professional_professionalqualificationrelation_add">
</el-button>
<el-button
type="success"
type="warning"
plain
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
@@ -109,18 +85,15 @@
<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>
<AuditState :state="scope.row.state" :options="auditStateOptions" />
</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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="qualificationConfigId" label="资格等级" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
@@ -142,40 +115,44 @@
v-if="scope.row.evidenceA && scope.row.evidenceA !== ''"
type="primary"
link
icon="Document"
@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">
<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"
icon="edit-pen"
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"
icon="CircleCheck"
v-if="permissions.professional_professionalqualificationrelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
icon="CircleClose"
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"
type="primary"
link
icon="Delete"
icon="delete"
v-if="permissions.professional_professionalqualificationrelation_del"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
@@ -189,18 +166,15 @@
@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>
<!-- 材料预览图片直接显示PDF 在组件内部 dialog 中显示 -->
<preview-file
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
dialog-title="职业资格材料"
/>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
@@ -215,28 +189,28 @@ 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,
examObj,
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'))
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 审核状态选项(独立定义,防止其他页面修改时被波及)
import type { StateOption } from '/@/components/AuditState/index.vue'
const auditStateOptions: StateOption[] = [
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
]
// 使用 Pinia store
const userInfoStore = useUserInfo()
@@ -261,10 +235,8 @@ 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)
// 搜索表单数据
@@ -274,12 +246,7 @@ const search = reactive({
realName: ''
})
// 图表数据
const chartOption = ref<any>({})
const chartData = ref<any[]>([])
// 材料预览
const dialogVisible = ref(false)
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
@@ -313,33 +280,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
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 = []
@@ -350,9 +291,6 @@ const handlePreview = (list: string[]) => {
url: v
})
})
nextTick(() => {
dialogVisible.value = true
})
})
}
@@ -363,14 +301,15 @@ const changeState = (row: any, val: number) => {
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
await examObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
} else if (val === -2) {
@@ -397,7 +336,7 @@ const resetQuery = () => {
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(3)
dataFormRef.value?.openDialog()
}
// 打开编辑窗口
@@ -416,8 +355,9 @@ const handleDel = (row: any) => {
state.pagination.current = state.pagination.current - 1
}
getDataList()
} catch (error) {
// Failed to delete
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
}

View File

@@ -0,0 +1,82 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 页面标题 -->
<div style="margin-bottom: 20px;">
<h2 style="margin: 0; font-size: 20px; font-weight: 600; color: #303133;">资格等级统计</h2>
</div>
<!-- 图表统计 -->
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
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 { getChartOption } from '/@/api/professional/professionaluser/professionalqualificationrelation'
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 图表引用
const chartRef = ref()
// 图表数据
const chartOption = ref<any>({})
const chartData = ref<any[]>([])
// 初始化图表
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
}
}
// 页面加载时初始化图表
onMounted(() => {
initChartOption()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -1,10 +1,11 @@
<template>
<el-dialog v-model="visible" title="编辑学历学位" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<el-dialog v-model="dialogVisible" title="编辑学历学位" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
:model="dataForm"
:rules="formRules"
:validate-on-rule-change="false"
label-width="120px"
>
<el-form-item label="毕业时间" prop="graduateTime">
@@ -14,7 +15,6 @@
placeholder="请选择毕业时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
@@ -24,7 +24,6 @@
filterable
clearable
placeholder="请选择教育类型"
style="width: 100%"
>
<el-option
v-for="item in educationTypeList"
@@ -41,7 +40,7 @@
filterable
clearable
placeholder="请选择学历"
style="width: 100%"
@change="handleQualificationChange"
>
<el-option
v-for="item in qualificationList"
@@ -58,7 +57,7 @@
filterable
clearable
placeholder="请选择学位"
style="width: 100%"
@change="handleDegreeChange"
>
<el-option
v-for="item in degreeList"
@@ -88,15 +87,17 @@
<el-form-item label="证书编码" prop="certificateNumber">
<el-input
v-model="dataForm.certificateNumber"
placeholder="请输入证书编码"
placeholder="请输入证书编码(仅支持英文和数字)"
clearable
show-word-limit
maxlength="100"
@input="handleCertificateNumberInput"
/>
</el-form-item>
<el-form-item label="学历证书" prop="materialA">
<el-form-item v-if="needQualificationImg" label="学历证书" prop="qualificationImg" required>
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileList"
@@ -105,15 +106,16 @@
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
<div style="margin-top: 8px;">
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</div>
</template>
</el-upload>
</el-form-item>
<el-form-item label="学位证书" prop="materialB">
<el-form-item v-if="needDegreeImg" label="学位证书" prop="degreeImg" required>
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileListB"
@@ -122,7 +124,9 @@
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
<div style="margin-top: 8px;">
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</div>
</template>
</el-upload>
</el-form-item>
@@ -131,31 +135,25 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { ref, reactive, computed, nextTick } 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 { addObj } 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
}>()
import { checkLocked } from '/@/api/professional/professionalstatuslock'
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
@@ -166,11 +164,8 @@ const message = useMessage()
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 对话框显示状态(内部管理)
const dialogVisible = ref(false)
// 表单数据
const dataForm = reactive({
@@ -181,65 +176,73 @@ const dataForm = reactive({
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 needQualificationImg = computed(() => {
return !!dataForm.qualificationConfigId
})
// 计算属性:是否需要学位证书(选择了学位且不等于 -1 或"无"
const needDegreeImg = computed(() => {
if (!dataForm.degreeConfigId) return false
// 检查是否为 -1 或"无"(处理类型转换)
const degreeId = String(dataForm.degreeConfigId)
if (degreeId === '-1') return false
// 检查学位列表中是否有名称为"无"的选项
const degreeItem = degreeList.value.find(item => String(item.id) === degreeId)
if (degreeItem && degreeItem.degreeName === '无') return false
return true
})
// 验证规则
const formRules = computed(() => {
const rules: any = {
graduateTime: [
{ required: true, message: '请选择毕业时间', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择教育类型', trigger: 'blur' }
],
graduateSchool: [
{ required: true, message: '请输入毕业学校', trigger: 'blur' }
],
major: [
{ required: true, message: '请输入所学专业', trigger: 'blur' }
],
certificateNumber: [
{ required: true, message: '请输入证书编码', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编码只能包含英文和数字', trigger: 'blur' }
]
}
// 只有当需要学历证书时才添加验证规则
if (needQualificationImg.value) {
rules.qualificationImg = [
{ required: true, message: '请上传学历证书', trigger: 'blur' }
]
}
// 只有当需要学位证书时才添加验证规则
if (needDegreeImg.value) {
rules.degreeImg = [
{ required: true, message: '请上传学位证书', trigger: 'blur' }
]
}
return rules
})
// 下拉列表数据
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('')
@@ -248,7 +251,7 @@ const fileListB = ref<any[]>([])
// 请求头
const headers = computed(() => {
return {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
@@ -259,53 +262,67 @@ const showForm = ref(false)
// 初始化字典数据
const initDicData = async () => {
try {
const [response, eduRes, quaRes, degRes] = await Promise.all([
getAllInfoAboutList(),
// 使用专门的 API 获取数据(与 index.vue 保持一致)
const [eduRes, quaRes, degRes] = await Promise.all([
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 || [])
educationTypeList.value = eduRes.data
}
// 获取学历列表
if (quaRes && quaRes.data) {
qualificationList.value = Array.isArray(quaRes.data)
? quaRes.data
: (quaRes.data.records || quaRes.data.list || [])
qualificationList.value = quaRes.data
}
// 获取学位列表
if (degRes && degRes.data) {
degreeList.value = Array.isArray(degRes.data)
? degRes.data
: (degRes.data.records || degRes.data.list || [])
degreeList.value = degRes.data
}
visible.value = true
} catch (error) {
// 获取字典数据失败
}
}
// 证书编号输入处理(只允许英文和数字)
const handleCertificateNumberInput = (value: string) => {
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
}
// 学历改变时的处理
const handleQualificationChange = () => {
// 如果不需要学历证书,清除学历证书字段、文件列表和验证错误
if (!needQualificationImg.value) {
dataForm.qualificationImg = ''
fileList.value = []
formRef.value?.clearValidate('qualificationImg')
} else {
// 如果需要学历证书,延迟清除验证(等字段显示后)
nextTick(() => {
formRef.value?.clearValidate('qualificationImg')
})
}
}
// 学位改变时的处理
const handleDegreeChange = () => {
// 如果不需要学位证书,清除学位证书字段、文件列表和验证错误
if (!needDegreeImg.value) {
dataForm.degreeImg = ''
fileListB.value = []
formRef.value?.clearValidate('degreeImg')
} else {
// 如果需要学位证书,延迟清除验证(等字段显示后)
nextTick(() => {
formRef.value?.clearValidate('degreeImg')
})
}
}
// 文件上传成功 - 学历证书
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
@@ -313,6 +330,8 @@ const materiaUploadSuccess = (response: any) => {
return
}
dataForm.qualificationImg = response.data.url
// 清除验证错误
formRef.value?.clearValidate('qualificationImg')
}
// 文件上传成功 - 学位证书
@@ -322,31 +341,74 @@ const materiaUploadSuccessB = (response: any) => {
return
}
dataForm.degreeImg = response.data.url
// 清除验证错误
formRef.value?.clearValidate('degreeImg')
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=1`
const openDialog = async (row?: any) => {
// 新增模式:先检查是否锁定
if (!row || !row.id) {
try {
const lockResponse = await checkLocked('acade')
if (lockResponse.data) {
message.warning("新增功能已锁定,暂不允许操作")
return
}
} catch (error) {
// 错误处理已在数据请求层统一处理,此处不需要提示
return
}
}
// 公共设置
url.value = `/professional/file/teacherAboutInfoUpload?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 || ''
})
// 根据是否有 row 设置不同的表单数据
if (row && row.id) {
// 编辑模式:使用传入的数据
// 确保类型匹配(将 id 转换为字符串或数字,与选项列表保持一致)
Object.assign(dataForm, {
graduateTime: row.graduateTime || '',
type: row.type !== null && row.type !== undefined ? String(row.type) : '',
qualificationConfigId: row.qualificationConfigId !== null && row.qualificationConfigId !== undefined ? String(row.qualificationConfigId) : '',
degreeConfigId: row.degreeConfigId !== null && row.degreeConfigId !== undefined ? String(row.degreeConfigId) : '',
graduateSchool: row.graduateSchool || '',
major: row.major || '',
certificateNumber: row.certificateNumber || '',
qualificationImg: row.qualificationImg || '',
degreeImg: row.degreeImg || '',
state: row.state || '',
id: row.id || ''
})
} else {
// 新增模式:重置为空
Object.assign(dataForm, {
graduateTime: '',
type: '',
qualificationConfigId: '',
degreeConfigId: '',
graduateSchool: '',
major: '',
certificateNumber: '',
qualificationImg: '',
degreeImg: '',
state: '',
id: ''
})
}
// 公共操作:先加载字典数据,再显示对话框
await initDicData()
// 等待字典数据加载完成后再显示表单,确保选项列表已准备好
await nextTick()
showForm.value = true
initDicData()
dialogVisible.value = true
// 对话框打开后,清除所有验证错误,避免立即显示验证提示
await nextTick()
formRef.value?.clearValidate()
}
// 提交表单
@@ -357,13 +419,31 @@ const dialogSubmit = async () => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
// 统一使用 addObj 接口(新增和编辑都使用)
const submitData: any = {
graduateTime: dataForm.graduateTime,
type: dataForm.type,
qualificationConfigId: dataForm.qualificationConfigId,
degreeConfigId: dataForm.degreeConfigId,
graduateSchool: dataForm.graduateSchool,
major: dataForm.major,
certificateNumber: dataForm.certificateNumber,
qualificationImg: dataForm.qualificationImg,
degreeImg: dataForm.degreeImg,
state: '' // 待审核
}
// 编辑时需要传递 id
if (dataForm.id) {
submitData.id = dataForm.id
}
await addObj(submitData)
message.success(dataForm.id ? "修改成功" : "提交成功")
dialogVisible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
// 错误处理已在数据请求层统一处理,此处不需要提示
} finally {
submitLoading.value = false
}

View File

@@ -1,28 +1,6 @@
<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"
@@ -36,7 +14,6 @@
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
@@ -52,7 +29,6 @@
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
@@ -61,7 +37,6 @@
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
@@ -87,7 +62,8 @@
v-if="permissions.professional_professionalteacheracademicrelation_add">
</el-button>
<el-button
type="success"
type="warning"
plain
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
@@ -109,25 +85,26 @@
<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>
<AuditState :state="scope.row.state" :options="auditStateOptions" />
</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>
<el-table-column label="姓名/工号" min-width="150" align="center">
<template #default="scope">
{{ getDegreeName(scope.row.degreeConfigId) }}
</template>
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="graduateTime" label="毕业时间" width="180" align="center">
<template #default="scope">
{{ scope.row.graduateTime ? scope.row.graduateTime.split(' ')[0] : '-' }}
</template>
</el-table-column>
<el-table-column prop="type" label="教育类型" min-width="120" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getEducationTypeName(scope.row.type) }}
</template>
</el-table-column>
<el-table-column prop="qualificationConfigId" label="学历" min-width="120" align="center" show-overflow-tooltip>
@@ -135,8 +112,15 @@
{{ getQualificationName(scope.row.qualificationConfigId) }}
</template>
</el-table-column>
<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="createTime" label="创建时间" width="180" align="center" />
<!-- <el-table-column prop="createTime" label="创建时间" width="180" align="center" /> -->
<el-table-column label="学历证书附件" width="130" align="center">
<template #default="scope">
@@ -144,10 +128,11 @@
v-if="scope.row.qualificationImg && scope.row.qualificationImg !== ''"
type="primary"
link
icon="Document"
@click="handlePreview(scope.row.qiList, 1)">查看
</el-button>
</el-button>
<span v-else>-</span>
</template>
</template>
</el-table-column>
<el-table-column label="学位证书附件" width="130" align="center">
@@ -156,40 +141,44 @@
v-if="scope.row.degreeImg && scope.row.degreeImg !== ''"
type="primary"
link
icon="Document"
@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">
<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"
icon="edit-pen"
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"
icon="CircleCheck"
v-if="permissions.professional_professionalteacheracademicrelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
icon="CircleClose"
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"
type="primary"
link
icon="Delete"
icon="delete"
v-if="permissions.professional_professionalteacheracademicrelation_del"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
@@ -203,18 +192,15 @@
@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>
<!-- 材料预览图片直接显示PDF 在组件内部 dialog 中显示 -->
<preview-file
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
:dialog-title="dialogTitle"
/>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
@@ -229,28 +215,29 @@ 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,
examObj,
delObj,
getChartOption,
exportExcel
} from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig'
import { getAllTypeList } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig'
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'))
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 审核状态选项(独立定义,防止其他页面修改时被波及)
import type { StateOption } from '/@/components/AuditState/index.vue'
const auditStateOptions: StateOption[] = [
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
]
// 使用 Pinia store
const userInfoStore = useUserInfo()
@@ -275,10 +262,8 @@ 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)
// 搜索表单数据
@@ -288,21 +273,17 @@ const search = reactive({
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[]>([])
const educationTypeList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
@@ -334,34 +315,6 @@ const state: BasicTableProps = reactive<BasicTableProps>({
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 = []
@@ -378,10 +331,6 @@ const handlePreview = (list: string[], type: number) => {
} else if (type === 2) {
dialogTitle.value = '学位证书附件'
}
nextTick(() => {
dialogVisible.value = true
})
})
}
@@ -392,14 +341,15 @@ const changeState = (row: any, val: number) => {
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
await examObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
} else if (val === -2) {
@@ -425,7 +375,7 @@ const resetQuery = () => {
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(1)
dataFormRef.value?.openDialog()
}
// 打开编辑窗口
@@ -444,8 +394,9 @@ const handleDel = (row: any) => {
state.pagination.current = state.pagination.current - 1
}
getDataList()
} catch (error) {
// Failed to delete
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
}
@@ -486,20 +437,37 @@ const getQualificationName = (id: string | number) => {
return item ? item.qualificationName : '-'
}
// 获取教育类型名称
const getEducationTypeName = (id: string | number | undefined) => {
if (!id) return '-'
const item = educationTypeList.value.find((item: any) => item.id === id || String(item.id) === String(id))
return item ? item.name : '-'
}
// 加载字典数据
const loadDictData = async () => {
try {
// 并行加载所有字典数据
const [degreeRes, qualRes, eduTypeRes] = await Promise.all([
getDegreeList(),
getQualificationList(),
getAllTypeList()
])
// 加载学位列表
const degreeRes = await getDegreeList()
if (degreeRes && degreeRes.data) {
degreeList.value = degreeRes.data
}
// 加载学历列表
const qualRes = await getQualificationList()
if (qualRes && qualRes.data) {
qualificationList.value = qualRes.data
}
// 加载教育类型列表
if (eduTypeRes && eduTypeRes.data) {
educationTypeList.value = eduTypeRes.data
}
} catch (error) {
// Failed to load dict data
}

View File

@@ -0,0 +1,83 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 页面标题 -->
<div style="margin-bottom: 20px;">
<h2 style="margin: 0; font-size: 20px; font-weight: 600; color: #303133;">学历统计</h2>
</div>
<!-- 图表统计 -->
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
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 { getChartOption } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 图表引用
const chartRef = ref()
// 图表数据
const chartOption = ref<any>({})
const chartData = ref<any[]>([])
// 初始化图表
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
}
}
// 页面加载时初始化图表
onMounted(() => {
initChartOption()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -1,5 +1,5 @@
<template>
<el-dialog v-model="visible" title="编辑教师资格证" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<el-dialog v-model="dialogVisible" title="编辑教师资格证" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
@@ -10,43 +10,31 @@
<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"
:label="item.cretificateName"
: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
placeholder="请输入证书编号(仅支持英文和数字)"
show-word-limit
maxlength="64"
@input="handleCertificateNumberInput"
/>
</el-form-item>
<el-form-item label="材料1" prop="materialA">
<el-form-item label="证明材料" prop="evidenceA" required>
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileList"
@@ -55,24 +43,9 @@
>
<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>
<div style="margin-top: 8px;">
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</div>
</template>
</el-upload>
</el-form-item>
@@ -81,7 +54,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
@@ -92,18 +65,12 @@
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 { addObj } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
import { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf'
// Props
const props = defineProps<{
visible?: boolean
}>()
import { checkLocked } from '/@/api/professional/professionalstatuslock'
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
@@ -114,23 +81,15 @@ const message = useMessage()
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 对话框显示状态(内部管理)
const dialogVisible = ref(false)
// 表单数据
const dataForm = reactive({
certificateConfId: '',
certificateTime: '',
certificateNumber: '',
materialA: '',
materialB: '',
evidenceA: '',
evidenceB: '',
state: '',
teacherNo: '',
id: ''
})
@@ -139,48 +98,23 @@ const formRules = {
certificateConfId: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
certificateTime: [
{ required: true, message: '请选择取证时间', trigger: 'change' }
],
certificateNumber: [
{ required: true, message: '请输入证书编号', trigger: 'blur' }
{ required: true, message: '请输入证书编号', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
],
evidenceA: [
{ required: true, message: '请上传证明材料', trigger: 'change' }
]
}
// 教师资格证列表
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(() => {
@@ -195,74 +129,74 @@ 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
const res = await getTeacherCertificateList()
teacherCertificateList.value = res.data || []
} catch (error) {
// 获取字典数据失败
}
}
// 文件上传成功 - 材料A
// 证书编号输入处理(只允许英文和数字)
const handleCertificateNumberInput = (value: string) => {
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
}
// 文件上传成功
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
// 清除验证错误
formRef.value?.clearValidate('evidenceA')
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=0`
const openDialog = async (row?: any) => {
// 新增模式:先检查是否锁定
if (!row || !row.id) {
try {
const lockResponse = await checkLocked('teacherTitle')
if (lockResponse.data) {
message.warning("新增功能已锁定,暂不允许操作")
return
}
} catch (error) {
// 错误处理已在数据请求层统一处理,此处不需要提示
return
}
}
// 公共设置
url.value = `/professional/file/teacherAboutInfoUpload?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 || ''
})
// 根据是否有 row 设置不同的表单数据
if (row && row.id) {
// 编辑模式:使用传入的数据
Object.assign(dataForm, {
certificateConfId: row.certificateConfId || '',
certificateNumber: row.certificateNumber || '',
evidenceA: row.evidenceA || '',
state: row.state || '',
id: row.id || ''
})
} else {
// 新增模式:重置为空
Object.assign(dataForm, {
certificateConfId: '',
certificateNumber: '',
evidenceA: '',
state: '',
id: ''
})
}
// 公共操作:加载字典数据并显示对话框
await initDicData()
showForm.value = true
initDicData()
dialogVisible.value = true
}
// 提交表单
@@ -273,13 +207,25 @@ const dialogSubmit = async () => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
// 统一使用 addObj 接口(新增和编辑都使用)
const submitData: any = {
certificateConfId: dataForm.certificateConfId,
certificateNumber: dataForm.certificateNumber,
evidenceA: dataForm.evidenceA,
state: '' // 待审核
}
// 编辑时需要传递 id
if (dataForm.id) {
submitData.id = dataForm.id
}
await addObj(submitData)
message.success(dataForm.id ? "修改成功" : "提交成功")
dialogVisible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
// 错误处理已在数据请求层统一处理,此处不需要提示
} finally {
submitLoading.value = false
}

View File

@@ -14,7 +14,6 @@
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
@@ -30,7 +29,6 @@
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
@@ -39,7 +37,6 @@
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
@@ -65,7 +62,8 @@
v-if="permissions.professional_professionalteachercertificaterelation_add">
</el-button>
<el-button
type="success"
type="warning"
plain
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
@@ -87,18 +85,15 @@
<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>
<AuditState :state="scope.row.state" :options="auditStateOptions" />
</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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="certificateConfId" label="关联资格证书" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
@@ -116,40 +111,44 @@
v-if="scope.row.evidenceA && scope.row.evidenceA !== ''"
type="primary"
link
icon="Document"
@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">
<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"
icon="edit-pen"
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"
icon="CircleCheck"
v-if="permissions.professional_professionalteachercertificaterelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
icon="CircleClose"
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"
type="primary"
link
icon="Delete"
icon="delete"
v-if="permissions.professional_professionalteachercertificaterelation_del"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
@@ -163,18 +162,15 @@
@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>
<!-- 材料预览图片直接显示PDF 在组件内部 dialog 中显示 -->
<preview-file
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
dialog-title="教师资格材料"
/>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
@@ -192,15 +188,24 @@ import { useDict } from '/@/hooks/dict'
import { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf'
import {
fetchList,
putObj,
examObj,
delObj,
exportExcel
} from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
import { defineAsyncComponent } from 'vue'
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.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'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
// 审核状态选项(独立定义,防止其他页面修改时被波及)
import type { StateOption } from '/@/components/AuditState/index.vue'
const auditStateOptions: StateOption[] = [
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
]
// 使用 Pinia store
const userInfoStore = useUserInfo()
@@ -225,7 +230,6 @@ 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)
@@ -238,7 +242,6 @@ const search = reactive({
})
// 材料预览
const dialogVisible = ref(false)
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
@@ -281,9 +284,6 @@ const handlePreview = (list: string[]) => {
url: v
})
})
nextTick(() => {
dialogVisible.value = true
})
})
}
@@ -294,14 +294,15 @@ const changeState = (row: any, val: number) => {
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
await examObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
} else if (val === -2) {
@@ -327,7 +328,7 @@ const resetQuery = () => {
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(0)
dataFormRef.value?.openDialog()
}
// 打开编辑窗口
@@ -346,8 +347,9 @@ const handleDel = (row: any) => {
state.pagination.current = state.pagination.current - 1
}
getDataList()
} catch (error) {
// Failed to delete
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
}

View File

@@ -1,79 +1,70 @@
<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>
<el-dialog v-model="dialogVisible" :title="title" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
<el-form
ref="formRef"
:model="dataForm"
:rules="dataRules"
label-width="120px"
>
<el-form-item label="荣誉" prop="honor">
<el-input v-model="dataForm.honor" placeholder="请输入荣誉" clearable />
</el-form-item>
<el-form-item label="表彰单位" prop="honorCompany">
<el-input v-model="dataForm.honorCompany" placeholder="请输入表彰单位" clearable />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker
v-model="dataForm.year"
type="year"
placeholder="请选择年份"
format="YYYY"
value-format="YYYY"
/>
</el-form-item>
<el-form-item label="证明材料" prop="attachment" required>
<el-upload
ref="uploadRef"
:headers="headers"
:limit="1"
:action="url"
:before-upload="beforeUpload"
:on-success="materiaUploadSuccess"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:file-list="fileList"
:accept="'.jpg,.jpeg,.png,.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<div style="margin-top: 8px;">
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button @click="dialogSubmit" type="primary" :loading="submitLoading"> </el-button>
<el-button @click="dialogVisible = 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 { ref, reactive, computed, nextTick } 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
}>()
import { addObj } from '/@/api/professional/professionaluser/professionalteacherhonor'
import { checkLocked } from '/@/api/professional/professionalstatuslock'
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
@@ -82,23 +73,20 @@ const message = useMessage()
// 表单引用
const formRef = ref()
const uploadRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 对话框显示状态(内部管理)
const dialogVisible = ref(false)
// 表单数据
const dataForm = reactive({
honor: '',
honorCompany: '',
year: null as number | null,
year: '',
materialA: '',
attachment: '',
state: '',
teacherNo: '',
teacherName: '',
id: ''
})
@@ -106,42 +94,20 @@ const dataForm = reactive({
// 表单验证规则
const dataRules = {
honor: [
{ required: true, message: '请填写荣誉', trigger: 'change' }
{ required: true, message: '请填写荣誉', trigger: 'blur' }
],
honorCompany: [
{ required: true, message: '请填写表彰单位', trigger: 'change' }
{ required: true, message: '请填写表彰单位', trigger: 'blur' }
],
year: [
{ required: true, message: '请填写年份', trigger: 'change' }
year: [
{ required: true, message: '请选择年份', trigger: 'change' }
],
attachment: [
{ 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('')
@@ -160,55 +126,129 @@ 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 beforeUpload = () => {
return true
}
// 文件超出限制时的处理(当 limit=1 且已有文件时,用户上传新文件会触发此事件)
// 根据 Element Plus 官方文档,在 on-exceed 中清空文件列表并添加新文件
const handleExceed = (files: File[]) => {
// 清空当前文件列表
uploadRef.value?.clearFiles()
fileList.value = []
dataForm.attachment = ''
// 等待 DOM 更新后,手动触发新文件的上传
nextTick(() => {
const newFile = files[0]
if (newFile && uploadRef.value) {
// 通过 input 元素手动触发文件上传
const input = uploadRef.value.$el?.querySelector('input[type="file"]')
if (input) {
// 创建新的 DataTransfer 对象并添加新文件
const dataTransfer = new DataTransfer()
dataTransfer.items.add(newFile)
input.files = dataTransfer.files
// 触发 change 事件,让 Element Plus 自动处理上传
const changeEvent = new Event('change', { bubbles: true })
input.dispatchEvent(changeEvent)
}
}
})
}
// 文件上传成功
const materiaUploadSuccess = (response: any) => {
const materiaUploadSuccess = (response: any, file: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
// 上传失败,移除文件
fileList.value = []
uploadRef.value?.clearFiles()
return
}
dataForm.attachment = response.data.url
if (response.data && response.data.url) {
dataForm.attachment = response.data.url
// 更新文件列表,确保显示新上传的文件(替换旧文件)
fileList.value = [{
name: file.name || '证明材料',
url: response.data.url,
uid: file.uid || Date.now(),
status: 'success'
}]
formRef.value?.clearValidate('attachment')
}
}
// 文件删除处理
const handleRemove = () => {
dataForm.attachment = ''
fileList.value = []
formRef.value?.clearValidate('attachment')
}
// 打开对话框
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 || ''
})
const openDialog = async (row?: any) => {
// 新增模式:先检查是否锁定
if (!row || !row.id) {
try {
const lockResponse = await checkLocked('remix')
if (lockResponse.data) {
message.warning("新增功能已锁定,暂不允许操作")
return
}
} catch (error) {
// 错误处理已在数据请求层统一处理,此处不需要提示
return
}
}
// 根据是否有 row 设置不同的表单数据
if (row && row.id) {
// 编辑模式:使用传入的数据
title.value = row.teacherName || ''
Object.assign(dataForm, {
honor: row.honor || '',
honorCompany: row.honorCompany || '',
year: row.year ? String(row.year) : '',
materialA: row.materialA || '',
attachment: row.attachment || '',
state: row.state || '',
teacherName: row.teacherName || '',
id: row.id || ''
})
// 回显已上传的证明材料
fileList.value = dataForm.attachment
? [{ name: '证明材料', url: dataForm.attachment }]
: []
} else {
// 新增模式:重置为空
title.value = '新增综合表彰'
Object.assign(dataForm, {
honor: '',
honorCompany: '',
year: '',
attachment: '',
state: '',
teacherName: '',
id: ''
})
fileList.value = []
}
// 公共设置:每次打开都重置上传地址
url.value = `/professional/file/teacherAboutInfoUpload?type=4`
// 公共操作:加载字典数据并显示对话框
await initDicData()
showForm.value = true
initDicData()
dialogVisible.value = true
}
// 提交表单
@@ -219,13 +259,13 @@ const dialogSubmit = async () => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
// 统一使用 addObj 接口(新增和编辑都使用同一个接口)
await addObj(dataForm)
message.success(dataForm.id ? "修改成功" : "提交成功")
dialogVisible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
// 错误处理已在数据请求层统一处理,此处不需要提示
} finally {
submitLoading.value = false
}

View File

@@ -14,7 +14,6 @@
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
@@ -30,7 +29,6 @@
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
@@ -39,7 +37,6 @@
<el-input
v-model="search.teacherName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
@@ -65,10 +62,11 @@
v-if="permissions.professional_professionalteacherhonor_add">
</el-button>
<el-button
type="success"
type="warning"
plain
icon="Download"
@click="handleDownLoadWord"
:loading="exportLoading">导出
:loading="exportLoading">导出信息
</el-button>
</div>
</el-row>
@@ -86,18 +84,15 @@
<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>
<AuditState :state="scope.row.state" :options="auditStateOptions" />
</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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="honor" label="荣誉" min-width="150" align="center" show-overflow-tooltip />
@@ -111,40 +106,44 @@
v-if="scope.row.attachment && scope.row.attachment !== ''"
type="primary"
link
@click="showEdvince(scope.row)">
icon="Document"
@click="showEdvince(scope.row)">查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<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"
icon="edit-pen"
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"
icon="CircleCheck"
v-if="permissions.professional_professionalteacherhonor_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="Close"
icon="CircleClose"
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"
type="primary"
link
icon="Delete"
icon="delete"
v-if="permissions.professional_professionalteacherhonor_del"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
@@ -158,17 +157,23 @@
@size-change="sizeChangeHandle"
/>
<!-- 材料预览图片直接显示PDF 在组件内部 dialog 中显示 -->
<preview-file
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
dialog-title="综合表彰材料"
/>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<ShowHonorEdvince ref="showHonorEdvinceRef" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
<DataForm ref="dataFormRef" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { BasicTableProps, useTable } from '/@/hooks/table'
@@ -177,14 +182,15 @@ import { useMessageBox } from '/@/hooks/message'
import { useDict } from '/@/hooks/dict'
import {
fetchList,
putObj,
examObj,
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'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
// 使用 Pinia store
const userInfoStore = useUserInfo()
@@ -206,11 +212,17 @@ const messageBox = useMessageBox()
// 字典数据
const { professional_state: professionalState } = useDict('professional_state')
// 审核状态选项(独立定义,防止其他页面修改时被波及)
import type { StateOption } from '/@/components/AuditState/index.vue'
const auditStateOptions: StateOption[] = [
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
]
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const multiDialogRef = ref()
const showHonorEdvinceRef = ref()
const backReasonRef = ref()
const dataFormRef = ref()
const showSearch = ref(true)
@@ -222,6 +234,9 @@ const search = reactive({
teacherName: ''
})
// 材料预览
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
@@ -241,9 +256,18 @@ const state: BasicTableProps = reactive<BasicTableProps>({
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 查看证明材料
// 查看证明材料:构造预览列表,交给 auth-img 处理
const showEdvince = (row: any) => {
showHonorEdvinceRef.value?.init(row)
imgUrl.value = []
nextTick(() => {
const list = row.attachment ? [row.attachment] : []
list.forEach(v => {
imgUrl.value.push({
title: '',
url: v
})
})
})
}
// 审核状态变更
@@ -253,20 +277,20 @@ const changeState = (row: any, val: number) => {
const str = '通过'
messageBox.confirm(`是否确认${str}${row.teacherName || row.realName}的申请`).then(async () => {
try {
await putObj({
await examObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
} else if (val === -2) {
// 驳回
const newRow = JSON.parse(JSON.stringify(row))
newRow.realName = newRow.teacherName
backReasonRef.value?.init(newRow, 'honor')
}
}
@@ -288,7 +312,7 @@ const resetQuery = () => {
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(4)
dataFormRef.value?.openDialog()
}
// 打开编辑窗口
@@ -307,8 +331,9 @@ const handleDel = (row: any) => {
state.pagination.current = state.pagination.current - 1
}
getDataList()
} catch (error) {
// Failed to delete
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
}

View File

@@ -1,44 +0,0 @@
<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

@@ -6,7 +6,7 @@
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter(search)"
@keyup-enter="handleFilter"
>
<template #default="{ visible }">
<template v-if="visible">
@@ -14,7 +14,6 @@
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
@@ -23,7 +22,6 @@
<el-input
v-model="search.teacherName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
@@ -33,7 +31,7 @@
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter(search)" icon="Search">查询</el-button>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
@@ -50,15 +48,17 @@
>
<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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.teacherName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<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 prop="changeDate" label="调令日期" min-width="120" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
@@ -84,9 +84,12 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/professionaluser/professionalteacherstationchange'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
// 表格引用
const tableRef = ref()
const searchFormRef = ref()

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -1,5 +1,5 @@
<template>
<el-dialog v-model="visible" title="编辑职称" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
<el-dialog v-model="dialogVisible" title="编辑职称" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div v-if="showForm">
<el-form
ref="formRef"
@@ -12,8 +12,7 @@
v-model="dataForm.professionalTitleConfigId"
filterable
clearable
placeholder="请选择职称等级"
style="width: 100%"
placeholder="请选择职称等级"
>
<el-option
v-for="item in proTitleList"
@@ -30,7 +29,6 @@
filterable
clearable
placeholder="请选择专业技术职务"
style="width: 100%"
>
<el-option
v-for="item in majorStationList"
@@ -48,7 +46,6 @@
placeholder="请选择取证时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
@@ -59,22 +56,23 @@
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="请输入证书编号"
placeholder="请输入证书编号(仅支持英文和数字)"
clearable
show-word-limit
maxlength="32"
@input="handleCertificateNumberInput"
/>
</el-form-item>
<el-form-item label="证明材料" prop="materialA">
<el-form-item label="证明材料" prop="evidence">
<el-upload
:headers="headers"
style="width:3.5cm;height:5.3cm;"
:limit="1"
:action="url"
:file-list="fileList"
@@ -83,7 +81,9 @@
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
<div style="margin-top: 8px;">
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
</div>
</template>
</el-upload>
</el-form-item>
@@ -92,7 +92,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
@@ -103,17 +103,13 @@
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
}>()
import { getProfessionalTitleList } from '/@/api/professional/rsbase/professionaltitlelevelconfig'
import { getMajorStationList } from '/@/api/professional/rsbase/professionalmajorstation'
import { addObj } from '/@/api/professional/professionaluser/professionaltitlerelation'
import { checkLocked } from '/@/api/professional/professionalstatuslock'
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'refreshData'): void
}>()
@@ -124,11 +120,8 @@ const message = useMessage()
const formRef = ref()
const submitLoading = ref(false)
// 对话框显示状态
const visible = computed({
get: () => props.visible || false,
set: (val) => emit('update:visible', val)
})
// 对话框显示状态(内部管理)
const dialogVisible = ref(false)
// 表单数据
const dataForm = reactive({
@@ -137,10 +130,8 @@ const dataForm = reactive({
certificateTime: '',
inOfficeDate: '',
certificateNumber: '',
materialA: '',
evidence: '',
state: '',
teacherNo: '',
id: ''
})
@@ -156,35 +147,21 @@ const formRules = {
{ required: true, message: '请选择取证时间', trigger: 'change' }
],
certificateNumber: [
{ required: true, message: '请输入证书编号', trigger: 'blur' }
{ required: true, message: '请输入证书编号', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
],
evidence: [
{ 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: []
majorStationList: []
})
// 上传相关
@@ -210,52 +187,93 @@ 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
const [titleResponse, stationResponse] = await Promise.all([
getProfessionalTitleList(),
getMajorStationList()
])
// 处理职称列表(与 index.vue 保持一致)
if (titleResponse && titleResponse.data) {
baseInfoAbout.proTitleList = titleResponse.data
}
// 处理专业技术职务列表(与 index.vue 保持一致)
if (stationResponse && stationResponse.data) {
baseInfoAbout.majorStationList = stationResponse.data
}
} catch (error) {
// 获取字典数据失败
message.error('获取字典数据失败')
}
}
// 证书编号输入处理(只允许英文和数字)
const handleCertificateNumberInput = (value: string) => {
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
}
// 文件上传成功
const materiaUploadSuccess = (response: any) => {
if (response.data && response.data.code === "-1") {
message.error("当前不允许上传文件")
return
}
// 统一使用 evidence 字段存储证明材料URL与后端API一致
dataForm.evidence = response.data.url
// 上传成功后清除该字段的验证错误
formRef.value?.clearValidate('evidence')
}
// 打开对话框
const openDialog = (row: any) => {
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=2`
const openDialog = async (row?: any) => {
// 新增模式:先检查是否锁定
if (!row || !row.id) {
try {
const lockResponse = await checkLocked('title')
if (lockResponse.data) {
message.warning("新增功能已锁定,暂不允许操作")
return
}
} catch (error) {
// 错误处理已在数据请求层统一处理,此处不需要提示
return
}
}
// 公共设置
url.value = `/professional/file/teacherAboutInfoUpload?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 || ''
})
// 根据是否有 row 设置不同的表单数据
if (row && row.id) {
// 编辑模式:使用传入的数据
Object.assign(dataForm, {
professionalTitleConfigId: row.professionalTitleConfigId || '',
majorStation: row.majorStation || '',
certificateTime: row.certificateTime || '',
inOfficeDate: row.inOfficeDate || '',
certificateNumber: row.certificateNumber || '',
evidence: row.evidence || '',
state: row.state || '',
id: row.id || ''
})
} else {
// 新增模式:重置为空
Object.assign(dataForm, {
professionalTitleConfigId: '',
majorStation: '',
certificateTime: '',
inOfficeDate: '',
certificateNumber: '',
evidence: '',
state: '',
id: ''
})
}
// 公共操作:加载字典数据并显示对话框
await initDicData()
showForm.value = true
initDicData()
dialogVisible.value = true
}
// 提交表单
@@ -266,13 +284,28 @@ const dialogSubmit = async () => {
if (valid) {
submitLoading.value = true
try {
dataForm.state = '0'
await putObj(dataForm)
message.success("操作成功")
visible.value = false
// 统一使用 addObj 接口(新增和编辑都使用)
const submitData: any = {
professionalTitleConfigId: dataForm.professionalTitleConfigId,
majorStation: dataForm.majorStation,
certificateTime: dataForm.certificateTime,
inOfficeDate: dataForm.inOfficeDate,
certificateNumber: dataForm.certificateNumber,
evidence: dataForm.evidence,
state: '' // 待审核
}
// 编辑时需要传递 id
if (dataForm.id) {
submitData.id = dataForm.id
}
await addObj(submitData)
message.success(dataForm.id ? "修改成功" : "提交成功")
dialogVisible.value = false
emit('refreshData')
} catch (error: any) {
message.error(error?.msg || '操作失败')
// 错误处理已在数据请求层统一处理,此处不需要提示
} finally {
submitLoading.value = false
}

View File

@@ -1,41 +1,6 @@
<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"
@@ -49,7 +14,6 @@
<el-select
v-model="search.state"
clearable
style="width: 200px"
placeholder="请选择审核状态"
>
<el-option
@@ -65,7 +29,6 @@
<el-input
v-model="search.teacherNo"
clearable
style="width: 200px"
placeholder="请输入工号"
/>
</el-form-item>
@@ -74,7 +37,6 @@
<el-input
v-model="search.realName"
clearable
style="width: 200px"
placeholder="请输入姓名"
/>
</el-form-item>
@@ -84,7 +46,6 @@
v-model="search.professionalTitleConfigId"
clearable
filterable
style="width: 200px"
placeholder="请选择职称"
>
<el-option
@@ -101,7 +62,6 @@
v-model="search.majorStation"
clearable
filterable
style="width: 200px"
placeholder="请选择专业技术职务"
>
<el-option
@@ -134,7 +94,9 @@
v-if="permissions.professional_professionaltitlerelation_add">
</el-button>
<el-button
type="success"
class="ml10"
type="warning"
plain
icon="Download"
v-if="permissions.professional_teacherbase_export"
@click="handleDownLoadWord"
@@ -188,7 +150,7 @@
v-if="scope.row.evidence && scope.row.evidence !== ''"
type="primary"
link
icon="View"
icon="Document"
@click="handlePreview(scope.row.srcList)">查看
</el-button>
<span v-else>-</span>
@@ -209,14 +171,14 @@
<el-button
type="success"
link
icon="check"
icon="CircleCheck"
v-if="permissions.professional_professionaltitlerelation_exam && scope.row.state === '0'"
@click="changeState(scope.row, 1)">通过
</el-button>
<el-button
type="danger"
link
icon="close"
icon="CircleClose"
v-if="permissions.professional_professionaltitlerelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
@click="changeState(scope.row, -2)">驳回
</el-button>
@@ -239,15 +201,13 @@
@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>
<!-- 材料预览图片直接显示PDF 在组件内部 dialog 中显示 -->
<preview-file
v-for="src in imgUrl"
:key="src.title"
:authSrc="src.url"
dialog-title="职称材料"
/>
<!-- 子组件 -->
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
@@ -265,30 +225,23 @@ 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,
examObj,
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])
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
// 使用 Pinia store
const userInfoStore = useUserInfo()
@@ -316,8 +269,6 @@ const searchFormRef = ref()
const multiDialogRef = ref()
const dataFormRef = ref()
const backReasonRef = ref()
const titleChartRef = ref()
const techChartRef = ref()
const showSearch = ref(true)
// 搜索表单数据
@@ -329,14 +280,7 @@ const search = reactive({
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 }>>([])
// 导出加载状态
@@ -371,54 +315,6 @@ const state: BasicTableProps = reactive<BasicTableProps>({
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 = []
@@ -429,9 +325,6 @@ const handlePreview = (list: string[]) => {
url: v
})
})
nextTick(() => {
dialogVisible.value = true
})
})
}
@@ -442,14 +335,15 @@ const changeState = (row: any, val: number) => {
const str = '通过'
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
try {
await putObj({
await examObj({
id: row.id,
state: val
})
message.success('操作成功')
getDataList()
} catch (error) {
// Failed to change state
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
} else if (val === -2) {
@@ -477,7 +371,7 @@ const resetQuery = () => {
// 打开新增窗口
const handleAdd = () => {
multiDialogRef.value?.init(2)
dataFormRef.value?.openDialog()
}
// 打开编辑窗口
@@ -496,8 +390,9 @@ const handleDel = (row: any) => {
state.pagination.current = state.pagination.current - 1
}
getDataList()
} catch (error) {
// Failed to delete
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
}
}).catch(() => {})
}

View File

@@ -0,0 +1,119 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 页面标题 -->
<div style="margin-bottom: 20px;">
<h2 style="margin: 0; font-size: 20px; font-weight: 600; color: #303133;">职称统计</h2>
</div>
<!-- 图表统计 -->
<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>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
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 { getChartOption } from '/@/api/professional/professionaluser/professionaltitlerelation'
// 注册 ECharts 组件
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
// 图表引用
const titleChartRef = ref()
const techChartRef = ref()
// 图表数据
const titleChartOption = ref<any>({})
const titleChartTableData = ref<any[]>([])
const techChartOption = ref<any>({})
const techChartTableData = ref<any[]>([])
// 初始化图表
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
}
}
// 页面加载时初始化图表
onMounted(() => {
initChartOption()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -201,8 +201,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -25,9 +25,11 @@
>
<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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.teacherName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="year" label="年份" width="100" align="center" />
@@ -174,11 +176,14 @@
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { defineAsyncComponent } 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'
import { fetchList, addObj, putObj, delObj } from '/@/api/professional/salaries/professionalyearbounds'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
// 使 Pinia store
const userInfoStore = useUserInfo()
@@ -287,7 +292,7 @@ const handleDel = (row: any) => {
message.success('删除成功')
getDataList(false) //
} catch (error: any) {
message.error(error?.msg || '删除失败')
message.error(error.msg)
}
}).catch(() => {
//
@@ -312,7 +317,7 @@ const handleSubmit = async () => {
dialogVisible.value = false
getDataList(false) //
} catch (error: any) {
message.error(error?.msg || '操作失败')
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -1,323 +0,0 @@
<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

@@ -1,320 +0,0 @@
<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

@@ -18,7 +18,6 @@
value-format="YYYY"
placeholder="请选择薪资年份"
clearable
style="width: 200px"
/>
</el-form-item>
@@ -30,11 +29,18 @@
value-format="M"
placeholder="请选择薪资月份"
clearable
style="width: 200px"
/>
</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>
<!-- 表格 -->
@@ -49,13 +55,13 @@
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="createTime" label="导出时间" width="180" align="center" />
<el-table-column prop="createTime" label="导出时间" min-width="180" align="center" />
<el-table-column prop="salaryYear" label="薪资年份" width="120" align="center" />
<el-table-column prop="salaryYear" label="薪资年份" min-width="120" align="center" />
<el-table-column prop="salaryMonth" label="薪资月份" width="120" align="center" />
<el-table-column prop="salaryMonth" label="薪资月份" min-width="120" align="center" />
<el-table-column prop="confirm" label="劳务日期锁定" width="150" align="center">
<el-table-column prop="confirm" label="劳务日期锁定" min-width="150" align="center">
<template #default="scope">
<el-tag :type="scope.row.confirm === '1' ? 'success' : 'info'">
{{ scope.row.confirm === '1' ? '是' : '否' }}
@@ -63,7 +69,7 @@
</template>
</el-table-column>
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<el-table-column label="操作" min-width="80" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.professional_salaryexportrecord_del"
@@ -92,7 +98,7 @@ 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'
import { fetchList, delObj } from '/@/api/professional/salaries/salaryexportrecord'
// 使 Pinia store
const userInfoStore = useUserInfo()
@@ -145,6 +151,16 @@ const handleFilter = () => {
getDataList() //
}
//
const resetQuery = () => {
searchFormRef.value?.formRef?.resetFields()
Object.assign(search, {
salaryYear: '',
salaryMonth: ''
})
handleFilter()
}
//
const handleDel = (row: any) => {
messageBox.confirm('确认删除?').then(async () => {
@@ -153,7 +169,7 @@ const handleDel = (row: any) => {
message.success('删除成功')
getDataList(false) //
} catch (error: any) {
message.error(error?.msg || '删除失败')
message.error(error.msg)
}
}).catch(() => {
//

View File

@@ -1,18 +1,6 @@
<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"
@@ -27,7 +15,6 @@
v-model="search.realName"
placeholder="请输入姓名"
clearable
style="width: 200px"
/>
</el-form-item>
@@ -36,7 +23,6 @@
v-model="search.teacherNo"
placeholder="请输入工号"
clearable
style="width: 200px"
/>
</el-form-item>
@@ -48,13 +34,33 @@
value-format="YYYY"
placeholder="请选择年份"
clearable
style="width: 200px"
/>
</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">
<el-button
type="primary"
plain
icon="UploadFilled"
v-if="permissions.teacher_award_import"
@click="handleImportBaseSalary">绩效导入
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
@@ -63,13 +69,14 @@
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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始日期" width="120" align="center" />
@@ -104,7 +111,7 @@ 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 { fetchList } from '/@/api/professional/salaries/teacherawardtax'
import ImportAwardTax from './importAwardTax.vue'
// 使 Pinia store
@@ -156,6 +163,17 @@ const handleFilter = () => {
getDataList() //
}
//
const resetQuery = () => {
searchFormRef.value?.formRef?.resetFields()
Object.assign(search, {
realName: '',
teacherNo: '',
year: ''
})
handleFilter()
}
//
const handleImportBaseSalary = () => {
importAwardTaxRef.value?.init()
@@ -163,7 +181,4 @@ const handleImportBaseSalary = () => {
</script>
<style lang="scss" scoped>
.data-table {
width: 100% !important;
}
</style>

View File

@@ -1,9 +1,9 @@
<template>
<el-dialog v-model="dialogEmptyFromVisible" title="教职工信息导出" width="80%">
<el-form style="margin-left: 7px" :inline="true">
<el-form :inline="true" label-width="100px" style="margin-bottom: 20px;">
<el-form-item>
<el-tag>导出教职工人数,依据'职工信息'页面检索条件,如部门,职称等级等条件</el-tag>
<el-tag type="warning">*请在'职工信息'页面点击搜索后,再点开当前导出页面</el-tag>
<el-tag type="info">导出依据"职工信息"页面检索条件</el-tag>
<el-tag type="warning" style="margin-left: 10px;">请在搜索后再打开导出页面</el-tag>
</el-form-item>
<el-form-item label="是否退休">
<el-select
@@ -11,7 +11,7 @@
filterable
reserve-keyword
clearable
>
style="width: 200px;">
<el-option
v-for="item in YES_OR_NO"
:key="item.value"
@@ -25,52 +25,54 @@
type="primary"
@click="exportTeacherInfo"
:loading="exportLoading"
size="small">导出</el-button>
:icon="Download">导出</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>
<!-- 信息 -->
<el-card shadow="never" style="margin-bottom: 20px;">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>基础信息</span>
<el-checkbox v-model="allCheckTeacherBaseInfo">全选</el-checkbox>
</div>
</template>
<el-checkbox-group v-model="checkedTeacherBaseInfo">
<el-checkbox v-for="(item, index) in teacherBasicCheckData" :label="item.value" :key="index" style="margin-right: 20px; margin-bottom: 10px;">
{{item.label}}
</el-checkbox>
</el-checkbox-group>
</el-card>
<!-- 岗位信息 -->
<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>
<!-- 岗位信息 -->
<el-card shadow="never" style="margin-bottom: 20px;">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>岗位信息</span>
<el-checkbox v-model="allCheckStationInfo">全选</el-checkbox>
</div>
</template>
<el-checkbox-group v-model="checkedTeacherStationInfo">
<el-checkbox v-for="(item, index) in teacherStationData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px;">
{{item.label}}
</el-checkbox>
</el-checkbox-group>
</el-card>
<!-- 其他 -->
<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-card shadow="never">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>其他</span>
<el-checkbox v-model="allCheckedother">全选</el-checkbox>
</div>
</template>
<el-checkbox-group v-model="checkedTeacherOtherInfo">
<el-checkbox v-for="(item, index) in teacherOtherData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px;">
{{item.label}}
</el-checkbox>
</el-checkbox-group>
</el-card>
</el-dialog>
@@ -79,6 +81,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElNotification } from 'element-plus'
import { Download } from '@element-plus/icons-vue'
import global from '/@/components/tools/commondict.vue'
import request from '/@/utils/request'
@@ -250,5 +253,4 @@
</script>
<style scoped>
</style>

View File

@@ -1,39 +1,46 @@
<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-dialog v-model="visible" title="导入职工信息" width="600" append-to-body>
<el-alert
type="warning"
:closable="false"
show-icon
style="margin-bottom: 20px;">
<template #title>
<span>导入前请先下载字典文件部分字段需严格按照字典值填写</span>
</template>
</el-alert>
<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>
<div style="text-align: center; margin-bottom: 20px;">
<a href="excel/dictlist.xlsx" rel="external nofollow" download="职工信息字典下载" style="text-decoration: none;">
<el-button type="success" :icon="Download">下载字典文件</el-button>
</a>
</div>
<el-upload
class="upload-demo"
action="/professional/file/importTeacherInfoSimple"
:headers="headers"
:accept="'.xls,.xlsx'"
:on-success="handleUploadSuccess"
:on-error="handleAvatarError"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 .xls .xlsx 格式的 Excel 文件
</div>
</template>
</el-upload>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ElNotification } from 'element-plus'
import { Download, UploadFilled } from '@element-plus/icons-vue'
import { Session } from '/@/utils/storage'
// 响应式数据
@@ -77,6 +84,10 @@
})
</script>
<style scoped>
<style scoped lang="scss">
.upload-demo {
:deep(.el-upload-dragger) {
width: 100%;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,322 +1,16 @@
<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-dialog v-model="dialogVisible" width="600" :title="waitShenheForm.title" append-to-body>
<!--人员调动-->
<el-form v-if="waitShenheForm.isPersonnelTransfer" ref="stationChangeFormRef" :model="waitShenheForm.form" :rules="stationChangeRules" label-width="120px">
<el-form-item label="工号 / 姓名">
<el-tag>{{waitShenheForm.form.teacherNo}} - {{waitShenheForm.form.realName}}</el-tag>
</el-form-item>
<el-form-item label="原部门名称">
<el-tag disabled>{{waitShenheForm.form.deptName}}</el-tag>
<el-input v-model="waitShenheForm.form.deptName" disabled />
</el-form-item>
<el-form-item label="现二级部门*">
<el-select v-model="newSecDeptCode" @change="getDeptListByParent" placeholder="请选择二级部门" style="width: 100%">
<el-form-item label="现二级部门" prop="newDeptCode">
<el-select v-model="newSecDeptCode" @change="getDeptListByParent" placeholder="请选择二级部门">
<el-option v-for="item in secDeptList"
:key="item.deptCode"
:label="item.deptName"
@@ -340,9 +34,8 @@
v-model="waitShenheForm.form.changeDate"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
@@ -362,18 +55,16 @@
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(5)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
<div style="text-align: right; width: 100%;">
<el-button type="primary" @click="dialogSubmit(5)">提交</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
</div>
</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 v-if="waitShenheForm.isPartyTransfer" ref="partChangeFormRef" :model="waitShenheForm.form" :rules="partChangeRules" label-width="120px">
<el-form-item label="工号 / 用户名">
<el-tag>{{waitShenheForm.form.teacherNo}} - {{waitShenheForm.form.realName}}</el-tag>
</el-form-item>
<el-form-item label="原支部名">
@@ -407,182 +98,126 @@
v-model="waitShenheForm.form.changeTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="waitShenheForm.form.remarks" placeholder="请输入备注" />
<el-input v-model="waitShenheForm.form.remarks" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogSubmit(6)">提交</el-button>
<el-button @click="educationDialogFromVisible = false">取消</el-button>
<div style="text-align: right; width: 100%;">
<el-button type="primary" @click="dialogSubmit(6)">提交</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
</div>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { ref, reactive, nextTick } from 'vue'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { updateOtherInfo, getMyTeacherNo, getAllInfoAboutList } from '/@/api/professional/teacherbase'
import { checkLocked } from '/@/api/professional/professionalstatuslock'
import { getAllInfoAboutList } from '/@/api/professional/professionaluser/teacherbase'
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 { addObj as addStationChangeObj } from '/@/api/professional/professionaluser/professionalteacherstationchange'
import { addObj as addPartyChangeObj } from '/@/api/professional/professionaluser/professionalpartychange'
import global from '/@/components/tools/commondict.vue'
import { Session } from '/@/utils/storage'
import type { Pagination } from '/@/hooks/table'
// 表单类型常量
const FORM_TYPE = {
PERSONNEL_TRANSFER: 5, // 人员调动
PARTY_TRANSFER: 6 // 党员调动
} as const
// 类型定义
interface FormData {
newDeptCodeList?: any[]
deptCodeList?: any[]
teacherNo?: string
realName?: string
userName?: string
deptName?: string
id?: string
oldBranchId?: string
oldBranchName?: string
newDeptCode?: string
newSecDeptCode?: string
changeDate?: string
pos?: string
remarks?: string
branchName?: string
partyFee?: number
changeTime?: string
[key: string]: any
}
interface WaitShenheForm {
form: FormData
title: string
isPersonnelTransfer: boolean
isPartyTransfer: boolean
}
// 字典数据
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' }]
newDeptCode: [{ required: true, message: '请选择要调入的二级部门', trigger: 'blur' }],
changeDate: [{ required: true, message: '请选择调令日期', trigger: 'blur' }],
pos: [{ required: true, message: '请选择岗位类型', trigger: 'blur' }]
}
// 党员调动验证规则
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) {
// 获取学位列表失败
}
branchName: [{ required: true, message: '请选择现支部', trigger: 'blur' }],
changeTime: [{ required: true, message: '请选择变动时间', trigger: 'blur' }]
}
// Props
/**
* 定义Props
* @param page 分页信息
* @param nowRow 当前行数据
*/
const props = defineProps<{
page: any
nowRow: any
page?: Pagination
nowRow: Record<string, any>
}>()
// Emits
const emit = defineEmits(['getList'])
/**
* 定义Emits
*/
const emit = defineEmits<{
(e: 'getList', page?: Pagination): void
}>()
// 消息提示 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 dialogVisible = ref(false)
const materialUrlFrom = reactive({
url: '/professional/file/teacherAboutInfoUpload',
fileListA: [] as any[],
fileListB: [] as any[],
fileListC: [] as any[]
/**
* 等待审核表单
*/
const waitShenheForm = reactive<WaitShenheForm>({
form: {
newDeptCodeList: [],
deptCodeList: []
},
title: '',
isPersonnelTransfer: false,
isPartyTransfer: false
})
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[]
partBranchList: [] as any[]
})
const secDeptList = ref<any[]>([])
@@ -591,212 +226,96 @@
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 initDicData = async () => {
const response = await getAllInfoAboutList()
const map = response.data
baseInfoAbout.partBranchList = map['partBranchList'] || []
}
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] = ''
}
}
/**
* 初始化对话框
* @param val 表单类型5-人员调动6-党员调动
*/
const init = async (val: number) => {
if (val !== FORM_TYPE.PERSONNEL_TRANSFER && val !== FORM_TYPE.PARTY_TRANSFER) {
return
}
await initDicData()
teacherNo.value = props.nowRow.teacherNo
handleFormType(val)
}
/**
* 处理表单类型显示
* @param val 表单类型
*/
const handleFormType = (val: number) => {
// 重置所有表单状态
waitShenheForm.isPersonnelTransfer = false
waitShenheForm.isPartyTransfer = false
if (val === FORM_TYPE.PERSONNEL_TRANSFER) {
// 人员调动:加载部门数据
waitShenheForm.title = "人员调动"
waitShenheForm.isPersonnelTransfer = true
waitShenheForm.form = { ...props.nowRow }
waitShenheForm.form.id = ''
waitShenheForm.form.newDeptCodeList = []
waitShenheForm.form.deptCodeList = []
materialUrlFrom.fileListA = []
materialUrlFrom.fileListB = []
materialUrlFrom.fileListC = []
materialUrlFrom.url = '/professional/file/teacherAboutInfoUpload'
newSecDeptCode.value = ''
newSecChildDeptCode.value = ''
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
getDeptListByLevelTwo().then((res: any) => {
secDeptList.value = res.data || []
dialogVisible.value = true
// 弹窗打开后,清除表单验证状态,避免立即显示验证提示
nextTick(() => {
stationChangeFormRef.value?.clearValidate()
})
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
})
} else if (val === FORM_TYPE.PARTY_TRANSFER) {
// 党员调动:设置原支部
waitShenheForm.title = "党员调动"
waitShenheForm.isPartyTransfer = true
waitShenheForm.form = { ...props.nowRow }
waitShenheForm.form.id = ''
waitShenheForm.form.realName = waitShenheForm.form.userName || waitShenheForm.form.realName
waitShenheForm.form.oldBranchName = waitShenheForm.form.oldBranchId
dialogVisible.value = true
// 弹窗打开后,清除表单验证状态,避免立即显示验证提示
nextTick(() => {
partChangeFormRef.value?.clearValidate()
})
}
}
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
}
/**
* 提交表单
* @param val 表单类型5-人员调动6-党员调动
*/
const dialogSubmit = async (val: number) => {
waitShenheForm.form.type = val
if (val !== FORM_TYPE.PERSONNEL_TRANSFER && val !== FORM_TYPE.PARTY_TRANSFER) {
return
}
waitShenheForm.form.teacherNo = teacherNo.value
// 表单验证
let formRef: any = null
// 表单验证配置
const formRefMap: Record<number, any> = {
[FORM_TYPE.PERSONNEL_TRANSFER]: stationChangeFormRef.value,
[FORM_TYPE.PARTY_TRANSFER]: partChangeFormRef.value
}
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
}
const formRef = formRefMap[val]
// 人员调动:在验证前同步数据
if (val === FORM_TYPE.PERSONNEL_TRANSFER) {
waitShenheForm.form.newDeptCode = newSecDeptCode.value
waitShenheForm.form.newSecDeptCode = newSecChildDeptCode.value ? newSecChildDeptCode.value : newSecDeptCode.value
} else if (val == 6) {
formRef = partChangeFormRef.value
waitShenheForm.form.newSecDeptCode = newSecChildDeptCode.value || newSecDeptCode.value
}
// 验证表单
@@ -808,27 +327,52 @@
}
}
// 先确认,用户取消时不执行后续操作
try {
await messageBox.confirm('确认提交?')
const res = await updateOtherInfo(waitShenheForm.form)
if (res.data.data == '-1') {
message.success("当前不允许提交")
} else {
} catch (err: any) {
// 用户取消操作,直接返回,不显示错误提示
return
}
// 用户确认后,执行提交操作
try {
let res: any
if (val === FORM_TYPE.PERSONNEL_TRANSFER) {
// 人员调动:使用人员调动接口
res = await addStationChangeObj(waitShenheForm.form)
} else if (val === FORM_TYPE.PARTY_TRANSFER) {
// 党员调动:使用党员调动接口
res = await addPartyChangeObj(waitShenheForm.form)
}
if (res && res.data === '-1') {
message.warning("当前不允许提交")
} else if (res) {
message.success("提交成功")
}
emit("getList", props.page)
educationDialogFromVisible.value = false
} catch (err) {
// 用户取消
emit("getList", props.page)
dialogVisible.value = false
}
} catch (err: any) {
// 处理业务错误
message.error(err.msg)
}
}
const getDeptListByParent = () => {
/**
* 获取子部门列表
*/
const getDeptListByParent = async () => {
newSecChildDeptCode.value = ''
newSecChildDeptCodeList.value = []
getDeptListByParentApi(newSecDeptCode.value).then((res: any) => {
newSecChildDeptCodeList.value = res.data.data
})
if (!newSecDeptCode.value) {
return
}
const res = await getDeptListByParentApi(newSecDeptCode.value)
newSecChildDeptCodeList.value = res.data || []
}
// 暴露方法
@@ -837,6 +381,3 @@
})
</script>
<style scoped>
</style>

View File

@@ -16,8 +16,8 @@
v-model="form.joinTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
@@ -26,8 +26,8 @@
v-model="form.correctionTime"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>

View File

@@ -1,7 +1,7 @@
<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-form :model="form" :rules="rules" label-width="130px" ref="formRef">
<el-form-item label="称谓" prop="title" required>
<el-select v-model="form.title" placeholder="请选择称谓" style="width: 100%">
<el-option
v-for="item in titleOptions"
@@ -11,20 +11,20 @@
/>
</el-select>
</el-form-item>
<el-form-item label="姓名" required>
<el-form-item label="姓名" prop="realName" required>
<el-input v-model="form.realName" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="出生年月" required>
<el-form-item label="出生年月" prop="birthday" required>
<el-date-picker
v-model="form.birthday"
type="date"
placeholder="选择日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd HH:mm:ss"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="政治面貌">
<el-form-item label="政治面貌" prop="politicsStatusId" required>
<el-select v-model="form.politicsStatusId" placeholder="请选择政治面貌" style="width: 100%">
<el-option
v-for="item in politicsStatusList"
@@ -34,7 +34,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="工作单位及职务">
<el-form-item label="工作单位及职务" prop="workStation" required>
<el-input
v-model="form.workStation"
type="textarea"
@@ -104,7 +104,30 @@ const handleClose = () => {
}
const handleSubmit = () => {
emit('submit', { ...form.value })
formRef.value?.validate((valid: boolean) => {
if (valid) {
emit('submit', { ...form.value })
}
})
}
// 表单验证规则
const rules = {
title: [
{ required: true, message: '请选择称谓', trigger: 'change' }
],
realName: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
birthday: [
{ required: true, message: '请选择出生年月', trigger: 'change' }
],
politicsStatusId: [
{ required: true, message: '请选择政治面貌', trigger: 'change' }
],
workStation: [
{ required: true, message: '请输入工作单位及职务', trigger: 'blur' }
]
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<el-dialog v-model="visible" title="薪资导出" width="500px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="120px">
<el-dialog v-model="visible" title="薪资导出" width="650px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
@@ -9,7 +9,6 @@
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="劳务日期锁定">
@@ -18,7 +17,7 @@
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-form-item label-width="0px">
<el-tag type="warning">选择是则本月其他造单批次无法指定到当前月份如需解锁请前往薪资导出记录删除记录即可</el-tag>
</el-form-item>
</el-form>

View File

@@ -1,36 +1,6 @@
<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"
@@ -45,7 +15,6 @@
v-model="search.teacherNo"
placeholder="请输入工号"
clearable
style="width: 200px"
/>
</el-form-item>
@@ -54,7 +23,6 @@
v-model="search.realName"
placeholder="请输入姓名"
clearable
style="width: 200px"
/>
</el-form-item>
@@ -63,7 +31,6 @@
v-model="search.idCard"
placeholder="请输入身份证号"
clearable
style="width: 200px"
/>
</el-form-item>
@@ -75,7 +42,6 @@
value-format="YYYY"
placeholder="请选择年份"
clearable
style="width: 200px"
/>
</el-form-item>
@@ -87,17 +53,15 @@
value-format="M"
placeholder="请选择月份"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="岗位别" prop="stationTypeId">
<el-form-item label="岗位别" prop="stationTypeId">
<el-select
v-model="search.stationTypeId"
filterable
clearable
placeholder="请选择岗位别"
style="width: 200px"
placeholder="请选择岗位别"
>
<el-option
v-for="item in stationLevelList"
@@ -109,8 +73,49 @@
</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">
<el-button
type="primary"
plain
icon="UploadFilled"
v-if="permissions.professional_salary_import"
@click="handleImportBaseSalary">工资条导入
</el-button>
<el-button
icon="View"
class="ml10"
v-if="permissions.professional_seach_auth"
@click="canSearch(1)">设置可查询
</el-button>
<el-button
icon="Hide"
class="ml10"
v-if="permissions.professional_seach_auth"
@click="canSearch(0)">设置不可查询
</el-button>
<el-button
type="danger"
plain
icon="Delete"
class="ml10"
v-if="permissions.professional_professionalsalaries_del"
@click="delbatch">批量删除
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
@@ -126,11 +131,12 @@
<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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="postSalary" label="岗位工资" min-width="120" align="center" show-overflow-tooltip />
@@ -147,7 +153,7 @@
<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' ? '' : '' }}
{{ scope.row.normalView === '1' ? '可查询' : '不可查询' }}
</el-tag>
</template>
</el-table-column>
@@ -181,17 +187,20 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { defineAsyncComponent } 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 { fetchList, delBatch, setCanSearch } from '/@/api/professional/salaries/teacherpayslip'
import { checkAuth } from '/@/api/professional/salaries/teachersalary'
import { getStationLevelList } from '/@/api/professional/professionalstationlevelconfig'
import SalaryInfo from './salaryInfo.vue'
import ImportBaseSalary from './importBaseSalary.vue'
import ExportBaseSalary from './exportBaseSalary.vue'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
// 使 Pinia store
const userInfoStore = useUserInfo()
const { userInfos } = storeToRefs(userInfoStore)
@@ -232,7 +241,9 @@ const search = reactive({
idCard: '',
nf: '',
yf: '',
stationTypeId: ''
stationTypeId: '',
canSearch: 0,
selectList:[]
})
// useTable
@@ -280,6 +291,20 @@ const handleFilter = () => {
getDataList() //
}
//
const resetQuery = () => {
searchFormRef.value?.formRef?.resetFields()
Object.assign(search, {
teacherNo: '',
realName: '',
idCard: '',
nf: '',
yf: '',
stationTypeId: ''
})
handleFilter()
}
//
const selectionChange = (selection: any[]) => {
selectList.value = selection
@@ -298,7 +323,7 @@ const handleImportBaseSalary = () => {
//
const delbatch = () => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
message.warning("请至少选择一名人员")
return
}
@@ -310,7 +335,7 @@ const delbatch = () => {
if (response.data.code == -1) {
message.error(response.data.data)
} else {
message.info("删除成功")
message.success("删除成功")
getDataList(false) //
}
} catch (error: any) {
@@ -323,19 +348,14 @@ const delbatch = () => {
// /
const canSearch = (val: number) => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
return
}
const params = {
canSearch: val,
selectList: selectList.value
}
search.canSearch=val
search.selectList=selectList.value
messageBox.confirm('确认设置?').then(async () => {
try {
await setCanSearch(params)
await setCanSearch(search)
message.success("设置成功")
getDataList(false) //
} catch (error: any) {

View File

@@ -0,0 +1,571 @@
<template>
<el-dialog
v-model="visible"
width="95%"
v-loading="baseLoading"
top="2vh"
:close-on-click-modal="false"
destroy-on-close
class="salary-info-dialog"
>
<div class="salary-info-container">
<!--基本信息-->
<div class="base-info-section">
<div class="base-info-content">
<div class="info-group">
<div class="info-item">
<span class="info-label">姓名</span>
<el-tag v-if="salaryData.baseInfo[0]?.realName">{{ salaryData.baseInfo[0].realName }}</el-tag>
<span v-else class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">身份证号</span>
<el-tag v-if="salaryData.baseInfo[0]?.idCard">{{ salaryData.baseInfo[0].idCard }}</el-tag>
<span v-else class="info-value">-</span>
</div>
</div>
<el-form :model="nowUser" :inline="true" class="search-form">
<el-form-item label="年份">
<el-date-picker
v-model="nowUser.nf"
type="year"
format="YYYY"
value-format="YYYY"
placeholder="选择年"
style="width: 150px">
</el-date-picker>
</el-form-item>
<el-form-item label="月份">
<el-date-picker
v-model="nowUser.yff"
type="month"
format="M"
value-format="M"
placeholder="选择月"
style="width: 150px">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button @click="searchUserInfo" type="primary" icon="Search">
查询
</el-button>
</el-form-item>
</el-form>
</div>
</div>
<el-card shadow="hover" class="info-card">
<template #header>
<div class="card-header">
<el-icon><Money /></el-icon>
<span>工资明细</span>
</div>
</template>
<!--应发部分-->
<div class="section-title">
<el-icon><TrendCharts /></el-icon>
<span>应发部分</span>
</div>
<el-table
:data="salaryData.baseInfo"
size="small"
border
class="salary-table"
style="width: 100%"
>
<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>
<div class="formula-tag">
<el-tag type="danger" size="small">基础工资应税收入= 岗位工资 +薪级工资+见习期工资+生活补贴+岗位津贴+教龄津贴+特教补贴+特级教师津贴+特岗津贴1+特岗津贴2+奖励绩效性工资+乡镇工作补贴+临时补贴+保留津贴+应休未休假-个人补缴-其他扣款-医疗救助金</el-tag>
</div>
<!--应扣部分-->
<div class="section-title">
<el-icon><Remove /></el-icon>
<span>应扣部分</span>
</div>
<el-table
size="small"
border
:data="salaryData.baseInfo"
class="salary-table"
style="width: 100%">
<el-table-column label="应扣部分">
<el-table-column label="住房公积金" min-width="90" prop="houseFund"></el-table-column>
<el-table-column label="医疗保险金" min-width="90" prop="medicalInsurance"></el-table-column>
<el-table-column label="失业保险金" min-width="90" prop="unemployInsurance"></el-table-column>
<el-table-column label="养老保险金" min-width="90" prop="endowInsurance"></el-table-column>
<el-table-column label="工会费" min-width="60" prop="unionFee"></el-table-column>
<el-table-column label="儿童统筹" min-width="90" prop="childrenWhole"></el-table-column>
<el-table-column label="个人所得税" width="90" prop="personalTax"></el-table-column>
<el-table-column label="其他扣款" min-width="90" prop="otherDeduction"></el-table-column>
<el-table-column label="病事假扣款" min-width="90" prop="sickDeduction"></el-table-column>
<el-table-column label="医疗救助基金" min-width="90" prop="medicalFund"></el-table-column>
<el-table-column label="工伤保险" min-width="90" prop="inductrialInjury"></el-table-column>
<el-table-column label="个人补缴" min-width="90" prop="personalPay"></el-table-column>
<el-table-column label="应扣合计" min-width="90" prop="withhold"></el-table-column>
</el-table-column>
</el-table>
<div class="formula-tag">
<el-tag size="small">个人所得税 = 个税计算数据中的 累计应补(退)税额 </el-tag>
</div>
<!--劳务费-->
<div v-if="showAllContent" class="section-title">
<el-icon><Document /></el-icon>
<span>造单收入清单</span>
</div>
<el-table
v-if="showAllContent"
size="small"
border
:data="allProjectData"
class="salary-table"
style="width: 100%">
<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>
<!--专项扣除-->
<div v-if="showAllContent" class="section-title">
<el-icon><DataAnalysis /></el-icon>
<span>个税计算数据</span>
</div>
<el-table
v-if="showAllContent"
size="small"
border
:data="salaryExtendData"
class="salary-table"
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>
<div v-if="showAllContent" class="formula-tag">
<el-tag type="warning" size="small">应税收入= 基础工资应税收入+造单收入</el-tag>
<el-tag type="success" size="small" class="ml10">累计专项扣除=当年累计个人承担的住房公积金+医疗保险金+失业保险金+养老保险金</el-tag>
</div>
<!--实发合计-->
<div class="section-title">
<el-icon><Wallet /></el-icon>
<span>实发合计</span>
</div>
<el-table
size="small"
border
:data="[staticsData]"
class="salary-table summary-table"
empty-text=" "
style="width: 100%">
<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>
<div class="formula-tag">
<el-tag type="primary" size="small">实发工资= 应发工资-应扣合计</el-tag>
</div>
</el-card>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { Money, TrendCharts, Remove, Document, DataAnalysis, Wallet, Search } from '@element-plus/icons-vue'
import { useMessage } from '/@/hooks/message'
import { queryUserInfo, queryExtendSalaryInfo } from '/@/api/professional/salaries/teacherpayslip'
import { checkAuth } from '/@/api/professional/salaries/teachersalary'
const message = useMessage()
// 对话框显示状态
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
} 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.salaryTax)
allProjectData.value = res.data.allProject
staticsData.orderMoney = res.data.totalMoney
} catch (error) {
// 查询失败
}
}
// 搜索用户信息
const searchUserInfo = async () => {
if (!nowUser.value.idCard) {
message.warning('请输入身份证号')
return
}
if (!nowUser.value.nf) {
message.warning('请选择年份')
return
}
if (!nowUser.value.yff) {
message.warning('请选择月份')
return
}
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) {
message.info('未查询到该时间段的数据')
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
resData.nf = nowUser.value.nf
resData.yf = nowUser.value.yff
resData.teacherNo = nowUser.value.teacherNo
makeUserInfo(resData)
await queryExtendSalaryInfoMethod(resData)
message.success('查询成功')
}
} catch (error: any) {
message.error(error?.msg || '查询失败')
} 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 lang="scss" scoped>
.salary-info-dialog {
:deep(.el-dialog__body) {
padding: 10px !important;
max-height: calc(100vh - 120px);
overflow-y: auto;
}
}
.salary-info-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.section-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #ebeef5;
font-size: 16px;
font-weight: 600;
color: #303133;
.el-icon {
font-size: 18px;
color: var(--el-color-primary);
}
}
.section-header-inline {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 600;
color: #303133;
margin-right: 16px;
.el-icon {
font-size: 16px;
color: var(--el-color-primary);
}
}
.info-card {
:deep(.el-card__header) {
padding: 16px 20px;
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
}
:deep(.el-card__body) {
padding: 20px;
}
}
.base-info-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 16px;
}
.info-group {
display: flex;
flex-direction: row;
gap: 16px;
flex-wrap: wrap;
}
.info-item {
display: flex;
// align-items: center;
gap: 6px;
.info-label {
font-weight: 500;
color: #606266;
font-size: 13px;
}
.info-value {
color: #303133;
font-size: 13px;
font-weight: 500;
}
}
.search-form {
margin-left: auto;
margin-bottom: 0;
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
margin: 24px 0 12px 0;
padding-bottom: 8px;
border-bottom: 2px solid #e4e7ed;
font-size: 15px;
font-weight: 600;
color: #303133;
.el-icon {
font-size: 16px;
color: var(--el-color-primary);
}
&:first-child {
margin-top: 0;
}
}
.salary-table {
margin-bottom: 16px;
:deep(.el-table__header) {
th {
background: #f5f7fa;
color: #606266;
font-weight: 600;
}
}
:deep(.el-table__body) {
td {
padding: 8px 0;
}
}
:deep(.el-table__cell) {
padding: 8px 4px;
font-size: 13px;
}
}
.summary-table {
:deep(.el-table__body) {
td {
font-weight: 600;
font-size: 14px;
color: var(--el-color-primary);
}
}
}
.formula-tag {
margin-top: 12px;
margin-bottom: 8px;
line-height: 1.6;
.el-tag {
margin-right: 8px;
padding: 6px 12px;
font-size: 12px;
white-space: normal;
word-break: break-all;
}
.ml10 {
margin-left: 10px;
}
}
// 响应式优化
@media (max-width: 1400px) {
.salary-info-dialog {
:deep(.el-dialog) {
width: 98% !important;
}
}
}
// 滚动条优化
.salary-info-dialog {
:deep(.el-dialog__body) {
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
&:hover {
background: #a8a8a8;
}
}
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<el-dialog v-model="visible" title="薪资导出" width="500px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="120px">
<el-dialog v-model="visible" title="薪资导出" width="650px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px">
<el-form-item label="日期">
<el-date-picker
v-model="chooseDate"
@@ -8,8 +8,7 @@
format="YYYY-M"
value-format="YYYY-M"
placeholder="请选择日期"
@change="handleChange"
style="width: 100%"
@change="handleChange"
/>
</el-form-item>
<el-form-item label="劳务日期锁定">
@@ -18,7 +17,7 @@
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-form-item label-width="0px">
<el-tag type="warning">选择是则本月其他造单批次无法指定到当前月份如需解锁请前往薪资导出记录删除记录即可</el-tag>
</el-form-item>
</el-form>

View File

@@ -1,48 +1,6 @@
<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"
@@ -103,12 +61,12 @@
/>
</el-form-item>
<el-form-item label="岗位别" prop="stationTypeId">
<el-form-item label="岗位别" prop="stationTypeId">
<el-select
v-model="search.stationTypeId"
filterable
clearable
placeholder="请选择岗位别"
placeholder="请选择岗位别"
style="width: 200px"
>
<el-option
@@ -121,8 +79,65 @@
</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">
<el-button
type="primary"
plain
icon="UploadFilled"
v-if="permissions.professional_salary_import"
@click="handleImportBaseSalary">人事薪资导入
</el-button>
<el-button
type="warning"
plain
icon="Download"
class="ml10"
v-if="permissions.professional_salary_finance_import"
@click="handleExportSalart">薪资导出
</el-button>
<el-button
type="primary"
plain
icon="UploadFilled"
class="ml10"
v-if="permissions.professional_salary_finance_import"
@click="handleImportTaxSalary">税金导入
</el-button>
<el-button
icon="View"
class="ml10"
v-if="permissions.professional_seach_auth"
@click="canSearch(1)">设置可查询
</el-button>
<el-button
icon="Hide"
class="ml10"
v-if="permissions.professional_seach_auth"
@click="canSearch(0)">设置不可查询
</el-button>
<el-button
type="danger"
plain
icon="Delete"
class="ml10"
v-if="permissions.professional_professionalsalaries_del"
@click="delbatch">批量删除
</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
@@ -138,11 +153,11 @@
<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 label="姓名/工号" min-width="150" align="center">
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="postSalary" label="岗位工资" min-width="120" align="center" show-overflow-tooltip />
@@ -159,12 +174,12 @@
<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' ? '' : '' }}
{{ scope.row.normalView === '1' ? '可查询' : '不可查询' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="150" align="center" fixed="right">
<el-table-column label="操作" min-width="80" align="center" fixed="right">
<template #default="scope">
<el-button
icon="document"
@@ -194,14 +209,17 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { defineAsyncComponent } 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 { fetchList, delBatch, setCanSearch, checkAuth } from '/@/api/professional/salaries/teachersalary'
import { getStationLevelList } from '/@/api/professional/professionalstationlevelconfig'
import SalaryInfo from './salaryInfo.vue'
import ImportBaseSalary from './importBaseSalary.vue'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
import ExportBaseSalary from './exportBaseSalary.vue'
import ImportTaxSalary from './importTaxSalary.vue'
@@ -246,7 +264,9 @@ const search = reactive({
idCard: '',
nf: '',
yf: '',
stationTypeId: ''
stationTypeId: '',
canSearch: 0,
selectList:[]
})
// useTable
@@ -294,6 +314,20 @@ const handleFilter = () => {
getDataList() //
}
//
const resetQuery = () => {
searchFormRef.value?.formRef?.resetFields()
Object.assign(search, {
teacherNo: '',
realName: '',
idCard: '',
nf: '',
yf: '',
stationTypeId: ''
})
handleFilter()
}
//
const selectionChange = (selection: any[]) => {
selectList.value = selection
@@ -322,7 +356,7 @@ const handleExportSalart = () => {
//
const delbatch = () => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
message.warning("请至少选择一名人员")
return
}
@@ -334,7 +368,7 @@ const delbatch = () => {
if (response.data.code == -1) {
message.error(response.data.data)
} else {
message.info("删除成功")
message.success("删除成功")
getDataList(false) //
}
} catch (error: any) {
@@ -347,19 +381,18 @@ const delbatch = () => {
// /
const canSearch = (val: number) => {
if (selectList.value.length === 0) {
message.info("请至少选择一名人员")
return
}
const params = {
canSearch: val,
selectList: selectList.value
}
// if (selectList.value.length === 0) {
// message.warning("")
// return
// }
search.canSearch=val
search.selectList=selectList.value
messageBox.confirm('确认设置?').then(async () => {
try {
await setCanSearch(params)
await setCanSearch(search)
message.success("设置成功")
getDataList(false) //
} catch (error: any) {

View File

@@ -0,0 +1,556 @@
<template>
<el-dialog
v-model="visible"
width="95%"
v-loading="baseLoading"
top="2vh"
:close-on-click-modal="false"
destroy-on-close
class="salary-info-dialog"
>
<div class="salary-info-container">
<!--基本信息-->
<div class="base-info-section">
<div class="base-info-content">
<div class="info-group">
<div class="info-item">
<span class="info-label">姓名</span>
<el-tag v-if="salaryData.baseInfo[0]?.realName">{{ salaryData.baseInfo[0].realName }}</el-tag>
<span v-else class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">身份证号</span>
<el-tag v-if="salaryData.baseInfo[0]?.idCard">{{ salaryData.baseInfo[0].idCard }}</el-tag>
<span v-else class="info-value">-</span>
</div>
</div>
<el-form :model="nowUser" :inline="true" class="search-form">
<el-form-item label="年份">
<el-date-picker
v-model="nowUser.nf"
type="year"
format="YYYY"
value-format="YYYY"
placeholder="选择年"
style="width: 150px">
</el-date-picker>
</el-form-item>
<el-form-item label="月份">
<el-date-picker
v-model="nowUser.yff"
type="month"
format="M"
value-format="M"
placeholder="选择月"
style="width: 150px">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button @click="searchUserInfo" type="primary" icon="Search">
查询
</el-button>
</el-form-item>
</el-form>
</div>
</div>
<el-card shadow="hover" class="info-card">
<template #header>
<div class="card-header">
<el-icon><Money /></el-icon>
<span>工资明细</span>
</div>
</template>
<!--应发部分-->
<div v-if="showAllContent" class="section-title">
<el-icon><TrendCharts /></el-icon>
<span>应发部分</span>
</div>
<el-table
v-if="showAllContent"
:data="salaryData.baseInfo"
size="small"
border
class="salary-table"
style="width: 100%"
>
<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>
<div v-if="showAllContent" class="formula-tag">
<el-tag type="danger" size="small">基础工资应税收入= 岗位工资 +薪级工资+见习期工资+生活补贴+岗位津贴+教龄津贴+特教补贴+特级教师津贴+特岗津贴1+特岗津贴2+奖励绩效性工资+乡镇工作补贴+临时补贴+保留津贴+应休未休假-个人补缴-其他扣款-医疗救助金</el-tag>
</div>
<!--应扣部分-->
<div v-if="showAllContent" class="section-title">
<el-icon><Remove /></el-icon>
<span>应扣部分</span>
</div>
<el-table
v-if="showAllContent"
size="small"
border
:data="salaryData.baseInfo"
class="salary-table"
style="width: 100%">
<el-table-column label="应扣部分">
<el-table-column label="住房公积金" min-width="90" prop="houseFund"></el-table-column>
<el-table-column label="医疗保险金" min-width="90" prop="medicalInsurance"></el-table-column>
<el-table-column label="失业保险金" min-width="90" prop="unemployInsurance"></el-table-column>
<el-table-column label="养老保险金" min-width="90" prop="endowInsurance"></el-table-column>
<el-table-column label="工会费" min-width="60" prop="unionFee"></el-table-column>
<el-table-column label="儿童统筹" min-width="90" prop="childrenWhole"></el-table-column>
<el-table-column label="个人所得税" width="90" prop="personalTax"></el-table-column>
<el-table-column label="其他扣款" min-width="90" prop="otherDeduction"></el-table-column>
<el-table-column label="病事假扣款" min-width="90" prop="sickDeduction"></el-table-column>
<el-table-column label="医疗救助基金" min-width="90" prop="medicalFund"></el-table-column>
<el-table-column label="工伤保险" min-width="90" prop="inductrialInjury"></el-table-column>
<el-table-column label="个人补缴" min-width="90" prop="personalPay"></el-table-column>
<el-table-column label="应扣合计" min-width="90" prop="withhold"></el-table-column>
</el-table-column>
</el-table>
<div v-if="showAllContent" class="formula-tag">
<el-tag size="small">个人所得税 = 个税计算数据中的 累计应补(退)税额 </el-tag>
</div>
<!--劳务费-->
<div class="section-title">
<el-icon><Document /></el-icon>
<span>造单收入清单</span>
</div>
<el-table
size="small"
border
:data="allProjectData"
class="salary-table"
style="width: 100%">
<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>
<!--专项扣除-->
<div class="section-title">
<el-icon><DataAnalysis /></el-icon>
<span>个税计算数据</span>
</div>
<el-table
size="small"
border
:data="salaryExtendData"
class="salary-table"
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>
<div class="formula-tag">
<el-tag type="warning" size="small">应税收入= 基础工资应税收入+造单收入</el-tag>
<el-tag type="success" size="small" class="ml10">累计专项扣除=当年累计个人承担的住房公积金+医疗保险金+失业保险金+养老保险金</el-tag>
</div>
<!--实发合计-->
<div v-if="showAllContent" class="section-title">
<el-icon><Wallet /></el-icon>
<span>实发合计</span>
</div>
<el-table
v-if="showAllContent"
size="small"
border
:data="[staticsData]"
class="salary-table summary-table"
empty-text=" "
style="width: 100%">
<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>
<div v-if="showAllContent" class="formula-tag">
<el-tag type="primary" size="small">实发工资= 应发工资-应扣合计</el-tag>
</div>
</el-card>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { Money, TrendCharts, Remove, Document, DataAnalysis, Wallet } from '@element-plus/icons-vue'
import { useMessage } from '/@/hooks/message'
import { queryUserInfo, queryExtendSalaryInfo, checkAuth } from '/@/api/professional/salaries/teachersalary'
const message = useMessage()
// 对话框显示状态
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
} 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.salaryTax)
allProjectData.value = res.data.allProject
staticsData.orderMoney = res.data.totalMoney
} catch (error) {
// 查询失败
}
}
// 搜索用户信息
const searchUserInfo = async () => {
if (!nowUser.value.idCard) {
message.warning('请输入身份证号')
return
}
if (!nowUser.value.nf) {
message.warning('请选择年份')
return
}
if (!nowUser.value.yff) {
message.warning('请选择月份')
return
}
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) {
message.info('未查询到该时间段的数据')
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
resData.nf = nowUser.value.nf
resData.yf = nowUser.value.yff
resData.teacherNo = nowUser.value.teacherNo
makeUserInfo(resData)
await queryExtendSalaryInfoMethod(resData)
message.success('查询成功')
}
} catch (error: any) {
message.error(error?.msg || '查询失败')
} 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 lang="scss" scoped>
.salary-info-dialog {
:deep(.el-dialog__body) {
padding: 10px !important;
max-height: calc(100vh - 120px);
overflow-y: auto;
}
}
.salary-info-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.base-info-section {
padding: 12px 16px;
background: #fff;
border-radius: 4px;
border: 1px solid #ebeef5;
}
.base-info-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 16px;
}
.info-group {
display: flex;
flex-direction: row;
gap: 16px;
flex-wrap: wrap;
}
.info-item {
display: flex;
gap: 6px;
.info-label {
font-weight: 500;
color: #606266;
font-size: 13px;
}
.info-value {
color: #303133;
font-size: 13px;
font-weight: 500;
}
}
.search-form {
margin-left: auto;
margin-bottom: 0;
}
.info-card {
:deep(.el-card__header) {
padding: 16px 20px;
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
}
:deep(.el-card__body) {
padding: 20px;
}
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #303133;
.el-icon {
font-size: 18px;
color: var(--el-color-primary);
}
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
margin: 24px 0 12px 0;
padding-bottom: 8px;
border-bottom: 2px solid #e4e7ed;
font-size: 15px;
font-weight: 600;
color: #303133;
.el-icon {
font-size: 16px;
color: var(--el-color-primary);
}
&:first-child {
margin-top: 0;
}
}
.salary-table {
margin-bottom: 16px;
:deep(.el-table__header) {
th {
background: #f5f7fa;
color: #606266;
font-weight: 600;
}
}
:deep(.el-table__body) {
td {
padding: 8px 0;
}
}
:deep(.el-table__cell) {
padding: 8px 4px;
font-size: 13px;
}
}
.summary-table {
:deep(.el-table__body) {
td {
font-weight: 600;
font-size: 14px;
color: var(--el-color-primary);
}
}
}
.formula-tag {
margin-top: 12px;
margin-bottom: 8px;
line-height: 1.6;
.el-tag {
margin-right: 8px;
padding: 6px 12px;
font-size: 12px;
white-space: normal;
word-break: break-all;
}
.ml10 {
margin-left: 10px;
}
}
// 响应式优化
@media (max-width: 1400px) {
.salary-info-dialog {
:deep(.el-dialog) {
width: 98% !important;
}
}
}
// 滚动条优化
.salary-info-dialog {
:deep(.el-dialog__body) {
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
&:hover {
background: #a8a8a8;
}
}
}
}
</style>

View File

@@ -233,8 +233,9 @@ const handleSubmit = async () => {
}
dialogVisible.value = false
getDataList()
} catch (error) {
// 提交失败
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}

View File

@@ -20,7 +20,7 @@
<el-table-column prop="resumeRemark" label="履历" show-overflow-tooltip />
<el-table-column prop="remarks" label="备注" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<template #default="scope">
<el-button
icon="Edit"
text