Merge branch 'developer' of ssh://code.cyweb.top:30033/scj/zhxy/v3/cloud-ui into developer

This commit is contained in:
guochunsi
2026-02-28 11:00:37 +08:00
73 changed files with 4005 additions and 569 deletions

View File

@@ -0,0 +1,131 @@
/*
* 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.
*
*/
import request from '/@/utils/request';
/**
* 获取采购文件列表(含历史版本)
* @param applyId 采购申请ID
*/
export function getDocList(applyId: number | string) {
return request({
url: '/purchase/purchasingdoc/list/' + applyId,
method: 'get'
});
}
/**
* 上传采购文件(招标代理)
* @param data 文件信息
*/
export function uploadDoc(data: any) {
return request({
url: '/purchase/purchasingdoc/upload',
method: 'post',
data
});
}
/**
* 重新上传采购文件
* @param data 文件信息
*/
export function reuploadDoc(data: any) {
return request({
url: '/purchase/purchasingdoc/reupload',
method: 'post',
data
});
}
/**
* 获取采购文件下载地址
* @param id 采购文件ID
*/
export function getDocDownloadUrl(id: number | string) {
return `/purchase/purchasingdoc/download/${id}`;
}
/**
* 确认无误
* @param data 审核信息
*/
export function confirmDoc(data: any) {
return request({
url: '/purchase/purchasingdoc/confirm',
method: 'post',
data
});
}
/**
* 退回修改
* @param data 审核信息
*/
export function returnDoc(data: any) {
return request({
url: '/purchase/purchasingdoc/return',
method: 'post',
data
});
}
/**
* 确认流程结束
* @param applyId 采购申请ID
*/
export function completeDoc(applyId: number | string) {
return request({
url: '/purchase/purchasingdoc/complete',
method: 'post',
params: { applyId }
});
}
/**
* 获取审核记录
* @param applyId 采购申请ID
*/
export function getAuditRecords(applyId: number | string) {
return request({
url: '/purchase/purchasingdoc/audit-records/' + applyId,
method: 'get'
});
}
/**
* 获取待审核列表
* @param params 分页参数
*/
export function getMyPending(params?: any) {
return request({
url: '/purchase/purchasingdoc/my-pending',
method: 'get',
params
});
}
/**
* 获取可执行操作
* @param applyId 采购申请ID
*/
export function getAvailableActions(applyId: number | string) {
return request({
url: '/purchase/purchasingdoc/actions/' + applyId,
method: 'get'
});
}

View File

@@ -0,0 +1,48 @@
/*
* 采购模板管理 API
*/
import request from '/@/utils/request';
/**
* 模板列表
*/
export function listTemplates(params?: any) {
return request({
url: '/purchase/purchasingtemplate/list',
method: 'get',
params,
});
}
/**
* 上传 / 覆盖模板
* @param formData 含 file、type 的 FormData
*/
export function uploadTemplate(formData: FormData) {
return request({
url: '/purchase/purchasingtemplate/upload',
method: 'post',
data: formData,
headers: { 'Content-Type': 'multipart/form-data' },
});
}
/**
* 获取模板下载地址
*/
export function getTemplateDownloadUrl(type: string) {
return `/purchase/purchasingtemplate/download?type=${encodeURIComponent(type)}`;
}
/**
* 更新模板类型名称
*/
export function updateTemplateTitle(data: { id: number | string; templateTitle: string }) {
return request({
url: '/purchase/purchasingtemplate/updateTitle',
method: 'post',
data,
});
}

View File

@@ -0,0 +1,61 @@
import request from '/@/utils/request';
/**
* 分页查询
* @param params 查询参数
*/
export function fetchList(params?: any) {
return request({
url: '/purchase/purchasingBusinessLeader/page',
method: 'get',
params
});
}
/**
* 通过id查询
* @param id ID
*/
export function getObj(id: string | number) {
return request({
url: '/purchase/purchasingBusinessLeader/details',
method: 'get',
params: { id }
});
}
/**
* 新增业务分管校领导
* @param obj 对象数据
*/
export function addObj(obj: any) {
return request({
url: '/purchase/purchasingBusinessLeader',
method: 'post',
data: obj
});
}
/**
* 修改业务分管校领导
* @param obj 对象数据
*/
export function putObj(obj: any) {
return request({
url: '/purchase/purchasingBusinessLeader',
method: 'put',
data: obj
});
}
/**
* 删除业务分管校领导
* @param ids ID数组
*/
export function delObjs(ids: string[] | number[]) {
return request({
url: '/purchase/purchasingBusinessLeader',
method: 'delete',
data: ids
});
}

View File

@@ -0,0 +1,41 @@
import request from '/@/utils/request'
export function fetchList(query?: any) {
return request({
url: '/purchase/purchasingPurchaseManager/page',
method: 'get',
params: query
})
}
export function getObj(id: any) {
return request({
url: '/purchase/purchasingPurchaseManager/details',
method: 'get',
params: { id }
})
}
export function addObj(obj: any) {
return request({
url: '/purchase/purchasingPurchaseManager',
method: 'post',
data: obj
})
}
export function delObjs(ids: any) {
return request({
url: '/purchase/purchasingPurchaseManager',
method: 'delete',
data: ids
})
}
export function putObj(obj: any) {
return request({
url: '/purchase/purchasingPurchaseManager',
method: 'put',
data: obj
})
}

View File

