merge code pull

This commit is contained in:
2026-01-26 11:07:39 +08:00
18 changed files with 1781 additions and 1337 deletions

View File

@@ -433,6 +433,19 @@ export const changeMajor = (obj: any) => {
});
};
/**
* 退档恢复
* @param obj
*/
export const resetSign = (obj: any) => {
return request({
url: '/recruit/recruitstudentsignup/resetSign',
method: 'post',
data: obj,
});
};
/**
* 重新推送
* @param obj
@@ -550,3 +563,11 @@ export const interview = (obj: any) => {
data: obj,
});
};
export const queryAllRecruitUser = () => {
return request({
url: '/recruit/recruitstudentsignup/queryAllRecruitUser',
method: 'get'
});
};

View File

@@ -52,8 +52,8 @@ export const delObj = (id: string | number) => {
*/
export const putObj = (obj: any) => {
return request({
url: '/recruit/recruitstudentsignupturnover',
method: 'put',
url: '/recruit/recruitstudentsignupturnover/edit',
method: 'post',
data: obj,
});
};

View File

@@ -2,7 +2,8 @@
<el-tag
:type="type"
:size="size"
:class="['clickable-tag', { 'has-action': actualRightIcon !== null }]"
:class="['clickable-tag', { 'has-action': actualRightIcon !== null}]"
:style="{ width: width ? `${width}px` : 'auto' }"
@click="handleClick">
<!-- 左侧图标 -->
<el-icon
@@ -43,6 +44,7 @@ interface Props {
leftIcon?: any // 左侧图标组件
middleIcon?: any // 中间图标组件(如警告图标)
rightIcon?: any // 右侧图标组件(默认为 Right null 则不显示)
width?: string | number // 自定义宽度
}
const props = withDefaults(defineProps<Props>(), {
@@ -50,7 +52,8 @@ const props = withDefaults(defineProps<Props>(), {
size: 'default',
leftIcon: undefined,
middleIcon: undefined,
rightIcon: undefined
rightIcon: undefined,
width: undefined
})
// 获取实际的右侧图标:未传值时使用默认图标,传 null 则不显示
@@ -97,7 +100,6 @@ export default {
}
}
}
.middle-icon {
animation: pulse 1.5s ease-in-out infinite;
}

View File

@@ -0,0 +1,162 @@
# DetailPopover 详情弹窗组件
一个通用的详情弹窗组件,用于展示结构化的详情信息。
## 功能特性
- ✅ 自定义标题和标题图标
- ✅ 支持多个详情项label + content
- ✅ 支持横向/纵向布局
- ✅ 支持自定义内容(插槽或组件)
- ✅ 支持标签图标
- ✅ 支持内容区域自定义样式类
## Props
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|-----|------|------|-------|--------|
| title | 标题 | string | - | '' |
| titleIcon | 标题图标组件 | Component | - | undefined |
| items | 详情项列表 | DetailItem[] | - | [] |
| placement | 弹出位置 | string | top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end | right |
| width | Popover 宽度 | string \| number | - | 300 |
| trigger | 触发方式 | string | click/focus/hover/contextmenu | click |
| popperClass | Popover 自定义类名 | string | - | '' |
## DetailItem 接口
```typescript
interface DetailItem {
label?: string // 标签文本
content?: string | number // 内容文本
labelIcon?: Component // 标签图标
layout?: 'horizontal' | 'vertical' // 布局方向,默认 vertical
contentClass?: string // 内容区域的自定义类名
component?: Component // 自定义组件
componentProps?: Record<string, any> // 自定义组件的 props
}
```
## Slots
| 插槽名 | 说明 | 参数 |
|--------|------|------|
| reference | 触发元素 | - |
| content-{index} | 自定义第 index 项的内容 | { item: DetailItem } |
| custom-content | 自定义内容(显示在所有详情项之后) | - |
## 使用示例
### 基础用法
```vue
<template>
<DetailPopover
title="专业变更详情"
:title-icon="InfoFilled"
:width="320"
:items="[
{ label: '旧专业', content: '计算机应用技术' },
{ label: '新专业', content: '软件技术', contentClass: 'new-major' }
]">
<template #reference>
<span class="link">查看详情</span>
</template>
</DetailPopover>
</template>
<script setup>
import DetailPopover from '/@/components/DetailPopover/index.vue'
import { InfoFilled } from '@element-plus/icons-vue'
</script>
```
### 横向布局
```vue
<DetailPopover
title="异动审核详情"
:items="[
{
label: '审核状态',
layout: 'horizontal',
content: '待审核'
}
]">
<template #reference>
<el-button>查看</el-button>
</template>
</DetailPopover>
```
### 使用插槽自定义内容
```vue
<DetailPopover
title="异动审核详情"
:items="[
{ label: '审核状态', layout: 'horizontal' },
{ label: '备注信息', contentClass: 'reason-content' }
]">
<template #reference>
<ClickableTag>待审核</ClickableTag>
</template>
<!-- 自定义第一项内容 -->
<template #content-0>
<ClickableTag type="warning">待审核</ClickableTag>
</template>
<!-- 自定义第二项内容 -->
<template #content-1>
<div class="reason-content">
<el-icon><Warning /></el-icon>
<span>需要补充材料</span>
</div>
</template>
</DetailPopover>
```
### 使用标签图标
```vue
<DetailPopover
title="详情"
:items="[
{
label: '状态',
labelIcon: CircleCheck,
content: '已完成'
}
]">
<template #reference>
<el-button>查看</el-button>
</template>
</DetailPopover>
```
## 样式自定义
可以通过 `contentClass` 为内容区域添加自定义样式类:
```vue
<DetailPopover
:items="[
{
label: '新专业',
content: '软件技术',
contentClass: 'new-major' // 添加自定义样式类
}
]">
<template #reference>
<span>查看</span>
</template>
</DetailPopover>
<style scoped>
.new-major {
color: var(--el-color-primary);
font-weight: 500;
}
</style>
```

View File

@@ -0,0 +1,185 @@
<template>
<el-popover
:placement="placement"
:width="width"
:trigger="trigger"
:popper-class="popperClass">
<template #reference>
<slot name="reference"></slot>
</template>
<!-- 弹出内容 -->
<div class="detail-popover">
<!-- 标题 -->
<div v-if="title" class="detail-title">
<el-icon v-if="titleIcon" class="title-icon">
<component :is="titleIcon" />
</el-icon>
<span>{{ title }}</span>
</div>
<!-- 详情项列表 -->
<div
v-for="(item, index) in items"
:key="index"
:class="['detail-section', { 'horizontal': item.layout === 'horizontal' }]">
<div v-if="item.label" class="section-label">
<el-icon v-if="item.labelIcon" class="label-icon">
<component :is="item.labelIcon" />
</el-icon>
<span>{{ item.label }}</span>
</div>
<div class="section-content" :class="item.contentClass">
<!-- 使用插槽自定义内容 -->
<slot
v-if="$slots[`content-${index}`]"
:name="`content-${index}`"
:item="item">
</slot>
<!-- 默认显示文本内容 -->
<template v-else>
<component
v-if="item.component"
:is="item.component"
v-bind="item.componentProps || {}">
</component>
<span v-else :class="item.contentClass">{{ item.content }}</span>
</template>
</div>
</div>
<!-- 自定义内容插槽 -->
<slot name="custom-content"></slot>
</div>
</el-popover>
</template>
<script setup lang="ts">
import type { Component } from 'vue'
export interface DetailItem {
label?: string // 标签文本
content?: string | number // 内容文本
labelIcon?: Component // 标签图标
layout?: 'horizontal' | 'vertical' // 布局方向,默认 vertical
contentClass?: string // 内容区域的自定义类名
component?: Component // 自定义组件
componentProps?: Record<string, any> // 自定义组件的 props
}
interface Props {
title?: string // 标题
titleIcon?: Component // 标题图标
items?: DetailItem[] // 详情项列表
placement?: 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end'
width?: string | number // Popover 宽度
trigger?: 'click' | 'focus' | 'hover' | 'contextmenu' // 触发方式
popperClass?: string // Popover 自定义类名
}
withDefaults(defineProps<Props>(), {
title: '',
titleIcon: undefined,
items: () => [],
placement: 'right',
width: 300,
trigger: 'click',
popperClass: ''
})
</script>
<script lang="ts">
export default {
name: 'DetailPopover'
}
</script>
<style scoped lang="scss">
.detail-popover {
padding: 0;
.detail-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 2px solid #EBEEF5;
.title-icon {
color: var(--el-color-primary);
font-size: 18px;
}
}
.detail-section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
// 横向布局
&.horizontal {
display: flex;
align-items: center;
gap: 16px;
.section-label {
margin-bottom: 0;
white-space: nowrap;
display: flex;
align-items: center;
gap: 6px;
}
.section-content {
flex: 1;
}
}
// 纵向布局(默认)
&:not(.horizontal) {
.section-label {
margin-bottom: 10px;
}
}
.section-label {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #909399;
font-weight: 500;
.label-icon {
font-size: 14px;
color: var(--el-color-primary);
}
}
.section-content {
font-size: 14px;
color: #303133;
line-height: 1.6;
word-break: break-all;
// 新专业样式(蓝色高亮)
&.new-major {
color: var(--el-color-primary);
font-weight: 500;
}
// 支持嵌套的样式类
:deep(.new-major) {
color: var(--el-color-primary);
font-weight: 500;
}
}
}
}
</style>

View File

