442 lines
14 KiB
Vue
Executable File
442 lines
14 KiB
Vue
Executable File
<template>
|
||
<div class="layout-padding">
|
||
<div class="layout-padding-auto layout-padding-view">
|
||
<!-- 搜索表单 -->
|
||
<search-form
|
||
v-show="showSearch"
|
||
:model="search"
|
||
ref="searchFormRef"
|
||
@keyup-enter="handleFilter"
|
||
>
|
||
<template #default="{ visible }">
|
||
<template v-if="visible">
|
||
<el-form-item label="审核状态" prop="state">
|
||
<el-select
|
||
v-model="search.state"
|
||
clearable
|
||
placeholder="请选择审核状态"
|
||
>
|
||
<el-option
|
||
v-for="item in professionalState"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="工号" prop="teacherNo">
|
||
<el-input
|
||
v-model="search.teacherNo"
|
||
clearable
|
||
placeholder="请输入工号"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="姓名" prop="teacherName">
|
||
<el-input
|
||
v-model="search.teacherName"
|
||
clearable
|
||
placeholder="请输入姓名"
|
||
/>
|
||
</el-form-item>
|
||
</template>
|
||
</template>
|
||
|
||
<!-- 操作按钮 -->
|
||
<template #actions>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
|
||
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
|
||
</el-form-item>
|
||
</template>
|
||
</search-form>
|
||
|
||
<!-- 操作按钮 -->
|
||
<el-row>
|
||
<div class="mb15" style="width: 100%;">
|
||
<el-button
|
||
v-if="hasAuth('professional_professionalteacherhonor_add')"
|
||
type="primary"
|
||
icon="FolderAdd"
|
||
@click="handleAdd">新 增
|
||
</el-button>
|
||
<el-button
|
||
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>
|
||
</el-row>
|
||
|
||
<!-- 表格 -->
|
||
<el-table
|
||
ref="tableRef"
|
||
:data="state.dataList"
|
||
row-key="id"
|
||
v-loading="state.loading"
|
||
border
|
||
:cell-style="tableStyle.cellStyle"
|
||
:header-cell-style="tableStyle.headerCellStyle"
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||
|
||
<el-table-column prop="state" label="审核状态" width="130" align="center">
|
||
<template #default="scope">
|
||
<DetailPopover
|
||
v-if="scope.row.state === '-2' && scope.row.backReason"
|
||
title="审核详情"
|
||
placement="top"
|
||
:width="300"
|
||
:items="[
|
||
{ label: '审核状态', layout: 'horizontal', content: getAuditStateTagConfig(scope.row.state)?.label },
|
||
{ label: '驳回理由', content: scope.row.backReason, contentClass: 'reason-content' }
|
||
]"
|
||
>
|
||
<template #reference>
|
||
<ClickableTag
|
||
:type="getAuditStateTagConfig(scope.row.state)?.type || 'danger'"
|
||
:left-icon="getAuditStateTagConfig(scope.row.state)?.leftIcon"
|
||
:effect="getAuditStateTagConfig(scope.row.state)?.effect || 'dark'"
|
||
>
|
||
{{ getAuditStateTagConfig(scope.row.state)?.label || '已驳回' }}
|
||
</ClickableTag>
|
||
</template>
|
||
<template #content-0>
|
||
<ClickableTag
|
||
:type="getAuditStateTagConfig(scope.row.state)?.type || 'danger'"
|
||
:left-icon="getAuditStateTagConfig(scope.row.state)?.leftIcon"
|
||
:effect="getAuditStateTagConfig(scope.row.state)?.effect || 'dark'"
|
||
:right-icon="null"
|
||
>
|
||
{{ getAuditStateTagConfig(scope.row.state)?.label || '已驳回' }}
|
||
</ClickableTag>
|
||
</template>
|
||
<template #content-1>
|
||
<div class="reason-content">
|
||
<el-icon class="reason-icon"><Warning /></el-icon>
|
||
<span>{{ scope.row.backReason }}</span>
|
||
</div>
|
||
</template>
|
||
</DetailPopover>
|
||
<AuditState v-else :state="scope.row.state" :options="auditStateOptions" />
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<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 />
|
||
|
||
<el-table-column prop="honorCompany" label="表彰单位" min-width="150" align="center" show-overflow-tooltip />
|
||
|
||
<el-table-column prop="year" label="年份" width="100" align="center" />
|
||
|
||
<el-table-column label="证明材料" width="120" align="center">
|
||
<template #default="scope">
|
||
<el-button
|
||
v-if="scope.row.attachment && scope.row.attachment !== ''"
|
||
type="primary"
|
||
link
|
||
icon="Document"
|
||
@click="showEdvince(scope.row)">查看
|
||
</el-button>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||
<template #default="scope">
|
||
<el-button
|
||
v-if="hasAuth('professional_professionalteacherhonor_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_professionalteacherhonor_exam') && scope.row.canExam"
|
||
type="success"
|
||
link
|
||
icon="CircleCheck"
|
||
@click="changeState(scope.row, 1)">通过
|
||
</el-button>
|
||
<el-button
|
||
v-if="hasAuth('professional_professionalteacherhonor_exam') && scope.row.canDeptExam"
|
||
type="success"
|
||
link
|
||
icon="CircleCheck"
|
||
@click="changeState(scope.row, 1)">部门通过
|
||
</el-button>
|
||
<el-button
|
||
v-if="hasAuth('professional_professionalteacherhonor_exam') && scope.row.canBack"
|
||
type="danger"
|
||
link
|
||
icon="CircleClose"
|
||
@click="changeState(scope.row, -2)">驳回
|
||
</el-button>
|
||
<el-button
|
||
v-if="hasAuth('professional_professionalteacherhonor_exam') && scope.row.canDeptBack"
|
||
type="danger"
|
||
link
|
||
icon="CircleClose"
|
||
@click="changeState(scope.row, -2)">部门驳回
|
||
</el-button>
|
||
<el-button
|
||
v-if="hasAuth('professional_professionalteacherhonor_del')"
|
||
type="danger"
|
||
link
|
||
icon="delete"
|
||
@click="handleDel(scope.row)">删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<pagination
|
||
v-bind="state.pagination"
|
||
@current-change="currentChangeHandle"
|
||
@size-change="sizeChangeHandle"
|
||
/>
|
||
|
||
<!-- 材料预览:图片直接显示,PDF 在组件内部 dialog 中显示 -->
|
||
<preview-file
|
||
v-for="src in imgUrl"
|
||
:key="src.title"
|
||
:authSrc="src.url"
|
||
dialog-title="综合表彰材料"
|
||
/>
|
||
|
||
<!-- 子组件 -->
|
||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, 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/professionalteacherhonor'
|
||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS, getStatusConfig } from '/@/config/global'
|
||
import { defineAsyncComponent } from 'vue'
|
||
import {makeExportTeacherInfoByTypeTask} from "/@/api/professional/professionalfile";
|
||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
|
||
const ClickableTag = defineAsyncComponent(() => import('/@/components/ClickableTag/index.vue'))
|
||
const DetailPopover = defineAsyncComponent(() => import('/@/components/DetailPopover/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'))
|
||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||
|
||
// 消息提示 hooks
|
||
const message = useMessage()
|
||
const messageBox = useMessageBox()
|
||
|
||
// 字典数据
|
||
const { professional_state: professionalState } = useDict('professional_state')
|
||
|
||
// 审核状态选项
|
||
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
|
||
|
||
// 审核状态转 ClickableTag 配置(用 options 的 icon 字符串,与 AuditState 一致为实心)
|
||
const getAuditStateTagConfig = (state: string | number) => {
|
||
const opt = getStatusConfig(auditStateOptions, state)
|
||
if (!opt) return null
|
||
return { type: opt.type, label: opt.label, leftIcon: opt.icon, effect: opt.effect || 'dark' }
|
||
}
|
||
|
||
// 无权限即无节点
|
||
const { hasAuth } = useAuth()
|
||
|
||
// 表格引用
|
||
const tableRef = ref()
|
||
const searchFormRef = ref()
|
||
const backReasonRef = ref()
|
||
const dataFormRef = ref()
|
||
const showSearch = ref(true)
|
||
|
||
// 搜索表单数据
|
||
const search = reactive({
|
||
state: '',
|
||
teacherNo: '',
|
||
teacherName: ''
|
||
})
|
||
|
||
// 材料预览
|
||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||
|
||
// 导出加载状态
|
||
const exportLoading = ref(false)
|
||
const importTeacherOtherInfoRef = ref()
|
||
|
||
// 配置 useTable
|
||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||
pageList: async (params: any) => {
|
||
const response = await fetchList(params)
|
||
return {
|
||
data: {
|
||
records: response.data.records || [],
|
||
total: response.data.total || 0
|
||
}
|
||
}
|
||
},
|
||
queryForm: search
|
||
})
|
||
|
||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
|
||
|
||
// 查看证明材料:构造预览列表,交给 auth-img 处理
|
||
const showEdvince = (row: any) => {
|
||
imgUrl.value = []
|
||
nextTick(() => {
|
||
const list = row.attachment ? [row.attachment] : []
|
||
list.forEach(v => {
|
||
imgUrl.value.push({
|
||
title: '',
|
||
url: v
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
// 审核状态变更
|
||
const changeState = (row: any, val: number) => {
|
||
if (val === 1) {
|
||
// 通过
|
||
const str = '通过'
|
||
messageBox.confirm(`是否确认${str}${row.teacherName || row.realName}的申请`).then(async () => {
|
||
try {
|
||
await examObj({
|
||
id: row.id,
|
||
state: val
|
||
})
|
||
message.success('操作成功')
|
||
getDataList()
|
||
} catch (error: any) {
|
||
// 处理业务错误
|
||
message.error(error.msg)
|
||
}
|
||
}).catch(() => {})
|
||
} else if (val === -2) {
|
||
// 驳回
|
||
const newRow = JSON.parse(JSON.stringify(row))
|
||
backReasonRef.value?.init(newRow, 'honor')
|
||
}
|
||
}
|
||
|
||
// 查询
|
||
const handleFilter = () => {
|
||
getDataList()
|
||
}
|
||
|
||
// 重置查询
|
||
const resetQuery = () => {
|
||
Object.assign(search, {
|
||
state: '',
|
||
teacherNo: '',
|
||
teacherName: ''
|
||
})
|
||
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: 'P20006' });
|
||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||
message.success('后台下载进行中,请稍后查看任务列表');
|
||
});
|
||
setTimeout(() => {
|
||
exportLoading.value = false;
|
||
}, 3000); // 5分钟后自动关闭
|
||
}
|
||
|
||
// 打开导入弹窗
|
||
const handleImportDialog = () => {
|
||
importTeacherOtherInfoRef.value?.init('honor')
|
||
}
|
||
|
||
// 表格数据由 useTable(createdIsNeed 默认 true)在挂载时自动请求
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
/* 驳回理由展示(与 backSchoolCheckin 一致) */
|
||
.reason-content {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
background-color: #fef0f0;
|
||
border-radius: 4px;
|
||
border-left: 3px solid #f56c6c;
|
||
|
||
.reason-icon {
|
||
color: #f56c6c;
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
span {
|
||
color: #f56c6c;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
word-break: break-all;
|
||
flex: 1;
|
||
}
|
||
}
|
||
</style>
|