add
This commit is contained in:
@@ -18,6 +18,15 @@ export function getTypeValue(type: string | number) {
|
||||
});
|
||||
}
|
||||
|
||||
// 批量获取字典类型值
|
||||
export function getDictsByTypes(types: string[]) {
|
||||
return request({
|
||||
url: '/admin/dict/item/typeList',
|
||||
method: 'post',
|
||||
data: types,
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchList(query: any) {
|
||||
return request({
|
||||
url: '/admin/dict/list',
|
||||
|
||||
@@ -87,6 +87,18 @@ export const putObj = (obj: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核
|
||||
* @param obj
|
||||
*/
|
||||
export const examObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacherpaper/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新状态
|
||||
* @param obj
|
||||
|
||||
@@ -87,6 +87,18 @@ export const putObj = (obj: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核
|
||||
* @param obj
|
||||
*/
|
||||
export const examObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteachingmaterial/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新状态
|
||||
* @param obj
|
||||
|
||||
@@ -75,6 +75,18 @@ export const putObj = (obj: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核
|
||||
* @param obj
|
||||
*/
|
||||
export const examObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionaltopiclist/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新状态
|
||||
* @param obj
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalpartychange',
|
||||
url: '/professional/professionalpartychange/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,8 +47,11 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalpartychange/${id}`,
|
||||
url: `/professional/professionalpartychange/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,8 +61,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalpartychange/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalpartychange/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,8 +75,8 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalpartychange',
|
||||
method: 'put',
|
||||
url: '/professional/professionalpartychange/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalpoliticsstatus',
|
||||
url: '/professional/professionalpoliticsstatus/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,7 +47,7 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const addPoliticssStatus = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalpoliticsstatus',
|
||||
url: '/professional/professionalpoliticsstatus/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -59,8 +59,11 @@ export const addPoliticssStatus = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalpoliticsstatus/${id}`,
|
||||
url: `/professional/professionalpoliticsstatus/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -70,8 +73,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalpoliticsstatus/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalpoliticsstatus/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -81,8 +87,11 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const dePoObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalpoliticsstatus/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalpoliticsstatus/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,8 +101,8 @@ export const dePoObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalpoliticsstatus',
|
||||
method: 'put',
|
||||
url: '/professional/professionalpoliticsstatus/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
@@ -35,7 +35,7 @@ export function fetchList(query?: any) {
|
||||
*/
|
||||
export function addObj(obj?: any) {
|
||||
return request({
|
||||
url: '/professional/professionalqualificationrelation',
|
||||
url: '/professional/professionalqualificationrelation/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,7 +47,7 @@ export function addObj(obj?: any) {
|
||||
*/
|
||||
export function addQuaRelation(obj?: any) {
|
||||
return request({
|
||||
url: '/professional/professionalqualificationrelation',
|
||||
url: '/professional/professionalqualificationrelation/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -59,8 +59,11 @@ export function addQuaRelation(obj?: any) {
|
||||
*/
|
||||
export function getObj(id: string | number) {
|
||||
return request({
|
||||
url: '/professional/professionalqualificationrelation/' + id,
|
||||
url: '/professional/professionalqualificationrelation/getById',
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,8 +73,11 @@ export function getObj(id: string | number) {
|
||||
*/
|
||||
export function delObj(id: string | number) {
|
||||
return request({
|
||||
url: '/professional/professionalqualificationrelation/' + id,
|
||||
method: 'delete',
|
||||
url: '/professional/professionalqualificationrelation/deleteById',
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,8 +87,11 @@ export function delObj(id: string | number) {
|
||||
*/
|
||||
export function delQuaObj(id: string | number) {
|
||||
return request({
|
||||
url: '/professional/professionalqualificationrelation/' + id,
|
||||
method: 'delete',
|
||||
url: '/professional/professionalqualificationrelation/deleteById',
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,8 +101,20 @@ export function delQuaObj(id: string | number) {
|
||||
*/
|
||||
export function putObj(obj?: any) {
|
||||
return request({
|
||||
url: '/professional/professionalqualificationrelation',
|
||||
method: 'put',
|
||||
url: '/professional/professionalqualificationrelation/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核对象
|
||||
* @param obj 对象数据
|
||||
*/
|
||||
export function examObj(obj?: any) {
|
||||
return request({
|
||||
url: '/professional/professionalqualificationrelation/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalsocial',
|
||||
url: '/professional/professionalsocial/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,7 +47,7 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const addSocialObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalsocial',
|
||||
url: '/professional/professionalsocial/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -59,8 +59,11 @@ export const addSocialObj = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalsocial/${id}`,
|
||||
url: `/professional/professionalsocial/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -70,8 +73,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalsocial/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalsocial/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -81,8 +87,11 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const delSocialObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalsocial/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalsocial/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,8 +101,8 @@ export const delSocialObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalsocial',
|
||||
method: 'put',
|
||||
url: '/professional/professionalsocial/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacheracademicrelation',
|
||||
url: '/professional/professionalteacheracademicrelation/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,7 +47,7 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const addAcadeRelation = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacheracademicrelation',
|
||||
url: '/professional/professionalteacheracademicrelation/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -59,8 +59,11 @@ export const addAcadeRelation = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteacheracademicrelation/${id}`,
|
||||
url: `/professional/professionalteacheracademicrelation/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -70,8 +73,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteacheracademicrelation/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalteacheracademicrelation/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -81,8 +87,11 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const delEduObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteacheracademicrelation/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalteacheracademicrelation/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,8 +101,20 @@ export const delEduObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacheracademicrelation',
|
||||
method: 'put',
|
||||
url: '/professional/professionalteacheracademicrelation/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核
|
||||
* @param obj
|
||||
*/
|
||||
export const examObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacheracademicrelation/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteachercertificaterelation',
|
||||
url: '/professional/professionalteachercertificaterelation/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,8 +47,11 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteachercertificaterelation/${id}`,
|
||||
url: `/professional/professionalteachercertificaterelation/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,8 +61,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteachercertificaterelation/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalteachercertificaterelation/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,8 +75,20 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteachercertificaterelation',
|
||||
method: 'put',
|
||||
url: '/professional/professionalteachercertificaterelation/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核
|
||||
* @param obj
|
||||
*/
|
||||
export const examObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteachercertificaterelation/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacherhonor',
|
||||
url: '/professional/professionalteacherhonor/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,8 +47,11 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteacherhonor/${id}`,
|
||||
url: `/professional/professionalteacherhonor/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,8 +61,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteacherhonor/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalteacherhonor/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,8 +75,20 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacherhonor',
|
||||
method: 'put',
|
||||
url: '/professional/professionalteacherhonor/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核
|
||||
* @param obj
|
||||
*/
|
||||
export const examObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacherhonor/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacherstationchange',
|
||||
url: '/professional/professionalteacherstationchange/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,8 +47,11 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteacherstationchange/${id}`,
|
||||
url: `/professional/professionalteacherstationchange/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,8 +61,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionalteacherstationchange/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionalteacherstationchange/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,8 +75,8 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionalteacherstationchange',
|
||||
method: 'put',
|
||||
url: '/professional/professionalteacherstationchange/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionaltitlerelation',
|
||||
url: '/professional/professionaltitlerelation/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,7 +47,7 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const addTitleRelationObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionaltitlerelation',
|
||||
url: '/professional/professionaltitlerelation/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -59,8 +59,11 @@ export const addTitleRelationObj = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionaltitlerelation/${id}`,
|
||||
url: `/professional/professionaltitlerelation/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -70,8 +73,22 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionaltitlerelation/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionaltitlerelation/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**审核
|
||||
* @param obj
|
||||
*/
|
||||
export const examObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionaltitlerelation/exam',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -81,8 +98,11 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const delTitleObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/professionaltitlerelation/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/professionaltitlerelation/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,8 +112,8 @@ export const delTitleObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/professionaltitlerelation',
|
||||
method: 'put',
|
||||
url: '/professional/professionaltitlerelation/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const fetchList = (query?: any) => {
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/teacherbase',
|
||||
url: '/professional/teacherbase/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
@@ -47,8 +47,11 @@ export const addObj = (obj: any) => {
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/teacherbase/${id}`,
|
||||
url: `/professional/teacherbase/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,8 +61,11 @@ export const getObj = (id: string | number) => {
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/teacherbase/${id}`,
|
||||
method: 'delete',
|
||||
url: `/professional/teacherbase/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,8 +75,8 @@ export const delObj = (id: string | number) => {
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/teacherbase',
|
||||
method: 'put',
|
||||
url: '/professional/teacherbase/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
@@ -336,3 +342,4 @@ export const search = (data: string | number) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
95
src/api/professional/stayschool/outercompany.ts
Normal file
95
src/api/professional/stayschool/outercompany.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, cyweb All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
*/
|
||||
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompany/page',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param obj
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompany/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/outercompany/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param id
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/outercompany/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param obj
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompany/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取列表(不分页)
|
||||
* @param query
|
||||
*/
|
||||
export const getList = (query?: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompany/getList',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
};
|
||||
|
||||
131
src/api/professional/stayschool/outercompanyemployee.ts
Normal file
131
src/api/professional/stayschool/outercompanyemployee.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, cyweb All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
*/
|
||||
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取列表
|
||||
* @param query
|
||||
*/
|
||||
export const fetchList = (query?: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompanyemployee/page',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param obj
|
||||
*/
|
||||
export const addObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompanyemployee/add',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存第二步
|
||||
* @param obj
|
||||
*/
|
||||
export const saveSecond = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompanyemployee/saveSecond',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
*/
|
||||
export const getObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/outercompanyemployee/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param id
|
||||
*/
|
||||
export const delObj = (id: string | number) => {
|
||||
return request({
|
||||
url: `/professional/outercompanyemployee/deleteById`,
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param obj
|
||||
*/
|
||||
export const putObj = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompanyemployee/edit',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param obj
|
||||
*/
|
||||
export const batchDel = (obj: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompanyemployee/batchDel',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
* @param data
|
||||
*/
|
||||
export const resetPassWord = (data: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompanyemployee/resetPassWord',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 远程模糊检索
|
||||
* @param params
|
||||
*/
|
||||
export const remoteInfo = (params?: any) => {
|
||||
return request({
|
||||
url: '/professional/outercompanyemployee/remoteInfo',
|
||||
method: 'get',
|
||||
params: params,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<el-tag
|
||||
:effect="currentOption.effect"
|
||||
v-if="currentOption"
|
||||
:type="currentOption.type"
|
||||
:class="{ 'audit-state-tag': showIcon && currentOption.icon }"
|
||||
@@ -18,6 +19,7 @@ export interface StateOption {
|
||||
label: string;
|
||||
type: 'success' | 'danger' | 'warning' | 'info' | '';
|
||||
icon?: string;
|
||||
effect?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -30,9 +32,9 @@ interface Props {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
state: '',
|
||||
options: () => [
|
||||
{ value: '1', label: '通过', type: 'success', icon: 'fa-solid fa-circle-check' },
|
||||
{ value: '-2', label: '驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark' },
|
||||
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock' }
|
||||
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check' , effect:"dark" },
|
||||
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect:"dark" },
|
||||
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock' ,effect:"light" }
|
||||
],
|
||||
showIcon: true,
|
||||
emptyText: '-'
|
||||
|
||||
114
src/components/GenderTag/index.vue
Normal file
114
src/components/GenderTag/index.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<el-tag
|
||||
v-if="showTag"
|
||||
:type="type"
|
||||
:effect="effect"
|
||||
>
|
||||
<span class="gender-tag">
|
||||
<el-icon>
|
||||
<Male v-if="isMale" />
|
||||
<Female v-else-if="isFemale" />
|
||||
</el-icon>
|
||||
<span class="gender-label">{{ label }}</span>
|
||||
</span>
|
||||
</el-tag>
|
||||
<span v-else class="gender-tag" :class="genderClass">
|
||||
<el-icon>
|
||||
<Male v-if="isMale" />
|
||||
<Female v-else-if="isFemale" />
|
||||
</el-icon>
|
||||
<span class="gender-label">{{ label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Male, Female } from '@element-plus/icons-vue'
|
||||
|
||||
interface Props {
|
||||
sex?: string | number; // 性别值:1=男,0=女
|
||||
showTag?: boolean; // 是否显示标签样式(有边框和背景),默认为 false
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
sex: '',
|
||||
showTag: false
|
||||
})
|
||||
|
||||
// 判断是否为男性(1=男)
|
||||
const isMale = computed(() => {
|
||||
const sex = String(props.sex)
|
||||
const sexNum = Number(props.sex)
|
||||
return sexNum === 1 || sex === '1'
|
||||
})
|
||||
|
||||
// 判断是否为女性(0=女)
|
||||
const isFemale = computed(() => {
|
||||
const sex = String(props.sex)
|
||||
const sexNum = Number(props.sex)
|
||||
return sexNum === 0 || sex === '0'
|
||||
})
|
||||
|
||||
// 根据性别计算显示内容
|
||||
const label = computed(() => {
|
||||
if (isMale.value) {
|
||||
return '男'
|
||||
} else if (isFemale.value) {
|
||||
return '女'
|
||||
}
|
||||
return '-'
|
||||
})
|
||||
|
||||
const type = computed(() => {
|
||||
if (isMale.value) {
|
||||
return 'primary' // 蓝色
|
||||
} else if (isFemale.value) {
|
||||
return 'danger' // 红色/粉色
|
||||
}
|
||||
return 'info' // 灰色
|
||||
})
|
||||
|
||||
const effect = computed(() => {
|
||||
if (isMale.value || isFemale.value) {
|
||||
return 'light'
|
||||
}
|
||||
return 'plain'
|
||||
})
|
||||
|
||||
const genderClass = computed(() => {
|
||||
if (isMale.value) {
|
||||
return 'gender-male'
|
||||
} else if (isFemale.value) {
|
||||
return 'gender-female'
|
||||
}
|
||||
return 'gender-unknown'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gender-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gender-tag .el-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.gender-male {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.gender-female {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.gender-unknown {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="search-form-container">
|
||||
<el-form :model="formModel" ref="formRef" :inline="true" @keyup.enter="handleKeyupEnter" class="search-form-inline">
|
||||
<el-form :model="formModel" ref="formRef" :inline="true" @keyup.enter="handleKeyupEnter">
|
||||
<!-- 直接展示的表单项 -->
|
||||
<slot :visible="true" :expanded="isExpanded"></slot>
|
||||
|
||||
@@ -154,8 +154,9 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-form-container {
|
||||
|
||||
:deep(.search-form-inline) {
|
||||
// 直接使用全局样式 el-form--inline,只需要覆盖特殊样式
|
||||
|
||||
:deep(.el-form--inline) {
|
||||
.collapse-trigger-item {
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
@@ -165,6 +166,18 @@ defineExpose({
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// 如果需要自定义宽度,可以覆盖全局样式(默认使用全局的 240px)
|
||||
// 如果需要改为 200px,取消下面的注释
|
||||
// .el-form-item {
|
||||
// & > .el-input,
|
||||
// .el-cascader,
|
||||
// .el-select,
|
||||
// .el-date-editor,
|
||||
// .el-autocomplete {
|
||||
// width: 200px;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// 可折叠内容区域 - 使用 contents 让包装器不影响布局
|
||||
|
||||
149
src/components/StatusTag/README.md
Normal file
149
src/components/StatusTag/README.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# StatusTag 组件
|
||||
|
||||
状态标签组件,用于显示状态值对应的标签文本,支持自定义样式和颜色。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持标签样式和纯文本样式两种显示模式
|
||||
- ✅ 支持自定义类型映射(typeMap)和颜色映射(colorMap)
|
||||
- ✅ 内置默认样式('1' → warning/dark,'0' → primary/light)
|
||||
- ✅ 外部传入优先,支持覆盖默认样式
|
||||
|
||||
## Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 必填 |
|
||||
|------|------|------|--------|------|
|
||||
| value | 当前状态值 | `string \| number` | `''` | 是 |
|
||||
| options | 选项列表,格式:`[{label: '是', value: '1'}, {label: '否', value: '0'}]` | `Option[]` | `[]` | 是 |
|
||||
| showTag | 是否显示标签样式(有边框和背景),`false` 为纯文本样式 | `boolean` | `true` | 否 |
|
||||
| typeMap | 自定义类型映射,用于标签模式,如:`{'1': {type: 'warning', effect: 'dark'}}` | `Record<string \| number, { type: string; effect?: string }>` | `{}` | 否 |
|
||||
| colorMap | 自定义颜色映射,用于纯文本模式,如:`{'1': '#E6A23C'}` | `Record<string \| number, string>` | `{}` | 否 |
|
||||
|
||||
### Option 接口
|
||||
|
||||
```typescript
|
||||
interface Option {
|
||||
label: string // 显示文本
|
||||
value: string | number // 选项值
|
||||
}
|
||||
```
|
||||
|
||||
## 默认样式
|
||||
|
||||
组件内置了默认的样式映射,无需传入 `typeMap` 或 `colorMap` 即可使用:
|
||||
|
||||
- **值 '1' 或 1**:
|
||||
- 标签模式:`warning` 类型 + `dark` 效果(橙色深色)
|
||||
- 纯文本模式:`var(--el-color-warning)`(橙色)
|
||||
|
||||
- **值 '0' 或 0**:
|
||||
- 标签模式:`primary` 类型 + `light` 效果(蓝色浅色)
|
||||
- 纯文本模式:`var(--el-color-primary)`(蓝色)
|
||||
|
||||
- **其他值**:
|
||||
- 标签模式:`info` 类型 + `light` 效果(灰色)
|
||||
- 纯文本模式:默认文本颜色
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<StatusTag
|
||||
:value="scope.row.tied"
|
||||
:options="YES_OR_NO"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import StatusTag from '/@/components/StatusTag/index.vue'
|
||||
|
||||
const YES_OR_NO = [
|
||||
{ label: '是', value: '1' },
|
||||
{ label: '否', value: '0' }
|
||||
]
|
||||
</script>
|
||||
```
|
||||
|
||||
### 纯文本模式(无边框和背景)
|
||||
|
||||
```vue
|
||||
<StatusTag
|
||||
:value="scope.row.tied"
|
||||
:options="YES_OR_NO"
|
||||
:show-tag="false"
|
||||
/>
|
||||
```
|
||||
|
||||
### 自定义类型映射
|
||||
|
||||
```vue
|
||||
<StatusTag
|
||||
:value="scope.row.status"
|
||||
:options="statusOptions"
|
||||
:type-map="{
|
||||
'1': { type: 'success', effect: 'dark' },
|
||||
'0': { type: 'danger', effect: 'light' }
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
### 自定义颜色映射(纯文本模式)
|
||||
|
||||
```vue
|
||||
<StatusTag
|
||||
:value="scope.row.status"
|
||||
:options="statusOptions"
|
||||
:show-tag="false"
|
||||
:color-map="{
|
||||
'1': '#67C23A',
|
||||
'0': '#F56C6C'
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="是否退休" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<StatusTag
|
||||
:value="scope.row.tied"
|
||||
:options="YES_OR_NO"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import global from '/@/components/tools/commondict.vue'
|
||||
|
||||
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
|
||||
const YES_OR_NO = global.YES_OR_NO
|
||||
</script>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **必须传入 `options`**:组件不提供默认选项,必须通过 `options` prop 传入选项列表
|
||||
2. **值匹配**:组件会自动匹配字符串和数字类型的值(如 `'1'` 和 `1` 会被视为相同)
|
||||
3. **样式优先级**:外部传入的 `typeMap` 和 `colorMap` 会覆盖默认样式
|
||||
4. **未匹配值**:如果 `value` 在 `options` 中找不到对应项,会显示 `'-'`
|
||||
|
||||
## 样式说明
|
||||
|
||||
### 标签模式(showTag: true)
|
||||
|
||||
使用 Element Plus 的 `el-tag` 组件,支持所有 `el-tag` 的类型和效果:
|
||||
- `type`: `success` | `info` | `warning` | `danger` | `primary`
|
||||
- `effect`: `dark` | `light` | `plain`
|
||||
|
||||
### 纯文本模式(showTag: false)
|
||||
|
||||
使用纯文本显示,通过 CSS 颜色控制样式,支持任何颜色值(CSS 变量、十六进制、RGB 等)。
|
||||
|
||||
140
src/components/StatusTag/index.vue
Normal file
140
src/components/StatusTag/index.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<el-tag
|
||||
v-if="showTag"
|
||||
:type="tagType"
|
||||
:effect="tagEffect"
|
||||
>
|
||||
{{ label }}
|
||||
</el-tag>
|
||||
<span v-else class="status-tag" :class="statusClass" :style="statusStyle">
|
||||
{{ label }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Option {
|
||||
label: string
|
||||
value: string | number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value?: string | number // 当前值
|
||||
options?: Option[] // 选项列表,格式:[{label: '是', value: '1'}, {label: '否', value: '0'}]
|
||||
showTag?: boolean // 是否显示标签样式(有边框和背景),默认为 true
|
||||
typeMap?: Record<string | number, { type: string; effect?: string }> // 自定义类型映射,如 {'1': {type: 'warning', effect: 'dark'}}
|
||||
colorMap?: Record<string | number, string> // 纯文本模式下的颜色映射,如 {'1': '#E6A23C'}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: '',
|
||||
options: () => [],
|
||||
showTag: true,
|
||||
typeMap: () => ({}),
|
||||
colorMap: () => ({})
|
||||
})
|
||||
|
||||
// 默认的类型映射(只使用字符串键)
|
||||
const defaultTypeMap: Record<string, { type: string; effect: string }> = {
|
||||
'1': { type: 'warning', effect: 'dark' },
|
||||
'0': { type: 'primary', effect: 'light' }
|
||||
}
|
||||
|
||||
// 默认的颜色映射(只使用字符串键)
|
||||
const defaultColorMap: Record<string, string> = {
|
||||
'1': 'var(--el-color-warning)',
|
||||
'0': 'var(--el-color-primary)'
|
||||
}
|
||||
|
||||
// 获取值的字符串形式(用于查找映射)
|
||||
const getValueKey = (value: string | number): string => {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// 合并后的类型映射(外部传入优先,否则使用默认)
|
||||
const mergedTypeMap = computed(() => {
|
||||
// 将外部传入的 typeMap 也转换为字符串键
|
||||
const externalTypeMap: Record<string, { type: string; effect?: string }> = {}
|
||||
Object.keys(props.typeMap).forEach(key => {
|
||||
externalTypeMap[String(key)] = props.typeMap[key]
|
||||
})
|
||||
return { ...defaultTypeMap, ...externalTypeMap }
|
||||
})
|
||||
|
||||
// 合并后的颜色映射(外部传入优先,否则使用默认)
|
||||
const mergedColorMap = computed(() => {
|
||||
// 将外部传入的 colorMap 也转换为字符串键
|
||||
const externalColorMap: Record<string, string> = {}
|
||||
Object.keys(props.colorMap).forEach(key => {
|
||||
externalColorMap[String(key)] = props.colorMap[key]
|
||||
})
|
||||
return { ...defaultColorMap, ...externalColorMap }
|
||||
})
|
||||
|
||||
// 合并后的选项列表(必须通过外部传入 options)
|
||||
const mergedOptions = computed(() => {
|
||||
// 必须传入 options,否则返回空数组
|
||||
return props.options && props.options.length > 0 ? props.options : []
|
||||
})
|
||||
|
||||
// 根据值找到对应的选项
|
||||
const currentOption = computed(() => {
|
||||
return mergedOptions.value.find((opt: Option) => {
|
||||
const optValue = String(opt.value)
|
||||
const propValue = String(props.value)
|
||||
return optValue === propValue || Number(opt.value) === Number(props.value)
|
||||
})
|
||||
})
|
||||
|
||||
// 显示标签
|
||||
const label = computed(() => {
|
||||
return currentOption.value?.label || '-'
|
||||
})
|
||||
|
||||
// 标签类型(showTag 为 true 时使用)
|
||||
const tagType = computed(() => {
|
||||
const valueKey = getValueKey(props.value)
|
||||
if (mergedTypeMap.value[valueKey]) {
|
||||
return mergedTypeMap.value[valueKey].type
|
||||
}
|
||||
return 'info'
|
||||
})
|
||||
|
||||
// 标签效果(showTag 为 true 时使用)
|
||||
const tagEffect = computed(() => {
|
||||
const valueKey = getValueKey(props.value)
|
||||
if (mergedTypeMap.value[valueKey]?.effect) {
|
||||
return mergedTypeMap.value[valueKey].effect
|
||||
}
|
||||
return 'light'
|
||||
})
|
||||
|
||||
// 纯文本模式下的样式类
|
||||
const statusClass = computed(() => {
|
||||
if (props.colorMap[props.value]) {
|
||||
return ''
|
||||
}
|
||||
return 'status-default'
|
||||
})
|
||||
|
||||
// 纯文本模式下的内联样式
|
||||
const statusStyle = computed(() => {
|
||||
const valueKey = getValueKey(props.value)
|
||||
if (mergedColorMap.value[valueKey]) {
|
||||
return { color: mergedColorMap.value[valueKey] }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-default {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
</style>
|
||||
|
||||
17
src/components/TableColumn/Provider.vue
Normal file
17
src/components/TableColumn/Provider.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { provide } from 'vue'
|
||||
|
||||
interface Props {
|
||||
isColumnVisible: (propOrLabel: string) => boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 提供 isColumnVisible 函数给子组件
|
||||
provide('isColumnVisible', props.isColumnVisible)
|
||||
</script>
|
||||
|
||||
76
src/components/TableColumn/README.md
Normal file
76
src/components/TableColumn/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# TableColumn 组件
|
||||
|
||||
一个自动处理列显示/隐藏的 `el-table-column` 包装组件。
|
||||
|
||||
## 功能
|
||||
|
||||
- 自动根据 `isColumnVisible` 函数控制列的显示/隐藏
|
||||
- 完全兼容 `el-table-column` 的所有属性和插槽
|
||||
- 无需在每个列上手动添加 `v-if="isColumnVisible('xxx')"`
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 在父组件中提供 `isColumnVisible` 函数
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table ref="tableRef">
|
||||
<TableColumnProvider :is-column-visible="isColumnVisible">
|
||||
<TableColumn prop="name" label="姓名" width="120" />
|
||||
<TableColumn prop="age" label="年龄" width="80" />
|
||||
</TableColumnProvider>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { provide } from 'vue'
|
||||
import TableColumnProvider from '/@/components/TableColumn/Provider.vue'
|
||||
import TableColumn from '/@/components/TableColumn/index.vue'
|
||||
|
||||
const isColumnVisible = (propOrLabel: string) => {
|
||||
// 你的列显示逻辑
|
||||
return true
|
||||
}
|
||||
|
||||
provide('isColumnVisible', isColumnVisible)
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 直接使用(如果已经在父组件中 provide)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table>
|
||||
<TableColumn prop="name" label="姓名" width="120" />
|
||||
<TableColumn prop="age" label="年龄" width="80" />
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TableColumn from '/@/components/TableColumn/index.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
继承 `el-table-column` 的所有属性,包括:
|
||||
- `prop`: 列的字段名
|
||||
- `label`: 列的标题
|
||||
- `width`: 列宽度
|
||||
- `min-width`: 最小宽度
|
||||
- `fixed`: 是否固定
|
||||
- 等等...
|
||||
|
||||
## Slots
|
||||
|
||||
继承 `el-table-column` 的所有插槽,包括:
|
||||
- `default`: 默认插槽
|
||||
- `header`: 表头插槽
|
||||
- 等等...
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 需要在父组件中使用 `provide` 提供 `isColumnVisible` 函数,或者使用 `TableColumnProvider` 组件
|
||||
2. `isColumnVisible` 函数接收 `prop` 或 `label` 作为参数
|
||||
3. 如果既没有 `prop` 也没有 `label`,列将始终显示
|
||||
|
||||
44
src/components/TableColumn/index.vue
Normal file
44
src/components/TableColumn/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<el-table-column
|
||||
v-if="shouldShow"
|
||||
:prop="prop"
|
||||
:label="label"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
||||
<slot :name="name" v-bind="slotProps" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
interface Props {
|
||||
prop?: string
|
||||
label?: string
|
||||
// 其他 el-table-column 的所有属性都通过 $attrs 传递
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
prop: '',
|
||||
label: ''
|
||||
})
|
||||
|
||||
// 从父组件注入 isColumnVisible 函数
|
||||
const isColumnVisible = inject<(propOrLabel: string) => boolean>('isColumnVisible', () => true)
|
||||
|
||||
// 计算是否应该显示该列
|
||||
const shouldShow = computed(() => {
|
||||
// 优先使用 prop,如果没有 prop 则使用 label
|
||||
const key = props.prop || props.label || ''
|
||||
if (!key) {
|
||||
// 如果没有 prop 和 label,默认显示(可能是序号列等特殊列)
|
||||
return true
|
||||
}
|
||||
|
||||
// isColumnVisible 函数会同时匹配 prop 和 label,所以直接传递即可
|
||||
return isColumnVisible(key)
|
||||
})
|
||||
</script>
|
||||
|
||||
162
src/components/TableColumnControl/README.md
Normal file
162
src/components/TableColumnControl/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# TableColumnControl 表格列显隐控制组件
|
||||
|
||||
一个通用的表格列显示/隐藏控制组件,支持动态控制表格列的显示状态。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 动态控制表格列的显示/隐藏
|
||||
- ✅ 支持全选/全不选
|
||||
- ✅ 支持重置为默认值
|
||||
- ✅ 支持 localStorage 持久化
|
||||
- ✅ 支持固定列(不可隐藏)
|
||||
- ✅ 支持始终显示的列
|
||||
- ✅ 可自定义触发按钮样式
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 表格列控制按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
@change="handleColumnChange"
|
||||
/>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
v-for="col in visibleTableColumns"
|
||||
:key="col.prop"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名', width: 120 },
|
||||
{ prop: 'age', label: '年龄', width: 80 },
|
||||
{ prop: 'email', label: '邮箱', width: 200 },
|
||||
{ prop: 'address', label: '地址', width: 300 }
|
||||
]
|
||||
|
||||
const visibleColumns = ref<string[]>(['name', 'age', 'email', 'address'])
|
||||
|
||||
// 根据 visibleColumns 过滤出需要显示的列
|
||||
const visibleTableColumns = computed(() => {
|
||||
return tableColumns.filter(col =>
|
||||
visibleColumns.value.includes(col.prop || col.label)
|
||||
)
|
||||
})
|
||||
|
||||
const handleColumnChange = (columns: string[]) => {
|
||||
console.log('显示的列:', columns)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 使用 localStorage 持久化
|
||||
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
storage-key="my-table-columns"
|
||||
/>
|
||||
```
|
||||
|
||||
### 固定列(不可隐藏)
|
||||
|
||||
```vue
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名', fixed: 'left' }, // 固定左侧,不可隐藏
|
||||
{ prop: 'age', label: '年龄' },
|
||||
{ prop: 'action', label: '操作', fixed: 'right' } // 固定右侧,不可隐藏
|
||||
]
|
||||
```
|
||||
|
||||
### 始终显示的列
|
||||
|
||||
```vue
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名', alwaysShow: true }, // 始终显示,不可隐藏
|
||||
{ prop: 'age', label: '年龄' }
|
||||
]
|
||||
```
|
||||
|
||||
### 自定义触发按钮
|
||||
|
||||
```vue
|
||||
<!-- 使用图标按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-type="default"
|
||||
trigger-circle
|
||||
/>
|
||||
|
||||
<!-- 使用文字按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-text="列设置"
|
||||
trigger-type="primary"
|
||||
/>
|
||||
|
||||
<!-- 使用链接按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-link
|
||||
trigger-text="自定义列"
|
||||
/>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|------|------|------|--------|
|
||||
| columns | 表格列配置数组 | Column[] | 必填 |
|
||||
| modelValue | 当前显示的列的 prop 或 label 数组 | string[] | - |
|
||||
| storageKey | localStorage 存储的 key,用于持久化 | string | - |
|
||||
| triggerType | 触发按钮的类型 | 'default' \| 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info' \| 'text' | 'default' |
|
||||
| triggerSize | 触发按钮的大小 | 'large' \| 'default' \| 'small' | 'default' |
|
||||
| triggerCircle | 触发按钮是否为圆形 | boolean | false |
|
||||
| triggerText | 触发按钮的文字 | string | '' |
|
||||
| triggerLink | 触发按钮是否为链接样式 | boolean | false |
|
||||
|
||||
## Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|--------|------|----------|
|
||||
| update:modelValue | 显示的列变化时触发 | (columns: string[]) |
|
||||
| change | 显示的列变化时触发 | (columns: string[]) |
|
||||
|
||||
## Column 接口
|
||||
|
||||
```typescript
|
||||
interface Column {
|
||||
prop?: string // 列的 prop,用于标识列
|
||||
label: string // 列的标签
|
||||
fixed?: boolean | 'left' | 'right' // 固定列,不可隐藏
|
||||
alwaysShow?: boolean // 始终显示的列,不可隐藏
|
||||
[key: string]: any // 其他属性
|
||||
}
|
||||
```
|
||||
|
||||
## 插槽
|
||||
|
||||
| 插槽名 | 说明 |
|
||||
|--------|------|
|
||||
| trigger | 自定义触发按钮内容 |
|
||||
|
||||
196
src/components/TableColumnControl/USAGE.md
Normal file
196
src/components/TableColumnControl/USAGE.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# TableColumnControl 使用指南
|
||||
|
||||
## 两种使用方式
|
||||
|
||||
### 方式一:自动提取(推荐)✨
|
||||
|
||||
自动从 `el-table` 中提取列配置,无需手动配置。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 列设置按钮 -->
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
trigger-text="列设置"
|
||||
:auto-extract-options="{
|
||||
alwaysShow: ['name', 'action'], // 始终显示的列(prop 或 label)
|
||||
defaultHidden: ['remark'], // 默认隐藏的列
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table ref="tableRef" :data="tableData">
|
||||
<el-table-column prop="name" label="姓名" width="120" />
|
||||
<el-table-column prop="age" label="年龄" width="80" />
|
||||
<el-table-column prop="email" label="邮箱" width="200" />
|
||||
<el-table-column prop="remark" label="备注" width="300" />
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button @click="handleEdit(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
|
||||
const tableRef = ref<TableInstance>()
|
||||
const visibleTableColumns = ref<string[]>([])
|
||||
const tableData = ref([...])
|
||||
</script>
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- ✅ 无需手动配置列信息
|
||||
- ✅ 自动同步表格列的变化
|
||||
- ✅ 代码更简洁
|
||||
|
||||
### 方式二:手动配置
|
||||
|
||||
手动传入列配置,适合需要自定义列信息的场景。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 列设置按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumnConfig"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
trigger-text="列设置"
|
||||
/>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('name')"
|
||||
prop="name"
|
||||
label="姓名"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('age')"
|
||||
prop="age"
|
||||
label="年龄"
|
||||
width="80"
|
||||
/>
|
||||
<!-- ... 其他列 ... -->
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
|
||||
const tableColumnConfig = [
|
||||
{ prop: 'name', label: '姓名', width: 120 },
|
||||
{ prop: 'age', label: '年龄', width: 80 },
|
||||
{ prop: 'email', label: '邮箱', width: 200 },
|
||||
{ prop: 'action', label: '操作', width: 150, fixed: 'right', alwaysShow: true }
|
||||
]
|
||||
|
||||
const visibleTableColumns = ref<string[]>([])
|
||||
const tableData = ref([...])
|
||||
|
||||
// 判断列是否显示
|
||||
const isColumnVisible = (propOrLabel: string) => {
|
||||
const column = tableColumnConfig.find(col => (col.prop || col.label) === propOrLabel)
|
||||
if (column && (column.fixed !== undefined || column.alwaysShow)) {
|
||||
return true
|
||||
}
|
||||
return visibleTableColumns.value.includes(propOrLabel)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 自动提取的配置选项
|
||||
|
||||
```typescript
|
||||
interface AutoExtractOptions {
|
||||
// 默认隐藏的列(prop 或 label)
|
||||
defaultHidden?: string[]
|
||||
|
||||
// 始终显示的列(prop 或 label)
|
||||
alwaysShow?: string[]
|
||||
|
||||
// 列配置映射(用于自定义列的显示名称等)
|
||||
columnMap?: Record<string, Partial<ColumnConfig>>
|
||||
}
|
||||
```
|
||||
|
||||
### 示例
|
||||
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
:auto-extract-options="{
|
||||
// 默认隐藏备注列
|
||||
defaultHidden: ['remark', 'description'],
|
||||
|
||||
// 始终显示姓名和操作列
|
||||
alwaysShow: ['name', 'action'],
|
||||
|
||||
// 自定义列的显示名称
|
||||
columnMap: {
|
||||
'email': { label: '电子邮箱' },
|
||||
'phone': { label: '联系电话' }
|
||||
}
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **自动提取的限制**:
|
||||
- 需要在表格渲染完成后才能提取列配置
|
||||
- 如果表格列是动态生成的,可能需要调用 `refreshColumns()` 方法
|
||||
|
||||
2. **固定列**:
|
||||
- 使用 `fixed="left"` 或 `fixed="right"` 的列会自动标记为不可隐藏
|
||||
- 在 `alwaysShow` 中指定的列也会不可隐藏
|
||||
|
||||
3. **存储键(storageKey)**:
|
||||
- 建议为每个页面使用唯一的 `storageKey`,避免列配置冲突
|
||||
- 格式建议:`页面名称-table-columns`,如 `user-list-table-columns`
|
||||
|
||||
4. **性能考虑**:
|
||||
- 自动提取会在组件挂载和表格更新时执行
|
||||
- 对于大型表格,建议使用手动配置以获得更好的性能
|
||||
|
||||
## 迁移指南
|
||||
|
||||
从手动配置迁移到自动提取:
|
||||
|
||||
**之前(手动配置):**
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:columns="tableColumnConfig"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
/>
|
||||
```
|
||||
|
||||
**之后(自动提取):**
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
/>
|
||||
```
|
||||
|
||||
只需要:
|
||||
1. 将 `:columns` 改为 `:table-ref="tableRef"`
|
||||
2. 在 `el-table` 上添加 `ref="tableRef"`
|
||||
3. 移除 `tableColumnConfig` 和 `isColumnVisible` 函数(如果不再需要)
|
||||
|
||||
438
src/components/TableColumnControl/index.vue
Normal file
438
src/components/TableColumnControl/index.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<div class="table-column-control">
|
||||
<el-button
|
||||
:type="triggerType"
|
||||
:icon="slots.trigger ? undefined : Menu"
|
||||
:size="triggerSize"
|
||||
:circle="triggerCircle"
|
||||
:link="triggerLink"
|
||||
@click="visible = true"
|
||||
>
|
||||
<slot name="trigger">
|
||||
{{ triggerText || '列显隐' }}
|
||||
</slot>
|
||||
</el-button>
|
||||
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="列显隐设置"
|
||||
:width="dialogWidth"
|
||||
append-to-body
|
||||
>
|
||||
<div class="column-control-content">
|
||||
<div class="column-control-body">
|
||||
<el-checkbox-group v-model="checkedColumns" @change="handleColumnChange" class="column-checkbox-group">
|
||||
<div
|
||||
v-for="column in actualColumns"
|
||||
:key="column.prop || column.label"
|
||||
class="column-item"
|
||||
>
|
||||
<el-checkbox
|
||||
:label="column.prop || column.label"
|
||||
:disabled="!!column.fixed || column.alwaysShow"
|
||||
>
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
<!-- <el-tag v-if="column.fixed !== undefined" size="small" type="info">
|
||||
{{ column.fixed === 'left' ? '固定左侧' : column.fixed === 'right' ? '固定右侧' : '固定' }}
|
||||
</el-tag> -->
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, nextTick, useSlots, type Ref } from 'vue'
|
||||
import { Menu } from '@element-plus/icons-vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
import { useTableColumns, type ColumnConfig } from '/@/composables/useTableColumns'
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
interface Column {
|
||||
prop?: string
|
||||
label: string
|
||||
fixed?: boolean | 'left' | 'right'
|
||||
alwaysShow?: boolean // 始终显示的列,不可隐藏
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface Props {
|
||||
columns?: Column[] // 手动配置的列(可选,如果提供了 tableRef 则自动提取)
|
||||
tableRef?: Ref<TableInstance | undefined> // el-table 的 ref,用于自动提取列配置
|
||||
modelValue?: string[] // 当前显示的列
|
||||
storageKey?: string // localStorage 存储的 key,用于持久化
|
||||
triggerType?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text'
|
||||
triggerSize?: 'large' | 'default' | 'small'
|
||||
triggerCircle?: boolean
|
||||
triggerText?: string
|
||||
triggerLink?: boolean
|
||||
dialogWidth?: string // 对话框宽度
|
||||
// 自动提取时的配置选项
|
||||
autoExtractOptions?: {
|
||||
defaultHidden?: string[]
|
||||
alwaysShow?: string[]
|
||||
columnMap?: Record<string, Partial<ColumnConfig>>
|
||||
}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
triggerType: 'default',
|
||||
triggerSize: 'default',
|
||||
triggerCircle: false,
|
||||
triggerText: '',
|
||||
triggerLink: false,
|
||||
dialogWidth: '800px'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string[]]
|
||||
'change': [value: string[]]
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const checkedColumns = ref<string[]>([])
|
||||
|
||||
// 如果提供了 tableRef,使用自动提取;否则使用手动配置的 columns
|
||||
const tableColumnsResult = props.tableRef
|
||||
? useTableColumns(props.tableRef, props.storageKey, props.autoExtractOptions)
|
||||
: {
|
||||
columns: computed(() => []),
|
||||
visibleColumns: computed(() => []),
|
||||
updateVisibleColumns: () => {},
|
||||
refreshColumns: () => {},
|
||||
isColumnVisible: () => true
|
||||
}
|
||||
|
||||
const {
|
||||
columns: autoColumns,
|
||||
visibleColumns: autoVisibleColumns,
|
||||
updateVisibleColumns: updateAutoVisibleColumns,
|
||||
refreshColumns: refreshAutoColumns,
|
||||
isColumnVisible: autoIsColumnVisible
|
||||
} = tableColumnsResult
|
||||
|
||||
// 实际使用的列配置
|
||||
const actualColumns = computed(() => {
|
||||
const result = props.tableRef && autoColumns.value.length > 0
|
||||
? autoColumns.value
|
||||
: props.columns || []
|
||||
return result
|
||||
})
|
||||
|
||||
// 获取所有列(包括固定列和 alwaysShow 列)
|
||||
const getAllColumns = (): string[] => {
|
||||
return actualColumns.value.map(col => col.prop || col.label)
|
||||
}
|
||||
|
||||
// 初始化选中的列
|
||||
const initCheckedColumns = () => {
|
||||
if (props.modelValue && props.modelValue.length > 0) {
|
||||
checkedColumns.value = [...props.modelValue]
|
||||
} else if (props.tableRef && autoVisibleColumns.value.length > 0) {
|
||||
// 使用自动提取的可见列
|
||||
const allSelectableColumns = actualColumns.value
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
|
||||
// 如果保存的列数量少于所有列,默认全部选中
|
||||
if (autoVisibleColumns.value.length < allSelectableColumns.length) {
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...allSelectableColumns, ...fixedAndAlwaysShow])]
|
||||
} else {
|
||||
// 使用保存的列,但确保包含固定列和 alwaysShow 列
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...autoVisibleColumns.value, ...fixedAndAlwaysShow])]
|
||||
}
|
||||
} else if (props.storageKey) {
|
||||
// 从 localStorage 读取
|
||||
const saved = localStorage.getItem(props.storageKey)
|
||||
if (saved) {
|
||||
try {
|
||||
const savedColumns = JSON.parse(saved)
|
||||
const allSelectableColumns = actualColumns.value
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
|
||||
// 如果保存的列数量少于所有列,默认全部选中
|
||||
if (savedColumns.length < allSelectableColumns.length) {
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...allSelectableColumns, ...fixedAndAlwaysShow])]
|
||||
} else {
|
||||
// 使用保存的列,但确保包含固定列和 alwaysShow 列
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...savedColumns, ...fixedAndAlwaysShow])]
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果解析失败,使用默认值(所有列)
|
||||
checkedColumns.value = getAllColumns()
|
||||
}
|
||||
} else {
|
||||
// 首次使用,默认全部选中
|
||||
checkedColumns.value = getAllColumns()
|
||||
}
|
||||
} else {
|
||||
// 没有 storageKey,默认全部选中
|
||||
checkedColumns.value = getAllColumns()
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 actualColumns 变化,更新选中状态
|
||||
watch(actualColumns, (newColumns) => {
|
||||
if (newColumns.length > 0 && checkedColumns.value.length === 0) {
|
||||
// 如果列数据已加载但选中列表为空,初始化选中所有列
|
||||
initCheckedColumns()
|
||||
} else if (newColumns.length > 0) {
|
||||
// 确保固定列和 alwaysShow 列始终在选中列表中
|
||||
const fixedAndAlwaysShow = newColumns
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
const currentChecked = checkedColumns.value
|
||||
const missingFixed = fixedAndAlwaysShow.filter(col => !currentChecked.includes(col))
|
||||
if (missingFixed.length > 0) {
|
||||
checkedColumns.value = [...currentChecked, ...missingFixed]
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 监听弹窗打开,触发列配置重新提取
|
||||
watch(visible, (newVal) => {
|
||||
if (newVal) {
|
||||
// 弹窗打开时,确保选中状态正确初始化(但不重置用户已保存的设置)
|
||||
if (actualColumns.value.length > 0) {
|
||||
// 只有在 checkedColumns 为空时才初始化,避免覆盖用户已保存的设置
|
||||
if (checkedColumns.value.length === 0) {
|
||||
initCheckedColumns()
|
||||
} else {
|
||||
// 如果已有选中状态,只确保固定列和 alwaysShow 列在选中列表中
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
const currentChecked = checkedColumns.value
|
||||
const missingFixed = fixedAndAlwaysShow.filter(col => !currentChecked.includes(col))
|
||||
if (missingFixed.length > 0) {
|
||||
checkedColumns.value = [...currentChecked, ...missingFixed]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newVal && props.tableRef) {
|
||||
// 尝试多种方式获取表格实例
|
||||
const getTableInstance = (): TableInstance | null => {
|
||||
// 方法1: 直接从 props.tableRef.value 获取
|
||||
if (props.tableRef?.value) {
|
||||
return props.tableRef.value
|
||||
}
|
||||
|
||||
// 方法2: 尝试从 props.tableRef 本身获取(可能是直接的 ref 对象)
|
||||
if ((props.tableRef as any).value) {
|
||||
return (props.tableRef as any).value
|
||||
}
|
||||
|
||||
// 方法3: 如果 props.tableRef 本身就是表格实例(不应该发生,但作为备用)
|
||||
if ((props.tableRef as any).$el) {
|
||||
return props.tableRef as any
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// 如果 tableRef.value 已经有值,直接提取
|
||||
const tableInstance = getTableInstance()
|
||||
if (tableInstance) {
|
||||
const tableEl = (tableInstance as any).$el
|
||||
if (tableEl) {
|
||||
nextTick(() => {
|
||||
refreshAutoColumns()
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 等待 tableRef.value 有值后再提取
|
||||
let waitCount = 0
|
||||
const maxWaitCount = 50 // 最多等待 5 秒(50 * 100ms)
|
||||
let waitTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const waitForTableRef = () => {
|
||||
// 尝试获取表格实例
|
||||
const tableInstance = getTableInstance()
|
||||
if (tableInstance) {
|
||||
const tableEl = (tableInstance as any).$el
|
||||
if (tableEl) {
|
||||
nextTick(() => {
|
||||
refreshAutoColumns()
|
||||
|
||||
// 如果还是没有数据,多次重试
|
||||
let retryCount = 0
|
||||
const maxRetries = 10
|
||||
const retryInterval = setInterval(() => {
|
||||
retryCount++
|
||||
refreshAutoColumns()
|
||||
if (actualColumns.value.length > 0 || retryCount >= maxRetries) {
|
||||
clearInterval(retryInterval)
|
||||
}
|
||||
}, 200)
|
||||
})
|
||||
return // 成功获取,退出
|
||||
}
|
||||
}
|
||||
|
||||
// 继续等待
|
||||
waitCount++
|
||||
if (waitCount < maxWaitCount) {
|
||||
waitTimer = setTimeout(waitForTableRef, 100)
|
||||
} else {
|
||||
// 即使超时,也尝试提取一次(可能表格已经渲染了,只是 ref 没有正确绑定)
|
||||
refreshAutoColumns()
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟一下,确保表格已渲染
|
||||
setTimeout(() => {
|
||||
waitForTableRef()
|
||||
}, 300)
|
||||
|
||||
// 清理函数:弹窗关闭时清除等待定时器
|
||||
return () => {
|
||||
if (waitTimer) {
|
||||
clearTimeout(waitTimer)
|
||||
waitTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 tableRef.value 的变化,当它被赋值时触发列配置提取
|
||||
if (props.tableRef) {
|
||||
// 尝试多种方式监听 tableRef.value 的变化
|
||||
watch(() => {
|
||||
// 尝试多种方式获取 tableRef.value
|
||||
if (props.tableRef?.value) {
|
||||
return props.tableRef.value
|
||||
}
|
||||
if ((props.tableRef as any).value) {
|
||||
return (props.tableRef as any).value
|
||||
}
|
||||
return null
|
||||
}, (newVal, oldVal) => {
|
||||
if (newVal && newVal !== oldVal) {
|
||||
// 延迟一下,确保表格完全渲染
|
||||
setTimeout(() => {
|
||||
nextTick(() => {
|
||||
refreshAutoColumns()
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
}, { immediate: true })
|
||||
}
|
||||
|
||||
// 暴露给父组件使用
|
||||
defineExpose({
|
||||
isColumnVisible: autoIsColumnVisible,
|
||||
visibleColumns: autoVisibleColumns,
|
||||
refreshColumns: refreshAutoColumns
|
||||
})
|
||||
|
||||
// 列变化处理(实时生效)
|
||||
const handleColumnChange = (value: string[]) => {
|
||||
// 确保固定列和 alwaysShow 列始终在选中列表中
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
const finalValue = [...new Set([...value, ...fixedAndAlwaysShow])]
|
||||
|
||||
// 立即应用更改
|
||||
emit('update:modelValue', finalValue)
|
||||
emit('change', finalValue)
|
||||
|
||||
// 如果使用自动提取,同步更新(只更新可选择的列)
|
||||
if (props.tableRef) {
|
||||
const selectableValue = finalValue.filter(col => {
|
||||
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
updateAutoVisibleColumns(selectableValue)
|
||||
} else {
|
||||
// 保存到 localStorage(只保存可选择的列)
|
||||
if (props.storageKey) {
|
||||
const selectableValue = finalValue.filter(col => {
|
||||
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
localStorage.setItem(props.storageKey, JSON.stringify(selectableValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听外部 modelValue 变化
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
checkedColumns.value = [...newVal]
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
initCheckedColumns()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-column-control {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.column-control-content {
|
||||
.column-control-body {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.column-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.column-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 120px;
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 0;
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-control-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
92
src/components/tools/action-dropdown.vue
Normal file
92
src/components/tools/action-dropdown.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<el-dropdown
|
||||
v-if="hasVisibleItems"
|
||||
trigger="click"
|
||||
@command="handleCommand"
|
||||
:style="dropdownStyle"
|
||||
>
|
||||
<el-button
|
||||
:type="buttonType"
|
||||
link
|
||||
:style="buttonStyle"
|
||||
>
|
||||
<slot name="button">
|
||||
{{ buttonText }}
|
||||
<el-icon v-if="buttonIcon" class="el-icon--right" :style="iconStyle">
|
||||
<component :is="buttonIcon" v-if="buttonIcon" />
|
||||
</el-icon>
|
||||
</slot>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in visibleItems"
|
||||
:key="item.command"
|
||||
:command="item.command"
|
||||
>
|
||||
<el-icon v-if="item.icon">
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
<span :style="item.icon ? { marginLeft: '8px' } : {}">{{ item.label }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Operation } from '@element-plus/icons-vue'
|
||||
|
||||
interface MenuItem {
|
||||
command: string
|
||||
label: string
|
||||
icon?: any
|
||||
visible?: boolean | (() => boolean)
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items: MenuItem[]
|
||||
buttonText?: string
|
||||
buttonIcon?: any
|
||||
buttonType?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text'
|
||||
buttonStyle?: string | Record<string, any>
|
||||
dropdownStyle?: string | Record<string, any>
|
||||
iconStyle?: string | Record<string, any>
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
buttonText: '更多',
|
||||
buttonIcon: Operation,
|
||||
buttonType: 'primary',
|
||||
buttonStyle: () => ({ whiteSpace: 'nowrap' }),
|
||||
dropdownStyle: () => ({ marginLeft: '12px' }),
|
||||
iconStyle: () => ({ marginLeft: '4px' })
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
command: [command: string]
|
||||
}>()
|
||||
|
||||
// 计算可见的菜单项
|
||||
const visibleItems = computed(() => {
|
||||
return props.items.filter(item => {
|
||||
if (item.visible === undefined) return true
|
||||
if (typeof item.visible === 'boolean') return item.visible
|
||||
if (typeof item.visible === 'function') return item.visible()
|
||||
return false
|
||||
})
|
||||
})
|
||||
|
||||
// 是否有可见的菜单项
|
||||
const hasVisibleItems = computed(() => {
|
||||
return visibleItems.value.length > 0
|
||||
})
|
||||
|
||||
// 处理命令
|
||||
const handleCommand = (command: string) => {
|
||||
emit('command', command)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,93 +1,85 @@
|
||||
<template>
|
||||
<div style="width: 100%;height: 100%;">
|
||||
<div style="width: 100%; height: 100%;">
|
||||
<!-- 图片:直接使用原始地址展示(保持原有行为) -->
|
||||
<viewer :images="[authSrc]" v-if="!showIframe">
|
||||
<img v-if="!showIframe" ref="imgRef" :width="imgWidth || '100%'" :height="imgHeight || '100%'" :src="authSrc" />
|
||||
<img
|
||||
v-if="!showIframe"
|
||||
ref="imgRef"
|
||||
:width="imgWidth ? imgWidth : '100%;'"
|
||||
:height="imgHeight ? imgHeight : '100%;'"
|
||||
:src="authSrc"
|
||||
/>
|
||||
</viewer>
|
||||
<iframe v-if="showIframe" ref="authIframeRef" style="width: 100%;height: 100%;" >
|
||||
</iframe>
|
||||
|
||||
<!-- PDF:通过 iframe + 带 token 的请求展示 -->
|
||||
<iframe
|
||||
v-if="showIframe"
|
||||
ref="authIframeRef"
|
||||
style="width: 100%; height: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick } from 'vue';
|
||||
import { Session } from "/@/utils/storage";
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps<{
|
||||
authSrc: string;
|
||||
imgWidth?: string;
|
||||
imgHeight?: string;
|
||||
}>();
|
||||
authSrc: string
|
||||
imgWidth?: string
|
||||
imgHeight?: string
|
||||
}>()
|
||||
|
||||
// 定义响应式数据
|
||||
const showIframe = ref(false);
|
||||
const imgRef = ref<HTMLImageElement | null>(null);
|
||||
const authIframeRef = ref<HTMLIFrameElement | null>(null);
|
||||
const showIframe = ref(false)
|
||||
const authIframeRef = ref<HTMLIFrameElement | null>(null)
|
||||
const imgRef = ref<HTMLImageElement | null>(null)
|
||||
|
||||
// 携带token请求img的src
|
||||
// 携带 token 请求 img/pdf 的 src
|
||||
const getImgSrcByToken = (src?: string) => {
|
||||
if (props.authSrc.indexOf(".pdf") >= 0) {
|
||||
showIframe.value = true;
|
||||
const targetSrc = src || props.authSrc
|
||||
|
||||
if (targetSrc.indexOf('.pdf') >= 0) {
|
||||
// PDF:通过 iframe 展示
|
||||
showIframe.value = true
|
||||
nextTick(() => {
|
||||
const imgSrc = src || props.authSrc;
|
||||
const tenantId = Session.getTenant();
|
||||
const iframe = authIframeRef.value;
|
||||
if (!iframe) return;
|
||||
|
||||
const request = new XMLHttpRequest();
|
||||
request.responseType = 'blob';
|
||||
request.open('get', imgSrc, true);
|
||||
request.setRequestHeader('Authorization', "Bearer " + Session.getToken());
|
||||
request.setRequestHeader('TENANT-ID', tenantId);
|
||||
const tenantId = Session.getTenant()
|
||||
const iframe = authIframeRef.value
|
||||
if (!iframe) return
|
||||
|
||||
const request = new XMLHttpRequest()
|
||||
request.responseType = 'blob'
|
||||
request.open('get', targetSrc, true)
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + Session.getToken())
|
||||
request.setRequestHeader('TENANT-ID', tenantId)
|
||||
request.onreadystatechange = () => {
|
||||
if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
|
||||
const binaryData: BlobPart[] = [];
|
||||
binaryData.push(request.response);
|
||||
iframe.src = window.URL.createObjectURL(new Blob(binaryData, { type: 'application/pdf' }));
|
||||
if (request.readyState === XMLHttpRequest.DONE && request.status === 200) {
|
||||
const binaryData: BlobPart[] = []
|
||||
binaryData.push(request.response)
|
||||
iframe.src = window.URL.createObjectURL(new Blob(binaryData, { type: 'application/pdf' }))
|
||||
iframe.onload = () => {
|
||||
URL.revokeObjectURL(iframe.src);
|
||||
};
|
||||
URL.revokeObjectURL(iframe.src)
|
||||
}
|
||||
}
|
||||
};
|
||||
request.send(null);
|
||||
});
|
||||
}
|
||||
request.send(null)
|
||||
})
|
||||
} else {
|
||||
// 图片处理逻辑(已注释,如需要可取消注释并更新)
|
||||
// showIframe.value = false;
|
||||
// const imgSrc = src || props.authSrc;
|
||||
// const tenantId = Session.getTenant();
|
||||
// const img = imgRef.value;
|
||||
// if (!img) return;
|
||||
//
|
||||
// const request = new XMLHttpRequest();
|
||||
// request.responseType = 'blob';
|
||||
// request.open('get', imgSrc, true);
|
||||
// request.setRequestHeader('Authorization', "Bearer " + Session.getToken());
|
||||
// request.setRequestHeader('TENANT-ID', tenantId);
|
||||
// request.onreadystatechange = () => {
|
||||
// if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
|
||||
// img.src = URL.createObjectURL(request.response);
|
||||
// img.onload = () => {
|
||||
// URL.revokeObjectURL(img.src);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// request.send(null);
|
||||
// 图片:保持原有行为(直接使用 authSrc,不做 token 转发)
|
||||
showIframe.value = false
|
||||
// 如需带 token 加载图片,可参考被注释的旧逻辑在此扩展
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 刷新图片
|
||||
const refreshImg = (src?: string) => {
|
||||
getImgSrcByToken(src);
|
||||
};
|
||||
getImgSrcByToken(src)
|
||||
}
|
||||
|
||||
// 暴露方法供外部调用
|
||||
defineExpose({
|
||||
refreshImg
|
||||
});
|
||||
|
||||
// 组件挂载时执行
|
||||
onMounted(() => {
|
||||
getImgSrcByToken();
|
||||
});
|
||||
getImgSrcByToken()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
refreshImg,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
141
src/components/tools/preview-file.vue
Normal file
141
src/components/tools/preview-file.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<!-- 组件不占据任何布局空间,所有预览组件都是 teleported 的 -->
|
||||
<div style="display: none;">
|
||||
<!-- 图片:直接使用 el-image-viewer 全屏预览 -->
|
||||
<el-image-viewer
|
||||
v-if="!showIframe && imageSrc && imagePreviewVisible"
|
||||
:url-list="[imageSrc]"
|
||||
:teleported="true"
|
||||
hide-on-click-modal
|
||||
@close="imagePreviewVisible = false"
|
||||
/>
|
||||
|
||||
<!-- PDF:在 dialog 中显示 -->
|
||||
<el-dialog
|
||||
v-if="showIframe"
|
||||
v-model="pdfDialogVisible"
|
||||
:title="dialogTitle || '文件预览'"
|
||||
append-to-body
|
||||
width="90%"
|
||||
class="pdf-preview-dialog"
|
||||
>
|
||||
<iframe ref="authIframeRef" :style="{ width: '100%', height: pdfIframeHeight }" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick, computed, onUnmounted } from 'vue';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { Session } from "/@/utils/storage";
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps<{
|
||||
authSrc: string;
|
||||
imgWidth?: string;
|
||||
imgHeight?: string;
|
||||
dialogTitle?: string;
|
||||
}>();
|
||||
|
||||
// 定义响应式数据
|
||||
const showIframe = ref(false);
|
||||
const imageSrc = ref<string>('');
|
||||
const imagePreviewVisible = ref(false);
|
||||
const pdfDialogVisible = ref(false);
|
||||
const authIframeRef = ref<HTMLIFrameElement | null>(null);
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
// 计算 PDF iframe 的合适高度(优先使用外部传入的 imgHeight,否则根据窗口高度动态计算)
|
||||
const pdfIframeHeight = computed(() => {
|
||||
// 如果外部传入了 imgHeight,优先使用
|
||||
if (props.imgHeight) {
|
||||
return props.imgHeight;
|
||||
}
|
||||
// 否则根据窗口高度动态计算:dialog header 约 50px,padding 约 40px,留一些余量
|
||||
return `${windowHeight.value - 120}px`;
|
||||
});
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
getImgSrcByToken();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
|
||||
// 携带token请求img的src
|
||||
const getImgSrcByToken = (src?: string) => {
|
||||
if (props.authSrc.indexOf(".pdf") >= 0) {
|
||||
showIframe.value = true;
|
||||
pdfDialogVisible.value = true;
|
||||
nextTick(() => {
|
||||
const imgSrc = src || props.authSrc;
|
||||
const tenantId = Session.getTenant();
|
||||
const iframe = authIframeRef.value;
|
||||
if (!iframe) return;
|
||||
|
||||
const request = new XMLHttpRequest();
|
||||
request.responseType = 'blob';
|
||||
request.open('get', imgSrc, true);
|
||||
request.setRequestHeader('Authorization', "Bearer " + Session.getToken());
|
||||
request.setRequestHeader('TENANT-ID', tenantId);
|
||||
request.onreadystatechange = () => {
|
||||
if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
|
||||
const binaryData: BlobPart[] = [];
|
||||
binaryData.push(request.response);
|
||||
iframe.src = window.URL.createObjectURL(new Blob(binaryData, { type: 'application/pdf' }));
|
||||
iframe.onload = () => {
|
||||
URL.revokeObjectURL(iframe.src);
|
||||
};
|
||||
}
|
||||
};
|
||||
request.send(null);
|
||||
});
|
||||
} else {
|
||||
// 图片处理逻辑:加载后直接打开预览
|
||||
showIframe.value = false;
|
||||
pdfDialogVisible.value = false;
|
||||
const imgSrc = src || props.authSrc;
|
||||
const tenantId = Session.getTenant();
|
||||
|
||||
const request = new XMLHttpRequest();
|
||||
request.responseType = 'blob';
|
||||
request.open('get', imgSrc, true);
|
||||
request.setRequestHeader('Authorization', "Bearer " + Session.getToken());
|
||||
request.setRequestHeader('TENANT-ID', tenantId);
|
||||
request.onreadystatechange = () => {
|
||||
if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
|
||||
imageSrc.value = URL.createObjectURL(request.response);
|
||||
imagePreviewVisible.value = true;
|
||||
}
|
||||
};
|
||||
request.send(null);
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新图片
|
||||
const refreshImg = (src?: string) => {
|
||||
getImgSrcByToken(src);
|
||||
};
|
||||
|
||||
// 暴露方法供外部调用
|
||||
defineExpose({
|
||||
refreshImg
|
||||
});
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.pdf-preview-dialog :deep(.el-dialog__body) {
|
||||
padding: 20px !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
454
src/composables/useTableColumns.ts
Normal file
454
src/composables/useTableColumns.ts
Normal file
@@ -0,0 +1,454 @@
|
||||
import { ref, computed, watch, onMounted, nextTick, type Ref } from 'vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
|
||||
export interface ColumnConfig {
|
||||
prop?: string
|
||||
label: string
|
||||
width?: number | string
|
||||
minWidth?: number | string
|
||||
fixed?: boolean | 'left' | 'right'
|
||||
alwaysShow?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动从 el-table 提取列配置的 composable
|
||||
* @param tableRef el-table 的 ref
|
||||
* @param storageKey localStorage 存储的 key,用于持久化
|
||||
* @param options 额外配置选项
|
||||
*/
|
||||
export function useTableColumns(
|
||||
tableRef: Ref<TableInstance | undefined> | any, // 支持多种类型的 ref
|
||||
storageKey?: string,
|
||||
options?: {
|
||||
// 默认隐藏的列(prop 或 label)
|
||||
defaultHidden?: string[]
|
||||
// 始终显示的列(prop 或 label)
|
||||
alwaysShow?: string[]
|
||||
// 列配置映射(用于自定义列的显示名称等)
|
||||
columnMap?: Record<string, Partial<ColumnConfig>>
|
||||
}
|
||||
) {
|
||||
const columns = ref<ColumnConfig[]>([])
|
||||
const visibleColumns = ref<string[]>([])
|
||||
const isInitialized = ref(false) // 标记是否已经初始化过(用户是否操作过)
|
||||
const hasInitializedVisibleColumns = ref(false) // 标记是否已经初始化过可见列(避免重复初始化)
|
||||
|
||||
// 获取表格实例的辅助函数
|
||||
const getTableInstance = (): TableInstance | null => {
|
||||
// 方法1: 直接从 tableRef.value 获取
|
||||
if (tableRef?.value) {
|
||||
return tableRef.value
|
||||
}
|
||||
// 方法2: 如果 tableRef 本身有 value 属性(可能是直接的 ref 对象)
|
||||
if ((tableRef as any).value) {
|
||||
return (tableRef as any).value
|
||||
}
|
||||
// 方法3: 如果 tableRef 本身就是表格实例(不应该发生,但作为备用)
|
||||
if ((tableRef as any).$el) {
|
||||
return tableRef as any
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 从 el-table 提取列配置
|
||||
const extractColumns = (): ColumnConfig[] => {
|
||||
const tableInstance = getTableInstance()
|
||||
if (!tableInstance) {
|
||||
return []
|
||||
}
|
||||
|
||||
const table = tableInstance
|
||||
const extracted: ColumnConfig[] = []
|
||||
|
||||
try {
|
||||
// 方法1: 从 table.store 中提取(Element Plus 内部实现)
|
||||
const store = (table as any).store
|
||||
|
||||
if (store) {
|
||||
// 尝试多种路径访问列数据
|
||||
let tableColumns: any[] = []
|
||||
|
||||
if (store.states && store.states.columns) {
|
||||
tableColumns = store.states.columns.value || []
|
||||
} else if (store.columns) {
|
||||
tableColumns = Array.isArray(store.columns) ? store.columns : (store.columns.value || [])
|
||||
} else if ((table as any).columns) {
|
||||
tableColumns = Array.isArray((table as any).columns) ? (table as any).columns : ((table as any).columns.value || [])
|
||||
}
|
||||
|
||||
if (tableColumns.length > 0) {
|
||||
tableColumns.forEach((col: any) => {
|
||||
// 跳过序号列
|
||||
if (col.type === 'index' || col.type === 'selection') {
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试多种方式获取 label
|
||||
const label = col.label || col.columnKey || col.property || col.prop || ''
|
||||
|
||||
// 如果没有 label,跳过这一列
|
||||
if (!label) {
|
||||
return
|
||||
}
|
||||
|
||||
const config: ColumnConfig = {
|
||||
prop: col.property || col.prop || '',
|
||||
label: label,
|
||||
width: col.width,
|
||||
minWidth: col.minWidth,
|
||||
// Element Plus 中非固定列的 fixed 通常是 false,这里统一将 false 归一为 undefined
|
||||
fixed: col.fixed ? col.fixed : undefined,
|
||||
}
|
||||
|
||||
// 应用自定义映射
|
||||
if (options?.columnMap && config.prop) {
|
||||
const mapped = options.columnMap[config.prop]
|
||||
if (mapped) {
|
||||
Object.assign(config, mapped)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用 alwaysShow 配置
|
||||
if (options?.alwaysShow) {
|
||||
const key = config.prop || config.label
|
||||
if (key && options.alwaysShow.includes(key)) {
|
||||
config.alwaysShow = true
|
||||
}
|
||||
}
|
||||
|
||||
extracted.push(config)
|
||||
})
|
||||
|
||||
if (extracted.length > 0) {
|
||||
return extracted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 方法2: 从 DOM 中提取(备用方案,更可靠)
|
||||
const tableEl = (table as any).$el
|
||||
|
||||
if (tableEl) {
|
||||
// 尝试多种选择器来查找表头
|
||||
let columnHeaders: NodeListOf<HTMLElement> | null = null
|
||||
|
||||
// 方法2.1: 从主表格头部查找
|
||||
const headerWrapper = tableEl.querySelector('.el-table__header-wrapper')
|
||||
if (headerWrapper) {
|
||||
columnHeaders = headerWrapper.querySelectorAll('th')
|
||||
}
|
||||
|
||||
// 方法2.2: 如果找不到,从整个表格查找
|
||||
if (!columnHeaders || columnHeaders.length === 0) {
|
||||
columnHeaders = tableEl.querySelectorAll('th')
|
||||
}
|
||||
|
||||
// 方法2.3: 如果还是找不到,尝试查找固定列
|
||||
if (!columnHeaders || columnHeaders.length === 0) {
|
||||
const fixedLeft = tableEl.querySelector('.el-table__fixed-left')
|
||||
if (fixedLeft) {
|
||||
columnHeaders = fixedLeft.querySelectorAll('th')
|
||||
}
|
||||
}
|
||||
|
||||
if (!columnHeaders || columnHeaders.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 创建一个映射,通过列索引匹配 prop
|
||||
const propMap = new Map<number, string>()
|
||||
|
||||
// 尝试从表格的 slot 或配置中获取 prop
|
||||
// 通过检查表格的列定义来匹配
|
||||
if (store && store.states && store.states.columns) {
|
||||
const tableColumns = store.states.columns.value || []
|
||||
tableColumns.forEach((col: any, idx: number) => {
|
||||
if (col.property || col.prop) {
|
||||
propMap.set(idx, col.property || col.prop)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
columnHeaders.forEach((th: HTMLElement, index: number) => {
|
||||
// 尝试多种方式获取 label 文本
|
||||
// 1. 从 .cell 元素
|
||||
const cell = th.querySelector('.cell') as HTMLElement | null
|
||||
// 2. 从所有可能的文本节点
|
||||
let rawLabel = ''
|
||||
|
||||
if (cell) {
|
||||
rawLabel = cell.innerText || cell.textContent || ''
|
||||
} else {
|
||||
// 尝试从 th 的所有子元素中查找文本
|
||||
const textNodes: string[] = []
|
||||
const walker = document.createTreeWalker(
|
||||
th,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
null
|
||||
)
|
||||
let node: Node | null
|
||||
while ((node = walker.nextNode())) {
|
||||
const text = node.textContent?.trim()
|
||||
if (text) {
|
||||
textNodes.push(text)
|
||||
}
|
||||
}
|
||||
rawLabel = textNodes.join(' ') || th.innerText || th.textContent || ''
|
||||
}
|
||||
|
||||
const label = rawLabel.trim()
|
||||
|
||||
// 排除序号列和空列
|
||||
if (!label || label === '序号') {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否是固定列
|
||||
let fixed: 'left' | 'right' | undefined
|
||||
const thParent = th.closest('table')
|
||||
if (thParent) {
|
||||
if (thParent.classList.contains('el-table__fixed-left') || th.closest('.el-table__fixed-left')) {
|
||||
fixed = 'left'
|
||||
} else if (thParent.classList.contains('el-table__fixed-right') || th.closest('.el-table__fixed-right')) {
|
||||
fixed = 'right'
|
||||
} else if (th.classList.contains('is-left')) {
|
||||
fixed = 'left'
|
||||
} else if (th.classList.contains('is-right')) {
|
||||
fixed = 'right'
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试从属性中获取宽度
|
||||
const widthAttr = th.getAttribute('width') || th.style.width
|
||||
let width: number | string | undefined
|
||||
if (widthAttr) {
|
||||
const numWidth = parseInt(widthAttr)
|
||||
width = isNaN(numWidth) ? widthAttr : numWidth
|
||||
}
|
||||
|
||||
// 尝试从对应的 body cell 中获取 data-key 或其他属性来匹配 prop
|
||||
let prop = propMap.get(index)
|
||||
|
||||
// 如果没有找到 prop,使用 label 作为 prop(isColumnVisible 会同时匹配 prop 和 label)
|
||||
// 或者使用默认的 column_${index}
|
||||
if (!prop) {
|
||||
prop = `column_${index}`
|
||||
}
|
||||
|
||||
const columnConfig = {
|
||||
prop: prop,
|
||||
label,
|
||||
width,
|
||||
// DOM 提取的 fixed 只有 left/right,这里也归一为 undefined 或 'left'/'right'
|
||||
fixed: fixed ? fixed : undefined,
|
||||
}
|
||||
extracted.push(columnConfig)
|
||||
})
|
||||
|
||||
if (extracted.length > 0) {
|
||||
return extracted
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 提取失败,静默处理
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// 初始化列配置
|
||||
const initColumns = async () => {
|
||||
// 等待多个渲染周期,确保表格完全渲染
|
||||
await nextTick()
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
await nextTick()
|
||||
|
||||
let extracted = extractColumns()
|
||||
|
||||
// 如果第一次提取失败,多次重试
|
||||
if (extracted.length === 0) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
extracted = extractColumns()
|
||||
if (extracted.length > 0) break
|
||||
}
|
||||
}
|
||||
|
||||
if (extracted.length > 0) {
|
||||
// 合并列配置:
|
||||
// - 第一次提取时直接赋值
|
||||
// - 后续提取时与已有列做并集,避免因为列被隐藏导致配置丢失,
|
||||
// 从而在“列设置”弹窗中看不到已隐藏的列
|
||||
if (columns.value.length === 0) {
|
||||
columns.value = extracted
|
||||
} else {
|
||||
const keyOf = (col: ColumnConfig) => col.prop || col.label
|
||||
const map = new Map<string, ColumnConfig>()
|
||||
|
||||
// 先放入已有列
|
||||
columns.value.forEach(col => {
|
||||
const key = keyOf(col)
|
||||
if (key) {
|
||||
map.set(key, { ...col })
|
||||
}
|
||||
})
|
||||
|
||||
// 再合并本次提取的列(更新宽度、fixed 等信息,避免旧配置过时)
|
||||
extracted.forEach(col => {
|
||||
const key = keyOf(col)
|
||||
if (!key) return
|
||||
const exist = map.get(key)
|
||||
if (exist) {
|
||||
map.set(key, {
|
||||
...exist,
|
||||
...col,
|
||||
})
|
||||
} else {
|
||||
map.set(key, { ...col })
|
||||
}
|
||||
})
|
||||
|
||||
columns.value = Array.from(map.values())
|
||||
}
|
||||
|
||||
// 初始化可见列(只在第一次初始化时执行,避免后续刷新时重置用户设置)
|
||||
if (!hasInitializedVisibleColumns.value) {
|
||||
if (storageKey) {
|
||||
const saved = localStorage.getItem(storageKey)
|
||||
if (saved) {
|
||||
try {
|
||||
const savedColumns = JSON.parse(saved)
|
||||
// 验证保存的列是否仍然存在
|
||||
const validColumns = columns.value
|
||||
.filter(col => !col.alwaysShow && col.fixed === undefined)
|
||||
.map(col => col.prop || col.label)
|
||||
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
|
||||
|
||||
// 如果保存的列数量少于所有列,说明是旧数据,默认显示所有列
|
||||
if (filteredSaved.length < validColumns.length) {
|
||||
visibleColumns.value = validColumns
|
||||
isInitialized.value = false // 旧数据,视为未初始化
|
||||
} else {
|
||||
visibleColumns.value = filteredSaved
|
||||
isInitialized.value = true // 有保存的数据,视为已初始化
|
||||
}
|
||||
} catch (e) {
|
||||
initDefaultVisibleColumns()
|
||||
isInitialized.value = false
|
||||
}
|
||||
} else {
|
||||
// 首次使用,默认显示所有列
|
||||
initDefaultVisibleColumns()
|
||||
isInitialized.value = false
|
||||
}
|
||||
} else {
|
||||
// 没有 storageKey,默认显示所有列
|
||||
initDefaultVisibleColumns()
|
||||
isInitialized.value = false
|
||||
}
|
||||
hasInitializedVisibleColumns.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化默认可见列
|
||||
const initDefaultVisibleColumns = () => {
|
||||
// 默认显示所有可选择的列(除了固定列和 alwaysShow 列)
|
||||
// 注意:固定列和 alwaysShow 列不需要在 visibleColumns 中,因为它们始终显示
|
||||
// 默认全部选中,展示所有列
|
||||
visibleColumns.value = columns.value
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
|
||||
// 判断列是否可见
|
||||
const isColumnVisible = (propOrLabel: string): boolean => {
|
||||
// 如果列配置还没有提取完成,默认显示所有列(避免初始渲染时所有列被隐藏)
|
||||
if (columns.value.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
const column = columns.value.find(col =>
|
||||
(col.prop || col.label) === propOrLabel
|
||||
)
|
||||
|
||||
// 如果找不到对应的列配置,默认显示(可能是新添加的列)
|
||||
if (!column) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 固定列和始终显示的列始终显示
|
||||
if (column.fixed || column.alwaysShow) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果还未初始化(用户未操作过),默认显示所有列
|
||||
if (!isInitialized.value) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果已经初始化,严格按照可见列列表判断
|
||||
// 如果可见列列表为空,说明用户取消了所有列,应该隐藏所有非固定列
|
||||
if (visibleColumns.value.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否在可见列列表中
|
||||
return visibleColumns.value.includes(propOrLabel)
|
||||
}
|
||||
|
||||
// 更新可见列
|
||||
const updateVisibleColumns = (newColumns: string[]) => {
|
||||
visibleColumns.value = newColumns
|
||||
isInitialized.value = true // 标记已经初始化(用户已操作)
|
||||
if (storageKey) {
|
||||
localStorage.setItem(storageKey, JSON.stringify(newColumns))
|
||||
}
|
||||
}
|
||||
|
||||
// 监听表格变化,重新提取列配置
|
||||
watch(() => {
|
||||
// 尝试多种方式获取 tableRef.value
|
||||
if (tableRef?.value) {
|
||||
return tableRef.value
|
||||
}
|
||||
if ((tableRef as any).value) {
|
||||
return (tableRef as any).value
|
||||
}
|
||||
return null
|
||||
}, (newVal, oldVal) => {
|
||||
if (newVal && newVal !== oldVal) {
|
||||
// 延迟一下,确保表格已经渲染
|
||||
setTimeout(() => {
|
||||
initColumns()
|
||||
}, 200)
|
||||
} else if (newVal && !oldVal) {
|
||||
// 如果从 undefined 变为有值,也触发提取
|
||||
setTimeout(() => {
|
||||
initColumns()
|
||||
}, 200)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 组件挂载后初始化
|
||||
onMounted(() => {
|
||||
// 延迟初始化,确保表格已经渲染
|
||||
// 如果 tableRef.value 已经有值,立即初始化;否则等待 watch 触发
|
||||
const tableInstance = getTableInstance()
|
||||
if (tableInstance) {
|
||||
setTimeout(() => {
|
||||
initColumns()
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
columns: computed(() => columns.value),
|
||||
visibleColumns: computed(() => visibleColumns.value),
|
||||
isColumnVisible,
|
||||
updateVisibleColumns,
|
||||
refreshColumns: initColumns,
|
||||
}
|
||||
}
|
||||
|
||||
40
src/directives/v-column-visible.ts
Normal file
40
src/directives/v-column-visible.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 列显示/隐藏指令
|
||||
* 用法: v-column-visible="'propName'" 或 v-column-visible="propName"
|
||||
*/
|
||||
import type { Directive } from 'vue'
|
||||
|
||||
interface ColumnVisibleBinding {
|
||||
value: string // prop 或 label
|
||||
arg?: string
|
||||
}
|
||||
|
||||
export const vColumnVisible: Directive<HTMLElement, string> = {
|
||||
mounted(el, binding) {
|
||||
// 获取 isColumnVisible 函数(需要从父组件注入或通过其他方式获取)
|
||||
const isColumnVisible = (el as any).__isColumnVisible || (() => true)
|
||||
const propOrLabel = binding.value
|
||||
|
||||
if (!isColumnVisible(propOrLabel)) {
|
||||
// 隐藏该列(通过隐藏父元素)
|
||||
const columnElement = el.closest('.el-table-column') || el.parentElement
|
||||
if (columnElement) {
|
||||
(columnElement as HTMLElement).style.display = 'none'
|
||||
}
|
||||
}
|
||||
},
|
||||
updated(el, binding) {
|
||||
const isColumnVisible = (el as any).__isColumnVisible || (() => true)
|
||||
const propOrLabel = binding.value
|
||||
const columnElement = el.closest('.el-table-column') || el.parentElement
|
||||
|
||||
if (columnElement) {
|
||||
if (isColumnVisible(propOrLabel)) {
|
||||
(columnElement as HTMLElement).style.display = ''
|
||||
} else {
|
||||
(columnElement as HTMLElement).style.display = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,21 +60,21 @@ function sheet_from_array_of_arrays(data, opts) {
|
||||
if (range.e.c < C) range.e.c = C;
|
||||
var cell = {v: data[R][C]};
|
||||
if (cell.v == null) continue;
|
||||
var cell_ref = XLSX.utils.encode_cell({c: C, r: R});
|
||||
// var cell_ref = XLSX.utils.encode_cell({c: C, r: R});
|
||||
|
||||
if (typeof cell.v === 'number') cell.t = 'n';
|
||||
else if (typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if (cell.v instanceof Date) {
|
||||
cell.t = 'n';
|
||||
cell.z = XLSX.SSF._table[14];
|
||||
cell.v = datenum(cell.v);
|
||||
// cell.t = 'n';
|
||||
// cell.z = XLSX.SSF._table[14];
|
||||
// cell.v = datenum(cell.v);
|
||||
}
|
||||
else cell.t = 's';
|
||||
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
|
||||
// if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
@@ -112,9 +112,9 @@ export function export_table_to_excel(id) {
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
// var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
|
||||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
|
||||
// saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
|
||||
}
|
||||
|
||||
function formatJson(jsonData) {
|
||||
@@ -135,7 +135,7 @@ export function export_json_to_excel(th, jsonData, defaultTitle) {
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
// var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
var title = defaultTitle || '列表'
|
||||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx")
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="!v.meta.tagsViewName ? v.name : v.meta.tagsViewName">
|
||||
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
|
||||
<div v-if="!v.meta.tagsViewName">{{ $t(v.name) }}</div>
|
||||
<div v-else>{{ v.meta.tagsViewName }}</div>
|
||||
<div v-if="!v.meta.tagsViewName">{{ $t(v.name.split("_")[0]) }}</div>
|
||||
<div v-else>{{ v.meta.tagsViewName.split("_")[0] }}</div>
|
||||
</span>
|
||||
<a v-else @click.prevent="onBreadcrumbClick(v)">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.name) }}
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.name.split('_')[0]) }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
|
||||
@@ -388,7 +388,10 @@ const onMousedownMenu = (v: RouteItem, e: MouseEvent) => {
|
||||
};
|
||||
// 当前的 tagsView 项点击时
|
||||
const onTagsClick = (v: RouteItem, k: number) => {
|
||||
state.tagsRefsIndex = k;
|
||||
state.tagsRefsIndex = k;
|
||||
if(v.name.indexOf("router.home")!=0){
|
||||
v.name=v.name.replaceAll("_","").replaceAll(v.id,"")+"_"+v.id
|
||||
}
|
||||
router.push(v);
|
||||
};
|
||||
// 处理 url,地址栏链接有参数时,tagsview 右键菜单刷新功能失效问题,感谢 @ZzZz-RIPPER、@dejavuuuuu
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<span class="font-semibold">{{ $t(val.name) }}</span>
|
||||
<span class="font-semibold">{{ $t(val.name.split('_')[0]) }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children"/>
|
||||
</el-sub-menu>
|
||||
@@ -16,12 +16,12 @@
|
||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<!-- 此处可 指定 color='red' 等指定顶栏SVG颜色 -->
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<p class="font-semibold">{{ $t(val.name) }}</p>
|
||||
<p class="font-semibold">{{ $t(val.name.split('_')[0]) }}</p>
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
{{ $t(val.name) }}
|
||||
{{ $t(val.name.split('_')[0]) }}
|
||||
</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
<!-- <span>{{ $t(val.name) }}</span>-->
|
||||
<span>{{ $t(val.name.split('_')[0]) }}</span>
|
||||
</template>
|
||||
<sub-item :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
@@ -11,12 +12,14 @@
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
</template>
|
||||
<!-- <span>{{ $t(val.name) }}</span>-->
|
||||
<span>{{ $t(val.name.split('_')[0]) }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ $t(val.name) }}
|
||||
<!-- {{ $t(val.name) }}-->
|
||||
{{ $t(val.name.split('_')[0]) }}
|
||||
</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
<span>{{ $t(val.name.split('_')[0]) }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
@@ -21,10 +21,10 @@
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
<span>{{ $t(val.name.split('_')[0]) }}</span>
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.name) }}</a>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.name.split('_')[0]) }}</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
@@ -66,6 +66,9 @@ export function formatTwoStageRoutes(arr: any) {
|
||||
if (v.path === '/') {
|
||||
newArr.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] });
|
||||
} else {
|
||||
if (v.id) {
|
||||
v.name=v.name+"_"+v.id;
|
||||
}
|
||||
// 判断是否是动态路由(xx/:id/:name),用于 tagsView 等中使用
|
||||
if (v.path.indexOf('/:') > -1) {
|
||||
v.meta['isDynamic'] = true;
|
||||
|
||||
@@ -67,12 +67,22 @@
|
||||
// https://github.com/element-plus/element-plus/pull/15352
|
||||
.el-form--inline {
|
||||
.el-form-item {
|
||||
// 默认宽度 200px(输入框、单选、选择器等)
|
||||
& > .el-input,
|
||||
.el-cascader,
|
||||
.el-select,
|
||||
.el-date-editor,
|
||||
.el-autocomplete {
|
||||
width: 240px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
// 日期选择器:单日期为 200px,双日期(范围选择)为 240px
|
||||
.el-date-editor {
|
||||
width: 200px;
|
||||
|
||||
// 日期范围选择器(双日期)使用 240px
|
||||
&.el-range-editor {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,14 @@ export function setTagsViewNameI18n(item: any) {
|
||||
tagsViewName = query?.tagsViewName || params?.tagsViewName;
|
||||
}
|
||||
} else {
|
||||
let name=''
|
||||
if (item.name.indexOf("_") >= 0) {
|
||||
name=item.name.split("_")[0]
|
||||
}else{
|
||||
name=item.name
|
||||
}
|
||||
// 非自定义 tagsView 名称
|
||||
tagsViewName = i18n.global.t(item.name);
|
||||
tagsViewName = i18n.global.t(name);
|
||||
}
|
||||
return tagsViewName;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ service.interceptors.request.use(
|
||||
* @returns 如果响应成功,则返回响应的data属性;否则,抛出错误或者执行其他操作
|
||||
*/
|
||||
const handleResponse = (response: AxiosResponse<any>) => {
|
||||
console.log("response",response)
|
||||
if (response.data.code === 1) {
|
||||
throw response.data;
|
||||
}
|
||||
@@ -114,7 +113,8 @@ const handleResponse = (response: AxiosResponse<any>) => {
|
||||
* 添加 Axios 的响应拦截器,用于全局响应结果处理
|
||||
*/
|
||||
service.interceptors.response.use(handleResponse, (error) => {
|
||||
const status = Number(error.response.status) || 200;
|
||||
// 处理 HTTP 错误
|
||||
const status = Number(error.response?.status) || 200;
|
||||
if (status === 423) {
|
||||
return Promise.reject({ msg: '"演示环境,仅供预览"' });
|
||||
}
|
||||
@@ -147,7 +147,7 @@ service.interceptors.response.use(handleResponse, (error) => {
|
||||
error.response.data = decrypt(error.response?.data.encryption);
|
||||
}
|
||||
|
||||
return Promise.reject(error.response.data);
|
||||
return Promise.reject(error.response?.data);
|
||||
});
|
||||
|
||||
// 导出 axios 实例
|
||||
|
||||
@@ -79,12 +79,12 @@
|
||||
<el-table-column :label="$t('sysuser.index')" type="index" width="60" fixed="left" />
|
||||
<el-table-column :label="$t('sysuser.username')" prop="username" fixed="left" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysuser.name')" prop="realName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysuser.phone')" prop="phone" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysuser.post')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-for="(item, index) in scope.row.postList" :key="index">{{ item.postName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column :label="$t('sysuser.phone')" prop="phone" show-overflow-tooltip></el-table-column>-->
|
||||
<!-- <el-table-column :label="$t('sysuser.post')" show-overflow-tooltip>-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <el-tag v-for="(item, index) in scope.row.postList" :key="index">{{ item.postName }}</el-tag>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column :label="$t('sysuser.role')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-for="(item, index) in scope.row.roleList" :key="index">{{ item.roleName }}</el-tag>
|
||||
|
||||
@@ -82,7 +82,7 @@ import { list } from '/@/api/gen/datasource';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { validateNull } from '/@/utils/validate';
|
||||
import BatchGenDialog from './BatchGenDialog.vue';
|
||||
import BatchGenDialog from './batchGenDialog.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
// 定义变量内容
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import authImg from '@/components/tools/auth-img.vue'
|
||||
import { ref, defineAsyncComponent } from 'vue'
|
||||
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
|
||||
|
||||
// Props
|
||||
defineProps<{
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="驳回" width="600px" :close-on-click-modal="false" destroy-on-close>
|
||||
<el-form label-width="150px">
|
||||
<el-form-item label="姓名:">
|
||||
<el-tag>{{ dataForm.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="工号:">
|
||||
<el-tag>{{ dataForm.teacherNo }}</el-tag>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="姓名 / 工号:">
|
||||
<el-tag>{{ dataForm.name }} - {{ dataForm.teacherNo }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型:">
|
||||
<el-tag>{{ typeName }}</el-tag>
|
||||
<el-input v-model="typeName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="驳回理由">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="dataForm.backReason"
|
||||
:rows="4"
|
||||
:rows="3"
|
||||
placeholder="请输入驳回理由"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
@@ -24,8 +21,8 @@
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">关 闭</el-button>
|
||||
<el-button @click="saveData" :loading="loading" type="primary">保 存</el-button>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button @click="saveData" :loading="loading" type="primary">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -34,14 +31,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { putObj as editTitle } from '/@/api/professional/professionaluser/professionaltitlerelation'
|
||||
import { putObj as editQua } from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
import { putObj as editCer } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
|
||||
import { putObj as editEducation } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
import { putObj as editHonor } from '/@/api/professional/professionaluser/professionalteacherhonor'
|
||||
import { putObj as editPaper } from '/@/api/professional/professionalteacherpaper'
|
||||
import { putObj as editMaterial } from '/@/api/professional/professionalteachingmaterial'
|
||||
import { putObj as editTopic } from '/@/api/professional/professionaltopiclist'
|
||||
import { examObj as editTitle } from '/@/api/professional/professionaluser/professionaltitlerelation'
|
||||
import { examObj as editQua } from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
import { examObj as editCer } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
|
||||
import { examObj as editEducation } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
import { examObj as editHonor } from '/@/api/professional/professionaluser/professionalteacherhonor'
|
||||
import { examObj as editPaper } from '/@/api/professional/professionalteacherpaper'
|
||||
import { examObj as editMaterial } from '/@/api/professional/professionalteachingmaterial'
|
||||
import { examObj as editTopic } from '/@/api/professional/professionaltopiclist'
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-row shadow="hover" v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="单位名称" prop="companyName">
|
||||
<el-input
|
||||
@@ -29,12 +29,6 @@
|
||||
@click="handleAdd"
|
||||
v-if="permissions.professional_outercompany_add">新 增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -60,9 +54,9 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
|
||||
<el-table-column prop="updateTime" label="更新时间" min-width="180" align="center" />
|
||||
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" min-width="150" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="permissions.professional_outercompany_edit"
|
||||
@@ -76,7 +70,6 @@
|
||||
icon="delete"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -153,7 +146,7 @@ import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { useMessageBox } from '/@/hooks/message'
|
||||
import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/outercompany'
|
||||
import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/stayschool/outercompany'
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -286,8 +279,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-row shadow="hover" v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="单位名称" prop="companyName">
|
||||
<el-input
|
||||
@@ -29,12 +29,12 @@
|
||||
@click="handleAdd"
|
||||
v-if="permissions.professional_outercompany_add">新 增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
<!-- <right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
></right-toolbar> -->
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
|
||||
<el-table-column prop="updateTime" label="更新时间" min-width="180" align="center" />
|
||||
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" min-width="150" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="permissions.professional_outercompany_edit"
|
||||
@@ -76,7 +76,6 @@
|
||||
icon="delete"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -153,7 +152,7 @@ import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { useMessageBox } from '/@/hooks/message'
|
||||
import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/outercompany'
|
||||
import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/stayschool/outercompany'
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -286,8 +285,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-row shadow="hover" v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="单位名称" prop="companyName">
|
||||
<el-input
|
||||
@@ -20,25 +20,6 @@
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row>
|
||||
<div class="mb15" style="width: 100%;">
|
||||
<!-- 新增按钮已注释,此页面为只读 -->
|
||||
<!-- <el-button
|
||||
type="primary"
|
||||
icon="FolderAdd"
|
||||
@click="handleAdd"
|
||||
v-if="permissions.professional_outercompany_add">新 增
|
||||
</el-button> -->
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
@@ -61,7 +42,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
|
||||
<el-table-column prop="updateTime" label="更新时间" min-width="180" align="center" />
|
||||
|
||||
<!-- 操作列已注释,此页面为只读 -->
|
||||
<!-- <el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
@@ -100,7 +81,7 @@ import { ref, reactive, computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList } from '/@/api/professional/outercompany'
|
||||
import { fetchList } from '/@/api/professional/stayschool/outercompany'
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
311
src/views/professional/outercompanyemployee/form.vue
Normal file
311
src/views/professional/outercompanyemployee/form.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="formData.id ? '编辑' : '新增'"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
class="form-content"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位名称" prop="companyId">
|
||||
<el-select
|
||||
v-model="formData.companyId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择单位"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in companyList"
|
||||
:key="item.id"
|
||||
:label="item.companyName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职员编号" prop="employeeNo">
|
||||
<el-input
|
||||
v-model="formData.employeeNo"
|
||||
placeholder="系统自动生成"
|
||||
disabled
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="formData.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证" prop="idCard">
|
||||
<el-input
|
||||
v-model="formData.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
clearable
|
||||
maxlength="18"
|
||||
@input="handleIdCardInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input
|
||||
v-model="formData.mobile"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
clearable
|
||||
maxlength="11"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职位" prop="position">
|
||||
<el-input
|
||||
v-model="formData.position"
|
||||
placeholder="请输入职位"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家庭地址" prop="address">
|
||||
<el-input
|
||||
v-model="formData.address"
|
||||
placeholder="请输入家庭地址"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="允许进出" prop="inoutFlag">
|
||||
<el-select
|
||||
v-model="formData.inoutFlag"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in yesNoDict"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="formData.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, putObj } from '/@/api/professional/stayschool/outercompanyemployee'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
formData: {
|
||||
id?: string
|
||||
companyId?: string
|
||||
companyName?: string
|
||||
employeeNo?: string
|
||||
realName?: string
|
||||
idCard?: string
|
||||
mobile?: string
|
||||
position?: string
|
||||
address?: string
|
||||
inoutFlag?: string
|
||||
remarks?: string
|
||||
}
|
||||
companyList: any[]
|
||||
saveApi?: (data: any) => Promise<any> // 自定义保存 API 函数(用于培训单位、二期单位等)
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
formData: () => ({
|
||||
id: '',
|
||||
companyId: '',
|
||||
companyName: '',
|
||||
employeeNo: '',
|
||||
realName: '',
|
||||
idCard: '',
|
||||
mobile: '',
|
||||
position: '',
|
||||
address: '',
|
||||
inoutFlag: '',
|
||||
remarks: ''
|
||||
}),
|
||||
companyList: () => [],
|
||||
saveApi: undefined
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
// 字典数据
|
||||
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
|
||||
|
||||
// 消息提示
|
||||
const message = useMessage()
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref()
|
||||
|
||||
// 提交加载状态
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 弹窗显示状态
|
||||
const visible = ref(false)
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
visible.value = val
|
||||
})
|
||||
|
||||
watch(visible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
companyId: [
|
||||
{ required: true, message: '请选择单位', trigger: 'change' }
|
||||
],
|
||||
realName: [
|
||||
{ required: true, message: '请填写姓名', trigger: 'blur' }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: '请输入正确身份证', trigger: 'blur' },
|
||||
{ min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur' },
|
||||
{ pattern: /^(\d{15}|\d{17}[\dXx])$/, message: '身份证号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请填写手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: '请填写家庭住址', trigger: 'blur' }
|
||||
],
|
||||
inoutFlag: [
|
||||
{ required: true, message: '请选择是否允许进出', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 身份证号输入限制:只允许数字和X/x,最大长度18
|
||||
const handleIdCardInput = (value: string) => {
|
||||
// 只保留数字和X/x
|
||||
const filtered = value.replace(/[^\dXx]/g, '')
|
||||
if (filtered !== value) {
|
||||
props.formData.idCard = filtered
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
// 设置单位名称
|
||||
const selectedCompany = props.companyList.find(item => item.id === props.formData.companyId)
|
||||
const submitData = {
|
||||
...props.formData,
|
||||
companyName: selectedCompany?.companyName || ''
|
||||
}
|
||||
|
||||
if (props.formData.id) {
|
||||
await putObj(submitData)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
// 如果提供了自定义保存函数,使用它;否则使用默认的 addObj
|
||||
if (props.saveApi) {
|
||||
await props.saveApi(submitData)
|
||||
} else {
|
||||
await addObj(submitData)
|
||||
}
|
||||
message.success('添加成功')
|
||||
}
|
||||
handleClose()
|
||||
emit('success')
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-content {
|
||||
:deep(.el-row) {
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-row .el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
284
src/views/professional/outercompanyemployee/formTrain.vue
Normal file
284
src/views/professional/outercompanyemployee/formTrain.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="formData.id ? '编辑' : '新增'"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
class="form-content"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级名称" prop="companyId">
|
||||
<el-select
|
||||
v-model="formData.companyId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择班级"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in companyList"
|
||||
:key="item.id"
|
||||
:label="item.companyName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员编号" prop="employeeNo">
|
||||
<el-input
|
||||
v-model="formData.employeeNo"
|
||||
placeholder="系统自动生成"
|
||||
disabled
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="formData.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证" prop="idCard">
|
||||
<el-input
|
||||
v-model="formData.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
clearable
|
||||
maxlength="18"
|
||||
@input="handleIdCardInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input
|
||||
v-model="formData.mobile"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
clearable
|
||||
maxlength="11"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="允许进出" prop="inoutFlag">
|
||||
<el-select
|
||||
v-model="formData.inoutFlag"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in yesNoDict"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="formData.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { putObj } from '/@/api/professional/stayschool/outercompanyemployee'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
formData: {
|
||||
id?: string
|
||||
companyId?: string
|
||||
companyName?: string
|
||||
employeeNo?: string
|
||||
realName?: string
|
||||
idCard?: string
|
||||
mobile?: string
|
||||
inoutFlag?: string
|
||||
remarks?: string
|
||||
}
|
||||
companyList: any[]
|
||||
saveApi?: (data: any) => Promise<any> // 自定义保存 API 函数
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
formData: () => ({
|
||||
id: '',
|
||||
companyId: '',
|
||||
companyName: '',
|
||||
employeeNo: '',
|
||||
realName: '',
|
||||
idCard: '',
|
||||
mobile: '',
|
||||
inoutFlag: '',
|
||||
remarks: ''
|
||||
}),
|
||||
companyList: () => [],
|
||||
saveApi: undefined
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
// 字典数据
|
||||
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
|
||||
|
||||
// 消息提示
|
||||
const message = useMessage()
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref()
|
||||
|
||||
// 提交加载状态
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 弹窗显示状态
|
||||
const visible = ref(false)
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
visible.value = val
|
||||
})
|
||||
|
||||
watch(visible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
companyId: [
|
||||
{ required: true, message: '请选择班级', trigger: 'change' }
|
||||
],
|
||||
realName: [
|
||||
{ required: true, message: '请填写姓名', trigger: 'blur' }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: '请输入正确身份证', trigger: 'blur' },
|
||||
{ min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur' },
|
||||
{ pattern: /^(\d{15}|\d{17}[\dXx])$/, message: '身份证号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请填写手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
],
|
||||
inoutFlag: [
|
||||
{ required: true, message: '请选择是否允许进出', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 身份证号输入限制:只允许数字和X/x,最大长度18
|
||||
const handleIdCardInput = (value: string) => {
|
||||
// 只保留数字和X/x
|
||||
const filtered = value.replace(/[^\dXx]/g, '')
|
||||
if (filtered !== value) {
|
||||
props.formData.idCard = filtered
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
// 设置班级名称
|
||||
const selectedCompany = props.companyList.find(item => item.id === props.formData.companyId)
|
||||
const submitData = {
|
||||
...props.formData,
|
||||
companyName: selectedCompany?.companyName || ''
|
||||
}
|
||||
|
||||
if (props.formData.id) {
|
||||
await putObj(submitData)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
// 使用自定义保存函数
|
||||
if (props.saveApi) {
|
||||
await props.saveApi(submitData)
|
||||
} else {
|
||||
message.error('请提供保存 API 函数')
|
||||
return
|
||||
}
|
||||
message.success('添加成功')
|
||||
}
|
||||
handleClose()
|
||||
emit('success')
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-content {
|
||||
:deep(.el-row) {
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-row .el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-row shadow="hover" v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="handleFilter">
|
||||
<el-form-item label="单位名称" prop="companyId">
|
||||
<el-select
|
||||
@@ -10,7 +10,6 @@
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择单位"
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in companyList"
|
||||
@@ -25,7 +24,6 @@
|
||||
v-model="state.queryForm.employeeNo"
|
||||
placeholder="请输入职员编号"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
@@ -33,7 +31,6 @@
|
||||
v-model="state.queryForm.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证" prop="idCard">
|
||||
@@ -41,7 +38,6 @@
|
||||
v-model="state.queryForm.idCard"
|
||||
placeholder="请输入身份证"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
@@ -49,7 +45,6 @@
|
||||
v-model="state.queryForm.mobile"
|
||||
placeholder="请输入手机"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="允许进出" prop="inoutFlag">
|
||||
@@ -57,7 +52,6 @@
|
||||
v-model="state.queryForm.inoutFlag"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in yesNoDict"
|
||||
@@ -68,15 +62,15 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="handleFilter">查询</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row>
|
||||
<div class="mb15" style="width: 100%;">
|
||||
<div class="mb15">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="FolderAdd"
|
||||
@@ -85,26 +79,28 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
class="ml10"
|
||||
@click="handleExportIn"
|
||||
v-if="permission.scope == '1'"
|
||||
>导 入
|
||||
</el-button>
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
class="ml10"
|
||||
@click="handleExportScore"
|
||||
:loading="exportLoading"
|
||||
icon="Download">导出
|
||||
:loading="exportLoading">导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="batchDelect">批量删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
@click="batchDelect">批量删除
|
||||
</el-button> -->
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -140,18 +136,18 @@
|
||||
|
||||
<el-table-column label="头像" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<img
|
||||
width="50px"
|
||||
height="50px"
|
||||
@click="handlePictureCardPreview(scope.row.employeeNo)"
|
||||
:src="getImageView(scope.row.employeeNo)"
|
||||
@error="handleImageError"
|
||||
style="cursor: pointer; object-fit: cover; border-radius: 4px;"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Picture"
|
||||
@click="handlePictureCardPreview(scope.row.employeeNo)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
|
||||
<!-- <el-table-column prop="updateTime" label="更新时间" width="180" align="center" /> -->
|
||||
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
@@ -167,17 +163,15 @@
|
||||
icon="RefreshLeft"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="resetPassword(scope.row)">重置密码
|
||||
</el-button>
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
v-if="permissions.professional_outercompanyemployee_del"
|
||||
icon="delete"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -190,167 +184,18 @@
|
||||
/>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<el-dialog
|
||||
<form-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="form.id ? '编辑' : '新增'"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位名称" prop="companyId">
|
||||
<el-select
|
||||
v-model="form.companyId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择单位"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in companyList"
|
||||
:key="item.id"
|
||||
:label="item.companyName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职员编号" prop="employeeNo">
|
||||
<el-input
|
||||
v-model="form.employeeNo"
|
||||
placeholder="系统自动生成"
|
||||
disabled
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="form.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证" prop="idCard">
|
||||
<el-input
|
||||
v-model="form.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input
|
||||
v-model="form.mobile"
|
||||
placeholder="请输入手机号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职位" prop="position">
|
||||
<el-input
|
||||
v-model="form.position"
|
||||
placeholder="请输入职位"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家庭地址" prop="address">
|
||||
<el-input
|
||||
v-model="form.address"
|
||||
placeholder="请输入家庭地址"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="允许进出" prop="inoutFlag">
|
||||
<el-select
|
||||
v-model="form.inoutFlag"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in yesNoDict"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
:form-data="form"
|
||||
:company-list="companyList"
|
||||
@success="handleFormSuccess"
|
||||
/>
|
||||
|
||||
<!-- 头像预览对话框 -->
|
||||
<el-dialog v-model="dialogUploadVisible" title="头像预览" append-to-body>
|
||||
<img width="100%" :src="dialogImageUrl" alt="" style="max-height: 600px; object-fit: contain;" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 上传头像对话框 -->
|
||||
<el-dialog v-model="dialogAvatarVisible" title="上传头像" width="50%" append-to-body>
|
||||
<el-upload
|
||||
action="/basic/basicstudent/uploadAvatarBase64"
|
||||
list-type="picture-card"
|
||||
name="file"
|
||||
:headers="headers"
|
||||
:limit="1"
|
||||
:data="uploadData"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeUpload"
|
||||
:on-remove="removeHandler"
|
||||
:http-request="httpRequest"
|
||||
:on-success="uploadSuccess"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">上传头像,人脸识别用</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog v-model="dialogViewVisible" title="导入文件" append-to-body>
|
||||
<el-upload
|
||||
@@ -396,17 +241,16 @@ import { Session } from '/@/utils/storage'
|
||||
import { validateNull } from '/@/utils/validate'
|
||||
import axios from 'axios'
|
||||
import request from '/@/utils/request'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { Plus, Picture } from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchList,
|
||||
getObj,
|
||||
addObj,
|
||||
putObj,
|
||||
delObj,
|
||||
batchDel,
|
||||
resetPassWord
|
||||
} from '/@/api/professional/outercompanyemployee'
|
||||
import { getList as getCompanyList } from '/@/api/professional/outercompany'
|
||||
} from '/@/api/professional/stayschool/outercompanyemployee'
|
||||
import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany'
|
||||
import FormDialog from './form.vue'
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -426,7 +270,7 @@ const message = useMessage()
|
||||
const messageBox = useMessageBox()
|
||||
|
||||
// 字典数据
|
||||
const { yes_no: yesNoDict } = useDict('yes_no')
|
||||
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
|
||||
|
||||
// 获取字典标签的辅助函数
|
||||
const getDictLabel = (value: string | number) => {
|
||||
@@ -436,7 +280,6 @@ const getDictLabel = (value: string | number) => {
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const queryRef = ref()
|
||||
const uploadFormRef = ref()
|
||||
|
||||
@@ -446,9 +289,7 @@ const showSearch = ref(true)
|
||||
// 弹窗状态
|
||||
const dialogVisible = ref(false)
|
||||
const dialogUploadVisible = ref(false)
|
||||
const dialogAvatarVisible = ref(false)
|
||||
const dialogViewVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 选中的行数据
|
||||
@@ -476,52 +317,14 @@ const form = reactive({
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
companyId: [
|
||||
{ required: true, message: '请选择单位', trigger: 'change' }
|
||||
],
|
||||
realName: [
|
||||
{ required: true, message: '请填写姓名', trigger: 'blur' }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: '请填写身份证号', trigger: 'blur' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请填写手机号', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: '请填写家庭住址', trigger: 'blur' }
|
||||
],
|
||||
inoutFlag: [
|
||||
{ required: true, message: '请选择是否允许进出', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 头像相关
|
||||
const dialogImageUrl = ref('')
|
||||
const fileList = ref<any[]>([])
|
||||
const rowData = ref<any>({})
|
||||
const fileReader = ref<FileReader | null>(null)
|
||||
|
||||
// 上传相关
|
||||
const uploadData = reactive({
|
||||
bucketName: "base",
|
||||
module: "basic",
|
||||
username: ""
|
||||
})
|
||||
|
||||
// 导入相关
|
||||
const fileName = ref('')
|
||||
const filesList = ref<any[]>([])
|
||||
let files: File | null = null
|
||||
|
||||
// 请求头
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
"Authorization": 'Bearer ' + Session.getToken()
|
||||
}
|
||||
})
|
||||
|
||||
// 配置 useTable - 注意这个 API 返回的数据结构特殊
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
@@ -665,36 +468,9 @@ const resetPassword = (row: any) => {
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
// 设置单位名称
|
||||
const selectedCompany = companyList.value.find(item => item.id === form.companyId)
|
||||
if (selectedCompany) {
|
||||
form.companyName = selectedCompany.companyName
|
||||
}
|
||||
|
||||
if (form.id) {
|
||||
await putObj(form)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await addObj(form)
|
||||
message.success('添加成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
// 表单提交成功回调
|
||||
const handleFormSuccess = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 获取图片 URL
|
||||
@@ -704,98 +480,12 @@ const getImageView = (employeeNo: string) => {
|
||||
return `${baseUrl}/admin/user/photo/${employeeNo}?${timestamp}`
|
||||
}
|
||||
|
||||
// 图片加载错误处理
|
||||
const handleImageError = (event: Event) => {
|
||||
const img = event.target as HTMLImageElement
|
||||
img.src = '/img/default/no_pic.png'
|
||||
}
|
||||
|
||||
// 预览头像
|
||||
const handlePictureCardPreview = (employeeNo: string) => {
|
||||
dialogImageUrl.value = getImageView(employeeNo)
|
||||
dialogUploadVisible.value = true
|
||||
}
|
||||
|
||||
// 上传头像对话框
|
||||
const uploadAvatarDialog = (row: any) => {
|
||||
rowData.value = row
|
||||
fileList.value = []
|
||||
uploadData.username = row.employeeNo
|
||||
dialogAvatarVisible.value = true
|
||||
}
|
||||
|
||||
// 上传前验证
|
||||
const beforeUpload = (file: File) => {
|
||||
const isLt5M = file.size < 1 * 1024 * 1024
|
||||
if (fileList.value.length >= 1) {
|
||||
message.warning('只能上传一张头像')
|
||||
return false
|
||||
}
|
||||
if (!isLt5M) {
|
||||
message.warning('文件大小不能超过1M')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 自定义上传
|
||||
const httpRequest = (options: any) => {
|
||||
const file = options.file
|
||||
if (!fileReader.value) {
|
||||
fileReader.value = new FileReader()
|
||||
}
|
||||
|
||||
if (file) {
|
||||
fileReader.value.readAsDataURL(file)
|
||||
}
|
||||
|
||||
fileReader.value.onload = () => {
|
||||
const base64Str = fileReader.value?.result as string
|
||||
|
||||
const config = {
|
||||
url: '/basic/basicstudent/uploadAvatarBase64',
|
||||
method: 'post',
|
||||
data: {
|
||||
base64Str: base64Str.split(',')[1],
|
||||
username: rowData.value.employeeNo
|
||||
},
|
||||
timeout: 10000,
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
progressEvent.percent = progressEvent.loaded / progressEvent.total * 100
|
||||
options.onProgress(progressEvent, file)
|
||||
}
|
||||
}
|
||||
|
||||
axios(config)
|
||||
.then(res => {
|
||||
options.onSuccess(res, file)
|
||||
getDataList()
|
||||
})
|
||||
.catch(err => {
|
||||
options.onError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 移除文件
|
||||
const removeHandler = (file: any) => {
|
||||
const index = fileList.value.findIndex(f => f.uid === file.uid)
|
||||
if (index !== -1) {
|
||||
fileList.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传成功
|
||||
const uploadSuccess = (res: any, file: any) => {
|
||||
if (res.data) {
|
||||
const data = res.data
|
||||
file.key = data.key
|
||||
fileList.value.push(file)
|
||||
message.success('上传成功')
|
||||
dialogAvatarVisible.value = false
|
||||
getDataList()
|
||||
}
|
||||
}
|
||||
|
||||
// 导入
|
||||
const handleExportIn = () => {
|
||||
@@ -901,11 +591,6 @@ const handleExportScore = async () => {
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
if (!window.FileReader) {
|
||||
console.error('Your browser does not support FileReader API!')
|
||||
} else {
|
||||
fileReader.value = new FileReader()
|
||||
}
|
||||
loadCompanyList()
|
||||
})
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-row shadow="hover" v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="handleFilter">
|
||||
<el-form-item label="单位名称" prop="companyId">
|
||||
<el-select
|
||||
@@ -76,35 +76,50 @@
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row>
|
||||
<div class="mb15" style="width: 100%;">
|
||||
<div class="mb15" style="width: 100%; position: relative;">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="FolderAdd"
|
||||
icon="FolderAdd"
|
||||
@click="handleAdd"
|
||||
v-if="permissions.professional_outercompanyemployee_add">新 增
|
||||
v-if="permissions.professional_outercompanyemployee_add"
|
||||
>
|
||||
新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
@click="handleExportIn"
|
||||
v-if="permission.scope == '1'"
|
||||
>导 入
|
||||
class="ml10"
|
||||
>
|
||||
导 入
|
||||
</el-button>
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExportScore"
|
||||
:loading="exportLoading"
|
||||
icon="Download">导出
|
||||
class="ml10"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="batchDelect">批量删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
@click="batchDelect"
|
||||
class="ml10"
|
||||
>
|
||||
批量删除
|
||||
</el-button> -->
|
||||
<!-- <right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
style="position: absolute; right: 0; top: 0;"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
></right-toolbar> -->
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -140,18 +155,18 @@
|
||||
|
||||
<el-table-column label="头像" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<img
|
||||
width="50px"
|
||||
height="50px"
|
||||
@click="handlePictureCardPreview(scope.row.employeeNo)"
|
||||
:src="getImageView(scope.row.employeeNo)"
|
||||
@error="handleImageError"
|
||||
style="cursor: pointer; object-fit: cover; border-radius: 4px;"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Picture"
|
||||
@click="handlePictureCardPreview(scope.row.employeeNo)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
|
||||
<!-- <el-table-column prop="updateTime" label="更新时间" width="180" align="center" /> -->
|
||||
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
@@ -167,17 +182,15 @@
|
||||
icon="RefreshLeft"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="resetPassword(scope.row)">重置密码
|
||||
</el-button>
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
v-if="permissions.professional_outercompanyemployee_del"
|
||||
icon="delete"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -190,167 +203,19 @@
|
||||
/>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<el-dialog
|
||||
<form-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="form.id ? '编辑' : '新增'"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位名称" prop="companyId">
|
||||
<el-select
|
||||
v-model="form.companyId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择单位"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in companyList"
|
||||
:key="item.id"
|
||||
:label="item.companyName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职员编号" prop="employeeNo">
|
||||
<el-input
|
||||
v-model="form.employeeNo"
|
||||
placeholder="系统自动生成"
|
||||
disabled
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="form.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证" prop="idCard">
|
||||
<el-input
|
||||
v-model="form.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input
|
||||
v-model="form.mobile"
|
||||
placeholder="请输入手机号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职位" prop="position">
|
||||
<el-input
|
||||
v-model="form.position"
|
||||
placeholder="请输入职位"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家庭地址" prop="address">
|
||||
<el-input
|
||||
v-model="form.address"
|
||||
placeholder="请输入家庭地址"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="允许进出" prop="inoutFlag">
|
||||
<el-select
|
||||
v-model="form.inoutFlag"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in yesNoDict"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
:form-data="form"
|
||||
:company-list="companyList"
|
||||
:save-api="saveSecond"
|
||||
@success="handleFormSuccess"
|
||||
/>
|
||||
|
||||
<!-- 头像预览对话框 -->
|
||||
<el-dialog v-model="dialogUploadVisible" title="头像预览" append-to-body>
|
||||
<img width="100%" :src="dialogImageUrl" alt="" style="max-height: 600px; object-fit: contain;" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 上传头像对话框 -->
|
||||
<el-dialog v-model="dialogAvatarVisible" title="上传头像" width="50%" append-to-body>
|
||||
<el-upload
|
||||
action="/basic/basicstudent/uploadAvatarBase64"
|
||||
list-type="picture-card"
|
||||
name="file"
|
||||
:headers="headers"
|
||||
:limit="1"
|
||||
:data="uploadData"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeUpload"
|
||||
:on-remove="removeHandler"
|
||||
:http-request="httpRequest"
|
||||
:on-success="uploadSuccess"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">上传头像,人脸识别用</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog v-model="dialogViewVisible" title="导入文件" append-to-body>
|
||||
<el-upload
|
||||
@@ -396,18 +261,17 @@ import { Session } from '/@/utils/storage'
|
||||
import { validateNull } from '/@/utils/validate'
|
||||
import axios from 'axios'
|
||||
import request from '/@/utils/request'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
fetchList,
|
||||
getObj,
|
||||
saveSecond,
|
||||
putObj,
|
||||
delObj,
|
||||
batchDel,
|
||||
resetPassWord
|
||||
} from '/@/api/professional/outercompanyemployee'
|
||||
import { getList as getCompanyList } from '/@/api/professional/outercompany'
|
||||
} from '/@/api/professional/stayschool/outercompanyemployee'
|
||||
import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany'
|
||||
import FormDialog from './form.vue'
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -427,7 +291,7 @@ const message = useMessage()
|
||||
const messageBox = useMessageBox()
|
||||
|
||||
// 字典数据
|
||||
const { yes_no: yesNoDict } = useDict('yes_no')
|
||||
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
|
||||
|
||||
// 获取字典标签的辅助函数
|
||||
const getDictLabel = (value: string | number) => {
|
||||
@@ -437,7 +301,6 @@ const getDictLabel = (value: string | number) => {
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const queryRef = ref()
|
||||
const uploadFormRef = ref()
|
||||
|
||||
@@ -447,9 +310,7 @@ const showSearch = ref(true)
|
||||
// 弹窗状态
|
||||
const dialogVisible = ref(false)
|
||||
const dialogUploadVisible = ref(false)
|
||||
const dialogAvatarVisible = ref(false)
|
||||
const dialogViewVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 选中的行数据
|
||||
@@ -477,52 +338,15 @@ const form = reactive({
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
companyId: [
|
||||
{ required: true, message: '请选择单位', trigger: 'change' }
|
||||
],
|
||||
realName: [
|
||||
{ required: true, message: '请填写姓名', trigger: 'blur' }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: '请填写身份证号', trigger: 'blur' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请填写手机号', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: '请填写家庭住址', trigger: 'blur' }
|
||||
],
|
||||
inoutFlag: [
|
||||
{ required: true, message: '请选择是否允许进出', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 头像相关
|
||||
const dialogImageUrl = ref('')
|
||||
const fileList = ref<any[]>([])
|
||||
const rowData = ref<any>({})
|
||||
const fileReader = ref<FileReader | null>(null)
|
||||
|
||||
// 上传相关
|
||||
const uploadData = reactive({
|
||||
bucketName: "base",
|
||||
module: "basic",
|
||||
username: ""
|
||||
})
|
||||
|
||||
// 导入相关
|
||||
const fileName = ref('')
|
||||
const filesList = ref<any[]>([])
|
||||
let files: File | null = null
|
||||
|
||||
// 请求头
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
"Authorization": 'Bearer ' + Session.getToken()
|
||||
}
|
||||
})
|
||||
|
||||
// 配置 useTable - 注意这个 API 返回的数据结构特殊
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
@@ -666,36 +490,9 @@ const resetPassword = (row: any) => {
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
// 设置单位名称
|
||||
const selectedCompany = companyList.value.find(item => item.id === form.companyId)
|
||||
if (selectedCompany) {
|
||||
form.companyName = selectedCompany.companyName
|
||||
}
|
||||
|
||||
if (form.id) {
|
||||
await putObj(form)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await saveSecond(form) // 使用 saveSecond API
|
||||
message.success('添加成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
// 表单提交成功回调
|
||||
const handleFormSuccess = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 获取图片 URL
|
||||
@@ -705,90 +502,12 @@ const getImageView = (employeeNo: string) => {
|
||||
return `${baseUrl}/admin/user/photo/${employeeNo}?${timestamp}`
|
||||
}
|
||||
|
||||
// 图片加载错误处理
|
||||
const handleImageError = (event: Event) => {
|
||||
const img = event.target as HTMLImageElement
|
||||
img.src = '/img/default/no_pic.png'
|
||||
}
|
||||
|
||||
// 预览头像
|
||||
const handlePictureCardPreview = (employeeNo: string) => {
|
||||
dialogImageUrl.value = getImageView(employeeNo)
|
||||
dialogUploadVisible.value = true
|
||||
}
|
||||
|
||||
// 上传前验证
|
||||
const beforeUpload = (file: File) => {
|
||||
const isLt5M = file.size < 1 * 1024 * 1024
|
||||
if (fileList.value.length >= 1) {
|
||||
message.warning('只能上传一张头像')
|
||||
return false
|
||||
}
|
||||
if (!isLt5M) {
|
||||
message.warning('文件大小不能超过1M')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 自定义上传
|
||||
const httpRequest = (options: any) => {
|
||||
const file = options.file
|
||||
if (!fileReader.value) {
|
||||
fileReader.value = new FileReader()
|
||||
}
|
||||
|
||||
if (file) {
|
||||
fileReader.value.readAsDataURL(file)
|
||||
}
|
||||
|
||||
fileReader.value.onload = () => {
|
||||
const base64Str = fileReader.value?.result as string
|
||||
|
||||
const config = {
|
||||
url: '/basic/basicstudent/uploadAvatarBase64',
|
||||
method: 'post',
|
||||
data: {
|
||||
base64Str: base64Str.split(',')[1],
|
||||
username: rowData.value.employeeNo
|
||||
},
|
||||
timeout: 10000,
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
progressEvent.percent = progressEvent.loaded / progressEvent.total * 100
|
||||
options.onProgress(progressEvent, file)
|
||||
}
|
||||
}
|
||||
|
||||
axios(config)
|
||||
.then(res => {
|
||||
options.onSuccess(res, file)
|
||||
getDataList()
|
||||
})
|
||||
.catch(err => {
|
||||
options.onError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 移除文件
|
||||
const removeHandler = (file: any) => {
|
||||
const index = fileList.value.findIndex(f => f.uid === file.uid)
|
||||
if (index !== -1) {
|
||||
fileList.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传成功
|
||||
const uploadSuccess = (res: any, file: any) => {
|
||||
if (res.data) {
|
||||
const data = res.data
|
||||
file.key = data.key
|
||||
fileList.value.push(file)
|
||||
message.success('上传成功')
|
||||
dialogAvatarVisible.value = false
|
||||
getDataList()
|
||||
}
|
||||
}
|
||||
|
||||
// 导入
|
||||
const handleExportIn = () => {
|
||||
@@ -894,12 +613,6 @@ const handleExportScore = async () => {
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
if (!window.FileReader) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Your browser does not support FileReader API!')
|
||||
} else {
|
||||
fileReader.value = new FileReader()
|
||||
}
|
||||
loadCompanyList()
|
||||
})
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-row shadow="hover" v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="handleFilter">
|
||||
<el-form-item label="班级名称" prop="companyId">
|
||||
<el-select
|
||||
@@ -79,32 +79,47 @@
|
||||
<div class="mb15" style="width: 100%;">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="FolderAdd"
|
||||
icon="FolderAdd"
|
||||
@click="handleAdd"
|
||||
v-if="permissions.professional_outercompanyemployee_add">新 增
|
||||
v-if="permissions.professional_outercompanyemployee_add"
|
||||
>
|
||||
新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
@click="handleExportIn"
|
||||
v-if="permission.scope == '1'"
|
||||
>导 入
|
||||
class="ml10"
|
||||
>
|
||||
导 入
|
||||
</el-button>
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExportScore"
|
||||
:loading="exportLoading"
|
||||
icon="Download">导出
|
||||
class="ml10"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="batchDelect">批量删除
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
@click="batchDelect"
|
||||
class="ml10"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
></right-toolbar> -->
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -140,18 +155,18 @@
|
||||
|
||||
<el-table-column label="头像" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<img
|
||||
width="50px"
|
||||
height="50px"
|
||||
@click="handlePictureCardPreview(scope.row.employeeNo)"
|
||||
:src="getImageView(scope.row.employeeNo)"
|
||||
@error="handleImageError"
|
||||
style="cursor: pointer; object-fit: cover; border-radius: 4px;"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Picture"
|
||||
@click="handlePictureCardPreview(scope.row.employeeNo)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
|
||||
<!-- <el-table-column prop="updateTime" label="更新时间" width="180" align="center" /> -->
|
||||
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
@@ -167,17 +182,15 @@
|
||||
icon="RefreshLeft"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="resetPassword(scope.row)">重置密码
|
||||
</el-button>
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
v-if="permissions.professional_outercompanyemployee_del"
|
||||
icon="delete"
|
||||
link
|
||||
type="primary"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -190,146 +203,19 @@
|
||||
/>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<el-dialog
|
||||
<form-train
|
||||
v-model="dialogVisible"
|
||||
:title="form.id ? '编辑' : '新增'"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级名称" prop="companyId">
|
||||
<el-select
|
||||
v-model="form.companyId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择班级"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in companyList"
|
||||
:key="item.id"
|
||||
:label="item.companyName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员编号" prop="employeeNo">
|
||||
<el-input
|
||||
v-model="form.employeeNo"
|
||||
placeholder="系统自动生成"
|
||||
disabled
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="form.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证" prop="idCard">
|
||||
<el-input
|
||||
v-model="form.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input
|
||||
v-model="form.mobile"
|
||||
placeholder="请输入手机号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="允许进出" prop="inoutFlag">
|
||||
<el-select
|
||||
v-model="form.inoutFlag"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in yesNoDict"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
:form-data="form"
|
||||
:company-list="companyList"
|
||||
:save-api="saveSecond"
|
||||
@success="handleFormSuccess"
|
||||
/>
|
||||
|
||||
<!-- 头像预览对话框 -->
|
||||
<el-dialog v-model="dialogUploadVisible" title="头像预览" append-to-body>
|
||||
<img width="100%" :src="dialogImageUrl" alt="" style="max-height: 600px; object-fit: contain;" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 上传头像对话框 -->
|
||||
<el-dialog v-model="dialogAvatarVisible" title="上传头像" width="50%" append-to-body>
|
||||
<el-upload
|
||||
action="/basic/basicstudent/uploadAvatarBase64"
|
||||
list-type="picture-card"
|
||||
name="file"
|
||||
:headers="headers"
|
||||
:limit="1"
|
||||
:data="uploadData"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeUpload"
|
||||
:on-remove="removeHandler"
|
||||
:http-request="httpRequest"
|
||||
:on-success="uploadSuccess"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">上传头像,人脸识别用</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog v-model="dialogViewVisible" title="导入文件" append-to-body>
|
||||
<el-upload
|
||||
@@ -375,18 +261,18 @@ import { Session } from '/@/utils/storage'
|
||||
import { validateNull } from '/@/utils/validate'
|
||||
import axios from 'axios'
|
||||
import request from '/@/utils/request'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { Plus, UploadFilled, Download, Delete, Picture } from '@element-plus/icons-vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
fetchList,
|
||||
getObj,
|
||||
saveSecond,
|
||||
putObj,
|
||||
delObj,
|
||||
batchDel,
|
||||
resetPassWord
|
||||
} from '/@/api/professional/outercompanyemployee'
|
||||
import { getList as getCompanyList } from '/@/api/professional/outercompany'
|
||||
} from '/@/api/professional/stayschool/outercompanyemployee'
|
||||
import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany'
|
||||
import FormTrain from './formTrain.vue'
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -406,7 +292,7 @@ const message = useMessage()
|
||||
const messageBox = useMessageBox()
|
||||
|
||||
// 字典数据
|
||||
const { yes_no: yesNoDict } = useDict('yes_no')
|
||||
const { yes_no_type: yesNoDict } = useDict('yes_no_type')
|
||||
|
||||
// 获取字典标签的辅助函数
|
||||
const getDictLabel = (value: string | number) => {
|
||||
@@ -416,7 +302,6 @@ const getDictLabel = (value: string | number) => {
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const queryRef = ref()
|
||||
const uploadFormRef = ref()
|
||||
|
||||
@@ -426,9 +311,7 @@ const showSearch = ref(true)
|
||||
// 弹窗状态
|
||||
const dialogVisible = ref(false)
|
||||
const dialogUploadVisible = ref(false)
|
||||
const dialogAvatarVisible = ref(false)
|
||||
const dialogViewVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 选中的行数据
|
||||
@@ -454,37 +337,15 @@ const form = reactive({
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 表单验证规则 - 培训单位没有必填验证
|
||||
const formRules = {
|
||||
companyId: [
|
||||
{ required: true, message: '请选择班级', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 头像相关
|
||||
const dialogImageUrl = ref('')
|
||||
const fileList = ref<any[]>([])
|
||||
const rowData = ref<any>({})
|
||||
const fileReader = ref<FileReader | null>(null)
|
||||
|
||||
// 上传相关
|
||||
const uploadData = reactive({
|
||||
bucketName: "base",
|
||||
module: "basic",
|
||||
username: ""
|
||||
})
|
||||
|
||||
// 导入相关
|
||||
const fileName = ref('')
|
||||
const filesList = ref<any[]>([])
|
||||
let files: File | null = null
|
||||
|
||||
// 请求头
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
"Authorization": 'Bearer ' + Session.getToken()
|
||||
}
|
||||
})
|
||||
|
||||
// 配置 useTable - 注意这个 API 返回的数据结构特殊
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
@@ -624,36 +485,9 @@ const resetPassword = (row: any) => {
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
// 设置单位名称
|
||||
const selectedCompany = companyList.value.find(item => item.id === form.companyId)
|
||||
if (selectedCompany) {
|
||||
form.companyName = selectedCompany.companyName
|
||||
}
|
||||
|
||||
if (form.id) {
|
||||
await putObj(form)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await saveSecond(form) // 使用 saveSecond API
|
||||
message.success('添加成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
// 表单提交成功回调
|
||||
const handleFormSuccess = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 获取图片 URL
|
||||
@@ -663,90 +497,12 @@ const getImageView = (employeeNo: string) => {
|
||||
return `${baseUrl}/admin/user/photo/${employeeNo}?${timestamp}`
|
||||
}
|
||||
|
||||
// 图片加载错误处理
|
||||
const handleImageError = (event: Event) => {
|
||||
const img = event.target as HTMLImageElement
|
||||
img.src = '/img/default/no_pic.png'
|
||||
}
|
||||
|
||||
// 预览头像
|
||||
const handlePictureCardPreview = (employeeNo: string) => {
|
||||
dialogImageUrl.value = getImageView(employeeNo)
|
||||
dialogUploadVisible.value = true
|
||||
}
|
||||
|
||||
// 上传前验证
|
||||
const beforeUpload = (file: File) => {
|
||||
const isLt5M = file.size < 1 * 1024 * 1024
|
||||
if (fileList.value.length >= 1) {
|
||||
message.warning('只能上传一张头像')
|
||||
return false
|
||||
}
|
||||
if (!isLt5M) {
|
||||
message.warning('文件大小不能超过1M')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 自定义上传
|
||||
const httpRequest = (options: any) => {
|
||||
const file = options.file
|
||||
if (!fileReader.value) {
|
||||
fileReader.value = new FileReader()
|
||||
}
|
||||
|
||||
if (file) {
|
||||
fileReader.value.readAsDataURL(file)
|
||||
}
|
||||
|
||||
fileReader.value.onload = () => {
|
||||
const base64Str = fileReader.value?.result as string
|
||||
|
||||
const config = {
|
||||
url: '/basic/basicstudent/uploadAvatarBase64',
|
||||
method: 'post',
|
||||
data: {
|
||||
base64Str: base64Str.split(',')[1],
|
||||
username: rowData.value.employeeNo
|
||||
},
|
||||
timeout: 10000,
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
progressEvent.percent = progressEvent.loaded / progressEvent.total * 100
|
||||
options.onProgress(progressEvent, file)
|
||||
}
|
||||
}
|
||||
|
||||
axios(config)
|
||||
.then(res => {
|
||||
options.onSuccess(res, file)
|
||||
getDataList()
|
||||
})
|
||||
.catch(err => {
|
||||
options.onError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 移除文件
|
||||
const removeHandler = (file: any) => {
|
||||
const index = fileList.value.findIndex(f => f.uid === file.uid)
|
||||
if (index !== -1) {
|
||||
fileList.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传成功
|
||||
const uploadSuccess = (res: any, file: any) => {
|
||||
if (res.data) {
|
||||
const data = res.data
|
||||
file.key = data.key
|
||||
fileList.value.push(file)
|
||||
message.success('上传成功')
|
||||
dialogAvatarVisible.value = false
|
||||
getDataList()
|
||||
}
|
||||
}
|
||||
|
||||
// 导入
|
||||
const handleExportIn = () => {
|
||||
@@ -852,12 +608,6 @@ const handleExportScore = async () => {
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
if (!window.FileReader) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Your browser does not support FileReader API!')
|
||||
} else {
|
||||
fileReader.value = new FileReader()
|
||||
}
|
||||
loadCompanyList()
|
||||
})
|
||||
</script>
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
@click="handleEdit(scope.row)">编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
type="primary"
|
||||
link
|
||||
icon="Delete"
|
||||
v-if="permissions.professional_professionalpartybranch_del"
|
||||
@@ -232,8 +232,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-show="showSearch"
|
||||
:model="search"
|
||||
ref="searchFormRef"
|
||||
@keyup-enter="handleFilter(search)"
|
||||
@keyup-enter="handleFilter"
|
||||
>
|
||||
<template #default="{ visible }">
|
||||
<template v-if="visible">
|
||||
@@ -14,7 +14,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherNo"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入工号"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -23,7 +22,6 @@
|
||||
<el-input
|
||||
v-model="search.realName"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入用户名"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -33,7 +31,7 @@
|
||||
<!-- 操作按钮 -->
|
||||
<template #actions>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFilter(search)" icon="Search">查询</el-button>
|
||||
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
@@ -50,9 +48,11 @@
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="realName" label="用户名" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="oldBranchName" label="原支部名称" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<el-table-column prop="changeTime" label="变动时间" width="180" align="center" />
|
||||
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
|
||||
<!-- <el-table-column prop="createTime" label="创建时间" width="180" align="center" /> -->
|
||||
|
||||
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
@@ -83,9 +83,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList } from '/@/api/professional/professionaluser/professionalpartychange'
|
||||
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="编辑职业资格" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<el-dialog v-model="dialogVisible" title="编辑职业资格" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<div v-if="showForm">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
@@ -55,15 +55,17 @@
|
||||
<el-form-item label="证书编号" prop="certificateNumber">
|
||||
<el-input
|
||||
v-model="dataForm.certificateNumber"
|
||||
placeholder="请输入证书编号"
|
||||
placeholder="请输入证书编号(仅支持英文和数字)"
|
||||
clearable
|
||||
show-word-limit
|
||||
maxlength="32"
|
||||
@input="handleCertificateNumberInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="材料1" prop="materialA">
|
||||
<el-upload
|
||||
:headers="headers"
|
||||
style="width:3.5cm;height:5.3cm;"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:file-list="fileList"
|
||||
@@ -72,7 +74,9 @@
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
@@ -81,7 +85,7 @@
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -92,17 +96,13 @@
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
|
||||
import { putObj } from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
visible?: boolean
|
||||
}>()
|
||||
import { addObj } from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
import { checkLocked } from '/@/api/professional/professionalstatuslock'
|
||||
import { getLevelList } from '/@/api/professional/rsbase/professionalqualificationconfig'
|
||||
import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype'
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'refreshData'): void
|
||||
}>()
|
||||
|
||||
@@ -113,11 +113,8 @@ const message = useMessage()
|
||||
const formRef = ref()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 对话框显示状态
|
||||
const visible = computed({
|
||||
get: () => props.visible || false,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
// 对话框显示状态(内部管理)
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const dataForm = reactive({
|
||||
@@ -128,7 +125,6 @@ const dataForm = reactive({
|
||||
materialA: '',
|
||||
evidenceA: '',
|
||||
state: '',
|
||||
teacherNo: '',
|
||||
id: ''
|
||||
})
|
||||
|
||||
@@ -144,35 +140,21 @@ const formRules = {
|
||||
{ required: true, message: '请选择取证时间', trigger: 'change' }
|
||||
],
|
||||
certificateNumber: [
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' }
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
|
||||
],
|
||||
materialA: [
|
||||
{ required: true, message: '请上传证明材料', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 基础信息
|
||||
const baseInfoAbout = reactive<{
|
||||
stationTypeList: any[]
|
||||
atStationList: any[]
|
||||
teacherTypeList: any[]
|
||||
employmentNatureList: any[]
|
||||
stationLevelList: any[]
|
||||
stationDutyLevelList: any[]
|
||||
workTypeList: any[]
|
||||
proTitleList: any[]
|
||||
majorStationList: any[]
|
||||
qualificationList: any[]
|
||||
partBranchList: any[]
|
||||
}>({
|
||||
stationTypeList: [],
|
||||
atStationList: [],
|
||||
teacherTypeList: [],
|
||||
employmentNatureList: [],
|
||||
stationLevelList: [],
|
||||
stationDutyLevelList: [],
|
||||
workTypeList: [],
|
||||
proTitleList: [],
|
||||
majorStationList: [],
|
||||
qualificationList: [],
|
||||
partBranchList: []
|
||||
qualificationList: []
|
||||
})
|
||||
|
||||
// 上传相关
|
||||
@@ -198,25 +180,31 @@ const qualificationList = computed(() => baseInfoAbout.qualificationList as any[
|
||||
// 初始化字典数据
|
||||
const initDicData = async () => {
|
||||
try {
|
||||
const response = await getAllInfoAboutList()
|
||||
const map = response.data.data
|
||||
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
|
||||
baseInfoAbout.atStationList = map['atStationList'] || []
|
||||
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
|
||||
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
|
||||
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
|
||||
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
|
||||
baseInfoAbout.workTypeList = map['workTypeList'] || []
|
||||
baseInfoAbout.proTitleList = map['proTitleList'] || []
|
||||
baseInfoAbout.majorStationList = map['majorStationList'] || []
|
||||
baseInfoAbout.qualificationList = map['qualificationList'] || []
|
||||
baseInfoAbout.partBranchList = map['partBranchList'] || []
|
||||
visible.value = true
|
||||
// 使用专门的 API 获取数据(与 index.vue 保持一致)
|
||||
const [levelRes, workRes] = await Promise.all([
|
||||
getLevelList(),
|
||||
getWorkTypeList()
|
||||
])
|
||||
|
||||
// 处理资格等级列表(与 index.vue 保持一致)
|
||||
if (levelRes && levelRes.data) {
|
||||
baseInfoAbout.qualificationList = levelRes.data
|
||||
}
|
||||
|
||||
// 处理工种列表(与 index.vue 保持一致)
|
||||
if (workRes && workRes.data) {
|
||||
baseInfoAbout.workTypeList = workRes.data
|
||||
}
|
||||
} catch (error) {
|
||||
// 获取字典数据失败
|
||||
}
|
||||
}
|
||||
|
||||
// 证书编号输入处理(只允许英文和数字)
|
||||
const handleCertificateNumberInput = (value: string) => {
|
||||
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
|
||||
}
|
||||
|
||||
// 文件上传成功
|
||||
const materiaUploadSuccess = (response: any) => {
|
||||
if (response.data && response.data.code === "-1") {
|
||||
@@ -224,25 +212,62 @@ const materiaUploadSuccess = (response: any) => {
|
||||
return
|
||||
}
|
||||
dataForm.evidenceA = response.data.url
|
||||
dataForm.materialA = response.data.url
|
||||
// 上传成功后清除该字段的验证错误
|
||||
formRef.value?.clearValidate('materialA')
|
||||
}
|
||||
|
||||
// 打开对话框
|
||||
const openDialog = (row: any) => {
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=3`
|
||||
const openDialog = async (row?: any) => {
|
||||
// 新增模式:先检查是否锁定
|
||||
if (!row || !row.id) {
|
||||
try {
|
||||
const lockResponse = await checkLocked('job')
|
||||
if (lockResponse.data) {
|
||||
message.warning("新增功能已锁定,暂不允许操作")
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 公共设置
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?type=3`
|
||||
fileList.value = []
|
||||
Object.assign(dataForm, {
|
||||
worker: row.worker || '',
|
||||
qualificationConfigId: row.qualificationConfigId || '',
|
||||
certificateTime: row.certificateTime || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
materialA: row.materialA || '',
|
||||
evidenceA: row.evidenceA || '',
|
||||
state: row.state || '',
|
||||
teacherNo: row.teacherNo || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
|
||||
// 根据是否有 row 设置不同的表单数据
|
||||
if (row && row.id) {
|
||||
// 编辑模式:使用传入的数据
|
||||
Object.assign(dataForm, {
|
||||
worker: row.worker || '',
|
||||
qualificationConfigId: row.qualificationConfigId || '',
|
||||
certificateTime: row.certificateTime || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
materialA: row.materialA || '',
|
||||
evidenceA: row.evidenceA || '',
|
||||
state: row.state || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
} else {
|
||||
// 新增模式:重置为空
|
||||
Object.assign(dataForm, {
|
||||
worker: '',
|
||||
qualificationConfigId: '',
|
||||
certificateTime: '',
|
||||
certificateNumber: '',
|
||||
materialA: '',
|
||||
evidenceA: '',
|
||||
state: '',
|
||||
id: ''
|
||||
})
|
||||
}
|
||||
|
||||
// 公共操作:加载字典数据并显示对话框
|
||||
await initDicData()
|
||||
showForm.value = true
|
||||
initDicData()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -253,13 +278,18 @@ const dialogSubmit = async () => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
dataForm.state = '0'
|
||||
await putObj(dataForm)
|
||||
message.success("操作成功")
|
||||
visible.value = false
|
||||
// 统一使用 addObj 接口(新增和编辑都使用同一个接口)
|
||||
// 确保 evidenceA 或 materialA 有值
|
||||
if (!dataForm.evidenceA && dataForm.materialA) {
|
||||
dataForm.evidenceA = dataForm.materialA
|
||||
}
|
||||
|
||||
await addObj(dataForm)
|
||||
message.success(dataForm.id ? "修改成功" : "提交成功")
|
||||
dialogVisible.value = false
|
||||
emit('refreshData')
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -1,28 +1,6 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 图表统计 -->
|
||||
<div style="text-align: right; margin-bottom: 20px;">
|
||||
<el-collapse accordion @change="initChartOption">
|
||||
<el-collapse-item title="资格等级统计">
|
||||
<div style="width: 100%">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="chartRef" style="width: 100%; height: 400px;" :option="chartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="chartData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="name" label="等级" width="180" align="center" />
|
||||
<el-table-column prop="value" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="占比" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<search-form
|
||||
v-show="showSearch"
|
||||
@@ -36,7 +14,6 @@
|
||||
<el-select
|
||||
v-model="search.state"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请选择审核状态"
|
||||
>
|
||||
<el-option
|
||||
@@ -52,7 +29,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherNo"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入工号"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -61,7 +37,6 @@
|
||||
<el-input
|
||||
v-model="search.realName"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -87,7 +62,8 @@
|
||||
v-if="permissions.professional_professionalqualificationrelation_add">新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
v-if="permissions.professional_teacherbase_export"
|
||||
@click="handleDownLoadWord"
|
||||
@@ -109,18 +85,15 @@
|
||||
|
||||
<el-table-column prop="state" label="审核状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
|
||||
<span v-else>-</span>
|
||||
<AuditState :state="scope.row.state" :options="auditStateOptions" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="qualificationConfigId" label="资格等级" min-width="150" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
@@ -142,40 +115,44 @@
|
||||
v-if="scope.row.evidenceA && scope.row.evidenceA !== ''"
|
||||
type="primary"
|
||||
link
|
||||
icon="Document"
|
||||
@click="handlePreview(scope.row.srcList)">查看
|
||||
</el-button>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Edit"
|
||||
icon="edit-pen"
|
||||
v-if="permissions.professional_professionalqualificationrelation_edit && (scope.row.state === '0' || scope.row.state === '-2')"
|
||||
@click="handleEdit(scope.row)">编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
icon="Check"
|
||||
icon="CircleCheck"
|
||||
v-if="permissions.professional_professionalqualificationrelation_exam && scope.row.state === '0'"
|
||||
@click="changeState(scope.row, 1)">通过
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
icon="Close"
|
||||
icon="CircleClose"
|
||||
v-if="permissions.professional_professionalqualificationrelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
|
||||
@click="changeState(scope.row, -2)">驳回
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
type="primary"
|
||||
link
|
||||
icon="Delete"
|
||||
icon="delete"
|
||||
v-if="permissions.professional_professionalqualificationrelation_del"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -189,18 +166,15 @@
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
|
||||
<!-- 材料预览对话框 -->
|
||||
<el-dialog v-model="dialogVisible" title="职业资格材料" append-to-body width="90%">
|
||||
<auth-img
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
style="height:1000px;"
|
||||
/>
|
||||
</el-dialog>
|
||||
<!-- 材料预览:图片直接显示,PDF 在组件内部 dialog 中显示 -->
|
||||
<preview-file
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
dialog-title="职业资格材料"
|
||||
/>
|
||||
|
||||
<!-- 子组件 -->
|
||||
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
@@ -215,28 +189,28 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { useMessageBox } from '/@/hooks/message'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import { TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import {
|
||||
fetchList,
|
||||
putObj,
|
||||
examObj,
|
||||
delObj,
|
||||
getChartOption,
|
||||
exportExcel
|
||||
} from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
import { getLevelList } from '/@/api/professional/rsbase/professionalqualificationconfig'
|
||||
import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
|
||||
import DataForm from './form.vue'
|
||||
import ProfessionalBackResaon from '/@/views/professional/common/professional-back-resaon.vue'
|
||||
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
|
||||
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'))
|
||||
|
||||
// 注册 ECharts 组件
|
||||
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
||||
// 审核状态选项(独立定义,防止其他页面修改时被波及)
|
||||
import type { StateOption } from '/@/components/AuditState/index.vue'
|
||||
const auditStateOptions: StateOption[] = [
|
||||
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
|
||||
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
|
||||
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
|
||||
]
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -261,10 +235,8 @@ const { professional_state: professionalState } = useDict('professional_state')
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const multiDialogRef = ref()
|
||||
const dataFormRef = ref()
|
||||
const backReasonRef = ref()
|
||||
const chartRef = ref()
|
||||
const showSearch = ref(true)
|
||||
|
||||
// 搜索表单数据
|
||||
@@ -274,12 +246,7 @@ const search = reactive({
|
||||
realName: ''
|
||||
})
|
||||
|
||||
// 图表数据
|
||||
const chartOption = ref<any>({})
|
||||
const chartData = ref<any[]>([])
|
||||
|
||||
// 材料预览
|
||||
const dialogVisible = ref(false)
|
||||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
@@ -313,33 +280,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
// 初始化图表
|
||||
const initChartOption = async () => {
|
||||
try {
|
||||
const response = await getChartOption()
|
||||
chartOption.value = response.data.data.option || {}
|
||||
|
||||
let total = 0
|
||||
if (chartOption.value.series && chartOption.value.series[0] && chartOption.value.series[0].data) {
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
total += item.value || 0
|
||||
})
|
||||
|
||||
chartData.value = []
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = total > 0 ? Number((item.value / total * 100).toFixed(1)) : 0
|
||||
chartData.value.push({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Failed to load chart data
|
||||
}
|
||||
}
|
||||
|
||||
// 预览材料
|
||||
// 预览材料
|
||||
const handlePreview = (list: string[]) => {
|
||||
imgUrl.value = []
|
||||
@@ -350,9 +291,6 @@ const handlePreview = (list: string[]) => {
|
||||
url: v
|
||||
})
|
||||
})
|
||||
nextTick(() => {
|
||||
dialogVisible.value = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -363,14 +301,15 @@ const changeState = (row: any, val: number) => {
|
||||
const str = '通过'
|
||||
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
|
||||
try {
|
||||
await putObj({
|
||||
await examObj({
|
||||
id: row.id,
|
||||
state: val
|
||||
})
|
||||
message.success('操作成功')
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to change state
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
} else if (val === -2) {
|
||||
@@ -397,7 +336,7 @@ const resetQuery = () => {
|
||||
|
||||
// 打开新增窗口
|
||||
const handleAdd = () => {
|
||||
multiDialogRef.value?.init(3)
|
||||
dataFormRef.value?.openDialog()
|
||||
}
|
||||
|
||||
// 打开编辑窗口
|
||||
@@ -416,8 +355,9 @@ const handleDel = (row: any) => {
|
||||
state.pagination.current = state.pagination.current - 1
|
||||
}
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to delete
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 页面标题 -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
<h2 style="margin: 0; font-size: 20px; font-weight: 600; color: #303133;">资格等级统计</h2>
|
||||
</div>
|
||||
|
||||
<!-- 图表统计 -->
|
||||
<div style="width: 100%">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="chartRef" style="width: 100%; height: 400px;" :option="chartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="chartData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="name" label="等级" width="180" align="center" />
|
||||
<el-table-column prop="value" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="占比" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import { TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { getChartOption } from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
|
||||
// 注册 ECharts 组件
|
||||
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
||||
|
||||
// 图表引用
|
||||
const chartRef = ref()
|
||||
|
||||
// 图表数据
|
||||
const chartOption = ref<any>({})
|
||||
const chartData = ref<any[]>([])
|
||||
|
||||
// 初始化图表
|
||||
const initChartOption = async () => {
|
||||
try {
|
||||
const response = await getChartOption()
|
||||
chartOption.value = response.data.data.option || {}
|
||||
|
||||
let total = 0
|
||||
if (chartOption.value.series && chartOption.value.series[0] && chartOption.value.series[0].data) {
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
total += item.value || 0
|
||||
})
|
||||
|
||||
chartData.value = []
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = total > 0 ? Number((item.value / total * 100).toFixed(1)) : 0
|
||||
chartData.value.push({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Failed to load chart data
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时初始化图表
|
||||
onMounted(() => {
|
||||
initChartOption()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="编辑学历学位" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<el-dialog v-model="dialogVisible" title="编辑学历学位" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<div v-if="showForm">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="dataForm"
|
||||
:rules="formRules"
|
||||
:validate-on-rule-change="false"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="毕业时间" prop="graduateTime">
|
||||
@@ -14,7 +15,6 @@
|
||||
placeholder="请选择毕业时间"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择教育类型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in educationTypeList"
|
||||
@@ -41,7 +40,7 @@
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择学历"
|
||||
style="width: 100%"
|
||||
@change="handleQualificationChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in qualificationList"
|
||||
@@ -58,7 +57,7 @@
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择学位"
|
||||
style="width: 100%"
|
||||
@change="handleDegreeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in degreeList"
|
||||
@@ -88,15 +87,17 @@
|
||||
<el-form-item label="证书编码" prop="certificateNumber">
|
||||
<el-input
|
||||
v-model="dataForm.certificateNumber"
|
||||
placeholder="请输入证书编码"
|
||||
placeholder="请输入证书编码(仅支持英文和数字)"
|
||||
clearable
|
||||
show-word-limit
|
||||
maxlength="100"
|
||||
@input="handleCertificateNumberInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="学历证书" prop="materialA">
|
||||
<el-form-item v-if="needQualificationImg" label="学历证书" prop="qualificationImg" required>
|
||||
<el-upload
|
||||
:headers="headers"
|
||||
style="width:3.5cm;height:5.3cm;"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:file-list="fileList"
|
||||
@@ -105,15 +106,16 @@
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="学位证书" prop="materialB">
|
||||
<el-form-item v-if="needDegreeImg" label="学位证书" prop="degreeImg" required>
|
||||
<el-upload
|
||||
:headers="headers"
|
||||
style="width:3.5cm;height:5.3cm;"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:file-list="fileListB"
|
||||
@@ -122,7 +124,9 @@
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
@@ -131,31 +135,25 @@
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
|
||||
import { putObj } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
import { addObj } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
import { getAllTypeList } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig'
|
||||
import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig'
|
||||
import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
visible?: boolean
|
||||
}>()
|
||||
import { checkLocked } from '/@/api/professional/professionalstatuslock'
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'refreshData'): void
|
||||
}>()
|
||||
|
||||
@@ -166,11 +164,8 @@ const message = useMessage()
|
||||
const formRef = ref()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 对话框显示状态
|
||||
const visible = computed({
|
||||
get: () => props.visible || false,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
// 对话框显示状态(内部管理)
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const dataForm = reactive({
|
||||
@@ -181,65 +176,73 @@ const dataForm = reactive({
|
||||
graduateSchool: '',
|
||||
major: '',
|
||||
certificateNumber: '',
|
||||
materialA: '',
|
||||
materialB: '',
|
||||
qualificationImg: '',
|
||||
degreeImg: '',
|
||||
state: '',
|
||||
teacherNo: '',
|
||||
id: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
graduateTime: [
|
||||
{ required: true, message: '请选择毕业时间', trigger: 'change' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: '请选择教育类型', trigger: 'change' }
|
||||
],
|
||||
graduateSchool: [
|
||||
{ required: true, message: '请输入毕业学校', trigger: 'blur' }
|
||||
],
|
||||
major: [
|
||||
{ required: true, message: '请输入所学专业', trigger: 'blur' }
|
||||
],
|
||||
certificateNumber: [
|
||||
{ required: true, message: '请输入证书编码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
// 计算属性:是否需要学历证书(选择了学历)
|
||||
const needQualificationImg = computed(() => {
|
||||
return !!dataForm.qualificationConfigId
|
||||
})
|
||||
|
||||
// 计算属性:是否需要学位证书(选择了学位且不等于 -1 或"无")
|
||||
const needDegreeImg = computed(() => {
|
||||
if (!dataForm.degreeConfigId) return false
|
||||
// 检查是否为 -1 或"无"(处理类型转换)
|
||||
const degreeId = String(dataForm.degreeConfigId)
|
||||
if (degreeId === '-1') return false
|
||||
// 检查学位列表中是否有名称为"无"的选项
|
||||
const degreeItem = degreeList.value.find(item => String(item.id) === degreeId)
|
||||
if (degreeItem && degreeItem.degreeName === '无') return false
|
||||
return true
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const formRules = computed(() => {
|
||||
const rules: any = {
|
||||
graduateTime: [
|
||||
{ required: true, message: '请选择毕业时间', trigger: 'blur' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: '请选择教育类型', trigger: 'blur' }
|
||||
],
|
||||
graduateSchool: [
|
||||
{ required: true, message: '请输入毕业学校', trigger: 'blur' }
|
||||
],
|
||||
major: [
|
||||
{ required: true, message: '请输入所学专业', trigger: 'blur' }
|
||||
],
|
||||
certificateNumber: [
|
||||
{ required: true, message: '请输入证书编码', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编码只能包含英文和数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 只有当需要学历证书时才添加验证规则
|
||||
if (needQualificationImg.value) {
|
||||
rules.qualificationImg = [
|
||||
{ required: true, message: '请上传学历证书', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 只有当需要学位证书时才添加验证规则
|
||||
if (needDegreeImg.value) {
|
||||
rules.degreeImg = [
|
||||
{ required: true, message: '请上传学位证书', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
return rules
|
||||
})
|
||||
|
||||
// 下拉列表数据
|
||||
const educationTypeList = ref<any[]>([])
|
||||
const qualificationList = ref<any[]>([])
|
||||
const degreeList = ref<any[]>([])
|
||||
|
||||
// 基础信息
|
||||
const baseInfoAbout = reactive<{
|
||||
stationTypeList: any[]
|
||||
atStationList: any[]
|
||||
teacherTypeList: any[]
|
||||
employmentNatureList: any[]
|
||||
stationLevelList: any[]
|
||||
stationDutyLevelList: any[]
|
||||
workTypeList: any[]
|
||||
proTitleList: any[]
|
||||
majorStationList: any[]
|
||||
qualificationList: any[]
|
||||
partBranchList: any[]
|
||||
}>({
|
||||
stationTypeList: [],
|
||||
atStationList: [],
|
||||
teacherTypeList: [],
|
||||
employmentNatureList: [],
|
||||
stationLevelList: [],
|
||||
stationDutyLevelList: [],
|
||||
workTypeList: [],
|
||||
proTitleList: [],
|
||||
majorStationList: [],
|
||||
qualificationList: [],
|
||||
partBranchList: []
|
||||
})
|
||||
// 基础信息(简化,只保留需要的)
|
||||
|
||||
// 上传相关
|
||||
const url = ref('')
|
||||
@@ -248,7 +251,7 @@ const fileListB = ref<any[]>([])
|
||||
|
||||
// 请求头
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
return {
|
||||
"Authorization": 'Bearer ' + Session.getToken()
|
||||
}
|
||||
})
|
||||
@@ -259,53 +262,67 @@ const showForm = ref(false)
|
||||
// 初始化字典数据
|
||||
const initDicData = async () => {
|
||||
try {
|
||||
const [response, eduRes, quaRes, degRes] = await Promise.all([
|
||||
getAllInfoAboutList(),
|
||||
// 使用专门的 API 获取数据(与 index.vue 保持一致)
|
||||
const [eduRes, quaRes, degRes] = await Promise.all([
|
||||
getAllTypeList(),
|
||||
getQualificationList(),
|
||||
getDegreeList()
|
||||
])
|
||||
|
||||
const map = response.data.data
|
||||
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
|
||||
baseInfoAbout.atStationList = map['atStationList'] || []
|
||||
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
|
||||
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
|
||||
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
|
||||
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
|
||||
baseInfoAbout.workTypeList = map['workTypeList'] || []
|
||||
baseInfoAbout.proTitleList = map['proTitleList'] || []
|
||||
baseInfoAbout.majorStationList = map['majorStationList'] || []
|
||||
baseInfoAbout.qualificationList = map['qualificationList'] || []
|
||||
baseInfoAbout.partBranchList = map['partBranchList'] || []
|
||||
|
||||
// 获取教育类型列表
|
||||
if (eduRes && eduRes.data) {
|
||||
educationTypeList.value = Array.isArray(eduRes.data)
|
||||
? eduRes.data
|
||||
: (eduRes.data.records || eduRes.data.list || [])
|
||||
educationTypeList.value = eduRes.data
|
||||
}
|
||||
|
||||
// 获取学历列表
|
||||
if (quaRes && quaRes.data) {
|
||||
qualificationList.value = Array.isArray(quaRes.data)
|
||||
? quaRes.data
|
||||
: (quaRes.data.records || quaRes.data.list || [])
|
||||
qualificationList.value = quaRes.data
|
||||
}
|
||||
|
||||
// 获取学位列表
|
||||
if (degRes && degRes.data) {
|
||||
degreeList.value = Array.isArray(degRes.data)
|
||||
? degRes.data
|
||||
: (degRes.data.records || degRes.data.list || [])
|
||||
degreeList.value = degRes.data
|
||||
}
|
||||
|
||||
visible.value = true
|
||||
} catch (error) {
|
||||
// 获取字典数据失败
|
||||
}
|
||||
}
|
||||
|
||||
// 证书编号输入处理(只允许英文和数字)
|
||||
const handleCertificateNumberInput = (value: string) => {
|
||||
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
|
||||
}
|
||||
|
||||
// 学历改变时的处理
|
||||
const handleQualificationChange = () => {
|
||||
// 如果不需要学历证书,清除学历证书字段、文件列表和验证错误
|
||||
if (!needQualificationImg.value) {
|
||||
dataForm.qualificationImg = ''
|
||||
fileList.value = []
|
||||
formRef.value?.clearValidate('qualificationImg')
|
||||
} else {
|
||||
// 如果需要学历证书,延迟清除验证(等字段显示后)
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate('qualificationImg')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 学位改变时的处理
|
||||
const handleDegreeChange = () => {
|
||||
// 如果不需要学位证书,清除学位证书字段、文件列表和验证错误
|
||||
if (!needDegreeImg.value) {
|
||||
dataForm.degreeImg = ''
|
||||
fileListB.value = []
|
||||
formRef.value?.clearValidate('degreeImg')
|
||||
} else {
|
||||
// 如果需要学位证书,延迟清除验证(等字段显示后)
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate('degreeImg')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 文件上传成功 - 学历证书
|
||||
const materiaUploadSuccess = (response: any) => {
|
||||
if (response.data && response.data.code === "-1") {
|
||||
@@ -313,6 +330,8 @@ const materiaUploadSuccess = (response: any) => {
|
||||
return
|
||||
}
|
||||
dataForm.qualificationImg = response.data.url
|
||||
// 清除验证错误
|
||||
formRef.value?.clearValidate('qualificationImg')
|
||||
}
|
||||
|
||||
// 文件上传成功 - 学位证书
|
||||
@@ -322,31 +341,74 @@ const materiaUploadSuccessB = (response: any) => {
|
||||
return
|
||||
}
|
||||
dataForm.degreeImg = response.data.url
|
||||
// 清除验证错误
|
||||
formRef.value?.clearValidate('degreeImg')
|
||||
}
|
||||
|
||||
// 打开对话框
|
||||
const openDialog = (row: any) => {
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=1`
|
||||
const openDialog = async (row?: any) => {
|
||||
// 新增模式:先检查是否锁定
|
||||
if (!row || !row.id) {
|
||||
try {
|
||||
const lockResponse = await checkLocked('acade')
|
||||
if (lockResponse.data) {
|
||||
message.warning("新增功能已锁定,暂不允许操作")
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 公共设置
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?type=1`
|
||||
fileList.value = []
|
||||
fileListB.value = []
|
||||
Object.assign(dataForm, {
|
||||
graduateTime: row.graduateTime || '',
|
||||
type: row.type || '',
|
||||
qualificationConfigId: row.qualificationConfigId || '',
|
||||
degreeConfigId: row.degreeConfigId || '',
|
||||
graduateSchool: row.graduateSchool || '',
|
||||
major: row.major || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
materialA: row.materialA || '',
|
||||
materialB: row.materialB || '',
|
||||
qualificationImg: row.qualificationImg || '',
|
||||
degreeImg: row.degreeImg || '',
|
||||
state: row.state || '',
|
||||
teacherNo: row.teacherNo || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
|
||||
// 根据是否有 row 设置不同的表单数据
|
||||
if (row && row.id) {
|
||||
// 编辑模式:使用传入的数据
|
||||
// 确保类型匹配(将 id 转换为字符串或数字,与选项列表保持一致)
|
||||
Object.assign(dataForm, {
|
||||
graduateTime: row.graduateTime || '',
|
||||
type: row.type !== null && row.type !== undefined ? String(row.type) : '',
|
||||
qualificationConfigId: row.qualificationConfigId !== null && row.qualificationConfigId !== undefined ? String(row.qualificationConfigId) : '',
|
||||
degreeConfigId: row.degreeConfigId !== null && row.degreeConfigId !== undefined ? String(row.degreeConfigId) : '',
|
||||
graduateSchool: row.graduateSchool || '',
|
||||
major: row.major || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
qualificationImg: row.qualificationImg || '',
|
||||
degreeImg: row.degreeImg || '',
|
||||
state: row.state || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
} else {
|
||||
// 新增模式:重置为空
|
||||
Object.assign(dataForm, {
|
||||
graduateTime: '',
|
||||
type: '',
|
||||
qualificationConfigId: '',
|
||||
degreeConfigId: '',
|
||||
graduateSchool: '',
|
||||
major: '',
|
||||
certificateNumber: '',
|
||||
qualificationImg: '',
|
||||
degreeImg: '',
|
||||
state: '',
|
||||
id: ''
|
||||
})
|
||||
}
|
||||
|
||||
// 公共操作:先加载字典数据,再显示对话框
|
||||
await initDicData()
|
||||
// 等待字典数据加载完成后再显示表单,确保选项列表已准备好
|
||||
await nextTick()
|
||||
showForm.value = true
|
||||
initDicData()
|
||||
dialogVisible.value = true
|
||||
// 对话框打开后,清除所有验证错误,避免立即显示验证提示
|
||||
await nextTick()
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -357,13 +419,31 @@ const dialogSubmit = async () => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
dataForm.state = '0'
|
||||
await putObj(dataForm)
|
||||
message.success("操作成功")
|
||||
visible.value = false
|
||||
// 统一使用 addObj 接口(新增和编辑都使用)
|
||||
const submitData: any = {
|
||||
graduateTime: dataForm.graduateTime,
|
||||
type: dataForm.type,
|
||||
qualificationConfigId: dataForm.qualificationConfigId,
|
||||
degreeConfigId: dataForm.degreeConfigId,
|
||||
graduateSchool: dataForm.graduateSchool,
|
||||
major: dataForm.major,
|
||||
certificateNumber: dataForm.certificateNumber,
|
||||
qualificationImg: dataForm.qualificationImg,
|
||||
degreeImg: dataForm.degreeImg,
|
||||
state: '' // 待审核
|
||||
}
|
||||
|
||||
// 编辑时需要传递 id
|
||||
if (dataForm.id) {
|
||||
submitData.id = dataForm.id
|
||||
}
|
||||
|
||||
await addObj(submitData)
|
||||
message.success(dataForm.id ? "修改成功" : "提交成功")
|
||||
dialogVisible.value = false
|
||||
emit('refreshData')
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -1,28 +1,6 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 图表统计 -->
|
||||
<div style="text-align: right; margin-bottom: 20px;">
|
||||
<el-collapse accordion @change="initChartOption">
|
||||
<el-collapse-item title="学历统计">
|
||||
<div style="width: 100%">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="chartRef" style="width: 100%; height: 400px;" :option="chartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="chartData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="xl" label="学历" width="180" align="center" />
|
||||
<el-table-column prop="total" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="占比" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<search-form
|
||||
v-show="showSearch"
|
||||
@@ -36,7 +14,6 @@
|
||||
<el-select
|
||||
v-model="search.state"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请选择审核状态"
|
||||
>
|
||||
<el-option
|
||||
@@ -52,7 +29,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherNo"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入工号"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -61,7 +37,6 @@
|
||||
<el-input
|
||||
v-model="search.realName"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -87,7 +62,8 @@
|
||||
v-if="permissions.professional_professionalteacheracademicrelation_add">新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
v-if="permissions.professional_teacherbase_export"
|
||||
@click="handleDownLoadWord"
|
||||
@@ -109,25 +85,26 @@
|
||||
|
||||
<el-table-column prop="state" label="审核状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<AuditState :state="scope.row.state" :options="auditStateOptions" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="graduateTime" label="毕业时间" width="180" align="center" />
|
||||
|
||||
<el-table-column prop="degreeConfigId" label="学位" min-width="120" align="center" show-overflow-tooltip>
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
{{ getDegreeName(scope.row.degreeConfigId) }}
|
||||
</template>
|
||||
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="graduateTime" label="毕业时间" width="180" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.graduateTime ? scope.row.graduateTime.split(' ')[0] : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="type" label="教育类型" min-width="120" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getEducationTypeName(scope.row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="qualificationConfigId" label="学历" min-width="120" align="center" show-overflow-tooltip>
|
||||
@@ -135,8 +112,15 @@
|
||||
{{ getQualificationName(scope.row.qualificationConfigId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="degreeConfigId" label="学位" min-width="120" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getDegreeName(scope.row.degreeConfigId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
|
||||
|
||||
<!-- <el-table-column prop="createTime" label="创建时间" width="180" align="center" /> -->
|
||||
|
||||
<el-table-column label="学历证书附件" width="130" align="center">
|
||||
<template #default="scope">
|
||||
@@ -144,10 +128,11 @@
|
||||
v-if="scope.row.qualificationImg && scope.row.qualificationImg !== ''"
|
||||
type="primary"
|
||||
link
|
||||
icon="Document"
|
||||
@click="handlePreview(scope.row.qiList, 1)">查看
|
||||
</el-button>
|
||||
</el-button>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="学位证书附件" width="130" align="center">
|
||||
@@ -156,40 +141,44 @@
|
||||
v-if="scope.row.degreeImg && scope.row.degreeImg !== ''"
|
||||
type="primary"
|
||||
link
|
||||
icon="Document"
|
||||
@click="handlePreview(scope.row.deList, 2)">查看
|
||||
</el-button>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Edit"
|
||||
icon="edit-pen"
|
||||
v-if="permissions.professional_professionalteacheracademicrelation_edit && (scope.row.state === '0' || scope.row.state === '-2')"
|
||||
@click="handleEdit(scope.row)">编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
icon="Check"
|
||||
icon="CircleCheck"
|
||||
v-if="permissions.professional_professionalteacheracademicrelation_exam && scope.row.state === '0'"
|
||||
@click="changeState(scope.row, 1)">通过
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
icon="Close"
|
||||
icon="CircleClose"
|
||||
v-if="permissions.professional_professionalteacheracademicrelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
|
||||
@click="changeState(scope.row, -2)">驳回
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
type="primary"
|
||||
link
|
||||
icon="Delete"
|
||||
icon="delete"
|
||||
v-if="permissions.professional_professionalteacheracademicrelation_del"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -203,18 +192,15 @@
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
|
||||
<!-- 材料预览对话框 -->
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" append-to-body width="90%">
|
||||
<auth-img
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
style="height:1000px;"
|
||||
/>
|
||||
</el-dialog>
|
||||
<!-- 材料预览:图片直接显示,PDF 在组件内部 dialog 中显示 -->
|
||||
<preview-file
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
:dialog-title="dialogTitle"
|
||||
/>
|
||||
|
||||
<!-- 子组件 -->
|
||||
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
@@ -229,28 +215,29 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { useMessageBox } from '/@/hooks/message'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import { TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import {
|
||||
fetchList,
|
||||
putObj,
|
||||
examObj,
|
||||
delObj,
|
||||
getChartOption,
|
||||
exportExcel
|
||||
} 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 { defineAsyncComponent } from 'vue'
|
||||
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
|
||||
import DataForm from './form.vue'
|
||||
import ProfessionalBackResaon from '/@/views/professional/common/professional-back-resaon.vue'
|
||||
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
|
||||
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'))
|
||||
|
||||
// 注册 ECharts 组件
|
||||
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
||||
// 审核状态选项(独立定义,防止其他页面修改时被波及)
|
||||
import type { StateOption } from '/@/components/AuditState/index.vue'
|
||||
const auditStateOptions: StateOption[] = [
|
||||
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
|
||||
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
|
||||
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
|
||||
]
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -275,10 +262,8 @@ const { professional_state: professionalState } = useDict('professional_state')
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const multiDialogRef = ref()
|
||||
const dataFormRef = ref()
|
||||
const backReasonRef = ref()
|
||||
const chartRef = ref()
|
||||
const showSearch = ref(true)
|
||||
|
||||
// 搜索表单数据
|
||||
@@ -288,21 +273,17 @@ const search = reactive({
|
||||
realName: ''
|
||||
})
|
||||
|
||||
// 图表数据
|
||||
const chartOption = ref<any>({})
|
||||
const chartData = ref<any[]>([])
|
||||
|
||||
// 材料预览
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 学位和学历列表
|
||||
// 学位、学历和教育类型列表
|
||||
const degreeList = ref<any[]>([])
|
||||
const qualificationList = ref<any[]>([])
|
||||
const educationTypeList = ref<any[]>([])
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
@@ -334,34 +315,6 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
// 初始化图表
|
||||
const initChartOption = async () => {
|
||||
try {
|
||||
const response = await getChartOption()
|
||||
chartOption.value = response.data.data || {}
|
||||
|
||||
// 处理图表数据(如果有的话)
|
||||
if (chartOption.value.series && chartOption.value.series[0] && chartOption.value.series[0].data) {
|
||||
let total = 0
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
total += item.value || 0
|
||||
})
|
||||
|
||||
chartData.value = []
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = total > 0 ? Number((item.value / total * 100).toFixed(1)) : 0
|
||||
chartData.value.push({
|
||||
xl: item.name,
|
||||
total: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Failed to load chart data
|
||||
}
|
||||
}
|
||||
|
||||
// 预览材料
|
||||
const handlePreview = (list: string[], type: number) => {
|
||||
imgUrl.value = []
|
||||
@@ -378,10 +331,6 @@ const handlePreview = (list: string[], type: number) => {
|
||||
} else if (type === 2) {
|
||||
dialogTitle.value = '学位证书附件'
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
dialogVisible.value = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -392,14 +341,15 @@ const changeState = (row: any, val: number) => {
|
||||
const str = '通过'
|
||||
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
|
||||
try {
|
||||
await putObj({
|
||||
await examObj({
|
||||
id: row.id,
|
||||
state: val
|
||||
})
|
||||
message.success('操作成功')
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to change state
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
} else if (val === -2) {
|
||||
@@ -425,7 +375,7 @@ const resetQuery = () => {
|
||||
|
||||
// 打开新增窗口
|
||||
const handleAdd = () => {
|
||||
multiDialogRef.value?.init(1)
|
||||
dataFormRef.value?.openDialog()
|
||||
}
|
||||
|
||||
// 打开编辑窗口
|
||||
@@ -444,8 +394,9 @@ const handleDel = (row: any) => {
|
||||
state.pagination.current = state.pagination.current - 1
|
||||
}
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to delete
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
@@ -486,20 +437,37 @@ const getQualificationName = (id: string | number) => {
|
||||
return item ? item.qualificationName : '-'
|
||||
}
|
||||
|
||||
// 获取教育类型名称
|
||||
const getEducationTypeName = (id: string | number | undefined) => {
|
||||
if (!id) return '-'
|
||||
const item = educationTypeList.value.find((item: any) => item.id === id || String(item.id) === String(id))
|
||||
return item ? item.name : '-'
|
||||
}
|
||||
|
||||
// 加载字典数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
// 并行加载所有字典数据
|
||||
const [degreeRes, qualRes, eduTypeRes] = await Promise.all([
|
||||
getDegreeList(),
|
||||
getQualificationList(),
|
||||
getAllTypeList()
|
||||
])
|
||||
|
||||
// 加载学位列表
|
||||
const degreeRes = await getDegreeList()
|
||||
if (degreeRes && degreeRes.data) {
|
||||
degreeList.value = degreeRes.data
|
||||
}
|
||||
|
||||
// 加载学历列表
|
||||
const qualRes = await getQualificationList()
|
||||
if (qualRes && qualRes.data) {
|
||||
qualificationList.value = qualRes.data
|
||||
}
|
||||
|
||||
// 加载教育类型列表
|
||||
if (eduTypeRes && eduTypeRes.data) {
|
||||
educationTypeList.value = eduTypeRes.data
|
||||
}
|
||||
} catch (error) {
|
||||
// Failed to load dict data
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 页面标题 -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
<h2 style="margin: 0; font-size: 20px; font-weight: 600; color: #303133;">学历统计</h2>
|
||||
</div>
|
||||
|
||||
<!-- 图表统计 -->
|
||||
<div style="width: 100%">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="chartRef" style="width: 100%; height: 400px;" :option="chartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="chartData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="xl" label="学历" width="180" align="center" />
|
||||
<el-table-column prop="total" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="占比" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import { TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { getChartOption } from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
|
||||
// 注册 ECharts 组件
|
||||
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
||||
|
||||
// 图表引用
|
||||
const chartRef = ref()
|
||||
|
||||
// 图表数据
|
||||
const chartOption = ref<any>({})
|
||||
const chartData = ref<any[]>([])
|
||||
|
||||
// 初始化图表
|
||||
const initChartOption = async () => {
|
||||
try {
|
||||
const response = await getChartOption()
|
||||
chartOption.value = response.data.data || {}
|
||||
|
||||
// 处理图表数据
|
||||
if (chartOption.value.series && chartOption.value.series[0] && chartOption.value.series[0].data) {
|
||||
let total = 0
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
total += item.value || 0
|
||||
})
|
||||
|
||||
chartData.value = []
|
||||
chartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = total > 0 ? Number((item.value / total * 100).toFixed(1)) : 0
|
||||
chartData.value.push({
|
||||
xl: item.name,
|
||||
total: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Failed to load chart data
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时初始化图表
|
||||
onMounted(() => {
|
||||
initChartOption()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="编辑教师资格证" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<el-dialog v-model="dialogVisible" title="编辑教师资格证" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<div v-if="showForm">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
@@ -10,43 +10,31 @@
|
||||
<el-form-item label="类型" prop="certificateConfId">
|
||||
<el-select
|
||||
v-model="dataForm.certificateConfId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择类型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in teacherCertificateList"
|
||||
:key="item.id"
|
||||
:label="item.cretificateName || item.certificateName"
|
||||
:label="item.cretificateName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="取证时间" prop="certificateTime">
|
||||
<el-date-picker
|
||||
v-model="dataForm.certificateTime"
|
||||
type="date"
|
||||
placeholder="请选择取证时间"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="证书编号" prop="certificateNumber">
|
||||
<el-input
|
||||
v-model="dataForm.certificateNumber"
|
||||
placeholder="请输入证书编号"
|
||||
clearable
|
||||
placeholder="请输入证书编号(仅支持英文和数字)"
|
||||
show-word-limit
|
||||
maxlength="64"
|
||||
@input="handleCertificateNumberInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="材料1" prop="materialA">
|
||||
<el-form-item label="证明材料" prop="evidenceA" required>
|
||||
<el-upload
|
||||
:headers="headers"
|
||||
style="width:3.5cm;height:5.3cm;"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:file-list="fileList"
|
||||
@@ -55,24 +43,9 @@
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="材料2" prop="materialB">
|
||||
<el-upload
|
||||
:headers="headers"
|
||||
style="width:3.5cm;height:5.3cm;"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:file-list="fileListB"
|
||||
:on-success="materiaUploadSuccessB"
|
||||
:accept="'.jpg,.jpeg,.png,.pdf'"
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
@@ -81,7 +54,7 @@
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -92,18 +65,12 @@
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
|
||||
import { putObj } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
|
||||
import { addObj } from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
|
||||
import { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
visible?: boolean
|
||||
}>()
|
||||
import { checkLocked } from '/@/api/professional/professionalstatuslock'
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'refreshData'): void
|
||||
}>()
|
||||
|
||||
@@ -114,23 +81,15 @@ const message = useMessage()
|
||||
const formRef = ref()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 对话框显示状态
|
||||
const visible = computed({
|
||||
get: () => props.visible || false,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
// 对话框显示状态(内部管理)
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const dataForm = reactive({
|
||||
certificateConfId: '',
|
||||
certificateTime: '',
|
||||
certificateNumber: '',
|
||||
materialA: '',
|
||||
materialB: '',
|
||||
evidenceA: '',
|
||||
evidenceB: '',
|
||||
state: '',
|
||||
teacherNo: '',
|
||||
id: ''
|
||||
})
|
||||
|
||||
@@ -139,48 +98,23 @@ const formRules = {
|
||||
certificateConfId: [
|
||||
{ required: true, message: '请选择类型', trigger: 'change' }
|
||||
],
|
||||
certificateTime: [
|
||||
{ required: true, message: '请选择取证时间', trigger: 'change' }
|
||||
],
|
||||
certificateNumber: [
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' }
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
|
||||
],
|
||||
evidenceA: [
|
||||
{ required: true, message: '请上传证明材料', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 教师资格证列表
|
||||
const teacherCertificateList = ref<any[]>([])
|
||||
|
||||
// 基础信息
|
||||
const baseInfoAbout = reactive<{
|
||||
stationTypeList: any[]
|
||||
atStationList: any[]
|
||||
teacherTypeList: any[]
|
||||
employmentNatureList: any[]
|
||||
stationLevelList: any[]
|
||||
stationDutyLevelList: any[]
|
||||
workTypeList: any[]
|
||||
proTitleList: any[]
|
||||
majorStationList: any[]
|
||||
qualificationList: any[]
|
||||
partBranchList: any[]
|
||||
}>({
|
||||
stationTypeList: [],
|
||||
atStationList: [],
|
||||
teacherTypeList: [],
|
||||
employmentNatureList: [],
|
||||
stationLevelList: [],
|
||||
stationDutyLevelList: [],
|
||||
workTypeList: [],
|
||||
proTitleList: [],
|
||||
majorStationList: [],
|
||||
qualificationList: [],
|
||||
partBranchList: []
|
||||
})
|
||||
// 基础信息(简化,只保留需要的)
|
||||
|
||||
// 上传相关
|
||||
const url = ref('')
|
||||
const fileList = ref<any[]>([])
|
||||
const fileListB = ref<any[]>([])
|
||||
|
||||
// 请求头
|
||||
const headers = computed(() => {
|
||||
@@ -195,74 +129,74 @@ const showForm = ref(false)
|
||||
// 初始化字典数据
|
||||
const initDicData = async () => {
|
||||
try {
|
||||
const [response, certResponse] = await Promise.all([
|
||||
getAllInfoAboutList(),
|
||||
getTeacherCertificateList()
|
||||
])
|
||||
|
||||
const map = response.data.data
|
||||
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
|
||||
baseInfoAbout.atStationList = map['atStationList'] || []
|
||||
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
|
||||
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
|
||||
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
|
||||
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
|
||||
baseInfoAbout.workTypeList = map['workTypeList'] || []
|
||||
baseInfoAbout.proTitleList = map['proTitleList'] || []
|
||||
baseInfoAbout.majorStationList = map['majorStationList'] || []
|
||||
baseInfoAbout.qualificationList = map['qualificationList'] || []
|
||||
baseInfoAbout.partBranchList = map['partBranchList'] || []
|
||||
|
||||
// 获取教师资格证列表
|
||||
if (certResponse && certResponse.data) {
|
||||
teacherCertificateList.value = Array.isArray(certResponse.data)
|
||||
? certResponse.data
|
||||
: (certResponse.data.records || certResponse.data.list || [])
|
||||
}
|
||||
|
||||
visible.value = true
|
||||
const res = await getTeacherCertificateList()
|
||||
teacherCertificateList.value = res.data || []
|
||||
} catch (error) {
|
||||
// 获取字典数据失败
|
||||
}
|
||||
}
|
||||
|
||||
// 文件上传成功 - 材料A
|
||||
// 证书编号输入处理(只允许英文和数字)
|
||||
const handleCertificateNumberInput = (value: string) => {
|
||||
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
|
||||
}
|
||||
|
||||
// 文件上传成功
|
||||
const materiaUploadSuccess = (response: any) => {
|
||||
if (response.data && response.data.code === "-1") {
|
||||
message.error("当前不允许上传文件")
|
||||
return
|
||||
}
|
||||
dataForm.evidenceA = response.data.url
|
||||
}
|
||||
|
||||
// 文件上传成功 - 材料B
|
||||
const materiaUploadSuccessB = (response: any) => {
|
||||
if (response.data && response.data.code === "-1") {
|
||||
message.error("当前不允许上传文件")
|
||||
return
|
||||
}
|
||||
dataForm.evidenceB = response.data.url
|
||||
// 清除验证错误
|
||||
formRef.value?.clearValidate('evidenceA')
|
||||
}
|
||||
|
||||
// 打开对话框
|
||||
const openDialog = (row: any) => {
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=0`
|
||||
const openDialog = async (row?: any) => {
|
||||
// 新增模式:先检查是否锁定
|
||||
if (!row || !row.id) {
|
||||
try {
|
||||
const lockResponse = await checkLocked('teacherTitle')
|
||||
if (lockResponse.data) {
|
||||
message.warning("新增功能已锁定,暂不允许操作")
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 公共设置
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?type=0`
|
||||
fileList.value = []
|
||||
fileListB.value = []
|
||||
Object.assign(dataForm, {
|
||||
certificateConfId: row.certificateConfId || '',
|
||||
certificateTime: row.certificateTime || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
materialA: row.materialA || '',
|
||||
materialB: row.materialB || '',
|
||||
evidenceA: row.evidenceA || '',
|
||||
evidenceB: row.evidenceB || '',
|
||||
state: row.state || '',
|
||||
teacherNo: row.teacherNo || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
|
||||
// 根据是否有 row 设置不同的表单数据
|
||||
if (row && row.id) {
|
||||
// 编辑模式:使用传入的数据
|
||||
Object.assign(dataForm, {
|
||||
certificateConfId: row.certificateConfId || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
evidenceA: row.evidenceA || '',
|
||||
state: row.state || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
} else {
|
||||
// 新增模式:重置为空
|
||||
Object.assign(dataForm, {
|
||||
certificateConfId: '',
|
||||
certificateNumber: '',
|
||||
evidenceA: '',
|
||||
state: '',
|
||||
id: ''
|
||||
})
|
||||
}
|
||||
|
||||
// 公共操作:加载字典数据并显示对话框
|
||||
await initDicData()
|
||||
showForm.value = true
|
||||
initDicData()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -273,13 +207,25 @@ const dialogSubmit = async () => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
dataForm.state = '0'
|
||||
await putObj(dataForm)
|
||||
message.success("操作成功")
|
||||
visible.value = false
|
||||
// 统一使用 addObj 接口(新增和编辑都使用)
|
||||
const submitData: any = {
|
||||
certificateConfId: dataForm.certificateConfId,
|
||||
certificateNumber: dataForm.certificateNumber,
|
||||
evidenceA: dataForm.evidenceA,
|
||||
state: '' // 待审核
|
||||
}
|
||||
|
||||
// 编辑时需要传递 id
|
||||
if (dataForm.id) {
|
||||
submitData.id = dataForm.id
|
||||
}
|
||||
|
||||
await addObj(submitData)
|
||||
message.success(dataForm.id ? "修改成功" : "提交成功")
|
||||
dialogVisible.value = false
|
||||
emit('refreshData')
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<el-select
|
||||
v-model="search.state"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请选择审核状态"
|
||||
>
|
||||
<el-option
|
||||
@@ -30,7 +29,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherNo"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入工号"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -39,7 +37,6 @@
|
||||
<el-input
|
||||
v-model="search.realName"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -65,7 +62,8 @@
|
||||
v-if="permissions.professional_professionalteachercertificaterelation_add">新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
v-if="permissions.professional_teacherbase_export"
|
||||
@click="handleDownLoadWord"
|
||||
@@ -87,18 +85,15 @@
|
||||
|
||||
<el-table-column prop="state" label="审核状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<AuditState :state="scope.row.state" :options="auditStateOptions" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="certificateConfId" label="关联资格证书" min-width="150" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
@@ -116,40 +111,44 @@
|
||||
v-if="scope.row.evidenceA && scope.row.evidenceA !== ''"
|
||||
type="primary"
|
||||
link
|
||||
icon="Document"
|
||||
@click="handlePreview(scope.row.srcList)">查看
|
||||
</el-button>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Edit"
|
||||
icon="edit-pen"
|
||||
v-if="permissions.professional_professionalteachercertificaterelation_edit && (scope.row.state === '0' || scope.row.state === '-2')"
|
||||
@click="handleEdit(scope.row)">编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
icon="Check"
|
||||
icon="CircleCheck"
|
||||
v-if="permissions.professional_professionalteachercertificaterelation_exam && scope.row.state === '0'"
|
||||
@click="changeState(scope.row, 1)">通过
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
icon="Close"
|
||||
icon="CircleClose"
|
||||
v-if="permissions.professional_professionalteachercertificaterelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
|
||||
@click="changeState(scope.row, -2)">驳回
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
type="primary"
|
||||
link
|
||||
icon="Delete"
|
||||
icon="delete"
|
||||
v-if="permissions.professional_professionalteachercertificaterelation_del"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -163,18 +162,15 @@
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
|
||||
<!-- 材料预览对话框 -->
|
||||
<el-dialog v-model="dialogVisible" title="教师资格材料" append-to-body width="90%">
|
||||
<auth-img
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
style="height:1000px;"
|
||||
/>
|
||||
</el-dialog>
|
||||
<!-- 材料预览:图片直接显示,PDF 在组件内部 dialog 中显示 -->
|
||||
<preview-file
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
dialog-title="教师资格材料"
|
||||
/>
|
||||
|
||||
<!-- 子组件 -->
|
||||
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
@@ -192,15 +188,24 @@ import { useDict } from '/@/hooks/dict'
|
||||
import { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf'
|
||||
import {
|
||||
fetchList,
|
||||
putObj,
|
||||
examObj,
|
||||
delObj,
|
||||
exportExcel
|
||||
} from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
|
||||
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 authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
|
||||
// 审核状态选项(独立定义,防止其他页面修改时被波及)
|
||||
import type { StateOption } from '/@/components/AuditState/index.vue'
|
||||
const auditStateOptions: StateOption[] = [
|
||||
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
|
||||
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
|
||||
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
|
||||
]
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -225,7 +230,6 @@ const { professional_state: professionalState } = useDict('professional_state')
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const multiDialogRef = ref()
|
||||
const dataFormRef = ref()
|
||||
const backReasonRef = ref()
|
||||
const showSearch = ref(true)
|
||||
@@ -238,7 +242,6 @@ const search = reactive({
|
||||
})
|
||||
|
||||
// 材料预览
|
||||
const dialogVisible = ref(false)
|
||||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
@@ -281,9 +284,6 @@ const handlePreview = (list: string[]) => {
|
||||
url: v
|
||||
})
|
||||
})
|
||||
nextTick(() => {
|
||||
dialogVisible.value = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -294,14 +294,15 @@ const changeState = (row: any, val: number) => {
|
||||
const str = '通过'
|
||||
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
|
||||
try {
|
||||
await putObj({
|
||||
await examObj({
|
||||
id: row.id,
|
||||
state: val
|
||||
})
|
||||
message.success('操作成功')
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to change state
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
} else if (val === -2) {
|
||||
@@ -327,7 +328,7 @@ const resetQuery = () => {
|
||||
|
||||
// 打开新增窗口
|
||||
const handleAdd = () => {
|
||||
multiDialogRef.value?.init(0)
|
||||
dataFormRef.value?.openDialog()
|
||||
}
|
||||
|
||||
// 打开编辑窗口
|
||||
@@ -346,8 +347,9 @@ const handleDel = (row: any) => {
|
||||
state.pagination.current = state.pagination.current - 1
|
||||
}
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to delete
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
@@ -1,79 +1,70 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="80%" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<el-row>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="荣誉" prop="honor">
|
||||
<el-input v-model="dataForm.honor" placeholder="请输入荣誉" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表彰单位" prop="honorCompany">
|
||||
<el-input v-model="dataForm.honorCompany" placeholder="请输入表彰单位" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年份" prop="year">
|
||||
<el-input-number
|
||||
v-model="dataForm.year"
|
||||
:controls="false"
|
||||
:min="1900"
|
||||
:max="2100"
|
||||
placeholder="请输入年份"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证明材料" prop="materialA">
|
||||
<el-upload
|
||||
:headers="headers"
|
||||
style="width:3.5cm;height:5.3cm;"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:on-success="materiaUploadSuccess"
|
||||
:file-list="fileList"
|
||||
:accept="'.jpg,.jpeg,.png,.pdf'"
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-dialog v-model="dialogVisible" :title="title" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="荣誉" prop="honor">
|
||||
<el-input v-model="dataForm.honor" placeholder="请输入荣誉" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="表彰单位" prop="honorCompany">
|
||||
<el-input v-model="dataForm.honorCompany" placeholder="请输入表彰单位" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="年份" prop="year">
|
||||
<el-date-picker
|
||||
v-model="dataForm.year"
|
||||
type="year"
|
||||
placeholder="请选择年份"
|
||||
format="YYYY"
|
||||
value-format="YYYY"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="证明材料" prop="attachment" required>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:headers="headers"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="materiaUploadSuccess"
|
||||
:on-remove="handleRemove"
|
||||
:on-exceed="handleExceed"
|
||||
:file-list="fileList"
|
||||
:accept="'.jpg,.jpeg,.png,.pdf'"
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">关 闭</el-button>
|
||||
<el-button @click="dialogSubmit" type="primary" :loading="submitLoading">保 存</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button @click="dialogSubmit" type="primary" :loading="submitLoading">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
|
||||
import { putObj } from '/@/api/professional/professionaluser/professionalteacherhonor'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
visible?: boolean
|
||||
}>()
|
||||
import { addObj } from '/@/api/professional/professionaluser/professionalteacherhonor'
|
||||
import { checkLocked } from '/@/api/professional/professionalstatuslock'
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'refreshData'): void
|
||||
}>()
|
||||
|
||||
@@ -82,23 +73,20 @@ const message = useMessage()
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref()
|
||||
const uploadRef = ref()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 对话框显示状态
|
||||
const visible = computed({
|
||||
get: () => props.visible || false,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
// 对话框显示状态(内部管理)
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const dataForm = reactive({
|
||||
honor: '',
|
||||
honorCompany: '',
|
||||
year: null as number | null,
|
||||
year: '',
|
||||
materialA: '',
|
||||
attachment: '',
|
||||
state: '',
|
||||
teacherNo: '',
|
||||
teacherName: '',
|
||||
id: ''
|
||||
})
|
||||
@@ -106,42 +94,20 @@ const dataForm = reactive({
|
||||
// 表单验证规则
|
||||
const dataRules = {
|
||||
honor: [
|
||||
{ required: true, message: '请填写荣誉', trigger: 'change' }
|
||||
{ required: true, message: '请填写荣誉', trigger: 'blur' }
|
||||
],
|
||||
honorCompany: [
|
||||
{ required: true, message: '请填写表彰单位', trigger: 'change' }
|
||||
{ required: true, message: '请填写表彰单位', trigger: 'blur' }
|
||||
],
|
||||
year: [
|
||||
{ required: true, message: '请填写年份', trigger: 'change' }
|
||||
year: [
|
||||
{ required: true, message: '请选择年份', trigger: 'change' }
|
||||
],
|
||||
attachment: [
|
||||
{ required: true, message: '请上传证明材料', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 基础信息
|
||||
const baseInfoAbout = reactive<{
|
||||
stationTypeList: any[]
|
||||
atStationList: any[]
|
||||
teacherTypeList: any[]
|
||||
employmentNatureList: any[]
|
||||
stationLevelList: any[]
|
||||
stationDutyLevelList: any[]
|
||||
workTypeList: any[]
|
||||
proTitleList: any[]
|
||||
majorStationList: any[]
|
||||
qualificationList: any[]
|
||||
partBranchList: any[]
|
||||
}>({
|
||||
stationTypeList: [],
|
||||
atStationList: [],
|
||||
teacherTypeList: [],
|
||||
employmentNatureList: [],
|
||||
stationLevelList: [],
|
||||
stationDutyLevelList: [],
|
||||
workTypeList: [],
|
||||
proTitleList: [],
|
||||
majorStationList: [],
|
||||
qualificationList: [],
|
||||
partBranchList: []
|
||||
})
|
||||
// 基础信息(简化,只保留需要的)
|
||||
|
||||
// 上传相关
|
||||
const url = ref('')
|
||||
@@ -160,55 +126,129 @@ const showForm = ref(false)
|
||||
// 标题
|
||||
const title = ref('')
|
||||
|
||||
// 初始化字典数据
|
||||
// 初始化字典数据(简化,不需要加载字典数据)
|
||||
const initDicData = async () => {
|
||||
try {
|
||||
const response = await getAllInfoAboutList()
|
||||
const map = response.data.data
|
||||
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
|
||||
baseInfoAbout.atStationList = map['atStationList'] || []
|
||||
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
|
||||
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
|
||||
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
|
||||
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
|
||||
baseInfoAbout.workTypeList = map['workTypeList'] || []
|
||||
baseInfoAbout.proTitleList = map['proTitleList'] || []
|
||||
baseInfoAbout.majorStationList = map['majorStationList'] || []
|
||||
baseInfoAbout.qualificationList = map['qualificationList'] || []
|
||||
baseInfoAbout.partBranchList = map['partBranchList'] || []
|
||||
visible.value = true
|
||||
} catch (error) {
|
||||
// 获取字典数据失败
|
||||
}
|
||||
// 综合表彰不需要额外的字典数据
|
||||
}
|
||||
|
||||
// 上传前的处理
|
||||
const beforeUpload = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 文件超出限制时的处理(当 limit=1 且已有文件时,用户上传新文件会触发此事件)
|
||||
// 根据 Element Plus 官方文档,在 on-exceed 中清空文件列表并添加新文件
|
||||
const handleExceed = (files: File[]) => {
|
||||
// 清空当前文件列表
|
||||
uploadRef.value?.clearFiles()
|
||||
fileList.value = []
|
||||
dataForm.attachment = ''
|
||||
|
||||
// 等待 DOM 更新后,手动触发新文件的上传
|
||||
nextTick(() => {
|
||||
const newFile = files[0]
|
||||
if (newFile && uploadRef.value) {
|
||||
// 通过 input 元素手动触发文件上传
|
||||
const input = uploadRef.value.$el?.querySelector('input[type="file"]')
|
||||
if (input) {
|
||||
// 创建新的 DataTransfer 对象并添加新文件
|
||||
const dataTransfer = new DataTransfer()
|
||||
dataTransfer.items.add(newFile)
|
||||
input.files = dataTransfer.files
|
||||
|
||||
// 触发 change 事件,让 Element Plus 自动处理上传
|
||||
const changeEvent = new Event('change', { bubbles: true })
|
||||
input.dispatchEvent(changeEvent)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 文件上传成功
|
||||
const materiaUploadSuccess = (response: any) => {
|
||||
const materiaUploadSuccess = (response: any, file: any) => {
|
||||
if (response.data && response.data.code === "-1") {
|
||||
message.error("当前不允许上传文件")
|
||||
// 上传失败,移除文件
|
||||
fileList.value = []
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
dataForm.attachment = response.data.url
|
||||
if (response.data && response.data.url) {
|
||||
dataForm.attachment = response.data.url
|
||||
// 更新文件列表,确保显示新上传的文件(替换旧文件)
|
||||
fileList.value = [{
|
||||
name: file.name || '证明材料',
|
||||
url: response.data.url,
|
||||
uid: file.uid || Date.now(),
|
||||
status: 'success'
|
||||
}]
|
||||
formRef.value?.clearValidate('attachment')
|
||||
}
|
||||
}
|
||||
|
||||
// 文件删除处理
|
||||
const handleRemove = () => {
|
||||
dataForm.attachment = ''
|
||||
fileList.value = []
|
||||
formRef.value?.clearValidate('attachment')
|
||||
}
|
||||
|
||||
// 打开对话框
|
||||
const openDialog = (row: any) => {
|
||||
title.value = row.teacherName || ''
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=4`
|
||||
fileList.value = []
|
||||
Object.assign(dataForm, {
|
||||
honor: row.honor || '',
|
||||
honorCompany: row.honorCompany || '',
|
||||
year: row.year || null,
|
||||
materialA: row.materialA || '',
|
||||
attachment: row.attachment || '',
|
||||
state: row.state || '',
|
||||
teacherNo: row.teacherNo || '',
|
||||
teacherName: row.teacherName || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
const openDialog = async (row?: any) => {
|
||||
// 新增模式:先检查是否锁定
|
||||
if (!row || !row.id) {
|
||||
try {
|
||||
const lockResponse = await checkLocked('remix')
|
||||
if (lockResponse.data) {
|
||||
message.warning("新增功能已锁定,暂不允许操作")
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 根据是否有 row 设置不同的表单数据
|
||||
if (row && row.id) {
|
||||
// 编辑模式:使用传入的数据
|
||||
title.value = row.teacherName || ''
|
||||
Object.assign(dataForm, {
|
||||
honor: row.honor || '',
|
||||
honorCompany: row.honorCompany || '',
|
||||
year: row.year ? String(row.year) : '',
|
||||
materialA: row.materialA || '',
|
||||
attachment: row.attachment || '',
|
||||
state: row.state || '',
|
||||
teacherName: row.teacherName || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
// 回显已上传的证明材料
|
||||
fileList.value = dataForm.attachment
|
||||
? [{ name: '证明材料', url: dataForm.attachment }]
|
||||
: []
|
||||
} else {
|
||||
// 新增模式:重置为空
|
||||
title.value = '新增综合表彰'
|
||||
Object.assign(dataForm, {
|
||||
honor: '',
|
||||
honorCompany: '',
|
||||
year: '',
|
||||
attachment: '',
|
||||
state: '',
|
||||
teacherName: '',
|
||||
id: ''
|
||||
})
|
||||
fileList.value = []
|
||||
}
|
||||
|
||||
// 公共设置:每次打开都重置上传地址
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?type=4`
|
||||
|
||||
// 公共操作:加载字典数据并显示对话框
|
||||
await initDicData()
|
||||
showForm.value = true
|
||||
initDicData()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -219,13 +259,13 @@ const dialogSubmit = async () => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
dataForm.state = '0'
|
||||
await putObj(dataForm)
|
||||
message.success("操作成功")
|
||||
visible.value = false
|
||||
// 统一使用 addObj 接口(新增和编辑都使用同一个接口)
|
||||
await addObj(dataForm)
|
||||
message.success(dataForm.id ? "修改成功" : "提交成功")
|
||||
dialogVisible.value = false
|
||||
emit('refreshData')
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<el-select
|
||||
v-model="search.state"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请选择审核状态"
|
||||
>
|
||||
<el-option
|
||||
@@ -30,7 +29,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherNo"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入工号"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -39,7 +37,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherName"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -65,10 +62,11 @@
|
||||
v-if="permissions.professional_professionalteacherhonor_add">新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading">导出
|
||||
:loading="exportLoading">导出信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
@@ -86,18 +84,15 @@
|
||||
|
||||
<el-table-column prop="state" label="审核状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.state === '1'" type="success">通过</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '-2'" type="danger">驳回</el-tag>
|
||||
<el-tag v-else-if="scope.row.state === '0'" type="warning">待审核</el-tag>
|
||||
<span v-else>-</span>
|
||||
<AuditState :state="scope.row.state" :options="auditStateOptions" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="honor" label="荣誉" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
@@ -111,40 +106,44 @@
|
||||
v-if="scope.row.attachment && scope.row.attachment !== ''"
|
||||
type="primary"
|
||||
link
|
||||
@click="showEdvince(scope.row)">查 看
|
||||
icon="Document"
|
||||
@click="showEdvince(scope.row)">查看
|
||||
</el-button>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Edit"
|
||||
icon="edit-pen"
|
||||
v-if="permissions.professional_professionalteacherhonor_edit && (scope.row.state === '0' || scope.row.state === '-2')"
|
||||
@click="handleEdit(scope.row)">编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
icon="Check"
|
||||
icon="CircleCheck"
|
||||
v-if="permissions.professional_professionalteacherhonor_exam && scope.row.state === '0'"
|
||||
@click="changeState(scope.row, 1)">通过
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
icon="Close"
|
||||
icon="CircleClose"
|
||||
v-if="permissions.professional_professionalteacherhonor_exam && (scope.row.state === '0' || scope.row.state === '1')"
|
||||
@click="changeState(scope.row, -2)">驳回
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
type="primary"
|
||||
link
|
||||
icon="Delete"
|
||||
icon="delete"
|
||||
v-if="permissions.professional_professionalteacherhonor_del"
|
||||
style="margin-left: 12px"
|
||||
@click="handleDel(scope.row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -158,17 +157,23 @@
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
|
||||
<!-- 材料预览:图片直接显示,PDF 在组件内部 dialog 中显示 -->
|
||||
<preview-file
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
dialog-title="综合表彰材料"
|
||||
/>
|
||||
|
||||
<!-- 子组件 -->
|
||||
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
|
||||
<ShowHonorEdvince ref="showHonorEdvinceRef" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<DataForm ref="dataFormRef" />
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
@@ -177,14 +182,15 @@ import { useMessageBox } from '/@/hooks/message'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import {
|
||||
fetchList,
|
||||
putObj,
|
||||
examObj,
|
||||
delObj
|
||||
} from '/@/api/professional/professionaluser/professionalteacherhonor'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
|
||||
import ShowHonorEdvince from './showHonorEdvince.vue'
|
||||
import ProfessionalBackResaon from '/@/views/professional/common/professional-back-resaon.vue'
|
||||
import DataForm from './form.vue'
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -206,11 +212,17 @@ const messageBox = useMessageBox()
|
||||
// 字典数据
|
||||
const { professional_state: professionalState } = useDict('professional_state')
|
||||
|
||||
// 审核状态选项(独立定义,防止其他页面修改时被波及)
|
||||
import type { StateOption } from '/@/components/AuditState/index.vue'
|
||||
const auditStateOptions: StateOption[] = [
|
||||
{ value: '1', label: '已通过', type: 'success', icon: 'fa-solid fa-circle-check', effect: 'dark' },
|
||||
{ value: '-2', label: '已驳回', type: 'danger', icon: 'fa-solid fa-circle-xmark', effect: 'dark' },
|
||||
{ value: '0', label: '待审核', type: 'warning', icon: 'fa-regular fa-clock', effect: 'light' }
|
||||
]
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const multiDialogRef = ref()
|
||||
const showHonorEdvinceRef = ref()
|
||||
const backReasonRef = ref()
|
||||
const dataFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
@@ -222,6 +234,9 @@ const search = reactive({
|
||||
teacherName: ''
|
||||
})
|
||||
|
||||
// 材料预览
|
||||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
|
||||
@@ -241,9 +256,18 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
// 查看证明材料
|
||||
// 查看证明材料:构造预览列表,交给 auth-img 处理
|
||||
const showEdvince = (row: any) => {
|
||||
showHonorEdvinceRef.value?.init(row)
|
||||
imgUrl.value = []
|
||||
nextTick(() => {
|
||||
const list = row.attachment ? [row.attachment] : []
|
||||
list.forEach(v => {
|
||||
imgUrl.value.push({
|
||||
title: '',
|
||||
url: v
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 审核状态变更
|
||||
@@ -253,20 +277,20 @@ const changeState = (row: any, val: number) => {
|
||||
const str = '通过'
|
||||
messageBox.confirm(`是否确认${str}${row.teacherName || row.realName}的申请`).then(async () => {
|
||||
try {
|
||||
await putObj({
|
||||
await examObj({
|
||||
id: row.id,
|
||||
state: val
|
||||
})
|
||||
message.success('操作成功')
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to change state
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
} else if (val === -2) {
|
||||
// 驳回
|
||||
const newRow = JSON.parse(JSON.stringify(row))
|
||||
newRow.realName = newRow.teacherName
|
||||
backReasonRef.value?.init(newRow, 'honor')
|
||||
}
|
||||
}
|
||||
@@ -288,7 +312,7 @@ const resetQuery = () => {
|
||||
|
||||
// 打开新增窗口
|
||||
const handleAdd = () => {
|
||||
multiDialogRef.value?.init(4)
|
||||
dataFormRef.value?.openDialog()
|
||||
}
|
||||
|
||||
// 打开编辑窗口
|
||||
@@ -307,8 +331,9 @@ const handleDel = (row: any) => {
|
||||
state.pagination.current = state.pagination.current - 1
|
||||
}
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to delete
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" width="90%" append-to-body title="综合表彰材料">
|
||||
<auth-img
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
style="height:1000px;"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick } from 'vue'
|
||||
import authImg from '/@/components/tools/auth-img.vue'
|
||||
|
||||
interface ImageItem {
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const visible = ref(false)
|
||||
const row = ref<any>({})
|
||||
const imgUrl = ref<ImageItem[]>([])
|
||||
|
||||
const init = (rowData: any) => {
|
||||
row.value = rowData
|
||||
imgUrl.value = []
|
||||
nextTick(() => {
|
||||
const obj: ImageItem = {
|
||||
title: '',
|
||||
url: row.value.attachment
|
||||
}
|
||||
imgUrl.value.push(obj)
|
||||
visible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -6,7 +6,7 @@
|
||||
v-show="showSearch"
|
||||
:model="search"
|
||||
ref="searchFormRef"
|
||||
@keyup-enter="handleFilter(search)"
|
||||
@keyup-enter="handleFilter"
|
||||
>
|
||||
<template #default="{ visible }">
|
||||
<template v-if="visible">
|
||||
@@ -14,7 +14,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherNo"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入工号"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -23,7 +22,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherName"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -33,7 +31,7 @@
|
||||
<!-- 操作按钮 -->
|
||||
<template #actions>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFilter(search)" icon="Search">查询</el-button>
|
||||
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
@@ -50,15 +48,17 @@
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<TeacherNameNo :name="scope.row.teacherName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="oldDeptName" label="原部门名称" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="newDeptName" label="现部门名称" min-width="150" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="changeDate" label="调令日期" width="120" align="center" />
|
||||
<el-table-column prop="changeDate" label="调令日期" min-width="120" align="center" />
|
||||
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
@@ -84,9 +84,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList } from '/@/api/professional/professionaluser/professionalteacherstationchange'
|
||||
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="编辑职称" width="800px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<el-dialog v-model="dialogVisible" title="编辑职称" width="600px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<div v-if="showForm">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
@@ -12,8 +12,7 @@
|
||||
v-model="dataForm.professionalTitleConfigId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择职称等级"
|
||||
style="width: 100%"
|
||||
placeholder="请选择职称等级"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in proTitleList"
|
||||
@@ -30,7 +29,6 @@
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择专业技术职务"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in majorStationList"
|
||||
@@ -48,7 +46,6 @@
|
||||
placeholder="请选择取证时间"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -59,22 +56,23 @@
|
||||
placeholder="请选择职称任职时间"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="证书编号" prop="certificateNumber">
|
||||
<el-input
|
||||
v-model="dataForm.certificateNumber"
|
||||
placeholder="请输入证书编号"
|
||||
placeholder="请输入证书编号(仅支持英文和数字)"
|
||||
clearable
|
||||
show-word-limit
|
||||
maxlength="32"
|
||||
@input="handleCertificateNumberInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="证明材料" prop="materialA">
|
||||
<el-form-item label="证明材料" prop="evidence">
|
||||
<el-upload
|
||||
:headers="headers"
|
||||
style="width:3.5cm;height:5.3cm;"
|
||||
:limit="1"
|
||||
:action="url"
|
||||
:file-list="fileList"
|
||||
@@ -83,7 +81,9 @@
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-tag>仅支持jpg,jpeg,png,pdf后缀的文件上传</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogSubmit" :loading="submitLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -103,17 +103,13 @@
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { getAllInfoAboutList } from '/@/api/professional/teacherbase'
|
||||
import { putObj } from '/@/api/professional/professionaluser/professionaltitlerelation'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
visible?: boolean
|
||||
}>()
|
||||
import { getProfessionalTitleList } from '/@/api/professional/rsbase/professionaltitlelevelconfig'
|
||||
import { getMajorStationList } from '/@/api/professional/rsbase/professionalmajorstation'
|
||||
import { addObj } from '/@/api/professional/professionaluser/professionaltitlerelation'
|
||||
import { checkLocked } from '/@/api/professional/professionalstatuslock'
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'refreshData'): void
|
||||
}>()
|
||||
|
||||
@@ -124,11 +120,8 @@ const message = useMessage()
|
||||
const formRef = ref()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 对话框显示状态
|
||||
const visible = computed({
|
||||
get: () => props.visible || false,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
// 对话框显示状态(内部管理)
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const dataForm = reactive({
|
||||
@@ -137,10 +130,8 @@ const dataForm = reactive({
|
||||
certificateTime: '',
|
||||
inOfficeDate: '',
|
||||
certificateNumber: '',
|
||||
materialA: '',
|
||||
evidence: '',
|
||||
state: '',
|
||||
teacherNo: '',
|
||||
id: ''
|
||||
})
|
||||
|
||||
@@ -156,35 +147,21 @@ const formRules = {
|
||||
{ required: true, message: '请选择取证时间', trigger: 'change' }
|
||||
],
|
||||
certificateNumber: [
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' }
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
|
||||
],
|
||||
evidence: [
|
||||
{ required: true, message: '请上传证明材料', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 基础信息
|
||||
const baseInfoAbout = reactive<{
|
||||
stationTypeList: any[]
|
||||
atStationList: any[]
|
||||
teacherTypeList: any[]
|
||||
employmentNatureList: any[]
|
||||
stationLevelList: any[]
|
||||
stationDutyLevelList: any[]
|
||||
workTypeList: any[]
|
||||
proTitleList: any[]
|
||||
majorStationList: any[]
|
||||
qualificationList: any[]
|
||||
partBranchList: any[]
|
||||
}>({
|
||||
stationTypeList: [],
|
||||
atStationList: [],
|
||||
teacherTypeList: [],
|
||||
employmentNatureList: [],
|
||||
stationLevelList: [],
|
||||
stationDutyLevelList: [],
|
||||
workTypeList: [],
|
||||
proTitleList: [],
|
||||
majorStationList: [],
|
||||
qualificationList: [],
|
||||
partBranchList: []
|
||||
majorStationList: []
|
||||
})
|
||||
|
||||
// 上传相关
|
||||
@@ -210,52 +187,93 @@ const majorStationList = computed(() => baseInfoAbout.majorStationList as any[])
|
||||
// 初始化字典数据
|
||||
const initDicData = async () => {
|
||||
try {
|
||||
const response = await getAllInfoAboutList()
|
||||
const map = response.data.data
|
||||
baseInfoAbout.stationTypeList = map['stationTypeList'] || []
|
||||
baseInfoAbout.atStationList = map['atStationList'] || []
|
||||
baseInfoAbout.teacherTypeList = map['teacherTypeList'] || []
|
||||
baseInfoAbout.employmentNatureList = map['employmentNatureList'] || []
|
||||
baseInfoAbout.stationLevelList = map['stationLevelList'] || []
|
||||
baseInfoAbout.stationDutyLevelList = map['stationDutyLevelList'] || []
|
||||
baseInfoAbout.workTypeList = map['workTypeList'] || []
|
||||
baseInfoAbout.proTitleList = map['proTitleList'] || []
|
||||
baseInfoAbout.majorStationList = map['majorStationList'] || []
|
||||
baseInfoAbout.qualificationList = map['qualificationList'] || []
|
||||
baseInfoAbout.partBranchList = map['partBranchList'] || []
|
||||
visible.value = true
|
||||
const [titleResponse, stationResponse] = await Promise.all([
|
||||
getProfessionalTitleList(),
|
||||
getMajorStationList()
|
||||
])
|
||||
|
||||
// 处理职称列表(与 index.vue 保持一致)
|
||||
if (titleResponse && titleResponse.data) {
|
||||
baseInfoAbout.proTitleList = titleResponse.data
|
||||
}
|
||||
|
||||
// 处理专业技术职务列表(与 index.vue 保持一致)
|
||||
if (stationResponse && stationResponse.data) {
|
||||
baseInfoAbout.majorStationList = stationResponse.data
|
||||
}
|
||||
} catch (error) {
|
||||
// 获取字典数据失败
|
||||
message.error('获取字典数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 证书编号输入处理(只允许英文和数字)
|
||||
const handleCertificateNumberInput = (value: string) => {
|
||||
dataForm.certificateNumber = value.replace(/[^A-Za-z0-9]/g, '')
|
||||
}
|
||||
|
||||
// 文件上传成功
|
||||
const materiaUploadSuccess = (response: any) => {
|
||||
if (response.data && response.data.code === "-1") {
|
||||
message.error("当前不允许上传文件")
|
||||
return
|
||||
}
|
||||
// 统一使用 evidence 字段存储证明材料URL(与后端API一致)
|
||||
dataForm.evidence = response.data.url
|
||||
// 上传成功后清除该字段的验证错误
|
||||
formRef.value?.clearValidate('evidence')
|
||||
}
|
||||
|
||||
// 打开对话框
|
||||
const openDialog = (row: any) => {
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?teacherNo=${row.teacherNo}&type=2`
|
||||
const openDialog = async (row?: any) => {
|
||||
// 新增模式:先检查是否锁定
|
||||
if (!row || !row.id) {
|
||||
try {
|
||||
const lockResponse = await checkLocked('title')
|
||||
if (lockResponse.data) {
|
||||
message.warning("新增功能已锁定,暂不允许操作")
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 公共设置
|
||||
url.value = `/professional/file/teacherAboutInfoUpload?type=2`
|
||||
fileList.value = []
|
||||
Object.assign(dataForm, {
|
||||
professionalTitleConfigId: row.professionalTitleConfigId || '',
|
||||
majorStation: row.majorStation || '',
|
||||
certificateTime: row.certificateTime || '',
|
||||
inOfficeDate: row.inOfficeDate || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
materialA: row.materialA || '',
|
||||
evidence: row.evidence || '',
|
||||
state: row.state || '',
|
||||
teacherNo: row.teacherNo || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
|
||||
// 根据是否有 row 设置不同的表单数据
|
||||
if (row && row.id) {
|
||||
// 编辑模式:使用传入的数据
|
||||
Object.assign(dataForm, {
|
||||
professionalTitleConfigId: row.professionalTitleConfigId || '',
|
||||
majorStation: row.majorStation || '',
|
||||
certificateTime: row.certificateTime || '',
|
||||
inOfficeDate: row.inOfficeDate || '',
|
||||
certificateNumber: row.certificateNumber || '',
|
||||
evidence: row.evidence || '',
|
||||
state: row.state || '',
|
||||
id: row.id || ''
|
||||
})
|
||||
} else {
|
||||
// 新增模式:重置为空
|
||||
Object.assign(dataForm, {
|
||||
professionalTitleConfigId: '',
|
||||
majorStation: '',
|
||||
certificateTime: '',
|
||||
inOfficeDate: '',
|
||||
certificateNumber: '',
|
||||
evidence: '',
|
||||
state: '',
|
||||
id: ''
|
||||
})
|
||||
}
|
||||
|
||||
// 公共操作:加载字典数据并显示对话框
|
||||
await initDicData()
|
||||
showForm.value = true
|
||||
initDicData()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -266,13 +284,28 @@ const dialogSubmit = async () => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
dataForm.state = '0'
|
||||
await putObj(dataForm)
|
||||
message.success("操作成功")
|
||||
visible.value = false
|
||||
// 统一使用 addObj 接口(新增和编辑都使用)
|
||||
const submitData: any = {
|
||||
professionalTitleConfigId: dataForm.professionalTitleConfigId,
|
||||
majorStation: dataForm.majorStation,
|
||||
certificateTime: dataForm.certificateTime,
|
||||
inOfficeDate: dataForm.inOfficeDate,
|
||||
certificateNumber: dataForm.certificateNumber,
|
||||
evidence: dataForm.evidence,
|
||||
state: '' // 待审核
|
||||
}
|
||||
|
||||
// 编辑时需要传递 id
|
||||
if (dataForm.id) {
|
||||
submitData.id = dataForm.id
|
||||
}
|
||||
|
||||
await addObj(submitData)
|
||||
message.success(dataForm.id ? "修改成功" : "提交成功")
|
||||
dialogVisible.value = false
|
||||
emit('refreshData')
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
// 错误处理已在数据请求层统一处理,此处不需要提示
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -1,41 +1,6 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 图表统计 -->
|
||||
<div style="text-align: right; margin-bottom: 20px;">
|
||||
<el-collapse accordion @change="initChartOption">
|
||||
<el-collapse-item title="职称统计">
|
||||
<div style="width: 100%">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="titleChartRef" style="width: 100%; height: 400px;" :option="titleChartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="titleChartTableData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="name" label="职称" width="180" align="center" />
|
||||
<el-table-column prop="value" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="比例" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24" style="margin-top: 20px;">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="techChartRef" style="width: 100%; height: 400px;" :option="techChartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="techChartTableData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="name" label="技术职务" width="180" align="center" />
|
||||
<el-table-column prop="value" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="比例" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<search-form
|
||||
v-show="showSearch"
|
||||
@@ -49,7 +14,6 @@
|
||||
<el-select
|
||||
v-model="search.state"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请选择审核状态"
|
||||
>
|
||||
<el-option
|
||||
@@ -65,7 +29,6 @@
|
||||
<el-input
|
||||
v-model="search.teacherNo"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入工号"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -74,7 +37,6 @@
|
||||
<el-input
|
||||
v-model="search.realName"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -84,7 +46,6 @@
|
||||
v-model="search.professionalTitleConfigId"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px"
|
||||
placeholder="请选择职称"
|
||||
>
|
||||
<el-option
|
||||
@@ -101,7 +62,6 @@
|
||||
v-model="search.majorStation"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px"
|
||||
placeholder="请选择专业技术职务"
|
||||
>
|
||||
<el-option
|
||||
@@ -134,7 +94,9 @@
|
||||
v-if="permissions.professional_professionaltitlerelation_add">新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
class="ml10"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
v-if="permissions.professional_teacherbase_export"
|
||||
@click="handleDownLoadWord"
|
||||
@@ -188,7 +150,7 @@
|
||||
v-if="scope.row.evidence && scope.row.evidence !== ''"
|
||||
type="primary"
|
||||
link
|
||||
icon="View"
|
||||
icon="Document"
|
||||
@click="handlePreview(scope.row.srcList)">查看
|
||||
</el-button>
|
||||
<span v-else>-</span>
|
||||
@@ -209,14 +171,14 @@
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
icon="check"
|
||||
icon="CircleCheck"
|
||||
v-if="permissions.professional_professionaltitlerelation_exam && scope.row.state === '0'"
|
||||
@click="changeState(scope.row, 1)">通过
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
icon="close"
|
||||
icon="CircleClose"
|
||||
v-if="permissions.professional_professionaltitlerelation_exam && (scope.row.state === '0' || scope.row.state === '1')"
|
||||
@click="changeState(scope.row, -2)">驳回
|
||||
</el-button>
|
||||
@@ -239,15 +201,13 @@
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
|
||||
<!-- 材料预览对话框 -->
|
||||
<el-dialog v-model="dialogVisible" title="职称材料" append-to-body width="90%">
|
||||
<auth-img
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
style="height:1000px;"
|
||||
/>
|
||||
</el-dialog>
|
||||
<!-- 材料预览:图片直接显示,PDF 在组件内部 dialog 中显示 -->
|
||||
<preview-file
|
||||
v-for="src in imgUrl"
|
||||
:key="src.title"
|
||||
:authSrc="src.url"
|
||||
dialog-title="职称材料"
|
||||
/>
|
||||
|
||||
<!-- 子组件 -->
|
||||
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
|
||||
@@ -265,30 +225,23 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { useMessageBox } from '/@/hooks/message'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import { TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
// 接口
|
||||
import {
|
||||
fetchList,
|
||||
putObj,
|
||||
examObj,
|
||||
delObj,
|
||||
getChartOption,
|
||||
exportRelation
|
||||
} from '/@/api/professional/professionaluser/professionaltitlerelation'
|
||||
import { getProfessionalTitleList } from '/@/api/professional/rsbase/professionaltitlelevelconfig'
|
||||
import { getMajorStationList } from '/@/api/professional/rsbase/professionalmajorstation'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
// 子组件
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
|
||||
const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/teacherbase/multiDialog.vue'))
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'))
|
||||
|
||||
// 注册 ECharts 组件
|
||||
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -316,8 +269,6 @@ const searchFormRef = ref()
|
||||
const multiDialogRef = ref()
|
||||
const dataFormRef = ref()
|
||||
const backReasonRef = ref()
|
||||
const titleChartRef = ref()
|
||||
const techChartRef = ref()
|
||||
const showSearch = ref(true)
|
||||
|
||||
// 搜索表单数据
|
||||
@@ -329,14 +280,7 @@ const search = reactive({
|
||||
majorStation: ''
|
||||
})
|
||||
|
||||
// 图表数据
|
||||
const titleChartOption = ref<any>({})
|
||||
const titleChartTableData = ref<any[]>([])
|
||||
const techChartOption = ref<any>({})
|
||||
const techChartTableData = ref<any[]>([])
|
||||
|
||||
// 材料预览
|
||||
const dialogVisible = ref(false)
|
||||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
@@ -371,54 +315,6 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
// 初始化图表
|
||||
const initChartOption = async () => {
|
||||
try {
|
||||
const response = await getChartOption()
|
||||
const data = response.data.data || {}
|
||||
|
||||
// 职称图表
|
||||
titleChartOption.value = data.titleOption || {}
|
||||
let titleTotal = 0
|
||||
if (titleChartOption.value.series && titleChartOption.value.series[0] && titleChartOption.value.series[0].data) {
|
||||
titleChartOption.value.series[0].data.forEach((item: any) => {
|
||||
titleTotal += item.value || 0
|
||||
})
|
||||
|
||||
titleChartTableData.value = []
|
||||
titleChartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = titleTotal > 0 ? Number((item.value / titleTotal * 100).toFixed(1)) : 0
|
||||
titleChartTableData.value.push({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 技术职务图表
|
||||
techChartOption.value = data.techOption || {}
|
||||
let techTotal = 0
|
||||
if (techChartOption.value.series && techChartOption.value.series[0] && techChartOption.value.series[0].data) {
|
||||
techChartOption.value.series[0].data.forEach((item: any) => {
|
||||
techTotal += item.value || 0
|
||||
})
|
||||
|
||||
techChartTableData.value = []
|
||||
techChartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = techTotal > 0 ? Number((item.value / techTotal * 100).toFixed(1)) : 0
|
||||
techChartTableData.value.push({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Failed to load chart data
|
||||
}
|
||||
}
|
||||
|
||||
// 预览材料
|
||||
const handlePreview = (list: string[]) => {
|
||||
imgUrl.value = []
|
||||
@@ -429,9 +325,6 @@ const handlePreview = (list: string[]) => {
|
||||
url: v
|
||||
})
|
||||
})
|
||||
nextTick(() => {
|
||||
dialogVisible.value = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -442,14 +335,15 @@ const changeState = (row: any, val: number) => {
|
||||
const str = '通过'
|
||||
messageBox.confirm(`是否确认${str}${row.realName}的申请`).then(async () => {
|
||||
try {
|
||||
await putObj({
|
||||
await examObj({
|
||||
id: row.id,
|
||||
state: val
|
||||
})
|
||||
message.success('操作成功')
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to change state
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
} else if (val === -2) {
|
||||
@@ -477,7 +371,7 @@ const resetQuery = () => {
|
||||
|
||||
// 打开新增窗口
|
||||
const handleAdd = () => {
|
||||
multiDialogRef.value?.init(2)
|
||||
dataFormRef.value?.openDialog()
|
||||
}
|
||||
|
||||
// 打开编辑窗口
|
||||
@@ -496,8 +390,9 @@ const handleDel = (row: any) => {
|
||||
state.pagination.current = state.pagination.current - 1
|
||||
}
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// Failed to delete
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
119
src/views/professional/professionaltitlerelation/statistics.vue
Normal file
119
src/views/professional/professionaltitlerelation/statistics.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 页面标题 -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
<h2 style="margin: 0; font-size: 20px; font-weight: 600; color: #303133;">职称统计</h2>
|
||||
</div>
|
||||
|
||||
<!-- 图表统计 -->
|
||||
<div style="width: 100%">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="titleChartRef" style="width: 100%; height: 400px;" :option="titleChartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="titleChartTableData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="name" label="职称" width="180" align="center" />
|
||||
<el-table-column prop="value" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="比例" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24" style="margin-top: 20px;">
|
||||
<el-col :span="12">
|
||||
<v-chart ref="techChartRef" style="width: 100%; height: 400px;" :option="techChartOption" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="techChartTableData" border show-summary style="width: 100%">
|
||||
<el-table-column prop="name" label="技术职务" width="180" align="center" />
|
||||
<el-table-column prop="value" label="人数" width="180" align="center" />
|
||||
<el-table-column prop="rate" label="比例" width="180" align="center" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import { TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { getChartOption } from '/@/api/professional/professionaluser/professionaltitlerelation'
|
||||
|
||||
// 注册 ECharts 组件
|
||||
use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
||||
|
||||
// 图表引用
|
||||
const titleChartRef = ref()
|
||||
const techChartRef = ref()
|
||||
|
||||
// 图表数据
|
||||
const titleChartOption = ref<any>({})
|
||||
const titleChartTableData = ref<any[]>([])
|
||||
const techChartOption = ref<any>({})
|
||||
const techChartTableData = ref<any[]>([])
|
||||
|
||||
// 初始化图表
|
||||
const initChartOption = async () => {
|
||||
try {
|
||||
const response = await getChartOption()
|
||||
const data = response.data.data || {}
|
||||
|
||||
// 职称图表
|
||||
titleChartOption.value = data.titleOption || {}
|
||||
let titleTotal = 0
|
||||
if (titleChartOption.value.series && titleChartOption.value.series[0] && titleChartOption.value.series[0].data) {
|
||||
titleChartOption.value.series[0].data.forEach((item: any) => {
|
||||
titleTotal += item.value || 0
|
||||
})
|
||||
|
||||
titleChartTableData.value = []
|
||||
titleChartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = titleTotal > 0 ? Number((item.value / titleTotal * 100).toFixed(1)) : 0
|
||||
titleChartTableData.value.push({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 技术职务图表
|
||||
techChartOption.value = data.techOption || {}
|
||||
let techTotal = 0
|
||||
if (techChartOption.value.series && techChartOption.value.series[0] && techChartOption.value.series[0].data) {
|
||||
techChartOption.value.series[0].data.forEach((item: any) => {
|
||||
techTotal += item.value || 0
|
||||
})
|
||||
|
||||
techChartTableData.value = []
|
||||
techChartOption.value.series[0].data.forEach((item: any) => {
|
||||
const rate = techTotal > 0 ? Number((item.value / techTotal * 100).toFixed(1)) : 0
|
||||
techChartTableData.value.push({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
rate: `${rate}%`
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Failed to load chart data
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时初始化图表
|
||||
onMounted(() => {
|
||||
initChartOption()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -233,8 +233,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -201,8 +201,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
// 提交失败
|
||||
} catch (error: any) {
|
||||
// 处理业务错误
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<TeacherNameNo :name="scope.row.teacherName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="year" label="年份" width="100" align="center" />
|
||||
|
||||
@@ -174,11 +176,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { fetchList, addObj, putObj, delObj } from '/@/api/professional/professionalyearbounds'
|
||||
import { fetchList, addObj, putObj, delObj } from '/@/api/professional/salaries/professionalyearbounds'
|
||||
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -287,7 +292,7 @@ const handleDel = (row: any) => {
|
||||
message.success('删除成功')
|
||||
getDataList(false) // 删除后保持当前页
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '删除失败')
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
@@ -312,7 +317,7 @@ const handleSubmit = async () => {
|
||||
dialogVisible.value = false
|
||||
getDataList(false) // 提交后保持当前页
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '操作失败')
|
||||
message.error(error.msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" width="100%" v-loading="baseLoading" top="0" :close-on-click-modal="false" destroy-on-close>
|
||||
<!--基本信息-->
|
||||
<el-card shadow="hover">
|
||||
<el-row :span="24">
|
||||
<el-col>
|
||||
<el-table
|
||||
:data="salaryData.baseInfo"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="realName"
|
||||
label="姓名"
|
||||
width="80">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="idCard"
|
||||
label="身份证号"
|
||||
width="200">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="年份"
|
||||
width="300">
|
||||
<template #default>
|
||||
<el-date-picker
|
||||
v-model="nowUser.nf"
|
||||
type="year"
|
||||
format="YYYY"
|
||||
value-format="YYYY"
|
||||
style="width: 200px"
|
||||
placeholder="选择年">
|
||||
</el-date-picker>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="月份"
|
||||
width="300">
|
||||
<template #default>
|
||||
<el-date-picker
|
||||
v-model="nowUser.yff"
|
||||
type="month"
|
||||
format="M"
|
||||
value-format="M"
|
||||
style="width: 200px"
|
||||
placeholder="选择月">
|
||||
</el-date-picker>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="200">
|
||||
<template #default>
|
||||
<el-button @click="searchUserInfo" type="primary" size="small">搜索</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover">
|
||||
<!--应发部分-->
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
:data="salaryData.baseInfo"
|
||||
size="small"
|
||||
border
|
||||
>
|
||||
<el-table-column label="应发部分">
|
||||
<el-table-column prop="baseSalary" label="基础专项绩效" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="postSalary" label="岗位工资" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="payWage" label="薪级工资" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="见习期工资" prop="studentPay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="liveAllowance" label="生活补贴" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="postAllowance" label="岗位津贴" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="住房(租金)补贴" prop="houseSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="新职工住房补贴" prop="newHouseSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="回民补贴" prop="huiSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="养老保险补贴" prop="oldSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="教龄津贴" prop="ageAllowance" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特教补贴" prop="specialSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特级教师津贴" prop="teacherAllowance" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特岗津贴(一)" prop="sPostAllowance1" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特岗津贴(二)" prop="sPostAllowance2" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="其他" prop="other" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="奖励性绩效工资" prop="meritPay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="乡镇工作补贴" prop="villageSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="临时性补贴" prop="temporarySubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="上下班交通补贴" prop="trafficSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="保留津贴" prop="keepAllowance" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="补发工资" prop="retroactivePay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="应休未休假补贴" prop="vacationMoney" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="应发工资" prop="shouldPay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="基础专项绩效" prop="baseSalary" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="基础工资应税收入" prop="shouldTaxMoney" min-width="60" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag type="danger">基础工资应税收入= 岗位工资 +薪级工资+见习期工资+生活补贴+岗位津贴+教龄津贴+特教补贴+特级教师津贴+特岗津贴(1)+特岗津贴(2)+奖励绩效性工资+乡镇工作补贴+临时补贴+保留津贴+应休未休假-个人补缴-其他扣款-医疗救助金 </el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!--应扣部分-->
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
size="small"
|
||||
border
|
||||
:data="salaryData.baseInfo"
|
||||
style="width: 100%">
|
||||
<el-table-column label="应扣部分">
|
||||
<el-table-column label="住房公积金" width="90" prop="houseFund"></el-table-column>
|
||||
<el-table-column label="医疗保险金" width="90" prop="medicalInsurance"></el-table-column>
|
||||
<el-table-column label="失业保险金" width="90" prop="unemployInsurance"></el-table-column>
|
||||
<el-table-column label="养老保险金" width="90" prop="endowInsurance"></el-table-column>
|
||||
<el-table-column label="工会费" width="60" prop="unionFee"></el-table-column>
|
||||
<el-table-column label="儿童统筹" width="90" prop="childrenWhole"></el-table-column>
|
||||
<el-table-column label="个人所得税" width="90" prop="personalTax"></el-table-column>
|
||||
<el-table-column label="其他扣款" width="90" prop="otherDeduction"></el-table-column>
|
||||
<el-table-column label="病事假扣款" width="90" prop="sickDeduction"></el-table-column>
|
||||
<el-table-column label="医疗救助基金" width="90" prop="medicalFund"></el-table-column>
|
||||
<el-table-column label="工伤保险" width="90" prop="inductrialInjury"></el-table-column>
|
||||
<el-table-column label="个人补缴" width="90" prop="personalPay"></el-table-column>
|
||||
<el-table-column label="应扣合计" width="90" prop="withhold"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag>个人所得税 = 个税计算数据中的《 累计应补(退)税额 》</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!--劳务费-->
|
||||
<el-row v-if="showAllContent">
|
||||
<el-col :span="24">
|
||||
<el-table size="small" border :data="allProjectData">
|
||||
<el-table-column label="造单收入清单">
|
||||
<el-table-column label="造单部门" prop="deptName"></el-table-column>
|
||||
<el-table-column label="造单人" prop="createName"></el-table-column>
|
||||
<el-table-column label="项目编号" prop="projectNo"></el-table-column>
|
||||
<el-table-column label="项目名" prop="title"></el-table-column>
|
||||
<el-table-column label="金额" prop="realMoney"></el-table-column>
|
||||
<el-table-column label="付讫时间" prop="payTime"></el-table-column>
|
||||
<el-table-column label="免税或暂不交税" prop="freeTax">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.freeTax=='1'">√</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!--专项扣除-->
|
||||
<el-row v-if="showAllContent">
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
size="small"
|
||||
border
|
||||
:data="salaryExtendData"
|
||||
style="width: 100%">
|
||||
<el-table-column label="个税计算数据">
|
||||
<el-table-column prop="currentIncome" label="应税收入" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalIncome" label="累计收入额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalDeduction" label="累计减除费用" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalSpecialDecution" label="累计专项扣除" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="累计专项扣除附加" align="center">
|
||||
<el-table-column prop="totalChildEdu" label="累计子女教育" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalConEdu" label="累计继续教育" min-width="60" align="center"> </el-table-column>
|
||||
<el-table-column prop="totalHouseInterest" label="累计住房贷款利息" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalHouse" label="累计住房租金" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalSupportOld" label="累计赡养老人" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalBabyMoney" label="累计婴幼儿照护费用" min-width="60" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalTaxMoney" label="累计应纳税所得额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="taxRate" label="税率" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="quickDecution" label="速算扣除数" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalRealTaxPay" label="累计应扣缴税额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalPrePayTax" label="累计已预缴税额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalRetrieveTax" label="累计应补(退)税额" min-width="60" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag type="warning">应税收入= 基础工资应税收入+造单收入 </el-tag>
|
||||
<el-tag type="success">累计专项扣除=当年累计个人承担的住房公积金+医疗保险金+失业保险金+养老保险金 </el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!--实发合计-->
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
size="small"
|
||||
border
|
||||
:data="[staticsData]"
|
||||
empty-text=" ">
|
||||
<el-table-column label="小计">
|
||||
<el-table-column label="应发工资" prop="shouldPay"></el-table-column>
|
||||
<el-table-column label="应扣合计" prop="shouldDedu"></el-table-column>
|
||||
<el-table-column label="实发工资" prop="realWage"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag type="primary">实发工资= 应发工资-应扣合计 </el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { queryUserInfo, queryExtendSalaryInfo } from '/@/api/professional/teacherpayslip'
|
||||
import { checkAuth } from '/@/api/professional/teachersalary'
|
||||
|
||||
// 对话框显示状态
|
||||
const visible = ref(false)
|
||||
|
||||
// 数据
|
||||
const salaryData = reactive({
|
||||
baseInfo: [] as any[]
|
||||
})
|
||||
|
||||
const nowUser = ref<any>({})
|
||||
const baseLoading = ref(false)
|
||||
const salaryExtendData = ref<any[]>([])
|
||||
const allProjectData = ref<any[]>([])
|
||||
const staticsData = reactive({
|
||||
shouldPay: 0,
|
||||
shouldDedu: 0,
|
||||
realWage: 0,
|
||||
orderMoney: 0,
|
||||
personTax: 0
|
||||
})
|
||||
const showAllContent = ref(false)
|
||||
|
||||
// 检查权限
|
||||
const checkAuthMethod = async () => {
|
||||
try {
|
||||
const res = await checkAuth()
|
||||
showAllContent.value = res.data.data
|
||||
} catch (error) {
|
||||
// 检查权限失败
|
||||
}
|
||||
}
|
||||
|
||||
// 构建用户信息
|
||||
const makeUserInfo = (data: any) => {
|
||||
const row = JSON.parse(JSON.stringify(data))
|
||||
salaryData.baseInfo = []
|
||||
row.realName = (row.realName == "" || row.realName == null ? row.userName : row.realName)
|
||||
salaryData.baseInfo.push(row)
|
||||
|
||||
staticsData.shouldPay = row.shouldPay || 0
|
||||
staticsData.shouldDedu = row.withhold || 0
|
||||
staticsData.realWage = row.realWage || 0
|
||||
staticsData.personTax = (row.personalTax as number) || 0
|
||||
}
|
||||
|
||||
// 查询扩展薪资信息
|
||||
const queryExtendSalaryInfoMethod = async (row: any) => {
|
||||
salaryExtendData.value = []
|
||||
const params = { nf: row.nf, yf: row.yf, teacherNo: row.teacherNo }
|
||||
try {
|
||||
const res = await queryExtendSalaryInfo(params)
|
||||
salaryExtendData.value.push(res.data.data.salaryTax)
|
||||
allProjectData.value = res.data.data.allProject
|
||||
staticsData.orderMoney = res.data.data.totalMoney
|
||||
} catch (error) {
|
||||
// 查询失败
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索用户信息
|
||||
const searchUserInfo = async () => {
|
||||
baseLoading.value = true
|
||||
const data = { idCard: nowUser.value.idCard, nf: nowUser.value.nf, yf: nowUser.value.yff }
|
||||
try {
|
||||
const response = await queryUserInfo(data)
|
||||
if (response.data.data == null) {
|
||||
salaryData.baseInfo = []
|
||||
const obj: any = {}
|
||||
obj.realName = nowUser.value.realName
|
||||
obj.idCard = nowUser.value.idCard
|
||||
salaryData.baseInfo.push(obj)
|
||||
|
||||
salaryExtendData.value = []
|
||||
Object.assign(staticsData, {
|
||||
shouldPay: 0,
|
||||
shouldDedu: 0,
|
||||
realWage: 0,
|
||||
orderMoney: 0,
|
||||
personTax: 0
|
||||
})
|
||||
} else {
|
||||
const resData = response.data.data
|
||||
resData.realName = nowUser.value.realName
|
||||
makeUserInfo(resData)
|
||||
await queryExtendSalaryInfoMethod(resData)
|
||||
}
|
||||
} catch (error) {
|
||||
// 查询失败
|
||||
} finally {
|
||||
baseLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const init = (row: any) => {
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
checkAuthMethod()
|
||||
nowUser.value = JSON.parse(JSON.stringify(row))
|
||||
nowUser.value.yff = row.yf
|
||||
makeUserInfo(row)
|
||||
queryExtendSalaryInfoMethod(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -1,320 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" width="100%" v-loading="baseLoading" top="0" :close-on-click-modal="false" destroy-on-close>
|
||||
<!--基本信息-->
|
||||
<el-card shadow="hover">
|
||||
<el-row :span="24">
|
||||
<el-col>
|
||||
<el-table
|
||||
:data="salaryData.baseInfo"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="realName"
|
||||
label="姓名"
|
||||
width="80">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="idCard"
|
||||
label="身份证号"
|
||||
width="200">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="年份"
|
||||
width="300">
|
||||
<template #default>
|
||||
<el-date-picker
|
||||
v-model="nowUser.nf"
|
||||
type="year"
|
||||
format="YYYY"
|
||||
value-format="YYYY"
|
||||
style="width: 200px"
|
||||
placeholder="选择年">
|
||||
</el-date-picker>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="月份"
|
||||
width="300">
|
||||
<template #default>
|
||||
<el-date-picker
|
||||
v-model="nowUser.yff"
|
||||
type="month"
|
||||
format="M"
|
||||
value-format="M"
|
||||
style="width: 200px"
|
||||
placeholder="选择月">
|
||||
</el-date-picker>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="200">
|
||||
<template #default>
|
||||
<el-button @click="searchUserInfo" type="primary" size="small">搜索</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover">
|
||||
<!--应发部分-->
|
||||
<el-row v-if="showAllContent">
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
:data="salaryData.baseInfo"
|
||||
size="small"
|
||||
border
|
||||
>
|
||||
<el-table-column label="应发部分">
|
||||
<el-table-column prop="postSalary" label="岗位工资" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="payWage" label="薪级工资" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="见习期工资" prop="studentPay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="liveAllowance" label="生活补贴" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="postAllowance" label="岗位津贴" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="住房(租金)补贴" prop="houseSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="新职工住房补贴" prop="newHouseSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="回民补贴" prop="huiSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="养老保险补贴" prop="oldSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="教龄津贴" prop="ageAllowance" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特教补贴" prop="specialSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特级教师津贴" prop="teacherAllowance" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特岗津贴(一)" prop="sPostAllowance1" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="特岗津贴(二)" prop="sPostAllowance2" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="其他" prop="other" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="奖励性绩效工资" prop="meritPay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="乡镇工作补贴" prop="villageSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="临时性补贴" prop="temporarySubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="上下班交通补贴" prop="trafficSubsidies" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="保留津贴" prop="keepAllowance" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="补发工资" prop="retroactivePay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="应休未休假补贴" prop="vacationMoney" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="应发工资" prop="shouldPay" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="基础专项绩效" prop="baseSalary" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="基础工资应税收入" prop="shouldTaxMoney" min-width="60" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag type="danger">基础工资应税收入= 岗位工资 +薪级工资+见习期工资+生活补贴+岗位津贴+教龄津贴+特教补贴+特级教师津贴+特岗津贴(1)+特岗津贴(2)+奖励绩效性工资+乡镇工作补贴+临时补贴+保留津贴+应休未休假-个人补缴-其他扣款-医疗救助金 </el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!--应扣部分-->
|
||||
<el-row v-if="showAllContent">
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
size="small"
|
||||
border
|
||||
:data="salaryData.baseInfo"
|
||||
style="width: 100%">
|
||||
<el-table-column label="应扣部分">
|
||||
<el-table-column label="住房公积金" width="90" prop="houseFund"></el-table-column>
|
||||
<el-table-column label="医疗保险金" width="90" prop="medicalInsurance"></el-table-column>
|
||||
<el-table-column label="失业保险金" width="90" prop="unemployInsurance"></el-table-column>
|
||||
<el-table-column label="养老保险金" width="90" prop="endowInsurance"></el-table-column>
|
||||
<el-table-column label="工会费" width="60" prop="unionFee"></el-table-column>
|
||||
<el-table-column label="儿童统筹" width="90" prop="childrenWhole"></el-table-column>
|
||||
<el-table-column label="个人所得税" width="90" prop="personalTax"></el-table-column>
|
||||
<el-table-column label="其他扣款" width="90" prop="otherDeduction"></el-table-column>
|
||||
<el-table-column label="病事假扣款" width="90" prop="sickDeduction"></el-table-column>
|
||||
<el-table-column label="医疗救助基金" width="90" prop="medicalFund"></el-table-column>
|
||||
<el-table-column label="工伤保险" width="90" prop="inductrialInjury"></el-table-column>
|
||||
<el-table-column label="个人补缴" width="90" prop="personalPay"></el-table-column>
|
||||
<el-table-column label="应扣合计" width="90" prop="withhold"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag>个人所得税 = 个税计算数据中的《 累计应补(退)税额 》</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!--劳务费-->
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-table size="small" border :data="allProjectData" show-summary>
|
||||
<el-table-column label="造单收入清单">
|
||||
<el-table-column label="造单部门" prop="deptName"></el-table-column>
|
||||
<el-table-column label="造单人" prop="createName"></el-table-column>
|
||||
<el-table-column label="项目编号" prop="projectNo"></el-table-column>
|
||||
<el-table-column label="项目名" prop="title"></el-table-column>
|
||||
<el-table-column label="金额" prop="realMoney"></el-table-column>
|
||||
<el-table-column label="付讫时间" prop="payTime"></el-table-column>
|
||||
<el-table-column label="免税或暂不交税" prop="freeTax">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.freeTax=='1'">√</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!--专项扣除-->
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
size="small"
|
||||
border
|
||||
:data="salaryExtendData"
|
||||
style="width: 100%">
|
||||
<el-table-column label="个税计算数据">
|
||||
<el-table-column prop="currentIncome" label="应税收入" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalIncome" label="累计收入额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalDeduction" label="累计减除费用" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalSpecialDecution" label="累计专项扣除" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column label="累计专项扣除附加" align="center">
|
||||
<el-table-column prop="totalChildEdu" label="累计子女教育" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalConEdu" label="累计继续教育" min-width="60" align="center"> </el-table-column>
|
||||
<el-table-column prop="totalHouseInterest" label="累计住房贷款利息" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalHouse" label="累计住房租金" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalSupportOld" label="累计赡养老人" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalBabyMoney" label="累计婴幼儿照护费用" min-width="60" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalTaxMoney" label="累计应纳税所得额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="taxRate" label="税率" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="quickDecution" label="速算扣除数" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalRealTaxPay" label="累计应扣缴税额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalPrePayTax" label="累计已预缴税额" min-width="60" align="center"></el-table-column>
|
||||
<el-table-column prop="totalRetrieveTax" label="累计应补(退)税额" min-width="60" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!--实发合计-->
|
||||
<el-row v-if="showAllContent">
|
||||
<el-col :span="24">
|
||||
<el-table
|
||||
size="small"
|
||||
border
|
||||
:data="[staticsData]"
|
||||
empty-text=" ">
|
||||
<el-table-column label="小计">
|
||||
<el-table-column label="应发工资" prop="shouldPay"></el-table-column>
|
||||
<el-table-column label="应扣合计" prop="shouldDedu"></el-table-column>
|
||||
<el-table-column label="实发工资" prop="realWage"></el-table-column>
|
||||
<el-table-column label="造单收入" prop="orderMoney"></el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag type="primary">实发工资= 应发工资-应扣合计 </el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { queryUserInfo, queryExtendSalaryInfo, checkAuth } from '/@/api/professional/teachersalary'
|
||||
|
||||
// 对话框显示状态
|
||||
const visible = ref(false)
|
||||
|
||||
// 数据
|
||||
const salaryData = reactive({
|
||||
baseInfo: [] as any[]
|
||||
})
|
||||
|
||||
const nowUser = ref<any>({})
|
||||
const baseLoading = ref(false)
|
||||
const salaryExtendData = ref<any[]>([])
|
||||
const allProjectData = ref<any[]>([])
|
||||
const staticsData = reactive({
|
||||
shouldPay: 0,
|
||||
shouldDedu: 0,
|
||||
realWage: 0,
|
||||
orderMoney: 0,
|
||||
personTax: 0
|
||||
})
|
||||
const showAllContent = ref(false)
|
||||
|
||||
// 检查权限
|
||||
const checkAuthMethod = async () => {
|
||||
try {
|
||||
const res = await checkAuth()
|
||||
showAllContent.value = res.data.data
|
||||
} catch (error) {
|
||||
// 检查权限失败
|
||||
}
|
||||
}
|
||||
|
||||
// 构建用户信息
|
||||
const makeUserInfo = (data: any) => {
|
||||
const row = JSON.parse(JSON.stringify(data))
|
||||
salaryData.baseInfo = []
|
||||
row.realName = (row.realName == "" || row.realName == null ? row.userName : row.realName)
|
||||
salaryData.baseInfo.push(row)
|
||||
|
||||
staticsData.shouldPay = row.shouldPay || 0
|
||||
staticsData.shouldDedu = row.withhold || 0
|
||||
staticsData.realWage = row.realWage || 0
|
||||
staticsData.personTax = (row.personalTax as number) || 0
|
||||
}
|
||||
|
||||
// 查询扩展薪资信息
|
||||
const queryExtendSalaryInfoMethod = async (row: any) => {
|
||||
salaryExtendData.value = []
|
||||
const params = { nf: row.nf, yf: row.yf, teacherNo: row.teacherNo }
|
||||
try {
|
||||
const res = await queryExtendSalaryInfo(params)
|
||||
salaryExtendData.value.push(res.data.data.salaryTax)
|
||||
allProjectData.value = res.data.data.allProject
|
||||
staticsData.orderMoney = res.data.data.totalMoney
|
||||
} catch (error) {
|
||||
// 查询失败
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索用户信息
|
||||
const searchUserInfo = async () => {
|
||||
baseLoading.value = true
|
||||
const data = { idCard: nowUser.value.idCard, nf: nowUser.value.nf, yf: nowUser.value.yff }
|
||||
try {
|
||||
const response = await queryUserInfo(data)
|
||||
if (response.data.data == null) {
|
||||
salaryData.baseInfo = []
|
||||
const obj: any = {}
|
||||
obj.realName = nowUser.value.realName
|
||||
obj.idCard = nowUser.value.idCard
|
||||
salaryData.baseInfo.push(obj)
|
||||
|
||||
salaryExtendData.value = []
|
||||
Object.assign(staticsData, {
|
||||
shouldPay: 0,
|
||||
shouldDedu: 0,
|
||||
realWage: 0,
|
||||
orderMoney: 0,
|
||||
personTax: 0
|
||||
})
|
||||
} else {
|
||||
const resData = response.data.data
|
||||
resData.realName = nowUser.value.realName
|
||||
makeUserInfo(resData)
|
||||
await queryExtendSalaryInfoMethod(resData)
|
||||
}
|
||||
} catch (error) {
|
||||
// 查询失败
|
||||
} finally {
|
||||
baseLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const init = (row: any) => {
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
checkAuthMethod()
|
||||
nowUser.value = JSON.parse(JSON.stringify(row))
|
||||
nowUser.value.yff = row.yf
|
||||
makeUserInfo(row)
|
||||
queryExtendSalaryInfoMethod(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -18,7 +18,6 @@
|
||||
value-format="YYYY"
|
||||
placeholder="请选择薪资年份"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -30,11 +29,18 @@
|
||||
value-format="M"
|
||||
placeholder="请选择薪资月份"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 查询和重置按钮 -->
|
||||
<template #actions>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</search-form>
|
||||
|
||||
<!-- 表格 -->
|
||||
@@ -49,13 +55,13 @@
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
|
||||
<el-table-column prop="createTime" label="导出时间" width="180" align="center" />
|
||||
<el-table-column prop="createTime" label="导出时间" min-width="180" align="center" />
|
||||
|
||||
<el-table-column prop="salaryYear" label="薪资年份" width="120" align="center" />
|
||||
<el-table-column prop="salaryYear" label="薪资年份" min-width="120" align="center" />
|
||||
|
||||
<el-table-column prop="salaryMonth" label="薪资月份" width="120" align="center" />
|
||||
<el-table-column prop="salaryMonth" label="薪资月份" min-width="120" align="center" />
|
||||
|
||||
<el-table-column prop="confirm" label="劳务日期锁定" width="150" align="center">
|
||||
<el-table-column prop="confirm" label="劳务日期锁定" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.confirm === '1' ? 'success' : 'info'">
|
||||
{{ scope.row.confirm === '1' ? '是' : '否' }}
|
||||
@@ -63,7 +69,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" min-width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" min-width="80" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="permissions.professional_salaryexportrecord_del"
|
||||
@@ -92,7 +98,7 @@ import { storeToRefs } from 'pinia'
|
||||
import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { fetchList, delObj } from '/@/api/professional/salaryexportrecord'
|
||||
import { fetchList, delObj } from '/@/api/professional/salaries/salaryexportrecord'
|
||||
|
||||
// 使用 Pinia store
|
||||
const userInfoStore = useUserInfo()
|
||||
@@ -145,6 +151,16 @@ const handleFilter = () => {
|
||||
getDataList() // 查询后跳转到第一页
|
||||
}
|
||||
|
||||
// 重置
|
||||
const resetQuery = () => {
|
||||
searchFormRef.value?.formRef?.resetFields()
|
||||
Object.assign(search, {
|
||||
salaryYear: '',
|
||||
salaryMonth: ''
|
||||
})
|
||||
handleFilter()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDel = (row: any) => {
|
||||
messageBox.confirm('确认删除?').then(async () => {
|
||||
@@ -153,7 +169,7 @@ const handleDel = (row: any) => {
|
||||
message.success('删除成功')
|
||||
getDataList(false) // 删除后保持当前页
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || '删除失败')
|
||||
message.error(error.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
@@ -1,18 +1,6 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 操作按钮 -->
|
||||
<el-row>
|
||||
<div class="mb15" style="width: 100%;">
|
||||
<el-button
|
||||
size="small"
|
||||
v-if="permissions.teacher_award_import"
|
||||
type="primary"
|
||||
@click="handleImportBaseSalary">绩效导入
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<search-form
|
||||
v-show="showSearch"
|
||||
@@ -27,7 +15,6 @@
|
||||
v-model="search.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -36,7 +23,6 @@
|
||||
v-model="search.teacherNo"
|
||||
placeholder="请输入工号"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -48,13 +34,33 @@
|
||||
value-format="YYYY"
|
||||
placeholder="请选择年份"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 查询和重置按钮 -->
|
||||
<template #actions>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</search-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row>
|
||||
<div class="mb15">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
v-if="permissions.teacher_award_import"
|
||||
@click="handleImportBaseSalary">绩效导入
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
@@ -63,13 +69,14 @@
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="data-table"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
|
||||
<el-table-column prop="realName" label="姓名" min-width="120" align="center" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="teacherNo" label="工号" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="姓名/工号" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="startDate" label="开始日期" width="120" align="center" />
|
||||
|
||||
@@ -104,7 +111,7 @@ import { ref, reactive, computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList } from '/@/api/professional/teacherawardtax'
|
||||
import { fetchList } from '/@/api/professional/salaries/teacherawardtax'
|
||||
import ImportAwardTax from './importAwardTax.vue'
|
||||
|
||||
// 使用 Pinia store
|
||||
@@ -156,6 +163,17 @@ const handleFilter = () => {
|
||||
getDataList() // 查询后跳转到第一页
|
||||
}
|
||||
|
||||
// 重置
|
||||
const resetQuery = () => {
|
||||
searchFormRef.value?.formRef?.resetFields()
|
||||
Object.assign(search, {
|
||||
realName: '',
|
||||
teacherNo: '',
|
||||
year: ''
|
||||
})
|
||||
handleFilter()
|
||||
}
|
||||
|
||||
// 导入
|
||||
const handleImportBaseSalary = () => {
|
||||
importAwardTaxRef.value?.init()
|
||||
@@ -163,7 +181,4 @@ const handleImportBaseSalary = () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.data-table {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogEmptyFromVisible" title="教职工信息导出" width="80%">
|
||||
<el-form style="margin-left: 7px" :inline="true">
|
||||
<el-form :inline="true" label-width="100px" style="margin-bottom: 20px;">
|
||||
<el-form-item>
|
||||
<el-tag>导出教职工人数,依据'职工信息'页面检索条件,如部门,职称等级等条件</el-tag>
|
||||
<el-tag type="warning">*请在'职工信息'页面点击搜索后,再点开当前导出页面</el-tag>
|
||||
<el-tag type="info">导出依据"职工信息"页面的检索条件</el-tag>
|
||||
<el-tag type="warning" style="margin-left: 10px;">请在搜索后再打开导出页面</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否退休">
|
||||
<el-select
|
||||
@@ -11,7 +11,7 @@
|
||||
filterable
|
||||
reserve-keyword
|
||||
clearable
|
||||
>
|
||||
style="width: 200px;">
|
||||
<el-option
|
||||
v-for="item in YES_OR_NO"
|
||||
:key="item.value"
|
||||
@@ -25,52 +25,54 @@
|
||||
type="primary"
|
||||
@click="exportTeacherInfo"
|
||||
:loading="exportLoading"
|
||||
size="small">导出</el-button>
|
||||
:icon="Download">导出</el-button>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div class="div_stuData" style="margin-bottom: 5px;margin-top: 5px;">基础信息
|
||||
<el-checkbox v-model="allCheckTeacherBaseInfo" style="margin-left:20px">全选</el-checkbox>
|
||||
</div>
|
||||
<el-tabs type="border-card" tab-position="left">
|
||||
<el-form ref="baseForm" label-width="120px">
|
||||
<el-row>
|
||||
<el-checkbox-group v-model="checkedTeacherBaseInfo">
|
||||
<el-checkbox v-for="(item,index) in teacherBasicCheckData" :label="item.value" :key="index">{{item.label}}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tabs>
|
||||
<!-- 基础信息 -->
|
||||
<el-card shadow="never" style="margin-bottom: 20px;">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>基础信息</span>
|
||||
<el-checkbox v-model="allCheckTeacherBaseInfo">全选</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<el-checkbox-group v-model="checkedTeacherBaseInfo">
|
||||
<el-checkbox v-for="(item, index) in teacherBasicCheckData" :label="item.value" :key="index" style="margin-right: 20px; margin-bottom: 10px;">
|
||||
{{item.label}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-card>
|
||||
|
||||
<!-- 岗位信息 -->
|
||||
<div class="div_stuData" style="margin-bottom: 5px;margin-top: 5px;">岗位信息
|
||||
<el-checkbox v-model="allCheckStationInfo">全选</el-checkbox>
|
||||
</div>
|
||||
<el-tabs type="border-card" tab-position="left">
|
||||
<el-form ref="baseForm" label-width="120px">
|
||||
<el-row>
|
||||
<el-checkbox-group v-model="checkedTeacherStationInfo">
|
||||
<el-checkbox v-for="(item, index) in teacherStationData" :key="index" :label="item.value">{{item.label}}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tabs>
|
||||
<!-- 岗位信息 -->
|
||||
<el-card shadow="never" style="margin-bottom: 20px;">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>岗位信息</span>
|
||||
<el-checkbox v-model="allCheckStationInfo">全选</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<el-checkbox-group v-model="checkedTeacherStationInfo">
|
||||
<el-checkbox v-for="(item, index) in teacherStationData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px;">
|
||||
{{item.label}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-card>
|
||||
|
||||
<!-- 其他 -->
|
||||
<div class="div_stuData" style="margin-bottom: 5px;margin-top: 5px;">其他
|
||||
<el-checkbox v-model="allCheckedother" style="margin-left: 20px">全选</el-checkbox>
|
||||
</div>
|
||||
<el-tabs type="border-card" tab-position="left">
|
||||
<el-form ref="baseForm" label-width="120px">
|
||||
<el-row>
|
||||
<el-checkbox-group v-model="checkedTeacherOtherInfo">
|
||||
<el-checkbox v-for="(item, index) in teacherOtherData" :key="index" :label="item.value">{{item.label}}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tabs>
|
||||
<!-- 其他 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>其他</span>
|
||||
<el-checkbox v-model="allCheckedother">全选</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<el-checkbox-group v-model="checkedTeacherOtherInfo">
|
||||
<el-checkbox v-for="(item, index) in teacherOtherData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px;">
|
||||
{{item.label}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-card>
|
||||
|
||||
</el-dialog>
|
||||
|
||||
@@ -79,6 +81,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { Download } from '@element-plus/icons-vue'
|
||||
import global from '/@/components/tools/commondict.vue'
|
||||
import request from '/@/utils/request'
|
||||
|
||||
@@ -250,5 +253,4 @@
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user