@@ -76,8 +76,9 @@ export const getMNObj = (id: string | number) => {
*/
export const delObj = (id: string | number) => {
return request({
url: `/recruit/recruitImitateAdjustBatch/${id}`,
method: 'delete',
url: `/recruit/recruitImitateAdjustBatch/delete`,
method: 'post',
data: { id:id }
});
};
@@ -87,8 +88,9 @@ export const delObj = (id: string | number) => {
*/
export const delMNObj = (id: string | number) => {
return request({
url: `/recruit/recruitImitateAdjustBatch/delMNObj/${id}`,
method: 'delete',
url: `/recruit/recruitImitateAdjustBatch/delMNObj`,
method: 'post',
data: { id:id }
});
};

View File

@@ -0,0 +1,106 @@
import request from "/@/utils/request"
// ========== 基础CRUD接口 ==========
/**
* 分页查询列表数据
* @param query - 查询参数对象
* @returns Promise<分页数据>
*/
export function fetchList(query?: Object) {
return request({
url: '/recruit/recruitPolicyFile/page',
method: 'get',
params: query
})
}
/**
* 新增数据
* @param obj - 要新增的数据对象
* @returns Promise<boolean> - 操作结果
*/
export function addObj(obj?: Object) {
return request({
url: '/recruit/recruitPolicyFile',
method: 'post',
data: obj
})
}
/**
* 获取详情数据
* @param obj - 查询参数对象包含ID等
* @returns Promise<数据详情>
*/
export function getObj(obj?: Object) {
return request({
url: '/recruit/recruitPolicyFile/details',
method: 'get',
params: obj
})
}
/**
* 批量删除数据
* @param ids - 要删除的ID数组
* @returns Promise<操作结果>
*/
export function delObjs(ids?: Object) {
return request({
url: '/recruit/recruitPolicyFile',
method: 'delete',
data: ids
})
}
/**
* 更新数据
* @param obj - 要更新的数据对象
* @returns Promise<操作结果>
*/
export function putObj(obj?: Object) {
return request({
url: '/recruit/recruitPolicyFile',
method: 'put',
data: obj
})
}
// ========== 工具函数 ==========
/**
* 验证字段值唯一性
* @param rule - 验证规则对象
* @param value - 要验证的值
* @param callback - 验证回调函数
* @param isEdit - 是否为编辑模式
*
* @example
* // 在表单验证规则中使用
* fieldName: [
* {
* validator: (rule, value, callback) => {
* validateExist(rule, value, callback, form.id !== '');
* },
* trigger: 'blur',
* },
* ]
*/
export function validateExist(rule: any, value: any, callback: any, isEdit: boolean) {
// 编辑模式下跳过验证
if (isEdit) {
return callback();
}
// 查询是否存在相同值
getObj({ [rule.field]: value }).then((response) => {
const result = response.data;
if (result !== null && result.length > 0) {
callback(new Error('数据已经存在'));
} else {
callback();
}
});
}

View File

@@ -0,0 +1,56 @@
import request from "/@/utils/request"
// ========== 基础CRUD接口 ==========
/**
* 分页查询列表数据
* @param query - 查询参数对象
* @returns Promise<分页数据>
*/
export function fetchList(query?: Object) {
return request({
url: '/recruit/recruitPreexamPeople/page',
method: 'get',
params: query
})
}
/**
* 新增数据
* @param obj - 要新增的数据对象
* @returns Promise<boolean> - 操作结果
*/
export function addObj(obj?: Object) {
return request({
url: '/recruit/recruitPreexamPeople',
method: 'post',
data: obj
})
}
/**
* 获取详情数据
* @param obj - 查询参数对象包含ID等
* @returns Promise<数据详情>
*/
export function getObj(obj?: Object) {
return request({
url: '/recruit/recruitPreexamPeople/details',
method: 'get',
params: obj
})
}
/**
* 删除
* @param id
*/
export const delObj = (id: string | number) => {
return request({
url: `/recruit/recruitPreexamPeople/delete`,
method: 'post',
data: { id: id }
});
};

View File

@@ -0,0 +1,17 @@
import request from "/@/utils/request"
// ========== 基础CRUD接口 ==========
/**
* 分页查询列表数据
* @param query - 查询参数对象
* @returns Promise<分页数据>
*/
export function fetchList(query?: Object) {
return request({
url: '/recruit/recruitSchoolHistory/page',
method: 'get',
params: query
})
}

View File

@@ -0,0 +1,19 @@
import request from '/@/utils/request';
export const exportPreStuSuccess = (data?: any) => {
return request({
url: '/recruit/file/exportPreStuSuccess',
method: 'post',
data: data,
});
};
export const exportAdjustExcel = (data?: any) => {
return request({
url: '/recruit/file/exportAdjustExcel',
method: 'post',
data: data,
});
};

View File

@@ -376,7 +376,19 @@ const handleRemove = (file: { name?: string; id?: string; url?: string }) => {
};
const handlePreview = (file: any) => {
other.downBlobFile(file.url, {}, file.name);
// 优先使用文件ID下载采购附件使用 downloadById 接口)
if (file.id) {
// 判断是否是采购附件(通过 fileType 判断)
const downloadUrl = `/purchase/purchasingfiles/downloadById?fileId=${encodeURIComponent(file.id)}`;
other.downBlobFile(downloadUrl, {}, file.name || file.fileTitle || '文件');
return;
}
// 兼容旧的URL下载方式
if (file.url) {
other.downBlobFile(file.url, {}, file.name || file.fileTitle || '文件');
return;
}
useMessage().warning('无法获取文件下载信息');
};
// 添加 handleExceed 函数

View File

@@ -45,7 +45,7 @@
type="textarea" :placeholder="t('jfcomment.inputRemarkTip')">
</el-input>
</el-form-item>
<el-form-item :label="t('jfcomment.signName')">
<el-form-item :label="t('jfcomment.signName')" style="display: none">
<sign-name ref="signNameRef" :currJob="data.currJob"></sign-name>
</el-form-item>
<el-form-item>

View File

@@ -46,7 +46,8 @@
const methods = {
open(flowData) {
flowData = stringifyRemoveNullKey(flowData);
data.flowData = !window.isWebTest ? {contact: "演示环境不能操作,如需了解联系我们"}: JSON.parse(flowData);
// data.flowData = !window.isWebTest ? {contact: "演示环境不能操作,如需了解联系我们"}: JSON.parse(flowData);
data.flowData = JSON.parse(flowData);
data.viewJsonVisible = true;
},
onClose() {

View File

@@ -123,7 +123,7 @@ export function useTable(options?: BasicTableProps) {
};
// 覆盖默认值
const state = mergeDefaultOptions(defaultOptions, options);
const state = mergeDefaultOptions(defaultOptions, options || {});
/**
* 发起分页查询,并设置表格数据和分页信息

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { Session } from '/@/utils/storage';
import {Local, Session} from '/@/utils/storage';
import { getUserInfo, login, loginByMobile, loginBySocial, refreshTokenApi } from '/@/api/login/index';
import { useMessage } from '/@/hooks/message';
import { initUserTableConfigs } from '/@/api/admin/usertable';
@@ -34,9 +34,13 @@ export const useUserInfo = defineStore('userInfo', {
return new Promise((resolve, reject) => {
login(data)
.then((res) => {
debugger
// 存储token 信息
Session.set('token', res.access_token);
Session.set('refresh_token', res.refresh_token);
Local.remove('roleCode');
Local.remove('roleName');
Local.remove('roleId');
resolve(res);
})
.catch((err) => {

View File

@@ -128,3 +128,8 @@ const resetQuery = () => {
queryRef.value.resetFields();
};
</script>
<style scoped>
:deep(.el-table__body tr td) {
text-align: left !important;
}
</style>

View File

@@ -13,16 +13,21 @@
<el-form-item class="role-form-item">
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
<template v-for="(roles, groupName) in allRoleGroups" :key="groupName">
<div class="role-group">
<el-divider>{{ groupName }}</el-divider>
<el-radio-button
v-for="item in roles"
:key="item.roleCode"
:label="item.roleCode"
>
{{ item.roleName }}
</el-radio-button>
</div>
<el-card class="role-group-card" shadow="hover">
<template #header>
<span class="group-name">{{ groupName }}</span>
</template>
<div class="role-group">
<el-radio-button
v-for="item in roles"
:key="item.roleCode"
:label="item.roleCode"
size="small"
>
{{ item.roleName }}
</el-radio-button>
</div>
</el-card>
</template>
</el-radio-group>
</el-form-item>
@@ -121,31 +126,12 @@ defineExpose({
flex-wrap: wrap;
}
}
.role-group {
width: 100%;
flex: 0 0 100%;
margin-bottom: 12px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 12px;
&:last-child {
margin-bottom: 0;
}
}
.group-name {
font-size: 13px;
color: var(--el-text-color-secondary);
margin-bottom: 6px;
}
.role-radio-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: flex-start;
flex-direction: column;
gap: 6px;
width: 100%;
/* 每个分组内按钮可换行,分组之间不重叠 */
:deep(.el-radio-button) {
margin: 0;
}
@@ -153,9 +139,35 @@ defineExpose({
border-radius: 6px !important;
border: 1px solid var(--el-border-color) !important;
margin-left: 0 !important;
line-height: 1.3;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
border-color: var(--el-color-primary) !important;
}
}
.role-group-card {
width: 100%;
flex: 0 0 auto;
:deep(.el-card__header) {
padding: 6px 12px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
:deep(.el-card__body) {
padding: 6px 12px 8px;
}
}
.role-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 8px;
}
.group-name {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>

View File

@@ -46,7 +46,8 @@
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog('add')">
@click="formDialogRef.openDialog('add')"
v-auth="'purchase_purchasingagent_add'">
新增
</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
@@ -91,14 +92,16 @@
<el-button
icon="Edit"
link
type="primary"
type="primary"
v-auth="'purchase_purchasingagent_edit'"
@click="formDialogRef.openDialog('edit', scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
type="danger"
v-auth="'purchase_purchasingagent_del'"
@click="handleDelete(scope.row)">
删除
</el-button>

View File

@@ -12,7 +12,8 @@
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
type="primary"
v-auth="'purchase_purchasingcategory_add'"
@click="formDialogRef.openDialog('add')">
新增
</el-button>
@@ -65,14 +66,16 @@
<el-button
icon="Edit"
link
type="primary"
type="primary"
v-auth="'purchase_purchasingcategory_edit'"
@click="formDialogRef.openDialog('edit', scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
type="danger"
v-auth="'purchase_purchasingcategory_del'"
@click="handleDelete(scope.row)">
删除
</el-button>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
<template>
<el-table :data="records" stripe v-loading="loading" max-height="400">
<el-table-column prop="operateTypeDesc" label="操作类型" width="100" align="center">
<template #default="scope">
<el-tag :type="getOperateTypeStyle(scope.row.operateType)">
{{ scope.row.operateTypeDesc }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="operateRoleDesc" label="操作角色" width="100" />
<el-table-column prop="operateByName" label="操作人" width="100" />
<el-table-column prop="currentVersion" label="文件版本" width="80" align="center" />
<el-table-column prop="remark" label="批注意见" min-width="200" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.remark || '-' }}
</template>
</el-table-column>
<el-table-column prop="operateTime" label="操作时间" width="160" />
</el-table>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { getAuditRecords } from '/@/api/finance/purchasingdoc'
const props = defineProps<{
applyId: number | string
}>()
const records = ref<any[]>([])
const loading = ref(false)
const loadRecords = async () => {
if (!props.applyId) return
loading.value = true
try {
const res = await getAuditRecords(props.applyId)
records.value = res.data || []
} catch (e) {
records.value = []
} finally {
loading.value = false
}
}
const refresh = () => {
loadRecords()
}
const getOperateTypeStyle = (type: string) => {
const styleMap: Record<string, string> = {
'UPLOAD': 'primary',
'CONFIRM': 'success',
'RETURN': 'warning',
'COMPLETE': 'success'
}
return styleMap[type] || 'info'
}
watch(() => props.applyId, () => {
loadRecords()
}, { immediate: true })
defineExpose({ refresh })
</script>

View File

@@ -0,0 +1,323 @@
<template>
<el-dialog
v-model="visible"
title="采购文件审核"
width="900px"
destroy-on-close
@close="handleClose">
<el-tabs v-model="activeTab">
<!-- 项目信息 -->
<el-tab-pane label="项目信息" name="info">
<el-descriptions :column="2" border>
<el-descriptions-item label="采购编号">{{ applyInfo.purchaseNo }}</el-descriptions-item>
<el-descriptions-item label="项目名称">{{ applyInfo.projectName }}</el-descriptions-item>
<el-descriptions-item label="需求部门">{{ applyInfo.deptName }}</el-descriptions-item>
<el-descriptions-item label="预算金额">{{ applyInfo.budget ? Number(applyInfo.budget).toLocaleString() + '元' : '-' }}</el-descriptions-item>
<el-descriptions-item label="招标代理">{{ applyInfo.agentName || '-' }}</el-descriptions-item>
<el-descriptions-item label="审核状态">
<el-tag :type="getStatusType(applyInfo.docAuditStatus)">
{{ getStatusLabel(applyInfo.docAuditStatus) }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 采购需求文件 -->
<el-tab-pane label="采购需求文件" name="requirement">
<el-table :data="requirementFiles" stripe v-loading="requirementLoading">
<el-table-column prop="fileTitle" label="文件名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="fileType" label="文件类型" width="120">
<template #default="scope">
{{ getFileTypeLabel(scope.row.fileType) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
<el-button type="primary" link icon="Download" @click="handleDownloadRequirement(scope.row)">
下载
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 采购文件 -->
<el-tab-pane label="采购文件" name="doc">
<div class="doc-header">
<el-button v-if="canUpload" type="primary" icon="Upload" @click="handleUpload">
{{ applyInfo.docAuditStatus === 'RETURNED' ? '重新上传' : '上传文件' }}
</el-button>
</div>
<el-table :data="docList" stripe v-loading="docLoading">
<el-table-column prop="fileName" label="文件名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="version" label="版本" width="80" align="center" />
<el-table-column prop="uploadByName" label="上传人" width="100" />
<el-table-column prop="uploadTime" label="上传时间" width="160" />
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
<el-button type="primary" link icon="Download" @click="handleDownloadDoc(scope.row)">
下载
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 审核记录 -->
<el-tab-pane label="审核记录" name="audit">
<AuditRecordList :apply-id="applyInfo.id" ref="auditRecordListRef" />
</el-tab-pane>
</el-tabs>
<!-- 操作区域 -->
<template #footer>
<div class="dialog-footer">
<el-button v-if="canConfirm" type="success" @click="handleConfirm">确认无误</el-button>
<el-button v-if="canReturn" type="warning" @click="handleReturn">退回修改</el-button>
<el-button v-if="canComplete" type="primary" @click="handleComplete">确认流程结束</el-button>
<el-button @click="handleClose">关闭</el-button>
</div>
</template>
<!-- 退回原因弹窗 -->
<el-dialog v-model="returnDialogVisible" title="退回原因" width="400px" append-to-body>
<el-form>
<el-form-item label="退回原因">
<el-input v-model="returnRemark" type="textarea" :rows="3" placeholder="请输入退回原因" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="returnDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitReturn">确定</el-button>
</template>
</el-dialog>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { getObj, getApplyFiles } from '/@/api/finance/purchasingrequisition'
import { getDocList, uploadDoc, reuploadDoc, confirmDoc, returnDoc, completeDoc, getAvailableActions, getDocDownloadUrl } from '/@/api/finance/purchasingdoc'
import other from '/@/utils/other'
const AuditRecordList = defineAsyncComponent(() => import('./AuditRecordList.vue'));
const emit = defineEmits(['refresh']);
const visible = ref(false)
const activeTab = ref('info')
const applyInfo = ref<any>({})
const requirementFiles = ref<any[]>([])
const requirementLoading = ref(false)
const docList = ref<any[]>([])
const docLoading = ref(false)
const auditRecordListRef = ref()
const availableActions = ref<string[]>([])
const returnDialogVisible = ref(false)
const returnRemark = ref('')
const canUpload = computed(() => availableActions.value.includes('upload'))
const canConfirm = computed(() => availableActions.value.includes('confirm'))
const canReturn = computed(() => availableActions.value.includes('return'))
const canComplete = computed(() => availableActions.value.includes('complete'))
const open = async (row: any) => {
visible.value = true
activeTab.value = 'info'
applyInfo.value = {}
requirementFiles.value = []
docList.value = []
returnRemark.value = ''
// 加载详情
try {
const res = await getObj(row.id)
applyInfo.value = res.data || res
} catch (e: any) {
useMessage().error(e?.msg || '加载项目信息失败')
return
}
// 加载可执行操作
try {
const actionsRes = await getAvailableActions(row.id)
availableActions.value = actionsRes.data || []
} catch (e) {
availableActions.value = []
}
// 加载采购需求文件
loadRequirementFiles()
// 加载采购文件
loadDocList()
}
const loadRequirementFiles = async () => {
if (!applyInfo.value.id) return
requirementLoading.value = true
try {
const res = await getApplyFiles(applyInfo.value.id)
const files = res.data || res || []
// 过滤采购需求文件fileType=120
requirementFiles.value = files.filter((f: any) => f.fileType === '120')
} catch (e) {
requirementFiles.value = []
} finally {
requirementLoading.value = false
}
}
const loadDocList = async () => {
if (!applyInfo.value.id) return
docLoading.value = true
try {
const res = await getDocList(applyInfo.value.id)
docList.value = res.data || []
} catch (e) {
docList.value = []
} finally {
docLoading.value = false
}
}
const handleUpload = () => {
// 触发文件上传
const input = document.createElement('input')
input.type = 'file'
input.accept = '*/*'
input.onchange = async (e: any) => {
const file = e.target.files[0]
if (!file) return
// TODO: 实现文件上传到OSS然后调用上传接口
useMessage().info('文件上传功能需要配合OSS实现')
}
input.click()
}
const handleDownloadRequirement = (row: any) => {
if (row.remark) {
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(row.remark)}&fileTitle=${encodeURIComponent(row.fileTitle)}`
other.downBlobFile(url, {}, row.fileTitle)
}
}
const handleDownloadDoc = (row: any) => {
const url = getDocDownloadUrl(row.id)
other.downBlobFile(url, {}, row.fileName)
}
const handleConfirm = async () => {
try {
await useMessageBox().confirm('确定要确认该采购文件无误吗?')
} catch {
return
}
try {
await confirmDoc({ applyId: applyInfo.value.id })
useMessage().success('确认成功')
emit('refresh')
loadDocList()
auditRecordListRef.value?.refresh()
// 重新加载可执行操作
const actionsRes = await getAvailableActions(applyInfo.value.id)
availableActions.value = actionsRes.data || []
} catch (e: any) {
useMessage().error(e?.msg || '确认失败')
}
}
const handleReturn = () => {
returnRemark.value = ''
returnDialogVisible.value = true
}
const submitReturn = async () => {
try {
await returnDoc({ applyId: applyInfo.value.id, remark: returnRemark.value })
useMessage().success('退回成功')
returnDialogVisible.value = false
emit('refresh')
loadDocList()
auditRecordListRef.value?.refresh()
// 重新加载可执行操作
const actionsRes = await getAvailableActions(applyInfo.value.id)
availableActions.value = actionsRes.data || []
} catch (e: any) {
useMessage().error(e?.msg || '退回失败')
}
}
const handleComplete = async () => {
try {
await useMessageBox().confirm('确定要确认流程结束吗?')
} catch {
return
}
try {
await completeDoc(applyInfo.value.id)
useMessage().success('流程已结束')
emit('refresh')
loadDocList()
auditRecordListRef.value?.refresh()
// 重新加载可执行操作
const actionsRes = await getAvailableActions(applyInfo.value.id)
availableActions.value = actionsRes.data || []
} catch (e: any) {
useMessage().error(e?.msg || '操作失败')
}
}
const handleClose = () => {
visible.value = false
}
const getStatusType = (status: string) => {
const typeMap: Record<string, string> = {
'PENDING_UPLOAD': 'info',
'ASSET_REVIEWING': 'warning',
'DEPT_REVIEWING': 'warning',
'AUDIT_REVIEWING': 'warning',
'ASSET_CONFIRMING': 'primary',
'COMPLETED': 'success',
'RETURNED': 'danger'
}
return typeMap[status] || 'info'
}
const getStatusLabel = (status: string) => {
const labelMap: Record<string, string> = {
'PENDING_UPLOAD': '待上传',
'ASSET_REVIEWING': '资产管理处审核中',
'DEPT_REVIEWING': '需求部门审核中',
'AUDIT_REVIEWING': '内审部门审核中',
'ASSET_CONFIRMING': '资产管理处确认中',
'COMPLETED': '已完成',
'RETURNED': '已退回'
}
return labelMap[status] || '-'
}
const getFileTypeLabel = (type: string) => {
const labelMap: Record<string, string> = {
'120': '采购需求表',
'130': '采购文件'
}
return labelMap[type] || type
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.doc-header {
margin-bottom: 16px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="采购编号" prop="purchaseNo">
<el-input
v-model="state.queryForm.purchaseNo"
placeholder="请输入采购编号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="state.queryForm.projectName"
placeholder="请输入项目名称"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="审核状态" prop="docAuditStatus">
<el-select
v-model="state.queryForm.docAuditStatus"
placeholder="请选择审核状态"
clearable
style="width: 200px">
<el-option label="待上传" value="PENDING_UPLOAD" />
<el-option label="资产管理处审核中" value="ASSET_REVIEWING" />
<el-option label="需求部门审核中" value="DEPT_REVIEWING" />
<el-option label="内审部门审核中" value="AUDIT_REVIEWING" />
<el-option label="资产管理处确认中" value="ASSET_CONFIRMING" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已退回" value="RETURNED" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><DocumentChecked /></el-icon>
采购文件审核
</span>
<div class="header-actions">
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="purchaseNo" label="采购编号" min-width="140" show-overflow-tooltip />
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip />
<el-table-column prop="budget" label="预算金额(元)" width="120" align="right">
<template #default="scope">
{{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }}
</template>
</el-table-column>
<el-table-column prop="docAuditStatus" label="审核状态" width="140" align="center">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.docAuditStatus)">
{{ getStatusLabel(scope.row.docAuditStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="currentDocVersion" label="当前版本" width="100" align="center">
<template #default="scope">
{{ scope.row.currentDocVersion || '-' }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="120">
<template #default="scope">
<el-button type="primary" link icon="View" @click="handleAudit(scope.row)">
审核
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-if="state.pagination && state.pagination.total && state.pagination.total > 0"
:total="state.pagination.total"
:current="state.pagination.current"
:size="state.pagination.size"
@sizeChange="sizeChangeHandle"
@currentChange="currentChangeHandle"
/>
</el-card>
</div>
<!-- 审核弹窗 -->
<DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="PurchasingDocAudit">
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { useMessage } from "/@/hooks/message";
import { getPage } from "/@/api/finance/purchasingrequisition";
import { Search, DocumentChecked, List } from '@element-plus/icons-vue'
// 引入组件
const DocAuditDialog = defineAsyncComponent(() => import('./DocAuditDialog.vue'));
const docAuditDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: getPage,
queryForm: {
purchaseNo: '',
projectName: '',
docAuditStatus: '',
},
createdIsNeed: true
});
const { getDataList, tableStyle, sizeChangeHandle, currentChangeHandle } = useTable(state);
const handleReset = () => {
searchFormRef.value?.resetFields();
getDataList();
};
const getStatusType = (status: string) => {
const typeMap: Record<string, string> = {
'PENDING_UPLOAD': 'info',
'ASSET_REVIEWING': 'warning',
'DEPT_REVIEWING': 'warning',
'AUDIT_REVIEWING': 'warning',
'ASSET_CONFIRMING': 'primary',
'COMPLETED': 'success',
'RETURNED': 'danger'
};
return typeMap[status] || 'info';
};
const getStatusLabel = (status: string) => {
const labelMap: Record<string, string> = {
'PENDING_UPLOAD': '待上传',
'ASSET_REVIEWING': '资产管理处审核中',
'DEPT_REVIEWING': '需求部门审核中',
'AUDIT_REVIEWING': '内审部门审核中',
'ASSET_CONFIRMING': '资产管理处确认中',
'COMPLETED': '已完成',
'RETURNED': '已退回'
};
return labelMap[status] || '-';
};
const handleAudit = (row: any) => {
docAuditDialogRef.value?.open(row);
};
onMounted(() => {
// 页面加载时的初始化逻辑
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -235,52 +235,7 @@
</template>
</template>
</el-table-column>
<el-table-column prop="fileFlowStatus" label="文件审批状态" width="110" align="center">
<template #header>
<el-icon><DocumentChecked /></el-icon>
<span style="margin-left: 4px">文件审批状态</span>
</template>
<template #default="scope">
<template v-if="scope.row.fileFlowInstId">
<el-tooltip content="点击查看审批过程" placement="top">
<el-tag
v-if="scope.row.fileFlowStatus === '-2'"
type="info"
class="status-tag-clickable"
@click="handleShowFileFlowComment(scope.row)">撤回</el-tag>
<el-tag
v-else-if="scope.row.fileFlowStatus === '-1'"
type="warning"
class="status-tag-clickable"
@click="handleShowFileFlowComment(scope.row)">暂存</el-tag>
<el-tag
v-else-if="scope.row.fileFlowStatus === '0'"
type="primary"
class="status-tag-clickable"
@click="handleShowFileFlowComment(scope.row)">运行中</el-tag>
<el-tag
v-else-if="scope.row.fileFlowStatus === '1'"
type="success"
class="status-tag-clickable"
@click="handleShowFileFlowComment(scope.row)">完成</el-tag>
<el-tag
v-else-if="scope.row.fileFlowStatus === '2'"
type="danger"
class="status-tag-clickable"
@click="handleShowFileFlowComment(scope.row)">作废</el-tag>
<el-tag
v-else-if="scope.row.fileFlowStatus === '3'"
type="info"
class="status-tag-clickable"
@click="handleShowFileFlowComment(scope.row)">终止</el-tag>
<span v-else class="status-tag-clickable" @click="handleShowFileFlowComment(scope.row)">-</span>
</el-tooltip>
</template>
<template v-else>
<span style="color: #909399;"></span>
</template>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<div class="op-cell">
@@ -391,6 +346,9 @@
</span>
</template>
</el-dialog>
<!-- 采购文件审核弹窗 -->
<DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />
</div>
</template>
@@ -412,6 +370,7 @@ const ImplementForm = defineAsyncComponent(() => import('./implementForm.vue'));
const ActionDropdown = defineAsyncComponent(() => import('/@/components/tools/action-dropdown.vue'));
const PurchasingAcceptModal = defineAsyncComponent(() => import('./accept/PurchasingAcceptModal.vue'));
const FlowCommentTimeline = defineAsyncComponent(() => import('/@/views/jsonflow/comment/timeline.vue'));
const DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue'));
// 字典数据和品目树数据
const dictData = ref({
@@ -431,6 +390,7 @@ const formDialogRef = ref()
const acceptModalRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const docAuditDialogRef = ref()
/** 审批过程弹窗:是否显示、当前行对应的流程 job供 Comment 组件用)、类型(申请单/文件) */
const showFlowComment = ref(false)
const currFlowJob = ref<{ id?: number; flowInstId?: number } | null>(null)
@@ -599,6 +559,11 @@ const handleImplement = (row: any) => {
implementFormRef.value?.openDialog(row);
};
/** 打开采购文件审核 */
const handleDocAudit = (row: any) => {
docAuditDialogRef.value?.open(row);
};
/**
* 删除当前行
* @param row - 当前行数据
@@ -611,7 +576,7 @@ const handleDelete = async (row: any) => {
}
try {
await delObj(row.id);
await delObj({id:row.id});
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
@@ -692,6 +657,12 @@ const getActionMenuItems = (row: any) => {
icon: Collection,
visible: () => row?.purchaseMode === '2' || (row?.purchaseMode === '0' && row?.purchaseType === '4'),
},
{
command: 'docAudit',
label: '采购文件审核',
icon: DocumentChecked,
visible: () => row?.implementType === '2' && row?.agentId,
},
];
};
@@ -725,6 +696,9 @@ const handleMoreCommand = (command: string, row: any) => {
case 'assignAgent':
openAssignAgentDialog(row);
break;
case 'docAudit':
handleDocAudit(row);
break;
}
};

View File

@@ -0,0 +1,271 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<div class="header-actions">
<el-button
type="primary"
icon="FolderAdd"
@click="openUploadDialog()"
v-auth="'purchase_template_add'">
新增模板
</el-button>
<el-alert type="info" :closable="false" class="mb3 mt-3" show-icon>
此处模版中的模版编码对应用户端下载模版匹配请勿随意修改或删除正常情况下如有发生模版变化重新上传即可如有新增模版请联系管理员进行处理
</el-alert>
</div>
</div>
</template>
<el-table :data="tableData" v-loading="loading" stripe class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="templateTitle" label="模板类型名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="templateType" label="模板类型编码" min-width="180" show-overflow-tooltip />
<el-table-column prop="templateName" label="模板名称" min-width="220" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" show-overflow-tooltip />
<el-table-column label="操作" width="260" align="center" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
link
icon="Download"
@click="handleDownload(row)"
v-auth="'purchase_template_view'">
下载
</el-button>
<el-button
type="primary"
link
icon="UploadFilled"
@click="openUploadDialog(row)"
v-auth="'purchase_template_add'">
重新上传
</el-button>
<el-button
type="primary"
link
icon="Edit"
@click="openEditDialog(row)"
v-auth="'purchase_template_add'">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="uploadDialogVisible" title="上传模板" width="450px" destroy-on-close>
<el-form :model="uploadForm" label-width="100px">
<el-form-item label="模板类型编码" required>
<el-input
v-model="uploadForm.templateType"
placeholder="例如: business_negotiation, inquiry"
:disabled="!!uploadForm.lockType"
/>
</el-form-item>
<el-form-item label="模板类型名称">
<el-input
v-model="uploadForm.templateTitle"
placeholder="例如: 部门采购询价模版"
/>
</el-form-item>
<el-form-item label="模板文件" required>
<el-upload
class="upload-block"
:auto-upload="false"
:limit="1"
:file-list="fileList"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
>
<el-button type="primary" icon="UploadFilled">选择文件</el-button>
<template #tip>
<div class="el-upload__tip">支持 docdocx Word 模板文件上传后前端下载将使用该文件</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="uploadDialogVisible = false"> </el-button>
<el-button type="primary" :loading="uploading" @click="handleUploadConfirm"> </el-button>
</template>
</el-dialog>
<el-dialog v-model="editDialogVisible" title="编辑模板" width="420px" destroy-on-close>
<el-form :model="editForm" label-width="100px">
<el-form-item label="模板类型编码">
<el-input v-model="editForm.templateType" disabled />
</el-form-item>
<el-form-item label="模板类型名称" required>
<el-input v-model="editForm.templateTitle" placeholder="请输入模板类型名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" :loading="editing" @click="handleEditConfirm"> </el-button>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup lang="ts" name="PurchasingTemplateManage">
import { ref, reactive, onMounted } from 'vue';
import { Document, List, UploadFilled, Download, Edit } from '@element-plus/icons-vue';
import { listTemplates, uploadTemplate, getTemplateDownloadUrl, updateTemplateTitle } from '/@/api/finance/purchasingtemplate';
import { useMessage } from '/@/hooks/message';
const loading = ref(false);
const tableData = ref<any[]>([]);
const uploadDialogVisible = ref(false);
const uploading = ref(false);
const uploadForm = reactive<{
templateType: string;
templateTitle: string;
lockType?: boolean;
}>({
templateType: '',
templateTitle: '',
lockType: false,
});
const editDialogVisible = ref(false);
const editing = ref(false);
const editForm = reactive<{
id: number | null;
templateType: string;
templateTitle: string;
}>({
id: null,
templateType: '',
templateTitle: '',
});
const fileList = ref<any[]>([]);
const currentFile = ref<File | null>(null);
const fetchData = async () => {
loading.value = true;
try {
const res = await listTemplates();
tableData.value = (res && res.data) || [];
} catch (e) {
tableData.value = [];
} finally {
loading.value = false;
}
};
onMounted(fetchData);
const openUploadDialog = (row?: any) => {
uploadDialogVisible.value = true;
uploadForm.templateType = row?.templateType || '';
uploadForm.templateTitle = row?.templateTitle || '';
uploadForm.lockType = !!row?.templateType;
fileList.value = [];
currentFile.value = null;
};
const handleFileChange = (file: any, files: any[]) => {
fileList.value = files.slice(-1);
currentFile.value = file.raw || null;
};
const handleFileRemove = () => {
fileList.value = [];
currentFile.value = null;
};
const handleUploadConfirm = async () => {
if (!uploadForm.templateType || !uploadForm.templateType.trim()) {
useMessage().error('请填写模板类型编码');
return;
}
if (!currentFile.value) {
useMessage().error('请选择要上传的模板文件');
return;
}
uploading.value = true;
try {
const formData = new FormData();
formData.append('type', uploadForm.templateType.trim());
if (uploadForm.templateTitle && uploadForm.templateTitle.trim()) {
formData.append('title', uploadForm.templateTitle.trim());
}
formData.append('file', currentFile.value);
await uploadTemplate(formData);
useMessage().success('模板上传成功');
uploadDialogVisible.value = false;
await fetchData();
} catch (e) {
useMessage().error('模板上传失败');
} finally {
uploading.value = false;
}
};
const handleDownload = async (row: any) => {
if (!row?.templateType) {
useMessage().error('缺少模板类型编码');
return;
}
const url = getTemplateDownloadUrl(row.templateType);
const fileName = row.templateName || row.templateTitle || row.templateType;
try {
await (window as any).other?.downBlobFile?.(url, {}, fileName) ||
// 兼容直接使用工具函数
(await import('/@/utils/other')).default.downBlobFile(url, {}, fileName);
} catch (e) {
// 如果工具函数不可用,则退回 window.open
window.open(url, '_blank');
}
};
const openEditDialog = (row: any) => {
editDialogVisible.value = true;
editForm.id = row?.id ?? null;
editForm.templateType = row?.templateType || '';
editForm.templateTitle = row?.templateTitle || '';
};
const handleEditConfirm = async () => {
if (!editForm.id) {
useMessage().error('缺少模板ID');
return;
}
if (!editForm.templateTitle || !editForm.templateTitle.trim()) {
useMessage().error('请输入模板类型名称');
return;
}
editing.value = true;
try {
await updateTemplateTitle({
id: editForm.id,
templateTitle: editForm.templateTitle.trim(),
});
useMessage().success('保存成功');
editDialogVisible.value = false;
await fetchData();
} catch (e) {
useMessage().error('保存失败');
} finally {
editing.value = false;
}
};
</script>
<style scoped>
.upload-block {
display: inline-block;
}
</style>

View File

@@ -46,7 +46,7 @@
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
业务分管部门及人员
业务分管处室及人员
</span>
<div class="header-actions">
<el-button

View File

@@ -0,0 +1,182 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
width="600px"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-form-item label="选取用户" prop="userId">
<org-selector
v-model:orgList="userList"
type="user"
:multiple="false"
@update:orgList="handleUserChange" />
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input
v-model="form.name"
placeholder="选择用户后自动填充"
readonly />
</el-form-item>
<el-form-item label="用户工号" prop="username">
<el-input
v-model="form.username"
placeholder="选择用户后自动填充"
readonly />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="PurchasingBusinessLeaderForm">
import { reactive, ref, nextTick } from 'vue'
import { getObj, addObj, putObj } from '/@/api/purchase/purchasingBusinessLeader';
import { useMessage } from '/@/hooks/message';
import orgSelector from '/@/components/OrgSelector/index.vue';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const userList = ref<any[]>([]);
const form = reactive({
id: '',
userId: '',
username: '',
name: '',
remark: '',
});
const visible = ref(false);
const loading = ref(false);
const dataRules = ref({
userId: [
{ required: true, message: '请选取用户', trigger: 'change' }
],
name: [
{ required: true, message: '请先选取用户', trigger: 'blur' }
],
username: [
{ required: true, message: '请先选取用户', trigger: 'blur' }
],
});
// 处理用户选择变化
const handleUserChange = (list: any[]) => {
if (list && list.length > 0) {
const user = list[0];
form.userId = user.userId || user.id || '';
form.username = user.username || user.userName || '';
form.name = user.name || user.realName || '';
} else {
form.userId = '';
form.username = '';
form.name = '';
}
};
// 打开弹窗
const openDialog = async (id?: string) => {
visible.value = true;
form.id = '';
form.userId = '';
form.username = '';
form.name = '';
form.remark = '';
userList.value = [];
nextTick(() => {
dataFormRef.value?.resetFields();
if (id) {
// 编辑时,先获取详情数据
loading.value = true;
getObj(id).then((res: any) => {
if (res.data && res.data.length > 0) {
const data = res.data[0];
Object.assign(form, {
id: data.id || '',
userId: data.userId || '',
username: data.username || '',
name: data.name || '',
remark: data.remark || '',
});
// 设置用户列表用于回显
if (data.userId) {
userList.value = [{
userId: data.userId,
username: data.username,
name: data.name,
id: data.userId,
userName: data.username,
realName: data.name,
type: 'user',
}];
}
}
loading.value = false;
}).catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
};
// 提交
const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
if (form.id) {
await putObj(form);
useMessage().success('编辑成功');
} else {
await addObj(form);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,240 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="姓名" prop="name">
<el-input
v-model="state.queryForm.name"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="用户工号" prop="username">
<el-input
v-model="state.queryForm.username"
placeholder="请输入用户工号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><User /></el-icon>
业务分管校领导
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog()"
v-auth="'purchasing_bus_leader_add'">
新增
</el-button>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
class="ml10"
v-auth="'purchasing_bus_leader_del'"
@click="handleDelete(selectObjs)">
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'purchasing_bus_leader_export'"
@exportExcel="exportExcel"
class="ml10"
@queryTable="getDataList" />
</div>
</div>
</template>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
class="modern-table">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" min-width="120" show-overflow-tooltip>
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="username" label="用户工号" min-width="150" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">用户工号</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="300" show-overflow-tooltip>
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" show-overflow-tooltip>
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button
icon="Delete"
link
type="danger"
v-auth="'purchasing_bus_leader_del'"
@click="handleDelete([scope.row.id])">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-if="state.pagination"
v-show="state.pagination.total && state.pagination.total > 0"
:total="state.pagination.total"
:current="state.pagination.current"
:size="state.pagination.size"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
/>
</el-card>
</div>
<!-- 编辑新增表单对话框 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="PurchasingBusinessLeader">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/purchase/purchasingBusinessLeader";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, User, UserFilled, EditPen, Clock, Search } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义变量内容
const tableRef = ref()
const formDialogRef = ref()
const queryRef = ref()
const showSearch = ref(true)
const selectObjs = ref([]) as any
const multiple = ref(true)
/**
* 定义响应式表格数据
*/
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: fetchList,
queryForm: {
name: '',
username: '',
},
createdIsNeed: true
});
/**
* 使用 useTable 定义表格相关操作
*/
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state);
/**
* 重置搜索表单
*/
const resetQuery = () => {
queryRef.value?.resetFields();
selectObjs.value = [];
multiple.value = true;
getDataList();
};
/**
* 导出Excel文件
*/
const exportExcel = () => {
downBlobFile(
'/purchase/purchasingBusinessLeader/export',
Object.assign(state.queryForm, { ids: selectObjs.value }),
'purchasingBusinessLeader.xlsx'
);
};
/**
* 表格多选事件处理
* @param objs 选中的数据行
*/
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
/**
* 删除当前行
* @param ids - 要删除的ID数组
*/
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?');
} catch {
return;
}
try {
await delObjs(ids);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,228 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
width="700px"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<!-- 新增时先选取用户选后自动带出姓名工号 -->
<el-form-item label="选取用户" prop="userId">
<org-selector
v-model:orgList="userList"
type="user"
:multiple="false"
@update:orgList="handleUserChange" />
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input
v-model="form.name"
placeholder="选择用户后自动填充"
readonly />
</el-form-item>
<el-form-item label="用户工号" prop="username">
<el-input
v-model="form.username"
placeholder="选择用户后自动填充"
readonly />
</el-form-item>
<el-form-item label="部门" prop="deptId">
<org-selector
v-model:orgList="deptList"
type="dept"
:multiple="false"
@update:orgList="handleDeptChange" />
</el-form-item>
<el-form-item label="部门名称" prop="deptName">
<el-input
v-model="form.deptName"
placeholder="选择部门后自动填充"
readonly />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="PurchasingPurchaseManagerForm">
import { reactive, ref, nextTick } from 'vue'
import { getObj, addObj, putObj } from '/@/api/purchase/purchasingPurchaseManager';
import { useMessage } from '/@/hooks/message';
import orgSelector from '/@/components/OrgSelector/index.vue';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const deptList = ref<any[]>([]);
const userList = ref<any[]>([]);
const form = reactive({
id: '',
deptId: '',
deptName: '',
userId: '',
username: '',
name: '',
remark: '',
});
const visible = ref(false);
const loading = ref(false);
const dataRules = ref({
userId: [
{ required: true, message: '请选取用户(分管负责人)', trigger: 'change' }
],
deptId: [
{ required: true, message: '请选择部门', trigger: 'change' }
],
name: [
{ required: true, message: '请先选取用户', trigger: 'blur' }
],
username: [
{ required: true, message: '请先选取用户', trigger: 'blur' }
],
});
// 处理部门选择变化
const handleDeptChange = (list: any[]) => {
if (list && list.length > 0) {
const dept = list[0];
form.deptId = dept.deptId || dept.id || '';
form.deptName = dept.name || dept.deptName || '';
} else {
form.deptId = '';
form.deptName = '';
}
};
// 处理用户选择变化(选取用户后自动带出姓名、工号)
const handleUserChange = (list: any[]) => {
if (list && list.length > 0) {
const user = list[0];
form.userId = user.userId || user.id || '';
form.username = user.username || user.userName || '';
form.name = user.name || user.realName || '';
} else {
form.userId = '';
form.username = '';
form.name = '';
}
};
// 打开弹窗
const openDialog = async (id?: string) => {
visible.value = true;
form.id = '';
form.deptId = '';
form.deptName = '';
form.userId = '';
form.username = '';
form.name = '';
form.remark = '';
deptList.value = [];
userList.value = [];
nextTick(() => {
dataFormRef.value?.resetFields();
if (id) {
// 编辑时,先获取详情数据
loading.value = true;
getObj(id).then((res: any) => {
if (res.data && res.data.length > 0) {
const data = res.data[0];
Object.assign(form, {
id: data.id || '',
deptId: data.deptId || '',
deptName: data.deptName || '',
userId: data.userId || '',
username: data.username || '',
name: data.name || '',
remark: data.remark || '',
});
// 设置部门列表用于回显
if (data.deptId) {
deptList.value = [{
deptId: data.deptId,
name: data.deptName,
id: data.deptId,
type: 'dept',
}];
}
// 设置用户列表用于回显
if (data.userId) {
userList.value = [{
userId: data.userId,
username: data.username,
name: data.name,
id: data.userId,
userName: data.username,
realName: data.name,
type: 'user',
}];
}
}
loading.value = false;
}).catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
};
// 提交
const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
if (form.id) {
await putObj(form);
useMessage().success('编辑成功');
} else {
await addObj(form);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="部门名称" prop="deptName">
<el-input
v-model="state.queryForm.deptName"
placeholder="请输入部门名称"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="用户工号" prop="username">
<el-input
v-model="state.queryForm.username"
placeholder="请输入用户工号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input
v-model="state.queryForm.name"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
采购分管领导
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog()"
v-auth="'purchase_manager_add'">
新增
</el-button>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
class="ml10"
v-auth="'purchase_manager_del'"
@click="handleDelete(selectObjs)">
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'purchase_manager_export'"
@exportExcel="exportExcel"
class="ml10"
@queryTable="getDataList" />
</div>
</div>
</template>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
class="modern-table">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="部门名称" min-width="200" show-overflow-tooltip>
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">部门名称</span>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" min-width="120" show-overflow-tooltip>
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="username" label="用户工号" min-width="150" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">用户工号</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="300" show-overflow-tooltip>
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" show-overflow-tooltip>
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button
icon="Delete"
link
type="danger"
v-auth="'purchase_manager_del'"
@click="handleDelete([scope.row.id])">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-if="state.pagination"
v-show="state.pagination.total && state.pagination.total > 0"
:total="state.pagination.total"
:current="state.pagination.current"
:size="state.pagination.size"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
/>
</el-card>
</div>
<!-- 编辑新增表单对话框 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="PurchasingPurchaseManager">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/purchase/purchasingPurchaseManager";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, Document, User, UserFilled, EditPen, Clock, Search, OfficeBuilding } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义变量内容
const tableRef = ref()
const formDialogRef = ref()
const queryRef = ref()
const showSearch = ref(true)
const selectObjs = ref([]) as any
const multiple = ref(true)
/**
* 定义响应式表格数据
*/
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: fetchList,
queryForm: {
deptName: '',
username: '',
name: '',
},
createdIsNeed: true
});
/**
* 使用 useTable 定义表格相关操作
*/
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state);
/**
* 重置搜索表单
*/
const resetQuery = () => {
queryRef.value?.resetFields();
selectObjs.value = [];
multiple.value = true;
getDataList();
};
/**
* 导出Excel文件
*/
const exportExcel = () => {
downBlobFile(
'/purchasingPurchaseManager/export',
Object.assign(state.queryForm, { ids: selectObjs.value }),
'purchasingPurchaseManager.xlsx'
);
};
/**
* 表格多选事件处理
* @param objs 选中的数据行
*/
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
/**
* 删除当前行
* @param ids - 要删除的ID数组
*/
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?');
} catch {
return;
}
try {
await delObjs(ids);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -39,13 +39,15 @@
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><User /></el-icon>
领导党委管理
党委人员
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog('add')">
@click="formDialogRef.openDialog('add')"
v-auth="'purchase_purchasingSchoolLeader_add'"
>
新增
</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
@@ -103,7 +105,8 @@
<el-button
icon="Delete"
link
type="danger"
type="danger"
v-auth="'purchase_purchasingSchoolLeader_del'"
@click="handleDelete(scope.row)">
删除
</el-button>

View File

@@ -61,7 +61,8 @@ const uploadRef = ref<{ clearFiles?: () => void }>()
const titleMap: Record<string, string> = {
R10001: '计划专业导入',
R10002: '地区分数导入',
R10003: '中招平台数据导入'
R10003: '中招平台数据导入',
R10004: '学校维护导入',
}
// 方法
const init = (type: any) => {

View File

@@ -177,7 +177,7 @@ const remoteTeacherByQuery = (query: string) => {
serialNumberList.value = []
if (query !== '') {
setTimeout(() => {
getList({ groupId: dataForm.groupId, name: query }).then((response: any) => {
getList({ groupId: dataForm.groupId, serialNumber: query }).then((response: any) => {
serialNumberList.value = response.data
})
}, 200)

View File

@@ -85,7 +85,8 @@
type="warning"
link
icon="Download"
@click="handleExport(scope.row.batchCode, scope.row.groupId)"
:loading="btnLoading"
@click="handleExport(scope.row)"
>
导出模拟结果
</el-button>
@@ -132,6 +133,7 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
import { delObj, fetchList } from '/@/api/recruit/recruitImitateAdjustBatch'
import {exportAdjustExcel} from "/@/api/recruit/recruitfile"
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'))
const MnTable = defineAsyncComponent(() => import('./mnTable.vue'))
@@ -204,17 +206,14 @@ const showTable = (batchNo: string, groupId: string) => {
})
}
const btnLoading=ref(false)
// 导出模拟结果
const handleExport = async (code: string, gid: string) => {
try {
await downBlobFile(
'/recruit/recruitImitateAdjustBatch/exportExcel',
{ batchNo: code, groupId: gid },
'招生模拟统计.xls'
)
} catch (error: any) {
message.error(error.msg || '导出失败')
}
const handleExport = async (data:any) => {
btnLoading.value=true
exportAdjustExcel(data).then((res:any)=>{
messageBox.success('导出后台执行中,请稍后查看下载任务')
btnLoading.value=false
})
}
// 删除

View File

@@ -3,7 +3,7 @@
title="模拟列表"
:close-on-click-modal="false"
v-model="visible"
width="80%"
width="98%"
>
<el-form :inline="true">
<el-form-item>
@@ -109,6 +109,7 @@
<el-table-column
header-align="center"
align="center"
min-width="200"
label="操作">
<template #default="scope">
<el-button v-if="hasAuth('recruit_recruitImitateAdjustBatch_edit')" type="text" size="small" :icon="Edit" @click="addOrUpdateHandle(scope.row.id)">修改</el-button>

View File

@@ -0,0 +1,243 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="文件地址" prop="fileUrl">
<el-upload
:headers="headers"
:limit="1"
:action="uploadUrl"
:file-list="fileList"
:on-success="handleUploadSuccess"
:accept="'.pdf'"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<div style="margin-top: 8px;">
<el-tag>仅支持pdf后缀的文件上传</el-tag>
</div>
</template>
</el-upload>
<!-- <el-upload-->
<!-- :action="uploadUrl"-->
<!-- class="avatar-uploader"-->
<!-- name="file"-->
<!-- :headers="headers"-->
<!-- :data="uploadData"-->
<!-- :file-list="fileList"-->
<!-- :before-upload="beforeUpload"-->
<!-- :http-request="httpRequest"-->
<!-- :limit="1"-->
<!-- :accept="['.jpg,.jpeg,.png,.pdf']"-->
<!-- :on-success="handleUploadSuccess">-->
<!-- <el-button size="small" type="primary">点击上传</el-button>-->
<!-- <template #tip>-->
<!-- <div style="margin-top: 8px;">-->
<!-- <el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>-->
<!-- </div>-->
<!-- </template>-->
<!-- </el-upload>-->
</el-form-item>
</el-col>
<!-- <el-col :span="12" class="mb20">-->
<!-- <el-form-item label="1 资助政策文件" prop="type">-->
<!-- <el-input v-model="form.type" placeholder="请输入1 资助政策文件"/>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="RecruitPolicyFileDialog">
// ========== 1. 导入语句 ==========
import { useMessage } from "/@/hooks/message";
import { getObj, addObj, putObj } from '/@/api/recruit/recruitPolicyFile';
import {Plus} from "@element-plus/icons-vue";
import {reactive, ref} from "vue";
import axios from "axios";
import { Session } from '/@/utils/storage'
const fileList = ref<any[]>([])
// ========== 2. 组件定义 ==========
// 定义组件事件
const emit = defineEmits(['refresh']);
// ========== 3. 响应式数据定义 ==========
// 基础响应式变量
const dataFormRef = ref(); // 表单引用
const visible = ref(false); // 弹窗显示状态
const loading = ref(false); // 加载状态
const baseUrl = import.meta.env.VITE_API_URL
// 表单数据对象
const form = reactive({
id: '', // 主键
fileUrl: '', // 文件地址
title: '', // 标题
type: '', // 1 资助政策文件
});
const uploadUrl = baseUrl + '/recruit/file/uploadPdf'
// 请求头
const headers = computed(() => {
return {
"Authorization": 'Bearer ' + Session.getToken()
}
})
const uploadData = reactive({})
const fileReader = ref<FileReader | null>(null)
// 上传前验证
const beforeUpload = (file: File) => {
const isLt5M = file.size < 10 * 1024 * 1024
if (!isLt5M) {
useMessage().error('文件大小不能超过10M')
return false
}
return true
}
// 通用上传成功回调(单文件 - avatar模式
const handleUploadSuccess = (res:any) => {
const fileUrl = res.fileUrl
form['fileUrl'] = fileUrl
}
// ========== 4. 字典数据处理 ==========
// ========== 5. 表单校验规则 ==========
const dataRules = ref({
title: [
{ required: true, message: '请输入标题', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' },
],
fileUrl: [
{ required: true, message: '请上传文件', trigger: 'blur' },
]
});
// ========== 6. 方法定义 ==========
// 获取详情数据
const getRecruitPolicyFileData = async (id: string) => {
try {
loading.value = true;
const { data } = await getObj({ id: id });
// 直接将第一条数据赋值给表单
Object.assign(form, data[0]);
} catch (error) {
useMessage().error('获取数据失败');
} finally {
loading.value = false;
}
};
// 打开弹窗方法
const openDialog = (id: string) => {
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取RecruitPolicyFile信息
if (id) {
form.id = id;
getRecruitPolicyFileData(id);
}
};
// 提交表单方法
const onSubmit = async () => {
loading.value = true; // 防止重复提交
// 表单校验
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
// 根据是否有ID判断是新增还是修改
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh'); // 通知父组件刷新列表
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化 FileReader
onMounted(() => {
if (!window.FileReader) {
useMessage().error('您的浏览器不支持 FileReader API!')
} else {
fileReader.value = new FileReader()
}
})
// ========== 7. 对外暴露 ==========
// 暴露方法给父组件
defineExpose({
openDialog
});
</script>
<style lang="scss" scoped>
//.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

@@ -0,0 +1,232 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮区域 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="folder-add"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()"
v-auth="'recruit_recruitPolicyFile_add'"
>
新增
</el-button>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
v-auth="'recruit_recruitPolicyFile_del'"
@click="handleDelete(selectObjs)"
>
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'recruit_recruitPolicyFile_export'"
@exportExcel="exportExcel"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList"
/>
</div>
</el-row>
<!-- 数据表格区域 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column
prop="fileUrl"
label="文件"
show-overflow-tooltip
>
<template #default="scope">
<el-button
type="primary"
link
icon="Document"
@click="handlePreview(scope.row.fileUrl)"
>查看
</el-button>
</template>
</el-table-column>
<el-table-column
prop="title"
label="标题"
show-overflow-tooltip
/>
<!-- <el-table-column -->
<!-- prop="type" -->
<!-- label="1 资助政策文件" -->
<!-- show-overflow-tooltip-->
<!-- />-->
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
icon="edit-pen"
text
type="primary"
v-auth="'recruit_recruitPolicyFile_edit'"
@click="formDialogRef.openDialog(scope.row.id)"
>
编辑
</el-button>
<el-button
icon="delete"
text
type="primary"
v-auth="'recruit_recruitPolicyFile_del'"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination"
/>
</div>
<!-- 编辑新增弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 导入excel弹窗 (需要在 upms-biz/resources/file 下维护模板) -->
<upload-excel
ref="excelUploadRef"
title="导入"
url="/recruit/recruitPolicyFile/import"
temp-url="/admin/sys-file/local/file/recruitPolicyFile.xlsx"
@refreshDataList="getDataList"
/>
<preview-file v-for="src in imgUrl" :key="src.title" :authSrc="src.url" dialog-title="职称材料" />
</div>
</template>
<script setup lang="ts" name="systemRecruitPolicyFile">
// ========== 导入声明 ==========
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/recruit/recruitPolicyFile";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
import {defineAsyncComponent, ref} from "vue";
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'));
// ========== 组件声明 ==========
// 异步加载表单弹窗组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const baseUrl = import.meta.env.VITE_API_URL
// ========== 字典数据 ==========
// ========== 组件引用 ==========
const formDialogRef = ref(); // 表单弹窗引用
const excelUploadRef = ref(); // Excel上传弹窗引用
const queryRef = ref(); // 查询表单引用
// ========== 响应式数据 ==========
const showSearch = ref(true); // 是否显示搜索区域
const selectObjs = ref([]) as any; // 表格多选数据
const multiple = ref(true); // 是否多选
// ========== 表格状态 ==========
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, // 查询参数
pageList: fetchList // 分页查询方法
});
const imgUrl = ref<Array<{ title: string; url: string }>>([]);
// ========== Hook引用 ==========
// 表格相关Hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state);
// ========== 方法定义 ==========
/**
* 重置查询条件
*/
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields();
// 清空多选
selectObjs.value = [];
// 重新查询
getDataList();
};
/**
* 导出Excel文件
*/
const exportExcel = () => {
downBlobFile(
'/recruit/recruitPolicyFile/export',
Object.assign(state.queryForm, { ids: selectObjs }),
'recruitPolicyFile.xlsx'
);
};
/**
* 表格多选事件处理
* @param objs 选中的数据行
*/
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 预览材料
const handlePreview = (url: string) => {
imgUrl.value = [];
nextTick(() => {
imgUrl.value.push({
title: '',
url: baseUrl+url,
});
});
};
/**
* 删除数据处理
* @param ids 要删除的数据ID数组
*/
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
</script>

View File

@@ -0,0 +1,129 @@
<!--
- 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>
<el-dialog v-model="dialogVisible" title="保存审核人员" width="600px" append-to-body @close="handleClose">
<el-form :model="{ belongTeacherNos }" label-width="100px">
<el-form-item label="选择教师:">
<el-select
v-model="belongTeacherNos"
filterable
remote
clearable
reserve-keyword
placeholder="请选择或输入教师姓名"
:remote-method="remoteTeacherByQuery"
style="width: 100%"
>
<el-option
v-for="item in teacherList"
:key="item.teacherNo"
:label="item.realName"
:value="item.teacherNo"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="AddExamPeopleForm">
import { ref, watch } from 'vue'
import {getTeacherInfoCommon} from '/@/api/professional/professionaluser/teacherbase'
// Props
const props = defineProps<{
visible: boolean
timeRange: string[]
}>()
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'save', data: { teacherNo: string; startTime: string; endTime: string }): void
}>()
// 弹窗显示状态
const dialogVisible = ref(false)
const belongTeacherNos = ref('')
const teacherList = ref<any[]>([])
// 监听 visible 变化
watch(() => props.visible, (newVal) => {
dialogVisible.value = newVal
if (newVal) {
belongTeacherNos.value = ''
teacherList.value = []
}
})
// 监听 dialogVisible 变化,同步到父组件
watch(dialogVisible, (newVal) => {
emit('update:visible', newVal)
})
// 检索教师
const remoteTeacherByQuery = (query: string) => {
teacherList.value = []
if (query !== '') {
setTimeout(() => {
getTeacherInfoCommon({searchKeywords:query,tied:"0"}).then(response => {
teacherList.value = response.data
})
}, 200)
}
}
// 关闭窗口
const handleClose = () => {
belongTeacherNos.value = ''
dialogVisible.value = false
}
// 保存
const handleSave = () => {
if (props.timeRange.length === 0) {
emit('save', {
teacherNo: belongTeacherNos.value,
startTime: '',
endTime: ''
})
return
}
emit('save', {
teacherNo: belongTeacherNos.value,
startTime: props.timeRange[0],
endTime: props.timeRange[1]
})
handleClose()
}
</script>
<style lang="scss" scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@@ -0,0 +1,225 @@
<!--
- 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>
<el-dialog
title="招生审核人员管理"
:close-on-click-modal="false"
v-model="visible"
width="800"
:append-to-body="true"
destroy-on-close
>
<div class="dialog-content">
<!-- 时间选择器和操作按钮 -->
<el-form :inline="true" :model="queryForm">
<el-form-item label="审核时间范围:">
<el-time-picker
is-range
v-model="form.time1"
format="HH:mm:ss"
value-format="HH:mm:ss"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择时间范围"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="mb15" v-if="hasAuth('recruit_recruitexampeople_add')">
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
>
</el-button>
</div>
<!-- 表格 -->
<div class="table-wrapper">
<el-table
ref="tableRef"
:data="state.dataList || []"
v-loading="state.loading"
border
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
empty-text="暂无数据"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="工号" align="center" prop="teacherNo" show-overflow-tooltip>
</el-table-column>
<el-table-column label="姓名" align="center" prop="teacherName" show-overflow-tooltip>
</el-table-column>
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
v-if="hasAuth('recruit_recruitexampeople_del')"
type="danger"
link
icon="Delete"
@click="handleDel(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
</div>
</div>
<!-- 保存审核人员弹窗 -->
<add-form
v-model:visible="setTeachNoFormVisible"
:time-range="form.time1"
@save="handleSave"
/>
</el-dialog>
</template>
<script setup lang="ts" name="recruitexampeople">
import { ref, reactive, nextTick, defineAsyncComponent } from 'vue'
import { useAuth } from '/@/hooks/auth'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { addObj, delObj, fetchList } from '/@/api/recruit/recruitPreexamPeople'
const AddForm = defineAsyncComponent(() => import('./add-form.vue'))
const { hasAuth } = useAuth()
// 消息提示 hooks
const message = useMessage()
const messageBox = useMessageBox()
// 表格引用
const tableRef = ref()
// 弹窗状态
const visible = ref(false)
const setTeachNoFormVisible = ref(false)
// 表单数据
const form = reactive({
time1: [] as string[]
})
// 查询表单
const queryForm = reactive<Record<string, any>>({})
// 表格状态
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: queryForm,
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records || [],
total: response.data.total || 0
}
}
},
createdIsNeed: false, // 弹窗组件,不在挂载时自动加载数据
dataList: [], // 确保 dataList 初始化为空数组
loading: false, // 确保 loading 初始化为 false
onLoaded: async (state: any) => {
// 如果有数据,设置时间范围
if (state.dataList && state.dataList.length > 0) {
form.time1 = [state.dataList[0].startTime, state.dataList[0].endTime]
}
}
})
// 使用 table hook
// 注意useTable 会直接修改传入的 state 对象,所以不需要从返回值中获取 state
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
// 初始化
const init = () => {
visible.value = true
form.time1 = []
// 等待弹窗显示后再加载数据
nextTick(() => {
getDataList()
})
}
// 新增
const handleAdd = () => {
setTeachNoFormVisible.value = true
}
// 保存审核人员
const handleSave = async (data: { teacherNo: string; startTime: string; endTime: string }) => {
if (form.time1.length === 0) {
message.error('审核时间不能为空')
return
}
try {
await addObj({
teacherNo: data.teacherNo,
startTime: data.startTime,
endTime: data.endTime
})
message.success('添加成功')
getDataList()
} catch (error: any) {
message.error(error.msg || '添加失败')
}
}
// 删除
const handleDel = async (row: any) => {
try {
await messageBox.confirm(`是否确认删除工号为${row.teacherNo}的记录?`)
await delObj(row.id)
message.success('删除成功')
getDataList()
} catch {
// 用户取消
}
}
// 重置查询
const resetQuery = () => {
Object.keys(queryForm).forEach(key => {
queryForm[key] = ''
})
getDataList()
}
// 暴露方法
defineExpose({
init
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -79,6 +79,15 @@
</el-table-column>
<el-table-column prop="majorCode" label="专业序号" align="center" show-overflow-tooltip />
<el-table-column prop="majorName" label="专业名称" align="center" show-overflow-tooltip />
<el-table-column prop="majorName" label="规范专业名称" align="center" show-overflow-tooltip >
<template #default="scope">
<span v-if="scope.row.standardMajorName">
{{ scope.row.standardMajorName+"-"+scope.row.standardMajorTwoName }}
</span>
<span v-else>
</span>
</template>
</el-table-column>
<el-table-column prop="deptCode" label="学院" align="center" show-overflow-tooltip>
<template #default="scope">
{{ getDeptName(scope.row.deptCode) }}

View File

@@ -92,7 +92,8 @@
>
新增
</el-button>
<el-button
<el-button
v-auth="'pre_stu_success_export'"
type="warning"
plain
icon="Download"
@@ -102,6 +103,10 @@
>
导出
</el-button>
<el-button v-if="hasAuth('recruit_preexamPeople_add')" type="primary" plain icon="UserFilled" class="ml10" @click="editExam">
审核人员管理
</el-button>
</div>
<!-- 表格 -->
@@ -191,6 +196,8 @@
:planMajorList="planMajorList"
:schoolList="schoolList"
/>
<pre-exam-people-index ref="PreExamPeopleIndexRef"></pre-exam-people-index>
</div>
</div>
</template>
@@ -207,7 +214,11 @@ import { getList } from '/@/api/recruit/recruitstudentplangroup'
import { listcz } from '/@/api/recruit/recruitstudentplan'
import { queryByGroupId as schoolListApi} from '/@/api/recruit/recruitstudentschool'
import { getDeptListByLevelTwo } from '/@/api/basic/basicdept'
import {exportPreStuSuccess} from '/@/api/recruit/recruitfile'
const PreExamPeopleIndex = defineAsyncComponent(() => import('@/views/recruit/recruitPreexamPeople/index.vue'));
const PreExamPeopleIndexRef=ref()
const TableForm = defineAsyncComponent(() => import('./enrolplantemplate-form.vue'))
const { hasAuth } = useAuth()
// 消息提示 hooks
@@ -366,20 +377,30 @@ const resetQuery = () => {
// 导出
const dataExportHandle = async () => {
try {
exportLoading.value = true
await downBlobFile(
'/recruit/recruitprestudent/export',
queryForm,
'预登记导出.xls'
)
} catch (error: any) {
message.error(error.msg || '导出失败')
} finally {
exportLoading.value = true
exportPreStuSuccess(queryForm).then((res:any)=>{
message.success('导出后台执行中,请稍后查看下载任务')
exportLoading.value = false
}
})
}
// 编辑审核人员
const editExam = () => {
// 如果组件已经加载,立即初始化
if (PreExamPeopleIndexRef.value && typeof PreExamPeopleIndexRef.value.init === 'function') {
nextTick(() => {
try {
PreExamPeopleIndexRef.value.init();
} catch (error: any) {
message.error('初始化预登记人员弹窗失败:' + (error.message || '未知错误'));
}
});
}
// 否则等待 watch 监听器处理
};
onMounted(() => {
init()
})

View File

@@ -42,13 +42,29 @@
<!-- 操作按钮 -->
<div class="mb15">
<el-button
v-if="hasAuth('recruit_recruitstudentplangroup_add')"
v-if="hasAuth('recruit_recruitstudentschool_add')"
type="primary"
icon="FolderAdd"
@click="addOrUpdateHandle"
>
</el-button>
<el-button
v-auth="'recruit_studentschool_import'"
type="primary"
plain
icon="UploadFilled"
@click="handleImportDialog"
>导入信息
</el-button>
<el-button
v-if="hasAuth('recruit_schoolhistory_view')"
type="plain"
icon="View"
@click="handleShowHistory"
>
变更历史
</el-button>
</div>
<!-- 表格 -->
@@ -109,6 +125,11 @@
<!-- 弹窗, 新增 / 修改 -->
<table-form ref="addOrUpdateRef" @refreshDataList="getDataList" />
<major-group-by-dept-form v-if="majorGroupByDeptVisible" ref="majorGroupByDeptRef" />
<school-history ref="SchoolHistoryRef"></school-history>
<import-recruit-info ref="ImportRecruitInfoRef" @refreshDataList="getDataList"></import-recruit-info>
</div>
</div>
</template>
@@ -124,11 +145,16 @@ import { getDeptList } from '/@/api/basic/basicclass'
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'))
const MajorGroupByDeptForm = defineAsyncComponent(() => import('/@/views/recruit/recruitplanmajor/majorGroupByDept.vue'))
const ImportRecruitInfo = defineAsyncComponent(() => import('/@/views/recruit/common/import-recruit-info.vue'));
const SchoolHistory = defineAsyncComponent(() => import('/@/views/recruit/recruitstudentschool/school-history.vue'))
const { hasAuth } = useAuth()
// 消息提示 hooks
const message = useMessage()
const messageBox = useMessageBox()
const ImportRecruitInfoRef=ref<any>();
const SchoolHistoryRef=ref()
// 表格引用
const tableRef = ref()
const searchFormRef = ref()
@@ -236,6 +262,14 @@ const resetQuery = () => {
getDataList()
}
const handleShowHistory=()=>{
SchoolHistoryRef.value?.init()
}
const handleImportDialog = () => {
ImportRecruitInfoRef.value?.init("R10004");
};
onMounted(() => {
init()
})

View File

@@ -0,0 +1,63 @@
<template>
<el-dialog v-model="visible" width="80%" title="学校变更历史">
<el-table ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
stripe
row-key="id"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column label="学校代码" prop="schoolCode"></el-table-column>
<el-table-column label="旧名称" prop="oldName"></el-table-column>
<el-table-column label="新名称" prop="newName"></el-table-column>
<el-table-column label="变动时间" prop="createTime"></el-table-column>
</el-table>
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
</el-dialog>
</template>
<script setup lang="ts">
import {useTable,BasicTableProps} from "/@/hooks/table";
import {fetchList} from "/@/api/recruit/recruitSchoolHistory";
// 表格状态
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
createdIsNeed: false,
pageList: async (params: any) => {
const response = await fetchList(params)
return {
data: {
records: response.data.records,
total: response.data.total
}
}
},
createdIsNeed: false
})
const visible = ref(false)
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
const init=()=>{
visible.value = true
getDataList()
}
defineExpose({
init
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,9 +1,8 @@
<template>
<div class="mod-config">
<basic-container>
<div >
<el-form :inline="true" :model="dataForm" @keyup.enter="handleFilter" ref="searchFormRef">
<el-form-item label="招生计划" prop="groupId">
<el-select v-model="dataForm.groupId" filterable placeholder="请选择招生计划" size="small">
<el-select v-model="dataForm.groupId" filterable placeholder="请选择招生计划" >
<el-option
v-for="item in planList"
:key="item.id"
@@ -14,10 +13,10 @@
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" size="small"
<el-button icon="Search" type="primary"
@click="handleFilter">查询
</el-button>
<el-button icon="Delete" type="default" plain size="small"
<el-button icon="Delete" type="default" plain
@click="resetForm">清空
</el-button>
</el-form-item>
@@ -27,27 +26,24 @@
<el-button icon="Download" type="warning" size="small" @click="dataExportHandle">导出</el-button>
</el-form-item>
</el-form>
<div class="avue-crud">
<el-table :data="dataList" border stripe v-loading="dataListLoading"
:summary-method="getSummaries" show-summary
:summary-method="getSummaries" show-summary height="700"
>
<el-table-column align="center" header-align="center" prop="provinceName" label="省市" />
<el-table-column align="center" header-align="center" prop="peopleNum" label="人数" />
<el-table-column align="center" header-align="center" prop="peopleRate" label="占比" />
</el-table>
<chart ref="typeEchartBarRef" style="width:100%;margin-top:80px" :options="chartOption" theme="macarons"></chart>
<!-- <chart ref="typeEchartBarRef" style="width:100%;margin-top:80px" :options="chartOption" theme="macarons"></chart>-->
</div>
</basic-container>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import axios from 'axios'
import { getAreaStatic } from "@/api/recruit/recruitstudentsignup"
import { getList } from "@/api/recruit/recruitstudentplangroup"
import { getAreaStatic } from '/@/api/recruit/recruitstudentsignup'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
// 响应式数据
const dataForm = reactive({
@@ -65,11 +61,13 @@ const typeEchartBarRef = ref()
// 初始化
const init = () => {
getList().then((data: any) => {
planList.value = data.data
planList.value = Array.isArray(data?.data) ? data.data : []
if (planList.value.length > 0) {
dataForm.groupId = planList.value[0].id
}
getDataList()
}).catch(() => {
planList.value = []
})
}
@@ -79,8 +77,26 @@ const getDataList = () => {
dataForm.degreeOfEducation = '1'
dataListLoading.value = true
getAreaStatic(dataForm).then((response: any) => {
dataList.value = response.data
chartOption.value = response.data.option
const res = response?.data
// 兼容:接口可能返回 { data: [], option: {} } 或直接返回数组
dataList.value = Array.isArray(res) ? res : (res?.data || res?.records || res?.list || [])
const rawOption = Array.isArray(res) ? null : (res?.option ?? null)
// 确保 chart 的 option 合法series[].data 必须为数组,避免 .includes 报错
if (rawOption && typeof rawOption === 'object') {
const option = { ...rawOption }
if (Array.isArray(option.series)) {
option.series = option.series.map((s: any) => ({
...s,
data: Array.isArray(s?.data) ? s.data : []
}))
}
if (option.data !== undefined && !Array.isArray(option.data)) {
option.data = []
}
chartOption.value = option
} else {
chartOption.value = {}
}
dataListLoading.value = false
}).catch(() => {
dataListLoading.value = false

View File

@@ -1,9 +1,8 @@
<template>
<div class="mod-config">
<basic-container>
<div>
<el-form :inline="true" :model="dataForm" @keyup.enter="handleFilter" ref="searchFormRef">
<el-form-item label="招生计划" prop="groupId">
<el-select v-model="dataForm.groupId" filterable placeholder="请选择招生计划" size="small">
<el-select v-model="dataForm.groupId" filterable placeholder="请选择招生计划" >
<el-option
v-for="item in planList"
:key="item.id"
@@ -14,30 +13,26 @@
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" size="small"
<el-button icon="Search" type="primary"
@click="handleFilter">查询
</el-button>
<el-button icon="Delete" type="default" plain size="small"
<el-button icon="Delete" type="default" plain
@click="resetForm">清空
</el-button>
</el-form-item>
</el-form>
<el-form>
<el-form-item>
<el-button icon="Download" type="warning" size="small" @click="dataExportHandle">导出</el-button>
<el-button icon="Download" type="warning" @click="dataExportHandle">导出</el-button>
</el-form-item>
</el-form>
<div class="avue-crud">
<el-table :data="dataList" border stripe v-loading="dataListLoading"
:summary-method="getSummaries" show-summary
>
<el-table :data="dataList" border stripe v-loading="dataListLoading" height="700"
:summary-method="getSummaries" show-summary>
<el-table-column align="center" header-align="center" prop="deptName" label="部门" />
<el-table-column align="center" header-align="center" prop="allNum" label="合计" />
<el-table-column align="center" header-align="center" prop="schoolNum" label="学校推荐" />
<el-table-column align="center" header-align="center" prop="contantNum" label="联系人" />
</el-table>
</div>
</basic-container>
</div>
</template>

View File

@@ -1,9 +1,8 @@
<template>
<div class="mod-config">
<basic-container>
<el-form :inline="true" :model="dataForm" @keyup.enter="handleFilter" ref="searchFormRef">
<el-form-item label="招生计划" prop="groupId">
<el-select v-model="dataForm.groupId" filterable placeholder="请选择招生计划" size="small">
<el-select v-model="dataForm.groupId" filterable placeholder="请选择招生计划">
<el-option
v-for="item in planList"
:key="item.id"
@@ -14,17 +13,17 @@
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" size="small"
<el-button icon="Search" type="primary"
@click="handleFilter">查询
</el-button>
<el-button icon="Delete" type="default" plain size="small"
<el-button icon="Delete" type="default" plain
@click="resetForm">清空
</el-button>
</el-form-item>
</el-form>
<el-form>
<el-form-item>
<el-button icon="Download" type="warning" size="small" @click="dataExportHandle">导出</el-button>
<el-button icon="Download" type="warning" @click="dataExportHandle">导出</el-button>
</el-form-item>
</el-form>
<div class="avue-crud">
@@ -40,7 +39,6 @@
<el-table-column align="center" header-align="center" prop="allNum" label="高中+技职校" />
</el-table>
</div>
</basic-container>
</div>
</template>

View File

@@ -671,8 +671,9 @@
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit('0')" v-if="hasAuth('recruit_recruitstudentsignup_add') && canSubmit&&!dataForm.id">保存并送审</el-button>
<el-button type="primary" @click="dataFormSubmit('0')" v-if="hasAuth('recruit_recruitstudentsignup_edit') && canSubmit&&dataForm.id">保存</el-button>
<el-button type="success" icon="CircleCheck" @click="dataFormSubmit('20')" v-if="hasAuth('signup_info_exam') && canSubmit&&dataForm.id">确认录取</el-button>
<el-button type="danger" icon="CircleClose" @click="dataFormSubmit('-20')" v-if="hasAuth('signup_info_exam') && canSubmit&&dataForm.id">驳回录取</el-button>
<el-button type="success" icon="CircleCheck" @click="dataFormSubmit('20',false)" v-if="hasAuth('signup_info_exam') && canSubmit&&dataForm.id">确认录取</el-button>
<el-button type="warning" icon="CircleCheck" @click="dataFormSubmit('20',true)" v-if="hasAuth('signup_pass_force') && canSubmit&&dataForm.id">强制录取</el-button>
<el-button type="danger" icon="CircleClose" @click="dataFormSubmit('-20',false)" v-if="hasAuth('signup_info_exam') && canSubmit&&dataForm.id">驳回</el-button>
</div>
</template>
@@ -1493,7 +1494,7 @@ const updateTuitionByMajorAndEducation = () => {
}
// 表单提交
const dataFormSubmit = (state: string) => {
const dataFormSubmit = (state: string,force:any) => {
dataForm.auditStatus = state
let submitTitle = "确认通过该学生的报名申请么?"
// 新增模式
@@ -1545,6 +1546,7 @@ const dataFormSubmit = (state: string) => {
canSubmit.value = false
if (dataForm.id) {
// 编辑模式
dataForm.force=force
putObj(dataForm).then(() => {
message.success('操作成功')
visible.value = false

View File

@@ -289,6 +289,15 @@
@click="handleImportDialog"
>导入中招平台数据
</el-button>
<el-button
v-auth="'recruit_sign_sync'"
type="warning"
plain
icon="Refresh"
:loading="exportLoading"
@click="handleSyncData"
>一键同步省人社
</el-button>
<!-- <el-button-->
<!-- v-if="hasAuth('zipExport')"-->
<!-- type="warning"-->
@@ -1158,6 +1167,16 @@ const handleImportDialog = () => {
ImportRecruitInfoRef.value?.init("R10003");
};
const exportLoading=ref(false);
const handleSyncData=()=>{
exportLoading.value=true;
setTimeout(() => {
exportLoading.value=false;
message.success('同步成功');
}, 3000);
}
onMounted(() => {
init()
})

View File

@@ -62,7 +62,7 @@
<el-table-column prop="minScore" header-align="center" align="center" label="最低分" />
<el-table-column prop="avgScore" header-align="center" align="center" label="平均分" />
<el-table-column prop="majorPeopleNum" header-align="center" align="center" label="专业人数" />
<el-table-column prop="avgScoreDB" header-align="center" align="center" label="均分对比" />
<!-- <el-table-column prop="avgScoreDB" header-align="center" align="center" label="均分对比" />-->
</el-table>
</div>
</div>
@@ -76,12 +76,13 @@ import { useDict } from '/@/hooks/dict'
import { juniorlneStatic } from '/@/api/recruit/recruitstudentsignup'
import { getList } from '/@/api/recruit/recruitstudentplangroup'
import { getDeptListByLevelTwo } from '/@/api/basic/basicdept'
import {getDictsByTypes} from "/@/api/admin/dict";
// 消息提示 hooks
const message = useMessage()
// 字典
const { getTypeValue } = useDict()
// const { useDict } = useDict()
// 引用
const searchFormRef = ref()
@@ -104,19 +105,24 @@ const queryForm = reactive({
// 使用 table hook 获取样式
const { tableStyle, downBlobFile } = useTable()
const queryDictData=()=>{
getDictsByTypes(['basic_major_years']).then((res:any)=>{
majorYears.value = res.data.basic_major_years || []
})
}
// 初始化
const init = async () => {
try {
const [deptResponse, majorYearsRes, planData] = await Promise.all([
queryDictData()
const [deptResponse, planData] = await Promise.all([
getDeptListByLevelTwo(),
getTypeValue('basic_major_years'),
getList()
])
deptCodes.value = deptResponse.data || []
majorYears.value = majorYearsRes.data || []
planList.value = planData.data || []
if (planList.value.length > 0) {
queryForm.groupId = planList.value[0].id
getDataList()

View File

@@ -102,7 +102,8 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit" v-if="canSubmit">确认修改</el-button>
<el-button type="primary" @click="dataFormSubmit(false)" v-if="canSubmit">确认修改</el-button>
<el-button type="warning" @click="dataFormSubmit(true)" v-if="canSubmit" v-auth="'recruit_signup_change_force'">强制修改</el-button>
</div>
</template>
</el-dialog>
@@ -272,12 +273,13 @@ const changeCM = (id: string) => {
// 表单提交
const dataFormSubmit = async () => {
const dataFormSubmit = async (force:any) => {
const titleText = "确认调整录取专业么?"
if (dataForm.confirmedMajor == dataForm.newConfirmedMajor) {
message.error('新专业不能和原专业相同')
return
}
dataForm.force=force
try {
await messageBox.confirm(titleText)
dataFormRef.value?.validate((valid: boolean) => {

View File

@@ -183,9 +183,10 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit('1')" v-if="hasAuth('recruit_recruitstudentsignup_edit') && canSubmit">保存</el-button>
<el-button type="success" icon="CircleCheck" @click="dataFormSubmit('2')" v-if="hasAuth('signup_material_exam') && canSubmit">通过</el-button>
<el-button type="danger" icon="CircleClose" @click="dataFormSubmit('3')" v-if="hasAuth('signup_material_exam') && canSubmit">驳回</el-button>
<el-button type="primary" @click="dataFormSubmit('1',false)" v-if="hasAuth('recruit_recruitstudentsignup_edit') && canSubmit">保存</el-button>
<el-button type="success" icon="CircleCheck" @click="dataFormSubmit('2',false)" v-if="hasAuth('signup_material_exam') && canSubmit">通过</el-button>
<el-button type="success" icon="CircleCheck" @click="dataFormSubmit('2',true)" v-if="hasAuth('signup_material_pass_force') && canSubmit">强制通过</el-button>
<el-button type="danger" icon="CircleClose" @click="dataFormSubmit('3',false)" v-if="hasAuth('signup_material_exam') && canSubmit">驳回</el-button>
</div>
</template>
@@ -412,13 +413,14 @@ const initData = () => {
}
// 表单提交
const dataFormSubmit = (submitType: string) => {
const dataFormSubmit = (submitType: string,force:any) => {
if ((dataForm.zlshRemark == '' || !dataForm.zlshRemark) && submitType == '3') {
message.error('请填写驳回理由')
return
}
dataForm.zlsh = submitType
canSubmit.value = false
dataForm.force=force
materialExam(dataForm).then(() => {
message.success('操作成功')
visible.value = false

View File

@@ -201,7 +201,8 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelPlace">取消</el-button>
<el-button type="primary" @click="update">保存</el-button>
<el-button type="primary" @click="update(false)">保存</el-button>
<el-button type="warning" @click="update(true)" v-auth="'recruit_signup_change_force'">强制保存</el-button>
</div>
</template>
</el-dialog>
@@ -273,7 +274,8 @@ const exarmForm = reactive({
isMajorChange: '',
examRemark: '',
groupId: '',
remarks: ''
remarks: '',
force:false
})
// 表单验证规则
@@ -340,11 +342,11 @@ const cancelPlace = () => {
}
// 保存审核
const update = async () => {
const update = async (force:any) => {
try {
const valid = await exarmFormRef.value?.validate().catch(() => {})
if (!valid) return
exarmForm.force=force
await putObj(exarmForm)
message.success('审核成功')
majorChangeVisible.value = false