@@ -1405,7 +1396,7 @@
import { useMessage, useMessageBox } from '/@/hooks/message'
import axios from 'axios'
// 导入图标
- import { User, Phone, Document, CreditCard, Briefcase, Flag, Plus, Edit, Delete, Connection, School, Medal, Files, Clock, Setting } from '@element-plus/icons-vue'
+ import { User, Phone, Document, CreditCard, Briefcase, Flag, Plus, Edit, Delete, Connection, School, Medal, Files, Clock, Setting, Operation, Switch, CircleCheck, CircleClose, Refresh, Download } from '@element-plus/icons-vue'
// 导入api(统一使用 /@/ 路径别名)
import {
@@ -1420,6 +1411,7 @@
updateInout,
exportTeacherInfo as exportTeacherInfoApi
} from '/@/api/professional/professionaluser/teacherbase'
+ import {getDictsByTypes} from '/@/api/admin/dict'
import {getNationalList} from '/@/api/basic/basicnation'
import {addPoliticssStatus, dePoObj} from '/@/api/professional/professionaluser/professionalpoliticsstatus'
import {addAcadeRelation, delEduObj} from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
@@ -1444,7 +1436,11 @@
// 导入工具
import { Session } from '/@/utils/storage'
+ // 导入 composables(已移除,改为通过 TableColumnControl 组件暴露)
// 导入组件(使用 defineAsyncComponent 异步加载,优化性能)
+ const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
+ const GenderTag = defineAsyncComponent(() => import('/@/components/GenderTag/index.vue'))
+ const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
const ShowEvidence = defineAsyncComponent(() => import("../common/showEvidence.vue"));
const ShowHonorEdvince = defineAsyncComponent(() => import("../common/showHonorEdvince.vue"));
const ExportTeacherInfoDialog = defineAsyncComponent(() => import('./export-teacher-info.vue'));
@@ -1453,6 +1449,10 @@
const PoliticsDialog = defineAsyncComponent(() => import('./politics-dialog.vue'));
const RelationDialog = defineAsyncComponent(() => import('./relation-dialog.vue'));
const StatusLockDialog = defineAsyncComponent(() => import('./status-lock-dialog.vue'));
+ const ActionDropdown = defineAsyncComponent(() => import('./action-dropdown.vue'));
+ const TableColumnControl = defineAsyncComponent(() => import('/@/components/TableColumnControl/index.vue'));
+ const TableColumn = defineAsyncComponent(() => import('/@/components/TableColumn/index.vue'));
+ const TableColumnProvider = defineAsyncComponent(() => import('/@/components/TableColumn/Provider.vue'));
// Pagination 组件已在模板中使用,无需导入(全局组件)
// 导入工具
import {validateNull} from "/@/utils/validate";
@@ -1570,6 +1570,7 @@
const qualificationList = ref([])
const degreeList = ref([])
const activeName = ref('')
+ const subActiveName = ref('subBaseInfo') // 子标签页当前激活的标签
const dialogImageUrl = ref('')
const nationalList = ref([])
const fileList = ref([])
@@ -1588,6 +1589,33 @@
const zgzData = ref([])
const nowImage = ref("")
const dataPolitics = ref([])
+
+ // 表格引用
+ const tableRef = ref()
+ // TableColumnControl 组件引用
+ const tableColumnControlRef = ref()
+
+ // 调试:监听 tableRef.value 的变化
+ watch(() => tableRef.value, (newVal, oldVal) => {
+ console.log('[index.vue] tableRef.value 变化:', { newVal, oldVal, hasValue: !!newVal })
+ if (newVal) {
+ console.log('[index.vue] tableRef.value 已赋值,表格实例:', newVal)
+ const tableEl = (newVal as any).$el
+ console.log('[index.vue] tableRef.value.$el:', tableEl)
+ }
+ }, { immediate: true })
+
+ // 从 TableColumnControl 获取 isColumnVisible 和 visibleTableColumns
+ const isColumnVisible = (propOrLabel: string) => {
+ if (tableColumnControlRef.value?.isColumnVisible) {
+ return tableColumnControlRef.value.isColumnVisible(propOrLabel)
+ }
+ // 默认显示所有列(在 TableColumnControl 初始化之前)
+ return true
+ }
+
+ const visibleTableColumns = ref([])
+
// 静态配置数据
const defaultProps = {
label: "deptName",
@@ -1646,14 +1674,20 @@
],
telPhone: [
{required: true, message: '请输入正确的电话号码', trigger: 'blur'},
- {min: 11, max: 11, message: '长度为 11', trigger: 'blur'}
+ {pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur'}
],
idCard: [
{required: true, message: '请输入正确身份证', trigger: 'blur'},
- {min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur'}
+ {min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur'},
+ {pattern: /^(\d{15}|\d{17}[\dXx])$/, message: '身份证号格式不正确(15位或18位,18位最后一位可以是X)', trigger: 'blur'}
],
telPhoneTwo: [
- {required: false, min: 11, max: 11, message: '长度为 11', trigger: 'blur'}
+ {required: false, pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur'}
+ ],
+ bankNo: [
+ {required: true, message: '请输入银行卡号', trigger: 'blur'},
+ {pattern: /^\d{16,30}$/, message: '银行卡号格式不正确(16-30位数字)', trigger: 'blur'},
+ {min: 16, max: 30, message: '银行卡号长度为 16 到 30 位', trigger: 'blur'}
],
bankOpen: [
{required: true, message: '请输入开户行', trigger: 'blur'}
@@ -1793,6 +1827,46 @@
}
})
+ // 合并表单验证规则
+ const mergedRules = computed(() => {
+ // 将 baseInfo 的规则加上 baseInfo 前缀
+ const baseInfoRules: Record = {}
+ Object.keys(rules).forEach(key => {
+ // 在编辑模式下,某些字段被禁用(如 realName, teacherNo),不需要验证
+ // 根据 isAdd 状态动态调整验证规则
+ if (isAdd.value) {
+ // 新增模式:使用原始规则
+ baseInfoRules[`baseInfo.${key}`] = rules[key]
+ } else {
+ // 编辑模式:对于被禁用的字段,移除必填验证
+ if (key === 'realName' || key === 'teacherNo') {
+ // 这些字段在编辑模式下被禁用,移除必填验证
+ const rule = rules[key]
+ if (Array.isArray(rule)) {
+ baseInfoRules[`baseInfo.${key}`] = rule.filter((r: any) => !r.required)
+ } else {
+ baseInfoRules[`baseInfo.${key}`] = rule
+ }
+ } else {
+ baseInfoRules[`baseInfo.${key}`] = rules[key]
+ }
+ }
+ })
+
+ // 岗位信息规则:始终包含所有岗位信息规则,以便在保存时能够验证
+ const stationRules: Record = {}
+ Object.keys(stationFormValidate).forEach(key => {
+ stationRules[`professionalStationRelation.${key}`] = stationFormValidate[key]
+ })
+
+ return {
+ // baseInfo 的规则(加上 baseInfo 前缀)
+ ...baseInfoRules,
+ // professionalStationRelation 的规则(始终包含)
+ ...stationRules
+ }
+ })
+
// 生命周期
onMounted(async () => {
// 先初始化基础数据
@@ -1801,6 +1875,17 @@
await loadSearchDictData()
// 初始化完成后加载表格数据
getDataList()
+
+ // 调试:检查 tableRef.value 是否被赋值
+ await nextTick()
+ setTimeout(() => {
+ console.log('[index.vue] onMounted: tableRef.value:', tableRef.value)
+ if (tableRef.value) {
+ console.log('[index.vue] onMounted: tableRef.value.$el:', (tableRef.value as any).$el)
+ } else {
+ console.warn('[index.vue] onMounted: tableRef.value 仍未赋值')
+ }
+ }, 500)
})
// 加载搜索条件字典数据
@@ -1836,16 +1921,19 @@
// 暴露方法给模板使用(如果需要)
// watch 监听器
- watch(activeName, (val) => {
+ watch(activeName, (val, oldVal) => {
//监听切换状态-计划单
- if (val != "first") {
- setTimeout(() => {
+ // 从其他标签页切换到非"first"标签页时检查基础信息
+ // 避免在初始化时触发检查(oldVal 为空字符串表示初始化)
+ // 同时需要确保对话框已打开(dialogFromVisible 为 true)
+ if (val != "first" && oldVal && oldVal !== '' && dialogFromVisible.value) {
+ setTimeout(() => {
if (!form.baseInfo.id || form.baseInfo.id == '') {
message.info("请先完善基础信息")
- saveSubmit();
- }
- }, 500)
+ activeName.value = "first"
}
+ }, 500)
+ }
})
watch(() => form.professionalStationRelation.employmentNature, (newVal) => {
@@ -1987,10 +2075,75 @@
multiDialogRef.value?.init(val)
})
}
+
+ // 获取操作菜单项配置
+ const getActionMenuItems = (row: any) => {
+ return [
+ {
+ command: 'export',
+ label: '导出',
+ icon: Download,
+ visible: permissions.value.professional_teacherbase_export
+ },
+ {
+ command: 'personnel-transfer',
+ label: '人员调动',
+ icon: Switch,
+ visible: permissions.value.professional_teacherbase_status_lock
+ },
+ {
+ command: 'party-transfer',
+ label: '党员调动',
+ icon: Switch,
+ visible: permissions.value.professional_teacherbase_status_lock
+ },
+ {
+ command: 'allow-inout',
+ label: '允许进出',
+ icon: CircleCheck,
+ visible: permissions.value.professional_teacherbase_inout && row.inoutFlag == '0'
+ },
+ {
+ command: 'forbid-inout',
+ label: '禁止进出',
+ icon: CircleClose,
+ visible: permissions.value.professional_teacherbase_inout && row.inoutFlag == '1'
+ },
+ {
+ command: 'reset-password',
+ label: '重置密码',
+ icon: Refresh,
+ visible: permissions.value.professional_teacherbase_resetpw
+ }
+ ]
+ }
+
+ // 处理更多操作下拉菜单命令
+ const handleMoreCommand = (command: string, row: any, index: number) => {
+ switch (command) {
+ case 'export':
+ handleDownLoadWord(row.teacherNo)
+ break
+ case 'personnel-transfer':
+ handleWaitExam(row, 5)
+ break
+ case 'party-transfer':
+ handleWaitExam(row, 6)
+ break
+ case 'allow-inout':
+ updateInoutFlag(row, 1)
+ break
+ case 'forbid-inout':
+ updateInoutFlag(row, 0)
+ break
+ case 'reset-password':
+ resetPassword(row)
+ break
+ }
+ }
// 分页处理方法已由 useTable 提供,无需手动实现
-
- const tableRef = ref()
+ // tableRef 已在上面定义(用于 useTableColumns)
const exportExcel = (form: any, url: string) => {
return axios({
@@ -2028,44 +2181,71 @@
const saveSubmit = () => {
canSave.value = false
if (activeName.value == "first") {
- baseForm.value?.validate((valid: boolean) => {
- if (valid) {
+ // 构建基本信息需要验证的字段列表(始终包含所有基本信息字段)
+ const baseInfoFields = Object.keys(rules).map(key => `baseInfo.${key}`)
+
+ // 构建岗位信息需要验证的字段列表(始终包含所有岗位信息字段)
+ const stationFields = Object.keys(stationFormValidate).map(key => `professionalStationRelation.${key}`)
+
+ // 根据当前子标签页决定先验证哪个
+ const currentFields = subActiveName.value === 'subBaseInfo'
+ ? baseInfoFields
+ : stationFields
+
+ const otherFields = subActiveName.value === 'subBaseInfo'
+ ? stationFields
+ : baseInfoFields
+
+ // 先验证当前子标签页的字段
+ baseForm.value?.validateField(currentFields, (isCurrentValid: boolean, currentInvalidFields: any) => {
+ if (!isCurrentValid) {
+ // 当前子标签页验证失败,提示用户
+ console.log('当前子标签页验证失败的字段:', currentInvalidFields);
+ if (subActiveName.value === 'subBaseInfo') {
+ message.info("请完善基本信息");
+ } else {
+ message.info("请完善岗位信息");
+ }
+ setTimeout(() => {
+ canSave.value = true
+ }, 1000)
+ return
+ }
+
+ // 当前子标签页验证通过,继续验证另一个子标签页
+ baseForm.value?.validateField(otherFields, (isOtherValid: boolean, otherInvalidFields: any) => {
+ if (!isOtherValid) {
+ // 另一个子标签页验证失败,跳转到那个子标签页
+ console.log('另一个子标签页验证失败的字段:', otherInvalidFields);
+ if (subActiveName.value === 'subBaseInfo') {
+ subActiveName.value = 'subStation'
+ message.info("基本信息已完善,请完善岗位信息");
+ } else {
+ subActiveName.value = 'subBaseInfo'
+ message.info("岗位信息已完善,请完善基本信息");
+ }
+ setTimeout(() => {
+ canSave.value = true
+ }, 1000)
+ return
+ }
+
+ // 两个子标签页都验证通过,执行保存
addInformation(form).then((response: any) => {
- const data = response.data.data;
+ const data = response.data;
message.success('保存成功')
form.baseInfo.id = data.baseId;
form.professionalStationRelation.id = data.relationId;
getDataList()
- }).catch(()=>{
- });
- } else {
- message.info("请完善基础信息");
- console.log('error submit!!');
- }
+ }).catch(() => {
+ // 错误处理
});
+ setTimeout(() => {
+ canSave.value = true
+ }, 1000)
+ })
+ })
}
-
- if (activeName.value == "seven") {
- stationForm.value?.validate((valid: boolean) => {
- if (valid) {
- addInformation(form).then((response: any) => {
- const data = response.data.data;
- message.success('保存成功')
- form.baseInfo.id = data.baseId;
- form.professionalStationRelation.id = data.relationId;
- getDataList()
- }).catch(()=>{
- });
- } else {
- message.info("请完善岗位信息");
- console.log('error submit!!');
- }
- });
- }
-
- setTimeout(()=>{
- canSave.value = true
- },1000)
}
// getNodeData 方法已移除,如需要请重新实现
// 政治面貌相关
@@ -2532,6 +2712,13 @@
getDicts(religiousBelief).then((response: any) => {
religiousBeliefDic.value = response.data;
}),
+ // 批量获取字典数据
+ getDictsByTypes(['religious_belief', 'heath', 'teacher_cate']).then((response: any) => {
+ religiousBeliefDic.value = response.data.religious_belief;
+ healthList.value = response.data.heath;
+ teacherCateList.value = response.data.teacher_cate;
+
+ }),
// 民族
getNationalList().then((response: any) => {
nationalList.value = response.data;
@@ -2942,7 +3129,16 @@
const handleImportDialog = () => {
importTeacherInfoRef.value?.init()
- }
+ }
+
+ // 身份证号输入限制:只允许数字和X/x,最大长度18
+ const handleIdCardInput = (value: string) => {
+ // 只保留数字和X/x
+ const filtered = value.replace(/[^\dXx]/g, '')
+ if (filtered !== value) {
+ form.baseInfo.idCard = filtered
+ }
+ }