a
This commit is contained in:
79218
docs/默认模块.openapi.json
79218
docs/默认模块.openapi.json
File diff suppressed because it is too large
Load Diff
28
src/App.vue
28
src/App.vue
@@ -4,6 +4,7 @@
|
||||
<LockScreen v-if="themeConfig.isLockScreen" />
|
||||
<Settings ref="settingsRef" v-show="themeConfig.lockScreenTime > 1" />
|
||||
<CloseFull v-if="!themeConfig.isLockScreen" />
|
||||
<ChangeRole ref="changeRoleFirRef" title="请选择角色" :require-select-to-close="true" />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
@@ -14,16 +15,19 @@ import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import { Local, Session } from '/@/utils/storage';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { needRoleSelection, isRoleDialogTriggered, setRoleDialogTriggered } from '/@/utils/roleSelect';
|
||||
import setIntroduction from '/@/utils/setIconfont';
|
||||
|
||||
// 引入组件
|
||||
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
|
||||
const Settings = defineAsyncComponent(() => import('./layout/navBars/breadcrumb/settings.vue'));
|
||||
const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue'));
|
||||
const ChangeRole = defineAsyncComponent(() => import('/@/views/admin/system/role/change-role.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const { messages, locale } = useI18n();
|
||||
const settingsRef = ref();
|
||||
const changeRoleFirRef = ref<{ open: () => void }>();
|
||||
const route = useRoute();
|
||||
const stores = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
@@ -44,6 +48,7 @@ const getGlobalComponentSize = computed(() => {
|
||||
const getGlobalI18n = computed(() => {
|
||||
return messages.value[locale.value];
|
||||
});
|
||||
|
||||
// 设置初始化,防止刷新时恢复默认
|
||||
onBeforeMount(() => {
|
||||
// 设置批量第三方 icon 图标
|
||||
@@ -51,10 +56,19 @@ onBeforeMount(() => {
|
||||
// 设置批量第三方 js
|
||||
setIntroduction.jsCdn();
|
||||
});
|
||||
// 页面加载时
|
||||
// 角色选择弹框是否已在本轮打开过(防止事件被触发两次)
|
||||
let roleDialogOpenedThisSession = false
|
||||
onMounted(() => {
|
||||
// 唯一入口:只通过事件打开,且只打开一次;延迟打开以等待异步组件挂载
|
||||
mittBus.on('openRoleSelectDialog', () => {
|
||||
if (roleDialogOpenedThisSession) return
|
||||
roleDialogOpenedThisSession = true
|
||||
setTimeout(() => {
|
||||
changeRoleFirRef.value?.open()
|
||||
}, 300)
|
||||
})
|
||||
nextTick(() => {
|
||||
// 监听布局配'置弹窗点击打开
|
||||
// 监听布局配置弹窗点击打开
|
||||
mittBus.on('openSettingsDrawer', () => {
|
||||
settingsRef.value.openDrawer();
|
||||
});
|
||||
@@ -67,11 +81,17 @@ onMounted(() => {
|
||||
if (Session.get('isTagsViewCurrenFull')) {
|
||||
stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull'));
|
||||
}
|
||||
});
|
||||
// 与请求拦截器共用同一逻辑:先设标志再 emit,由监听器统一打开(监听器内会延迟 300ms 以等待异步组件挂载)
|
||||
if (Session.getToken() && needRoleSelection() && !isRoleDialogTriggered()) {
|
||||
setRoleDialogTriggered(true)
|
||||
mittBus.emit('openRoleSelectDialog')
|
||||
}
|
||||
})
|
||||
});
|
||||
// 页面销毁时,关闭监听布局配置/i18n监听
|
||||
// 页面销毁时,关闭监听
|
||||
onUnmounted(() => {
|
||||
mittBus.off('openSettingsDrawer', () => {});
|
||||
mittBus.off('openRoleSelectDialog');
|
||||
});
|
||||
// 监听路由的变化,设置网站标题
|
||||
watch(
|
||||
|
||||
@@ -61,6 +61,54 @@ export const delObj = (ids: Object) => {
|
||||
data: ids,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 批量设置角色分组
|
||||
* @param roleIds 角色ID列表
|
||||
* @param roleGroup 分组名称(空表示未分组)
|
||||
*/
|
||||
export const batchUpdateRoleGroup = (roleIds: string[], roleGroup: string) => {
|
||||
return request({
|
||||
url: '/admin/role/batchGroup',
|
||||
method: 'put',
|
||||
data: { roleIds, roleGroup: roleGroup || '' },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量指定角色关联用户
|
||||
* @param roleId 角色ID
|
||||
* @param userIds 用户ID列表
|
||||
*/
|
||||
export const assignUsersToRole = (roleId: string, userIds: string[]) => {
|
||||
return request({
|
||||
url: '/admin/role/assignUsers',
|
||||
method: 'post',
|
||||
data: { roleId, userIds },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据角色ID查询该角色下绑定的用户列表(含部门、姓名、工号)
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
export const getUsersByRoleId = (roleId: string) => {
|
||||
return request({
|
||||
url: '/admin/role/users/' + roleId,
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 解除指定用户与该角色的关联
|
||||
* @param roleId 角色ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
export const unassignUserFromRole = (roleId: string, userId: string) => {
|
||||
return request({
|
||||
url: `/admin/role/users/${roleId}/${userId}`,
|
||||
method: 'delete',
|
||||
});
|
||||
};
|
||||
|
||||
export const permissionUpd = (roleId: string, menuIds: string) => {
|
||||
return request({
|
||||
|
||||
@@ -28,3 +28,16 @@ export const fetchList = (query?: any) => {
|
||||
params: query,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载任务文件
|
||||
* @param data 如 { id: 任务id }
|
||||
*/
|
||||
export const downloadTaskFile = (data?: any) => {
|
||||
return request({
|
||||
url: '/basic/basicAsyncTask/downloadTaskFile',
|
||||
method: 'post',
|
||||
data: data,
|
||||
responseType: 'blob',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -76,13 +76,13 @@ export const delObj = (id: string | number) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param obj
|
||||
* 更新(编辑)
|
||||
* @param obj 含 id 及需修改字段,走接口文档 /edit 接口
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/basic/basicclass',
|
||||
method: 'put',
|
||||
url: '/basic/basicclass/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取树形列表
|
||||
* 获取树形列表(全量,数据量大时建议用懒加载接口)
|
||||
* @param params 查询参数
|
||||
*/
|
||||
export function getTree(params?: any) {
|
||||
@@ -29,6 +29,28 @@ export function getTree(params?: any) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树根节点(懒加载用)
|
||||
*/
|
||||
export function getTreeRoots() {
|
||||
return request({
|
||||
url: '/purchase/purchasingcategory/tree/roots',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子节点(懒加载用)
|
||||
* @param parentCode 父节点编码
|
||||
*/
|
||||
export function getTreeChildren(parentCode: string) {
|
||||
return request({
|
||||
url: '/purchase/purchasingcategory/tree/children',
|
||||
method: 'get',
|
||||
params: { parentCode }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param obj 对象数据
|
||||
@@ -49,7 +71,7 @@ export function delObj(id: string | number) {
|
||||
return request({
|
||||
url: '/purchase/purchasingcategory/delete',
|
||||
method: 'post',
|
||||
data: id
|
||||
data: {id:id}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -100,3 +100,101 @@ export function delObj(id: number) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取采购申请附件列表
|
||||
* @param purchaseId 采购申请ID
|
||||
*/
|
||||
export function getApplyFiles(purchaseId: string | number) {
|
||||
return request({
|
||||
url: '/purchase/purchasingfiles/applyFiles',
|
||||
method: 'post',
|
||||
params: { purchaseId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 履约验收关联的合同列表(未被使用的合同)
|
||||
* @param params 可选参数,如 id 等
|
||||
*/
|
||||
export function getContracts(params?: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingapply/getContracts',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 实施采购:上传采购文件并关联到申请单(可同时保存采购代表人方式与人员)
|
||||
* @param id 采购申请ID
|
||||
* @param fileIds 已上传的采购文件ID列表(fileType=130)
|
||||
* @param implementType 实施采购方式 1:自行组织采购 2:委托代理采购
|
||||
* @param representorTeacherNo 需求部门初审-指定采购代表人(单人)
|
||||
* @param representors 需求部门初审-部门多人逗号分隔
|
||||
*/
|
||||
export function implementApply(
|
||||
id: number,
|
||||
fileIds: string[],
|
||||
implementType?: string,
|
||||
representorTeacherNo?: string,
|
||||
representors?: string
|
||||
) {
|
||||
return request({
|
||||
url: '/purchase/purchasingapply/implement',
|
||||
method: 'get',
|
||||
params: { id, fileIds, implementType, representorTeacherNo, representors }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起采购文件审批流程(需已实施采购并上传采购文件)
|
||||
* @param id 采购申请ID
|
||||
* @param representorTeacherNo 需求部门初审-指定采购代表人(单人,用户ID或工号)
|
||||
* @param representors 需求部门初审-部门多人由系统抽取(多人,用户ID或工号逗号分隔)
|
||||
*/
|
||||
export function startFileFlow(
|
||||
id: number,
|
||||
representorTeacherNo?: string,
|
||||
representors?: string
|
||||
) {
|
||||
return request({
|
||||
url: '/purchase/purchasingapply/startFileFlow',
|
||||
method: 'post',
|
||||
data: { id, representorTeacherNo, representors }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门下人员(用于选采购代表人)
|
||||
*/
|
||||
export function getDeptMembers() {
|
||||
return request({
|
||||
url: '/purchase/purchasingapply/getDeptMembers',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件归档:按文件类型打包下载该申请单下所有附件的下载地址(GET 请求,浏览器直接下载 zip)
|
||||
* @param purchaseId 采购申请ID
|
||||
*/
|
||||
export function getArchiveDownloadUrl(purchaseId: string | number) {
|
||||
return `/purchase/purchasingfiles/archive?purchaseId=${encodeURIComponent(String(purchaseId))}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载审批表:导出采购审批表 Word 文档(apply.docx 模板,仅占位符替换)
|
||||
* @param id 采购申请ID
|
||||
*/
|
||||
export function getApplyTemplateDownloadUrl(id: string | number) {
|
||||
return `/purchase/purchasingapply/export-apply-template?id=${encodeURIComponent(String(id))}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件审批表:导出采购文件审批表 Word 文档(fileapply.docx 模板)
|
||||
* @param id 采购申请ID
|
||||
*/
|
||||
export function getFileApplyTemplateDownloadUrl(id: string | number) {
|
||||
return `/purchase/purchasingapply/export-file-apply-template?id=${encodeURIComponent(String(id))}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,3 +8,11 @@ export const makeExportTeacherInfoBySelfTask = (data?: any) => {
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const makeExportTeacherInfoByTypeTask = (data?: any) => {
|
||||
return request({
|
||||
url: '/professional/file/makeExportTeacherInfoByTypeTask',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -138,16 +138,3 @@ export const titleCountInfo = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出Excel
|
||||
* @param data 查询参数
|
||||
*/
|
||||
export const exportRelation = (data: any) => {
|
||||
return request({
|
||||
url: '/professional/professionaltitlerelation/exportRelation',
|
||||
method: 'post',
|
||||
data: data,
|
||||
responseType: 'blob',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -67,6 +67,85 @@ export function putObj(obj?: Object) {
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 履约验收流程接口 ==========
|
||||
|
||||
/**
|
||||
* 第一步:保存履约验收公共配置,按分期次数自动生成批次
|
||||
*/
|
||||
export function saveCommonConfig(data: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingAccept/saveCommonConfig',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取履约验收公共配置及批次列表
|
||||
*/
|
||||
export function getCommonConfigWithBatches(purchaseId: string) {
|
||||
return request({
|
||||
url: '/purchase/purchasingAccept/commonConfigWithBatches',
|
||||
method: 'get',
|
||||
params: { purchaseId }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 第二步:更新单个批次
|
||||
*/
|
||||
export function updateBatch(data: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingAccept/updateBatch',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验收详情(含验收内容、验收小组)
|
||||
*/
|
||||
export function getDetail(purchaseId: string, batch?: number) {
|
||||
return request({
|
||||
url: '/purchase/purchasingAccept/detail',
|
||||
method: 'get',
|
||||
params: { purchaseId, batch }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否允许填报方式(金额<30万)
|
||||
*/
|
||||
export function canFillForm(purchaseId: string) {
|
||||
return request({
|
||||
url: '/purchase/purchasingAccept/canFillForm',
|
||||
method: 'get',
|
||||
params: { purchaseId }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据品目类型获取验收项配置
|
||||
*/
|
||||
export function getAcceptanceItems(acceptanceType: string) {
|
||||
return request({
|
||||
url: `/purchase/acceptanceItemConfig/listByType/${acceptanceType}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载履约验收模板
|
||||
*/
|
||||
export function downloadPerformanceAcceptanceTemplate(purchaseId: string, batch?: number) {
|
||||
return request({
|
||||
url: '/purchase/purchasingAccept/export-performance-acceptance-template',
|
||||
method: 'get',
|
||||
params: { purchaseId, batch },
|
||||
responseType: 'blob' // 重要:用于文件下载
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 工具函数 ==========
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,7 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/recruit/recruitstudentplan/deletById`,
|
||||
url: `/recruit/recruitstudentplan/deleteById`,
|
||||
method: 'post',
|
||||
data: { id :id},
|
||||
});
|
||||
|
||||
@@ -22,6 +22,19 @@ export const getActivityInfoList = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看详情 - 根据活动ID获取活动子项目列表
|
||||
* 接口文档:GET /api/stuwork/activityinfosub/getActivityInfoSubList
|
||||
* @param activityInfoId 活动信息ID
|
||||
*/
|
||||
export const getActivityInfoSubList = (activityInfoId: string) => {
|
||||
return request({
|
||||
url: '/stuwork/activityinfosub/getActivityInfoSubList',
|
||||
method: 'get',
|
||||
params: { activityInfoId }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除活动子项目
|
||||
* @param ids
|
||||
|
||||
14
src/api/stuwork/classassets.ts
Normal file
14
src/api/stuwork/classassets.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 教室公物编辑及门锁密码
|
||||
* 接口文档:POST /api/stuwork/classassets/edit
|
||||
* @param data 教室公物数据(包含 buildingNo, deptName, classCode, position, platformType, tyType, tvType, chairCnt, tableCnt, remarks, password 等)
|
||||
*/
|
||||
export const editAssets = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/classassets/edit',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
@@ -103,3 +103,37 @@ export const fearchRoomStuNum = (roomNo: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 互换宿舍(文档:sourceSutNo / targetStuNO)
|
||||
*/
|
||||
export const exchangeRoom = (data: { sourceSutNo: string; targetStuNO: string }) => {
|
||||
return request({
|
||||
url: '/stuwork/dormroomstudent/exchangeRoom',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 打印宿舍卡(按房间号获取打印数据)
|
||||
*/
|
||||
export const printDormRoomData = (roomNo: string) => {
|
||||
return request({
|
||||
url: '/stuwork/dormroomstudent/printDormRoomData',
|
||||
method: 'get',
|
||||
params: { roomNo }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 空 n 人宿舍导出
|
||||
*/
|
||||
export const exportEmptyPeopleRoomExcel = (data?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/dormroomstudent/exportEmptyPeopleRoomExcel',
|
||||
method: 'post',
|
||||
data: data || {},
|
||||
responseType: 'blob'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
35
src/api/stuwork/dormsignrecord.ts
Normal file
35
src/api/stuwork/dormsignrecord.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 分页查询宿舍点名列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/dormsignrecord/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增宿舍点名
|
||||
* @param data
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/dormsignrecord',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化宿舍学生信息用于考勤
|
||||
*/
|
||||
export const initDormStuInfoForAttendance = () => {
|
||||
return request({
|
||||
url: '/stuwork/dormsignrecord/task/initDormStuInfoForAttendance',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
85
src/api/stuwork/filemanager.ts
Normal file
85
src/api/stuwork/filemanager.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 文件列表(分页)
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/filemanager/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 文件列表(不分页,按层级)
|
||||
* @param query
|
||||
*/
|
||||
export const getList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/filemanager/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 文件详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/filemanager/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增文件
|
||||
* @param data
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/filemanager',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑文件
|
||||
* @param data
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/filemanager/edit',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑文件夹
|
||||
* @param data
|
||||
*/
|
||||
export const editFile = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/filemanager/editFile',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param ids
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/filemanager/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
61
src/api/stuwork/gradustu.ts
Normal file
61
src/api/stuwork/gradustu.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import request from '/@/utils/request'
|
||||
|
||||
/**
|
||||
* 分页查询毕业学生(毕业审核列表)
|
||||
* 后端返回 data.dataList.records / data.dataList.total,此处归一为 data.records / data.total 供 useTable 使用
|
||||
* @param query current, size, graduYear, status, type, stuNo, realName, classCode, deptCode, ...
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/stugraducheck/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
}).then((res: any) => {
|
||||
const raw = res.data || {}
|
||||
const dataList = raw.dataList || {}
|
||||
return {
|
||||
...res,
|
||||
data: {
|
||||
records: dataList.records || [],
|
||||
total: dataList.total ?? 0,
|
||||
canExamConduct: raw.canExamConduct,
|
||||
canExamScore: raw.canExamScore,
|
||||
canExamSkill: raw.canExamSkill,
|
||||
canExamStuPunish: raw.canExamStuPunish,
|
||||
canExamBaseInfo: raw.canExamBaseInfo
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成毕业生信息
|
||||
* @param data 如 { type: '0' }
|
||||
*/
|
||||
export const makeGraduStu = (data?: { type?: string }) => {
|
||||
return request({
|
||||
url: '/stuwork/stugraducheck/makeGraduStu',
|
||||
method: 'post',
|
||||
data: data || { type: '0' }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年毕业生列表(用于统计页前端汇总,大 size 拉取)
|
||||
* @param graduYear 毕业年份
|
||||
*/
|
||||
export const fetchListForAnalyse = (graduYear: string | number) => {
|
||||
return request({
|
||||
url: '/stuwork/stugraducheck/page',
|
||||
method: 'get',
|
||||
params: {
|
||||
graduYear,
|
||||
current: 1,
|
||||
size: 9999
|
||||
}
|
||||
}).then((res: any) => {
|
||||
const raw = res.data || {}
|
||||
const dataList = raw.dataList || {}
|
||||
return (dataList.records || []) as any[]
|
||||
})
|
||||
}
|
||||
61
src/api/stuwork/moralplan.ts
Normal file
61
src/api/stuwork/moralplan.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 分页查询德育计划列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/moralplan/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增德育计划
|
||||
* @param data
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/moralplan',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/moralplan/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑德育计划
|
||||
* @param data
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/moralplan/edit',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除德育计划
|
||||
* @param ids
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/moralplan/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
74
src/api/stuwork/onlinebooks.ts
Normal file
74
src/api/stuwork/onlinebooks.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 分页查询在线书列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooks/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取在线书列表(用于下拉选择)
|
||||
* @param query
|
||||
*/
|
||||
export const getList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooks/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增在线书
|
||||
* @param data
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooks',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooks/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑在线书
|
||||
* @param data
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooks/edit',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除在线书
|
||||
* @param ids
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooks/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
|
||||
74
src/api/stuwork/onlinebooksbrowsinghistory.ts
Normal file
74
src/api/stuwork/onlinebooksbrowsinghistory.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 分页查询在线书浏览记录列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooksbrowsinghistory/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取在线书浏览记录列表(用于下拉选择)
|
||||
* @param query
|
||||
*/
|
||||
export const getList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooksbrowsinghistory/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增在线书浏览记录
|
||||
* @param data
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooksbrowsinghistory',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooksbrowsinghistory/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑在线书浏览记录
|
||||
* @param data
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooksbrowsinghistory/edit',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除在线书浏览记录
|
||||
* @param ids
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebooksbrowsinghistory/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
|
||||
74
src/api/stuwork/onlinebookscategory.ts
Normal file
74
src/api/stuwork/onlinebookscategory.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 分页查询在线书类别列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebookscategory/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取在线书类别列表(用于下拉选择)
|
||||
* @param query
|
||||
*/
|
||||
export const getList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebookscategory/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增在线书类别
|
||||
* @param data
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebookscategory',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebookscategory/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑在线书类别
|
||||
* @param data
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebookscategory/edit',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除在线书类别
|
||||
* @param ids
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/onlinebookscategory/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
|
||||
73
src/api/stuwork/psychologicalcounselingduty.ts
Normal file
73
src/api/stuwork/psychologicalcounselingduty.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import request from '/@/utils/request'
|
||||
|
||||
/**
|
||||
* 按月份返回值班表(列表)
|
||||
* @param params year, month
|
||||
*/
|
||||
export const listByMonth = (params: { year: string | number; month: string | number }) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingduty/listByMonth',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台获取某年某月值班表,回显到日历/列表
|
||||
* @param params year, month
|
||||
*/
|
||||
export const getDutyByMonth = (params: { year: string | number; month: string | number }) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingduty/getDutyByMonth',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 id 查询值班详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingduty/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增/批量新增值班
|
||||
* @param list 每项 { date: 'YYYY-MM-DD', teacherUserName: '工号', weekType?: 'single'|'double' }
|
||||
*/
|
||||
export const saveDuty = (list: Array<{ date: string; teacherUserName: string; weekType?: string }>) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingduty/saveDuty',
|
||||
method: 'post',
|
||||
data: list
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 一键清空某月值班
|
||||
* @param data { year, month }
|
||||
*/
|
||||
export const clearDuty = (data: { year: number; month: number }) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingduty/clearDuty',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除单个值班(按日期)
|
||||
* @param data { days: 'YYYY-MM-DD' }
|
||||
*/
|
||||
export const clearOneDuty = (data: { days: string }) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingduty/clearOneDuty',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
61
src/api/stuwork/psychologicalcounselingreservation.ts
Normal file
61
src/api/stuwork/psychologicalcounselingreservation.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import request from '/@/utils/request'
|
||||
|
||||
/**
|
||||
* 分页查询预约记录
|
||||
* @param query current, size, stuNo, classNo, reservationTime, isHandle
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingreservation/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 id 查询预约记录详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingreservation/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增预约记录
|
||||
* @param data teacherNo, reservationTime, classNo, stuNo, stuName, phone, remarks, realName?, isHandle?
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingreservation',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改预约记录
|
||||
* @param data 含 id 及需修改字段
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingreservation/edit',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 id 删除预约记录
|
||||
* @param ids id 数组
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingreservation/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
71
src/api/stuwork/psychologicalcounselingteacher.ts
Normal file
71
src/api/stuwork/psychologicalcounselingteacher.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import request from '/@/utils/request'
|
||||
|
||||
/**
|
||||
* 分页查询心理咨询预约师
|
||||
* @param query current, size, realName
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingteacher/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预约师列表(不分页)
|
||||
*/
|
||||
export const getList = () => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingteacher/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 id 查询详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingteacher/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增心理咨询预约师
|
||||
* @param data userName, realName, phone, remarks
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingteacher',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改心理咨询预约师
|
||||
* @param data id, userName, realName, phone, remarks
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingteacher/edit',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 id 删除心理咨询预约师
|
||||
* @param ids id 数组
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/psychologicalcounselingteacher/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
@@ -36,6 +36,18 @@ export const getDetail = (id: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 通过学年学号查看详情(接口文档:GET /stuwork/stuconduct/queryDataByStuNo)
|
||||
* @param params stuNo 学号, schoolYear 学年
|
||||
*/
|
||||
export const queryDataByStuNo = (params: { stuNo: string; schoolYear: string }) => {
|
||||
return request({
|
||||
url: '/stuwork/stuconduct/queryDataByStuNo',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑操行考核
|
||||
* @param data
|
||||
|
||||
25
src/api/stuwork/stugraducheck.ts
Normal file
25
src/api/stuwork/stugraducheck.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '/@/utils/request'
|
||||
|
||||
/**
|
||||
* 毕业学生名单 - 分页查询
|
||||
* 接口文档:GET /api/stuwork/stugraducheck/page
|
||||
* 参数:current, size, graduYear, status, type, stuNo, realName, classCode, deptCode 等
|
||||
* 返回归一为 data.records / data.total 供 useTable 使用
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/stugraducheck/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
}).then((res: any) => {
|
||||
const raw = res.data || {}
|
||||
const dataList = raw.dataList || {}
|
||||
return {
|
||||
...res,
|
||||
data: {
|
||||
records: dataList.records || [],
|
||||
total: dataList.total ?? 0
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -60,6 +60,18 @@ export const delObj = (ids: string[]) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 撤销学籍异动
|
||||
* @param ids 异动记录ID列表
|
||||
*/
|
||||
export const cancelObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/stuturnover/cancel',
|
||||
method: 'post',
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出学籍异动
|
||||
* @param query
|
||||
|
||||
@@ -12,3 +12,29 @@ export const getClassRoomByClassCode = (classCode: string | number) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 教室安排
|
||||
* 接口文档:POST /api/stuwork/teachclassroomassign/addClassRoomAssign
|
||||
* @param data buildingNo 楼号, position 位置, classCode 班级代码
|
||||
*/
|
||||
export const addClassRoomAssign = (data: { buildingNo?: string | number; position?: string; classCode?: string }) => {
|
||||
return request({
|
||||
url: '/stuwork/teachclassroomassign/addClassRoomAssign',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消教室安排
|
||||
* 接口文档:POST /api/stuwork/teachclassroomassign/delClassRoomAssign
|
||||
* @param data 教室基础数据(包含 id, classCode, position 等)
|
||||
*/
|
||||
export const delClassRoomAssign = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/teachclassroomassign/delClassRoomAssign',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
61
src/api/stuwork/termactivity.ts
Normal file
61
src/api/stuwork/termactivity.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 分页查询学期活动列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/stuwork/termactivity/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取学期活动详情
|
||||
* @param id
|
||||
*/
|
||||
export const getDetail = (id: string) => {
|
||||
return request({
|
||||
url: '/stuwork/termactivity/detail',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学期活动
|
||||
* @param data
|
||||
*/
|
||||
export const addObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/termactivity',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑学期活动
|
||||
* @param data
|
||||
*/
|
||||
export const editObj = (data: any) => {
|
||||
return request({
|
||||
url: '/stuwork/termactivity/edit',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学期活动
|
||||
* @param ids
|
||||
*/
|
||||
export const delObj = (ids: string[]) => {
|
||||
return request({
|
||||
url: '/stuwork/termactivity/delete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
@@ -162,6 +162,8 @@ let saveDialog = () => {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
avatar: item.avatar,
|
||||
username: item.username,
|
||||
userName: item.username,
|
||||
}));
|
||||
emits('change', checkedList);
|
||||
//selectedList.value=[]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div v-if="props.disabled">
|
||||
<div v-if="fileList.length === 0" class="flex justify-center items-center px-4 text-gray-400 bg-gray-50 rounded-md p">
|
||||
<el-icon class="mr-2 text-lg"><Document /></el-icon>
|
||||
<span class="text-sm">{{ $t('excel.noFiles') }}</span>
|
||||
<span class="text-sm">无</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
@@ -139,6 +139,13 @@ const baseUrl = import.meta.env.VITE_API_URL || '';
|
||||
|
||||
// 获取文件名
|
||||
const getFileName = (file: any): string => {
|
||||
// 优先使用 fileTitle,其次使用 name,最后从 URL 中提取
|
||||
if (file.fileTitle) {
|
||||
return file.fileTitle;
|
||||
}
|
||||
if (file.name) {
|
||||
return file.name;
|
||||
}
|
||||
return file.url ? other.getQueryString(file.url, 'fileName') || other.getQueryString(file.url, 'originalFileName') : 'File';
|
||||
};
|
||||
|
||||
@@ -349,23 +356,23 @@ function handleUploadSuccess(res: any, file: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// 上传结束处理
|
||||
// 上传结束处理:传出完整 fileList(含 name),便于父组件回显文件名
|
||||
const uploadedSuccessfully = () => {
|
||||
if (number.value > 0 && uploadList.value.length === number.value) {
|
||||
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
|
||||
uploadList.value = [];
|
||||
number.value = 0;
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
emit('update:modelValue', fileList.value);
|
||||
emit('change', listToString(fileList.value), fileList.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = (file: { name?: string }) => {
|
||||
if (file.name) {
|
||||
fileList.value = fileList.value.filter((f) => f.name !== file.name);
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
emit('change', listToString(fileList.value), fileList.value);
|
||||
}
|
||||
const handleRemove = (file: { name?: string; id?: string; url?: string }) => {
|
||||
fileList.value = fileList.value.filter(
|
||||
(f) => !(f.id && f.id === file.id) && !(f.name && f.name === file.name)
|
||||
);
|
||||
emit('update:modelValue', fileList.value.length ? fileList.value : '');
|
||||
emit('change', listToString(fileList.value), fileList.value);
|
||||
};
|
||||
|
||||
const handlePreview = (file: any) => {
|
||||
|
||||
Binary file not shown.
@@ -529,11 +529,11 @@
|
||||
if (elTabs) {
|
||||
let find = elTabs.find(f => f.isSave !== true);
|
||||
// 这里测试流程,临时屏蔽判断 todo
|
||||
// if (find) useMessage().info(find.formName + ' 未保存')
|
||||
// else {
|
||||
if (find) useMessage().info(find.formName + ' 未保存')
|
||||
else {
|
||||
methods.timeoutLoading()
|
||||
btnMethods.onHandleJob(jobBtn)
|
||||
// }
|
||||
}
|
||||
return
|
||||
}
|
||||
methods.timeoutLoading()
|
||||
|
||||
@@ -17,7 +17,8 @@ import SignInput from "./sign/index.vue";
|
||||
// vite glob导入
|
||||
const modules: Record<string, () => Promise<unknown>> = import.meta.glob(
|
||||
['../../views/jsonflow/*/*.vue', '../../views/order/*/*.vue',
|
||||
'../../views/purchase/*/*.vue','../../views/finance/purchasingrequisition/add.vue']
|
||||
'../../views/purchase/*/*.vue', '../../views/finance/purchasingrequisition/add.vue',
|
||||
'../../views/finance/purchasingrequisition/implement.vue']
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -156,8 +156,10 @@ export function useTable(options?: BasicTableProps) {
|
||||
if (state.onLoaded) await state.onLoaded(state);
|
||||
if (state.onCascaded) await state.onCascaded(state);
|
||||
} catch (err: any) {
|
||||
// 捕获异常并显示错误提示
|
||||
ElMessage.error(err.msg || err.data.msg);
|
||||
// 全局拦截器已展示过错误时不再重复弹窗
|
||||
if (!err?._messageShown) {
|
||||
ElMessage.error(err?.msg || err?.data?.msg || '请求失败');
|
||||
}
|
||||
} finally {
|
||||
// 结束加载数据,设置state.loading为false
|
||||
state.loading = false;
|
||||
|
||||
@@ -155,6 +155,10 @@ export function useTableColumnControl(
|
||||
* 根据 visibleColumns 和 columnOrder 计算最终显示的列
|
||||
*/
|
||||
const visibleColumnsSorted = computed(() => {
|
||||
// 如果 visibleColumns 为空,显示所有列(初始化时)
|
||||
if (visibleColumns.value.length === 0) {
|
||||
return tableColumns.filter(col => !col.alwaysShow && !col.fixed)
|
||||
}
|
||||
// 过滤出可见的列
|
||||
const columns = tableColumns.filter(col => {
|
||||
const key = col.prop || col.label || ''
|
||||
|
||||
@@ -28,7 +28,12 @@
|
||||
>
|
||||
<el-table-column label="所属模块" prop="moduleName" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="任务类型" prop="typeLabel" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="任务名称" prop="detailType" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="任务名称" prop="detailType" min-width="150" show-overflow-tooltip >
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.type==2" type="text" icon="Download" class="task-name-text" :loading="downloadingId === row.id" @click="handleDownloadFile(row)">{{row.detailType}}</el-button>
|
||||
<span v-else class="task-name-text">{{row.detailType}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务状态" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ row.status }}</el-tag>
|
||||
@@ -58,7 +63,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from 'vue'
|
||||
import { fetchList } from '/@/api/basic/basicasynctask'
|
||||
import { fetchList, downloadTaskFile } from '/@/api/basic/basicasynctask'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
|
||||
type TaskTab = 'upload' | 'download' | 'other'
|
||||
|
||||
@@ -87,6 +93,7 @@ const tableStyle = {
|
||||
}
|
||||
|
||||
const emptyText = computed(() => EMPTY_TEXT_MAP[activeTab.value])
|
||||
const message = useMessage()
|
||||
|
||||
const loadList = async () => {
|
||||
const type = activeTab.value
|
||||
@@ -135,6 +142,34 @@ const open = () => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const downloadingId = ref<string | number | null>(null)
|
||||
const handleDownloadFile = async (row: any) => {
|
||||
if (!row?.id) return
|
||||
downloadingId.value = row.id
|
||||
try {
|
||||
const response: any = await downloadTaskFile({ id: row.id })
|
||||
const blob = (response && response.data instanceof Blob)
|
||||
? response.data
|
||||
: (response instanceof Blob ? response : new Blob([response]))
|
||||
const dateStr = new Date().toISOString().slice(0, 10)
|
||||
const baseName = row.detailType ? String(row.detailType).replace(/\s+/g, '_') : '下载文件'
|
||||
const fileName = `${baseName}_${dateStr}.xls`
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
message.success('下载成功')
|
||||
} catch {
|
||||
message.error('下载失败')
|
||||
} finally {
|
||||
downloadingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
|
||||
@@ -253,15 +253,7 @@ const getIsDot = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 登录后若存储中无角色信息则弹出角色切换框
|
||||
const openChangeRoleIfMissing = () => {
|
||||
const hasRole = Local.get('roleCode') && Local.get('roleName') && Local.get('roleId')
|
||||
if (!hasRole) {
|
||||
nextTick(() => {
|
||||
setTimeout(() => ChangeRoleRef.value?.open(), 100)
|
||||
})
|
||||
}
|
||||
}
|
||||
// 首次登录缺角色时由 App.vue + 请求拦截器统一弹出「首次登录请选择角色」弹框,此处不再自动打开
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@@ -271,7 +263,6 @@ onMounted(() => {
|
||||
}
|
||||
useFlowJob().topJobList()
|
||||
getIsDot()
|
||||
openChangeRoleIfMissing()
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -41,11 +41,18 @@ export async function initBackEndControlRoutes() {
|
||||
await useUserInfo().setUserInfos();
|
||||
// 获取路由菜单数据
|
||||
const res = await getBackEndControlRoutes();
|
||||
// 无登录权限时,添加判断
|
||||
const menuList = res.data || [];
|
||||
// 无登录权限时仍走后续流程,用 dynamicRoutes 作为子路由并写入 store,避免 routesList 一直为空导致 beforeEach 无限请求
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
|
||||
if ((res.data || []).length <= 0) return Promise.resolve(true);
|
||||
if (menuList.length <= 0) {
|
||||
useRequestOldRoutes().setRequestOldRoutes([]);
|
||||
baseRoutes[0].children = [...dynamicRoutes, ...(await backEndComponent([]) || [])];
|
||||
await setAddRoute();
|
||||
await setFilterMenuAndCacheTagsViewRoutes();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
// 存储接口原始路由(未处理component),根据需求选择使用
|
||||
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data)));
|
||||
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(menuList)));
|
||||
// 处理路由(component),替换 baseRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
baseRoutes[0].children = [...dynamicRoutes, ...(await backEndComponent(res.data))];
|
||||
// 添加动态路由
|
||||
|
||||
@@ -107,6 +107,14 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
||||
isAuth: false, // 不需要认证,纯页面展示
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/finance/purchasingrequisition/implement',
|
||||
name: 'purchasingrequisition.implement',
|
||||
component: () => import('/@/views/finance/purchasingrequisition/implement.vue'),
|
||||
meta: {
|
||||
isAuth: false, // 供流程 iframe 嵌入
|
||||
},
|
||||
},
|
||||
...staticRoutesFlow
|
||||
];
|
||||
|
||||
|
||||
1
src/types/mitt.d.ts
vendored
1
src/types/mitt.d.ts
vendored
@@ -23,6 +23,7 @@ declare type MittType<T = any> = {
|
||||
openShareTagsView?: string;
|
||||
onTagsViewRefreshRouterView?: T;
|
||||
onCurrentContextmenuClick?: T;
|
||||
openRoleSelectDialog?: string;
|
||||
};
|
||||
|
||||
// mitt 参数类型定义
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import {useMessage, useMessageBox} from '/@/hooks/message';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import qs from 'qs';
|
||||
import other from './other';
|
||||
import {paramsFilter} from "/@/flow";
|
||||
import { paramsFilter } from "/@/flow";
|
||||
import { wrapEncryption, encryptRequestParams, decrypt } from './apiCrypto';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { needRoleSelection, isRoleDialogTriggered, setRoleDialogTriggered } from '/@/utils/roleSelect';
|
||||
|
||||
// 常用header
|
||||
export enum CommonHeaderEnum {
|
||||
@@ -81,6 +83,12 @@ service.interceptors.request.use(
|
||||
// 自动适配单体和微服务架构不同的URL
|
||||
config.url = other.adaptationUrl(config.url);
|
||||
|
||||
// 发送请求时判断:已登录但缺少角色信息则弹出角色选择;若弹框已触发则不再重复弹出
|
||||
if (token && needRoleSelection() && !isRoleDialogTriggered()) {
|
||||
setRoleDialogTriggered(true);
|
||||
mittBus.emit('openRoleSelectDialog');
|
||||
}
|
||||
|
||||
// 处理完毕,返回config对象
|
||||
return config;
|
||||
},
|
||||
@@ -97,9 +105,10 @@ service.interceptors.request.use(
|
||||
*/
|
||||
const handleResponse = (response: AxiosResponse<any>) => {
|
||||
if (response.data.code === 1) {
|
||||
// 业务错误,统一弹出错误提示
|
||||
// 业务错误,统一弹出错误提示(标记已展示,避免 hook/页面 catch 再次弹窗)
|
||||
if (response.data.msg) {
|
||||
useMessage().error(response.data.msg);
|
||||
response.data._messageShown = true;
|
||||
}
|
||||
throw response.data;
|
||||
}
|
||||
|
||||
24
src/utils/roleSelect.ts
Normal file
24
src/utils/roleSelect.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Local } from '/@/utils/storage';
|
||||
|
||||
/** Local 是否缺少角色信息(缺任一则需弹出角色选择) */
|
||||
export function needRoleSelection(): boolean {
|
||||
try {
|
||||
const roleCode = Local.get('roleCode');
|
||||
const roleName = Local.get('roleName');
|
||||
const roleId = Local.get('roleId');
|
||||
return !roleCode || !roleName || !roleId;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** 角色选择弹框是否已触发(防止多请求同时触发时重复弹出) */
|
||||
let roleDialogTriggered = false;
|
||||
|
||||
export function isRoleDialogTriggered(): boolean {
|
||||
return roleDialogTriggered;
|
||||
}
|
||||
|
||||
export function setRoleDialogTriggered(value: boolean): void {
|
||||
roleDialogTriggered = value;
|
||||
}
|
||||
@@ -1,56 +1,79 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="角色切换"
|
||||
:title="dialogTitle"
|
||||
width="50%"
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:before-close="handleBeforeClose"
|
||||
center
|
||||
>
|
||||
<el-form>
|
||||
<!-- <el-form-item label="学校">-->
|
||||
<!-- <el-tag>{{schoolName}}</el-tag>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item label="角色" class="role-form-item">
|
||||
<el-form-item class="role-form-item">
|
||||
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
|
||||
<el-radio-button
|
||||
v-for="item in allRole"
|
||||
:key="item.roleCode"
|
||||
:label="item.roleCode"
|
||||
>
|
||||
{{ item.roleName }}
|
||||
</el-radio-button>
|
||||
<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>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<!-- <el-button type="primary" @click="handleChangeRole">切换</el-button>-->
|
||||
<template v-if="!requireSelectToClose" #footer>
|
||||
<el-button @click="handleFooterClose">关 闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, toRef } from 'vue'
|
||||
import { listAllRole } from '/@/api/admin/role'
|
||||
import { Local } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
|
||||
/** 弹框标题,如「角色切换」「登录角色选择」 */
|
||||
const props = withDefaults(
|
||||
defineProps<{ title?: string; requireSelectToClose?: boolean }>(),
|
||||
{ title: '角色切换', requireSelectToClose: false }
|
||||
)
|
||||
const dialogTitle = computed(() => props.title)
|
||||
|
||||
const visible = ref(false)
|
||||
const radio = ref('')
|
||||
const allRole = reactive<any[]>([])
|
||||
/** 按分组名分组的角色列表:{ "未分组": [{ roleId, roleName, roleCode, ... }], ... } */
|
||||
const allRoleGroups = ref<Record<string, any[]>>({})
|
||||
const requireSelectToClose = toRef(props, 'requireSelectToClose')
|
||||
|
||||
const open = () => {
|
||||
if (visible.value) return
|
||||
visible.value = true
|
||||
listAllRole().then((res) => {
|
||||
Object.assign(allRole, res.data)
|
||||
allRoleGroups.value = res.data && typeof res.data === 'object' && !Array.isArray(res.data)
|
||||
? res.data
|
||||
: { '未分组': Array.isArray(res.data) ? res.data : [] }
|
||||
radio.value = Local.get('roleCode')
|
||||
visible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
/** 根据 roleCode 从分组数据中查找角色 */
|
||||
const findRoleByCode = (code: string) => {
|
||||
for (const roles of Object.values(allRoleGroups.value)) {
|
||||
if (!Array.isArray(roles)) continue
|
||||
const found = roles.find((r: any) => r.roleCode === code)
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const canClose = () => {
|
||||
if (!radio.value) {
|
||||
useMessage().warning('请选择一个角色')
|
||||
@@ -60,6 +83,10 @@ const canClose = () => {
|
||||
}
|
||||
|
||||
const handleBeforeClose = (done: () => void) => {
|
||||
if (requireSelectToClose.value) {
|
||||
useMessage().warning('请先选择登录角色')
|
||||
return
|
||||
}
|
||||
if (!canClose()) return
|
||||
done()
|
||||
}
|
||||
@@ -70,7 +97,7 @@ const handleFooterClose = () => {
|
||||
}
|
||||
|
||||
const handleChangeRole = (label: string) => {
|
||||
const obj = allRole.find((v: any) => v.roleCode === label)
|
||||
const obj = findRoleByCode(label)
|
||||
if (!obj) return
|
||||
Local.set('roleCode', obj.roleCode)
|
||||
Local.set('roleName', obj.roleName)
|
||||
@@ -94,6 +121,18 @@ defineExpose({
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
.role-group {
|
||||
margin-bottom: 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;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" width="50%" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" center>
|
||||
<template #title>
|
||||
<div style="margin: 0 auto;width:100%;text-align:center;font-size:18px;font-weight:bold;">
|
||||
登录角色选择
|
||||
</div>
|
||||
</template>
|
||||
<div style="margin: 0 auto;width:100%;text-align:center;font-size:18px;font-weight:bold;">
|
||||
<el-radio-group v-model="radio">
|
||||
<el-radio-button v-for="(item,index) in allRole" :key="index" :label="item.roleCode" @click.native="handleChangeRole(item.roleCode)">{{item.roleName}}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {listAllRole} from '/@/api/admin/role'
|
||||
import {Local, Session} from '/@/utils/storage';
|
||||
import {useMessage} from "/@/hooks/message";
|
||||
// import {querySchoolName} from "/@/api/admin/tenant"
|
||||
|
||||
const visible=ref(false)
|
||||
const radio=ref('')
|
||||
const allRole=reactive([])
|
||||
const schoolName=ref('')
|
||||
|
||||
const open=()=>{
|
||||
// handleQuerySchoolName()
|
||||
listAllRole().then(res=>{
|
||||
Object.assign(allRole,res.data)
|
||||
radio.value=Local.get("roleCode")
|
||||
visible.value=true
|
||||
})
|
||||
}
|
||||
const handleChangeRole=(label:any)=>{
|
||||
let obj:any=allRole.find((v:any) => v.roleCode == label)
|
||||
Local.set("roleCode",obj.roleCode)
|
||||
Local.set("roleName",obj.roleName)
|
||||
Local.set("roleId",obj.roleId)
|
||||
useMessage().success("操作成功")
|
||||
setTimeout(()=>{
|
||||
window.location.reload()
|
||||
},500)
|
||||
}
|
||||
|
||||
// const handleQuerySchoolName=()=>{
|
||||
// querySchoolName({id:Session.get("tenantId")}).then((res:any)=>{
|
||||
// schoolName.value=res.data
|
||||
// })
|
||||
// }
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -7,6 +7,12 @@
|
||||
<el-form-item :label="$t('sysrole.roleCode')" prop="roleCode">
|
||||
<el-input :placeholder="$t('sysrole.please_enter_the_role_Code')" :disabled="form.roleId !== ''" clearable v-model="form.roleCode"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="分组" prop="roleGroup">
|
||||
<el-input placeholder="用于列表树状分组展示,可留空" clearable v-model="form.roleGroup" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="roleSort">
|
||||
<el-input-number v-model="form.roleSort" :min="0" :max="9999" placeholder="数值越小越靠前" controls-position="right" style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.roleDesc')" prop="roleDesc">
|
||||
<el-input
|
||||
maxlength="100"
|
||||
@@ -67,6 +73,8 @@ const form = reactive({
|
||||
roleId: '',
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
roleGroup: '',
|
||||
roleSort: 0,
|
||||
roleDesc: '',
|
||||
dsType: 0,
|
||||
dsScope: '',
|
||||
@@ -187,6 +195,7 @@ const getRoleData = (id: string) => {
|
||||
// 获取部门数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
if (res.data.roleSort == null) form.roleSort = 0;
|
||||
if (res.data.dsScope) {
|
||||
dataForm.checkedDsScope = res.data.dsScope.split(',');
|
||||
} else {
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" v-auth="'sys_user_del'" @click="handleDelete(selectObjs)">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" type="primary" class="ml10" v-auth="'sys_role_edit'" @click="showBatchGroupDialog = true">
|
||||
批量指定分组
|
||||
</el-button>
|
||||
<el-button plain :disabled="selectObjs.length !== 1" type="primary" class="ml10" v-auth="'sys_role_edit'" @click="openAssignUserDialog">
|
||||
批量指定关联用户
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_role_export'"
|
||||
@@ -36,55 +42,165 @@
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
:data="roleTreeData"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
row-key="roleId"
|
||||
@selection-change="handleSelectionChange"
|
||||
row-key="roleId"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
|
||||
@selection-change="handleSelectionChange"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="handleSelectable" width="50" align="center" />
|
||||
<el-table-column type="index" :label="$t('sysrole.index')" width="80" />
|
||||
<el-table-column prop="roleName" :label="$t('sysrole.roleName')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleDesc" :label="$t('sysrole.roleDesc')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="data_authority" :label="$t('sysrole.data_authority')" show-overflow-tooltip>
|
||||
<el-table-column type="selection" :selectable="handleSelectable" width="50" align="left" />
|
||||
<el-table-column type="index" :label="$t('sysrole.index')" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dictType" :value="scope.row.dsType"></dict-tag>
|
||||
<span v-if="scope.row._isGroup">—</span>
|
||||
<span v-else>{{ scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysrole.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250">
|
||||
<el-table-column prop="roleName" :label="$t('sysrole.roleName')" show-overflow-tooltip min-width="140" align="left">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" icon="edit-pen" v-auth="'sys_role_edit'" @click="roleDialogRef.openDialog(scope.row.roleId)">{{
|
||||
$t('common.editBtn')
|
||||
}}</el-button>
|
||||
|
||||
<el-button text type="primary" icon="turn-off" v-auth="'sys_role_perm'" @click="permessionRef.openDialog(scope.row)">{{
|
||||
$t('sysrole.permissionTip')
|
||||
}}</el-button>
|
||||
|
||||
<el-tooltip :content="$t('sysrole.deleteDisabledTip')" :disabled="scope.row.roleId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
icon="delete"
|
||||
:disabled="scope.row.roleId === '1'"
|
||||
v-auth="'sys_role_del'"
|
||||
@click="handleDelete([scope.row.roleId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-if="scope.row._isGroup" class="role-group-name">{{ scope.row.roleName }}</span>
|
||||
<span v-else>{{ scope.row.roleName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleSort" label="排序" width="80" align="center">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : (scope.row.roleSort ?? 0) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip min-width="120">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleCode }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleDesc" :label="$t('sysrole.roleDesc')" show-overflow-tooltip min-width="140">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleDesc }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="data_authority" :label="$t('sysrole.data_authority')" show-overflow-tooltip width="100">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row._isGroup">—</template>
|
||||
<dict-tag v-else :options="dictType" :value="scope.row.dsType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysrole.createTime')" show-overflow-tooltip width="165">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.createTime }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250" fixed="right">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row._isGroup">—</template>
|
||||
<template v-else>
|
||||
<el-button text type="primary" icon="edit-pen" v-auth="'sys_role_edit'" @click="roleDialogRef.openDialog(scope.row.roleId)">{{
|
||||
$t('common.editBtn')
|
||||
}}</el-button>
|
||||
<el-button text type="primary" icon="turn-off" v-auth="'sys_role_perm'" @click="permessionRef.openDialog(scope.row)">{{
|
||||
$t('sysrole.permissionTip')
|
||||
}}</el-button>
|
||||
<el-button text type="primary" icon="user" v-auth="'sys_role_view'" @click="openRoleUsersDialog(scope.row)">查看关联用户</el-button>
|
||||
<el-tooltip :content="$t('sysrole.deleteDisabledTip')" :disabled="scope.row.roleId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
icon="delete"
|
||||
:disabled="scope.row.roleId === '1'"
|
||||
v-auth="'sys_role_del'"
|
||||
@click="handleDelete([scope.row.roleId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 批量指定分组弹窗 -->
|
||||
<el-dialog v-model="showBatchGroupDialog" title="批量指定分组" width="400px" @close="batchGroupName = ''">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="分组名称">
|
||||
<el-input v-model="batchGroupName" placeholder="输入分组名,留空为未分组" clearable maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showBatchGroupDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="batchGroupLoading" @click="handleBatchGroup">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量指定关联用户弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showAssignUserDialog"
|
||||
title="批量指定关联用户"
|
||||
width="720px"
|
||||
destroy-on-close
|
||||
@close="assignUserKeyword = ''; assignUserType = ''; assignUserList = []; assignUserTree = []; assignSelectedIds = []">
|
||||
<template v-if="assignCurrentRole">
|
||||
<div class="mb12"><el-text type="info">当前角色:{{ assignCurrentRole.roleName }}({{ assignCurrentRole.roleCode }})</el-text></div>
|
||||
<el-form :inline="true" class="mb12">
|
||||
<el-form-item label="用户类型" required>
|
||||
<el-radio-group v-model="assignUserType">
|
||||
<el-radio label="1">教职工</el-radio>
|
||||
<el-radio label="2">学生</el-radio>
|
||||
<el-radio label="3">驻校单位</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名/工号">
|
||||
<el-input v-model="assignUserKeyword" placeholder="姓名或工号检索" clearable style="width: 180px" @keyup.enter="loadAssignUserList" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="assignUserLoading" @click="loadAssignUserList">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="assign-user-tip mb8" v-if="!assignUserType">请先选择用户类型后再查询</div>
|
||||
<el-table
|
||||
v-else
|
||||
:data="assignUserTree"
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
default-expand-all
|
||||
v-loading="assignUserLoading"
|
||||
max-height="360"
|
||||
border
|
||||
@selection-change="handleAssignUserSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="left" :selectable="(row: any) => !row._isDept" />
|
||||
<el-table-column prop="label" label="部门 / 姓名" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row._isDept" class="dept-row">{{ row.label }}</span>
|
||||
<span v-else>{{ row.realName || row.username || row.label }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="工号" width="120">
|
||||
<template #default="{ row }">{{ row._isDept ? '—' : row.username }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deptName" label="部门" width="140">
|
||||
<template #default="{ row }">{{ row._isDept ? '—' : (row.deptName || '—') }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="showAssignUserDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="assignSubmitLoading" :disabled="assignSelectedIds.length === 0" @click="handleAssignUsersSubmit">确定(已选 {{ assignSelectedIds.length }} 人)</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看角色关联用户弹窗 -->
|
||||
<el-dialog v-model="showRoleUsersDialog" title="关联用户" width="560px" destroy-on-close>
|
||||
<template v-if="currentRoleForUsers">
|
||||
<div class="mb12"><el-text type="info">角色:{{ currentRoleForUsers.roleName }}({{ currentRoleForUsers.roleCode }})</el-text></div>
|
||||
<el-table :data="roleUsersList" v-loading="roleUsersLoading" max-height="400" border>
|
||||
<el-table-column prop="deptName" label="部门" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="realName" label="姓名" width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="username" label="工号" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="danger" icon="delete" v-auth="'sys_role_edit'" @click="handleUnassignUser(row)">解除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mt8" v-if="!roleUsersLoading && roleUsersList.length === 0"><el-text type="info">该角色下暂无关联用户</el-text></div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 角色编辑、新增 -->
|
||||
<role-dialog ref="roleDialogRef" @refresh="getDataList()" />
|
||||
<!-- 导入角色 -->
|
||||
@@ -101,8 +217,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemRole">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { pageList, delObj } from '/@/api/admin/role';
|
||||
import { list, delObj, batchUpdateRoleGroup, assignUsersToRole, getUsersByRoleId, unassignUserFromRole } from '/@/api/admin/role';
|
||||
import { pageList as userPageList } from '/@/api/admin/user';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -121,15 +239,103 @@ const showSearch = ref(true);
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
// 批量指定分组
|
||||
const showBatchGroupDialog = ref(false);
|
||||
const batchGroupName = ref('');
|
||||
const batchGroupLoading = ref(false);
|
||||
|
||||
// 查看角色关联用户
|
||||
const showRoleUsersDialog = ref(false);
|
||||
const roleUsersList = ref<any[]>([]);
|
||||
const roleUsersLoading = ref(false);
|
||||
const currentRoleForUsers = ref<{ roleName: string; roleCode: string; roleId: string } | null>(null);
|
||||
|
||||
// 批量指定关联用户
|
||||
const showAssignUserDialog = ref(false);
|
||||
const assignUserType = ref('');
|
||||
const assignUserKeyword = ref('');
|
||||
const assignUserList = ref<any[]>([]);
|
||||
const assignUserLoading = ref(false);
|
||||
const assignSubmitLoading = ref(false);
|
||||
const assignSelectedIds = ref<string[]>([]);
|
||||
const assignCurrentRole = computed(() => {
|
||||
const id = selectObjs.value && selectObjs.value[0];
|
||||
if (!id) return null;
|
||||
return (state.dataList || []).find((r: any) => r.roleId === id) || null;
|
||||
});
|
||||
/** 按部门分组的用户树(用于表格树形展示) */
|
||||
const assignUserTree = computed(() => {
|
||||
const list = assignUserList.value || [];
|
||||
const map = new Map<string, any[]>();
|
||||
list.forEach((u: any) => {
|
||||
const deptKey = u.deptName && String(u.deptName).trim() ? u.deptName : '未分配部门';
|
||||
if (!map.has(deptKey)) map.set(deptKey, []);
|
||||
map.get(deptKey)!.push({ ...u, id: u.userId, _isDept: false });
|
||||
});
|
||||
const result: any[] = [];
|
||||
map.forEach((users, deptName) => {
|
||||
result.push({
|
||||
id: `dept_${deptName}`,
|
||||
label: deptName,
|
||||
_isDept: true,
|
||||
children: users,
|
||||
});
|
||||
});
|
||||
result.sort((a, b) => a.label.localeCompare(b.label));
|
||||
return result;
|
||||
});
|
||||
|
||||
// 列表不分页,显示全部数据
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
roleName: '',
|
||||
},
|
||||
pageList: pageList, // H
|
||||
isPage: false,
|
||||
pageList: async (params: any) => {
|
||||
const res = await list(params);
|
||||
let data = res?.data || [];
|
||||
if (Array.isArray(data) && params?.roleName) {
|
||||
const kw = String(params.roleName).trim().toLowerCase();
|
||||
if (kw) {
|
||||
data = data.filter(
|
||||
(r: any) =>
|
||||
(r.roleName && r.roleName.toLowerCase().includes(kw)) ||
|
||||
(r.roleCode && r.roleCode.toLowerCase().includes(kw))
|
||||
);
|
||||
}
|
||||
}
|
||||
return { data };
|
||||
},
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
/** 按分组构建树形数据:分组为父节点,角色为子节点 */
|
||||
const roleTreeData = computed(() => {
|
||||
const list = state.dataList || [];
|
||||
const groupMap = new Map<string, any[]>();
|
||||
list.forEach((row: any) => {
|
||||
const groupName = row.roleGroup && String(row.roleGroup).trim() ? String(row.roleGroup).trim() : '未分组';
|
||||
if (!groupMap.has(groupName)) groupMap.set(groupName, []);
|
||||
groupMap.get(groupName)!.push(row);
|
||||
});
|
||||
const result: any[] = [];
|
||||
groupMap.forEach((roles, groupName) => {
|
||||
result.push({
|
||||
roleId: `group_${groupName}`,
|
||||
roleName: groupName,
|
||||
_isGroup: true,
|
||||
children: roles,
|
||||
});
|
||||
});
|
||||
// 未分组放最后,其余按分组名排序
|
||||
result.sort((a, b) => {
|
||||
if (a.roleName === '未分组') return 1;
|
||||
if (b.roleName === '未分组') return -1;
|
||||
return a.roleName.localeCompare(b.roleName);
|
||||
});
|
||||
return result;
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
@@ -153,8 +359,8 @@ const dictType = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
// table hook(无分页,不暴露 currentChangeHandle/sizeChangeHandle)
|
||||
const { getDataList, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -167,15 +373,15 @@ const exportExcel = () => {
|
||||
downBlobFile('/admin/role/export',Object.assign(state.queryForm,{ids:selectObjs}), 'role.xlsx');
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
// 是否可以多选(分组行不可选,管理员角色不可选)
|
||||
const handleSelectable = (row: any) => {
|
||||
return row.roleId !== '1';
|
||||
return !row._isGroup && row.roleId !== '1';
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
// 多选事件(仅角色行可选,排除分组行)
|
||||
const handleSelectionChange = (objs: { roleId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ roleId }) => roleId);
|
||||
multiple.value = !objs.length;
|
||||
selectObjs.value = objs.map(({ roleId }) => roleId).filter((id: string) => !String(id).startsWith('group_'));
|
||||
multiple.value = !selectObjs.value.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
@@ -194,4 +400,129 @@ const handleDelete = async (ids: string[]) => {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
async function loadRoleUsersList() {
|
||||
const role = currentRoleForUsers.value;
|
||||
if (!role) return;
|
||||
roleUsersLoading.value = true;
|
||||
try {
|
||||
const res = await getUsersByRoleId(role.roleId);
|
||||
const data = res?.data;
|
||||
roleUsersList.value = Array.isArray(data) ? data : [];
|
||||
} catch {
|
||||
roleUsersList.value = [];
|
||||
} finally {
|
||||
roleUsersLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openRoleUsersDialog(row: any) {
|
||||
currentRoleForUsers.value = { roleName: row.roleName, roleCode: row.roleCode, roleId: row.roleId };
|
||||
showRoleUsersDialog.value = true;
|
||||
roleUsersList.value = [];
|
||||
await loadRoleUsersList();
|
||||
}
|
||||
|
||||
async function handleUnassignUser(row: any) {
|
||||
const roleId = currentRoleForUsers.value?.roleId;
|
||||
const userId = row.userId;
|
||||
if (!roleId || !userId) return;
|
||||
try {
|
||||
await useMessageBox().confirm(`确定解除「${row.realName || row.username}」与该角色的关联?`);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await unassignUserFromRole(roleId, userId);
|
||||
useMessage().success('已解除关联');
|
||||
await loadRoleUsersList();
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '操作失败');
|
||||
}
|
||||
}
|
||||
|
||||
function openAssignUserDialog() {
|
||||
if (selectObjs.value.length !== 1) return;
|
||||
showAssignUserDialog.value = true;
|
||||
assignUserType.value = '';
|
||||
assignUserKeyword.value = '';
|
||||
assignUserList.value = [];
|
||||
assignSelectedIds.value = [];
|
||||
}
|
||||
|
||||
async function loadAssignUserList() {
|
||||
if (!assignUserType.value) return;
|
||||
assignUserLoading.value = true;
|
||||
try {
|
||||
const res = await userPageList({
|
||||
current: 1,
|
||||
size: 2000,
|
||||
userType: assignUserType.value,
|
||||
realName: assignUserKeyword.value ? assignUserKeyword.value.trim() : undefined,
|
||||
});
|
||||
const records = res?.data?.records ?? res?.records ?? (Array.isArray(res?.data) ? res.data : []);
|
||||
assignUserList.value = Array.isArray(records) ? records : [];
|
||||
} catch (e) {
|
||||
assignUserList.value = [];
|
||||
} finally {
|
||||
assignUserLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleAssignUserSelectionChange(rows: any[]) {
|
||||
assignSelectedIds.value = (rows || [])
|
||||
.filter((r: any) => !r._isDept && r.userId)
|
||||
.map((r: any) => r.userId);
|
||||
}
|
||||
|
||||
async function handleAssignUsersSubmit() {
|
||||
const roleId = assignCurrentRole.value?.roleId;
|
||||
if (!roleId || !assignSelectedIds.value.length) {
|
||||
useMessage().warning('请选择要关联的用户');
|
||||
return;
|
||||
}
|
||||
assignSubmitLoading.value = true;
|
||||
try {
|
||||
await assignUsersToRole(roleId, assignSelectedIds.value);
|
||||
useMessage().success('已关联 ' + assignSelectedIds.value.length + ' 名用户');
|
||||
showAssignUserDialog.value = false;
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '操作失败');
|
||||
} finally {
|
||||
assignSubmitLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 批量指定分组
|
||||
const handleBatchGroup = async () => {
|
||||
const ids = selectObjs.value || [];
|
||||
if (!ids.length) {
|
||||
useMessage().warning('请先勾选要设置分组的角色');
|
||||
return;
|
||||
}
|
||||
batchGroupLoading.value = true;
|
||||
try {
|
||||
await batchUpdateRoleGroup(ids, batchGroupName.value || '');
|
||||
useMessage().success('分组已更新');
|
||||
showBatchGroupDialog.value = false;
|
||||
batchGroupName.value = '';
|
||||
getDataList();
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '操作失败');
|
||||
} finally {
|
||||
batchGroupLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.role-group-name {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.mb12 { margin-bottom: 12px; }
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
.mt8 { margin-top: 8px; }
|
||||
.assign-user-tip { color: var(--el-text-color-secondary); font-size: 13px; }
|
||||
.dept-row { font-weight: 600; color: var(--el-text-color-primary); }
|
||||
</style>
|
||||
|
||||
@@ -204,7 +204,8 @@ const dataRules = ref({
|
||||
{ required: true, message: '学院不能为空', trigger: 'change' }
|
||||
],
|
||||
classCode: [
|
||||
{ required: true, message: '班级代码不能为空', trigger: 'blur' }
|
||||
{ required: true, message: '班级代码不能为空', trigger: 'blur' },
|
||||
{ min: 4, message: '班级代码至少4位(班号取后4位)', trigger: 'blur' }
|
||||
],
|
||||
classNo: [
|
||||
{ required: true, message: '班号不能为空', trigger: 'blur' }
|
||||
@@ -406,11 +407,11 @@ watch(() => form.enterDate, (newVal) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 监听班级代码,取后四位为班号
|
||||
// 监听班级代码,取后四位为班号(班级代码至少4位)
|
||||
watch(() => form.classCode, (newVal) => {
|
||||
if (newVal) {
|
||||
const length = newVal.length
|
||||
if (length > 4) {
|
||||
if (length >= 4) {
|
||||
// 只在新增模式下自动填充班号,编辑模式下如果班号为空才填充
|
||||
if (!form.id || !form.classNo) {
|
||||
form.classNo = newVal.substring(length - 4, length)
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="200">
|
||||
<el-table-column label="操作" align="center" fixed="right" width="260">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="View"
|
||||
|
||||
@@ -262,13 +262,25 @@
|
||||
<template #default="scope" v-if="col.prop === 'gender'">
|
||||
<GenderTag :gender="scope.row.gender" />
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'education'">
|
||||
<el-tag v-if="getEducationLabel(scope.row.education)" size="small" type="info" effect="plain">
|
||||
{{ getEducationLabel(scope.row.education) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'isDorm'">
|
||||
<el-tag v-if="scope.row.isDorm === 1 || scope.row.isDorm === '1'" size="small" type="success" effect="plain">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.isDorm === 0 || scope.row.isDorm === '0'" size="small" type="info" effect="plain">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'enrollStatus'">
|
||||
<el-tag v-if="scope.row.enrollStatus" size="small" type="info" effect="plain">{{ scope.row.enrollStatus }}</el-tag>
|
||||
<el-tag
|
||||
v-if="getEnrollStatusLabel(scope.row.enrollStatus)"
|
||||
size="small"
|
||||
type="info"
|
||||
effect="plain">
|
||||
{{ getEnrollStatusLabel(scope.row.enrollStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'stuStatus'">
|
||||
@@ -427,7 +439,6 @@ import {
|
||||
editIsleader,
|
||||
updateInout,
|
||||
updateStuSimpleInfo,
|
||||
getStuStatus,
|
||||
prePrint
|
||||
} from "/@/api/basic/basicstudent";
|
||||
import { getDeptList, getClassListByRole } from "/@/api/basic/basicclass";
|
||||
@@ -456,6 +467,7 @@ const deptList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
const statusList = ref<any[]>([])
|
||||
const stuStatusList = ref<any[]>([])
|
||||
const educationList = ref<any[]>([])
|
||||
const selectedRows = ref<any[]>([])
|
||||
const importCertificateDialogVisible = ref(false)
|
||||
const uploadLoading = ref(false)
|
||||
@@ -781,9 +793,17 @@ const getClassListData = async () => {
|
||||
// 获取学籍状态列表
|
||||
const getStatusListData = async () => {
|
||||
try {
|
||||
const res = await getStuStatus()
|
||||
const res = await getDicts('enroll_status')
|
||||
if (res.data) {
|
||||
statusList.value = Array.isArray(res.data) ? res.data : []
|
||||
if (Array.isArray(res.data)) {
|
||||
// 确保数据格式统一为 {label, value}
|
||||
statusList.value = res.data.map((item: any) => ({
|
||||
label: item.label || item.name || item.dictLabel || item.text || '',
|
||||
value: String(item.value || item.code || item.dictValue || item.id || '')
|
||||
})).filter((item: any) => item.label && item.value !== undefined && item.value !== null && item.value !== '')
|
||||
} else {
|
||||
statusList.value = []
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取学籍状态列表失败', err)
|
||||
@@ -791,6 +811,24 @@ const getStatusListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文化程度字典
|
||||
const getEducationListData = async () => {
|
||||
try {
|
||||
const res = await getDicts('pre_school_education')
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
educationList.value = res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
}))
|
||||
} else {
|
||||
educationList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取文化程度字典失败', err)
|
||||
educationList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学生状态列表
|
||||
const getStuStatusListData = async () => {
|
||||
try {
|
||||
@@ -813,6 +851,20 @@ const getStuStatusListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 根据学籍状态值获取标签
|
||||
const getEnrollStatusLabel = (value: any) => {
|
||||
if (value === undefined || value === null || value === '') return ''
|
||||
const status = statusList.value.find(item => String(item.value) === String(value))
|
||||
return status ? status.label : ''
|
||||
}
|
||||
|
||||
// 根据文化程度值获取标签
|
||||
const getEducationLabel = (value: any) => {
|
||||
if (value === undefined || value === null || value === '') return ''
|
||||
const item = educationList.value.find(i => String(i.value) === String(value))
|
||||
return item ? item.label : ''
|
||||
}
|
||||
|
||||
// 根据学生状态值获取标签
|
||||
const getStuStatusLabel = (value: any) => {
|
||||
if (value === undefined || value === null || value === '') return ''
|
||||
@@ -833,6 +885,7 @@ onMounted(() => {
|
||||
getDeptListData()
|
||||
getClassListData()
|
||||
getStatusListData()
|
||||
getEducationListData()
|
||||
getStuStatusListData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -35,6 +35,14 @@
|
||||
招标代理管理
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
icon="Files"
|
||||
link
|
||||
type="primary"
|
||||
|
||||
>
|
||||
代理汇总
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="FolderAdd"
|
||||
type="primary"
|
||||
@@ -170,7 +178,7 @@ const handleDelete = async (row: any) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(row.id);
|
||||
await delObj({"id":row.id});
|
||||
useMessage().success('删除成功');
|
||||
getDataList();
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -21,12 +21,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 树形表格 -->
|
||||
<!-- 树形表格(懒加载:仅首屏加载根节点,展开时再加载子节点) -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
lazy
|
||||
:load="loadTreeNode"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
@@ -88,7 +90,7 @@
|
||||
<script setup lang="ts" name="PurchasingCategory">
|
||||
import { ref, reactive, defineAsyncComponent } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { getTree, delObj } from "/@/api/finance/purchasingcategory";
|
||||
import { getTreeRoots, getTreeChildren, delObj } from "/@/api/finance/purchasingcategory";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { List, Document, DocumentCopy, EditPen } from '@element-plus/icons-vue'
|
||||
|
||||
@@ -100,19 +102,40 @@ const tableRef = ref()
|
||||
const formDialogRef = ref()
|
||||
|
||||
/**
|
||||
* 查询树形数据方法
|
||||
* @param params - 查询参数
|
||||
* @returns Promise<any>
|
||||
* 查询树根节点(懒加载:首屏只加载根节点)
|
||||
*/
|
||||
const queryTree = (params?: any) => {
|
||||
return getTree(params);
|
||||
const queryTreeRoots = () => {
|
||||
return getTreeRoots().then((res: any) => {
|
||||
const list = res?.data ?? [];
|
||||
return { data: Array.isArray(list) ? list : [] };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 懒加载子节点:展开某行时按需请求子节点
|
||||
* @param row 当前行
|
||||
* @param treeNode 树节点信息
|
||||
* @param resolve 回调,传入子节点数组
|
||||
*/
|
||||
const loadTreeNode = (row: any, treeNode: any, resolve: (data: any[]) => void) => {
|
||||
const parentCode = row?.code;
|
||||
if (!parentCode) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
getTreeChildren(parentCode)
|
||||
.then((res: any) => {
|
||||
const list = res?.data ?? [];
|
||||
resolve(Array.isArray(list) ? list : []);
|
||||
})
|
||||
.catch(() => resolve([]));
|
||||
};
|
||||
|
||||
/**
|
||||
* 定义响应式表格数据
|
||||
*/
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryTree,
|
||||
pageList: queryTreeRoots,
|
||||
queryForm: {},
|
||||
isPage: false, // 树形表格不分页
|
||||
});
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px" >
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收方式" prop="acceptType">
|
||||
<el-radio-group v-model="form.acceptType" :disabled="readonly">
|
||||
<el-radio label="1" :disabled="!canFill">填写履约验收评价表</el-radio>
|
||||
<el-radio label="2">上传履约验收评价表</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="!canFill" class="el-form-item__tip">金额≥30万,仅支持上传模版</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收日期" prop="acceptDate">
|
||||
<el-date-picker
|
||||
v-model="form.acceptDate"
|
||||
type="date"
|
||||
placeholder="请选择"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 填报方式:验收内容表格 -->
|
||||
<template v-if="form.acceptType === '1' && canFill">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收内容" prop="acceptContents">
|
||||
<el-table :data="form.acceptContents" border size="small" class="accept-content-table">
|
||||
<el-table-column prop="itemName" label="验收项" >
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<span>{{row.itemName}}</span>
|
||||
<el-input
|
||||
v-if="row.type === 'input'"
|
||||
v-model="row.remark"
|
||||
placeholder="请输入"
|
||||
size="small"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isQualified" label="合格/不合格" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-radio-group v-model="row.isQualified" size="small" :disabled="readonly">
|
||||
<el-radio label="1">合格</el-radio>
|
||||
<el-radio label="0">不合格</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 上传方式 -->
|
||||
<template v-if="form.acceptType === '2'">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="履约验收模版" prop="templateFileIds">
|
||||
<UploadFile
|
||||
v-model="templateFileIdsStr"
|
||||
:limit="1"
|
||||
:data="{ purchaseId: purchaseId || '', fileType: '110' }"
|
||||
upload-file-url="/purchase/purchasingfiles/upload"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收地点" prop="acceptAddress">
|
||||
<el-input v-model="form.acceptAddress" placeholder="请输入验收地点" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="存在问题及改进意见" prop="question">
|
||||
<el-input
|
||||
v-model="form.question"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入存在问题及改进意见"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 验收小组 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="验收小组" prop="acceptTeam">
|
||||
<div class="team-list">
|
||||
<div v-for="(m, idx) in form.acceptTeam" :key="idx" class="team-row">
|
||||
<el-select
|
||||
v-model="m.roleType"
|
||||
placeholder="身份"
|
||||
size="small"
|
||||
style="width: 130px; margin-right: 8px"
|
||||
:disabled="readonly"
|
||||
@change="(val) => onRoleChange(idx, val as string)"
|
||||
>
|
||||
<el-option label="组长(校内)" value="LEADER_IN" />
|
||||
<el-option label="组长(校外)" value="LEADER_OUT" />
|
||||
<el-option label="组员(校内)" value="MEMBER_IN" />
|
||||
<el-option label="组员(校外)" value="MEMBER_OUT" />
|
||||
</el-select>
|
||||
<template v-if="m.roleType === 'LEADER_IN' || m.roleType === 'MEMBER_IN'">
|
||||
<org-selector
|
||||
v-model:orgList="m.userList"
|
||||
type="user"
|
||||
:multiple="false"
|
||||
@update:orgList="(list: any[]) => onTeamUserChange(idx, list)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input v-model="m.name" placeholder="姓名" size="small" style="width:120px" :disabled="readonly" />
|
||||
<el-input v-model="m.deptName" placeholder="单位/部门" size="small" style="width:160px" :disabled="readonly" />
|
||||
</template>
|
||||
<el-button v-if="!readonly && form.acceptTeam.length > 3" type="danger" link size="small" @click="removeTeam(idx)">删除</el-button>
|
||||
</div>
|
||||
<el-button v-if="!readonly" type="primary" link size="small" @click="addTeam">+ 增加成员</el-button>
|
||||
</div>
|
||||
<div class="el-form-item__tip">
|
||||
至少3人,且为单数
|
||||
<template v-if="(previousBatchesTeams || []).length > 0">
|
||||
;
|
||||
<span class="copy-from-inline">
|
||||
从往期带入
|
||||
<el-select
|
||||
v-model="copyFromBatch"
|
||||
placeholder="同第N期"
|
||||
clearable
|
||||
size="small"
|
||||
style="width: 100px"
|
||||
@change="onCopyFromBatch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in (previousBatchesTeams || [])"
|
||||
:key="item.batch"
|
||||
:label="`同第${item.batch}期`"
|
||||
:value="item.batch"
|
||||
/>
|
||||
</el-select>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
canFill: boolean
|
||||
readonly?: boolean
|
||||
purchaseId?: string
|
||||
acceptanceItems?: any[]
|
||||
batchNum?: number
|
||||
previousBatchesTeams?: { batch: number; team: any[] }[]
|
||||
}>(),
|
||||
{ readonly: false, canFill: true, purchaseId: '', batchNum: 1, previousBatchesTeams: () => [] }
|
||||
)
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const templateFileIdsStr = ref('')
|
||||
const copyFromBatch = ref<number | null>(null)
|
||||
|
||||
const form = reactive({
|
||||
acceptType: '1',
|
||||
acceptDate: '',
|
||||
acceptContents: [] as any[],
|
||||
acceptTeam: [
|
||||
{ name: '', deptCode: '', deptName: '', roleType: '' },
|
||||
{ name: '', deptCode: '', deptName: '', roleType: '' },
|
||||
{ name: '', deptCode: '', deptName: '', roleType: '' },
|
||||
] as any[],
|
||||
templateFileIds: [] as string[],
|
||||
acceptAddress: '',
|
||||
question: '',
|
||||
remark: '',
|
||||
...props.modelValue,
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
Object.assign(form, val || {})
|
||||
// 金额≥30万时,强制为上传方式
|
||||
if (!props.canFill && form.acceptType === '1') {
|
||||
form.acceptType = '2'
|
||||
}
|
||||
}, { deep: true })
|
||||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||
// 金额≥30万时,默认选中上传方式
|
||||
watch(() => props.canFill, (val) => {
|
||||
if (!val && form.acceptType === '1') {
|
||||
form.acceptType = '2'
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => props.acceptanceItems, (items) => {
|
||||
if (items?.length && form.acceptContents.length === 0) {
|
||||
form.acceptContents = items.map((it: any) => ({
|
||||
configId: it.id,
|
||||
itemName: it.itemName,
|
||||
type: it.type,
|
||||
isQualified: '1',
|
||||
remark: '',
|
||||
}))
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(templateFileIdsStr, (s) => {
|
||||
const arr = s ? s.split(',').map((x: string) => x.trim()).filter(Boolean) : []
|
||||
if (JSON.stringify(form.templateFileIds) !== JSON.stringify(arr)) {
|
||||
form.templateFileIds = arr
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => form.templateFileIds, (arr) => {
|
||||
if (Array.isArray(arr) && arr.length) templateFileIdsStr.value = arr.join(',')
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
const addTeam = () => {
|
||||
form.acceptTeam.push({ name: '', deptCode: '', deptName: '', roleType: '' })
|
||||
}
|
||||
|
||||
const removeTeam = (idx: number) => {
|
||||
form.acceptTeam.splice(idx, 1)
|
||||
}
|
||||
|
||||
const onCopyFromBatch = (n: number | null) => {
|
||||
if (!n) return
|
||||
const item = props.previousBatchesTeams?.find((x) => x.batch === n)
|
||||
if (item?.team?.length) {
|
||||
form.acceptTeam = item.team.map((m: any) => ({
|
||||
name: m.name || '',
|
||||
deptCode: m.deptCode || '',
|
||||
deptName: m.deptName || '',
|
||||
roleType: m.roleType || '',
|
||||
}))
|
||||
}
|
||||
copyFromBatch.value = null
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
acceptType: [{ required: true, message: '请选择验收方式', trigger: 'change' }],
|
||||
acceptDate: [{ required: true, message: '请选择验收日期', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const onRoleChange = (idx: number, val: string) => {
|
||||
const isLeader = val === 'LEADER_IN' || val === 'LEADER_OUT'
|
||||
if (isLeader) {
|
||||
const hasOtherLeader = form.acceptTeam.some((m, i) =>
|
||||
i !== idx && (m.roleType === 'LEADER_IN' || m.roleType === 'LEADER_OUT')
|
||||
)
|
||||
if (hasOtherLeader) {
|
||||
// 只能有一个组长
|
||||
form.acceptTeam[idx].roleType = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onTeamUserChange = (idx: number, list: any[]) => {
|
||||
const m = form.acceptTeam[idx]
|
||||
if (!m) return
|
||||
if (list && list.length) {
|
||||
const u = list[0]
|
||||
m.name = u.name || u.realName || ''
|
||||
m.deptCode = u.deptCode || u.commonDeptCode || ''
|
||||
m.deptName = u.deptName || u.commonDeptName || ''
|
||||
m.userList = list
|
||||
} else {
|
||||
m.name = ''
|
||||
m.deptCode = ''
|
||||
m.deptName = ''
|
||||
m.userList = []
|
||||
}
|
||||
}
|
||||
|
||||
const validate = () => formRef.value?.validate()
|
||||
|
||||
defineExpose({ validate, form })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.copy-from-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.copy-from-inline :deep(.el-select) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="项目名称">
|
||||
<el-input :model-value="projectName || form.projectName" readonly placeholder="-" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="需求部门">
|
||||
<el-input :model-value="deptName || form.deptName" readonly placeholder="-" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否签订合同" prop="hasContract">
|
||||
<el-radio-group v-model="form.hasContract">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.hasContract === '1'">
|
||||
<el-form-item label="合同" prop="contractId">
|
||||
<el-select
|
||||
v-model="form.contractId"
|
||||
placeholder="请选择合同"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:loading="contractLoading"
|
||||
@visible-change="onContractSelectVisibleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractOptions"
|
||||
:key="item.id"
|
||||
:label="item.contractName || item.contractNo || item.id"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否分期验收" prop="isInstallment">
|
||||
<el-radio-group v-model="form.isInstallment">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.isInstallment === '1'">
|
||||
<el-form-item label="分期次数" prop="totalPhases">
|
||||
<el-input-number v-model="form.totalPhases" :min="1" :max="99" placeholder="请输入" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="供应商名称" prop="supplierName">
|
||||
<el-input v-model="form.supplierName" placeholder="选择合同后自动带出" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="供应商联系人及电话" prop="supplierContact">
|
||||
<el-input v-model="form.supplierContact" placeholder="请输入" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="采购人员" prop="purchaserId">
|
||||
<org-selector v-model:orgList="purchaserList" type="user" :multiple="false" @update:orgList="onPurchaserChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="资产管理员" prop="assetAdminId">
|
||||
<org-selector v-model:orgList="assetAdminList" type="user" :multiple="false" @update:orgList="onAssetAdminChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { getContracts } from '/@/api/finance/purchasingrequisition'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
projectName?: string
|
||||
deptName?: string
|
||||
/** 采购申请ID,用于拉取合同列表 */
|
||||
purchaseId?: string | number
|
||||
/** 每次打开弹窗时变化,用于强制重置内部 form */
|
||||
resetKey?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const contractOptions = ref<any[]>([])
|
||||
const contractLoading = ref(false)
|
||||
const contractLoaded = ref(false)
|
||||
const purchaserList = ref<any[]>([])
|
||||
const assetAdminList = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
supplierName: '',
|
||||
supplierContact: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
...props.modelValue,
|
||||
})
|
||||
|
||||
const syncFormFromModel = (val: Record<string, any> | undefined) => {
|
||||
Object.assign(form, val || {})
|
||||
// 同步采购人员、资产管理员回 org-selector
|
||||
if (form.purchaserId && form.purchaserName) {
|
||||
purchaserList.value = [{ id: form.purchaserId, name: form.purchaserName, type: 'user' }]
|
||||
} else {
|
||||
purchaserList.value = []
|
||||
}
|
||||
if (form.assetAdminId && form.assetAdminName) {
|
||||
assetAdminList.value = [{ id: form.assetAdminId, name: form.assetAdminName, type: 'user' }]
|
||||
} else {
|
||||
assetAdminList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const loadContractOptions = async () => {
|
||||
if (contractLoaded.value || contractLoading.value) return
|
||||
contractLoading.value = true
|
||||
try {
|
||||
const res = await getContracts(props.purchaseId ? { id: props.purchaseId } : {})
|
||||
const list = res?.data
|
||||
contractOptions.value = Array.isArray(list) ? list : []
|
||||
contractLoaded.value = true
|
||||
} catch (_) {
|
||||
contractOptions.value = []
|
||||
} finally {
|
||||
contractLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onContractSelectVisibleChange = (visible: boolean) => {
|
||||
if (visible && form.hasContract === '1' && contractOptions.value.length === 0) {
|
||||
loadContractOptions()
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, syncFormFromModel, { deep: true, immediate: true })
|
||||
// resetKey 变化时强制用 modelValue 覆盖内部 form,并重置合同列表以便重新拉取
|
||||
watch(() => props.resetKey, () => {
|
||||
syncFormFromModel(props.modelValue)
|
||||
contractLoaded.value = false
|
||||
contractOptions.value = []
|
||||
})
|
||||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||
|
||||
watch(() => form.hasContract, (val) => {
|
||||
if (val === '1') {
|
||||
contractLoaded.value = false
|
||||
loadContractOptions()
|
||||
} else {
|
||||
contractOptions.value = []
|
||||
contractLoaded.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 选择合同后,自动带出合同供应商名称
|
||||
watch(
|
||||
() => form.contractId,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
form.supplierName = ''
|
||||
return
|
||||
}
|
||||
const c = contractOptions.value.find((it: any) => it.id === val)
|
||||
if (c && c.supplierName) {
|
||||
form.supplierName = c.supplierName
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (form.hasContract === '1') {
|
||||
loadContractOptions()
|
||||
}
|
||||
})
|
||||
|
||||
const onPurchaserChange = (list: any[]) => {
|
||||
if (list?.length) {
|
||||
const u = list[0]
|
||||
form.purchaserId = u.userId || u.id || ''
|
||||
form.purchaserName = u.name || u.realName || ''
|
||||
} else {
|
||||
form.purchaserId = ''
|
||||
form.purchaserName = ''
|
||||
}
|
||||
}
|
||||
|
||||
const onAssetAdminChange = (list: any[]) => {
|
||||
if (list?.length) {
|
||||
const u = list[0]
|
||||
form.assetAdminId = u.userId || u.id || ''
|
||||
form.assetAdminName = u.name || u.realName || ''
|
||||
} else {
|
||||
form.assetAdminId = ''
|
||||
form.assetAdminName = ''
|
||||
}
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
isInstallment: [{ required: true, message: '请选择是否分期验收', trigger: 'change' }],
|
||||
totalPhases: [{ required: true, message: '请输入分期次数', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const validate = () => formRef.value?.validate()
|
||||
|
||||
defineExpose({ validate, form })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,575 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="履约验收"
|
||||
width="75%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="purchasing-accept-modal"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading" class="modal-body" :key="String(purchaseId)">
|
||||
<div class="main-tabs">
|
||||
<div class="main-tab-nav">
|
||||
<div
|
||||
class="main-tab-item"
|
||||
:class="{ active: mainTab === 'common' }"
|
||||
@click="mainTab = 'common'"
|
||||
>
|
||||
公共信息
|
||||
</div>
|
||||
<div
|
||||
class="main-tab-item"
|
||||
:class="{ active: mainTab === 'batch' }"
|
||||
@click="mainTab = 'batch'"
|
||||
>
|
||||
{{ commonForm?.isInstallment === '0' ? '验收' : '分期验收' }}{{ commonForm?.isInstallment !== '0' && batches.length > 0 ? ` (${batches.length})` : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-tab-content">
|
||||
<div v-show="mainTab === 'common'" class="tab-content">
|
||||
<AcceptCommonForm
|
||||
:key="`${purchaseId}-${openToken}`"
|
||||
:reset-key="openToken"
|
||||
ref="commonFormRef"
|
||||
v-model="commonForm"
|
||||
:purchase-id="purchaseId"
|
||||
:project-name="applyInfo?.projectName"
|
||||
:dept-name="applyInfo?.deptName"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="mainTab === 'batch'" class="tab-content">
|
||||
<div v-if="batches.length > 0">
|
||||
<div v-show="commonForm?.isInstallment !== '0'" class="batch-tabs">
|
||||
<div
|
||||
v-for="b in batches"
|
||||
:key="b.id"
|
||||
class="batch-tab-item"
|
||||
:class="{ active: String(b.batch) === activeTab, disabled: !canEditBatch(b.batch) }"
|
||||
@click="canEditBatch(b.batch) && (activeTab = String(b.batch))"
|
||||
>
|
||||
<span>第{{ b.batch }}期</span>
|
||||
<el-tag v-if="isBatchCompleted(b)" type="success" size="small">已填</el-tag>
|
||||
<el-tag v-else-if="!canEditBatch(b.batch)" type="info" size="small">需先完成上一期</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-panel">
|
||||
<AcceptBatchForm
|
||||
v-for="b in batches"
|
||||
v-show="String(b.batch) === activeTab"
|
||||
:key="b.id"
|
||||
:ref="(el) => setBatchFormRef(b.batch, el)"
|
||||
v-model="batchForms[b.batch]"
|
||||
:can-fill="canFillForm"
|
||||
:readonly="false"
|
||||
:purchase-id="String(purchaseId)"
|
||||
:acceptance-items="acceptanceItems"
|
||||
:batch-num="b.batch"
|
||||
:previous-batches-teams="getPreviousBatchesTeams(b.batch)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tip-box">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
请先在「公共信息」中填写并点击「保存公共配置」,系统将按分期次数自动生成验收批次
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
<!-- 下载履约验收模板按钮 -->
|
||||
<el-dropdown split-button type="primary" @click="handleDownloadTemplate" @command="handleDownloadTemplateCommand">
|
||||
下载履约模板
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="current" :disabled="!activeBatchId">
|
||||
下载当前期({{ activeTab }}期)
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="all" :disabled="batches.length === 0">
|
||||
下载全部期数
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
v-if="mainTab === 'common' || batches.length === 0"
|
||||
type="primary"
|
||||
@click="saveCommonConfig"
|
||||
:loading="saving"
|
||||
>
|
||||
保存公共配置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="mainTab === 'batch' && activeBatchId"
|
||||
type="primary"
|
||||
@click="saveCurrentBatch"
|
||||
:loading="saving"
|
||||
>
|
||||
保存第{{ activeTab }}期
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { handleBlobFile } from '/@/utils/other'
|
||||
import {
|
||||
saveCommonConfig as apiSaveCommonConfig,
|
||||
getCommonConfigWithBatches,
|
||||
updateBatch,
|
||||
canFillForm as apiCanFillForm,
|
||||
getAcceptanceItems,
|
||||
getDetail,
|
||||
downloadPerformanceAcceptanceTemplate,
|
||||
} from '/@/api/purchase/purchasingAccept'
|
||||
import AcceptCommonForm from './AcceptCommonForm.vue'
|
||||
import AcceptBatchForm from './AcceptBatchForm.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const purchaseId = ref<string | number>('')
|
||||
const applyInfo = ref<any>(null)
|
||||
const rowProjectType = ref<string>('A')
|
||||
const canFillForm = ref(true)
|
||||
const acceptanceItems = ref<any[]>([])
|
||||
const batches = ref<any[]>([])
|
||||
const mainTab = ref('common')
|
||||
const activeTab = ref('1')
|
||||
const commonFormRef = ref()
|
||||
const batchFormRefMap = ref<Record<number, any>>({})
|
||||
/** 使用 ref 并在每次打开时替换整个对象,确保子组件能感知引用变化并清空 */
|
||||
const commonForm = ref<Record<string, any>>({})
|
||||
/** 每次打开自增,用于强制 AcceptCommonForm 重新挂载,确保公共信息彻底清空 */
|
||||
const openToken = ref(0)
|
||||
const batchForms = reactive<Record<number, any>>({})
|
||||
/** 记录哪些期已保存到服务器,用于控制“下一期可填”:只有上一期已保存才允许填下一期 */
|
||||
const batchSavedFlags = ref<Record<number, boolean>>({})
|
||||
|
||||
const setBatchFormRef = (batch: number, el: any) => {
|
||||
if (el) batchFormRefMap.value[batch] = el
|
||||
}
|
||||
|
||||
const activeBatchId = computed(() => {
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||
return b?.id || ''
|
||||
})
|
||||
|
||||
const getPreviousBatchesTeams = (batchNum: number) => {
|
||||
const list: { batch: number; team: any[] }[] = []
|
||||
for (let n = 1; n < batchNum; n++) {
|
||||
const team = batchForms[n]?.acceptTeam
|
||||
if (Array.isArray(team) && team.length > 0) {
|
||||
list.push({ batch: n, team })
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/** 是否允许编辑该期:第 1 期始终可编辑;第 N 期仅当第 1~N-1 期均已保存后才可编辑 */
|
||||
const canEditBatch = (batch: number) => {
|
||||
if (batch === 1) return true
|
||||
for (let i = 1; i < batch; i++) {
|
||||
if (!batchSavedFlags.value[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 该期是否已保存(用于 tab 上显示“已填”标签) */
|
||||
const isBatchCompleted = (b: any) => {
|
||||
return !!batchSavedFlags.value[b.batch]
|
||||
}
|
||||
|
||||
const isBatchCompletedByIdx = (batch: number) => {
|
||||
return !!batchSavedFlags.value[batch]
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
if (!purchaseId.value) return
|
||||
const currentId = String(purchaseId.value)
|
||||
loading.value = true
|
||||
try {
|
||||
const [configRes, canFillRes] = await Promise.all([
|
||||
getCommonConfigWithBatches(currentId),
|
||||
apiCanFillForm(currentId),
|
||||
])
|
||||
// 防止快速切换:若已打开其他申请单,忽略本次结果
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
|
||||
const config = configRes?.data
|
||||
canFillForm.value = !!canFillRes?.data
|
||||
|
||||
if (config?.common) {
|
||||
applyInfo.value = config.common
|
||||
// 仅当存在已保存批次时,才用接口数据回填公共信息;否则保持 open() 中的默认清空值
|
||||
if (config?.batches?.length) {
|
||||
Object.assign(commonForm.value, {
|
||||
hasContract: config.common.hasContract || '0',
|
||||
contractId: config.common.contractId || '',
|
||||
isInstallment: config.common.isInstallment || '0',
|
||||
totalPhases: config.common.totalPhases || 1,
|
||||
supplierName: config.common.supplierName || '',
|
||||
supplierContact: config.common.supplierContact || '',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const projectType = applyInfo.value?.projectType || rowProjectType.value || 'A'
|
||||
const typeMap: Record<string, string> = { A: 'A', B: 'B', C: 'C' }
|
||||
const at = typeMap[projectType] || 'A'
|
||||
const itemsRes = await getAcceptanceItems(at)
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
acceptanceItems.value = itemsRes?.data || []
|
||||
|
||||
if (config?.batches?.length) {
|
||||
batches.value = config.batches.sort((a: any, b: any) => (a.batch || 0) - (b.batch || 0))
|
||||
activeTab.value = String(batches.value[0]?.batch || '1')
|
||||
mainTab.value = 'batch'
|
||||
for (const b of batches.value) {
|
||||
if (!batchForms[b.batch]) batchForms[b.batch] = {}
|
||||
}
|
||||
await loadBatchDetails()
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
} else {
|
||||
batches.value = []
|
||||
}
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadBatchDetails = async () => {
|
||||
for (const b of batches.value) {
|
||||
batchSavedFlags.value[b.batch] = false
|
||||
}
|
||||
for (const b of batches.value) {
|
||||
try {
|
||||
const res = await getDetail(String(purchaseId.value), b.batch)
|
||||
const d = res?.data
|
||||
if (d?.accept) {
|
||||
// 仅当该期在服务端有验收日期(且验收方式)时才视为已保存,避免空结构被当成“已填”
|
||||
const hasSaved = !!(d.accept.acceptDate && d.accept.acceptType)
|
||||
batchSavedFlags.value[b.batch] = hasSaved
|
||||
const itemMap = (acceptanceItems.value || []).reduce((acc: any, it: any) => {
|
||||
acc[it.id] = it
|
||||
return acc
|
||||
}, {})
|
||||
batchForms[b.batch] = {
|
||||
acceptType: d.accept.acceptType || '1',
|
||||
acceptDate: d.accept.acceptDate || '',
|
||||
acceptAddress: d.accept.acceptAddress || '',
|
||||
question: d.accept.question || '',
|
||||
remark: d.accept.remark || '',
|
||||
templateFileIds: d.accept.templateFileIds || [],
|
||||
acceptContents: (d.contents || []).map((c: any) => {
|
||||
const cfg = itemMap[c.configId]
|
||||
return {
|
||||
configId: c.configId,
|
||||
itemName: cfg?.itemName || '',
|
||||
type: cfg?.type,
|
||||
isQualified: c.isQualified || '1',
|
||||
remark: c.remark || '',
|
||||
}
|
||||
}),
|
||||
acceptTeam: (d.team || []).map((t: any) => ({
|
||||
name: t.name,
|
||||
deptCode: t.deptCode,
|
||||
deptName: t.deptName,
|
||||
roleType: t.roleType || '',
|
||||
})),
|
||||
}
|
||||
if (batchForms[b.batch].acceptTeam.length < 3) {
|
||||
while (batchForms[b.batch].acceptTeam.length < 3) {
|
||||
batchForms[b.batch].acceptTeam.push({ name: '', deptCode: '', deptName: '' })
|
||||
}
|
||||
}
|
||||
if (acceptanceItems.value.length && (!batchForms[b.batch].acceptContents || batchForms[b.batch].acceptContents.length === 0)) {
|
||||
batchForms[b.batch].acceptContents = acceptanceItems.value.map((it: any) => ({
|
||||
configId: it.id,
|
||||
itemName: it.itemName,
|
||||
type: it.type,
|
||||
isQualified: '1',
|
||||
remark: '',
|
||||
}))
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
const saveCommonConfig = async () => {
|
||||
const formRef = commonFormRef.value
|
||||
const valid = await formRef?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
// 直接从子组件 form 读取,确保拿到用户填写的最新值(避免 v-model 同步延迟)
|
||||
const form = formRef?.form || commonForm.value
|
||||
const isInstallment = form.isInstallment === '1' || form.isInstallment === 1
|
||||
if (isInstallment && (!form.totalPhases || form.totalPhases < 1)) {
|
||||
useMessage().error('请填写分期次数')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await apiSaveCommonConfig({
|
||||
purchaseId: String(purchaseId.value),
|
||||
hasContract: form.hasContract ?? '0',
|
||||
contractId: form.contractId ?? '',
|
||||
isInstallment: form.isInstallment ?? '0',
|
||||
totalPhases: isInstallment ? (Number(form.totalPhases) || 1) : 1,
|
||||
supplierName: String(form.supplierName ?? ''),
|
||||
supplierContact: String(form.supplierContact ?? ''),
|
||||
purchaserId: String(form.purchaserId ?? ''),
|
||||
purchaserName: String(form.purchaserName ?? ''),
|
||||
assetAdminId: String(form.assetAdminId ?? ''),
|
||||
assetAdminName: String(form.assetAdminName ?? ''),
|
||||
})
|
||||
useMessage().success('保存成功')
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const saveCurrentBatch = async () => {
|
||||
const curBatch = Number(activeTab.value)
|
||||
const batchFormRef = batchFormRefMap.value[curBatch]
|
||||
const valid = await batchFormRef?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||
if (!b?.id) return
|
||||
const form = batchForms[curBatch]
|
||||
if (!form) return
|
||||
|
||||
if (!form.acceptType) {
|
||||
useMessage().error('请选择验收方式')
|
||||
return
|
||||
}
|
||||
if (!form.acceptDate) {
|
||||
useMessage().error('请选择验收日期')
|
||||
return
|
||||
}
|
||||
|
||||
const team = (form.acceptTeam || []).filter((m: any) => m?.name)
|
||||
if (team.length < 3 || team.length % 2 === 0) {
|
||||
useMessage().error('验收小组至少3人且为单数')
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
await updateBatch({
|
||||
id: b.id,
|
||||
purchaseId: String(purchaseId.value),
|
||||
acceptType: form.acceptType,
|
||||
acceptDate: form.acceptDate,
|
||||
acceptAddress: form.acceptAddress,
|
||||
question: form.question,
|
||||
remark: form.remark,
|
||||
templateFileIds: form.templateFileIds || [],
|
||||
acceptContents: form.acceptContents || [],
|
||||
acceptTeam: team,
|
||||
})
|
||||
useMessage().success('保存成功')
|
||||
batchSavedFlags.value[curBatch] = true
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
// 下载履约验收模板
|
||||
const handleDownloadTemplate = async () => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().error('请先选择采购项目')
|
||||
return
|
||||
}
|
||||
|
||||
// 默认下载当前期
|
||||
await downloadTemplateForBatch(Number(activeTab.value))
|
||||
}
|
||||
|
||||
// 处理下拉菜单命令
|
||||
const handleDownloadTemplateCommand = async (command: string) => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().error('请先选择采购项目')
|
||||
return
|
||||
}
|
||||
|
||||
if (command === 'current') {
|
||||
await downloadTemplateForBatch(Number(activeTab.value))
|
||||
} else if (command === 'all') {
|
||||
// 下载全部期数的模板
|
||||
for (const batch of batches.value) {
|
||||
await downloadTemplateForBatch(batch.batch)
|
||||
}
|
||||
useMessage().success(`已触发${batches.value.length}期模板下载`)
|
||||
}
|
||||
}
|
||||
|
||||
// 为指定批次下载模板
|
||||
const downloadTemplateForBatch = async (batchNum: number) => {
|
||||
try {
|
||||
const response = await downloadPerformanceAcceptanceTemplate(String(purchaseId.value), batchNum)
|
||||
|
||||
// 使用项目中现有的工具函数处理文件下载
|
||||
const fileName = `履约验收表-${purchaseId.value}-第${batchNum}期-${new Date().getTime()}.docx`;
|
||||
handleBlobFile(response, fileName)
|
||||
|
||||
useMessage().success(`第${batchNum}期履约模板下载成功`)
|
||||
} catch (error: any) {
|
||||
console.error('下载模板失败:', error)
|
||||
useMessage().error(error?.msg || '下载履约模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_COMMON_FORM = {
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
supplierName: '',
|
||||
supplierContact: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
}
|
||||
|
||||
/** 将弹窗内所有内容恢复为初始空值(替换整个对象以确保引用变化) */
|
||||
const resetAllToDefault = () => {
|
||||
openToken.value++
|
||||
commonForm.value = { ...DEFAULT_COMMON_FORM }
|
||||
applyInfo.value = null
|
||||
mainTab.value = 'common'
|
||||
activeTab.value = '1'
|
||||
batchFormRefMap.value = {}
|
||||
batches.value = []
|
||||
acceptanceItems.value = []
|
||||
canFillForm.value = true
|
||||
Object.keys(batchForms).forEach((k) => delete batchForms[Number(k)])
|
||||
batchSavedFlags.value = {}
|
||||
}
|
||||
|
||||
const open = async (row: any) => {
|
||||
purchaseId.value = row?.id ?? ''
|
||||
rowProjectType.value = row?.projectType || 'A'
|
||||
|
||||
// 1. 先将弹窗内所有内容恢复为初始空值
|
||||
resetAllToDefault()
|
||||
|
||||
// 2. 显示弹窗并开启 loading,避免接口返回前展示旧数据
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
|
||||
// 3. 等待 Vue 完成渲染,确保子组件已接收并展示空值
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
// 4. 再进行接口查询并覆盖
|
||||
await loadData()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.main-tab-nav {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
.main-tab-item {
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
color: var(--el-text-color-regular);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.main-tab-item:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.main-tab-item.active {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
border-bottom-color: var(--el-color-primary);
|
||||
}
|
||||
.main-tab-content {
|
||||
padding-top: 4px;
|
||||
}
|
||||
.tab-content {
|
||||
min-height: 200px;
|
||||
display: block;
|
||||
}
|
||||
.tip-box {
|
||||
padding: 20px 0;
|
||||
}
|
||||
.batch-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.batch-tab-item {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.batch-tab-item:hover:not(.disabled) {
|
||||
border-color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.batch-tab-item.active {
|
||||
background: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
.batch-tab-item.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.batch-panel {
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 弹窗横向滚动修复,需非 scoped 以影响 el-dialog */
|
||||
.purchasing-accept-modal .el-dialog__body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
355
src/views/finance/purchasingrequisition/implement.vue
Normal file
355
src/views/finance/purchasingrequisition/implement.vue
Normal file
@@ -0,0 +1,355 @@
|
||||
<template>
|
||||
<div class="implement-page">
|
||||
<div class="implement-form">
|
||||
<el-form-item label="实施采购方式" required>
|
||||
<el-radio-group v-model="implementType">
|
||||
<el-radio label="1">自行组织采购</el-radio>
|
||||
<el-radio label="2">委托代理采购</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 采购文件版本列表:保留原文件,多版本分别显示 -->
|
||||
<el-divider content-position="left">采购文件版本</el-divider>
|
||||
<div v-if="purchaseFileVersions.length" class="file-versions mb-2">
|
||||
<el-table :data="purchaseFileVersions" border size="small" max-height="280">
|
||||
<el-table-column type="index" label="版本" width="70" align="center">
|
||||
<template #default="{ $index }">V{{ $index + 1 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileTitle" label="文件名" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" label="上传人" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="上传时间" width="165" align="center">
|
||||
<template #default="{ row }">{{ formatCreateTime(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="handleDownloadVersion(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="mb-2">可继续上传新版本(保留原文件,格式 doc/docx/pdf,单文件不超过 5MB):</div>
|
||||
<upload-file
|
||||
v-model="implementFileIds"
|
||||
:limit="5"
|
||||
:file-type="['doc', 'docx', 'pdf']"
|
||||
:data="{ fileType: PURCHASE_FILE_TYPE }"
|
||||
upload-file-url="/purchase/purchasingfiles/upload"
|
||||
/>
|
||||
|
||||
<!-- 仅部门审核角色显示:采购代表相关 -->
|
||||
<template v-if="isDeptAuditRole">
|
||||
<el-divider content-position="left">采购代表</el-divider>
|
||||
<div class="mb-2">需求部门初审需指定采购代表人,请选择一种方式:</div>
|
||||
<el-radio-group v-model="representorMode" class="mb-2">
|
||||
<el-radio label="single">指定采购代表人(单人)</el-radio>
|
||||
<el-radio label="multi">部门多人由系统自动抽取</el-radio>
|
||||
</el-radio-group>
|
||||
<el-form-item v-if="representorMode === 'single'" label="采购代表人">
|
||||
<el-select v-model="representorTeacherNo" placeholder="请选择" clearable filterable style="width: 100%">
|
||||
<el-option v-for="m in deptMembers" :key="m.userId || m.teacherNo || m.id" :label="m.realName || m.name || m.teacherNo" :value="m.userId || m.teacherNo || m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="部门多人">
|
||||
<el-select v-model="representorsMulti" placeholder="请选择多人,系统将自动抽取一人" clearable filterable multiple style="width: 100%">
|
||||
<el-option v-for="m in deptMembers" :key="m.userId || m.teacherNo || m.id" :label="m.realName || m.name || m.teacherNo" :value="m.userId || m.teacherNo || m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<div class="implement-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<template v-if="implementHasPurchaseFiles && !applyRow?.fileFlowInstId">
|
||||
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">保存实施采购</el-button>
|
||||
<el-button v-if="canStartFileFlow" type="success" :loading="startFileFlowSubmitting" @click="handleStartFileFlow">发起采购文件审批</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">确定</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingImplement">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { implementApply, getApplyFiles, startFileFlow, getDeptMembers, getObj } from '/@/api/finance/purchasingrequisition'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import other from '/@/utils/other'
|
||||
import UploadFile from '/@/components/Upload/index.vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import * as orderVue from '/@/api/order/order-key-vue'
|
||||
|
||||
/** 部门审核角色编码:仅该角色下显示采购代表相关页面和功能,流转至部门审核时需填写采购代表 */
|
||||
const PURCHASE_DEPT_AUDIT_ROLE_CODE = 'PURCHASE_DEPT_AUDIT'
|
||||
/** 采购中心角色编码:可保存/发起实施采购,但不出现采购代表相关内容和接口 */
|
||||
const PURCHASE_CENTER_ROLE_CODE = 'PURCHASE_CENTER'
|
||||
|
||||
const roleCode = computed(() => Session.getRoleCode() || '')
|
||||
const isDeptAuditRole = computed(() => roleCode.value === PURCHASE_DEPT_AUDIT_ROLE_CODE)
|
||||
const isPurchaseCenterRole = computed(() => roleCode.value === PURCHASE_CENTER_ROLE_CODE)
|
||||
/** 可发起采购文件审批:部门审核(需填采购代表)、采购中心(不填采购代表) */
|
||||
const canStartFileFlow = computed(() => isDeptAuditRole.value || isPurchaseCenterRole.value)
|
||||
|
||||
// 与编辑界面一致:支持流程 dynamic-link 传入 currJob/currElTab,申请单 ID 优先取 currJob.orderId
|
||||
const props = defineProps({
|
||||
currJob: { type: Object, default: null },
|
||||
currElTab: { type: Object, default: null }
|
||||
})
|
||||
const emit = defineEmits(['handleJob'])
|
||||
|
||||
/** 是否被流程 handle 页面通过 dynamic-link 嵌入 */
|
||||
const isFlowEmbed = computed(() => !!props.currJob)
|
||||
|
||||
const route = useRoute()
|
||||
const PURCHASE_FILE_TYPE = '130'
|
||||
|
||||
/** 申请单 ID(数值,用于 getObj 等):与 add 一致,优先流程 currJob.orderId,否则 route.query.id */
|
||||
const applyId = computed(() => {
|
||||
const raw = applyIdRaw.value
|
||||
if (raw == null || raw === '') return null
|
||||
const n = Number(raw)
|
||||
return Number.isNaN(n) ? null : n
|
||||
})
|
||||
|
||||
/** 申请单 ID 原始字符串(用于 getApplyFiles 的 purchaseId,与编辑页一致,避免类型/精度问题) */
|
||||
const applyIdRaw = computed(() => {
|
||||
if (props.currJob?.orderId != null && props.currJob?.orderId !== '') {
|
||||
return String(props.currJob.orderId)
|
||||
}
|
||||
const id = route.query.id
|
||||
return id != null && id !== '' ? String(id) : ''
|
||||
})
|
||||
|
||||
const applyRow = ref<any>(null)
|
||||
/** 已有采购文件版本列表(按 createTime 排序,用于展示与提交时一并关联) */
|
||||
const purchaseFileVersions = ref<{ id: string; fileTitle?: string; createBy?: string; createTime?: string; remark?: string }[]>([])
|
||||
/** 本次新上传的采购文件(仅新版本,不与已有版本混在一起) */
|
||||
const implementFileIds = ref<string | string[]>([])
|
||||
const implementType = ref<string>('1')
|
||||
const implementSubmitting = ref(false)
|
||||
|
||||
const representorMode = ref<'single' | 'multi'>('single')
|
||||
const representorTeacherNo = ref<string>('')
|
||||
const representorsMulti = ref<string[]>([])
|
||||
const deptMembers = ref<any[]>([])
|
||||
const startFileFlowSubmitting = ref(false)
|
||||
|
||||
const implementHasPurchaseFiles = computed(() => {
|
||||
if (purchaseFileVersions.value.length > 0) return true
|
||||
const raw = implementFileIds.value
|
||||
if (Array.isArray(raw)) return raw.length > 0
|
||||
return !!raw
|
||||
})
|
||||
|
||||
function formatCreateTime(t?: string) {
|
||||
if (!t) return '-'
|
||||
const d = new Date(t)
|
||||
return isNaN(d.getTime()) ? t : d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
function handleDownloadVersion(file: { remark?: string; fileTitle?: string }) {
|
||||
if (!file?.remark) {
|
||||
useMessage().warning('无法获取文件路径')
|
||||
return
|
||||
}
|
||||
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(file.remark)}&fileTitle=${encodeURIComponent(file.fileTitle || '采购文件')}`
|
||||
other.downBlobFile(url, {}, file.fileTitle || '采购文件')
|
||||
}
|
||||
|
||||
const isInIframe = () => typeof window !== 'undefined' && window.self !== window.top
|
||||
|
||||
const postMessage = (type: string, payload?: any) => {
|
||||
if (typeof window !== 'undefined' && window.parent) {
|
||||
window.parent.postMessage({ type, ...payload }, '*')
|
||||
}
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
const id = applyId.value
|
||||
if (!id) {
|
||||
useMessage().warning('缺少申请单ID')
|
||||
return
|
||||
}
|
||||
const needDeptMembers = isDeptAuditRole.value
|
||||
try {
|
||||
const idStr = applyIdRaw.value || String(id)
|
||||
const requests: [ReturnType<typeof getObj>, ReturnType<typeof getApplyFiles>, ReturnType<typeof getDeptMembers>?] = [
|
||||
getObj(id),
|
||||
getApplyFiles(idStr)
|
||||
]
|
||||
if (needDeptMembers) requests.push(getDeptMembers())
|
||||
const results = await Promise.all(requests)
|
||||
const detailRes = results[0]
|
||||
const filesRes = results[1]
|
||||
const membersRes = needDeptMembers ? results[2] : null
|
||||
|
||||
applyRow.value = detailRes?.data ? { ...detailRes.data, id: detailRes.data.id ?? id } : { id }
|
||||
const row = applyRow.value
|
||||
if (row?.implementType) implementType.value = row.implementType
|
||||
// 回显需求部门初审-采购代表人方式与人员(与发起采购文件审批时保存的一致)
|
||||
if (row?.representorTeacherNo) {
|
||||
representorMode.value = 'single'
|
||||
representorTeacherNo.value = row.representorTeacherNo ?? ''
|
||||
representorsMulti.value = []
|
||||
} else if (row?.representors) {
|
||||
representorMode.value = 'multi'
|
||||
representorTeacherNo.value = ''
|
||||
const parts = typeof row.representors === 'string' ? row.representors.split(',') : []
|
||||
representorsMulti.value = parts.map((s: string) => s.trim()).filter(Boolean)
|
||||
} else {
|
||||
representorTeacherNo.value = ''
|
||||
representorsMulti.value = []
|
||||
}
|
||||
const list = filesRes?.data || []
|
||||
const purchaseFiles = list.filter((f: any) => f.fileType === PURCHASE_FILE_TYPE)
|
||||
purchaseFileVersions.value = purchaseFiles.map((f: any) => ({
|
||||
id: String(f.id),
|
||||
fileTitle: f.fileTitle || f.file_title || '采购文件',
|
||||
createBy: f.createBy ?? f.create_by ?? '-',
|
||||
createTime: f.createTime || f.create_time,
|
||||
remark: f.remark
|
||||
}))
|
||||
deptMembers.value = needDeptMembers && membersRes?.data ? membersRes.data : []
|
||||
} catch (_) {
|
||||
applyRow.value = { id }
|
||||
purchaseFileVersions.value = []
|
||||
deptMembers.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
postMessage('purchasingimplement:close')
|
||||
if (!isInIframe()) {
|
||||
window.history.back()
|
||||
}
|
||||
}
|
||||
|
||||
const handleImplementSubmit = async () => {
|
||||
const row = applyRow.value
|
||||
if (!row?.id && !applyId.value) return
|
||||
const id = row?.id ?? applyId.value
|
||||
if (!id) return
|
||||
if (!implementType.value) {
|
||||
useMessage().warning('请选择实施采购方式')
|
||||
return
|
||||
}
|
||||
const existingIds = purchaseFileVersions.value.map((f) => f.id)
|
||||
const raw = implementFileIds.value
|
||||
const newIds: string[] = Array.isArray(raw)
|
||||
? raw.map((x: any) => (typeof x === 'object' && x?.id ? x.id : x)).filter(Boolean)
|
||||
: raw ? [String(raw)] : []
|
||||
const fileIds = [...existingIds, ...newIds]
|
||||
if (fileIds.length === 0) {
|
||||
useMessage().warning('请至少上传一个采购文件')
|
||||
return
|
||||
}
|
||||
// 仅部门审核角色提交采购代表;采购中心保存时不传采购代表
|
||||
const single = isDeptAuditRole.value && representorMode.value === 'single' ? representorTeacherNo.value : undefined
|
||||
const multi = isDeptAuditRole.value && representorMode.value === 'multi' && representorsMulti.value?.length ? representorsMulti.value.join(',') : undefined
|
||||
implementSubmitting.value = true
|
||||
try {
|
||||
await implementApply(id, fileIds, implementType.value, single, multi)
|
||||
useMessage().success('实施采购已保存')
|
||||
implementFileIds.value = []
|
||||
await loadData()
|
||||
postMessage('purchasingimplement:saved')
|
||||
// 流程嵌入场景:通知流程当前 Tab 已保存,避免审批时提示“未保存”
|
||||
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
|
||||
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emit)
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '实施采购失败')
|
||||
} finally {
|
||||
implementSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleStartFileFlow = async () => {
|
||||
const row = applyRow.value
|
||||
const id = row?.id ?? applyId.value
|
||||
if (!id) return
|
||||
// 部门审核角色必须填写采购代表;采购中心不填采购代表
|
||||
if (isDeptAuditRole.value) {
|
||||
if (representorMode.value === 'single') {
|
||||
if (!representorTeacherNo.value) {
|
||||
useMessage().warning('请选择采购代表人')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (!representorsMulti.value?.length) {
|
||||
useMessage().warning('请选择部门多人')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
startFileFlowSubmitting.value = true
|
||||
try {
|
||||
const single = isDeptAuditRole.value && representorMode.value === 'single' ? representorTeacherNo.value : undefined
|
||||
const multi = isDeptAuditRole.value && representorMode.value === 'multi' ? representorsMulti.value.join(',') : undefined
|
||||
await startFileFlow(id, single, multi)
|
||||
useMessage().success('已发起采购文件审批流程')
|
||||
postMessage('purchasingimplement:submitSuccess')
|
||||
await loadData()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '发起失败')
|
||||
} finally {
|
||||
startFileFlowSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程嵌入时供 handle.vue 调用的“保存”回调:与页面按钮保存逻辑保持一致 */
|
||||
async function flowSubmitForm() {
|
||||
await handleImplementSubmit()
|
||||
}
|
||||
|
||||
// 流程切换工单时重新加载数据(与 add 编辑页一致)
|
||||
watch(
|
||||
() => props.currJob?.orderId ?? props.currJob?.id,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal && applyId.value) {
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData()
|
||||
if (isInIframe()) {
|
||||
document.documentElement.classList.add('iframe-mode')
|
||||
document.body.classList.add('iframe-mode')
|
||||
}
|
||||
// 流程嵌入:注册 tab 保存回调,供审批页调用(与采购申请编辑页保持一致)
|
||||
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
|
||||
orderVue.currElTabIsExist(props.currJob, props.currElTab.id)
|
||||
await orderVue.currElTabIsView({}, props.currJob, props.currElTab.id, flowSubmitForm)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.implement-page {
|
||||
padding: 20px;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.implement-form {
|
||||
flex: 1;
|
||||
.mb-2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
.implement-form-tip {
|
||||
margin-top: 12px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.implement-footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
95
src/views/finance/purchasingrequisition/implementForm.vue
Normal file
95
src/views/finance/purchasingrequisition/implementForm.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="实施采购"
|
||||
width="50%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="implement-iframe-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="implement-iframe-content">
|
||||
<iframe
|
||||
ref="iframeRef"
|
||||
:src="iframeSrc"
|
||||
frameborder="0"
|
||||
class="implement-iframe"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ImplementForm">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const iframeRef = ref<HTMLIFrameElement>()
|
||||
const applyId = ref<string | number>('')
|
||||
|
||||
const iframeSrc = computed(() => {
|
||||
const baseUrl = window.location.origin + window.location.pathname
|
||||
return `${baseUrl}#/finance/purchasingrequisition/implement?id=${applyId.value}`
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === 'purchasingimplement:submitSuccess') {
|
||||
handleClose()
|
||||
emit('refresh')
|
||||
} else if (event.data?.type === 'purchasingimplement:close') {
|
||||
handleClose()
|
||||
} else if (event.data?.type === 'purchasingimplement:saved') {
|
||||
emit('refresh')
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = (row: { id: string | number }) => {
|
||||
applyId.value = row?.id ?? ''
|
||||
visible.value = true
|
||||
window.addEventListener('message', handleMessage)
|
||||
}
|
||||
|
||||
watch(visible, (val) => {
|
||||
if (!val) {
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
openDialog,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.implement-iframe-content {
|
||||
width: 100%;
|
||||
height: 65vh;
|
||||
min-height: 480px;
|
||||
max-height: calc(100vh - 180px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.implement-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 480px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.implement-iframe-dialog .el-dialog__body {
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -43,12 +43,11 @@
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="撤回" value="-2" />
|
||||
|
||||
<el-option label="暂存" value="-1" />
|
||||
<el-option label="运行中" value="0" />
|
||||
<el-option label="完成" value="1" />
|
||||
<el-option label="作废" value="2" />
|
||||
<el-option label="终止" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否集采" prop="isCentralized">
|
||||
@@ -77,6 +76,14 @@
|
||||
采购申请管理
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
icon="Files"
|
||||
link
|
||||
type="primary"
|
||||
|
||||
>
|
||||
采购申请汇总
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="FolderAdd"
|
||||
type="primary"
|
||||
@@ -102,7 +109,7 @@
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="code" label="申请单编号" min-width="140" show-overflow-tooltip>
|
||||
<el-table-column prop="purchaseNo" label="申请单编号" min-width="140" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
<span style="margin-left: 4px">申请单编号</span>
|
||||
@@ -160,8 +167,10 @@
|
||||
<span style="margin-left: 4px">是否特殊</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.isSpecial === '1' || scope.row.isSpecial === 1" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.isSpecial === '0' || scope.row.isSpecial === 0" type="info">否</el-tag>
|
||||
<el-tag v-if="String(scope.row.isSpecial) === '1'" type="warning">紧急</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isSpecial) === '2'" type="danger">单一</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isSpecial) === '3'" type="info">进口</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isSpecial) === '0'" type="info">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -171,8 +180,8 @@
|
||||
<span style="margin-left: 4px">是否集采</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.isCentralized === '1'" type="success">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.isCentralized === '0'" type="info">否</el-tag>
|
||||
<el-tag v-if="String(scope.row.isCentralized) === '1'" type="success">是</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isCentralized) === '0'" type="info">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -182,40 +191,111 @@
|
||||
<span style="margin-left: 4px">审核状态</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === '-2'" type="info">撤回</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '-1'" type="warning">暂存</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '0'" type="primary">运行中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '1'" type="success">完成</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '2'" type="danger">作废</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '3'" type="info">终止</el-tag>
|
||||
<span v-else>-</span>
|
||||
<el-tooltip v-if="scope.row.flowInstId" content="点击查看审批过程" placement="top">
|
||||
<el-tag
|
||||
v-if="scope.row.status === '-2'"
|
||||
type="info"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">撤回</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '-1'"
|
||||
type="warning"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">暂存</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '0'"
|
||||
type="primary"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">运行中</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '1'"
|
||||
type="success"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">完成</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '2'"
|
||||
type="danger"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">作废</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '3'"
|
||||
type="info"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">终止</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-tooltip>
|
||||
<template v-else>
|
||||
<el-tag v-if="scope.row.status === '-2'" type="info">撤回</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '-1'" type="warning">暂存</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '0'" type="primary">运行中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '1'" type="success">完成</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '2'" type="danger">作废</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '3'" type="info">终止</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="300">
|
||||
<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">
|
||||
<el-button
|
||||
icon="View"
|
||||
link
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog('view', scope.row)">
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === '-1'"
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog('edit', scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === '-1'"
|
||||
icon="Delete"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
<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">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="View"
|
||||
@click="handleView(scope.row)">
|
||||
查看
|
||||
</el-button>
|
||||
<ActionDropdown
|
||||
:items="getActionMenuItems(scope.row)"
|
||||
@command="(command) => handleMoreCommand(command, scope.row)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -226,52 +306,56 @@
|
||||
:total="state.pagination.total"
|
||||
:current="state.pagination.current"
|
||||
:size="state.pagination.size"
|
||||
@pagination="getDataList"
|
||||
@sizeChange="sizeChangeHandle"
|
||||
@currentChange="currentChangeHandle"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增页面 iframe 对话框 -->
|
||||
<el-dialog
|
||||
v-model="showAddIframe"
|
||||
title="新增采购申请"
|
||||
width="90%"
|
||||
:style="{ maxWidth: '1600px' }"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="true"
|
||||
destroy-on-close
|
||||
class="iframe-dialog"
|
||||
@close="closeAddIframe">
|
||||
<div class="iframe-dialog-content">
|
||||
<iframe
|
||||
ref="addIframeRef"
|
||||
:src="addIframeSrc"
|
||||
frameborder="0"
|
||||
class="add-iframe"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 编辑、新增表单对话框 -->
|
||||
<!-- 新增/编辑/查看:统一使用 form.vue 弹窗(iframe 引入 add.vue) -->
|
||||
<FormDialog
|
||||
ref="formDialogRef"
|
||||
:dict-data="dictData"
|
||||
@refresh="getDataList" />
|
||||
|
||||
<!-- 履约验收弹窗 -->
|
||||
<PurchasingAcceptModal ref="acceptModalRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 查看审批过程(申请单审批 / 文件审批) -->
|
||||
<el-dialog
|
||||
v-model="showFlowComment"
|
||||
v-if="showFlowComment"
|
||||
:title="currFlowCommentType === 'file' ? '查看文件审批过程' : '查看审批过程'"
|
||||
top="20px"
|
||||
width="90%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@close="currFlowJob = null; currFlowCommentType = 'apply'">
|
||||
<FlowCommentTimeline v-if="currFlowJob" :key="String(currFlowJob.flowInstId) + currFlowCommentType" :curr-job="currFlowJob" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 实施采购:iframe 嵌入 implement.vue,供列表与流程页面使用 -->
|
||||
<ImplementForm ref="implementFormRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingRequisition">
|
||||
import { ref, reactive, defineAsyncComponent, onUnmounted, onMounted } from 'vue'
|
||||
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { getPage, delObj } from "/@/api/finance/purchasingrequisition";
|
||||
import { getPage, delObj, submitObj, getArchiveDownloadUrl, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl } from "/@/api/finance/purchasingrequisition";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { getDicts } from '/@/api/admin/dict';
|
||||
import { getTree } from '/@/api/finance/purchasingcategory';
|
||||
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning } from '@element-plus/icons-vue'
|
||||
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked, Edit, Delete, Upload, FolderOpened, Download } from '@element-plus/icons-vue'
|
||||
import other from '/@/utils/other'
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
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 dictData = ref({
|
||||
@@ -288,11 +372,15 @@ const dictData = ref({
|
||||
const router = useRouter()
|
||||
const tableRef = ref()
|
||||
const formDialogRef = ref()
|
||||
const acceptModalRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const showAddIframe = ref(false)
|
||||
const addIframeRef = ref<HTMLIFrameElement>()
|
||||
const addIframeSrc = ref('')
|
||||
/** 审批过程弹窗:是否显示、当前行对应的流程 job(供 Comment 组件用)、类型(申请单/文件) */
|
||||
const showFlowComment = ref(false)
|
||||
const currFlowJob = ref<{ id?: number; flowInstId?: number } | null>(null)
|
||||
const currFlowCommentType = ref<'apply' | 'file'>('apply')
|
||||
|
||||
const implementFormRef = ref()
|
||||
|
||||
/**
|
||||
* 定义响应式表格数据
|
||||
@@ -312,7 +400,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
/**
|
||||
* 使用 useTable 定义表格相关操作
|
||||
*/
|
||||
const { getDataList, tableStyle } = useTable(state);
|
||||
const { getDataList, tableStyle, sizeChangeHandle, currentChangeHandle } = useTable(state);
|
||||
|
||||
/**
|
||||
* 重置搜索表单
|
||||
@@ -323,42 +411,67 @@ const handleReset = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增采购申请 - 在 iframe 中展示
|
||||
* 新增采购申请 - 统一通过 form.vue 弹窗(iframe 引入 add.vue)
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
// 构建 iframe 的 src,使用当前页面的 hash 路由
|
||||
const baseUrl = window.location.origin + window.location.pathname
|
||||
addIframeSrc.value = `${baseUrl}#/finance/purchasingrequisition/add`
|
||||
showAddIframe.value = true
|
||||
// 监听来自 iframe 的消息
|
||||
window.addEventListener('message', handleIframeMessage)
|
||||
formDialogRef.value?.openDialog('add')
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭新增 iframe
|
||||
*/
|
||||
const closeAddIframe = () => {
|
||||
showAddIframe.value = false
|
||||
// 移除消息监听器
|
||||
window.removeEventListener('message', handleIframeMessage)
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理 iframe 发送的消息
|
||||
* 点击审核状态:若有流程实例则打开「查看审批过程」弹窗(参考 hi-job.vue)
|
||||
* @param row - 当前行数据(需含 flowInstId)
|
||||
*/
|
||||
const handleIframeMessage = (event: MessageEvent) => {
|
||||
// 验证消息来源(可选,根据实际需求)
|
||||
// if (event.origin !== window.location.origin) return
|
||||
|
||||
if (event.data && event.data.type === 'purchasingrequisition:submitSuccess') {
|
||||
// 提交成功,关闭 iframe 并刷新列表
|
||||
closeAddIframe()
|
||||
getDataList()
|
||||
useMessage().success('提交成功')
|
||||
} else if (event.data && event.data.type === 'purchasingrequisition:close') {
|
||||
// 关闭 iframe
|
||||
closeAddIframe()
|
||||
/** 点击审核状态:打开申请单审批过程 */
|
||||
const handleShowFlowComment = (row: any) => {
|
||||
if (!row?.flowInstId) {
|
||||
useMessage().info('暂存状态无审批过程');
|
||||
return;
|
||||
}
|
||||
currFlowCommentType.value = 'apply';
|
||||
currFlowJob.value = { id: row.id, flowInstId: row.flowInstId };
|
||||
showFlowComment.value = true;
|
||||
};
|
||||
|
||||
/** 点击文件审批状态:打开文件审批过程 */
|
||||
const handleShowFileFlowComment = (row: any) => {
|
||||
if (!row?.fileFlowInstId) {
|
||||
useMessage().info('未发起文件审批流程');
|
||||
return;
|
||||
}
|
||||
currFlowCommentType.value = 'file';
|
||||
const flowInstId = typeof row.fileFlowInstId === 'string' ? parseInt(row.fileFlowInstId, 10) : row.fileFlowInstId;
|
||||
currFlowJob.value = { id: row.id, flowInstId: Number.isNaN(flowInstId) ? row.fileFlowInstId : flowInstId };
|
||||
showFlowComment.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开查看对话框
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
formDialogRef.value?.openDialog('view', row);
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开编辑对话框
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row);
|
||||
};
|
||||
|
||||
/**
|
||||
* 履约验收
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleAccept = (row: any) => {
|
||||
acceptModalRef.value?.open(row);
|
||||
};
|
||||
|
||||
/** 打开实施采购(仅暂存状态可点;通过 iframe 嵌入 implement.vue) */
|
||||
const handleImplement = (row: any) => {
|
||||
implementFormRef.value?.openDialog(row);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -381,6 +494,142 @@ const handleDelete = async (row: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 暂存状态下提交采购申请(启动流程) */
|
||||
const handleSubmit = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要提交该采购申请并启动流程吗?');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await submitObj({ id: row.id });
|
||||
useMessage().success('提交成功');
|
||||
getDataList();
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '提交失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 操作栏「更多」菜单项配置 */
|
||||
const getActionMenuItems = (row: any) => {
|
||||
const isTemp = row?.status === '-1';
|
||||
return [
|
||||
{
|
||||
command: 'edit',
|
||||
label: '编辑',
|
||||
icon: Edit,
|
||||
visible: () => isTemp,
|
||||
},
|
||||
{
|
||||
command: 'submit',
|
||||
label: '提交',
|
||||
icon: Upload,
|
||||
visible: () => isTemp,
|
||||
},
|
||||
{
|
||||
command: 'delete',
|
||||
label: '删除',
|
||||
icon: Delete,
|
||||
visible: () => isTemp,
|
||||
},
|
||||
{
|
||||
command: 'accept',
|
||||
label: '履约验收',
|
||||
icon: DocumentChecked,
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'implement',
|
||||
label: '实施采购',
|
||||
icon: Upload
|
||||
},
|
||||
{
|
||||
command: 'archive',
|
||||
label: '文件归档',
|
||||
icon: FolderOpened,
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'downloadApply',
|
||||
label: '下载审批表',
|
||||
icon: Download,
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'downloadFileApply',
|
||||
label: '下载文件审批表',
|
||||
icon: Download,
|
||||
visible: () => true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/** 处理更多操作下拉菜单命令 */
|
||||
const handleMoreCommand = (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'edit':
|
||||
handleEdit(row);
|
||||
break;
|
||||
case 'submit':
|
||||
handleSubmit(row);
|
||||
break;
|
||||
case 'delete':
|
||||
handleDelete(row);
|
||||
break;
|
||||
case 'accept':
|
||||
handleAccept(row);
|
||||
break;
|
||||
case 'implement':
|
||||
handleImplement(row);
|
||||
break;
|
||||
case 'archive':
|
||||
handleArchive(row);
|
||||
break;
|
||||
case 'downloadApply':
|
||||
handleDownloadApply(row);
|
||||
break;
|
||||
case 'downloadFileApply':
|
||||
handleDownloadFileApply(row);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/** 下载审批表 */
|
||||
const handleDownloadApply = (row: any) => {
|
||||
const id = row?.id ?? row?.purchaseId;
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID');
|
||||
return;
|
||||
}
|
||||
const url = getApplyTemplateDownloadUrl(id);
|
||||
const fileName = `审批表_${row?.purchaseNo || id}.docx`;
|
||||
other.downBlobFile(url, {}, fileName);
|
||||
};
|
||||
|
||||
/** 下载文件审批表 */
|
||||
const handleDownloadFileApply = (row: any) => {
|
||||
const id = row?.id ?? row?.purchaseId;
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID');
|
||||
return;
|
||||
}
|
||||
const url = getFileApplyTemplateDownloadUrl(id);
|
||||
const fileName = `文件审批表_${row?.purchaseNo || id}.docx`;
|
||||
other.downBlobFile(url, {}, fileName);
|
||||
};
|
||||
|
||||
/** 文件归档:按文件类型打包下载该申请单下所有附件 */
|
||||
const handleArchive = (row: any) => {
|
||||
const id = row?.id ?? row?.purchaseId;
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID');
|
||||
return;
|
||||
}
|
||||
const url = getArchiveDownloadUrl(id);
|
||||
const fileName = `归档_${row?.purchaseNo || id}.zip`;
|
||||
other.downBlobFile(url, {}, fileName);
|
||||
};
|
||||
|
||||
// 获取字典数据和品目树数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
@@ -526,53 +775,19 @@ onMounted(() => {
|
||||
loadDictData();
|
||||
});
|
||||
|
||||
// 组件卸载时清理事件监听器
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('message', handleIframeMessage)
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
|
||||
.iframe-dialog-content {
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
min-height: 500px;
|
||||
max-height: calc(100vh - 200px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.add-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
.op-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.iframe-dialog) {
|
||||
.el-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
margin-top: 5vh !important;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
flex-shrink: 0;
|
||||
padding: 20px 20px 10px;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: calc(100vh - 200px);
|
||||
}
|
||||
.status-tag-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -161,14 +161,14 @@
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
<footer class="el-dialog__footer" style="text-align: center;">
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="printForm">{{
|
||||
t('jfI18n.print')
|
||||
}}
|
||||
</el-button>
|
||||
</span>
|
||||
</footer>
|
||||
<!-- <footer class="el-dialog__footer" style="text-align: center;">-->
|
||||
<!-- <span class="dialog-footer">-->
|
||||
<!-- <el-button type="primary" @click="printForm">{{-->
|
||||
<!-- t('jfI18n.print')-->
|
||||
<!-- }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </span>-->
|
||||
<!-- </footer>-->
|
||||
|
||||
<el-drawer
|
||||
v-if="data.showHiJob"
|
||||
|
||||
121
src/views/professional/common/import-teacher-other-info.vue
Normal file
121
src/views/professional/common/import-teacher-other-info.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="600" append-to-body>
|
||||
<!-- <el-alert-->
|
||||
<!-- type="warning"-->
|
||||
<!-- :closable="false"-->
|
||||
<!-- show-icon-->
|
||||
<!-- style="margin-bottom: 20px;">-->
|
||||
<!-- <template #title>-->
|
||||
<!-- <span>下载模板</span>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-alert>-->
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px">
|
||||
<el-button type="success" :icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||
</div>
|
||||
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
class="upload-demo"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
:accept="'.xls,.xlsx'"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleAvatarError"
|
||||
:limit="1"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">只能上传 .xls 或 .xlsx 格式的 Excel 文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import { Download, UploadFilled } from '@element-plus/icons-vue';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { downBlobFile } from '/@/utils/other';
|
||||
|
||||
const title = ref('');
|
||||
// 响应式数据
|
||||
const visible = ref(false);
|
||||
|
||||
// 计算属性
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
Authorization: 'Bearer ' + Session.getToken(),
|
||||
TENANT_ID: Session.getTenant()
|
||||
};
|
||||
});
|
||||
|
||||
const uploadUrl = ref('')
|
||||
const currentType = ref('')
|
||||
const uploadRef = ref<{ clearFiles?: () => void }>()
|
||||
const titleMap: Record<string, string> = {
|
||||
titleRelation: '职称信息导入',
|
||||
quaRelation: '职业资格信息导入',
|
||||
cerRelation: '教师资格证信息导入',
|
||||
eduDegree: '学历学位信息导入',
|
||||
partyChange: '党组织变动信息导入',
|
||||
honor: '综合表彰信息导入'
|
||||
}
|
||||
// 方法
|
||||
const init = (type: any) => {
|
||||
currentType.value = type
|
||||
uploadUrl.value = '/professional/file/importTeacherOtherInfo?type=' + type
|
||||
title.value = titleMap[type] || '信息导入'
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
uploadRef.value?.clearFiles()
|
||||
})
|
||||
}
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'refreshData'): void
|
||||
}>()
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
visible.value = false;
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '导入成功',
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
emit('refreshData')
|
||||
};
|
||||
|
||||
const handleAvatarError = (err: any) => {
|
||||
const result = JSON.parse(err.message);
|
||||
if (result.code == '1') {
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: result.msg,
|
||||
duration: 30000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadTemplate = () => {
|
||||
downBlobFile('/professional/file/exportTeacherInfoTemplate', { type: currentType.value || 'titleRelation' }, title.value+'模板.xlsx')
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
init,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.upload-demo {
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -37,6 +37,20 @@
|
||||
</template>
|
||||
</search-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row>
|
||||
<div class="mb15" style="width: 100%;">
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
@@ -77,6 +91,9 @@
|
||||
@current-change="currentChangeHandle"
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
|
||||
<!-- 子组件 -->
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -88,6 +105,7 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList } from '/@/api/professional/professionaluser/professionalpartychange'
|
||||
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
@@ -100,6 +118,10 @@ const search = reactive({
|
||||
realName: ''
|
||||
})
|
||||
|
||||
// 导入加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: async (params: any) => {
|
||||
@@ -130,6 +152,11 @@ const resetQuery = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('partyChange')
|
||||
}
|
||||
|
||||
// 表格数据由 useTable(createdIsNeed 默认 true)在挂载时自动请求
|
||||
</script>
|
||||
|
||||
|
||||
@@ -73,6 +73,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading"
|
||||
>导出信息</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息</el-button>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<RightToolbar
|
||||
@@ -207,6 +215,7 @@
|
||||
<!-- 子组件 -->
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -220,11 +229,10 @@ import { useDict } from '/@/hooks/dict'
|
||||
import {
|
||||
fetchList,
|
||||
examObj,
|
||||
delObj,
|
||||
exportExcel
|
||||
} from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
delObj} from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
import { getLevelList } from '/@/api/professional/rsbase/professionalqualificationconfig'
|
||||
import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype'
|
||||
import { makeExportTeacherInfoByTypeTask } from '/@/api/professional/professionalfile';
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { Medal } from '@element-plus/icons-vue'
|
||||
@@ -234,6 +242,7 @@ const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/i
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 审核状态选项
|
||||
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
|
||||
@@ -265,8 +274,9 @@ const search = reactive({
|
||||
// 材料预览
|
||||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
// 导出/导入加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 资格等级和工种列表
|
||||
const qualificationLevelList = ref<any[]>([])
|
||||
@@ -380,27 +390,15 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response: any = await exportExcel(search)
|
||||
|
||||
const blob = new Blob([response as BlobPart])
|
||||
const fileName = '职业资格信息.xls'
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20003' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
};
|
||||
|
||||
// 获取资格等级名称
|
||||
const getQualificationLevelName = (id: string | number) => {
|
||||
@@ -414,6 +412,11 @@ const getWorkTypeName = (id: string | number) => {
|
||||
return item ? item.workName : '-'
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('quaRelation')
|
||||
}
|
||||
|
||||
// 加载字典数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
|
||||
@@ -84,10 +84,10 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="证书编码" prop="certificateNumber">
|
||||
<el-form-item label="证书编号" prop="certificateNumber">
|
||||
<el-input
|
||||
v-model="dataForm.certificateNumber"
|
||||
placeholder="请输入证书编码(仅支持英文和数字)"
|
||||
placeholder="请输入证书编号(仅支持英文和数字)"
|
||||
clearable
|
||||
show-word-limit
|
||||
maxlength="100"
|
||||
@@ -215,8 +215,8 @@ const formRules = computed(() => {
|
||||
{ required: true, message: '请输入所学专业', trigger: 'blur' }
|
||||
],
|
||||
certificateNumber: [
|
||||
{ required: true, message: '请输入证书编码', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编码只能包含英文和数字', trigger: 'blur' }
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading">导出信息
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog">导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -218,6 +226,7 @@
|
||||
<!-- 子组件 -->
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -232,19 +241,19 @@ import { useDict } from '/@/hooks/dict'
|
||||
import {
|
||||
fetchList,
|
||||
examObj,
|
||||
delObj,
|
||||
exportExcel
|
||||
} from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
delObj} from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
|
||||
import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig'
|
||||
import { getAllTypeList } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig'
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import {makeExportTeacherInfoByTypeTask} from "/@/api/professional/professionalfile";
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 审核状态选项
|
||||
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
|
||||
@@ -279,6 +288,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 学位、学历和教育类型列表
|
||||
const degreeList = ref<any[]>([])
|
||||
@@ -401,26 +411,14 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response: any = await exportExcel(search)
|
||||
|
||||
const blob = new Blob([response as BlobPart])
|
||||
const fileName = '学历学位信息.xls'
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20005' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
}
|
||||
|
||||
// 获取学位名称
|
||||
@@ -442,6 +440,11 @@ const getEducationTypeName = (id: string | number | undefined) => {
|
||||
return item ? item.name : '-'
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('eduDegree')
|
||||
}
|
||||
|
||||
// 加载字典数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
|
||||
@@ -69,6 +69,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading">导出信息
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog">导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -188,6 +196,7 @@
|
||||
<!-- 子组件 -->
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -203,9 +212,10 @@ import { getTeacherCertificateList } from '/@/api/professional/rsbase/profession
|
||||
import {
|
||||
fetchList,
|
||||
examObj,
|
||||
delObj,
|
||||
exportExcel
|
||||
delObj
|
||||
} from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
|
||||
import { makeExportTeacherInfoByTypeTask } from '/@/api/professional/professionalfile';
|
||||
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
@@ -213,6 +223,7 @@ const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/i
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 审核状态选项(独立定义,防止其他页面修改时被波及)
|
||||
import type { StateOption } from '/@/components/AuditState/index.vue'
|
||||
@@ -247,6 +258,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 证书列表
|
||||
const certificateList = ref<any[]>([])
|
||||
@@ -357,27 +369,15 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response: any = await exportExcel(search)
|
||||
|
||||
const blob = new Blob([response as BlobPart])
|
||||
const fileName = '教师资格证信息.xls'
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20004' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
};
|
||||
|
||||
// 获取证书名称
|
||||
const getCertificateName = (id: string | number) => {
|
||||
@@ -385,6 +385,11 @@ const getCertificateName = (id: string | number) => {
|
||||
return item ? item.cretificateName : '-'
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('cerRelation')
|
||||
}
|
||||
|
||||
// 加载字典数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
|
||||
@@ -68,6 +68,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading">导出信息
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog">导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -216,6 +224,7 @@
|
||||
<!-- 子组件 -->
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -234,6 +243,7 @@ import {
|
||||
} from '/@/api/professional/professionaluser/professionalteacherhonor'
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS, getStatusConfig } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import {makeExportTeacherInfoByTypeTask} from "/@/api/professional/professionalfile";
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
|
||||
const ClickableTag = defineAsyncComponent(() => import('/@/components/ClickableTag/index.vue'))
|
||||
@@ -241,6 +251,7 @@ const DetailPopover = defineAsyncComponent(() => import('/@/components/DetailPop
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 消息提示 hooks
|
||||
const message = useMessage()
|
||||
@@ -281,6 +292,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
@@ -382,29 +394,19 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response = await fetchList({
|
||||
current: 1,
|
||||
size: 999999,
|
||||
...search
|
||||
})
|
||||
const data = response.data.records || []
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20006' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
}
|
||||
|
||||
const tHeader = ['工号', '姓名', '荣誉', '表彰单位', '年份']
|
||||
const filterVal = ['teacherNo', 'teacherName', 'honor', 'honorCompany', 'year']
|
||||
|
||||
// 动态导入导出工具
|
||||
const { export_json_to_excel } = await import('/@/excel/Export2Excel.js')
|
||||
const exportData = data.map((v: any) => filterVal.map((j: string) => v[j]))
|
||||
export_json_to_excel(tHeader, exportData, '综合表彰.xls')
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('honor')
|
||||
}
|
||||
|
||||
// 表格数据由 useTable(createdIsNeed 默认 true)在挂载时自动请求
|
||||
|
||||
@@ -6,23 +6,25 @@
|
||||
show-icon
|
||||
style="margin-bottom: 20px;">
|
||||
<template #title>
|
||||
<span>导入前请先下载字典文件,部分字段需严格按照字典值填写</span>
|
||||
<span> 可先导出教职工信息,按照导出信息的模板填入职工数据,再执行导入</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<a href="excel/dictlist.xlsx" rel="external nofollow" download="职工信息字典下载" style="text-decoration: none;">
|
||||
<el-button type="success" :icon="Download">下载字典文件</el-button>
|
||||
</a>
|
||||
</div>
|
||||
<!-- <div style="text-align: center; margin-bottom: 20px;">-->
|
||||
<!-- <a href="excel/dictlist.xlsx" rel="external nofollow" download="职工信息字典下载" style="text-decoration: none;">-->
|
||||
<!-- <el-button type="success" :icon="Download">下载字典文件</el-button>-->
|
||||
<!-- </a>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
class="upload-demo"
|
||||
action="/professional/file/makeImportTeacherInfoSimpleTask"
|
||||
:headers="headers"
|
||||
:accept="'.xls,.xlsx'"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleAvatarError"
|
||||
:limit="1"
|
||||
drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
@@ -38,7 +40,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { Download, UploadFilled } from '@element-plus/icons-vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
@@ -53,9 +55,14 @@
|
||||
}
|
||||
})
|
||||
|
||||
const uploadRef = ref<{ clearFiles?: () => void }>()
|
||||
|
||||
// 方法
|
||||
const init = () => {
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
uploadRef.value?.clearFiles?.()
|
||||
})
|
||||
}
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
|
||||
@@ -1024,7 +1024,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="graduateSchool" label="毕业学校" min-width="180" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="major" label="所学专业" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="certificateNumber" label="证书编码" min-width="120" align="center" />
|
||||
<el-table-column prop="certificateNumber" label="证书编号" min-width="120" align="center" />
|
||||
<el-table-column label="学历证书" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading"
|
||||
:disabled="operType === 'view'"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.projectName">
|
||||
<el-form-item label="采购项目名称" prop="projectName">
|
||||
<el-input v-model="form.projectName" placeholder="请输入采购项目名称" :disabled="disabledFields.projectName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.projectType">
|
||||
<el-form-item label="项目类别" prop="projectType">
|
||||
<el-select v-model="form.projectType" placeholder="请选择" :disabled="disabledFields.projectType" clearable style="width: 100%">
|
||||
<el-option label="货物" value="A" />
|
||||
<el-option label="工程" value="B" />
|
||||
<el-option label="服务" value="C" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20" v-if="!hiddenFields.projectContent">
|
||||
<el-form-item label="采购内容" prop="projectContent">
|
||||
<el-input v-model="form.projectContent" type="textarea" :rows="3" placeholder="请输入采购内容" :disabled="disabledFields.projectContent" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.applyDate">
|
||||
<el-form-item label="填报日期" prop="applyDate">
|
||||
<el-date-picker
|
||||
v-model="form.applyDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
:disabled="disabledFields.applyDate"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.fundSource">
|
||||
<el-form-item label="资金来源" prop="fundSource">
|
||||
<el-input v-model="form.fundSource" placeholder="资金来源" :disabled="disabledFields.fundSource" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.budget">
|
||||
<el-form-item label="预算金额(元)" prop="budget">
|
||||
<el-input-number v-model="form.budget" :min="0" :precision="2" :disabled="disabledFields.budget" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.isCentralized">
|
||||
<el-form-item label="是否集采" prop="isCentralized">
|
||||
<el-select v-model="form.isCentralized" placeholder="请选择" :disabled="disabledFields.isCentralized" clearable style="width: 100%">
|
||||
<el-option label="否" value="0" />
|
||||
<el-option label="是" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.isSpecial">
|
||||
<el-form-item label="是否特殊情况" prop="isSpecial">
|
||||
<el-select v-model="form.isSpecial" placeholder="请选择" :disabled="disabledFields.isSpecial" clearable style="width: 100%">
|
||||
<el-option label="否" value="0" />
|
||||
<el-option label="紧急" value="1" />
|
||||
<el-option label="单一" value="2" />
|
||||
<el-option label="进口" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.purchaseMode">
|
||||
<el-form-item label="采购形式" prop="purchaseMode">
|
||||
<el-input v-model="form.purchaseMode" placeholder="采购形式" :disabled="disabledFields.purchaseMode" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.purchaseType">
|
||||
<el-form-item label="采购方式" prop="purchaseType">
|
||||
<el-input v-model="form.purchaseType" placeholder="采购方式" :disabled="disabledFields.purchaseType" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20" v-if="!hiddenFields.remark">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注" :disabled="disabledFields.remark" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template v-if="data.submitBtn">
|
||||
<footer class="el-dialog__footer">
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm" :disabled="loading">提交</el-button>
|
||||
</span>
|
||||
</footer>
|
||||
</template>
|
||||
<template v-else>
|
||||
<footer class="el-dialog__footer">
|
||||
<span class="dialog-footer" />
|
||||
</footer>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchaseApplyFlow">
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import * as purchaseApply from '/@/api/order/purchase-apply'
|
||||
import * as orderVue from '/@/api/order/order-key-vue'
|
||||
import { handleCustomFormPerm, handleFormPrint } from '/@/flow/utils/form-perm'
|
||||
import { deepClone } from '/@/utils/other'
|
||||
import { initCustomFormMethods } from '/@/views/order/index'
|
||||
|
||||
const emits = defineEmits(['handleJob'])
|
||||
|
||||
const dataFormRef = ref()
|
||||
const loading = ref(false)
|
||||
const operType = ref<'view' | 'flow'>('flow')
|
||||
|
||||
const props = defineProps({
|
||||
currJob: { type: Object, default: null },
|
||||
currElTab: { type: Object, default: () => ({}) }
|
||||
})
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
id: null,
|
||||
code: '',
|
||||
flowKey: 'PURCHASE_APPLY',
|
||||
flowInstId: null,
|
||||
purchaseNo: '',
|
||||
projectName: '',
|
||||
projectType: '',
|
||||
projectContent: '',
|
||||
applyDate: '',
|
||||
fundSource: '',
|
||||
budget: null,
|
||||
isCentralized: '',
|
||||
isSpecial: '',
|
||||
purchaseMode: '',
|
||||
purchaseSchool: '',
|
||||
purchaseType: '',
|
||||
categoryCode: '',
|
||||
fileIds: [],
|
||||
remark: '',
|
||||
runJobId: '',
|
||||
flowVarUser: null
|
||||
})
|
||||
|
||||
const dataRules = ref({
|
||||
projectContent: [{ required: true, message: '请输入采购内容', trigger: 'blur' }],
|
||||
budget: [{ required: true, message: '请输入预算金额', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const fieldsPerm = {
|
||||
projectName: false,
|
||||
projectType: false,
|
||||
projectContent: false,
|
||||
applyDate: false,
|
||||
fundSource: false,
|
||||
budget: false,
|
||||
isCentralized: false,
|
||||
isSpecial: false,
|
||||
purchaseMode: false,
|
||||
purchaseType: false,
|
||||
remark: false
|
||||
}
|
||||
const hiddenFields = reactive({ ...fieldsPerm })
|
||||
const disabledFields = reactive(deepClone(fieldsPerm))
|
||||
|
||||
const data = reactive({
|
||||
submitBtn: true,
|
||||
elTab: null as any
|
||||
})
|
||||
|
||||
const methods = initCustomFormMethods(data, disabledFields, operType)
|
||||
|
||||
function initJobData() {
|
||||
if (props.currJob?.orderId) handleGetObj(props.currJob.orderId)
|
||||
}
|
||||
|
||||
function handleGetObj(id: string | number) {
|
||||
purchaseApply.getObj(id).then(async (resp: any) => {
|
||||
const formData = resp?.data ?? {}
|
||||
Object.assign(form, formData)
|
||||
form.runJobId = props.currJob?.id ?? ''
|
||||
await initFormPermPrint()
|
||||
})
|
||||
}
|
||||
|
||||
async function initFormPermPrint() {
|
||||
const elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab?.id)
|
||||
const res = await handleCustomFormPerm(props, hiddenFields, disabledFields, elTab)
|
||||
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab?.id, submitForm, res?.callback)
|
||||
await handleFormPrint(form, elTab?.type, elTab?.id, '1')
|
||||
data.elTab = elTab
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
try {
|
||||
loading.value = true
|
||||
await purchaseApply.putObj(form)
|
||||
orderVue.currElTabIsSave(props.currJob, props.currElTab?.id, true, emits)
|
||||
useMessage().success(form.id ? '修改成功' : '保存成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg ?? '操作失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currJob?.id,
|
||||
() => { initJobData() }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
initJobData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-dialog__footer {
|
||||
text-align: center;
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,6 +11,26 @@
|
||||
: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"
|
||||
@@ -21,26 +41,7 @@
|
||||
<el-form-item label="部门名称" prop="deptName">
|
||||
<el-input
|
||||
v-model="form.deptName"
|
||||
placeholder="请选择部门后自动填充"
|
||||
readonly />
|
||||
</el-form-item>
|
||||
<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="请选择用户后自动填充"
|
||||
placeholder="选择部门后自动填充"
|
||||
readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
@@ -74,7 +75,7 @@ const emit = defineEmits(['refresh']);
|
||||
const dataFormRef = ref();
|
||||
const deptList = ref<any[]>([]);
|
||||
const userList = ref<any[]>([]);
|
||||
const dataForm = reactive({
|
||||
const form = reactive({
|
||||
id: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
@@ -87,17 +88,17 @@ const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataRules = ref({
|
||||
userId: [
|
||||
{ required: true, message: '请选取用户(分管负责人)', trigger: 'change' }
|
||||
],
|
||||
deptId: [
|
||||
{ required: true, message: '请选择部门', trigger: 'change' }
|
||||
],
|
||||
userId: [
|
||||
{ required: true, message: '请选择分管负责人', trigger: 'change' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '姓名不能为空', trigger: 'blur' }
|
||||
{ required: true, message: '请先选取用户', trigger: 'blur' }
|
||||
],
|
||||
username: [
|
||||
{ required: true, message: '用户工号不能为空', trigger: 'blur' }
|
||||
{ required: true, message: '请先选取用户', trigger: 'blur' }
|
||||
],
|
||||
});
|
||||
|
||||
@@ -105,38 +106,38 @@ const dataRules = ref({
|
||||
const handleDeptChange = (list: any[]) => {
|
||||
if (list && list.length > 0) {
|
||||
const dept = list[0];
|
||||
dataForm.deptId = dept.deptId || dept.id || '';
|
||||
dataForm.deptName = dept.name || dept.deptName || '';
|
||||
form.deptId = dept.deptId || dept.id || '';
|
||||
form.deptName = dept.name || dept.deptName || '';
|
||||
} else {
|
||||
dataForm.deptId = '';
|
||||
dataForm.deptName = '';
|
||||
form.deptId = '';
|
||||
form.deptName = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 处理用户选择变化
|
||||
// 处理用户选择变化(选取用户后自动带出姓名、工号)
|
||||
const handleUserChange = (list: any[]) => {
|
||||
if (list && list.length > 0) {
|
||||
const user = list[0];
|
||||
dataForm.userId = user.userId || user.id || '';
|
||||
dataForm.username = user.username || user.userName || '';
|
||||
dataForm.name = user.name || user.realName || '';
|
||||
form.userId = user.userId || user.id || '';
|
||||
form.username = user.username || user.userName || '';
|
||||
form.name = user.name || user.realName || '';
|
||||
} else {
|
||||
dataForm.userId = '';
|
||||
dataForm.username = '';
|
||||
dataForm.name = '';
|
||||
form.userId = '';
|
||||
form.username = '';
|
||||
form.name = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (id?: string) => {
|
||||
visible.value = true;
|
||||
dataForm.id = '';
|
||||
dataForm.deptId = '';
|
||||
dataForm.deptName = '';
|
||||
dataForm.userId = '';
|
||||
dataForm.username = '';
|
||||
dataForm.name = '';
|
||||
dataForm.remark = '';
|
||||
form.id = '';
|
||||
form.deptId = '';
|
||||
form.deptName = '';
|
||||
form.userId = '';
|
||||
form.username = '';
|
||||
form.name = '';
|
||||
form.remark = '';
|
||||
deptList.value = [];
|
||||
userList.value = [];
|
||||
|
||||
@@ -148,7 +149,7 @@ const openDialog = async (id?: string) => {
|
||||
getObj({ id }).then((res: any) => {
|
||||
if (res.data && res.data.length > 0) {
|
||||
const data = res.data[0];
|
||||
Object.assign(dataForm, {
|
||||
Object.assign(form, {
|
||||
id: data.id || '',
|
||||
deptId: data.deptId || '',
|
||||
deptName: data.deptName || '',
|
||||
@@ -201,17 +202,17 @@ const onSubmit = async () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dataForm.id) {
|
||||
await putObj(dataForm);
|
||||
if (form.id) {
|
||||
await putObj(form);
|
||||
useMessage().success('编辑成功');
|
||||
} else {
|
||||
await addObj(dataForm);
|
||||
await addObj(form);
|
||||
useMessage().success('新增成功');
|
||||
}
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
|
||||
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -56,15 +56,15 @@
|
||||
v-auth="'purchase_purchasingBusinessDept_add'">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
type="primary"
|
||||
class="ml10"
|
||||
@click="excelUploadRef.show()"
|
||||
v-auth="'purchase_purchasingBusinessDept_add'">
|
||||
导入
|
||||
</el-button>
|
||||
<!-- <el-button -->
|
||||
<!-- plain -->
|
||||
<!-- icon="UploadFilled" -->
|
||||
<!-- type="primary" -->
|
||||
<!-- class="ml10" -->
|
||||
<!-- @click="excelUploadRef.show()" -->
|
||||
<!-- v-auth="'purchase_purchasingBusinessDept_add'">-->
|
||||
<!-- 导入-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
@@ -134,14 +134,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
v-auth="'purchase_purchasingBusinessDept_edit'"
|
||||
@click="formDialogRef.openDialog(scope.row.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<!-- <el-button -->
|
||||
<!-- icon="Edit" -->
|
||||
<!-- link -->
|
||||
<!-- type="primary" -->
|
||||
<!-- v-auth="'purchase_purchasingBusinessDept_edit'"-->
|
||||
<!-- @click="formDialogRef.openDialog(scope.row.id)">-->
|
||||
<!-- 编辑-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
|
||||
@@ -93,13 +93,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog('edit', scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<!-- <el-button -->
|
||||
<!-- icon="Edit" -->
|
||||
<!-- link -->
|
||||
<!-- type="primary" -->
|
||||
<!-- @click="formDialogRef.openDialog('edit', scope.row)">-->
|
||||
<!-- 编辑-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
|
||||
120
src/views/recruit/common/import-recruit-info.vue
Normal file
120
src/views/recruit/common/import-recruit-info.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="600" append-to-body>
|
||||
<div style="text-align: center; margin-bottom: 20px" v-if="currentType!='R10003'">
|
||||
<el-button type="success" :icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="currentType=='R10003'"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px;">
|
||||
<template #title>
|
||||
<span> 请从中招平台导出数据后导入</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
class="upload-demo"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
:accept="'.xls,.xlsx'"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleAvatarError"
|
||||
:limit="1"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">只能上传 .xls 或 .xlsx 格式的 Excel 文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import { Download, UploadFilled } from '@element-plus/icons-vue';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { downBlobFile } from '/@/utils/other';
|
||||
|
||||
const title = ref('');
|
||||
// 响应式数据
|
||||
const visible = ref(false);
|
||||
|
||||
// 计算属性
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
Authorization: 'Bearer ' + Session.getToken(),
|
||||
TENANT_ID: Session.getTenant()
|
||||
};
|
||||
});
|
||||
|
||||
const uploadUrl = ref('')
|
||||
const currentType = ref('')
|
||||
const uploadRef = ref<{ clearFiles?: () => void }>()
|
||||
const titleMap: Record<string, string> = {
|
||||
R10001: '计划专业导入',
|
||||
R10002: '地区分数导入',
|
||||
R10003: '中招平台数据导入'
|
||||
}
|
||||
// 方法
|
||||
const init = (type: any) => {
|
||||
currentType.value = type
|
||||
uploadUrl.value = '/api/recruit/file/importRecruitInfo?type=' + type
|
||||
title.value = titleMap[type] || '信息导入'
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
uploadRef.value?.clearFiles()
|
||||
})
|
||||
}
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'refreshDataList'): void
|
||||
}>()
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
visible.value = false;
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '导入成功',
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
emit('refreshDataList')
|
||||
};
|
||||
|
||||
const handleAvatarError = (err: any) => {
|
||||
const result = JSON.parse(err.message);
|
||||
if (result.code == '1') {
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: result.msg,
|
||||
duration: 30000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadTemplate = () => {
|
||||
downBlobFile('/recruit/file/exportRecruitTemplate', { type: currentType.value || 'planMajor' }, title.value+'模板.xlsx')
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.upload-demo {
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,303 +16,279 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-form :model="queryForm" inline ref="searchFormRef">
|
||||
<el-form-item label="招生计划" prop="groupId">
|
||||
<el-select v-model="queryForm.groupId" filterable clearable placeholder="请选择招生计划">
|
||||
<el-option
|
||||
v-for="item in planList"
|
||||
:key="item.id"
|
||||
:label="item.groupName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select v-model="queryForm.deptCode" filterable clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="专业序号" prop="majorCode">
|
||||
<el-input v-model="queryForm.majorCode" placeholder="专业序号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryForm.majorName" placeholder="专业名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学制" prop="learnYear">
|
||||
<el-input v-model="queryForm.learnYear" placeholder="学制" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button plain icon="Refresh" class="ml10" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-form :model="queryForm" inline ref="searchFormRef">
|
||||
<el-form-item label="招生计划" prop="groupId">
|
||||
<el-select v-model="queryForm.groupId" filterable clearable placeholder="请选择招生计划">
|
||||
<el-option v-for="item in planList" :key="item.id" :label="item.groupName" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select v-model="queryForm.deptCode" filterable clearable placeholder="请选择">
|
||||
<el-option v-for="item in deptList" :key="item.deptCode" :label="item.deptName" :value="item.deptCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="专业序号" prop="majorCode">
|
||||
<el-input v-model="queryForm.majorCode" placeholder="专业序号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryForm.majorName" placeholder="专业名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学制" prop="learnYear">
|
||||
<el-input v-model="queryForm.learnYear" placeholder="学制" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button plain icon="Refresh" class="ml10" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="mb15">
|
||||
<el-button
|
||||
v-if="hasAuth('recruit_recruitplanmajor_add')"
|
||||
type="primary"
|
||||
icon="FolderAdd"
|
||||
@click="addOrUpdateHandle"
|
||||
>
|
||||
新 增
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="mb15">
|
||||
<el-button v-if="hasAuth('recruit_recruitplanmajor_add')" type="primary" icon="FolderAdd" @click="addOrUpdateHandle"> 新 增 </el-button>
|
||||
|
||||
<!-- 表格 -->
|
||||
<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 type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="groupId" label="招生计划名称" align="center" min-width="140" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getPlanName(scope.row.groupId) }}
|
||||
</template>
|
||||
</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="deptCode" label="学院" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getDeptName(scope.row.deptCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="learnYear" label="学制" align="center" width="80" show-overflow-tooltip />
|
||||
<el-table-column prop="majorLevel" label="层次" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="isOrder" label="订单班|中德班|联院班" align="center" width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getYesNoLabel(scope.row.isOrder) }}|{{ getYesNoLabel(scope.row.isZd) }}|{{ getYesNoLabel(scope.row.isUnion) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-button
|
||||
v-auth="'recruit_major_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table-column prop="sm" label="色盲不可录" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.sm"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
@change="changeSm(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stuworkMajorCode" label="正式专业代码" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getMajorCodeName(scope.row.stuworkMajorCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="cityPlanId" label="市平台招生计划" align="center" show-overflow-tooltip>
|
||||
<!-- 表格 -->
|
||||
<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 type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="groupId" label="招生计划名称" align="center" min-width="140" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getPlanName(scope.row.groupId) }}
|
||||
</template>
|
||||
</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="deptCode" label="学院" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getDeptName(scope.row.deptCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="learnYear" label="学制" align="center" width="80" show-overflow-tooltip />
|
||||
<el-table-column prop="majorLevel" label="层次" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="isOrder" label="订单班|中德班|联院班" align="center" width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getYesNoLabel(scope.row.isOrder) }}|{{ getYesNoLabel(scope.row.isZd) }}|{{ getYesNoLabel(scope.row.isUnion) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="sm" label="色盲不可录" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.sm" active-text="是" inactive-text="否" active-value="1" inactive-value="0" @change="changeSm(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stuworkMajorCode" label="正式专业代码" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getMajorCodeName(scope.row.stuworkMajorCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="cityPlanId" label="市平台招生计划" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.cityPlanName != undefined ? (scope.row.cityPlanName + '|' + scope.row.cityPlanYear) : '' }}
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<!-- <el-table-column prop="sort" label="排序" align="center" show-overflow-tooltip /> -->
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="EditPen"
|
||||
@click="addOrUpdateHandle(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasAuth('recruit_recruitplanmajor_del')"
|
||||
type="danger"
|
||||
link
|
||||
icon="Delete"
|
||||
@click="deleteHandle(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <el-table-column prop="sort" label="排序" align="center" show-overflow-tooltip /> -->
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="EditPen" @click="addOrUpdateHandle(scope.row)"> 修改 </el-button>
|
||||
<el-button v-if="hasAuth('recruit_recruitplanmajor_del')" type="danger" link icon="Delete" @click="deleteHandle(scope.row.id)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-bind="state.pagination"
|
||||
@current-change="currentChangeHandle"
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
<!-- 分页 -->
|
||||
<pagination v-bind="state.pagination" @current-change="currentChangeHandle" @size-change="sizeChangeHandle" />
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<table-form ref="addOrUpdateRef" @refreshDataList="getDataList" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<table-form ref="addOrUpdateRef" @refreshDataList="getDataList" />
|
||||
|
||||
<import-recruit-info ref="ImportRecruitInfoRef" @refreshDataList="getDataList"></import-recruit-info>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="recruitplanmajor">
|
||||
import { ref, reactive, onMounted, nextTick, defineAsyncComponent } from 'vue'
|
||||
import { useAuth } from '/@/hooks/auth'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import { getList } from '/@/api/recruit/recruitstudentplangroup'
|
||||
import { fetchList, delObj, editQuickField } from '/@/api/recruit/recruitstudentplan'
|
||||
import { getDeptList } from '/@/api/basic/basicclass'
|
||||
import { getMajorNameList } from '/@/api/basic/major'
|
||||
import { ref, reactive, onMounted, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { useAuth } from '/@/hooks/auth';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { getList } from '/@/api/recruit/recruitstudentplangroup';
|
||||
import { fetchList, delObj, editQuickField } from '/@/api/recruit/recruitstudentplan';
|
||||
import { getDeptList } from '/@/api/basic/basicclass';
|
||||
import { getMajorNameList } from '/@/api/basic/major';
|
||||
|
||||
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'))
|
||||
const { hasAuth } = useAuth()
|
||||
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'));
|
||||
const ImportRecruitInfo = defineAsyncComponent(() => import('/@/views/recruit/common/import-recruit-info.vue'));
|
||||
const ImportRecruitInfoRef=ref<any>();
|
||||
const { hasAuth } = useAuth();
|
||||
// 消息提示 hooks
|
||||
const message = useMessage()
|
||||
const messageBox = useMessageBox()
|
||||
const message = useMessage();
|
||||
const messageBox = useMessageBox();
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const addOrUpdateRef = ref()
|
||||
const tableRef = ref();
|
||||
const searchFormRef = ref();
|
||||
const addOrUpdateRef = ref();
|
||||
|
||||
// 字典数据
|
||||
const { yes_no_type } = useDict('yes_no_type')
|
||||
const { yes_no_type } = useDict('yes_no_type');
|
||||
|
||||
// 数据
|
||||
const planList = ref<any[]>([])
|
||||
const deptList = ref<any[]>([])
|
||||
const offcialZydmList = ref<any[]>([])
|
||||
const planList = ref<any[]>([]);
|
||||
const deptList = ref<any[]>([]);
|
||||
const offcialZydmList = ref<any[]>([]);
|
||||
|
||||
// 查询表单
|
||||
const queryForm = reactive({
|
||||
groupId: '',
|
||||
deptCode: '',
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
learnYear: ''
|
||||
})
|
||||
groupId: '',
|
||||
deptCode: '',
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
learnYear: '',
|
||||
});
|
||||
|
||||
// 获取计划名称
|
||||
const getPlanName = (groupId: string) => {
|
||||
const item = planList.value.find(item => item.id === groupId)
|
||||
return item ? item.groupName : ''
|
||||
}
|
||||
const item = planList.value.find((item) => item.id === groupId);
|
||||
return item ? item.groupName : '';
|
||||
};
|
||||
|
||||
// 获取学院名称
|
||||
const getDeptName = (deptCode: string) => {
|
||||
const item = deptList.value.find(item => item.deptCode === deptCode)
|
||||
return item ? item.deptName : ''
|
||||
}
|
||||
const item = deptList.value.find((item) => item.deptCode === deptCode);
|
||||
return item ? item.deptName : '';
|
||||
};
|
||||
|
||||
// 获取是/否标签
|
||||
const getYesNoLabel = (value: string) => {
|
||||
const item = yes_no_type.value.find((item: any) => item.value === value)
|
||||
return item ? item.label : ''
|
||||
}
|
||||
const item = yes_no_type.value.find((item: any) => item.value === value);
|
||||
return item ? item.label : '';
|
||||
};
|
||||
|
||||
// 获取专业代码名称
|
||||
const getMajorCodeName = (majorCode: string) => {
|
||||
const item = offcialZydmList.value.find(item => item.majorCode === majorCode)
|
||||
return item ? item.majorCodeAndName : ''
|
||||
}
|
||||
const item = offcialZydmList.value.find((item) => item.majorCode === majorCode);
|
||||
return item ? item.majorCodeAndName : '';
|
||||
};
|
||||
|
||||
// 表格状态
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
createdIsNeed: false
|
||||
})
|
||||
queryForm: queryForm,
|
||||
pageList: async (params: any) => {
|
||||
const response = await fetchList(params);
|
||||
return {
|
||||
data: {
|
||||
records: response.data.records,
|
||||
total: response.data.total,
|
||||
},
|
||||
};
|
||||
},
|
||||
createdIsNeed: false,
|
||||
});
|
||||
|
||||
// 使用 table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
|
||||
|
||||
// 初始化
|
||||
const init = async () => {
|
||||
try {
|
||||
// 查询二级学院信息
|
||||
const deptData = await getDeptList()
|
||||
deptList.value = deptData.data || []
|
||||
try {
|
||||
// 查询二级学院信息
|
||||
const deptData = await getDeptList();
|
||||
deptList.value = deptData.data || [];
|
||||
|
||||
// 获取招生计划列表
|
||||
const planData = await getList()
|
||||
planList.value = planData.data || []
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id
|
||||
}
|
||||
// 获取招生计划列表
|
||||
const planData = await getList();
|
||||
planList.value = planData.data || [];
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id;
|
||||
}
|
||||
|
||||
// 获取专业名称列表
|
||||
const majorData = await getMajorNameList()
|
||||
offcialZydmList.value = majorData.data || []
|
||||
// 获取专业名称列表
|
||||
const majorData = await getMajorNameList();
|
||||
offcialZydmList.value = majorData.data || [];
|
||||
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
|
||||
}
|
||||
}
|
||||
getDataList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 修改开关
|
||||
const changeSm = async (row: any) => {
|
||||
try {
|
||||
let parmas={id:row.id,sm:row.sm}
|
||||
await editQuickField(parmas)
|
||||
message.success('修改成功')
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
let parmas = { id: row.id, sm: row.sm };
|
||||
await editQuickField(parmas);
|
||||
message.success('修改成功');
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 新增 / 修改
|
||||
const addOrUpdateHandle = (row?: any) => {
|
||||
nextTick(() => {
|
||||
addOrUpdateRef.value?.init(row.id || null)
|
||||
})
|
||||
}
|
||||
nextTick(() => {
|
||||
addOrUpdateRef.value?.init(row.id || null);
|
||||
});
|
||||
};
|
||||
|
||||
// 删除
|
||||
const deleteHandle = async (id: string) => {
|
||||
try {
|
||||
await messageBox.confirm('是否确认删除本条数据?请谨慎操作')
|
||||
await delObj(id)
|
||||
message.success('删除成功')
|
||||
getDataList()
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
try {
|
||||
await messageBox.confirm('是否确认删除本条数据?请谨慎操作');
|
||||
await delObj(id);
|
||||
message.success('删除成功');
|
||||
getDataList();
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
queryForm.groupId = ''
|
||||
queryForm.deptCode = ''
|
||||
queryForm.majorCode = ''
|
||||
queryForm.majorName = ''
|
||||
queryForm.learnYear = ''
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id
|
||||
}
|
||||
getDataList()
|
||||
}
|
||||
searchFormRef.value?.resetFields();
|
||||
queryForm.groupId = '';
|
||||
queryForm.deptCode = '';
|
||||
queryForm.majorCode = '';
|
||||
queryForm.majorName = '';
|
||||
queryForm.learnYear = '';
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id;
|
||||
}
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const exportLoading = ref(false);
|
||||
|
||||
const handleImportDialog = () => {
|
||||
ImportRecruitInfoRef.value?.init("R10001");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -29,6 +29,15 @@
|
||||
>
|
||||
新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'recruit_areascore_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
@@ -83,6 +92,9 @@
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<table-form ref="addOrUpdateRef" @refreshDataList="getDataList" />
|
||||
|
||||
<import-recruit-info ref="ImportRecruitInfoRef" @refreshDataList="getDataList"></import-recruit-info>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -94,6 +106,8 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { getList } from '/@/api/recruit/recruitstudentplangroup'
|
||||
import { fetchList, delObj } from '/@/api/recruit/recruitstudentplancorrectscoreconfig'
|
||||
const ImportRecruitInfo = defineAsyncComponent(() => import('/@/views/recruit/common/import-recruit-info.vue'));
|
||||
const ImportRecruitInfoRef=ref<any>();
|
||||
|
||||
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'))
|
||||
const { hasAuth } = useAuth()
|
||||
@@ -183,6 +197,12 @@ const resetQuery = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const exportLoading = ref(false);
|
||||
|
||||
const handleImportDialog = () => {
|
||||
ImportRecruitInfoRef.value?.init("R10002");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
@@ -281,19 +281,28 @@
|
||||
@click="handleAddData">新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasAuth('zipExport')"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="downZip()">招生名单打包导出
|
||||
</el-button>
|
||||
<el-button
|
||||
class="ml10"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport()">名单导出
|
||||
v-auth="'recruit_zzpt_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入中招平台数据
|
||||
</el-button>
|
||||
<!-- <el-button-->
|
||||
<!-- v-if="hasAuth('zipExport')"-->
|
||||
<!-- type="warning"-->
|
||||
<!-- plain-->
|
||||
<!-- icon="Download"-->
|
||||
<!-- @click="downZip()">招生名单打包导出-->
|
||||
<!-- </el-button>-->
|
||||
<!-- <el-button -->
|
||||
<!-- class="ml10"-->
|
||||
<!-- type="warning"-->
|
||||
<!-- plain-->
|
||||
<!-- icon="Download"-->
|
||||
<!-- @click="handleExport()">名单导出-->
|
||||
<!-- </el-button>-->
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -634,7 +643,9 @@
|
||||
<AdmissionNoticeDialog ref="admissionNoticeDialogRef" @refresh="getDataList"></AdmissionNoticeDialog>
|
||||
|
||||
<InterviewForm ref="interviewFormRef" @refresh="getDataList"></InterviewForm>
|
||||
</div>
|
||||
|
||||
<import-recruit-info ref="ImportRecruitInfoRef" @refreshDataList="getDataList"></import-recruit-info>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -681,6 +692,8 @@ const InterviewForm = defineAsyncComponent(() => import('/@/views/recruit/recrui
|
||||
const PayQrcodeDialog = defineAsyncComponent(() => import('./PayQrcodeDialog.vue'))
|
||||
const AdmissionNoticeDialog = defineAsyncComponent(() => import('./AdmissionNoticeDialog.vue'))
|
||||
const ActionDropdown = defineAsyncComponent(() => import('/@/components/tools/action-dropdown.vue'))
|
||||
const ImportRecruitInfo = defineAsyncComponent(() => import('/@/views/recruit/common/import-recruit-info.vue'));
|
||||
const ImportRecruitInfoRef=ref<any>();
|
||||
const { hasAuth } = useAuth()
|
||||
// 消息提示 hooks
|
||||
const message = useMessage()
|
||||
@@ -1131,6 +1144,10 @@ watch(() => dataForm.groupId, () => {
|
||||
}
|
||||
})
|
||||
|
||||
const handleImportDialog = () => {
|
||||
ImportRecruitInfoRef.value?.init("R10003");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
@@ -4,25 +4,56 @@
|
||||
v-model="visible"
|
||||
:close-on-click-modal="false"
|
||||
draggable
|
||||
width="800px">
|
||||
<div v-loading="loading" class="detail-container" v-if="detailData">
|
||||
<el-descriptions :column="2" border>
|
||||
width="960px">
|
||||
<div class="detail-container">
|
||||
<!-- 活动主信息(来自列表行) -->
|
||||
<el-descriptions v-if="mainInfo.activityTheme" :column="2" border class="mb16">
|
||||
<el-descriptions-item label="活动主题" :span="2">
|
||||
{{ detailData.activityTheme || '-' }}
|
||||
{{ mainInfo.activityTheme || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="活动说明" :span="2">
|
||||
{{ detailData.remarks || '-' }}
|
||||
{{ mainInfo.remarks || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="活动兼报数">
|
||||
{{ detailData.maxSub !== undefined && detailData.maxSub !== null ? detailData.maxSub : '-' }}
|
||||
{{ mainInfo.maxSub !== undefined && mainInfo.maxSub !== null ? mainInfo.maxSub : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">
|
||||
{{ parseTime(detailData.startTime, '{y}-{m}-{d}') }}
|
||||
{{ mainInfo.startTime ? parseTime(mainInfo.startTime, '{y}-{m}-{d}') : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">
|
||||
{{ parseTime(detailData.endTime, '{y}-{m}-{d}') }}
|
||||
{{ mainInfo.endTime ? parseTime(mainInfo.endTime, '{y}-{m}-{d}') : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- 子项目列表(接口:getActivityInfoSubList) -->
|
||||
<div class="sub-title">子项目列表</div>
|
||||
<el-table
|
||||
:data="subList"
|
||||
v-loading="loading"
|
||||
border
|
||||
size="small"
|
||||
max-height="400"
|
||||
style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="subTitle" label="子项目名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="deptName" label="学院" width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="classNo" label="班号" width="80" align="center" />
|
||||
<el-table-column prop="classMasterName" label="班主任" width="90" show-overflow-tooltip />
|
||||
<el-table-column prop="startTime" label="开始时间" width="155" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.startTime ? parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="结束时间" width="155" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.endTime ? parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="maxNum" label="人数限制" width="88" align="center" />
|
||||
<el-table-column prop="applyNums" label="已报名" width="78" align="center" />
|
||||
<el-table-column prop="position" label="地点" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="projectDescription" label="项目描述" min-width="180" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-empty v-if="!loading && subList.length === 0" description="暂无子项目" :image-size="80" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -33,47 +64,40 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ActivityInfoDetailDialog">
|
||||
import { ref } from 'vue'
|
||||
import { getDetail } from '/@/api/stuwork/activityinfo'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { getActivityInfoSubList } from '/@/api/stuwork/activityinfosub'
|
||||
import { parseTime } from '/@/utils/formatTime'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
|
||||
// 定义变量内容
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const detailData = ref<any>({})
|
||||
const mainInfo = reactive<Record<string, any>>({})
|
||||
const subList = ref<any[]>([])
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (id: string) => {
|
||||
/**
|
||||
* 打开弹窗:使用接口 getActivityInfoSubList(activityInfoId) 获取详情子项目列表
|
||||
* @param activityInfoId 活动信息ID
|
||||
* @param row 列表行数据,用于展示活动主题等主信息
|
||||
*/
|
||||
const openDialog = async (activityInfoId: string, row?: any) => {
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
detailData.value = {}
|
||||
subList.value = []
|
||||
Object.keys(mainInfo).forEach((k) => delete mainInfo[k])
|
||||
if (row && typeof row === 'object') {
|
||||
Object.assign(mainInfo, row)
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getDetail(id)
|
||||
if (res.data) {
|
||||
// 根据接口文档,返回的数据可能是 { records: [...], total: ... } 格式
|
||||
// 如果是列表格式,取第一条;如果是对象,直接使用
|
||||
if (res.data.records && Array.isArray(res.data.records) && res.data.records.length > 0) {
|
||||
detailData.value = res.data.records[0]
|
||||
} else if (res.data.records && Array.isArray(res.data.records)) {
|
||||
// 列表为空
|
||||
useMessage().warning('未找到详情数据')
|
||||
visible.value = false
|
||||
} else {
|
||||
// 直接是对象
|
||||
detailData.value = res.data
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '获取详情失败')
|
||||
visible.value = false
|
||||
const res = await getActivityInfoSubList(activityInfoId)
|
||||
const data = res.data
|
||||
subList.value = Array.isArray(data) ? data : []
|
||||
} catch (_err) {
|
||||
subList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
@@ -81,7 +105,15 @@ defineExpose({
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-container {
|
||||
padding: 20px 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.mb16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.sub-title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -250,9 +250,9 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 查看详情
|
||||
// 查看详情(接口:getActivityInfoSubList,传入活动ID与行数据用于展示)
|
||||
const handleView = (row: any) => {
|
||||
detailDialogRef.value?.openDialog(row.id)
|
||||
detailDialogRef.value?.openDialog(row.id, row)
|
||||
}
|
||||
|
||||
// 编辑
|
||||
|
||||
@@ -48,6 +48,17 @@
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="默认扣分值" prop="score">
|
||||
<el-input-number
|
||||
v-model="form.score"
|
||||
:min="0"
|
||||
:max="999"
|
||||
:precision="0"
|
||||
placeholder="请输入默认扣分值"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
@@ -86,12 +97,13 @@ const loading = ref(false)
|
||||
const operType = ref('add')
|
||||
const categoryList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
// 提交表单数据(与接口文档一致:edit 含 score 默认扣分值)
|
||||
const form = reactive({
|
||||
id: '',
|
||||
categortyId: '',
|
||||
pointName: '',
|
||||
standard: '',
|
||||
score: undefined as number | undefined,
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
@@ -117,6 +129,7 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
form.categortyId = ''
|
||||
form.pointName = ''
|
||||
form.standard = ''
|
||||
form.score = undefined
|
||||
form.remarks = ''
|
||||
|
||||
// 编辑时填充数据
|
||||
@@ -125,20 +138,21 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
form.categortyId = row.categortyId || ''
|
||||
form.pointName = row.pointName || ''
|
||||
form.standard = row.standard || ''
|
||||
form.score = row.score !== undefined && row.score !== null ? Number(row.score) : undefined
|
||||
form.remarks = row.remarks || ''
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && (!row.pointName || !row.standard)) {
|
||||
// 如果需要获取详情(含 score)
|
||||
if (row.id && (!row.pointName || row.standard === undefined || row.score === undefined)) {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.categortyId = res.data.categortyId || ''
|
||||
form.pointName = res.data.pointName || ''
|
||||
form.standard = res.data.standard || ''
|
||||
form.score = res.data.score !== undefined && res.data.score !== null ? Number(res.data.score) : undefined
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
}).catch((err) => {
|
||||
}).finally(() => {
|
||||
}).catch(() => {}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
@@ -155,10 +169,12 @@ const onSubmit = async () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 与接口文档一致:categortyId, pointName, standard, score, remarks
|
||||
const submitData = {
|
||||
categortyId: form.categortyId,
|
||||
pointName: form.pointName,
|
||||
standard: form.standard || '',
|
||||
score: form.score !== undefined && form.score !== null ? Number(form.score) : 0,
|
||||
remarks: form.remarks || ''
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,21 @@
|
||||
:inline="true"
|
||||
@keyup.enter="getDataList"
|
||||
class="search-form">
|
||||
<el-form-item label="考核项" prop="categortyId">
|
||||
<el-select
|
||||
v-model="state.queryForm.categortyId"
|
||||
placeholder="请选择考核项"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in categoryList"
|
||||
:key="item.id"
|
||||
:label="item.category"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="指标名称" prop="pointName">
|
||||
<el-input
|
||||
v-model="state.queryForm.pointName"
|
||||
@@ -155,9 +170,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="AssessmentPoint">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/assessmentpoint";
|
||||
import { getList as getAssessmentCategoryList } from "/@/api/stuwork/assessmentcategory";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue'
|
||||
@@ -169,6 +185,7 @@ const formDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const categoryList = ref<any[]>([])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
@@ -194,9 +211,10 @@ const tableStyle = {
|
||||
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
|
||||
}
|
||||
|
||||
// 配置 useTable
|
||||
// 配置 useTable(分页接口支持 categortyId、pointName,与考核项对应)
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
categortyId: '',
|
||||
pointName: ''
|
||||
},
|
||||
pageList: fetchList,
|
||||
@@ -215,13 +233,28 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 获取考核项列表(与表单考核项对应,接口:assessmentcategory/list)
|
||||
const getCategoryList = async () => {
|
||||
try {
|
||||
const res = await getAssessmentCategoryList()
|
||||
categoryList.value = res.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
categoryList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.categortyId = ''
|
||||
state.queryForm.pointName = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCategoryList()
|
||||
})
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
|
||||
@@ -69,8 +69,7 @@
|
||||
v-model="form.score"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
:min="0"
|
||||
placeholder="请输入分数"
|
||||
placeholder="请输入分数(可为负数)"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -12,6 +12,53 @@
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="form.schoolYear"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolYearList"
|
||||
:key="item.year"
|
||||
:label="item.year"
|
||||
:value="item.year" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="form.schoolTerm"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolTermList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="班号" prop="classCode">
|
||||
<el-select
|
||||
v-model="form.classCode"
|
||||
placeholder="请选择班号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in classList"
|
||||
:key="item.classCode"
|
||||
:label="item.classNo"
|
||||
:value="item.classCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
@@ -52,9 +99,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ClassPlanFormDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/classplan'
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getClassListByRole } from '/@/api/basic/basicclass'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
import Editor from '/@/components/Editor/index.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
@@ -64,10 +114,16 @@ const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
classCode: '',
|
||||
title: '',
|
||||
content: '',
|
||||
remarks: ''
|
||||
@@ -75,6 +131,15 @@ const form = reactive({
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
schoolYear: [
|
||||
{ required: true, message: '请选择学年', trigger: 'change' }
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
classCode: [
|
||||
{ required: true, message: '请选择班号', trigger: 'change' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' }
|
||||
],
|
||||
@@ -92,6 +157,9 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.schoolYear = ''
|
||||
form.schoolTerm = ''
|
||||
form.classCode = ''
|
||||
form.title = ''
|
||||
form.content = ''
|
||||
form.remarks = ''
|
||||
@@ -99,6 +167,9 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.schoolYear = row.schoolYear || ''
|
||||
form.schoolTerm = row.schoolTerm || ''
|
||||
form.classCode = row.classCode || ''
|
||||
form.title = row.title || ''
|
||||
form.content = row.content || ''
|
||||
form.remarks = row.remarks || ''
|
||||
@@ -108,6 +179,9 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.schoolYear = res.data.schoolYear || form.schoolYear
|
||||
form.schoolTerm = res.data.schoolTerm || form.schoolTerm
|
||||
form.classCode = res.data.classCode || form.classCode
|
||||
form.content = res.data.content || ''
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
@@ -119,6 +193,43 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 学年列表(班级管理-学年接口)
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
schoolYearList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 学期字典(系统通用)
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res?.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label ?? item.dictLabel ?? item.name,
|
||||
value: item.value ?? item.dictValue ?? item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 班级列表(班级管理目录下)
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
classList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
classList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
@@ -129,6 +240,9 @@ const onSubmit = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData = {
|
||||
schoolYear: form.schoolYear,
|
||||
schoolTerm: form.schoolTerm,
|
||||
classCode: form.classCode,
|
||||
title: form.title,
|
||||
content: form.content,
|
||||
remarks: form.remarks
|
||||
@@ -154,6 +268,13 @@ const onSubmit = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化:加载学年、学期、班级
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
getClassListData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<script setup lang="ts" name="ClassroomBaseArrangeDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { editObj } from '/@/api/stuwork/classroombase'
|
||||
import { addClassRoomAssign } from '/@/api/stuwork/teachclassroomassign'
|
||||
import { queryAllClass } from '/@/api/basic/basicclass'
|
||||
import { getClassRoomList } from '/@/api/stuwork/teachclassroom'
|
||||
import { getBuildingList } from '/@/api/stuwork/teachbuilding'
|
||||
@@ -150,17 +150,16 @@ const onSubmit = async () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await editObj({
|
||||
id: form.id,
|
||||
await addClassRoomAssign({
|
||||
buildingNo: form.buildingNo,
|
||||
classCode: form.classCode,
|
||||
position: form.position
|
||||
position: form.position,
|
||||
classCode: form.classCode
|
||||
})
|
||||
useMessage().success('教室安排成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '教室安排失败')
|
||||
} catch (_err) {
|
||||
// 错误由 request 拦截器统一提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
327
src/views/stuwork/classroombase/assets.vue
Normal file
327
src/views/stuwork/classroombase/assets.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="教室公物编辑"
|
||||
v-model="visible"
|
||||
:width="800"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="楼号" prop="buildingNo">
|
||||
<el-input
|
||||
v-model="form.buildingNo"
|
||||
placeholder="楼号"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学院" prop="deptName">
|
||||
<el-input
|
||||
v-model="form.deptName"
|
||||
placeholder="学院"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-input
|
||||
v-model="form.classNo"
|
||||
placeholder="班级"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="教室位置" prop="position">
|
||||
<el-input
|
||||
v-model="form.position"
|
||||
placeholder="教室位置"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="讲台类型" prop="platformType">
|
||||
<el-select
|
||||
v-model="form.platformType"
|
||||
placeholder="请选择讲台类型"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in platformTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="投影类型" prop="tyType">
|
||||
<el-select
|
||||
v-model="form.tyType"
|
||||
placeholder="请选择投影类型"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in tyTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="电视机" prop="tvType">
|
||||
<el-select
|
||||
v-model="form.tvType"
|
||||
placeholder="请选择电视机"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in tvTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="方凳数量" prop="chairCnt">
|
||||
<el-input-number
|
||||
v-model="form.chairCnt"
|
||||
:min="0"
|
||||
placeholder="请输入方凳数量"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="课桌数量" prop="tableCnt">
|
||||
<el-input-number
|
||||
v-model="form.tableCnt"
|
||||
:min="0"
|
||||
placeholder="请输入课桌数量"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注" />
|
||||
</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="ClassroomAssetsDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { editAssets } from '/@/api/stuwork/classassets'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const platformTypeList = ref<any[]>([])
|
||||
const tyTypeList = ref<any[]>([])
|
||||
const tvTypeList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
buildingNo: '',
|
||||
deptName: '',
|
||||
deptCode: '',
|
||||
classCode: '',
|
||||
classNo: '',
|
||||
position: '',
|
||||
platformType: '',
|
||||
tyType: '',
|
||||
tvType: '',
|
||||
chairCnt: 0,
|
||||
tableCnt: 0,
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
platformType: [
|
||||
{ required: true, message: '请选择讲台类型', trigger: 'change' }
|
||||
],
|
||||
tyType: [
|
||||
{ required: true, message: '请选择投影类型', trigger: 'change' }
|
||||
],
|
||||
tvType: [
|
||||
{ required: true, message: '请选择电视机', trigger: 'change' }
|
||||
],
|
||||
chairCnt: [
|
||||
{ required: true, message: '请填写方凳数量', trigger: 'blur' }
|
||||
],
|
||||
tableCnt: [
|
||||
{ required: true, message: '请填写课桌数量', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (row: any) => {
|
||||
visible.value = true
|
||||
resetForm()
|
||||
|
||||
// 填充现有数据
|
||||
if (row) {
|
||||
form.id = row.id || ''
|
||||
form.buildingNo = row.buildingNo || ''
|
||||
form.deptName = row.deptName || ''
|
||||
form.deptCode = row.deptCode || ''
|
||||
form.classCode = row.classCode || ''
|
||||
form.classNo = row.classNo || ''
|
||||
form.position = row.position || ''
|
||||
form.platformType = row.platformType || ''
|
||||
form.tyType = row.tyType || ''
|
||||
form.tvType = row.tvType || ''
|
||||
form.chairCnt = row.chairCnt || 0
|
||||
form.tableCnt = row.tableCnt || 0
|
||||
form.remarks = row.remarks || ''
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
form.id = ''
|
||||
form.buildingNo = ''
|
||||
form.deptName = ''
|
||||
form.deptCode = ''
|
||||
form.classCode = ''
|
||||
form.classNo = ''
|
||||
form.position = ''
|
||||
form.platformType = ''
|
||||
form.tyType = ''
|
||||
form.tvType = ''
|
||||
form.chairCnt = 0
|
||||
form.tableCnt = 0
|
||||
form.remarks = ''
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData = {
|
||||
id: form.id,
|
||||
buildingNo: form.buildingNo,
|
||||
deptName: form.deptName,
|
||||
deptCode: form.deptCode,
|
||||
classCode: form.classCode,
|
||||
classNo: form.classNo,
|
||||
position: form.position,
|
||||
platformType: form.platformType,
|
||||
tyType: form.tyType,
|
||||
tvType: form.tvType,
|
||||
chairCnt: form.chairCnt,
|
||||
tableCnt: form.tableCnt,
|
||||
remarks: form.remarks
|
||||
}
|
||||
await editAssets(submitData)
|
||||
useMessage().success('保存成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (_err) {
|
||||
// 错误由 request 拦截器统一提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取讲台类型字典
|
||||
const getPlatformTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('platform_type')
|
||||
if (res.data) {
|
||||
platformTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
})) : []
|
||||
}
|
||||
} catch (err) {
|
||||
platformTypeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取投影类型字典
|
||||
const getTyTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('ty_type')
|
||||
if (res.data) {
|
||||
tyTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
})) : []
|
||||
}
|
||||
} catch (err) {
|
||||
tyTypeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取电视机类型字典
|
||||
const getTvTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('tv_type')
|
||||
if (res.data) {
|
||||
tvTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
})) : []
|
||||
}
|
||||
} catch (err) {
|
||||
tvTypeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getPlatformTypeDict()
|
||||
getTyTypeDict()
|
||||
getTvTypeDict()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -181,19 +181,43 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="380" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="!scope.row.classCode || !scope.row.position"
|
||||
icon="Setting"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleArrange(scope.row)">
|
||||
教室安排
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Close"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleCancelArrange(scope.row)">
|
||||
取消教室安排
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Collection"
|
||||
link
|
||||
type="success"
|
||||
@click="handleAssets(scope.row)"
|
||||
class="ml10">
|
||||
教室公物
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Lock"
|
||||
link
|
||||
type="warning"
|
||||
@click="handlePassword(scope.row)"
|
||||
class="ml10">
|
||||
门锁密码
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
@@ -213,6 +237,10 @@
|
||||
|
||||
<!-- 教室安排表单弹窗 -->
|
||||
<arrange-dialog ref="arrangeDialogRef" @refresh="getDataList" />
|
||||
<!-- 教室公物编辑弹窗 -->
|
||||
<assets-dialog ref="assetsDialogRef" @refresh="getDataList" />
|
||||
<!-- 门锁密码编辑弹窗 -->
|
||||
<password-dialog ref="passwordDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -226,7 +254,10 @@ import { getDicts } from "/@/api/admin/dict";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import ArrangeDialog from './arrange.vue'
|
||||
import { List, OfficeBuilding, CircleCheck, Location, UserFilled, Collection, Setting, Menu, Calendar, Search, Document } from '@element-plus/icons-vue'
|
||||
import AssetsDialog from './assets.vue'
|
||||
import PasswordDialog from './password.vue'
|
||||
import { delClassRoomAssign } from '/@/api/stuwork/teachclassroomassign'
|
||||
import { List, OfficeBuilding, CircleCheck, Location, UserFilled, Collection, Setting, Menu, Calendar, Search, Document, Close, Lock } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
// 定义变量内容
|
||||
@@ -240,6 +271,8 @@ const platformTypeList = ref<any[]>([])
|
||||
const tyTypeList = ref<any[]>([])
|
||||
const tvTypeList = ref<any[]>([])
|
||||
const arrangeDialogRef = ref()
|
||||
const assetsDialogRef = ref()
|
||||
const passwordDialogRef = ref()
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
@@ -416,6 +449,33 @@ const handleArrange = (row: any) => {
|
||||
arrangeDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 取消教室安排
|
||||
const handleCancelArrange = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('确定要取消教室安排吗?')
|
||||
await delClassRoomAssign(row)
|
||||
useMessage().success('取消成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '取消失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 教室公物
|
||||
const handleAssets = (row: any) => {
|
||||
assetsDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 门锁密码
|
||||
const handlePassword = (row: any) => {
|
||||
passwordDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 获取系部列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
|
||||
156
src/views/stuwork/classroombase/password.vue
Normal file
156
src/views/stuwork/classroombase/password.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="门锁密码"
|
||||
v-model="visible"
|
||||
:width="500"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="楼号">
|
||||
<el-input
|
||||
v-model="form.buildingNo"
|
||||
placeholder="楼号"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="教室位置">
|
||||
<el-input
|
||||
v-model="form.position"
|
||||
placeholder="教室位置"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="门锁密码" prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请输入门锁密码"
|
||||
clearable />
|
||||
</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="ClassroomPasswordDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { editAssets } from '/@/api/stuwork/classassets'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
buildingNo: '',
|
||||
deptName: '',
|
||||
deptCode: '',
|
||||
classCode: '',
|
||||
classNo: '',
|
||||
position: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
password: [
|
||||
{ required: true, message: '请输入门锁密码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (row: any) => {
|
||||
visible.value = true
|
||||
resetForm()
|
||||
|
||||
// 填充现有数据
|
||||
if (row) {
|
||||
form.id = row.id || ''
|
||||
form.buildingNo = row.buildingNo || ''
|
||||
form.deptName = row.deptName || ''
|
||||
form.deptCode = row.deptCode || ''
|
||||
form.classCode = row.classCode || ''
|
||||
form.classNo = row.classNo || ''
|
||||
form.position = row.position || ''
|
||||
form.password = row.password || ''
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
form.id = ''
|
||||
form.buildingNo = ''
|
||||
form.deptName = ''
|
||||
form.deptCode = ''
|
||||
form.classCode = ''
|
||||
form.classNo = ''
|
||||
form.position = ''
|
||||
form.password = ''
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 只提交必要的字段用于修改门锁密码
|
||||
const submitData = {
|
||||
id: form.id,
|
||||
buildingNo: form.buildingNo,
|
||||
deptName: form.deptName,
|
||||
deptCode: form.deptCode,
|
||||
classCode: form.classCode,
|
||||
classNo: form.classNo,
|
||||
position: form.position,
|
||||
password: form.password
|
||||
}
|
||||
await editAssets(submitData)
|
||||
useMessage().success('保存成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (_err) {
|
||||
// 错误由 request 拦截器统一提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -495,7 +495,9 @@ const confirmCheck = async () => {
|
||||
checkDialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '考核失败')
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '考核失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +514,9 @@ const handleDelete = async (ids: string[]) => {
|
||||
getDataList()
|
||||
useMessage().success('删除成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,40 +45,6 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="form.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="班级" prop="classNo">
|
||||
<el-select
|
||||
v-model="form.classNo"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in classList"
|
||||
:key="item.classCode"
|
||||
:label="item.classNo"
|
||||
:value="item.classNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -95,8 +61,6 @@ import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { initObj } from '/@/api/stuwork/classtheme'
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getClassListByRole } from '/@/api/basic/basicclass'
|
||||
import { getDeptList } from '/@/api/basic/basicclass'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
@@ -107,15 +71,11 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
const deptList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
deptCode: '',
|
||||
classNo: ''
|
||||
schoolTerm: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
@@ -125,12 +85,6 @@ const dataRules = {
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
deptCode: [
|
||||
{ required: true, message: '请选择学院', trigger: 'change' }
|
||||
],
|
||||
classNo: [
|
||||
{ required: true, message: '请选择班级', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -141,8 +95,6 @@ const openDialog = () => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.schoolYear = ''
|
||||
form.schoolTerm = ''
|
||||
form.deptCode = ''
|
||||
form.classNo = ''
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,34 +144,10 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
deptList.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
classList.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
getDeptListData()
|
||||
getClassListData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
|
||||
@@ -17,7 +17,19 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍号" prop="roomNo">
|
||||
<el-input v-model="form.roomNo" placeholder="请输入宿舍号" />
|
||||
<el-select
|
||||
v-model="form.roomNo"
|
||||
placeholder="请选择宿舍号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roomList"
|
||||
:key="item.roomNo"
|
||||
:label="item.roomNo"
|
||||
:value="item.roomNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="记录日期" prop="recordDate">
|
||||
<el-date-picker
|
||||
@@ -50,6 +62,7 @@ import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, putObj, getObj } from '/@/api/stuwork/dormhygienedaily'
|
||||
import { getBuildingList } from '/@/api/stuwork/dormbuilding'
|
||||
import { getRoomList } from '/@/api/stuwork/dormroom'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
@@ -59,6 +72,7 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const buildingList = ref<any[]>([])
|
||||
const roomList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -75,7 +89,7 @@ const dataRules = {
|
||||
{ required: true, message: '请选择楼号', trigger: 'change' }
|
||||
],
|
||||
roomNo: [
|
||||
{ required: true, message: '请输入宿舍号', trigger: 'blur' }
|
||||
{ required: true, message: '请选择宿舍号', trigger: 'change' }
|
||||
],
|
||||
recordDate: [
|
||||
{ required: true, message: '请选择记录日期', trigger: 'change' }
|
||||
@@ -159,9 +173,22 @@ const getBuildingListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取宿舍号列表
|
||||
const getRoomListData = async () => {
|
||||
try {
|
||||
const res = await getRoomList()
|
||||
if (res.data) {
|
||||
roomList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
roomList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getBuildingListData()
|
||||
getRoomListData()
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
|
||||
@@ -2,7 +2,19 @@
|
||||
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :width="600" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
|
||||
<el-form-item label="房间号" prop="roomNo">
|
||||
<el-input v-model="form.roomNo" placeholder="请输入房间号" />
|
||||
<el-select
|
||||
v-model="form.roomNo"
|
||||
placeholder="请选择房间号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roomList"
|
||||
:key="item.roomNo"
|
||||
:label="item.roomNo"
|
||||
:value="item.roomNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="整改时间" prop="reformDate">
|
||||
<el-date-picker
|
||||
@@ -31,9 +43,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormReformFormDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, putObj } from '/@/api/stuwork/dormreform'
|
||||
import { getRoomList } from '/@/api/stuwork/dormroom'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
@@ -42,6 +55,7 @@ const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const roomList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -54,7 +68,7 @@ const form = reactive({
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
roomNo: [
|
||||
{ required: true, message: '请输入房间号', trigger: 'blur' }
|
||||
{ required: true, message: '请选择房间号', trigger: 'change' }
|
||||
],
|
||||
reformDate: [
|
||||
{ required: true, message: '请选择整改时间', trigger: 'change' }
|
||||
@@ -90,6 +104,23 @@ const openDialog = (type: string = 'add', row?: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 获取房间号列表
|
||||
const getRoomListData = async () => {
|
||||
try {
|
||||
const res = await getRoomList()
|
||||
if (res.data) {
|
||||
roomList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
roomList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getRoomListData()
|
||||
})
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
@@ -89,9 +89,11 @@
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in bedNoList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item">
|
||||
:key="item.bedNo"
|
||||
:value="item.bedNo">
|
||||
<span :class="{ 'bed-option-occupied': item.haveStudent }">
|
||||
{{ item.bedNo }}{{ item.haveStudent ? ' (有人)' : '' }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -138,7 +140,8 @@ const loading = ref(false)
|
||||
const classList = ref<any[]>([])
|
||||
const studentList = ref<any[]>([])
|
||||
const roomList = ref<any[]>([])
|
||||
const bedNoList = ref<string[]>([])
|
||||
// 床位列表:支持 haveStudent 标记(true=有人,false=无人)
|
||||
const bedNoList = ref<Array<{ bedNo: string; haveStudent: boolean }>>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -206,7 +209,7 @@ const handleStudentChange = (stuNo: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 房间号变化时获取床位号列表
|
||||
// 房间号变化时获取床位号列表(支持 haveStudent:true=有人,false=无人)
|
||||
const handleRoomChange = async (roomNo: string) => {
|
||||
if (!roomNo) {
|
||||
bedNoList.value = []
|
||||
@@ -214,20 +217,25 @@ const handleRoomChange = async (roomNo: string) => {
|
||||
return
|
||||
}
|
||||
|
||||
const toBedItem = (item: any): { bedNo: string; haveStudent: boolean } => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return { bedNo: String(item), haveStudent: false }
|
||||
}
|
||||
return {
|
||||
bedNo: String(item?.bedNo ?? item?.value ?? item ?? ''),
|
||||
haveStudent: !!(item && item.haveStudent === true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fearchRoomStuNum(roomNo)
|
||||
if (res.data) {
|
||||
// 根据返回的数据结构处理床位号列表
|
||||
// 假设返回的是数字数组或对象数组
|
||||
if (Array.isArray(res.data)) {
|
||||
bedNoList.value = res.data.map((item: any) => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return String(item)
|
||||
}
|
||||
return String(item.bedNo || item.value || item)
|
||||
})
|
||||
bedNoList.value = res.data.map(toBedItem).filter((b) => b.bedNo)
|
||||
} else if (res.data.bedNos && Array.isArray(res.data.bedNos)) {
|
||||
bedNoList.value = res.data.bedNos.map((item: any) => String(item))
|
||||
bedNoList.value = res.data.bedNos.map((item: any) =>
|
||||
toBedItem(typeof item === 'object' ? item : { bedNo: String(item), haveStudent: false })
|
||||
)
|
||||
} else {
|
||||
bedNoList.value = []
|
||||
}
|
||||
@@ -321,3 +329,8 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bed-option-occupied {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -211,9 +211,13 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop || '']?.icon || OfficeBuilding" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 床位号列特殊模板 -->
|
||||
<!-- 床位号列:haveStudent 为 true 时变色标记有人 -->
|
||||
<template v-if="col.prop === 'bedNo'" #default="scope">
|
||||
<el-tag v-if="scope.row.bedNo" size="small" type="info" effect="plain">
|
||||
<el-tag
|
||||
v-if="scope.row.bedNo"
|
||||
size="small"
|
||||
:type="scope.row.haveStudent ? 'warning' : 'info'"
|
||||
effect="plain">
|
||||
{{ scope.row.bedNo }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
@@ -274,6 +278,12 @@
|
||||
|
||||
<!-- 转宿弹窗 -->
|
||||
<TransferDialog ref="transferDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 宿舍互换弹窗 -->
|
||||
<SwapDialog ref="swapDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 打印宿舍卡弹窗 -->
|
||||
<PrintCardDialog ref="printCardDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -281,7 +291,7 @@
|
||||
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObjs } from "/@/api/stuwork/dormroomstudent";
|
||||
import { fetchList, delObjs, exportEmptyPeopleRoomExcel } from "/@/api/stuwork/dormroomstudent";
|
||||
import { getDeptList } from "/@/api/basic/basicclass";
|
||||
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
|
||||
import { fetchDormRoomTreeList } from "/@/api/stuwork/dormroom";
|
||||
@@ -289,6 +299,8 @@ import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue';
|
||||
import TransferDialog from './transfer.vue';
|
||||
import SwapDialog from './swap.vue';
|
||||
import PrintCardDialog from './printCard.vue';
|
||||
import TreeSelect from '/@/components/TreeSelect/index.vue';
|
||||
import { List, OfficeBuilding, House, Grid, UserFilled, Phone, CreditCard, Avatar, User, Setting, Menu, Search, Document } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
@@ -301,6 +313,8 @@ const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const transferDialogRef = ref()
|
||||
const swapDialogRef = ref()
|
||||
const printCardDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const deptList = ref<any[]>([])
|
||||
@@ -425,29 +439,91 @@ const handleDormDataTypeChange = (dormdataType: string) => {
|
||||
getDormRoomTreeListData(dormdataType)
|
||||
}
|
||||
|
||||
// 打印宿舍卡
|
||||
// 打印宿舍卡(按房间号查询后打印)
|
||||
const handlePrintCard = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
printCardDialogRef.value?.openDialog()
|
||||
}
|
||||
|
||||
// 宿舍互换
|
||||
// 宿舍互换(两名学生互换宿舍)
|
||||
const handleRoomSwap = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
const query = {
|
||||
deptCode: searchForm.deptCode,
|
||||
buildingNo: searchForm.buildingNo,
|
||||
gender: searchForm.gender,
|
||||
roomNo: searchForm.roomNo || searchForm.roomNoInput,
|
||||
classNo: searchForm.classNo,
|
||||
stuNo: searchForm.stuNo,
|
||||
realName: searchForm.realName
|
||||
}
|
||||
swapDialogRef.value?.openDialog(query)
|
||||
}
|
||||
|
||||
// 导出
|
||||
const handleExport = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
// 导出:空 n 人宿舍导出(按当前筛选条件传参)
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
const params = {
|
||||
deptCode: searchForm.deptCode,
|
||||
buildingNo: searchForm.buildingNo,
|
||||
gender: searchForm.gender,
|
||||
dormdataType: searchForm.dormdataType,
|
||||
roomNo: searchForm.roomNo || searchForm.roomNoInput,
|
||||
classNo: searchForm.classNo,
|
||||
stuNo: searchForm.stuNo,
|
||||
realName: searchForm.realName
|
||||
}
|
||||
const res = await exportEmptyPeopleRoomExcel(params)
|
||||
const blob = res instanceof Blob ? res : new Blob([res as BlobPart], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `空宿舍导出_${Date.now()}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 名单导出
|
||||
const handleExportList = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
// 名单导出:与导出共用空 n 人宿舍导出接口,文件名区分
|
||||
const handleExportList = async () => {
|
||||
try {
|
||||
const params = {
|
||||
deptCode: searchForm.deptCode,
|
||||
buildingNo: searchForm.buildingNo,
|
||||
gender: searchForm.gender,
|
||||
dormdataType: searchForm.dormdataType,
|
||||
roomNo: searchForm.roomNo || searchForm.roomNoInput,
|
||||
classNo: searchForm.classNo,
|
||||
stuNo: searchForm.stuNo,
|
||||
realName: searchForm.realName
|
||||
}
|
||||
const res = await exportEmptyPeopleRoomExcel(params)
|
||||
const blob = res instanceof Blob ? res : new Blob([res as BlobPart], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `住宿学生名单_${Date.now()}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑(与转宿共用接口 edit,修改房间/床位/是否舍长)
|
||||
const handleEdit = (row: any) => {
|
||||
transferDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 转宿
|
||||
const handleTransfer = (row: any) => {
|
||||
transferDialogRef.value.openDialog(row)
|
||||
transferDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 退宿
|
||||
|
||||
152
src/views/stuwork/dormroomstudent/printCard.vue
Normal file
152
src/views/stuwork/dormroomstudent/printCard.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="打印宿舍卡"
|
||||
v-model="visible"
|
||||
:close-on-click-modal="false"
|
||||
width="640px"
|
||||
destroy-on-close
|
||||
@closed="onClosed">
|
||||
<div v-loading="loading">
|
||||
<el-form :inline="true" class="mb16">
|
||||
<el-form-item label="房间号">
|
||||
<el-input v-model="roomNo" placeholder="请输入房间号" clearable style="width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFetch">查询并预览</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-if="printData.length" ref="printAreaRef" class="print-area">
|
||||
<div v-for="(room, idx) in printData" :key="idx" class="room-card mb16">
|
||||
<div class="room-title">房间号:{{ room.roomNo }}</div>
|
||||
<table class="print-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>姓名</th>
|
||||
<th>学号</th>
|
||||
<th>床位</th>
|
||||
<th>是否舍长</th>
|
||||
<th>班级</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(s, i) in (room.dormRoomStudentVOList || [])" :key="i">
|
||||
<td>{{ s.realName }}</td>
|
||||
<td>{{ s.stuNo }}</td>
|
||||
<td>{{ s.bedNo }}</td>
|
||||
<td>{{ s.isLeader === '1' ? '是' : '否' }}</td>
|
||||
<td>{{ s.className || s.classNo }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">关 闭</el-button>
|
||||
<el-button type="primary" @click="handlePrint" :disabled="!printData.length">打 印</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormRoomStudentPrintCard">
|
||||
import { ref } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { printDormRoomData } from '/@/api/stuwork/dormroomstudent'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const roomNo = ref('')
|
||||
const printData = ref<any[]>([])
|
||||
const printAreaRef = ref<HTMLElement>()
|
||||
|
||||
const openDialog = (initialRoomNo?: string) => {
|
||||
visible.value = true
|
||||
roomNo.value = initialRoomNo || ''
|
||||
printData.value = []
|
||||
}
|
||||
|
||||
const onClosed = () => {
|
||||
printData.value = []
|
||||
}
|
||||
|
||||
const handleFetch = async () => {
|
||||
const no = (roomNo.value || '').trim()
|
||||
if (!no) {
|
||||
useMessage().warning('请输入房间号')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await printDormRoomData(no)
|
||||
const data = res?.data ?? res
|
||||
printData.value = Array.isArray(data) ? data : (data ? [data] : [])
|
||||
if (!printData.value.length) {
|
||||
useMessage().info('该房间暂无数据')
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '查询失败')
|
||||
printData.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handlePrint = () => {
|
||||
if (!printData.value.length) return
|
||||
const win = window.open('', '_blank')
|
||||
if (!win) {
|
||||
useMessage().error('无法打开打印窗口')
|
||||
return
|
||||
}
|
||||
const el = printAreaRef.value
|
||||
if (el) {
|
||||
win.document.write(`
|
||||
<!DOCTYPE html><html><head><meta charset="utf-8"><title>宿舍卡</title>
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #333; padding: 6px 8px; text-align: center; }
|
||||
.room-title { font-weight: bold; margin-bottom: 8px; }
|
||||
</style>
|
||||
</head><body>${el.innerHTML}</body></html>
|
||||
`)
|
||||
win.document.close()
|
||||
win.focus()
|
||||
setTimeout(() => {
|
||||
win.print()
|
||||
win.close()
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ openDialog })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.print-area {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.room-card {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.room-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.print-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
th,
|
||||
td {
|
||||
border: 1px solid var(--el-border-color);
|
||||
padding: 6px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.mb16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
136
src/views/stuwork/dormroomstudent/swap.vue
Normal file
136
src/views/stuwork/dormroomstudent/swap.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="宿舍互换"
|
||||
v-model="visible"
|
||||
:close-on-click-modal="false"
|
||||
draggable
|
||||
width="520px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
:validate-on-rule-change="false"
|
||||
v-loading="loading">
|
||||
<el-form-item label="学生一" prop="sourceSutNo">
|
||||
<el-select
|
||||
v-model="form.sourceSutNo"
|
||||
placeholder="请选择学生(学号)"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="onSourceChange">
|
||||
<el-option
|
||||
v-for="item in studentOptions"
|
||||
:key="item.stuNo"
|
||||
:label="`${item.realName}(${item.stuNo})${item.roomNo || ''}`"
|
||||
:value="item.stuNo" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学生二" prop="targetStuNO">
|
||||
<el-select
|
||||
v-model="form.targetStuNO"
|
||||
placeholder="请选择学生(学号)"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="onTargetChange">
|
||||
<el-option
|
||||
v-for="item in studentOptions"
|
||||
:key="item.stuNo"
|
||||
:label="`${item.realName}(${item.stuNo})${item.roomNo || ''}`"
|
||||
:value="item.stuNo" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :loading="submitting">确 认 互 换</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormRoomStudentSwapDialog">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { exchangeRoom } from '/@/api/stuwork/dormroomstudent'
|
||||
import { fetchList } from '/@/api/stuwork/dormroomstudent'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const formRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const studentOptions = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
sourceSutNo: '',
|
||||
targetStuNO: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
sourceSutNo: [{ required: true, message: '请选择学生一', trigger: 'change' }],
|
||||
targetStuNO: [{ required: true, message: '请选择学生二', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const onSourceChange = () => {
|
||||
if (form.sourceSutNo && form.targetStuNO && form.sourceSutNo === form.targetStuNO) {
|
||||
form.targetStuNO = ''
|
||||
}
|
||||
}
|
||||
const onTargetChange = () => {
|
||||
if (form.sourceSutNo && form.targetStuNO && form.sourceSutNo === form.targetStuNO) {
|
||||
form.sourceSutNo = ''
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = async (queryParams?: any) => {
|
||||
visible.value = true
|
||||
form.sourceSutNo = ''
|
||||
form.targetStuNO = ''
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchList({
|
||||
current: 1,
|
||||
size: 500,
|
||||
...queryParams
|
||||
})
|
||||
const list = res?.data?.records ?? res?.records ?? []
|
||||
studentOptions.value = Array.isArray(list) ? list : []
|
||||
} catch (err) {
|
||||
studentOptions.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
if (form.sourceSutNo === form.targetStuNO) {
|
||||
useMessage().warning('请选择两名不同学生')
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
await exchangeRoom({
|
||||
sourceSutNo: form.sourceSutNo,
|
||||
targetStuNO: form.targetStuNO
|
||||
})
|
||||
useMessage().success('互换成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ openDialog })
|
||||
</script>
|
||||
@@ -41,9 +41,11 @@
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in bedNoList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item">
|
||||
:key="item.bedNo"
|
||||
:value="item.bedNo">
|
||||
<span :class="{ 'bed-option-occupied': item.haveStudent }">
|
||||
{{ item.bedNo }}{{ item.haveStudent ? ' (有人)' : '' }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -95,7 +97,8 @@ const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const roomList = ref<any[]>([])
|
||||
const bedNoList = ref<string[]>([])
|
||||
// 床位列表:支持 haveStudent 标记(true=有人,false=无人)
|
||||
const bedNoList = ref<Array<{ bedNo: string; haveStudent: boolean }>>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -119,7 +122,7 @@ const dataRules = {
|
||||
]
|
||||
}
|
||||
|
||||
// 房间号变化时获取床位号列表
|
||||
// 房间号变化时获取床位号列表(支持 haveStudent:true=有人,false=无人)
|
||||
const handleRoomChange = async (roomNo: string) => {
|
||||
if (!roomNo) {
|
||||
bedNoList.value = []
|
||||
@@ -127,18 +130,25 @@ const handleRoomChange = async (roomNo: string) => {
|
||||
return
|
||||
}
|
||||
|
||||
const toBedItem = (item: any): { bedNo: string; haveStudent: boolean } => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return { bedNo: String(item), haveStudent: false }
|
||||
}
|
||||
return {
|
||||
bedNo: String(item?.bedNo ?? item?.value ?? item ?? ''),
|
||||
haveStudent: !!(item && item.haveStudent === true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fearchRoomStuNum(roomNo)
|
||||
if (res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
bedNoList.value = res.data.map((item: any) => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return String(item)
|
||||
}
|
||||
return String(item.bedNo || item.value || item)
|
||||
})
|
||||
bedNoList.value = res.data.map(toBedItem).filter((b) => b.bedNo)
|
||||
} else if (res.data.bedNos && Array.isArray(res.data.bedNos)) {
|
||||
bedNoList.value = res.data.bedNos.map((item: any) => String(item))
|
||||
bedNoList.value = res.data.bedNos.map((item: any) =>
|
||||
toBedItem(typeof item === 'object' ? item : { bedNo: String(item), haveStudent: false })
|
||||
)
|
||||
} else {
|
||||
bedNoList.value = []
|
||||
}
|
||||
@@ -157,20 +167,20 @@ const openDialog = async (row: any) => {
|
||||
visible.value = true
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = row.id || ''
|
||||
form.roomNo = row.roomNo || ''
|
||||
form.bedNo = row.bedNo || ''
|
||||
form.stuNo = row.stuNo || ''
|
||||
form.isLeader = row.isLeader || '0'
|
||||
bedNoList.value = []
|
||||
await nextTick()
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = row.id || ''
|
||||
form.roomNo = row.roomNo || ''
|
||||
form.bedNo = row.bedNo || ''
|
||||
form.stuNo = row.stuNo || ''
|
||||
form.isLeader = row.isLeader || '0'
|
||||
bedNoList.value = []
|
||||
|
||||
// 如果有房间号,获取床位号列表
|
||||
if (form.roomNo) {
|
||||
handleRoomChange(form.roomNo)
|
||||
}
|
||||
})
|
||||
// 如果有房间号,先拉取床位列表再回填床位号(避免 handleRoomChange 清空 bedNo)
|
||||
if (form.roomNo) {
|
||||
await handleRoomChange(form.roomNo)
|
||||
form.bedNo = row.bedNo || ''
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -193,7 +203,8 @@ const onSubmit = async () => {
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '转宿失败')
|
||||
// 统一交给全局拦截器处理错误提示,避免在这里重复弹出一次
|
||||
console.error('转宿失败', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -223,3 +234,8 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bed-option-occupied {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
</style>
|
||||
|
||||
216
src/views/stuwork/dormsignrecord/form.vue
Normal file
216
src/views/stuwork/dormsignrecord/form.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="新增宿舍点名"
|
||||
v-model="visible"
|
||||
:width="600"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="点名类型" prop="type">
|
||||
<el-select
|
||||
v-model="form.type"
|
||||
placeholder="请选择点名类型"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option label="普通住宿点名" value="1" />
|
||||
<el-option label="留宿点名" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="楼号" prop="buildId">
|
||||
<el-select
|
||||
v-model="form.buildId"
|
||||
placeholder="请选择楼号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in buildingList"
|
||||
:key="item.buildingNo"
|
||||
:label="item.buildingNo"
|
||||
:value="item.buildingNo" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="房间号" prop="roomNo">
|
||||
<el-input
|
||||
v-model="form.roomNo"
|
||||
placeholder="请输入房间号"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="学生列表" prop="list">
|
||||
<el-input
|
||||
v-model="studentNosInput"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
placeholder="请输入学号,多个学号用逗号或换行分隔"
|
||||
style="width: 100%" />
|
||||
<div class="form-tip">提示:多个学号可用逗号或换行分隔</div>
|
||||
</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="DormSignRecordFormDialog">
|
||||
import { ref, reactive, nextTick, computed, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj } from '/@/api/stuwork/dormsignrecord'
|
||||
import { getBuildingList } from '/@/api/stuwork/dormbuilding'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const buildingList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
type: '',
|
||||
buildId: '',
|
||||
roomNo: '',
|
||||
list: [] as Array<{ stuNo: string }>
|
||||
})
|
||||
|
||||
// 学生学号输入(用于显示和编辑)
|
||||
const studentNosInput = ref('')
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
type: [
|
||||
{ required: true, message: '请选择点名类型', trigger: 'change' }
|
||||
],
|
||||
buildId: [
|
||||
{ required: true, message: '请选择楼号', trigger: 'change' }
|
||||
],
|
||||
roomNo: [
|
||||
{ required: true, message: '请输入房间号', trigger: 'blur' }
|
||||
],
|
||||
list: [
|
||||
{ required: true, message: '请输入至少一个学号', trigger: 'change' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请输入至少一个学号'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async () => {
|
||||
visible.value = true
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.type = ''
|
||||
form.buildId = ''
|
||||
form.roomNo = ''
|
||||
form.list = []
|
||||
studentNosInput.value = ''
|
||||
})
|
||||
}
|
||||
|
||||
// 解析学号输入(支持逗号和换行分隔)
|
||||
const parseStudentNos = (input: string): string[] => {
|
||||
if (!input || !input.trim()) return []
|
||||
return input
|
||||
.split(/[,\n]/)
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
// 解析学号列表
|
||||
const stuNos = parseStudentNos(studentNosInput.value)
|
||||
if (stuNos.length === 0) {
|
||||
useMessage().warning('请输入至少一个学号')
|
||||
return
|
||||
}
|
||||
|
||||
form.list = stuNos.map(stuNo => ({ stuNo }))
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await addObj({
|
||||
type: form.type,
|
||||
buildId: form.buildId,
|
||||
roomNo: form.roomNo,
|
||||
list: form.list
|
||||
})
|
||||
useMessage().success('新增成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '新增失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取楼号列表
|
||||
const getBuildingListData = async () => {
|
||||
try {
|
||||
const res = await getBuildingList()
|
||||
buildingList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
buildingList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getBuildingListData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
367
src/views/stuwork/dormsignrecord/index.vue
Normal file
367
src/views/stuwork/dormsignrecord/index.vue
Normal file
@@ -0,0 +1,367 @@
|
||||
<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="deptCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="楼号" prop="buildNo">
|
||||
<el-select
|
||||
v-model="state.queryForm.buildNo"
|
||||
placeholder="请选择楼号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in buildingList"
|
||||
:key="item.buildingNo"
|
||||
:label="item.buildingNo"
|
||||
:value="item.buildingNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间号" prop="roomNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.roomNo"
|
||||
placeholder="请输入房间号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="stuNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.stuNo"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点名类型" prop="type">
|
||||
<el-select
|
||||
v-model="state.queryForm.type"
|
||||
placeholder="请选择点名类型"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="普通住宿点名" value="1" />
|
||||
<el-option label="留宿点名" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="考勤状态" prop="sign">
|
||||
<el-select
|
||||
v-model="state.queryForm.sign"
|
||||
placeholder="请选择考勤状态"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="未到" :value="0" />
|
||||
<el-option label="已到" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="考勤日期" prop="date">
|
||||
<el-date-picker
|
||||
v-model="state.queryForm.date"
|
||||
type="date"
|
||||
placeholder="请选择考勤日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain 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"><Document /></el-icon>
|
||||
宿舍点名管理列表
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
icon="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
新增点名
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="RefreshLeft"
|
||||
type="warning"
|
||||
class="ml10"
|
||||
@click="handleInit">
|
||||
初始化
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
@queryTable="getDataList">
|
||||
<TableColumnControl
|
||||
ref="columnControlRef"
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-type="default"
|
||||
trigger-circle
|
||||
@change="handleColumnChange"
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</TableColumnControl>
|
||||
</right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
: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>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip !== false"
|
||||
:align="col.align || 'center'">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 点名类型列特殊模板 -->
|
||||
<template v-if="col.prop === 'type'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.type === '1' ? 'primary' : 'success'" effect="plain">
|
||||
{{ scope.row.type === '1' ? '普通住宿点名' : '留宿点名' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 考勤状态列特殊模板 -->
|
||||
<template v-else-if="col.prop === 'sign'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.sign === '1' || scope.row.sign === 1 ? 'success' : 'danger'" effect="plain">
|
||||
{{ scope.row.sign === '1' || scope.row.sign === 1 ? '已到' : '未到' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 是否扫过脸列特殊模板 -->
|
||||
<template v-else-if="col.prop === 'isFace'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.isFace === '1' || scope.row.isFace === 1 ? 'success' : 'info'" effect="plain">
|
||||
{{ scope.row.isFace === '1' || scope.row.isFace === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 是否请假列特殊模板 -->
|
||||
<template v-else-if="col.prop === 'isApply'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.isApply === '1' || scope.row.isApply === 1 ? 'warning' : 'info'" effect="plain">
|
||||
{{ scope.row.isApply === '1' || scope.row.isApply === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
v-bind="state.pagination" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增点名弹窗 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormSignRecord">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, initDormStuInfoForAttendance } from "/@/api/stuwork/dormsignrecord";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { getDeptList } from "/@/api/basic/basicclass";
|
||||
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
|
||||
import FormDialog from './form.vue'
|
||||
import { Search, Document, List, Menu, RefreshLeft, OfficeBuilding, Grid, User, CreditCard, House, Check, Calendar, UserFilled } from '@element-plus/icons-vue'
|
||||
import RightToolbar from '/@/components/RightToolbar/index.vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const deptList = ref<any[]>([])
|
||||
const buildingList = ref<any[]>([])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'deptName', label: '学院', minWidth: 120, icon: OfficeBuilding },
|
||||
{ prop: 'className', label: '班级', minWidth: 120, icon: Grid },
|
||||
{ prop: 'stuName', label: '姓名', width: 100, icon: User },
|
||||
{ prop: 'stuNo', label: '学号', width: 120, icon: CreditCard },
|
||||
{ prop: 'roomNo', label: '房间号', width: 100, icon: House },
|
||||
{ prop: 'buildId', label: '楼号', width: 80, icon: OfficeBuilding },
|
||||
{ prop: 'type', label: '点名类型', width: 120, icon: Document },
|
||||
{ prop: 'sign', label: '考勤状态', width: 100, icon: Check },
|
||||
{ prop: 'date', label: '考勤日期', width: 120, icon: Calendar },
|
||||
{ prop: 'isFace', label: '是否扫过脸', width: 100, icon: UserFilled },
|
||||
{ prop: 'isApply', label: '是否请假', width: 100, icon: Document }
|
||||
]
|
||||
|
||||
// 使用表格列控制
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange,
|
||||
loadSavedConfig
|
||||
} = useTableColumnControl(tableColumns, { storageKey: route.path })
|
||||
|
||||
// 立即加载配置
|
||||
loadSavedConfig()
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
deptCode: '',
|
||||
buildNo: '',
|
||||
roomNo: '',
|
||||
stuNo: '',
|
||||
type: '',
|
||||
sign: '',
|
||||
date: ''
|
||||
},
|
||||
pageList: async (params: any) => {
|
||||
const res = await fetchList(params)
|
||||
const data = res?.data
|
||||
return {
|
||||
data: {
|
||||
records: data?.records ?? [],
|
||||
total: data?.total ?? 0
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true,
|
||||
isPage: true
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.deptCode = ''
|
||||
state.queryForm.buildNo = ''
|
||||
state.queryForm.roomNo = ''
|
||||
state.queryForm.stuNo = ''
|
||||
state.queryForm.type = ''
|
||||
state.queryForm.sign = ''
|
||||
state.queryForm.date = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const handleInit = async () => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('确定要初始化宿舍学生信息用于考勤吗?')
|
||||
await initDormStuInfoForAttendance()
|
||||
useMessage().success('初始化成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '初始化失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
deptList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取楼号列表
|
||||
const getBuildingListData = async () => {
|
||||
try {
|
||||
const res = await getBuildingList()
|
||||
buildingList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
buildingList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDeptListData()
|
||||
getBuildingListData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
@@ -184,11 +184,24 @@ const TimeRuleTableComponent = defineComponent({
|
||||
default: ({ row }: any) => {
|
||||
const timeList = getTimeList(row)
|
||||
return h('div', { class: 'time-slots-container' },
|
||||
timeList.map((timeSlot: any, index: number) =>
|
||||
h('div', { key: index, class: 'time-slot-item' }, [
|
||||
timeList.map((timeSlot: any, index: number) => {
|
||||
// 通过数组索引更新,确保响应式
|
||||
const updateStartTime = (val: string) => {
|
||||
const list = getTimeList(row)
|
||||
if (list[index]) {
|
||||
list[index].startTime = val || ''
|
||||
}
|
||||
}
|
||||
const updateEndTime = (val: string) => {
|
||||
const list = getTimeList(row)
|
||||
if (list[index]) {
|
||||
list[index].endTime = val || ''
|
||||
}
|
||||
}
|
||||
return h('div', { key: index, class: 'time-slot-item' }, [
|
||||
h(ElTimePicker, {
|
||||
modelValue: timeSlot.startTime,
|
||||
'onUpdate:modelValue': (val: string) => { timeSlot.startTime = val },
|
||||
modelValue: timeSlot.startTime || '',
|
||||
'onUpdate:modelValue': updateStartTime,
|
||||
format: 'HH:mm',
|
||||
valueFormat: 'HH:mm',
|
||||
placeholder: '开始时间',
|
||||
@@ -196,8 +209,8 @@ const TimeRuleTableComponent = defineComponent({
|
||||
}),
|
||||
h('span', { style: { margin: '0 10px' } }, '至'),
|
||||
h(ElTimePicker, {
|
||||
modelValue: timeSlot.endTime,
|
||||
'onUpdate:modelValue': (val: string) => { timeSlot.endTime = val },
|
||||
modelValue: timeSlot.endTime || '',
|
||||
'onUpdate:modelValue': updateEndTime,
|
||||
format: 'HH:mm',
|
||||
valueFormat: 'HH:mm',
|
||||
placeholder: '结束时间',
|
||||
@@ -213,7 +226,7 @@ const TimeRuleTableComponent = defineComponent({
|
||||
style: { marginLeft: '10px' }
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user