Files
school-developer/src/views/professional/professionaltitlerelation/index.vue
zhoutianchi 37e709cf7e 1
2026-02-06 14:24:42 +08:00

561 lines
16 KiB
Vue
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="titlerelation-page">
<div class="page-wrapper">
<!-- 内容区最上搜索其次标题+按钮再下方表格 -->
<div class="content-block">
<!-- 最上搜索区 -->
<div v-show="showSearch" class="content-block__filter">
<search-form :model="search" ref="searchFormRef" @keyup-enter="handleFilter">
<template #default="{ visible }">
<template v-if="visible">
<el-form-item label="姓名" prop="realName">
<el-input v-model="search.realName" clearable placeholder="请输入姓名" class="filter-input" />
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input v-model="search.teacherNo" clearable placeholder="请输入工号" class="filter-input" />
</el-form-item>
<el-form-item label="审核状态" prop="state">
<el-select v-model="search.state" clearable placeholder="请选择审核状态" class="filter-select">
<el-option v-for="item in professionalState" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="职称" prop="professionalTitleConfigId">
<el-select v-model="search.professionalTitleConfigId" clearable filterable placeholder="请选择职称" class="filter-select">
<el-option v-for="item in professionalTitleList" :key="item.id" :label="item.professionalTitle" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="专业技术职务" prop="majorStation">
<el-select v-model="search.majorStation" clearable filterable placeholder="请选择专业技术职务" class="filter-select">
<el-option v-for="item in majorStationList" :key="item.id" :label="item.majorStationName" :value="item.id" />
</el-select>
</el-form-item>
</template>
</template>
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</template>
</search-form>
</div>
<!-- 其次左侧按钮右侧 RightToolbar -->
<div class="content-block__header">
<div class="header-actions">
<div class="action-group">
<el-button v-if="hasAuth('professional_professionaltitlerelation_add')" type="primary" icon="FolderAdd" @click="handleAdd"
>新增</el-button
>
<el-button
v-if="hasAuth('professional_teacherbase_export')"
type="warning"
plain
icon="Download"
@click="handleDownLoadWord"
:loading="exportLoading"
>导出信息</el-button
>
<el-button
v-auth="'professional_teacherinfo_import'"
type="primary"
plain
icon="UploadFilled"
:loading="exportLoading"
@click="handleImportDialog"
>导入信息
</el-button>
</div>
<div class="header-right">
<RightToolbar v-model:showSearch="showSearch" @queryTable="getDataList" />
</div>
</div>
</div>
<!-- 再下方表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
row-key="id"
v-loading="state.loading"
border
stripe
class="titlerelation-table"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="state" label="审核状态" width="120" align="center">
<template #default="scope">
<AuditState :state="scope.row.state" />
</template>
</el-table-column>
<el-table-column label="姓名/工号" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template>
</el-table-column>
<el-table-column prop="professionalTitleConfigId" label="职称" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getProfessionalTitleName(scope.row.professionalTitleConfigId) }}
</template>
</el-table-column>
<el-table-column prop="majorStation" label="专业技术职务" min-width="150" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getMajorStationName(scope.row.majorStation) }}
</template>
</el-table-column>
<el-table-column label="证明材料" width="120" align="center">
<template #default="scope">
<el-button
v-if="scope.row.evidence && scope.row.evidence !== ''"
type="primary"
link
icon="Document"
@click="handlePreview(scope.row.srcList)"
>查看
</el-button>
<span v-else class="empty-text">-</span>
</template>
</el-table-column>
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="scope">
<div class="op-cell">
<el-button
v-if="hasAuth('professional_professionaltitlerelation_edit') && (scope.row.state === '0' || scope.row.state === '-2')"
type="primary"
link
icon="edit-pen"
@click="handleEdit(scope.row)"
>修改
</el-button>
<el-button
v-if="hasAuth('professional_professionaltitlerelation_exam') && scope.row.canExam"
type="success"
link
icon="CircleCheck"
@click="changeState(scope.row, 1)"
>通过
</el-button>
<el-button
v-if="hasAuth('professional_professionaltitlerelation_exam') && scope.row.canDeptExam"
type="success"
link
icon="CircleCheck"
@click="changeState(scope.row, 1)"
>部门通过
</el-button>
<el-button
v-if="hasAuth('professional_professionaltitlerelation_exam') && scope.row.canBack"
type="danger"
link
icon="CircleClose"
@click="changeState(scope.row, -2)"
>驳回
</el-button>
<el-button
v-if="hasAuth('professional_professionaltitlerelation_exam') && scope.row.canDeptBack"
type="danger"
link
icon="CircleClose"
@click="changeState(scope.row, -2)"
>部门驳回
</el-button>
<el-button v-if="hasAuth('professional_professionaltitlerelation_del')" type="danger" link icon="delete" @click="handleDel(scope.row)"
>删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination v-bind="state.pagination" @current-change="currentChangeHandle" @size-change="sizeChangeHandle" />
</div>
</div>
<!-- 材料预览图片直接显示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" />
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter"></import-teacher-other-info>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { useAuth } from '/@/hooks/auth';
import { useMessage } from '/@/hooks/message';
import { useMessageBox } from '/@/hooks/message';
import { useDict } from '/@/hooks/dict';
// 接口
import { fetchList, examObj, delObj } from '/@/api/professional/professionaluser/professionaltitlerelation';
import { getProfessionalTitleList } from '/@/api/professional/rsbase/professionaltitlelevelconfig';
import { getMajorStationList } from '/@/api/professional/rsbase/professionalmajorstation';
import { makeExportTeacherInfoByTypeTask } from '/@/api/professional/professionalfile';
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 previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'));
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'));
// 无权限即无节点:使用 useAuth + v-if
const { hasAuth } = useAuth();
// 消息提示 hooks
const message = useMessage();
const messageBox = useMessageBox();
// 字典数据
const { professional_state: professionalState } = useDict('professional_state');
// 表格引用
const tableRef = ref();
const searchFormRef = ref();
const multiDialogRef = ref();
const dataFormRef = ref();
const backReasonRef = ref();
const showSearch = ref(true); // 方案 F默认收起筛选首屏仅一张大卡片
// 搜索表单数据
const search = reactive({
state: '',
teacherNo: '',
realName: '',
professionalTitleConfigId: '',
majorStation: '',
});
// 材料预览
const imgUrl = ref<Array<{ title: string; url: string }>>([]);
// 导出加载状态
const exportLoading = ref(false);
// 职称和专业技术职务列表
const professionalTitleList = ref<any[]>([]);
const majorStationList = ref<any[]>([]);
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList(params);
const records = response.data.records || [];
// 处理证明材料列表
records.forEach((v: any) => {
if (v.evidence != null) {
v.srcList = [v.evidence];
} else {
v.srcList = [];
}
});
return {
data: {
records,
total: response.data.total || 0,
},
};
},
queryForm: search,
});
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
// 预览材料
const handlePreview = (list: string[]) => {
imgUrl.value = [];
nextTick(() => {
list.forEach((v) => {
imgUrl.value.push({
title: '',
url: v,
});
});
});
};
// 审核状态变更
const changeState = (row: any, val: number) => {
if (val === 1) {
const str = '通过';
messageBox
.confirm(`是否确认${str}${row.realName}的申请`)
.then(async () => {
try {
await examObj({
id: row.id,
state: val,
});
message.success('操作成功');
getDataList();
} catch (error: any) {
// 处理业务错误
}
})
.catch(() => {});
} else if (val === -2) {
backReasonRef.value?.init(row, 'title');
}
};
// 查询
const handleFilter = () => {
getDataList();
};
// 重置查询
const resetQuery = () => {
Object.assign(search, {
state: '',
teacherNo: '',
realName: '',
professionalTitleConfigId: '',
majorStation: '',
});
getDataList();
};
// 打开新增窗口
const handleAdd = () => {
dataFormRef.value?.openDialog();
};
// 打开编辑窗口
const handleEdit = (row: any) => {
dataFormRef.value?.openDialog(row);
};
// 删除
const handleDel = (row: any) => {
messageBox
.confirm('是否确认删除该条记录')
.then(async () => {
try {
await delObj(row.id);
message.success('删除成功');
// 如果当前页只剩一条数据,且不是第一页,则跳转到上一页
if (state.pagination && state.dataList && state.dataList.length === 1 && state.pagination.current && state.pagination.current > 1) {
state.pagination.current = state.pagination.current - 1;
}
getDataList();
} catch (error: any) {
// 处理业务错误
message.error(error.msg);
}
})
.catch(() => {});
};
// 导出
const handleDownLoadWord = async () => {
exportLoading.value = true;
let params = Object.assign(search, { type: 'P20002' });
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
message.success('后台下载进行中,请稍后查看任务列表');
});
setTimeout(() => {
exportLoading.value = false;
}, 3000); // 5分钟后自动关闭
};
// 获取职称名称
const getProfessionalTitleName = (id: string | number) => {
const item = professionalTitleList.value.find((item: any) => item.id === id);
return item ? item.professionalTitle : '-';
};
// 获取专业技术职务名称
const getMajorStationName = (id: string | number) => {
const item = majorStationList.value.find((item: any) => item.id === id);
return item ? item.majorStationName : '-';
};
// 加载字典数据
const loadDictData = async () => {
try {
// 加载职称列表
const titleRes = await getProfessionalTitleList();
if (titleRes && titleRes.data) {
professionalTitleList.value = titleRes.data;
}
// 加载专业技术职务列表
const majorRes = await getMajorStationList();
if (majorRes && majorRes.data) {
majorStationList.value = majorRes.data;
}
} catch (error) {
// Failed to load dict data
}
};
const importTeacherOtherInfoRef = ref();
const handleImportDialog = () => {
importTeacherOtherInfoRef.value?.init('titleRelation');
};
// 初始化:仅加载下拉字典,表格数据由 useTable 在 createdIsNeed: true 时自动请求
onMounted(async () => {
await loadDictData();
});
</script>
<style lang="scss" scoped>
.titlerelation-page {
padding: 12px;
min-height: 100%;
background: var(--el-bg-color-page, #f5f6f8);
}
.page-wrapper {
display: flex;
flex-direction: column;
gap: 0;
}
/* 筛选:内容区最上方,无上外边距;与下方标题栏间距用 margin-bottom */
.content-block__filter {
padding: 16px 20px 5px 20px;
margin-top: 0;
margin-bottom: 12px;
background: var(--el-fill-color-light);
border-radius: 8px;
:deep(.filter-input),
:deep(.filter-select) {
width: 180px;
}
}
/* 职称关系区域:无卡片,表头 + 表格 + 分页 */
.content-block {
background: var(--el-bg-color);
border-radius: 8px;
padding: 20px;
}
.content-block__header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
padding-bottom: 14px;
margin-bottom: 0;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.card-title {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 500;
color: var(--el-text-color-primary);
.title-icon {
font-size: 16px;
color: var(--el-color-primary);
}
}
.header-actions {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
width: 100%;
}
/* 按钮间距按规范 10px与 RightToolbar 区隔 */
.action-group {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
padding-left: 12px;
// border-left: 1px solid var(--el-border-color-lighter);
}
/* 表格 */
.titlerelation-table {
:deep(.el-table__body td) {
transition: background-color 0.12s ease;
}
:deep(.el-button + .el-button) {
margin-left: 0;
}
}
/* 操作列 */
.op-cell {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 4px 8px;
}
.empty-text {
color: var(--el-text-color-secondary, #909399);
}
/* 表格与分页间距 */
.content-block .titlerelation-table {
margin-top: 12px;
margin-bottom: 0;
}
.content-block .titlerelation-table + * {
margin-top: 16px;
}
/* 响应式 */
@media (max-width: 1200px) {
.titlerelation-page {
padding: 8px;
}
.card-header {
flex-direction: column;
align-items: flex-start;
}
.header-actions {
width: 100%;
justify-content: space-between;
}
}
@media (max-width: 768px) {
.action-group {
flex-direction: column;
width: 100%;
.el-button {
width: 100%;
}
}
}
</style>