@@ -62,12 +62,6 @@ export const PUSHED_STATUS_LIST = [
{ label: "已推送", value: "1" ,type: "success"},
];
// 数据来源 (使用字典 recruit_data_source
export const DATA_SOURCE_LIST = [
{ label: "学校", value: "0" },
{ label: "市平台", value: "1" }
];
// 录取通知书发放状态
export const NOTICE_SEND_STATUS_LIST = [
{ label: "未发放", value: "0" },
@@ -123,6 +117,13 @@ export const NEW_CITY_MATERIAL_STATUS_LIST = [
{ label: "已上传", value: "1" },
];
// 异动审核状态
export const TURNOVER_AUDIT_STATUS_LIST = [
{ label: "待审核", value: "1" ,type: "warning", icon: "Clock"},
{ label: "驳回", value: "2" ,type: "danger", icon: "CircleClose"},
{ label: "通过", value: "3" ,type: "success", icon: "CircleCheck"}
];
/**
* 根据值从状态列表中获取配置项
* @param statusList 状态列表

View File

@@ -350,6 +350,12 @@
color: var(--el-text-color-regular);
}
&.detail-dialog .el-dialog__body {
min-height: 80vh !important;
max-height: 80vh !important;
height: 80vh !important;
}
.el-dialog__footer {
padding: 12px 20px;

View File

@@ -49,6 +49,7 @@ import { ref, reactive } from 'vue'
import { useMessage } from '/@/hooks/message'
import { useDict } from '/@/hooks/dict'
import { putBackObj } from '/@/api/recruit/recruitstudentsignup'
import { getDicts } from '/@/api/admin/dict'
// 消息提示 hooks
const message = useMessage()
@@ -119,8 +120,8 @@ const init = async (formData: any, pageData: any) => {
checkInStatusData.value = []
try {
const data = await getTypeValue('check_in_status')
checkInStatusData.value = data.data || []
const dictData = await getDicts('check_in_status')
checkInStatusData.value = dictData.data || []
} catch (error) {
console.error('获取字典数据失败', error)
}

View File

@@ -1,32 +1,87 @@
<!--
- Copyright (c) 2018-2025, cyweb All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- Neither the name of the pig4cloud.com developer nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-->
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-form :model="queryForm" inline>
<el-form-item label="">
<el-form :model="queryForm" inline ref="searchFormRef">
<el-form-item label="关键词">
<el-input
v-model="queryForm.searchTotal"
clearable
placeholder="请输入姓名/身份证号/家庭联系人"
/>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="queryForm.deptCode"
filterable
clearable
placeholder="请选择学院"
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode"
/>
</el-select>
</el-form-item>
<el-form-item label="入学年份" prop="grade">
<el-select
v-model="queryForm.grade"
filterable
clearable
placeholder="请选择入学年份"
style="width: 150px">
<el-option
v-for="item in planList"
:key="item.id"
:label="item.year"
:value="item.year"
/>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="queryForm.classCode"
filterable
clearable
placeholder="请选择班级"
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode"
/>
</el-select>
</el-form-item>
<el-form-item label="报到状态" prop="checkInStatus">
<el-select
v-model="queryForm.checkInStatus"
filterable
clearable
placeholder="请选择报到状态"
style="width: 150px">
<el-option
v-for="item in checkInStatusData"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="住宿申请" prop="isDormApply">
<el-select
v-model="queryForm.isDormApply"
filterable
clearable
placeholder="请选择住宿申请"
style="width: 150px">
<el-option label="未通过" value="0" />
<el-option label="申请通过" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" class="ml10" @click="resetQuery">重置</el-button>
@@ -60,7 +115,7 @@
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptCode" label="学院" align="center" show-overflow-tooltip />
<el-table-column prop="classCode" label="班级" align="center" width="80" show-overflow-tooltip />
<el-table-column label="姓名/学号" align="center" width="150" show-overflow-tooltip>
<el-table-column label="姓名/学号" align="center" min-width="150" show-overflow-tooltip>
<template #default="scope">
<TeacherNameNo :name="scope.row.name" :no="scope.row.stuNo" />
</template>
@@ -78,32 +133,99 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="isDormApply" label="住宿申请" align="center" width="100">
<el-table-column prop="isRoom" label="是否住宿" align="center" width="120">
<template #default="scope">
<el-tag v-if="scope.row.isDormApply == '1'" type="success">通过</el-tag>
<el-tag v-else-if="scope.row.isDormApply == '0'" type="info">未通过</el-tag>
<template v-if="scope.row.isRoom == '1'">
<DetailPopover
title="住宿信息"
:title-icon="InfoFilled"
:width="320"
:items="[
{
label: '住宿申请',
layout: 'horizontal'
},
{
label: '宿舍号',
layout: 'horizontal'
},
{
label: '床位号',
layout: 'horizontal'
}
]">
<template #reference>
<el-tag type="success" class="dorm-tag">
{{ getStatusConfig(yes_no_type, scope.row.isRoom)?.label }}
<el-icon class="info-icon"><InfoFilled /></el-icon>
</el-tag>
</template>
<!-- 住宿申请状态 -->
<template #content-0>
<div class="dorm-apply-content">
<ClickableTag
v-if="scope.row.isDormApply == '1'"
type="success"
size="small"
:left-icon="CircleCheck"
:right-icon="null">
申请通过
</ClickableTag>
<ClickableTag
v-else-if="scope.row.isDormApply == '0'"
type="danger"
size="small"
:left-icon="CircleClose"
:right-icon="null">
未通过
</ClickableTag>
<span v-else class="empty-text">-</span>
</div>
</template>
<!-- 宿舍号 -->
<template #content-1>
<div class="dorm-room-content">
<span :class="scope.row.roomNo ? 'room-text' : 'empty-text'">
{{ scope.row.roomNo || '-' }}
</span>
</div>
</template>
<!-- 床位号 -->
<template #content-2>
<el-tag v-if="scope.row.bedNo" size="small" type="sucess" effect="dark">
{{ scope.row.bedNo }}
</el-tag>
</template>
</DetailPopover>
</template>
<span v-else>{{ getStatusConfig(yes_no_type, scope.row.isRoom)?.label }}</span>
</template>
</el-table-column>
<el-table-column prop="isRoom" label="是否住宿" align="center" width="100">
<el-table-column prop="degreeOfEducation" label="文化程度" align="center" show-overflow-tooltip >
<template #default="scope">
<span>{{ scope.row.isRoom === '1' ? '是' : scope.row.isRoom === '0' ? '否' : '' }}</span>
<span>{{ getStatusConfig(eduList, scope.row.degreeOfEducation)?.label }}</span>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="宿舍号" align="center" width="80" show-overflow-tooltip />
<el-table-column prop="bedNo" label="床位号" align="center" width="80" show-overflow-tooltip />
<el-table-column prop="degreeOfEducation" label="文化程度" align="center" show-overflow-tooltip />
<el-table-column prop="residenceDetail" label="居住地址" align="center" show-overflow-tooltip />
<el-table-column prop="parentName" label="家庭联系人" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="parentTelOne" label="家长电话1" align="center" show-overflow-tooltip />
<el-table-column prop="parentTelTwo" label="家长电话2" align="center" show-overflow-tooltip />
<el-table-column label="家长电话" align="center" show-overflow-tooltip>
<template #default="scope">
<div v-if="scope.row.parentTelOne || scope.row.parentTelTwo" class="parent-tel">
<span v-if="scope.row.parentTelOne" class="tel-item">{{ scope.row.parentTelOne }}</span>
<span v-if="scope.row.parentTelOne && scope.row.parentTelTwo" class="tel-separator">/</span>
<span v-if="scope.row.parentTelTwo" class="tel-item">{{ scope.row.parentTelTwo }}</span>
</div>
<span v-else class="empty-text">-</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.recruit_newstucheckin_edit"
v-auth="'recruit_newstucheckin_edit'"
type="primary"
link
icon="CircleCheck"
icon="EditPen"
@click="handleCheckIn(scope.row)"
>
报到
@@ -132,13 +254,23 @@ import { useUserInfo } from '/@/stores/userInfo'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { useMessage } from '/@/hooks/message'
import { fetchList } from '/@/api/recruit/newstucheckin'
import { getTypeValue } from '/@/api/admin/dict'
import { getDictsByTypes } from '/@/api/admin/dict'
import { useDict } from '/@/hooks/dict'
import request from '/@/utils/request'
import { getStatusConfig } from '/@/config/global'
import { getDeptList, getClassListByRole } from '/@/api/basic/basicclass'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
import DetailPopover from '/@/components/DetailPopover/index.vue'
import ClickableTag from '/@/components/ClickableTag/index.vue'
import { InfoFilled, CircleCheck, CircleClose, HomeFilled, Grid } from '@element-plus/icons-vue'
const StuCheckIn = defineAsyncComponent(() => import('./stu-check-in.vue'))
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const GenderTag = defineAsyncComponent(() => import('/@/components/GenderTag/index.vue'))
// 是否住宿字典
const { yes_no_type } = useDict('yes_no_type')
// 使用 Pinia store
const userInfoStore = useUserInfo()
const { userInfos } = storeToRefs(userInfoStore)
@@ -155,15 +287,29 @@ const permissions = computed(() => {
// 消息提示 hooks
const message = useMessage()
// 文化程度字典数据
const eduList = ref<any[]>([])
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
const stuCheckInRef = ref()
// 导出加载状态
const exportLoading = ref(false)
// 数据列表
const deptList = ref<any[]>([])
const planList = ref<any[]>([])
const classList = ref<any[]>([])
// 查询表单
const queryForm = reactive({
deptCode: '',
grade: '',
classCode: '',
checkInStatus: '',
isDormApply: '',
searchTotal: ''
})
@@ -176,6 +322,11 @@ const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
const response = await fetchList({
...params,
deptCode: queryForm.deptCode,
grade: queryForm.grade,
classCode: queryForm.classCode,
checkInStatus: queryForm.checkInStatus,
isDormApply: queryForm.isDormApply,
searchTotal: queryForm.searchTotal
})
return {
@@ -198,6 +349,12 @@ const getCheckInStatusLabel = (value: string) => {
// 重置查询
const resetQuery = () => {
searchFormRef.value?.resetFields()
queryForm.deptCode = ''
queryForm.grade = ''
queryForm.classCode = ''
queryForm.checkInStatus = ''
queryForm.isDormApply = ''
queryForm.searchTotal = ''
getDataList()
}
@@ -256,20 +413,123 @@ const handleExportOut = async () => {
}
// 查询报到状态字典
const getCheckInStatusData = async () => {
const getDictsData = async () => {
try {
const data = await getTypeValue('check_in_status')
checkInStatusData.value = data.data || []
const data = await getDictsByTypes(['check_in_status','finance_student_source'])
checkInStatusData.value = data.data.check_in_status || []
eduList.value = data.data.finance_student_source || []
} catch (error) {
// 获取报到状态字典失败
}
}
// 初始化数据
const init = async () => {
try {
// 获取学院列表
const deptData = await getDeptList()
deptList.value = deptData.data || []
// 获取入学年份列表(招生计划)
const planData = await getList()
planList.value = planData.data || []
// 获取班级列表
const classData = await getClassListByRole()
classList.value = classData.data || []
// 获取字典数据
await getDictsData()
getDataList()
} catch (error) {
message.error('初始化失败')
}
}
onMounted(() => {
getCheckInStatusData()
getDataList()
init()
})
</script>
<style lang="scss" scoped>
.parent-tel {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
.tel-item {
color: #303133;
}
.tel-separator {
color: #909399;
margin: 0 2px;
}
}
.empty-text {
color: #909399;
}
.dorm-tag {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.info-icon {
font-size: 12px;
opacity: 0.8;
}
}
// 住宿申请状态内容
.dorm-apply-content {
display: flex;
align-items: center;
}
// 宿舍号内容
.dorm-room-content {
display: flex;
align-items: center;
gap: 6px;
.room-icon {
color: var(--el-color-primary);
font-size: 16px;
}
.room-text {
color: var(--el-color-primary);
font-weight: 600;
font-size: 15px;
}
}
// 床位号内容
.dorm-bed-content {
display: flex;
align-items: center;
gap: 6px;
.bed-icon {
color: var(--el-color-primary);
font-size: 16px;
}
.bed-text {
color: var(--el-color-primary);
font-weight: 600;
font-size: 15px;
}
}
</style>

View File

@@ -1,30 +1,24 @@
<template>
<div>
<el-dialog v-model="newStuCheckInDialog" width="40%">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px"
<el-dialog v-model="newStuCheckInDialog" width="600" title="新生报到">
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px"
class="demo-ruleForm">
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.name" style=" width: 80%"></el-input>
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="form.gender" placeholder="请选择性别" style=" width: 80%">
<el-option
v-for="item in genderData"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-radio-group v-model="form.gender">
<el-radio v-for="item in genderData" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="form.idNumber" style=" width: 80%"></el-input>
<el-input v-model="form.idNumber"></el-input>
</el-form-item>
<el-form-item label="报到状态" prop="checkInStatus">
<el-select v-model="form.checkInStatus" filterable placeholder="请选择报到状态" style=" width: 80% ">
<el-select v-model="form.checkInStatus" filterable placeholder="请选择报到状态">
<el-option
v-for="item in checkInStatusData"
:key="item.value"
@@ -35,7 +29,7 @@
</el-form-item>
<el-form-item label="是否住宿" prop="isRoom" v-if="isRoomTab">
<el-select v-model="form.isRoom" filterable placeholder="是否住宿" style=" width: 80% ">
<el-select v-model="form.isRoom" filterable placeholder="是否住宿">
<el-option
v-for="item in yesOrNoData"
:key="item.value"
@@ -54,7 +48,6 @@
:remote-method="remoteMethod"
@change="handleRoomNoChange"
:loading="loading"
style=" width: 80% "
>
<el-option
v-for="item in roomNoList"
@@ -66,7 +59,7 @@
</el-form-item>
<el-form-item label="床号" prop="bedNo" v-if="isRoomTab && form.isRoom=='1'">
<el-select v-model="form.bedNo" filterable placeholder="请选择床号" style=" width: 80% " :key="bedNoKey">
<el-select v-model="form.bedNo" filterable placeholder="请选择床号" :key="bedNoKey">
<el-option
v-for="item in bedNoData"
:key="item.bedNo"
@@ -78,13 +71,13 @@
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="form.remarks" :rows="2" style=" width: 80%"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="checkIn" v-loading="submitLoading">确定</el-button>
<el-button @click="newStuCheckInDialog = false">取消</el-button>
<el-input v-model="form.remarks" :rows="2"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="newStuCheckInDialog = false">取消</el-button>
<el-button type="primary" @click="checkIn" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -92,10 +85,12 @@
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { useMessage } from '/@/hooks/message'
import { getDataByRoomNo } from "/@/api/stuwork/dormroom"
import { fearchRoomStuNum } from "/@/api/stuwork/dormroomstudent"
import { getDicts } from "/@/api/admin/dict"
import { putObj } from '@/api/recruit/newstucheckin'
import { putObj } from '/@/api/recruit/newstucheckin'
import { useDict } from '/@/hooks/dict'
import { getDicts } from '/@/api/admin/dict'
import { getDataByRoomNo } from '/@/api/stuwork/dormroom'
const { sexy:genderData ,yes_no_type:yesOrNoData} = useDict('sexy','yes_no_type')
// Emits
const emit = defineEmits<{
@@ -118,15 +113,7 @@ const loading = ref(false)
const isRoomTab = ref(false)
const bedNoKey = ref(0) // 用于强制更新床号选择器
const yesOrNoData = [
{ label: '否', value: '0' },
{ label: '是', value: '1' }
]
const genderData = [
{ label: '女', value: '2' },
{ label: '男', value: '1' }
]
const submitLoading = ref(false)
@@ -225,7 +212,6 @@ const init = (formData: any, pageData: any) => {
getDicts('check_in_status').then(data => {
checkInStatusData.value = data.data
})
console.log("OKKK")
}
// 报到提交
@@ -255,8 +241,6 @@ const remoteMethod = (query: string) => {
loading.value = true
getDataByRoomNo(data).then(data => {
roomNoList.value = data.data
console.log("this.roomNoList")
console.log(roomNoList.value)
loading.value = false
}).catch(() => {
loading.value = false
@@ -266,8 +250,7 @@ const remoteMethod = (query: string) => {
// 查询此房间为几人间
const fearchRoomStuNums = (roomNo: string) => {
const data = { "roomNo": roomNo }
fearchRoomStuNum(data).then(data => {
fearchRoomStuNum(roomNo).then(data => {
bedNoData.value = data.data
})
}

View File

@@ -299,7 +299,7 @@ import { getObj, addObjStu, putObj } from '/@/api/recruit/recruitprestudent'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
import { getDicts } from '/@/api/admin/dict'
import { queryAllTeacherByRecruit } from '/@/api/professional/professionaluser/teacherbase'
import { verifyIdCardAll, verifyPhone, verifyAdmissionNumber } from '/@/utils/toolsValidate'
import { verifyPhone, verifyAdmissionNumber } from '/@/utils/toolsValidate'
// Props
const props = defineProps<{
@@ -379,17 +379,7 @@ const dataRule = {
}
],
idCard: [
{ required: true, message: '身份证不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (value && !verifyIdCardAll(value)) {
callback(new Error('请输入正确的身份证号码'))
} else {
callback()
}
},
trigger: 'blur'
}
{ required: true, message: '身份证不能为空', trigger: 'blur' }
],
admission: [
{ required: true, message: '准考证不能为空', trigger: 'blur' },

File diff suppressed because it is too large Load Diff

View File

@@ -294,14 +294,6 @@
icon="Download"
@click="handleExport()">名单导出
</el-button>
<!-- <el-button
class="ml10"
type="primary"
plain
icon="UploadFilled"
v-auth="'recruit_send_img'"
@click="handleSendImg()">图片同步
</el-button> -->
</div>
</el-row>
@@ -637,23 +629,17 @@
<!-- 支付二维码弹窗 -->
<PayQrcodeDialog ref="payQrcodeDialogRef" @refresh="getDataList"></PayQrcodeDialog>
<!-- 延迟缴费弹窗 -->
<DelayPayTimeDialog ref="delayPayTimeDialogRef" @refresh="getDataList"></DelayPayTimeDialog>
<!-- 录取通知书弹窗 -->
<AdmissionNoticeDialog ref="admissionNoticeDialogRef" @refresh="getDataList"></AdmissionNoticeDialog>
<DormFW ref="dormFWRef"></DormFW>
<ShowMap ref="baiduMapRef"></ShowMap>
<InterviewForm ref="interviewFormRef" @refresh="getDataList"></InterviewForm>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="recruitstudentsignup">
import { ref, reactive, onMounted, nextTick, defineAsyncComponent, watch } from 'vue'
import { Edit, Check, DocumentChecked, Close, Switch, Tickets, Document, Upload, Warning, User, CircleCheck, CircleClose, Clock, WarningFilled } from '@element-plus/icons-vue'
import { Edit, Check, DocumentChecked, Close, Switch, Tickets, Document, Warning, User, CircleCheck } from '@element-plus/icons-vue'
import ClickableTag from '/@/components/ClickableTag/index.vue'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { BasicTableProps, useTable } from '/@/hooks/table'
@@ -661,27 +647,20 @@ import { auth } from '/@/utils/authFunction'
import axios from 'axios'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
import {
delObj,
exportZip,
fetchList,
leaveSchool,
rePush as rePushApi,
yjOut,
oneClass,
oneStuNo,
tbStuWork,
sendImg,
pushCity as pushCityApi
pushCity as pushCityApi,
resetSign as resetSignApi
} from '/@/api/recruit/recruitstudentsignup'
import { getLabelValue, getLabelValueByProps, getMajorLabelWithYears } from '/@/utils/dictLabel'
import { getLabelValueByProps, getMajorLabelWithYears } from '/@/utils/dictLabel'
import { getDeptList } from "/@/api/basic/basicclass";
import { listPlanByCondition as planMajor } from "/@/api/recruit/recruitstudentplan";
import { getDictsByTypes } from "/@/api/admin/dict";
import { getUserListByRole } from "/@/api/admin/user";
import { queryTeacherBaseByNo } from "/@/api/professional/professionaluser/teacherbase";
import { useDict } from '/@/hooks/dict'
import {
ROLE_CODE,
PUSHED_STATUS_LIST,
NOTICE_SEND_STATUS_LIST,
RECRUIT_MATERIAL_STATUS_LIST,
@@ -697,11 +676,8 @@ import { showLoading, hideLoading } from '/@/api/asset/loading'
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'))
const MajorChange = defineAsyncComponent(() => import('./majorChange.vue'))
const Update = defineAsyncComponent(() => import('./update.vue'))
const DormFW = defineAsyncComponent(() => import('./dormFW.vue'))
const ShowMap = defineAsyncComponent(() => import('./showMap.vue'))
const InterviewForm = defineAsyncComponent(() => import('/@/views/recruit/recruitstudentsignup/interviewForm.vue'))
const PayQrcodeDialog = defineAsyncComponent(() => import('./PayQrcodeDialog.vue'))
const DelayPayTimeDialog = defineAsyncComponent(() => import('./DelayPayTimeDialog.vue'))
const AdmissionNoticeDialog = defineAsyncComponent(() => import('./AdmissionNoticeDialog.vue'))
const ActionDropdown = defineAsyncComponent(() => import('/@/components/tools/action-dropdown.vue'))
@@ -715,11 +691,8 @@ const searchFormRef = ref()
const addOrUpdateRef = ref()
const majorChangeRef = ref()
const updateRef = ref()
const dormFWRef = ref()
const baiduMapRef = ref()
const interviewFormRef = ref()
const payQrcodeDialogRef = ref()
const delayPayTimeDialogRef = ref()
const admissionNoticeDialogRef = ref()
// 搜索表单显示状态
@@ -728,7 +701,7 @@ const showSearch = ref(true)
// 表单数据
const dataForm = reactive({
zlsh: '',
groupId: '',
groupId: '',
deptCode: '',
confirmedMajor: '',
degreeOfEducation: '',
@@ -739,13 +712,13 @@ const dataForm = reactive({
isNewCity: '',
startDate: '',
endDate: '',
lqStartDate: '',
lqEndDate: '',
lqStartDate: '',
lqEndDate: '',
paystatus: '',
graPic: '',
pushed: '',
wishMajorOne: '',
isTb: '',
isTb: '',
cityExamType: '',
interview: '',
type: '',
@@ -775,7 +748,6 @@ const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTa
// 弹窗状态(已移除,组件内部通过 v-model="visible" 控制)
// 列表数据
const auditorList = ref<any[]>([])
const planList = ref<any[]>([])
const eduList = ref<any[]>([])
const planMajorList = ref<any[]>([])
@@ -836,60 +808,15 @@ const remoteTeacherByQuery = (query: string) => {
teacherList.value = []
})
}, 200)
}
}
// 发送图片
const handleSendImg = () => {
messageBox.confirm('是否确认同步招生图片到市平台?请谨慎操作').then(() => {
return sendImg()
}).then(() => {
message.success('同步图片请求已发起,请耐心等待')
}).catch(() => {
hideLoading()
})
}
// 同步学工
const handleTbStuWork = () => {
if (dataForm.groupId == '') {
message.warning('招生计划不能为空')
return
}
messageBox.confirm('是否确认同步学工?请谨慎操作').then(() => {
showLoading()
return tbStuWork(dataForm)
}).then(() => {
hideLoading()
message.success('同步完成')
}).catch(() => {
hideLoading()
})
}
// 一键分配班级和学号
const handleOneClassAndStuNo = () => {
if (dataForm.groupId == '') {
message.warning('招生计划不能为空')
return
}
messageBox.confirm('是否确认一键分配班级和学号?请谨慎操作').then(() => {
showLoading()
return Promise.all([oneClass(dataForm), oneStuNo(dataForm)])
}).then(() => {
hideLoading()
message.success('分配完成')
}).catch(() => {
hideLoading()
})
}
}
// 下载zip
const downZip = () => {
if (dataForm.groupId == '') {
message.warning('招生计划不能为空')
return
}
return
}
showLoading()
exportZip(dataForm).then(res => {
hideLoading()
@@ -907,45 +834,6 @@ const downZip = () => {
})
}
// 百度地图
const baiduMap = (row: any) => {
// 组件内部通过 v-model="visible" 控制显示
nextTick(() => {
baiduMapRef.value?.init(row)
})
}
// 设置宿舍
const setDormFW = () => {
// 组件内部通过 v-model="visible" 控制显示
nextTick(() => {
dormFWRef.value?.init()
})
}
// 一键判断是否超出住宿范围
const handleYjOut = () => {
if (dataForm.groupId == '') {
message.warning('招生计划不能为空')
return
}
messageBox.confirm('是否确认一键判断是否超出住宿范围?请谨慎操作').then(() => {
return yjOut({ groupId: dataForm.groupId })
}).then(() => {
message.success('操作成功')
getDataList()
})
}
// 导出审核
const handleExportAudit = (type: number) => {
if (dataForm.groupId == '') {
message.warning('招生计划不能为空')
return
}
downFile(type)
}
// 导出Excel
const exportExcel = (form: any, url: string) => {
return axios({
@@ -959,23 +847,6 @@ const exportExcel = (form: any, url: string) => {
})
}
// 导出文件
const downFile = (type: number) => {
dataForm.type = String(type)
exportExcel(dataForm, '/recruit/recruitstudentsignup/exportExcel').then(res => {
const blob = new Blob([res.data])
const fileName = type == 1 ? '延迟缴费名单导出.xlsx' : '超时缴费名单导出.xlsx'
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
}).catch(() => {})
}
// 导出
const handleExport = () => {
if (dataForm.groupId == '') {
@@ -994,11 +865,6 @@ const handleExport = () => {
document.body.removeChild(elink)
}).catch(() => {})
}
// 去重
const unique = (arr: any[]) => {
const rese = new Map()
return arr.filter((item) => !rese.has(item.username) && rese.set(item.username, 1))
}
// 切换专业
const chanMajor = () => {
@@ -1016,8 +882,8 @@ const handleFilter = () => {
}
// 获取数据列表
const handleAddData=()=>{
addOrUpdateRef.value?.init(null, 1)
const handleAddData = ()=>{
addOrUpdateRef.value?.init(null, 1, dataForm.groupId)
}
// 新增 / 修改
@@ -1039,7 +905,7 @@ const edit = (id: string) => {
const majorChange = (id: string) => {
nextTick(() => {
majorChangeRef.value?.init(id)
})
})
}
// 退学
@@ -1049,17 +915,17 @@ const handleUpdate = (id: string, groupId: string, feeAgency: string) => {
}).then(() => {
message.success('操作成功')
getDataList()
})
})
}
// 删除
const deleteHandle = (id: string) => {
messageBox.confirm('是否确认删除本条数据?请谨慎操作').then(() => {
return delObj(id)
// 复学
const reEntry = (id: string) => {
messageBox.confirm('是否确认复学操作?请谨慎操作').then(() => {
return resetSignApi({ id })
}).then(() => {
message.success('删除成功')
message.success('操作成功')
getDataList()
})
})
}
// 重置表单
@@ -1093,18 +959,11 @@ const handlePushCity = (id: string) => {
// 重新推送
const handleRePush = (row: any) => {
// messageBox.confirm('是否确认重新推送本条数据?请谨慎操作').then(() => {
// return rePushApi({ id: row.id })
// }).then(() => {
// message.success('推送成功')
// getDataList()
// })
}
// 延迟缴费
const delayPayTimeSet = (row: any) => {
nextTick(() => {
delayPayTimeDialogRef.value?.init(row)
messageBox.confirm('是否确认重新推送本条数据?请谨慎操作').then(() => {
return rePushApi({ id: row.id })
}).then(() => {
message.success('推送成功')
getDataList()
})
}
@@ -1149,6 +1008,13 @@ const getActionMenuItems = (row: any) => {
icon: Close,
visible: () => auth('recruit_recruitstudentsignup_leaveSchool') && row.canQuit
},
// 复学
{
command: 'reEntry',
label: '复学',
icon: Check,
visible: () => auth('recruit_resetsign') && row.canReset
},
{
command: 'majorChange',
label: '调整专业',
@@ -1203,6 +1069,9 @@ const handleMoreCommand = (command: string, row: any) => {
case 'leaveSchool':
handleUpdate(row.id, row.groupId, row.feeAgency)
break
case 'reEntry':
reEntry(row.id)
break
case 'majorChange':
majorChange(row.id)
break
@@ -1252,18 +1121,6 @@ const init = async () => {
isOutList.value = res.data.recruit_data_source || []
})
// 所有经办人
getUserListByRole(ROLE_CODE.ROLE_RECRUIT_SECOND).then((res: any) => {
auditorList.value = res.data || []
getUserListByRole(ROLE_CODE.ROLE_RECRUIT).then((re: any) => {
if (re.data) {
re.data.forEach((r: any) => {
auditorList.value.push(r)
})
}
auditorList.value = unique(auditorList.value)
})
})
}
// 监听招生计划变化
@@ -1293,14 +1150,6 @@ onMounted(() => {
.major-icon {
color: var(--el-color-primary);
font-size: 14px;
flex-shrink: 0;
}
.major-text {
color: #303133;
font-weight: 500;
font-size: 13px;
}
}
@@ -1309,14 +1158,6 @@ onMounted(() => {
font-size: 13px;
}
// 面试结果样式
.interview-cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
// 面试详情弹窗样式
.interview-detail-popover {
.detail-title {
@@ -1543,89 +1384,4 @@ onMounted(() => {
}
}
}
// 旧样式保留(如果其他地方还在使用)
.material-check-cell {
display: flex;
flex-direction: column;
gap: 8px;
padding: 4px 0;
.check-item {
display: flex;
align-items: flex-start;
gap: 6px;
line-height: 1.5;
.check-label {
color: #606266;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
:deep(.el-tag) {
display: inline-flex;
align-items: center;
vertical-align: middle;
gap: 4px;
line-height: 1;
.tag-icon {
font-size: 12px;
vertical-align: middle;
}
}
}
.material-status {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: 6px;
align-items: center;
.material-tag {
margin: 0;
flex-shrink: 0;
}
.no-upload-text {
color: #67c23a;
font-size: 12px;
white-space: nowrap;
}
}
.check-remark {
margin-top: 4px;
.remark-content {
display: flex;
align-items: flex-start;
gap: 4px;
flex: 1;
.remark-icon {
color: #f56c6c;
font-size: 14px;
flex-shrink: 0;
margin-top: 2px;
}
.remark-text {
color: #f56c6c;
font-size: 12px;
line-height: 1.5;
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: top;
}
}
}
}
</style>

View File

@@ -311,7 +311,7 @@
header-align="center"
align="center"
width="100"
label="是否同步">
label="是否同步">
<template #default="scope">
<el-tag :type="getYesNoType(scope.row.isTb)?.type">
{{ getYesNoType(scope.row.isTb)?.label }}
@@ -376,7 +376,7 @@
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="changeClassVisible=false"> </el-button>
<el-button @click="changeClassVisible=false">取消</el-button>
<el-button @click="changeClassInfoHandle" type="primary">保存</el-button>
</div>
</template>
@@ -387,8 +387,6 @@
<script setup lang="ts">
import { ref, reactive, nextTick, onMounted, defineAsyncComponent } from 'vue'
import { Search, ZoomIn, Edit } from '@element-plus/icons-vue'
import { ElNotification } from 'element-plus'
import { useMessage } from '/@/hooks/message'
import { useMessageBox } from '/@/hooks/message'
import { BasicTableProps, useTable } from '/@/hooks/table'
@@ -400,14 +398,14 @@ import {
oneClass,
oneStuNo,
changeClassInfo,
getMajorClass
getMajorClass,
queryAllRecruitUser
} from '/@/api/recruit/recruitstudentsignup'
import { getLabelValueByProps } from '/@/utils/dictLabel'
import { getClassListByRole, getDeptList } from "/@/api/basic/basicclass"
import {listPlanByCondition as planMajor} from "/@/api/recruit/recruitstudentplan"
import { getDictsByTypes } from "/@/api/admin/dict"
import { getUserListByRole } from "/@/api/admin/user"
import { ROLE_CODE, PUSHED_STATUS_LIST, PAY_STATUS_LIST, getStatusConfig } from "/@/config/global"
import { PUSHED_STATUS_LIST, PAY_STATUS_LIST, getStatusConfig } from "/@/config/global"
import { showLoading, hideLoading } from '/@/api/asset/loading'
import { useDict } from '/@/hooks/dict'
@@ -642,22 +640,11 @@ const init = () => {
})
// 所有经办人
getUserListByRole(ROLE_CODE.ROLE_RECRUIT_SECOND).then((res: any) => {
auditorList.value = res.data
getUserListByRole(ROLE_CODE.ROLE_RECRUIT).then((re: any) => {
re.data.forEach((r: any) => {
auditorList.value.push(r)
})
auditorList.value = unique(auditorList.value)
})
queryAllRecruitUser().then((res: any) => {
auditorList.value = res.data || []
})
}
const unique = (arr: any[]) => {
const rese = new Map()
return arr.filter((item) => !rese.has(item.username) && rese.set(item.username, 1))
}
const chanMajor = () => {
planMajorList.value = []
planMajor({ groupId: dataForm.groupId }).then((data: any) => {

View File

@@ -1,30 +1,38 @@
<template>
<el-dialog v-model="visible" width="60%" :title="`面试审核(${row.name})`">
<el-row>
<el-radio v-model="status" label="1">通过</el-radio>
<el-radio v-model="status" label="-1">未通过</el-radio>
</el-row>
<el-row v-if="status == '-1'">
<br />
<el-input type="textarea" v-model="reason" placeholder="请输入未通过的原因"></el-input>
</el-row>
<el-dialog v-model="visible" width="600" :title="`面试审核(${row.name})`">
<el-form ref="dataFormRef" :model="dataForm" :rules="dataRule" label-width="120px">
<el-form-item label="面试结果" prop="interview">
<el-radio-group v-model="dataForm.interview" @change="handleInterviewChange">
<el-radio :label="item.value" v-for="item in interviewDicList" :key="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="未通过原因" prop="interviewReason" v-if="dataForm.interview == '-1'">
<el-input
type="textarea"
v-model="dataForm.interviewReason"
placeholder="请输入未通过的原因"
:rows="4">
</el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="confirm"><span>确认</span></el-button>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="confirm">确认</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
// @ts-ignore
import global from "@/components/tools/commondict.vue"
import { interview } from "@/api/recruit/recruitstudentsignup"
import { interview } from "/@/api/recruit/recruitstudentsignup"
import { INTERVIEW_DIC_LIST } from '/@/config/global'
import type { FormInstance } from 'element-plus'
const interviewDicList = INTERVIEW_DIC_LIST.filter((item) => item.value != '0')
// 消息提示 hooks
const message = useMessage()
@@ -36,31 +44,60 @@ const emit = defineEmits<{
// 响应式数据
const visible = ref(false)
const row = reactive<any>({})
const status = ref('1')
const reason = ref('')
const dataFormRef = ref<FormInstance>()
const dataForm = reactive({
interview: '1',
interviewReason: ''
})
// 表单验证规则
const dataRule = reactive({
interview: [
{ required: true, message: '请选择面试结果', trigger: 'change' }
],
interviewReason: [] as any[]
})
// 初始化
const init = (rowData: any) => {
visible.value = true
Object.assign(row, rowData)
status.value = rowData.interview || '1'
reason.value = rowData.interviewReason || ''
dataForm.interview = rowData.interview || '1'
dataForm.interviewReason = rowData.interviewReason || ''
// 重置表单验证状态
nextTick(() => {
dataFormRef.value?.clearValidate()
})
}
// 面试结果改变
const handleInterviewChange = () => {
dataForm.interviewReason = ''
if(dataForm.interview == '-1'){
dataRule.interviewReason = [
{ required: true, message: '请输入未通过的原因', trigger: 'blur' }
]
} else {
dataRule.interviewReason = []
}
}
// 确认
const confirm = () => {
if (!status.value || (status.value == '-1' && !reason.value)) {
message.warning('请选择通过还是未通过,未通过请输入原因')
return
} else {
interview({ "id": row.id, "interview": status.value, "interviewReason": reason.value }).then(() => {
dataFormRef.value?.validate((valid: boolean) => {
if (!valid) {
return
}
interview({
"id": row.id,
"interview": dataForm.interview,
"interviewReason": dataForm.interviewReason
}).then(() => {
message.success('操作成功')
visible.value = false
emit('refresh')
}).catch(() => {
message.error('操作失败')
})
}
})
}
// 暴露方法给父组件
@@ -70,7 +107,5 @@ defineExpose({
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
</style>

View File

@@ -25,14 +25,14 @@
<el-row>
<el-col :span="24">
<el-form-item label="姓名" prop="name">
<el-input type="text" v-model="dataForm.name" :disabled="type != 1"></el-input>
<el-input type="text" v-model="dataForm.name" disabled></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="身份证号" prop="idNumber">
<el-input type="text" v-model="dataForm.idNumber" :disabled="type != 2"></el-input>
<el-input type="text" v-model="dataForm.idNumber" disabled></el-input>
</el-form-item>
</el-col>
</el-row>
@@ -46,7 +46,7 @@
<el-row>
<el-col :span="24">
<el-form-item label="原录取专业" prop="confirmedMajor">
<el-select v-model="dataForm.confirmedMajor" filterable clearable placeholder="" :disabled="type != 1" @change="changeM(dataForm.confirmedMajor)">
<el-select v-model="dataForm.confirmedMajor" filterable clearable placeholder="" disabled>
<el-option
v-for="item in planMajorList"
:key="item.majorCode"
@@ -75,26 +75,26 @@
<el-row>
<el-col :span="12">
<el-form-item label="学费" prop="feeTuition">
<el-input-number v-model="dataForm.feeTuition" controls-position="right" :min="0" :max="999999" :step-strictly="true" :disabled="type == 2"></el-input-number>
<el-input-number v-model="dataForm.feeTuition" controls-position="right" :min="0" :max="999999" :step-strictly="true" disabled></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="代办费" prop="feeAgency">
<el-input-number v-model="dataForm.feeAgency" controls-position="right" :min="0" :max="999999" :step-strictly="true" :disabled="type == 2"></el-input-number>
<el-input-number v-model="dataForm.feeAgency" controls-position="right" :min="0" :max="999999" :step-strictly="true" disabled></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="总费用" prop="allMoney">
<span style="color: red">{{ dataForm.feeTuition + dataForm.feeAgency }}</span>
<span style="color: red">{{ Number(dataForm.feeTuition) + Number(dataForm.feeAgency) }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="审核备注" prop="auditRemarks">
<el-input type="textarea" v-model="dataForm.auditRemarks" placeholder="审核备注" :rows="2"></el-input>
<el-form-item label="备注" prop="auditRemarks">
<el-input type="textarea" v-model="dataForm.auditRemarks" placeholder="备注" :rows="2"></el-input>
</el-form-item>
</el-col>
</el-row>
@@ -110,17 +110,18 @@
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { ElNotification } from 'element-plus'
import { useMessageBox } from '/@/hooks/message'
import { useMessageBox, useMessage } from '/@/hooks/message'
import { getObj, changeMajor } from '/@/api/recruit/recruitstudentsignup'
import { getList } from "/@/api/recruit/recruitstudentplangroup"
import { listByEdu } from "/@/api/recruit/recruitstudentplan"
import { getDicts } from "/@/api/admin/dict"
import { list as scoreList } from "/@/api/recruit/recruitstudentplancorrectscoreconfig"
import { getStatusConfig, AUDIT_STATUS_LIST } from '/@/config/global'
const auditStatusList = AUDIT_STATUS_LIST
// 消息提示 hooks
const messageBox = useMessageBox()
const message = useMessage()
// Emits
const emit = defineEmits<{
(e: 'refreshDataList'): void
@@ -133,11 +134,9 @@ const dataFormRef = ref()
const visible = ref(false)
const canSubmit = ref(false)
const title = ref("")
const type = ref<number | null>(null)
const planList = ref<any[]>([])
const planMajorList = ref<any[]>([])
const agencyFeeList = ref<any[]>([])
const tuitionFeeList = ref<any[]>([])
const schoolCodeList = ref<any[]>([])
const dataForm = reactive({
@@ -210,7 +209,7 @@ const dataForm = reactive({
feeAgency: 0
})
const dataRule = {
const dataRule = reactive({
groupId: [
{ required: true, message: '招生计划不能为空', trigger: 'change' }
],
@@ -229,7 +228,7 @@ const dataRule = {
newConfirmedMajor: [
{ required: true, message: '新录取专业不能为空', trigger: 'change' }
]
}
})
// 初始化数据
const initData = () => {
@@ -238,56 +237,45 @@ const initData = () => {
})
}
// 改变新专业
const changeCM = (id: string) => {
if (id) {
let flag = false
planMajorList.value.forEach((e: any) => {
if (dataForm.newConfirmedMajor == e.majorCode && e.isZd == "1" && String(dataForm.degreeOfEducation) == "1") {
flag = true
}
})
if (String(dataForm.degreeOfEducation) == "1") {
dataForm.feeTuition = 0
tuitionFeeList.value.forEach((e: any) => {
if (e.label == "0" && flag) {
dataForm.feeTuition = e.value
}
})
}
// 根据新专业和文化程度更新学费
const updateTuitionByNewMajorAndEducation = () => {
if (!dataForm.newConfirmedMajor || !dataForm.degreeOfEducation) {
return
}
// 查找选中的新专业
const selectedMajor = planMajorList.value.find((major: any) => major.majorCode === dataForm.newConfirmedMajor)
if (!selectedMajor) {
return
}
// 根据文化程度选择对应的学费字段
// '1' = 初中 -> czFee
// '2' = 高中 -> gzFee
// '3' = 技职校 -> jzxFee
if (dataForm.degreeOfEducation === '1' && selectedMajor.czFee !== undefined && selectedMajor.czFee !== null) {
dataForm.feeTuition = selectedMajor.czFee
} else if (dataForm.degreeOfEducation === '2' && selectedMajor.gzFee !== undefined && selectedMajor.gzFee !== null) {
dataForm.feeTuition = selectedMajor.gzFee
} else if (dataForm.degreeOfEducation === '3' && selectedMajor.jzxFee !== undefined && selectedMajor.jzxFee !== null) {
dataForm.feeTuition = selectedMajor.jzxFee
}
}
// 改变专业
const changeM = (id: string) => {
// 改变专业
const changeCM = (id: string) => {
if (id) {
dataForm.confirmedMajor = id
// 是初中生并且是中德班
let flag = false
planMajorList.value.forEach((e: any) => {
if (dataForm.confirmedMajor == e.majorCode && e.isZd == "1" && String(dataForm.degreeOfEducation) == "1") {
flag = true
}
})
if (String(dataForm.degreeOfEducation) == "1") {
dataForm.feeTuition = 0
tuitionFeeList.value.forEach((e: any) => {
if (e.label == "0" && flag) {
dataForm.feeTuition = e.value
}
})
}
// 从新专业中获取学费
updateTuitionByNewMajorAndEducation()
}
}
// 表单提交
const dataFormSubmit = async () => {
const titleText = "确认调整录取专业么?"
if (dataForm.confirmedMajor == dataForm.newConfirmedMajor) {
ElNotification.error({
title: '错误',
message: '新专业不能和原专业相同'
})
message.error('新专业不能和原专业相同')
return
}
try {
@@ -297,10 +285,7 @@ const dataFormSubmit = async () => {
canSubmit.value = false
if (dataForm.id) {
changeMajor(dataForm).then(() => {
ElNotification.success({
title: '成功',
message: '操作成功'
})
message.success('操作成功')
visible.value = false
emit('refreshDataList')
}).catch(() => {
@@ -316,7 +301,7 @@ const dataFormSubmit = async () => {
// 初始化方法
const init = (id: string | null) => {
dataForm.id = id || null
dataForm.id = id || ""
visible.value = true
canSubmit.value = true
initData()
@@ -326,48 +311,43 @@ const init = (id: string | null) => {
// 获取数据字典代办费
getDicts('agency_fee').then((res: any) => {
agencyFeeList.value = res.data
// 获取数据字典学费
getDicts('tuition_fee').then((res: any) => {
tuitionFeeList.value = res.data
getObj(dataForm.id).then((response: any) => {
Object.assign(dataForm, response.data)
title.value = dataForm.serialNumber
// 获取文化程度对应的专业
planMajorList.value = []
getObj(dataForm.id).then((response: any) => {
Object.assign(dataForm, response.data)
title.value = dataForm.serialNumber
// 获取文化程度对应的专业
planMajorList.value = []
agencyFeeList.value.forEach((e: any) => {
if (String(dataForm.degreeOfEducation) == String(e.label)) {
dataForm.feeAgency = e.value
}
})
tuitionFeeList.value.forEach((e: any) => {
if (String(dataForm.degreeOfEducation) == String(e.label) && (String(dataForm.degreeOfEducation) != "1")) {
dataForm.feeTuition = e.value
}
})
listByEdu({ groupId: dataForm.groupId, degreeOfEducation: dataForm.degreeOfEducation }).then((e: any) => {
planMajorList.value = e.data
})
agencyFeeList.value.forEach((e: any) => {
if (String(dataForm.degreeOfEducation) == String(e.label)) {
dataForm.feeAgency = e.value
}
})
listByEdu({ groupId: dataForm.groupId, degreeOfEducation: dataForm.degreeOfEducation }).then((e: any) => {
planMajorList.value = e.data
// 加载专业列表后,如果已有新专业,更新学费
if (dataForm.newConfirmedMajor) {
updateTuitionByNewMajorAndEducation()
}
// 获取招生计划下的学校和分数线
scoreList({ groupId: dataForm.groupId }).then((data: any) => {
schoolCodeList.value = data.data
})
if ("1" == dataForm.degreeOfEducation) {
title.value = "C" + title.value
} else if ("2" == dataForm.degreeOfEducation) {
title.value = "G" + title.value
} else if ("3" == dataForm.degreeOfEducation) {
title.value = "J" + title.value
const educationPrefixMap: Record<string, string> = {
'1': 'C', // 初中
'2': 'G', // 高中
'3': 'J' // 技职校
}
const prefix = educationPrefixMap[String(dataForm.degreeOfEducation)]
if (prefix) {
title.value = prefix + title.value
}
// 从字典数据获取录取状态标签
const auditStatusConfig = getStatusConfig(auditStatusList, dataForm.auditStatus)
if (auditStatusConfig && auditStatusConfig.label) {
title.value = auditStatusConfig.label + " " + title.value
}
if ("-20" == dataForm.auditStatus) {
title.value = "未录取 " + title.value
} else if ("0" == dataForm.auditStatus) {
title.value = "待审核 " + title.value
} else if ("20" == dataForm.auditStatus) {
title.value = "已录取 " + title.value
}
})
})
})

View File

@@ -4,11 +4,11 @@
:close-on-click-modal="false"
v-model="visible"
append-to-body
width="900px">
<el-form :model="dataForm" :rules="dataRule" ref="dataFormRef" @keyup.enter="dataFormSubmit"
width="70%">
<el-form :model="dataForm" :rules="dataRule" ref="dataFormRef" @keyup.enter="() => dataFormSubmit('1')"
label-width="100px">
<el-row>
<el-col :span="24">
<el-col :span="8">
<el-form-item label="招生计划" prop="groupId">
<el-select v-model="dataForm.groupId" filterable :disabled="!!dataForm.id"
placeholder="请选择招生计划">
@@ -21,14 +21,12 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-col :span="8">
<el-form-item label="姓名" prop="name">
<el-input type="text" v-model="dataForm.name" :disabled="type != 1"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="8">
<el-form-item label="联系人" prop="contactName">
<el-select v-model="dataForm.contactName" filterable clearable placeholder="" :disabled="!(permissions.recruit_recruitprestudent_dj_sure || dataForm.auditStatus != '20')">
<el-option
@@ -42,89 +40,91 @@
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="成绩单" prop="scorePhoto">
<el-upload
action="/recruit/file/uploadAttachment"
list-type="picture-card"
:action="uploadUrl"
class="avatar-uploader"
name="file"
:headers="headers"
:limit="1"
:data="uploadData"
:file-list="fileList"
:show-file-list="false"
:before-upload="beforeUpload"
:on-preview="handlePictureCardPreview"
:on-remove="removeHandler"
:http-request="httpRequest"
:on-success="uploadSuccess">
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div v-if="dataForm.scorePhoto" class="avatar-wrapper">
<img :src="baseUrl + dataForm.scorePhoto" class="avatar"/>
</div>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="毕业证" prop="graPic">
<el-upload
action="/recruit/file/uploadAttachment"
list-type="picture-card"
:action="uploadUrl"
class="avatar-uploader"
name="file"
:headers="headers"
:limit="1"
:data="uploadData"
:file-list="graPicList"
:show-file-list="false"
:before-upload="beforeUpload"
:on-preview="handlePictureCardPreview"
:on-remove="remove2Handler"
:http-request="httpRequest"
:on-success="upload2Success">
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div v-if="dataForm.graPic" class="avatar-wrapper">
<img :src="baseUrl + dataForm.graPic" class="avatar"/>
</div>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="在常营业执照" prop="yyPic">
<el-upload
action="/recruit/file/uploadAttachment"
list-type="picture-card"
:action="uploadUrl"
class="avatar-uploader"
name="file"
:headers="headers"
:limit="1"
:data="uploadData"
:file-list="yyPicList"
:show-file-list="false"
:before-upload="beforeUpload"
:on-preview="handlePictureCardPreview"
:on-remove="remove3Handler"
:http-request="httpRequest"
:on-success="upload3Success">
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div v-if="dataForm.yyPic" class="avatar-wrapper">
<img :src="baseUrl + dataForm.yyPic" class="avatar"/>
</div>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="在常社保证明" prop="sbPic">
<el-upload
:action="uploadUrl"
class="avatar-uploader"
name="file"
:headers="headers"
:data="uploadData"
:show-file-list="false"
:before-upload="beforeUpload"
:http-request="httpRequest"
:on-success="upload5Success">
<div v-if="dataForm.sbPic" class="avatar-wrapper">
<img :src="baseUrl + dataForm.sbPic" class="avatar"/>
</div>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="在常就业社保证明" prop="sbPic">
<el-upload
action="/recruit/file/uploadAttachment"
list-type="picture-card"
name="file"
:headers="headers"
:limit="1"
:data="uploadData"
:file-list="sbPicList"
:before-upload="beforeUpload"
:on-preview="handlePictureCardPreview"
:on-remove="remove5Handler"
:http-request="httpRequest"
:on-success="upload5Success">
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col >
<el-form-item label="在常租赁合同/房产证明" prop="housePic">
<el-upload
action="/recruit/file/uploadAttachment"
:action="uploadUrl"
list-type="picture-card"
name="file"
:headers="headers"
@@ -137,14 +137,17 @@
:http-request="httpRequest"
:on-success="upload4Success">
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<template #tip>
<div>最多上传5张</div>
</template>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col>
<el-form-item label="户口本" prop="householdPic">
<el-upload
action="/recruit/file/uploadAttachment"
:action="uploadUrl"
list-type="picture-card"
name="file"
:headers="headers"
@@ -157,6 +160,9 @@
:http-request="httpRequest"
:on-success="upload6Success">
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<template #tip>
<div>最多上传5张</div>
</template>
</el-upload>
</el-form-item>
</el-col>
@@ -173,8 +179,8 @@
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit('1')" v-auth="'recruit_recruitstudentsignup_edit'" v-if="canSubmit">保存</el-button>
<el-button type="success" plain @click="dataFormSubmit('2')" v-auth="'signup_material_exam'" v-if="canSubmit">通过</el-button>
<el-button type="danger" plain @click="dataFormSubmit('3')" v-auth="'signup_material_exam'" v-if="canSubmit">驳回</el-button>
<el-button type="success" icon="CircleCheck" @click="dataFormSubmit('2')" v-auth="'signup_material_exam'" v-if="canSubmit">通过</el-button>
<el-button type="danger" icon="CircleClose" @click="dataFormSubmit('3')" v-auth="'signup_material_exam'" v-if="canSubmit">驳回</el-button>
</div>
</template>
@@ -187,18 +193,25 @@
<script setup lang="ts">
import { ref, reactive, nextTick, computed, onMounted } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import { ElNotification } from 'element-plus'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage'
import axios from 'axios'
import { getObj, materialExam } from '/@/api/recruit/recruitstudentsignup'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
import { queryAllTeacher } from '/@/api/professional/professionaluser/teacherbase'
import { AUDIT_STATUS_LIST, getStatusConfig } from '/@/config/global'
const auditStatusList = AUDIT_STATUS_LIST
// 消息提示 hooks
const message = useMessage()
// 使用 Pinia store
const userInfoStore = useUserInfo()
const { userInfos } = storeToRefs(userInfoStore)
const baseUrl = import.meta.env.VITE_API_URL
const uploadUrl = baseUrl + '/recruit/file/uploadAttachment'
// 创建权限对象
const permissions = computed(() => {
@@ -234,20 +247,7 @@ const type = ref<number | null>(null)
const contactNameList = ref<any[]>([])
const planList = ref<any[]>([])
const form = reactive({
attachment: '',
graPic: "",
yyPic: "",
housePic: "",
sbPic: "",
hkPic: ""
})
const fileList = ref<any[]>([])
const graPicList = ref<any[]>([])
const yyPicList = ref<any[]>([])
const houseList = ref<any[]>([])
const sbPicList = ref<any[]>([])
const hkPicList = ref<any[]>([])
const fileReader = ref<FileReader | null>(null)
@@ -259,69 +259,18 @@ const dataForm = reactive({
zlshRemark: "",
groupId: "",
name: "",
oldName: "",
gender: "",
nationality: "",
degreeOfEducation: "",
isLeagueMember: "",
schoolOfGraduation: "",
isAccommodation: "",
examRegistrationNumbers: "",
isMinimumLivingSecurity: "",
score: "",
postcode: "",
residenceType: "",
correctedScore: "",
placeScore: "",
schoolFrom: "",
idNumber: "",
residenceProvince: "",
residenceCity: "",
residenceArea: "",
residenceDetail: "",
homeAddressProvince: "",
homeAddressCity: "",
homeAddressArea: "",
homeAddressDetail: "",
parentName: "",
parentTelOne: "",
parentTelTwo: "",
selfTel: "",
wishMajorOne: "",
wishMajorTwo: "",
wishMajorThree: "",
confirmedMajor: "",
sevenMajor: "",
sixMajor: "",
fiveMajor: "",
fourMajor: "",
threeMajor: "",
twoMajor: "",
feeContribute: 0,
contactName: "",
scorePhoto: "",
graPic: "",
yyPic: "",
housePic: "",
sbPic: "",
contactName: "",
oldSerialNumber: "",
colorDiscrimination: "",
nutrition: "",
height: "",
weight: "",
pastMedicalHistory: "",
eyesightLeft: "",
eyesightRight: "",
correctEyesightLeft: "",
correctEyesightRight: "",
remarks: "",
auditRemarks: "",
serialNumber: "",
auditStatus: "",
schoolCode: "",
newConfirmedMajor: "",
householdPic: "",
zlsh: ""
zlsh: "",
// 以下字段从后端获取,用于显示和判断
serialNumber: "",
degreeOfEducation: "",
auditStatus: ""
})
const dataRule = {
@@ -348,7 +297,7 @@ const dataRule = {
// 初始化 FileReader
onMounted(() => {
if (!window.FileReader) {
console.error('Your browser does not support FileReader API!')
message.error('您的浏览器不支持 FileReader API!')
} else {
fileReader.value = new FileReader()
}
@@ -358,10 +307,7 @@ onMounted(() => {
const beforeUpload = (file: File) => {
const isLt5M = file.size < 10 * 1024 * 1024
if (!isLt5M) {
ElNotification.error({
title: '错误',
message: '文件大小不能超过10M'
})
message.error('文件大小不能超过10M')
return false
}
return true
@@ -373,68 +319,28 @@ const handlePictureCardPreview = (file: any) => {
dialogUploadVisible.value = true
}
// 移除文件处理
const removeHandler = (file: any) => {
const index = fileList.value.findIndex((item: any) => item.url === file.url)
if (index > -1) {
fileList.value.splice(index, 1)
// 通用移除文件处理(多文件)
const handleRemoveMultiple = (fileList: any[], dataFormField: string) => {
return (file: any) => {
const arr: any[] = []
const strArr: string[] = []
const dataFormObj = dataForm as any
fileList.forEach((e: any) => {
if (e.url != file.url) {
arr.push(e)
// 保存时使用原始路径
strArr.push(e.originalUrl || (e.url.startsWith(baseUrl) ? e.url.substring(baseUrl.length) : e.url))
}
})
fileList.splice(0, fileList.length, ...arr)
dataFormObj[dataFormField] = strArr.join(",")
}
form.attachment = ""
dataForm.scorePhoto = ""
}
const remove2Handler = (file: any) => {
const index = graPicList.value.findIndex((item: any) => item.url === file.url)
if (index > -1) {
graPicList.value.splice(index, 1)
}
form.graPic = ""
dataForm.graPic = ""
}
const remove3Handler = (file: any) => {
const index = yyPicList.value.findIndex((item: any) => item.url === file.url)
if (index > -1) {
yyPicList.value.splice(index, 1)
}
form.yyPic = ""
dataForm.yyPic = ""
}
const remove4Handler = (file: any) => {
const arr: any[] = []
const strArr: string[] = []
houseList.value.forEach((e: any) => {
if (e.url != file.url) {
arr.push(e)
strArr.push(e.url)
}
})
houseList.value = arr
dataForm.housePic = strArr.join(",")
}
const remove5Handler = (file: any) => {
const index = sbPicList.value.findIndex((item: any) => item.url === file.url)
if (index > -1) {
sbPicList.value.splice(index, 1)
}
form.sbPic = ""
dataForm.sbPic = ""
}
const remove6Handler = (file: any) => {
const arr: any[] = []
const strArr: string[] = []
hkPicList.value.forEach((e: any) => {
if (e.url != file.url) {
arr.push(e)
strArr.push(e.url)
}
})
hkPicList.value = arr
dataForm.householdPic = strArr.join(",")
}
// 移除文件处理(多图上传)
const remove4Handler = handleRemoveMultiple(houseList.value, 'housePic')
const remove6Handler = handleRemoveMultiple(hkPicList.value, 'householdPic')
// 自定义上传请求
const httpRequest = (options: any) => {
@@ -444,8 +350,9 @@ const httpRequest = (options: any) => {
fileReader.value.onload = () => {
const base64Str = fileReader.value?.result as string
const config = {
url: '/recruit/file/uploadAttachment',
url: uploadUrl,
method: 'post',
headers: headers.value,
data: {
base64Str: base64Str.split(',')[1]
},
@@ -466,66 +373,39 @@ const httpRequest = (options: any) => {
}
}
// 通用上传成功回调(单文件 - avatar模式
const handleUploadSuccess = (dataFormField: string) => {
return (res: any) => {
const fileUrl = res.data.fileUrl
const dataFormObj = dataForm as any
dataFormObj[dataFormField] = fileUrl
}
}
// 通用上传成功回调(多文件)
const handleUploadSuccessMultiple = (fileList: any[], dataFormField: string) => {
return (res: any) => {
const fileUrl = res.data.fileUrl // 后端返回的原始路径
const dataFormObj = dataForm as any
// 添加到文件列表,显示时添加 baseUrl 前缀,同时保存原始路径用于提交
fileList.push({ url: baseUrl + fileUrl, name: '', originalUrl: fileUrl })
// 保存时使用原始路径
const arr: string[] = []
fileList.forEach((e: any) => {
// 优先使用保存的原始路径,如果没有则从 url 中提取
arr.push(e.originalUrl || (e.url.startsWith(baseUrl) ? e.url.substring(baseUrl.length) : e.url))
})
dataFormObj[dataFormField] = arr.join(",")
}
}
// 上传成功回调
const uploadSuccess = (res: any, file: any) => {
form.attachment = res.data.fileUrl
if (fileList.value.length > 0) {
fileList.value[0] = { url: form.attachment, name: "" }
} else {
fileList.value.push({ url: form.attachment, name: "" })
}
dataForm.scorePhoto = form.attachment
}
const upload2Success = (res: any, file: any) => {
form.graPic = res.data.fileUrl
if (graPicList.value.length > 0) {
graPicList.value[0] = { url: form.graPic, name: "" }
} else {
graPicList.value.push({ url: form.graPic, name: "" })
}
dataForm.graPic = form.graPic
}
const upload3Success = (res: any, file: any) => {
form.yyPic = res.data.fileUrl
if (yyPicList.value.length > 0) {
yyPicList.value[0] = { url: form.yyPic, name: "" }
} else {
yyPicList.value.push({ url: form.yyPic, name: "" })
}
dataForm.yyPic = form.yyPic
}
const upload4Success = (res: any, file: any) => {
form.housePic = res.data.fileUrl
houseList.value.push({ url: form.housePic })
const arr: string[] = []
houseList.value.forEach((e: any) => {
arr.push(e.url)
})
dataForm.housePic = arr.join(",")
}
const upload5Success = (res: any, file: any) => {
form.sbPic = res.data.fileUrl
if (sbPicList.value.length > 0) {
sbPicList.value[0] = { url: form.sbPic, name: "" }
} else {
sbPicList.value.push({ url: form.sbPic, name: "" })
}
dataForm.sbPic = form.sbPic
}
const upload6Success = (res: any, file: any) => {
form.hkPic = res.data.fileUrl
hkPicList.value.push({ url: form.hkPic })
const arr: string[] = []
hkPicList.value.forEach((e: any) => {
arr.push(e.url)
})
dataForm.householdPic = arr.join(",")
}
const uploadSuccess = handleUploadSuccess('scorePhoto')
const upload2Success = handleUploadSuccess('graPic')
const upload3Success = handleUploadSuccess('yyPic')
const upload4Success = handleUploadSuccessMultiple(houseList.value, 'housePic')
const upload5Success = handleUploadSuccess('sbPic')
const upload6Success = handleUploadSuccessMultiple(hkPicList.value, 'householdPic')
// 初始化数据
const initData = () => {
@@ -540,20 +420,14 @@ const initData = () => {
// 表单提交
const dataFormSubmit = (submitType: string) => {
if (dataForm.zlshRemark == '' && submitType == '3') {
ElNotification.error({
title: '错误',
message: '请填写驳回理由'
})
if ((dataForm.zlshRemark == '' || !dataForm.zlshRemark) && submitType == '3') {
message.error('请填写驳回理由')
return
}
dataForm.zlsh = submitType
canSubmit.value = false
materialExam(dataForm).then(() => {
ElNotification.success({
title: '成功',
message: '操作成功'
})
message.success('操作成功')
visible.value = false
emit('refreshDataList')
}).catch(() => {
@@ -563,7 +437,7 @@ const dataFormSubmit = (submitType: string) => {
// 初始化方法
const init = (id: string | null) => {
dataForm.id = id || null
dataForm.id = id || ""
visible.value = true
canSubmit.value = true
initData()
@@ -571,54 +445,44 @@ const init = (id: string | null) => {
dataFormRef.value?.resetFields()
if (dataForm.id) {
getObj(dataForm.id).then((response: any) => {
fileList.value = []
graPicList.value = []
yyPicList.value = []
// 清空多图上传的列表
houseList.value = []
sbPicList.value = []
hkPicList.value = []
Object.assign(dataForm, response.data)
title.value = dataForm.serialNumber
if (dataForm.scorePhoto != '') {
fileList.value.push({ url: dataForm.scorePhoto, name: "" })
}
if (dataForm.graPic != '') {
graPicList.value.push({ url: dataForm.graPic, name: "" })
}
if (dataForm.yyPic != '') {
yyPicList.value.push({ url: dataForm.yyPic, name: "" })
}
if (dataForm.housePic != '') {
const arr = dataForm.housePic.split(",")
// avatar 模式直接使用 dataForm 中的字段,不需要 fileList
// 多图上传需要初始化列表
if (dataForm.housePic && dataForm.housePic != '') {
const arr = dataForm.housePic.split(",").filter((item: string) => item && item.trim())
arr.forEach((e: string) => {
houseList.value.push({ url: e })
// 保存原始路径,显示时添加 baseUrl 前缀
houseList.value.push({ url: baseUrl + e, name: '', originalUrl: e })
})
}
if (dataForm.sbPic != '') {
sbPicList.value.push({ url: dataForm.sbPic, name: "" })
}
if (dataForm.householdPic != '') {
const arr2 = dataForm.householdPic.split(",")
if (dataForm.householdPic && dataForm.householdPic != '') {
const arr2 = dataForm.householdPic.split(",").filter((item: string) => item && item.trim())
arr2.forEach((e: string) => {
hkPicList.value.push({ url: e })
// 保存原始路径,显示时添加 baseUrl 前缀
hkPicList.value.push({ url: baseUrl + e, name: '', originalUrl: e })
})
}
if ("1" == String(dataForm.degreeOfEducation)) {
title.value = "C" + title.value
} else if ("2" == String(dataForm.degreeOfEducation)) {
title.value = "G" + title.value
} else if ("3" == String(dataForm.degreeOfEducation)) {
title.value = "J" + title.value
const educationPrefixMap: Record<string, string> = {
'1': 'C', // 初中
'2': 'G', // 高中
'3': 'J' // 技职校
}
const prefix = educationPrefixMap[String(dataForm.degreeOfEducation)]
if (prefix) {
title.value = prefix + title.value
}
contactNameflag.value = false
if ("-20" == String(dataForm.auditStatus)) {
title.value = "未录取 " + title.value
} else if ("0" == String(dataForm.auditStatus)) {
title.value = "待审核 " + title.value
} else if ("20" == String(dataForm.auditStatus)) {
title.value = "已录取 " + title.value
contactNameflag.value = true
const auditStatusConfig = getStatusConfig(auditStatusList, dataForm.auditStatus)
if (auditStatusConfig) {
title.value = auditStatusConfig.label + " " + title.value
if (auditStatusConfig.value == '20') {
contactNameflag.value = true
}
}
})
}
@@ -637,16 +501,42 @@ defineExpose({
margin-bottom:18px!important;
}
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
}
.dialog-footer {
text-align: right;
}
.avatar-uploader {
:deep(.el-upload) {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
width: 148px;
height: 148px;
&:hover {
border-color: var(--el-color-primary);
}
}
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 148px;
height: 148px;
line-height: 148px;
text-align: center;
}
.avatar-wrapper {
width: 148px;
height: 148px;
.avatar {
width: 148px;
height: 148px;
display: block;
object-fit: cover;
cursor: pointer;
}
}
</style>

View File

@@ -1,20 +1,3 @@
<!--
- Copyright (c) 2018-2025, cyweb All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- Neither the name of the pig4cloud.com developer nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-->
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
@@ -60,37 +43,120 @@
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="type" label="异动类型" align="center" show-overflow-tooltip>
<el-table-column prop="type" label="异动类型" width="100" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getTypeLabel(scope.row.type) }}
</template>
</el-table-column>
<el-table-column prop="name" label="姓名[唯一号]" width="140" align="center" show-overflow-tooltip />
<el-table-column prop="majorChangeInfo" label="专业变更情况" align="center" show-overflow-tooltip />
<el-table-column label="学费变更情况" align="center">
<el-table-column prop="dbName" label="费用类型" align="center" show-overflow-tooltip />
<el-table-column prop="dbOldValue" label="原费用" align="center" show-overflow-tooltip />
<el-table-column prop="dbNewValue" label="新费用" align="center" show-overflow-tooltip />
<el-table-column prop="dbType" label="变更类型" align="center" show-overflow-tooltip />
<el-table-column prop="dbChangeValue" label="变更金额" align="center" show-overflow-tooltip />
</el-table-column>
<el-table-column label="分数变更情况" align="center">
<el-table-column prop="scoreName" label="分数类型" align="center" show-overflow-tooltip />
<el-table-column prop="scoreOldValue" label="原分值" align="center" show-overflow-tooltip />
<el-table-column prop="scoreNewValue" label="新分值" align="center" show-overflow-tooltip />
</el-table-column>
<el-table-column prop="createBy" label="异动发起人" align="center" show-overflow-tooltip />
<el-table-column prop="createDate" label="异动时间" align="center" show-overflow-tooltip />
<el-table-column prop="remarks" label="备注信息" align="center" show-overflow-tooltip />
<el-table-column prop="isMajorChange" label="异动审核" width="110" align="center" show-overflow-tooltip>
<el-table-column prop="name" label="姓名[唯一号]" min-width="160" align="center" show-overflow-tooltip />
<el-table-column prop="majorChangeInfo" label="专业变更情况" min-width="180" align="center" show-overflow-tooltip>
<template #default="scope">
<AuditState :state="scope.row.isMajorChange" :options="auditStateOptions" />
<DetailPopover
v-if="scope.row.newMajorInfo || scope.row.oldMajorInfo"
title="专业变更详情"
:title-icon="InfoFilled"
:width="320"
:items="(() => {
const items = []
if (scope.row.oldMajorInfo) {
items.push({ label: '旧专业', content: scope.row.oldMajorInfo })
}
if (scope.row.newMajorInfo) {
items.push({ label: '新专业', content: scope.row.newMajorInfo, contentClass: 'new-major' })
}
return items
})()">
<template #reference>
<span class="major-change-link">
{{ scope.row.newMajorInfo || '查看详情' }}
<el-icon class="title-icon"><InfoFilled /></el-icon>
</span>
</template>
</DetailPopover>
<span v-else class="empty-text">-</span>
</template>
</el-table-column>
<el-table-column label="代办费变更情况" min-width="130" align="center" show-overflow-tooltip>
<template #default="scope">
<span class="new-fee">{{ scope.row.newAgencyFee }}</span> / <span class="old-fee">{{ scope.row.oldAgencyFee }}</span>
</template>
</el-table-column>
<el-table-column label="学费变更情况" min-width="140" align="center" show-overflow-tooltip>
<template #default="scope">
<span class="new-fee">{{ scope.row.newFeeTuition }}</span> / <span class="old-fee">{{ scope.row.oldFeeTuition }}</span>
</template>
</el-table-column>
<el-table-column label="分数变更情况" min-width="130" align="center">
<template #default="scope">
<span class="new-fee">{{ scope.row.newCorrectedScore }}</span> / <span class="old-fee">{{ scope.row.oldCorrectedScore }}</span>
</template>
</el-table-column>
<el-table-column prop="createBy" label="异动发起人" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="createTime" label="异动时间" min-width="180" align="center" show-overflow-tooltip />
<el-table-column prop="isMajorChange" label="异动审核" min-width="110" align="center" show-overflow-tooltip>
<template #default="scope">
<template v-if="getStatusConfig(auditStateOptions, scope.row.isMajorChange)">
<!-- 有备注信息时使用 popover 包裹 ClickableTag -->
<DetailPopover
v-if="scope.row.remarks"
title="异动审核详情"
:width="300"
:items="[
{
label: '审核状态',
layout: 'horizontal',
content: getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.label
},
{
label: '备注信息',
content: scope.row.remarks,
contentClass: 'reason-content'
}
]">
<template #reference>
<ClickableTag
width="80"
:type="getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.type || 'info'"
:left-icon="getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.icon">
{{ getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.label }}
</ClickableTag>
</template>
<template #content-0>
<ClickableTag
:type="getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.type || 'info'"
:left-icon="getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.icon"
:right-icon="null">
{{ getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.label }}
</ClickableTag>
</template>
<template #content-1>
<div class="reason-content">
<el-icon class="reason-icon"><Warning /></el-icon>
<span>{{ scope.row.remarks }}</span>
</div>
</template>
</DetailPopover>
<!-- 没有备注信息时直接显示 ClickableTag -->
<ClickableTag
v-else
width="80"
:type="getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.type || 'info'"
:left-icon="getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.icon"
:right-icon="null">
{{ getStatusConfig(auditStateOptions, scope.row.isMajorChange)?.label }}
</ClickableTag>
</template>
<!-- 无状态 -->
<span v-else class="empty-text">-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="permissions.recruit_recruitstudentsignupturnover_edit && scope.row.isMajorChange == '1'"
v-auth="'recruit_recruitstudentsignupturnover_edit'"
v-if="scope.row.isMajorChange == '1'"
type="primary"
link
icon="EditPen"
@@ -111,9 +177,9 @@
<!-- 异动审核弹窗 -->
<el-dialog v-model="majorChangeVisible" title="异动审核" width="600px">
<el-form :model="exarmForm" ref="exarmFormRef" label-width="80px" :rules="dataRule">
<el-form :model="exarmForm" ref="exarmFormRef" label-width="100px" :rules="dataRule">
<el-form-item label="审核结果" prop="isMajorChange">
<el-select v-model="exarmForm.isMajorChange" filterable clearable placeholder="请选择审核结果" style="width: 100%">
<el-select v-model="exarmForm.isMajorChange" filterable clearable placeholder="请选择审核结果">
<el-option
v-for="item in isMajorChangeList"
:key="item.value"
@@ -125,7 +191,7 @@
<el-form-item label="审核意见" prop="remarks">
<el-input
type="textarea"
placeholder="请输入审核内容"
placeholder="请输入审核意见"
:autosize="{ minRows: 2, maxRows: 4 }"
style="width: 100%"
v-model="exarmForm.remarks"
@@ -145,29 +211,16 @@
</template>
<script setup lang="ts" name="recruitstudentsignupturnover">
import { ref, reactive, computed, onMounted, defineAsyncComponent } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserInfo } from '/@/stores/userInfo'
import { ref, reactive, computed, onMounted } from 'vue'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { useMessage } from '/@/hooks/message'
import { fetchList, putObj } from '/@/api/recruit/recruitstudentsignupturnover'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
import type { StateOption } from '/@/components/AuditState/index.vue'
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
// 使用 Pinia store
const userInfoStore = useUserInfo()
const { userInfos } = storeToRefs(userInfoStore)
// 创建权限对象
const permissions = computed(() => {
const perms: Record<string, boolean> = {}
userInfos.value.authBtnList.forEach((perm: string) => {
perms[perm] = true
})
return perms
})
import ClickableTag from '/@/components/ClickableTag/index.vue'
import DetailPopover from '/@/components/DetailPopover/index.vue'
import { Warning, InfoFilled } from '@element-plus/icons-vue'
import { TURNOVER_AUDIT_STATUS_LIST, getStatusConfig } from '/@/config/global'
import { getDicts } from '/@/api/admin/dict'
// 消息提示 hooks
const message = useMessage()
@@ -182,14 +235,11 @@ const majorChangeVisible = ref(false)
// 数据
const planList = ref<any[]>([])
const typeList = ref([{ label: '专业变更', value: '1' }, { label: '退学', value: '2' }])
// 使用字典 recruit_change_type
const typeList = ref<any[]>([])
// 审核状态选项配置(用于 AuditState 组件和检索条件)
const auditStateOptions = ref<StateOption[]>([
{ value: '1', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' },
{ value: '2', label: '驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
{ value: '3', label: '通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' }
])
// 审核状态选项配置(用于 ClickableTag 组件和检索条件)
const auditStateOptions = ref<any[]>(TURNOVER_AUDIT_STATUS_LIST)
// 从 auditStateOptions 派生检索条件列表(只包含 label 和 value
const majorChangeList = computed(() => {
@@ -202,7 +252,7 @@ const majorChangeList = computed(() => {
// 审核弹窗中的选项(只包含通过和驳回)
const isMajorChangeList = computed(() => {
return auditStateOptions.value
.filter(item => item.value === '2' || item.value === '3')
.filter(item => item.value !== '1')
.map(item => ({
label: item.label,
value: item.value
@@ -234,7 +284,7 @@ const dataRule = {
// 获取异动类型标签
const getTypeLabel = (type: string) => {
const item = typeList.value.find(item => item.value === type)
const item = typeList.value.find((it: any) => String(it.value) === String(type))
return item ? item.label : ''
}
@@ -265,9 +315,12 @@ const init = async () => {
if (planList.value.length > 0) {
queryForm.groupId = planList.value[0].id
}
const typeData = await getDicts('recruit_change_type')
typeList.value = typeData.data || []
getDataList()
} catch (error) {
message.error('初始化失败')
// eslint-disable-next-line no-console
console.log('初始化失败', error)
}
}
@@ -295,8 +348,9 @@ const update = async () => {
message.success('审核成功')
majorChangeVisible.value = false
getDataList()
} catch (error: any) {
message.error(error.msg || '审核失败')
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
}
}
@@ -318,4 +372,69 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.new-fee {
color: var(--el-color-primary);
font-weight: 600;
font-size: 14px;
}
.old-fee {
text-decoration: line-through;
color: #606266;
font-size: 13px;
margin-left: 4px;
}
.empty-text {
color: #909399;
}
// 备注内容样式(用于 DetailPopover 的插槽内容)
.reason-content {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 12px;
background-color: #fef0f0;
border-radius: 4px;
border-left: 3px solid #f56c6c;
.reason-icon {
color: #f56c6c;
font-size: 16px;
flex-shrink: 0;
margin-top: 1px;
}
span {
color: #f56c6c;
line-height: 1.6;
word-break: break-all;
flex: 1;
}
}
// 专业变更链接样式
.major-change-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--el-color-primary);
text-decoration: underline;
cursor: pointer;
font-weight: 500;
&:hover {
color: var(--el-color-primary-light-3);
}
.title-icon {
color: var(--el-color-primary);
}
}
// 新专业样式(用于 DetailPopover 的内容类)
.new-major {
color: var(--el-color-primary);
font-weight: 500;
}
</style>