解决API文档和路由配置合并冲突
This commit is contained in:
commit
79a96a7dfd
662 files changed
+127608
-205195
No files matched your search
@@ -17,6 +17,8 @@
|
||||
"MaybeRefOrGetter": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"Slot": true,
|
||||
"Slots": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"acceptHMRUpdate": true,
|
||||
@@ -35,6 +37,7 @@
|
||||
"createInjectionState": true,
|
||||
"createPinia": true,
|
||||
"createReactiveFn": true,
|
||||
"createRef": true,
|
||||
"createReusableTemplate": true,
|
||||
"createSharedComposable": true,
|
||||
"createTemplatePromise": true,
|
||||
@@ -315,9 +318,6 @@
|
||||
"watchThrottled": true,
|
||||
"watchTriggerable": true,
|
||||
"watchWithFilter": true,
|
||||
"whenever": true,
|
||||
"Slot": true,
|
||||
"Slots": true,
|
||||
"createRef": true
|
||||
"whenever": true
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,11 @@
|
||||
"element-plus": "2.9.8",
|
||||
"file-saver": "2.0.5",
|
||||
"highlight.js": "11.9.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"image-conversion": "2.1.1",
|
||||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.3.2",
|
||||
"jspdf": "^3.0.1",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "3.0.2",
|
||||
"screenfull": "6.0.2",
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 财务审批记录管理 - API服务
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-28
|
||||
*/
|
||||
|
||||
import request from '@/utils/request';
|
||||
import type { AxiosPromise } from 'axios';
|
||||
|
||||
// 审批记录查询参数
|
||||
export interface CwApprovalRecordQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
applicationNo?: string;
|
||||
projectName?: string;
|
||||
applicantName?: string;
|
||||
approvalStatus?: string;
|
||||
approverName?: string;
|
||||
currentApprover?: string;
|
||||
deptId?: string;
|
||||
submitTimeBegin?: string;
|
||||
submitTimeEnd?: string;
|
||||
needApproval?: boolean;
|
||||
}
|
||||
|
||||
// 审批记录VO
|
||||
export interface CwApprovalRecordVO {
|
||||
id: string | number;
|
||||
applicationId: string | number;
|
||||
applicationNo: string;
|
||||
projectName: string;
|
||||
applicantName: string;
|
||||
deptName: string;
|
||||
totalInvestment: number;
|
||||
currentApproverName: string;
|
||||
approvalStatus: string;
|
||||
submitTime: string;
|
||||
approvalTime?: string;
|
||||
approverName?: string;
|
||||
approvalComment?: string;
|
||||
approvalResult?: string;
|
||||
}
|
||||
|
||||
// 审批表单
|
||||
export interface CwApprovalForm {
|
||||
applicationId?: string | number;
|
||||
applicationIds?: Array<string | number>;
|
||||
approvalResult: string;
|
||||
approvalComment: string;
|
||||
nextApprover?: string | number;
|
||||
}
|
||||
|
||||
// 审批历史记录
|
||||
export interface CwApprovalHistoryVO {
|
||||
id: string | number;
|
||||
applicationId: string | number;
|
||||
approverName: string;
|
||||
approvalResult: string;
|
||||
approvalComment: string;
|
||||
approvalTime: string;
|
||||
stepName: string;
|
||||
stepOrder: number;
|
||||
}
|
||||
|
||||
// 查询审批记录列表
|
||||
export const listApprovalRecord = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 查询审批记录详情
|
||||
export const getApprovalRecord = (id: string | number): AxiosPromise<CwApprovalRecordVO> => {
|
||||
return request({
|
||||
url: '/cw/approval/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 审批申报(单个)
|
||||
export const approveApplication = (data: CwApprovalForm) => {
|
||||
return request({
|
||||
url: '/cw/approval/process',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 批量审批申报
|
||||
export const batchApproveApplication = (data: CwApprovalForm) => {
|
||||
return request({
|
||||
url: '/cw/approval/batchApprove',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 获取审批历史记录
|
||||
export const getApprovalHistory = (applicationId: string | number): AxiosPromise<CwApprovalHistoryVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/byApplication/' + applicationId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 获取当前用户待审批列表
|
||||
export const getPendingApprovals = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/pending',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取当前用户已审批列表
|
||||
export const getMyApprovals = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/my',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取部门审批列表
|
||||
export const getDepartmentApprovals = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/dept/pending',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取可选审批人列表
|
||||
export const getAvailableApprovers = (applicationId: string | number): AxiosPromise<any[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/approvers/' + applicationId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 导出审批记录
|
||||
export const exportApprovalRecord = (query: CwApprovalRecordQuery) => {
|
||||
return request({
|
||||
url: '/cw/approval/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取审批统计数据
|
||||
export const getApprovalStatistics = (year?: number) => {
|
||||
return request({
|
||||
url: '/cw/approval/statistics',
|
||||
method: 'get',
|
||||
params: { year }
|
||||
});
|
||||
};
|
||||
|
||||
// 按部门统计审批数据
|
||||
export const getApprovalStatsByDept = () => {
|
||||
return request({
|
||||
url: '/cw/approval/statsByDept',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 按月份统计审批数据
|
||||
export const getApprovalStatsByMonth = (year?: number) => {
|
||||
return request({
|
||||
url: '/cw/approval/statsByMonth',
|
||||
method: 'get',
|
||||
params: { query: { year } }
|
||||
});
|
||||
};
|
||||
|
||||
// 获取审批效率统计
|
||||
export const getApprovalEfficiency = () => {
|
||||
return request({
|
||||
url: '/cw/approval/efficiency',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 绩效指标管理 - API服务
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-08-01
|
||||
*/
|
||||
|
||||
import request from '@/utils/request';
|
||||
import type { AxiosPromise } from 'axios';
|
||||
|
||||
// 绩效指标类型定义
|
||||
export interface PerformanceIndicatorForm {
|
||||
id?: number;
|
||||
applicationId: number;
|
||||
applicationNo: string;
|
||||
indicatorType: string;
|
||||
indicatorTypeName: string;
|
||||
goalName: string;
|
||||
targetValue: string;
|
||||
weightPercentage: number;
|
||||
description?: string;
|
||||
unit?: string;
|
||||
measurementMethod?: string;
|
||||
dataSource?: string;
|
||||
}
|
||||
|
||||
// 查询绩效指标列表
|
||||
export const listPerformanceIndicator = (query: any): AxiosPromise<PerformanceIndicatorForm[]> => {
|
||||
return request({
|
||||
url: '/cw/performance/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 根据申报ID查询绩效指标
|
||||
export const getPerformanceByApplicationId = (applicationId: number): AxiosPromise<PerformanceIndicatorForm[]> => {
|
||||
return request({
|
||||
url: `/cw/performance/application/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 批量保存绩效指标
|
||||
export const batchSavePerformanceIndicator = (applicationId: number, applicationNo: string, data: PerformanceIndicatorForm[]) => {
|
||||
return request({
|
||||
url: '/cw/performance/batchSave',
|
||||
method: 'post',
|
||||
data: {
|
||||
applicationId,
|
||||
applicationNo,
|
||||
indicators: data
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 新增绩效指标
|
||||
export const addPerformanceIndicator = (data: PerformanceIndicatorForm) => {
|
||||
return request({
|
||||
url: '/cw/performance',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 修改绩效指标
|
||||
export const updatePerformanceIndicator = (data: PerformanceIndicatorForm) => {
|
||||
return request({
|
||||
url: '/cw/performance',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 删除绩效指标
|
||||
export const delPerformanceIndicator = (id: string | number) => {
|
||||
return request({
|
||||
url: '/cw/performance/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
// 根据申报ID删除绩效指标
|
||||
export const delPerformanceByApplicationId = (applicationId: number) => {
|
||||
return request({
|
||||
url: `/cw/performance/application/${applicationId}`,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 财务项目预算审批 - API服务
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-26
|
||||
*/
|
||||
|
||||
import request from '@/utils/request';
|
||||
import type { AxiosPromise } from 'axios';
|
||||
import type {
|
||||
CwProjectApplicationQuery,
|
||||
CwProjectApplicationVO,
|
||||
CwProjectApplicationForm,
|
||||
CwApproverConfigQuery,
|
||||
CwApproverConfigVO,
|
||||
CwApproverConfigForm,
|
||||
CwApplicationStatistics
|
||||
} from '@/api/cw/types';
|
||||
|
||||
// 查询项目预算申报列表
|
||||
export const listProjectApplication = (query: CwProjectApplicationQuery): AxiosPromise<CwProjectApplicationVO[]> => {
|
||||
return request({
|
||||
url: '/cw/application/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 查询项目预算申报详情
|
||||
export const getProjectApplication = (id: string | number): AxiosPromise<CwProjectApplicationVO> => {
|
||||
return request({
|
||||
url: '/cw/application/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 新增项目预算申报
|
||||
export const addProjectApplication = (data: CwProjectApplicationForm) => {
|
||||
return request({
|
||||
url: '/cw/application',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 修改项目预算申报
|
||||
export const updateProjectApplication = (data: CwProjectApplicationForm) => {
|
||||
return request({
|
||||
url: '/cw/application',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 删除项目预算申报
|
||||
export const delProjectApplication = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/cw/application/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
// 保存草稿
|
||||
export const saveDraft = (data: CwProjectApplicationForm) => {
|
||||
return request({
|
||||
url: '/cw/application/saveDraft',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 提交申报
|
||||
export const submitApplication = (id: string | number) => {
|
||||
return request({
|
||||
url: '/cw/application/submit/' + id,
|
||||
method: 'post'
|
||||
});
|
||||
};
|
||||
|
||||
// 撤回申报
|
||||
export const withdrawApplication = (id: string | number) => {
|
||||
return request({
|
||||
url: '/cw/application/withdraw/' + id,
|
||||
method: 'put'
|
||||
});
|
||||
};
|
||||
|
||||
// 取消申报
|
||||
export const cancelApplication = (id: string | number) => {
|
||||
return request({
|
||||
url: '/cw/application/cancel/' + id,
|
||||
method: 'put'
|
||||
});
|
||||
};
|
||||
|
||||
// 查询我的申报
|
||||
export const getMyApplications = (query: CwProjectApplicationQuery): AxiosPromise<CwProjectApplicationVO[]> => {
|
||||
return request({
|
||||
url: '/cw/application/my',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 查询部门申报
|
||||
export const getDepartmentApplications = (query: CwProjectApplicationQuery): AxiosPromise<CwProjectApplicationVO[]> => {
|
||||
return request({
|
||||
url: '/cw/application/dept',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 查询待处理申报
|
||||
export const getPendingApplications = (query: CwProjectApplicationQuery): AxiosPromise<CwProjectApplicationVO[]> => {
|
||||
return request({
|
||||
url: '/cw/application/pending',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 导出项目预算申报
|
||||
export const exportProjectApplication = (query: CwProjectApplicationQuery) => {
|
||||
return request({
|
||||
url: '/cw/application/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取申报统计数据
|
||||
export const getApplicationStatistics = (year?: number): AxiosPromise<CwApplicationStatistics> => {
|
||||
return request({
|
||||
url: '/cw/application/statistics',
|
||||
method: 'get',
|
||||
params: { year }
|
||||
});
|
||||
};
|
||||
|
||||
// 申报数量统计
|
||||
export const getApplicationCount = (): AxiosPromise<number> => {
|
||||
return request({
|
||||
url: '/cw/application/count',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 申报金额汇总
|
||||
export const getApplicationAmountSum = (): AxiosPromise<number> => {
|
||||
return request({
|
||||
url: '/cw/application/amountSum',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 按部门统计
|
||||
export const getStatisticsByDept = () => {
|
||||
return request({
|
||||
url: '/cw/application/statisticsByDept',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 按月份统计
|
||||
export const getStatisticsByMonth = (year?: number) => {
|
||||
return request({
|
||||
url: '/cw/application/statisticsByMonth',
|
||||
method: 'get',
|
||||
params: { year }
|
||||
});
|
||||
};
|
||||
|
||||
// ==================== 审批人配置相关 API ====================
|
||||
|
||||
// 查询审批人配置列表
|
||||
export const listApproverConfig = (query: CwApproverConfigQuery): AxiosPromise<CwApproverConfigVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approverConfig/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 查询审批人配置详情
|
||||
export const getApproverConfig = (id: string | number): AxiosPromise<CwApproverConfigVO> => {
|
||||
return request({
|
||||
url: '/cw/approverConfig/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 新增审批人配置
|
||||
export const addApproverConfig = (data: CwApproverConfigForm) => {
|
||||
return request({
|
||||
url: '/cw/approverConfig',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 修改审批人配置
|
||||
export const updateApproverConfig = (data: CwApproverConfigForm) => {
|
||||
return request({
|
||||
url: '/cw/approverConfig',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 删除审批人配置
|
||||
export const delApproverConfig = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/cw/approverConfig/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* 财务项目预算审批 - TypeScript类型定义
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-26
|
||||
*/
|
||||
|
||||
// 项目预算申报查询对象
|
||||
export interface CwProjectApplicationQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
applicationNo?: string;
|
||||
projectName?: string;
|
||||
principal?: string;
|
||||
approvalStatus?: string;
|
||||
departmentId?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
year?: number;
|
||||
}
|
||||
|
||||
// 项目预算申报视图对象
|
||||
export interface CwProjectApplicationVO {
|
||||
id?: number;
|
||||
applicationNo?: string;
|
||||
projectName?: string;
|
||||
principal?: string;
|
||||
contactPhone?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
targetSme?: boolean;
|
||||
targetMicroSme?: boolean;
|
||||
importEquipment?: boolean;
|
||||
applicationReason?: string;
|
||||
constructionContent?: string;
|
||||
totalPerformanceGoal?: string;
|
||||
currentYearGoal?: string;
|
||||
totalInvestment?: number;
|
||||
totalMunicipalFiscal?: number;
|
||||
totalSelfFunding?: number;
|
||||
totalOtherFunding?: number;
|
||||
currentYearTotal?: number;
|
||||
currentYearMunicipal?: number;
|
||||
currentYearSelf?: number;
|
||||
currentYearOther?: number;
|
||||
nextYearTotal?: number;
|
||||
nextYearMunicipal?: number;
|
||||
nextYearSelf?: number;
|
||||
nextYearOther?: number;
|
||||
futureYearTotal?: number;
|
||||
futureYearMunicipal?: number;
|
||||
futureYearSelf?: number;
|
||||
futureYearOther?: number;
|
||||
applicantId?: number;
|
||||
applicantName?: string;
|
||||
departmentId?: number;
|
||||
departmentName?: string;
|
||||
approvalStatus?: string;
|
||||
submitTime?: string;
|
||||
approveTime?: string;
|
||||
year?: number;
|
||||
createBy?: number;
|
||||
createTime?: string;
|
||||
updateBy?: number;
|
||||
updateTime?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 项目预算申报表单对象
|
||||
export interface CwProjectApplicationForm {
|
||||
id?: number;
|
||||
applicationNo?: string;
|
||||
projectName?: string;
|
||||
principal?: string;
|
||||
contactPhone?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
targetSme?: boolean;
|
||||
targetMicroSme?: boolean;
|
||||
importEquipment?: boolean;
|
||||
applicationReason?: string;
|
||||
constructionContent?: string;
|
||||
totalPerformanceGoal?: string;
|
||||
currentYearGoal?: string;
|
||||
totalInvestment?: number;
|
||||
totalMunicipalFiscal?: number;
|
||||
totalSelfFunding?: number;
|
||||
totalOtherFunding?: number;
|
||||
currentYearTotal?: number;
|
||||
currentYearMunicipal?: number;
|
||||
currentYearSelf?: number;
|
||||
currentYearOther?: number;
|
||||
nextYearTotal?: number;
|
||||
nextYearMunicipal?: number;
|
||||
nextYearSelf?: number;
|
||||
nextYearOther?: number;
|
||||
futureYearTotal?: number;
|
||||
futureYearMunicipal?: number;
|
||||
futureYearSelf?: number;
|
||||
futureYearOther?: number;
|
||||
applicantId?: number;
|
||||
applicantName?: string;
|
||||
departmentId?: number;
|
||||
departmentName?: string;
|
||||
approvalStatus?: string;
|
||||
year?: number;
|
||||
remark?: string;
|
||||
performanceIndicators?: any[]; // 绩效指标数据
|
||||
}
|
||||
|
||||
// 审批人配置查询对象
|
||||
export interface CwApproverConfigQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
configType?: string;
|
||||
deptId?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
// 审批人配置视图对象
|
||||
export interface CwApproverConfigVO {
|
||||
id?: number;
|
||||
configType?: string;
|
||||
deptId?: number;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
deptApproverId?: number;
|
||||
deptApproverName?: string;
|
||||
financeApproverId?: number;
|
||||
financeApproverName?: string;
|
||||
leaderApproverId?: number;
|
||||
leaderApproverName?: string;
|
||||
deptDelegateId?: number;
|
||||
deptDelegateName?: string;
|
||||
financeDelegateId?: number;
|
||||
financeDelegateName?: string;
|
||||
leaderDelegateId?: number;
|
||||
leaderDelegateName?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
// 审批人配置表单对象
|
||||
export interface CwApproverConfigForm {
|
||||
id?: number;
|
||||
configType?: string;
|
||||
deptId?: number;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
deptApproverId?: number;
|
||||
deptApproverName?: string;
|
||||
financeApproverId?: number;
|
||||
financeApproverName?: string;
|
||||
leaderApproverId?: number;
|
||||
leaderApproverName?: string;
|
||||
deptDelegateId?: number;
|
||||
deptDelegateName?: string;
|
||||
financeDelegateId?: number;
|
||||
financeDelegateName?: string;
|
||||
leaderDelegateId?: number;
|
||||
leaderDelegateName?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 项目申报统计对象
|
||||
export interface CwApplicationStatistics {
|
||||
totalCount: number;
|
||||
approvingCount: number;
|
||||
approvedCount: number;
|
||||
rejectedCount: number;
|
||||
totalAmount: number;
|
||||
monthlyTrend: MonthlyTrend[];
|
||||
deptStatistics: DeptStatistics[];
|
||||
statusDistribution: StatusDistribution[];
|
||||
}
|
||||
|
||||
// 部门统计对象
|
||||
export interface DeptStatistics {
|
||||
deptName: string;
|
||||
totalCount: number;
|
||||
totalAmount: number;
|
||||
approvedCount: number;
|
||||
approvedAmount: number;
|
||||
}
|
||||
|
||||
// 月度趋势对象
|
||||
export interface MonthlyTrend {
|
||||
month: string;
|
||||
count: number;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
// 状态分布对象
|
||||
export interface StatusDistribution {
|
||||
status: string;
|
||||
statusName: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
}
|
||||
@@ -8,7 +8,10 @@ export function listHallReservation(query: HallReservationQuery) {
|
||||
return request<TableDataInfo<HallReservationVO>>({
|
||||
url: '/hq/hallReservation/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
params: query,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +19,10 @@ export function listHallReservation(query: HallReservationQuery) {
|
||||
export function getHallReservation(id: string | number) {
|
||||
return request<R<HallReservationVO>>({
|
||||
url: '/hq/hallReservation/' + id,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +31,10 @@ export function auditHallReservation(data: HallReservationAuditForm) {
|
||||
return request<R<any>>({
|
||||
url: '/hq/hallReservation/audit',
|
||||
method: 'put',
|
||||
data: data
|
||||
data: data,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,6 +43,9 @@ export function checkTimeConflict(data: TimeCheckParams) {
|
||||
return request<R<TimeCheckResult>>({
|
||||
url: '/hq/hallReservation/checkTimeConflict',
|
||||
method: 'post',
|
||||
data: data
|
||||
data: data,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -12,7 +12,10 @@ export const listHallInfo = (query?: HallInfoQuery): AxiosPromise<HallInfoVO[]>
|
||||
return request({
|
||||
url: '/hq/hallInfo/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
params: query,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -23,7 +26,10 @@ export const listHallInfo = (query?: HallInfoQuery): AxiosPromise<HallInfoVO[]>
|
||||
export const getHallInfo = (id: string | number): AxiosPromise<HallInfoVO> => {
|
||||
return request({
|
||||
url: '/hq/hallInfo/' + id,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +41,10 @@ export const addHallInfo = (data: HallInfoForm) => {
|
||||
return request({
|
||||
url: '/hq/hallInfo',
|
||||
method: 'post',
|
||||
data: data
|
||||
data: data,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -47,7 +56,10 @@ export const updateHallInfo = (data: HallInfoForm) => {
|
||||
return request({
|
||||
url: '/hq/hallInfo',
|
||||
method: 'put',
|
||||
data: data
|
||||
data: data,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,6 +70,9 @@ export const updateHallInfo = (data: HallInfoForm) => {
|
||||
export const delHallInfo = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/hq/hallInfo/' + id,
|
||||
method: 'delete'
|
||||
method: 'delete',
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -12,7 +12,10 @@ export const listHallReservation = (query?: HallReservationQuery): AxiosPromise<
|
||||
return request({
|
||||
url: '/hq/hallReservation/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
params: query,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -23,7 +26,10 @@ export const listHallReservation = (query?: HallReservationQuery): AxiosPromise<
|
||||
export const getHallReservation = (id: string | number): AxiosPromise<HallReservationVO> => {
|
||||
return request({
|
||||
url: '/hq/hallReservation/' + id,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +41,10 @@ export const addHallReservation = (data: HallReservationForm) => {
|
||||
return request({
|
||||
url: '/hq/hallReservation',
|
||||
method: 'post',
|
||||
data: data
|
||||
data: data,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -47,7 +56,10 @@ export const updateHallReservation = (data: HallReservationForm) => {
|
||||
return request({
|
||||
url: '/hq/hallReservation',
|
||||
method: 'put',
|
||||
data: data
|
||||
data: data,
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,6 +70,9 @@ export const updateHallReservation = (data: HallReservationForm) => {
|
||||
export const delHallReservation = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/hq/hallReservation/' + id,
|
||||
method: 'delete'
|
||||
method: 'delete',
|
||||
headers: {
|
||||
isEncrypt: 'true'
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* 部门树结构
|
||||
*/
|
||||
export interface XgDeptTreeVO extends BaseEntity {
|
||||
id: number | string;
|
||||
label: string;
|
||||
parentId: number | string;
|
||||
weight: number;
|
||||
children: XgDeptTreeVO[];
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
// 学生查询参数
|
||||
export interface StudentQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
phonenumber?: string;
|
||||
status?: string;
|
||||
deptId?: number;
|
||||
}
|
||||
|
||||
// 学生视图对象
|
||||
export interface StudentVO {
|
||||
studentId: number | string;
|
||||
studentNo: string;
|
||||
studentName: string;
|
||||
nickName?: string;
|
||||
sex?: string;
|
||||
age?: number;
|
||||
phonenumber?: string;
|
||||
email?: string;
|
||||
idCard?: string;
|
||||
deptId?: number;
|
||||
deptName?: string;
|
||||
majorId?: number;
|
||||
majorName?: string;
|
||||
classId?: number;
|
||||
className?: string;
|
||||
address?: string;
|
||||
status?: string;
|
||||
createBy?: number;
|
||||
createTime?: string;
|
||||
updateBy?: number;
|
||||
updateTime?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 学生表单对象
|
||||
export interface StudentForm {
|
||||
studentId?: number | string;
|
||||
deptId?: number;
|
||||
studentNo: string;
|
||||
studentName: string;
|
||||
nickName?: string;
|
||||
password?: string;
|
||||
phonenumber?: string;
|
||||
email?: string;
|
||||
idCard?: string;
|
||||
sex?: string;
|
||||
status?: string;
|
||||
majorId?: number;
|
||||
classId?: number;
|
||||
address?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 统一响应类型
|
||||
export interface R<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// 表格数据信息类型
|
||||
export interface TableDataInfo<T> {
|
||||
total: number;
|
||||
rows: T;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { SysRegionVO, SysRegionForm, SysRegionQuery } from '@/api/system/SysRegion/types';
|
||||
|
||||
/**
|
||||
* 查询行政区划列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listSysRegion = (query?: SysRegionQuery): AxiosPromise<SysRegionVO[]> => {
|
||||
return request({
|
||||
url: '/system/SysRegion/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询行政区划详细
|
||||
* @param id
|
||||
*/
|
||||
export const getSysRegion = (id: string | number): AxiosPromise<SysRegionVO> => {
|
||||
return request({
|
||||
url: '/system/SysRegion/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增行政区划
|
||||
* @param data
|
||||
*/
|
||||
export const addSysRegion = (data: SysRegionForm) => {
|
||||
return request({
|
||||
url: '/system/SysRegion',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改行政区划
|
||||
* @param data
|
||||
*/
|
||||
export const updateSysRegion = (data: SysRegionForm) => {
|
||||
return request({
|
||||
url: '/system/SysRegion',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除行政区划
|
||||
* @param id
|
||||
*/
|
||||
export const delSysRegion = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/system/SysRegion/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
export interface SysRegionVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 行政区划编码
|
||||
*/
|
||||
code: string;
|
||||
|
||||
/**
|
||||
* 行政区划名称
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 行政区划简称
|
||||
*/
|
||||
shortName: string;
|
||||
|
||||
/**
|
||||
* 父级行政区划编码,顶级为0
|
||||
*/
|
||||
parentCode: number;
|
||||
|
||||
/**
|
||||
* 层级(1:省,2:市,3:区/县,4:街道/镇)
|
||||
*/
|
||||
level: number;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort: number;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
longitude: number;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
latitude: number;
|
||||
|
||||
/**
|
||||
* 逻辑删除标识(0未删除 1已删除)
|
||||
*/
|
||||
deleted: number;
|
||||
|
||||
/**
|
||||
* 子节点列表(用于树形结构)
|
||||
*/
|
||||
children?: SysRegionVO[];
|
||||
|
||||
}
|
||||
|
||||
export interface SysRegionForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 行政区划编码
|
||||
*/
|
||||
code?: string;
|
||||
|
||||
/**
|
||||
* 行政区划名称
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* 行政区划简称
|
||||
*/
|
||||
shortName?: string;
|
||||
|
||||
/**
|
||||
* 父级行政区划编码,顶级为0
|
||||
*/
|
||||
parentCode?: number;
|
||||
|
||||
/**
|
||||
* 层级(1:省,2:市,3:区/县,4:街道/镇)
|
||||
*/
|
||||
level?: number;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
longitude?: number;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
latitude?: number;
|
||||
|
||||
/**
|
||||
* 逻辑删除标识(0未删除 1已删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
}
|
||||
|
||||
export interface SysRegionQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 行政区划编码
|
||||
*/
|
||||
code?: string;
|
||||
|
||||
/**
|
||||
* 行政区划名称
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* 行政区划简称
|
||||
*/
|
||||
shortName?: string;
|
||||
|
||||
/**
|
||||
* 父级行政区划编码,顶级为0
|
||||
*/
|
||||
parentCode?: number;
|
||||
|
||||
/**
|
||||
* 层级(1:省,2:市,3:区/县,4:街道/镇)
|
||||
*/
|
||||
level?: number;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
longitude?: number;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
latitude?: number;
|
||||
|
||||
/**
|
||||
* 逻辑删除标识(0未删除 1已删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 重置bucket访问密码
|
||||
* @param bucketName bucket名称
|
||||
* @param newPassword 新密码
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function resetBucketPassword(bucketName: string, newPassword: string) {
|
||||
return request({
|
||||
url: '/system/bucket-password/reset',
|
||||
method: 'post',
|
||||
params: { bucketName, newPassword }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取bucket密码信息
|
||||
* @param bucketName bucket名称
|
||||
* @returns bucket密码信息
|
||||
*/
|
||||
export function getBucketPasswordInfo(bucketName: string) {
|
||||
return request({
|
||||
url: `/system/bucket-password/info/${bucketName}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取bucket访问记录
|
||||
* @param bucketName bucket名称
|
||||
* @param limit 限制数量,默认10条
|
||||
* @returns 访问记录列表
|
||||
*/
|
||||
export function getBucketAccessRecords(bucketName: string, limit: number = 10) {
|
||||
return request({
|
||||
url: `/system/bucket-password/records/${bucketName}`,
|
||||
method: 'get',
|
||||
params: { limit }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证bucket密码
|
||||
* @param bucketName bucket名称
|
||||
* @param password 密码
|
||||
* @returns 验证结果
|
||||
*/
|
||||
export function validateBucketPassword(bucketName: string, password: string) {
|
||||
return request({
|
||||
url: '/system/bucket-password/validate',
|
||||
method: 'post',
|
||||
params: { bucketName, password }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成强密码
|
||||
* @param length 密码长度,默认12位
|
||||
* @returns 生成的密码
|
||||
*/
|
||||
export function generateStrongPassword(length: number = 12): string {
|
||||
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
|
||||
let password = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
password += charset.charAt(Math.floor(Math.random() * charset.length))
|
||||
}
|
||||
return password
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码强度
|
||||
* @param password 密码
|
||||
* @returns 密码强度评估
|
||||
*/
|
||||
export function validatePasswordStrength(password: string): {
|
||||
isValid: boolean
|
||||
score: number
|
||||
suggestions: string[]
|
||||
} {
|
||||
const suggestions: string[] = []
|
||||
let score = 0
|
||||
|
||||
// 长度检查
|
||||
if (password.length >= 8) {
|
||||
score += 1
|
||||
} else {
|
||||
suggestions.push('密码长度至少8位')
|
||||
}
|
||||
|
||||
// 包含大写字母
|
||||
if (/[A-Z]/.test(password)) {
|
||||
score += 1
|
||||
} else {
|
||||
suggestions.push('包含大写字母')
|
||||
}
|
||||
|
||||
// 包含小写字母
|
||||
if (/[a-z]/.test(password)) {
|
||||
score += 1
|
||||
} else {
|
||||
suggestions.push('包含小写字母')
|
||||
}
|
||||
|
||||
// 包含数字
|
||||
if (/\d/.test(password)) {
|
||||
score += 1
|
||||
} else {
|
||||
suggestions.push('包含数字')
|
||||
}
|
||||
|
||||
// 包含特殊字符
|
||||
if (/[!@#$%^&*]/.test(password)) {
|
||||
score += 1
|
||||
} else {
|
||||
suggestions.push('包含特殊字符')
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: score >= 4,
|
||||
score,
|
||||
suggestions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取文件临时访问URL
|
||||
* @param ossId 文件ID
|
||||
* @param bucketPassword bucket访问密码
|
||||
* @param expireSeconds 过期时间(秒),默认300秒
|
||||
* @returns 临时访问URL
|
||||
*/
|
||||
export function getFileAccessUrl(ossId: number | string | bigint, bucketPassword?: string, expireSeconds: number = 300) {
|
||||
return request({
|
||||
url: `/resource/file/access/${ossId}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
bucketPassword,
|
||||
expireSeconds
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取文件临时访问URL
|
||||
* @param ossIds 文件ID列表
|
||||
* @param bucketPassword bucket访问密码
|
||||
* @param expireSeconds 过期时间(秒),默认300秒
|
||||
* @returns 文件ID与临时URL的映射
|
||||
*/
|
||||
export function getBatchFileAccessUrl(ossIds: number[], bucketPassword?: string, expireSeconds: number = 300) {
|
||||
return request({
|
||||
url: '/resource/file/access/batch',
|
||||
method: 'post',
|
||||
data: ossIds,
|
||||
params: {
|
||||
bucketPassword,
|
||||
expireSeconds
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件下载URL(带权限检查)
|
||||
* @param ossId 文件ID
|
||||
* @param bucketPassword bucket访问密码
|
||||
* @param expireSeconds 过期时间(秒),默认300秒
|
||||
* @returns 下载URL
|
||||
*/
|
||||
export function getFileDownloadUrl(ossId: number | string | bigint, bucketPassword?: string, expireSeconds: number = 300) {
|
||||
return request({
|
||||
url: `/resource/oss/download/${ossId}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
bucketPassword,
|
||||
expireSeconds
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件预览URL(带权限检查)
|
||||
* @param ossId 文件ID
|
||||
* @param bucketPassword bucket访问密码
|
||||
* @param expireSeconds 过期时间(秒),默认300秒
|
||||
* @returns 预览URL
|
||||
*/
|
||||
export function getFilePreviewUrl(ossId: number | string | bigint, bucketPassword?: string, expireSeconds: number = 300) {
|
||||
return request({
|
||||
url: `/resource/file/preview/${ossId}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
bucketPassword,
|
||||
expireSeconds
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全下载文件(带bucket密码验证)
|
||||
* @param ossId 文件ID
|
||||
* @param fileName 文件名
|
||||
* @param bucketPassword bucket访问密码
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export async function secureDownloadFile(ossId: number | string | bigint, fileName: string, bucketPassword?: string) {
|
||||
try {
|
||||
// 获取临时下载URL
|
||||
const { data: downloadUrl } = await getFileDownloadUrl(ossId, bucketPassword)
|
||||
|
||||
// 使用fetch下载文件
|
||||
const response = await fetch(downloadUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error('下载失败')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
|
||||
// 创建下载链接
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
// 释放内存
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
return Promise.resolve()
|
||||
} catch (error) {
|
||||
console.error('文件下载失败:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全预览文件(带bucket密码验证)
|
||||
* @param ossId 文件ID
|
||||
* @param contentType 文件类型
|
||||
* @param bucketPassword bucket访问密码
|
||||
* @returns Promise<string> 预览URL
|
||||
*/
|
||||
export async function securePreviewFile(ossId: number | string | bigint, contentType?: string, bucketPassword?: string) {
|
||||
try {
|
||||
// 获取临时预览URL
|
||||
const { data: previewUrl } = await getFilePreviewUrl(ossId, bucketPassword)
|
||||
|
||||
// 根据文件类型选择预览方式
|
||||
if (contentType?.startsWith('image/')) {
|
||||
// 图片直接返回URL用于预览
|
||||
return Promise.resolve(previewUrl)
|
||||
} else {
|
||||
// 其他文件在新窗口打开
|
||||
window.open(previewUrl, '_blank')
|
||||
return Promise.resolve(previewUrl)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('文件预览失败:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取bucket访问密码提示
|
||||
* @param bucketName bucket名称
|
||||
* @returns 密码提示信息
|
||||
*/
|
||||
export function getBucketPasswordHint(bucketName: string) {
|
||||
// 这里可以根据bucket名称返回相应的密码提示
|
||||
// 例如:从配置中获取密码提示信息
|
||||
return `请输入 ${bucketName} bucket 的访问密码`
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证bucket密码格式
|
||||
* @param password 密码
|
||||
* @returns 验证结果
|
||||
*/
|
||||
export function validateBucketPassword(password: string): boolean {
|
||||
// 简单的密码格式验证
|
||||
return password && password.length >= 6
|
||||
}
|
||||
@@ -48,6 +48,7 @@ export interface UserVO extends BaseEntity {
|
||||
postIds: any;
|
||||
roleId: any;
|
||||
admin: boolean;
|
||||
accountType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { DormBedVO, DormBedForm, DormBedQuery } from '@/api/xgss/dormBed/types';
|
||||
import { DormBedVO, DormBedForm, DormBedQuery } from '@/api/xg/xgss/dormBed/types';
|
||||
|
||||
/**
|
||||
* 查询宿舍床位信息列表
|
||||
File renamed without changes.
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { DormRoomVO, DormRoomForm, DormRoomQuery } from '@/api/xgss/dormRoom/types';
|
||||
import { DormRoomVO, DormRoomForm, DormRoomQuery } from '@/api/xg/xgss/dormRoom/types';
|
||||
|
||||
/**
|
||||
* 查询宿舍房间信息列表
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StuColBatchVO, StuColBatchForm, StuColBatchQuery } from '@/api/xg/xsjcxx/XgStuColBatch/stuColBatch/types';
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStuColBatch = (query?: StuColBatchQuery): AxiosPromise<StuColBatchVO[]> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStuColBatch = (id: string | number): AxiosPromise<StuColBatchVO> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const addStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const updateStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生信息收集批次
|
||||
* @param id
|
||||
*/
|
||||
export const delStuColBatch = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
export interface StuColBatchVO {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchForm extends BaseEntity {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -152,6 +152,8 @@ export interface ClassMsgVO {
|
||||
lastCountUpdate?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
/** 学制 */
|
||||
schoolSystem?: string;
|
||||
}
|
||||
|
||||
/** 班级表单数据 */
|
||||
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StuColBatchClassVO, StuColBatchClassForm, StuColBatchClassQuery } from '@/api/xg/xsjcxx/stuColBatchClass/stuColBatchClass/types';
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次班级权限列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStuColBatchClass = (query?: StuColBatchClassQuery): AxiosPromise<StuColBatchClassVO[]> => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次班级权限详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStuColBatchClass = (id: string | number): AxiosPromise<StuColBatchClassVO> => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生信息收集批次班级权限
|
||||
* @param data
|
||||
*/
|
||||
export const addStuColBatchClass = (data: StuColBatchClassForm) => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生信息收集批次班级权限
|
||||
* @param data
|
||||
*/
|
||||
export const updateStuColBatchClass = (data: StuColBatchClassForm) => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生信息收集批次班级权限
|
||||
* @param id
|
||||
*/
|
||||
export const delStuColBatchClass = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置学生信息收集批次班级权限
|
||||
* @param batchId 批次ID
|
||||
* @param deptIds 授权的班级ID列表
|
||||
*/
|
||||
export const setScope = (batchId: string | number, deptIds: Array<string | number>) => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/setScope',
|
||||
method: 'post',
|
||||
params: {
|
||||
batchId,
|
||||
deptIds
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取学生信息收集批次班级权限
|
||||
* @param batchId 批次ID
|
||||
*/
|
||||
export const getScope = (batchId: string | number) => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/getScope/' + batchId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取批次配置的权限范围详细信息
|
||||
* @param batchId 批次ID
|
||||
*/
|
||||
export const getScopeDetail = (batchId: string | number) => {
|
||||
return request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/getScopeDetail/' + batchId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
export interface StuColBatchClassVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
batchId: string | number;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
classId: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className: string;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
majorId: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
majorName: string;
|
||||
|
||||
/**
|
||||
* 院系ID
|
||||
*/
|
||||
collegeId: string | number;
|
||||
|
||||
/**
|
||||
* 院系名称
|
||||
*/
|
||||
collegeName: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted: number;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchClassForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
batchId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
majorId?: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 院系ID
|
||||
*/
|
||||
collegeId?: string | number;
|
||||
|
||||
/**
|
||||
* 院系名称
|
||||
*/
|
||||
collegeName?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchClassQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
batchId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
majorId?: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 院系ID
|
||||
*/
|
||||
collegeId?: string | number;
|
||||
|
||||
/**
|
||||
* 院系名称
|
||||
*/
|
||||
collegeName?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StuColBatchVO, StuColBatchForm, StuColBatchQuery } from '@/api/xg/xsjcxx/stuMsgAudit/types';
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStuColBatch = (query?: StuColBatchQuery): AxiosPromise<StuColBatchVO[]> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStuColBatch = (id: string | number): AxiosPromise<StuColBatchVO> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const addStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const updateStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生信息收集批次
|
||||
* @param id
|
||||
*/
|
||||
export const delStuColBatch = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
export interface StuColBatchVO {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchForm extends BaseEntity {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StuColBatchVO, StuColBatchForm, StuColBatchQuery } from '@/api/xg/xsjcxx/stuMsgSupply/types';
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStuColBatch = (query?: StuColBatchQuery): AxiosPromise<StuColBatchVO[]> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStuColBatch = (id: string | number): AxiosPromise<StuColBatchVO> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const addStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const updateStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生信息收集批次
|
||||
* @param id
|
||||
*/
|
||||
export const delStuColBatch = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
export interface StuColBatchVO {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchForm extends BaseEntity {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import type { XgDeptTreeVO, R, TableDataInfo, XgStudentMsgAdvancedSearchQuery } from '@/api/xg/xsjcxx/stuRoster/types';
|
||||
|
||||
/**
|
||||
* 查询部门下拉树结构
|
||||
* @param deptId 部门ID
|
||||
*/
|
||||
export const XgDeptTreeSelect = (belongDeptId?: number): AxiosPromise<XgDeptTreeVO[]> => {
|
||||
return request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource/currentUserAuthTree',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 查询院系树结构
|
||||
export function deptTreeSelect(): AxiosPromise<R<any>> {
|
||||
return request({
|
||||
url: '/system/dept/treeselect',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询学生花名册列表 - 使用高级搜索接口
|
||||
export function listStudentRoster(query?: XgStudentMsgAdvancedSearchQuery): AxiosPromise<R<TableDataInfo<any[]>>> {
|
||||
return request({
|
||||
url: '/studentMsg/studentMsg/advancedSearch',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
}
|
||||
|
||||
// 导出学生花名册
|
||||
export function exportStudentRoster(query?: any): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentMsg/studentMsg/export',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生个人信息
|
||||
* @param userId 用户ID,可为空,为空时使用当前登录用户ID
|
||||
*/
|
||||
export function getStudentPersonalInfo(userId?: number): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentMsg/studentMsg/personalInfo',
|
||||
method: 'get',
|
||||
params: { userId }
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
XgDeptTreeSelect,
|
||||
deptTreeSelect,
|
||||
listStudentRoster,
|
||||
exportStudentRoster,
|
||||
getStudentPersonalInfo
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
// 部门树节点类型
|
||||
export interface XgDeptTreeVO {
|
||||
id: number;
|
||||
label: string;
|
||||
children?: XgDeptTreeVO[];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// 通用响应类型
|
||||
export interface R<T = any> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// 分页数据信息
|
||||
export interface TableDataInfo<T = any> {
|
||||
total: number;
|
||||
rows: T;
|
||||
}
|
||||
|
||||
// 学生花名册查询参数
|
||||
export interface StudentRosterQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
studentNo?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
studentStatus?: string;
|
||||
deptIds?: number[];
|
||||
}
|
||||
|
||||
// 学生花名册数据
|
||||
export interface StudentRosterVO {
|
||||
studentId: number;
|
||||
studentNo: string;
|
||||
studentName: string;
|
||||
nickName?: string;
|
||||
sex: string;
|
||||
age?: number;
|
||||
phonenumber: string;
|
||||
email?: string;
|
||||
idCard: string;
|
||||
deptId: number;
|
||||
deptName: string;
|
||||
majorId?: number;
|
||||
majorName?: string;
|
||||
classId?: number;
|
||||
className?: string;
|
||||
address?: string;
|
||||
studentStatus: string;
|
||||
createTime: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 学生信息高级搜索查询参数
|
||||
export interface XgStudentMsgAdvancedSearchQuery {
|
||||
// 基础信息检索
|
||||
studentNo?: string;
|
||||
name?: string;
|
||||
idCard?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
gender?: string[];
|
||||
nationality?: string[];
|
||||
politicalStatus?: string[];
|
||||
birthDateStart?: string;
|
||||
birthDateEnd?: string;
|
||||
nameEnglish?: string;
|
||||
formerName?: string;
|
||||
nativePlace?: string[];
|
||||
hukouLocation?: string;
|
||||
homeAddress?: string;
|
||||
currentAddress?: string;
|
||||
qq?: string;
|
||||
wechat?: string;
|
||||
maritalStatus?: string[];
|
||||
religion?: string;
|
||||
householdRegisterType?: string[];
|
||||
educationLevel?: string[];
|
||||
|
||||
// 学籍信息检索
|
||||
studentStatus?: string[];
|
||||
enrollmentYearStart?: number;
|
||||
enrollmentYearEnd?: number;
|
||||
enrollmentDateStart?: string;
|
||||
enrollmentDateEnd?: string;
|
||||
admissionCategory?: string[];
|
||||
|
||||
// 学院专业班级检索
|
||||
collegeName?: string;
|
||||
majorName?: string;
|
||||
className?: string;
|
||||
grade?: string[];
|
||||
gradeMin?: string;
|
||||
gradeMax?: string;
|
||||
counselorName?: string;
|
||||
classTeacherName?: string;
|
||||
|
||||
// 家庭信息检索
|
||||
fatherName?: string;
|
||||
motherName?: string;
|
||||
guardianName?: string;
|
||||
familyIncomeMin?: number;
|
||||
familyIncomeMax?: number;
|
||||
perCapitaIncomeMin?: number;
|
||||
perCapitaIncomeMax?: number;
|
||||
familyEconomicStatus?: string[];
|
||||
familyMembersMin?: number;
|
||||
familyMembersMax?: number;
|
||||
|
||||
// 特殊身份检索
|
||||
isPoorStudent?: string[];
|
||||
isMinority?: string[];
|
||||
isDisabled?: string[];
|
||||
isOrphan?: string[];
|
||||
isLeftBehindChild?: string[];
|
||||
isSingleChild?: string[];
|
||||
isLowIncome?: string[];
|
||||
hasLoan?: string[];
|
||||
loanAmountMin?: number;
|
||||
loanAmountMax?: number;
|
||||
|
||||
// 身体状况检索
|
||||
heightMin?: number;
|
||||
heightMax?: number;
|
||||
weightMin?: number;
|
||||
weightMax?: number;
|
||||
bloodType?: string[];
|
||||
healthStatus?: string;
|
||||
disabilityType?: string[];
|
||||
disabilityLevel?: string;
|
||||
|
||||
// 住宿信息检索
|
||||
isBoarder?: string[];
|
||||
dormitoryBuilding?: string[];
|
||||
dormitoryRoom?: string;
|
||||
|
||||
// 家庭详细信息检索
|
||||
fatherPhone?: string;
|
||||
fatherWorkUnit?: string;
|
||||
motherPhone?: string;
|
||||
motherWorkUnit?: string;
|
||||
guardianPhone?: string;
|
||||
guardianRelation?: string;
|
||||
incomeSource?: string;
|
||||
emergencyContactName?: string;
|
||||
emergencyContactPhone?: string;
|
||||
emergencyContactRelation?: string;
|
||||
|
||||
// 扩展信息检索
|
||||
specialties?: string;
|
||||
awards?: string;
|
||||
violations?: string;
|
||||
socialActivities?: string;
|
||||
|
||||
// 健康详细信息检索
|
||||
specialCondition?: string;
|
||||
medicalHistory?: string;
|
||||
allergies?: string;
|
||||
|
||||
// 其他信息检索
|
||||
sourceRegion?: string[];
|
||||
graduateSchool?: string;
|
||||
trainingType?: string[];
|
||||
academicLevel?: string[];
|
||||
schoolSystemMin?: number;
|
||||
schoolSystemMax?: number;
|
||||
infoStatus?: string;
|
||||
dataSource?: string;
|
||||
isComplete?: number;
|
||||
completionRateMin?: number;
|
||||
completionRateMax?: number;
|
||||
|
||||
// 时间范围检索
|
||||
createTimeStart?: string;
|
||||
createTimeEnd?: string;
|
||||
updateTimeStart?: string;
|
||||
updateTimeEnd?: string;
|
||||
|
||||
// 部门树节点
|
||||
deptIds?: number[];
|
||||
|
||||
// 分页参数
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
File renamed without changes.
@@ -9,12 +9,17 @@ import { StudentQuery, StudentVO, StudentForm, XgDeptTreeVO, R, TableDataInfo }
|
||||
*/
|
||||
export const XgDeptTreeSelect = (belongDeptId?: number): AxiosPromise<XgDeptTreeVO[]> => {
|
||||
// 实际项目中连接后端接口
|
||||
// return request({
|
||||
// url: '/system/user/deptTree',
|
||||
// method: 'get',
|
||||
// params: {
|
||||
// belongDeptId
|
||||
// }
|
||||
// });
|
||||
|
||||
return request({
|
||||
url: '/system/user/deptTree',
|
||||
method: 'get',
|
||||
params: {
|
||||
belongDeptId
|
||||
}
|
||||
url: '/xgStudentManagerSource/studentManagerSource/currentUserAuthTree',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
// // 模拟数据,直接返回与页面组件期望格式一致的数据
|
||||
@@ -182,6 +187,15 @@ export function exportStudent(query?: StudentQuery): AxiosPromise<any> {
|
||||
}) as any;
|
||||
}
|
||||
|
||||
// 下载导入模板
|
||||
export function downloadImportTemplate(): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentMsg/studentMsg/importTemplate',
|
||||
method: 'post',
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 学生状态修改
|
||||
export function changeStudentStatus(studentId: string | number, status: string): AxiosPromise<R<any>> {
|
||||
// 实际使用时连接后端接口
|
||||
@@ -222,7 +236,7 @@ const mockStudentData: StudentVO[] = [
|
||||
classId: 101,
|
||||
className: '计算机2001班',
|
||||
address: '北京市海淀区',
|
||||
status: '0',
|
||||
studentStatus: '1',
|
||||
createTime: '2023-01-01 10:00:00',
|
||||
remark: ''
|
||||
},
|
||||
@@ -243,7 +257,7 @@ const mockStudentData: StudentVO[] = [
|
||||
classId: 101,
|
||||
className: '计算机2001班',
|
||||
address: '上海市浦东新区',
|
||||
status: '0',
|
||||
studentStatus: '2',
|
||||
createTime: '2023-01-01 10:10:00',
|
||||
remark: ''
|
||||
},
|
||||
@@ -264,7 +278,7 @@ const mockStudentData: StudentVO[] = [
|
||||
classId: 201,
|
||||
className: '软工2001班',
|
||||
address: '广州市天河区',
|
||||
status: '0',
|
||||
studentStatus: '5',
|
||||
createTime: '2023-01-02 09:00:00',
|
||||
remark: ''
|
||||
},
|
||||
@@ -285,7 +299,7 @@ const mockStudentData: StudentVO[] = [
|
||||
classId: 201,
|
||||
className: '软工2001班',
|
||||
address: '深圳市南山区',
|
||||
status: '0',
|
||||
studentStatus: '3',
|
||||
createTime: '2023-01-02 10:30:00',
|
||||
remark: ''
|
||||
},
|
||||
@@ -306,7 +320,7 @@ const mockStudentData: StudentVO[] = [
|
||||
classId: 301,
|
||||
className: '人工智能2001班',
|
||||
address: '杭州市西湖区',
|
||||
status: '1', // 停用状态
|
||||
studentStatus: '4',
|
||||
createTime: '2023-01-03 11:20:00',
|
||||
remark: '休学中'
|
||||
}
|
||||
+24
-1
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StudentMsgVO, StudentMsgForm, StudentMsgQuery } from '@/api/studentMsg/studentMsg/types';
|
||||
import { StudentMsgVO, StudentMsgForm, StudentMsgQuery } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
|
||||
|
||||
/**
|
||||
* 查询学生信息列表
|
||||
@@ -61,3 +61,26 @@ export const delStudentMsg = (id: string | number | Array<string | number>) => {
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取学生个人信息
|
||||
* @param userId 用户ID,可为空,为空时使用当前登录用户ID
|
||||
*/
|
||||
export const getPersonalInfo = (userId?: number): AxiosPromise<StudentMsgForm> => {
|
||||
return request({
|
||||
url: '/studentMsg/studentMsg/personalInfo',
|
||||
method: 'get',
|
||||
params: { userId }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载导入模板
|
||||
*/
|
||||
export const downloadImportTemplate = () => {
|
||||
return request({
|
||||
url: '/studentMsg/studentMsg/importTemplate',
|
||||
method: 'post',
|
||||
responseType: 'blob'
|
||||
});
|
||||
};
|
||||
+6
-6
@@ -35,7 +35,7 @@ export interface StudentMsgVO {
|
||||
formerName: string;
|
||||
|
||||
/**
|
||||
* 性别:M-男,F-女
|
||||
* 性别:0-男,1-女
|
||||
*/
|
||||
gender: string;
|
||||
|
||||
@@ -417,7 +417,7 @@ export interface StudentMsgVO {
|
||||
/**
|
||||
* 学历层次 - 预留,数据从班级表关联
|
||||
*/
|
||||
academicLevel: string;
|
||||
academicLevel: string | number;
|
||||
|
||||
/**
|
||||
* 培养方式:全日制、在职、函授、夜大等 - 预留,数据从班级表关联
|
||||
@@ -558,7 +558,7 @@ export interface StudentMsgForm extends BaseEntity {
|
||||
formerName?: string;
|
||||
|
||||
/**
|
||||
* 性别:M-男,F-女
|
||||
* 性别:0-男,1-女
|
||||
*/
|
||||
gender?: string;
|
||||
|
||||
@@ -940,7 +940,7 @@ export interface StudentMsgForm extends BaseEntity {
|
||||
/**
|
||||
* 学历层次 - 预留,数据从班级表关联
|
||||
*/
|
||||
academicLevel?: string;
|
||||
academicLevel?: string | number;
|
||||
|
||||
/**
|
||||
* 培养方式:全日制、在职、函授、夜大等 - 预留,数据从班级表关联
|
||||
@@ -1077,7 +1077,7 @@ export interface StudentMsgQuery extends PageQuery {
|
||||
formerName?: string;
|
||||
|
||||
/**
|
||||
* 性别:M-男,F-女
|
||||
* 性别:0-男,1-女
|
||||
*/
|
||||
gender?: string;
|
||||
|
||||
@@ -1459,7 +1459,7 @@ export interface StudentMsgQuery extends PageQuery {
|
||||
/**
|
||||
* 学历层次 - 预留,数据从班级表关联
|
||||
*/
|
||||
academicLevel?: string;
|
||||
academicLevel?: string | number;
|
||||
|
||||
/**
|
||||
* 培养方式:全日制、在职、函授、夜大等 - 预留,数据从班级表关联
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 部门树结构
|
||||
*/
|
||||
export interface XgDeptTreeVO extends BaseEntity {
|
||||
id: number | string;
|
||||
label: string;
|
||||
parentId: number | string;
|
||||
weight: number;
|
||||
children: XgDeptTreeVO[];
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
// 学生查询参数
|
||||
export interface StudentQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
name?: string;
|
||||
studentNo?: string;
|
||||
phone?: string;
|
||||
studentStatus?: string;
|
||||
deptId?: number;
|
||||
}
|
||||
|
||||
// 学生视图对象
|
||||
export interface StudentVO {
|
||||
id: number | string;
|
||||
studentNo: string;
|
||||
name: string;
|
||||
nickName?: string;
|
||||
gender?: string;
|
||||
age?: number;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
idCard?: string;
|
||||
collegeId?: number;
|
||||
colegeName?: string;
|
||||
majorId?: number;
|
||||
majorName?: string;
|
||||
classId?: number;
|
||||
className?: string;
|
||||
address?: string;
|
||||
studentStatus?: string;
|
||||
createBy?: number;
|
||||
createTime?: string;
|
||||
updateBy?: number;
|
||||
updateTime?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 学生表单对象
|
||||
export interface StudentForm {
|
||||
id?: number | string;
|
||||
// 原有字段
|
||||
studentId?: number | string;
|
||||
deptId?: number;
|
||||
studentNo: string;
|
||||
studentName: string;
|
||||
nickName?: string;
|
||||
password?: string;
|
||||
phonenumber?: string;
|
||||
email?: string;
|
||||
idCard?: string;
|
||||
sex?: string;
|
||||
status?: string;
|
||||
majorId?: number;
|
||||
classId?: number;
|
||||
address?: string;
|
||||
remark?: string;
|
||||
|
||||
// 基础信息字段
|
||||
student_no?: string;
|
||||
id_card?: string;
|
||||
name?: string;
|
||||
name_english?: string;
|
||||
former_name?: string;
|
||||
gender?: string;
|
||||
birth_date?: string;
|
||||
nationality?: string;
|
||||
native_place?: string;
|
||||
political_status?: string;
|
||||
hukou_location?: string;
|
||||
home_address?: string;
|
||||
current_address?: string;
|
||||
postal_code?: string;
|
||||
photo_url?: string;
|
||||
photo_path?: string;
|
||||
phone?: string;
|
||||
is_boarder?: number;
|
||||
|
||||
// 扩展信息字段
|
||||
education_level?: string;
|
||||
household_register_type?: string;
|
||||
religion?: string;
|
||||
source_region?: string;
|
||||
graduate_school?: string;
|
||||
qq?: string;
|
||||
wechat?: string;
|
||||
marital_status?: string;
|
||||
emergency_contact_name?: string;
|
||||
emergency_contact_phone?: string;
|
||||
emergency_contact_relation?: string;
|
||||
is_poor_student?: number;
|
||||
is_minority?: number;
|
||||
is_disabled?: number;
|
||||
is_orphan?: number;
|
||||
is_left_behind_child?: number;
|
||||
is_single_child?: number;
|
||||
is_low_income?: number;
|
||||
has_loan?: number;
|
||||
loan_amount?: string;
|
||||
specialties?: string;
|
||||
awards?: string;
|
||||
violations?: string;
|
||||
social_activities?: string;
|
||||
|
||||
// 家庭信息字段
|
||||
father_name?: string;
|
||||
father_phone?: string;
|
||||
father_work_unit?: string;
|
||||
mother_name?: string;
|
||||
mother_phone?: string;
|
||||
mother_work_unit?: string;
|
||||
guardian_name?: string;
|
||||
guardian_phone?: string;
|
||||
guardian_relation?: string;
|
||||
family_income?: string;
|
||||
per_capita_income?: string;
|
||||
income_source?: string;
|
||||
family_economic_status?: string;
|
||||
family_members?: string;
|
||||
|
||||
// 健康信息字段
|
||||
height?: number | string;
|
||||
weight?: number | string;
|
||||
blood_type?: string;
|
||||
health_status?: string;
|
||||
special_condition?: string;
|
||||
medical_history?: string;
|
||||
allergies?: string;
|
||||
disability_type?: string;
|
||||
disability_level?: string;
|
||||
|
||||
// 学籍信息字段
|
||||
admission_category?: string;
|
||||
student_status?: string;
|
||||
enrollment_year?: string;
|
||||
enrollment_date?: string;
|
||||
school_system?: string;
|
||||
academic_level?: string | number;
|
||||
training_type?: string;
|
||||
major_name?: string;
|
||||
class_name?: string;
|
||||
grade?: string;
|
||||
college_name?: string;
|
||||
counselor_name?: string;
|
||||
class_teacher_name?: string;
|
||||
dormitory_building?: string;
|
||||
dormitory_room?: string;
|
||||
|
||||
// 银行卡信息字段
|
||||
bank_card_no?: string;
|
||||
bank_name?: string;
|
||||
bank_branch?: string;
|
||||
card_holder_name?: string;
|
||||
}
|
||||
|
||||
// 统一响应类型
|
||||
export interface R<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// 表格数据信息类型
|
||||
export interface TableDataInfo<T> {
|
||||
total: number;
|
||||
rows: T;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StudentMsgBatchVO, StudentMsgBatchForm, StudentMsgBatchQuery } from '@/api/xg/xsjcxx/studentMsgBatch/studentMsgBatch/types';
|
||||
|
||||
/**
|
||||
* 查询学生信息批次收集列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStudentMsgBatch = (query?: StudentMsgBatchQuery): AxiosPromise<StudentMsgBatchVO[]> => {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生信息批次收集详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStudentMsgBatch = (id: string | number): AxiosPromise<StudentMsgBatchVO> => {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生信息批次收集
|
||||
* @param data
|
||||
*/
|
||||
export const addStudentMsgBatch = (data: StudentMsgBatchForm) => {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生信息批次收集
|
||||
* @param data
|
||||
*/
|
||||
export const updateStudentMsgBatch = (data: StudentMsgBatchForm) => {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生信息批次收集
|
||||
* @param id
|
||||
*/
|
||||
export const delStudentMsgBatch = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { XgClassPosDictVO, XgClassPosDictForm, XgClassPosDictQuery } from '@/api/xg/xsjcxx/xgClassPosDict/xgClassPosDict/types';
|
||||
|
||||
/**
|
||||
* 查询班级干部职务配置列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listXgClassPosDict = (query?: XgClassPosDictQuery): AxiosPromise<XgClassPosDictVO[]> => {
|
||||
return request({
|
||||
url: '/xgClassPosDict/xgClassPosDict/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询班级干部职务配置详细
|
||||
* @param id
|
||||
*/
|
||||
export const getXgClassPosDict = (id: string | number): AxiosPromise<XgClassPosDictVO> => {
|
||||
return request({
|
||||
url: '/xgClassPosDict/xgClassPosDict/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增班级干部职务配置
|
||||
* @param data
|
||||
*/
|
||||
export const addXgClassPosDict = (data: XgClassPosDictForm) => {
|
||||
return request({
|
||||
url: '/xgClassPosDict/xgClassPosDict',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改班级干部职务配置
|
||||
* @param data
|
||||
*/
|
||||
export const updateXgClassPosDict = (data: XgClassPosDictForm) => {
|
||||
return request({
|
||||
url: '/xgClassPosDict/xgClassPosDict',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除班级干部职务配置
|
||||
* @param id
|
||||
*/
|
||||
export const delXgClassPosDict = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/xgClassPosDict/xgClassPosDict/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,171 @@
|
||||
export interface XgClassPosDictVO {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted: number;
|
||||
|
||||
/**
|
||||
* 职务编码
|
||||
*/
|
||||
positionCode: string;
|
||||
|
||||
/**
|
||||
* 职务名称
|
||||
*/
|
||||
positionName: string;
|
||||
|
||||
/**
|
||||
* 学生职务类别:class-班级干部,party-党团干部,other-其他
|
||||
*/
|
||||
positionCategory: string;
|
||||
|
||||
/**
|
||||
* 学生职务级别:monitor-班长,vice-monitor-副班长,committee-委员,ordinary-普通成员
|
||||
*/
|
||||
positionLevel: number;
|
||||
|
||||
/**
|
||||
* 学生职务类型:regular-常规职务,special-特殊职务,temporary-临时职务
|
||||
*/
|
||||
positionType: string;
|
||||
|
||||
/**
|
||||
* 职责描述
|
||||
*/
|
||||
responsibilities: string;
|
||||
|
||||
/**
|
||||
* 状态:0-启用,1-停用
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 排序顺序
|
||||
*/
|
||||
sortOrder: number;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
}
|
||||
|
||||
export interface XgClassPosDictForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 职务编码
|
||||
*/
|
||||
positionCode?: string;
|
||||
|
||||
/**
|
||||
* 职务名称
|
||||
*/
|
||||
positionName?: string;
|
||||
|
||||
/**
|
||||
* 学生职务类别:class-班级干部,party-党团干部,other-其他
|
||||
*/
|
||||
positionCategory?: string;
|
||||
|
||||
/**
|
||||
* 学生职务级别:monitor-班长,vice-monitor-副班长,committee-委员,ordinary-普通成员
|
||||
*/
|
||||
positionLevel?: number;
|
||||
|
||||
/**
|
||||
* 学生职务类型:regular-常规职务,special-特殊职务,temporary-临时职务
|
||||
*/
|
||||
positionType?: string;
|
||||
|
||||
/**
|
||||
* 职责描述
|
||||
*/
|
||||
responsibilities?: string;
|
||||
|
||||
/**
|
||||
* 状态:0-启用,1-停用
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 排序顺序
|
||||
*/
|
||||
sortOrder?: number;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface XgClassPosDictQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 职务编码
|
||||
*/
|
||||
positionCode?: string;
|
||||
|
||||
/**
|
||||
* 职务名称
|
||||
*/
|
||||
positionName?: string;
|
||||
|
||||
/**
|
||||
* 学生职务类别:class-班级干部,party-党团干部,other-其他
|
||||
*/
|
||||
positionCategory?: string;
|
||||
|
||||
/**
|
||||
* 学生职务级别:monitor-班长,vice-monitor-副班长,committee-委员,ordinary-普通成员
|
||||
*/
|
||||
positionLevel?: number;
|
||||
|
||||
/**
|
||||
* 学生职务类型:regular-常规职务,special-特殊职务,temporary-临时职务
|
||||
*/
|
||||
positionType?: string;
|
||||
|
||||
/**
|
||||
* 职责描述
|
||||
*/
|
||||
responsibilities?: string;
|
||||
|
||||
/**
|
||||
* 状态:0-启用,1-停用
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 排序顺序
|
||||
*/
|
||||
sortOrder?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { XgClassPosMsgVO, XgClassPosMsgForm, XgClassPosMsgQuery } from '@/api/xg/xsjcxx/xgClassPosMsg/xgClassPosMsg/types';
|
||||
|
||||
/**
|
||||
* 查询班级干部信息列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listXgClassPosMsg = (query?: XgClassPosMsgQuery): AxiosPromise<XgClassPosMsgVO[]> => {
|
||||
return request({
|
||||
url: '/xgClassPosMsg/xgClassPosMsg/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询班级干部信息详细
|
||||
* @param id
|
||||
*/
|
||||
export const getXgClassPosMsg = (id: string | number): AxiosPromise<XgClassPosMsgVO> => {
|
||||
return request({
|
||||
url: '/xgClassPosMsg/xgClassPosMsg/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增班级干部信息
|
||||
* @param data
|
||||
*/
|
||||
export const addXgClassPosMsg = (data: XgClassPosMsgForm) => {
|
||||
return request({
|
||||
url: '/xgClassPosMsg/xgClassPosMsg',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改班级干部信息
|
||||
* @param data
|
||||
*/
|
||||
export const updateXgClassPosMsg = (data: XgClassPosMsgForm) => {
|
||||
return request({
|
||||
url: '/xgClassPosMsg/xgClassPosMsg',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除班级干部信息
|
||||
* @param id
|
||||
*/
|
||||
export const delXgClassPosMsg = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/xgClassPosMsg/xgClassPosMsg/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 学生干部离任
|
||||
* @param id 班级干部信息ID
|
||||
* @param termEndDate 任期结束日期
|
||||
* @param cadreStatus 学生职务任职状态
|
||||
* @param dismissalReason 离任原因
|
||||
*/
|
||||
export const dismissCadre = (id: string | number, termEndDate: string, cadreStatus: string, dismissalReason: string) => {
|
||||
return request({
|
||||
url: '/xgClassPosMsg/xgClassPosMsg/dismiss/' + id,
|
||||
method: 'post',
|
||||
params: {
|
||||
termEndDate,
|
||||
cadreStatus,
|
||||
dismissalReason
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,346 @@
|
||||
export interface XgClassPosMsgVO {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted: number;
|
||||
|
||||
/**
|
||||
* 班级ID,关联xg_class_msg.id
|
||||
*/
|
||||
classId: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称(联表查询获取)
|
||||
*/
|
||||
className: string;
|
||||
|
||||
/**
|
||||
* 专业ID,关联sys_dept.dept_id
|
||||
*/
|
||||
majorId: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称(联表查询获取)
|
||||
*/
|
||||
majorName: string;
|
||||
|
||||
/**
|
||||
* 学院ID,关联sys_dept.dept_id
|
||||
*/
|
||||
collegeId: string | number;
|
||||
|
||||
/**
|
||||
* 学院名称(联表查询获取)
|
||||
*/
|
||||
collegeName: string;
|
||||
|
||||
/**
|
||||
* 学生ID,关联xg_student_msg.id
|
||||
*/
|
||||
studentId: string | number;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
studentNo: string;
|
||||
|
||||
/**
|
||||
* 学生姓名(冗余字段)
|
||||
*/
|
||||
studentName: string;
|
||||
|
||||
/**
|
||||
* 职务编码,关联xg_class_pos_dict.position_code
|
||||
*/
|
||||
positionCode: string;
|
||||
|
||||
/**
|
||||
* 职务名称(联表查询获取)
|
||||
*/
|
||||
positionName: string;
|
||||
|
||||
/**
|
||||
* 任职日期
|
||||
*/
|
||||
appointmentDate: string;
|
||||
|
||||
/**
|
||||
* 任期开始日期
|
||||
*/
|
||||
termStartDate: string;
|
||||
|
||||
/**
|
||||
* 任期结束日期
|
||||
*/
|
||||
termEndDate: string;
|
||||
|
||||
/**
|
||||
* 任期时长(月)
|
||||
*/
|
||||
termDuration: number;
|
||||
|
||||
/**
|
||||
* 是否现任:0-否,1-是
|
||||
*/
|
||||
isCurrent: number;
|
||||
|
||||
/**
|
||||
* 学生职务任职状态:1-在任,2-到期,3-辞职,4-免职,5-调任,6-休学
|
||||
*/
|
||||
cadreStatus: string;
|
||||
|
||||
/**
|
||||
* 任命原因
|
||||
*/
|
||||
appointmentReason: string;
|
||||
|
||||
/**
|
||||
* 学生职务任命方式:appointment-任命,election-选举,recommendation-推荐
|
||||
*/
|
||||
appointmentMethod: string;
|
||||
|
||||
/**
|
||||
* 离任日期
|
||||
*/
|
||||
dismissalDate: string;
|
||||
|
||||
/**
|
||||
* 离任原因
|
||||
*/
|
||||
dismissalReason: string;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
}
|
||||
|
||||
export interface XgClassPosMsgForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 班级ID,关联xg_class_msg.id
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称(显示用,非数据库字段)
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 专业ID,关联sys_dept.dept_id
|
||||
*/
|
||||
majorId?: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称(显示用,非数据库字段)
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 学院ID,关联sys_dept.dept_id
|
||||
*/
|
||||
collegeId?: string | number;
|
||||
|
||||
/**
|
||||
* 学院名称(显示用,非数据库字段)
|
||||
*/
|
||||
collegeName?: string;
|
||||
|
||||
/**
|
||||
* 学生ID,关联xg_student_msg.id
|
||||
*/
|
||||
studentId?: string | number;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
studentNo?: string;
|
||||
|
||||
/**
|
||||
* 学生姓名(冗余字段)
|
||||
*/
|
||||
studentName?: string;
|
||||
|
||||
/**
|
||||
* 职务编码id,关联xg_class_pos_dict.id
|
||||
*/
|
||||
positionId?: string | number;
|
||||
|
||||
/**
|
||||
* 职务编码,关联xg_class_pos_dict.position_code
|
||||
*/
|
||||
positionCode?: string;
|
||||
|
||||
/**
|
||||
* 职务名称(显示用,非数据库字段)
|
||||
*/
|
||||
positionName?: string;
|
||||
|
||||
/**
|
||||
* 任职日期
|
||||
*/
|
||||
appointmentDate?: string;
|
||||
|
||||
/**
|
||||
* 任期开始日期
|
||||
*/
|
||||
termStartDate?: string;
|
||||
|
||||
/**
|
||||
* 任期结束日期
|
||||
*/
|
||||
termEndDate?: string;
|
||||
|
||||
/**
|
||||
* 任期时长(月)
|
||||
*/
|
||||
termDuration?: number;
|
||||
|
||||
/**
|
||||
* 是否现任:0-否,1-是
|
||||
*/
|
||||
isCurrent?: number;
|
||||
|
||||
/**
|
||||
* 学生职务任职状态:1-在任,2-到期,3-辞职,4-免职,5-调任,6-休学
|
||||
*/
|
||||
cadreStatus?: string;
|
||||
|
||||
/**
|
||||
* 任命原因
|
||||
*/
|
||||
appointmentReason?: string;
|
||||
|
||||
/**
|
||||
* 学生职务任命方式:appointment-任命,election-选举,recommendation-推荐
|
||||
*/
|
||||
appointmentMethod?: string;
|
||||
|
||||
/**
|
||||
* 离任日期
|
||||
*/
|
||||
dismissalDate?: string;
|
||||
|
||||
/**
|
||||
* 离任原因
|
||||
*/
|
||||
dismissalReason?: string;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface XgClassPosMsgQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 班级ID,关联xg_class_msg.id
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 学生ID,关联xg_student_msg.id
|
||||
*/
|
||||
studentId?: string | number;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
studentNo?: string;
|
||||
|
||||
/**
|
||||
* 学生姓名(冗余字段)
|
||||
*/
|
||||
studentName?: string;
|
||||
|
||||
/**
|
||||
* 职务编码,关联xg_class_pos_dict.position_code
|
||||
*/
|
||||
positionCode?: string;
|
||||
|
||||
/**
|
||||
* 任职日期
|
||||
*/
|
||||
appointmentDate?: string;
|
||||
|
||||
/**
|
||||
* 任期开始日期
|
||||
*/
|
||||
termStartDate?: string;
|
||||
|
||||
/**
|
||||
* 任期结束日期
|
||||
*/
|
||||
termEndDate?: string;
|
||||
|
||||
/**
|
||||
* 任期时长(月)
|
||||
*/
|
||||
termDuration?: number;
|
||||
|
||||
/**
|
||||
* 是否现任:0-否,1-是
|
||||
*/
|
||||
isCurrent?: number;
|
||||
|
||||
/**
|
||||
* 学生职务任职状态:1-在任,2-到期,3-辞职,4-免职,5-调任,6-休学
|
||||
*/
|
||||
cadreStatus?: string;
|
||||
|
||||
/**
|
||||
* 任命原因
|
||||
*/
|
||||
appointmentReason?: string;
|
||||
|
||||
/**
|
||||
* 学生职务任命方式:appointment-任命,election-选举,recommendation-推荐
|
||||
*/
|
||||
appointmentMethod?: string;
|
||||
|
||||
/**
|
||||
* 离任日期
|
||||
*/
|
||||
dismissalDate?: string;
|
||||
|
||||
/**
|
||||
* 离任原因
|
||||
*/
|
||||
dismissalReason?: string;
|
||||
|
||||
/**
|
||||
* 部门ID,用于按部门查询班级干部信息
|
||||
*/
|
||||
deptId?: string | number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StudentManagerVO, StudentManagerForm, StudentManagerQuery } from '@/api/xg/xsjcxx/xgStudentManager/studentManager/types';
|
||||
|
||||
// 临时定义一个接口来表示返回的教师数据
|
||||
export interface TeacherData {
|
||||
id: string | number;
|
||||
managerName: string;
|
||||
phone: string;
|
||||
gender: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的教职工信息列表
|
||||
* @param managerType 管理员类型(1 全校管理员 2 自定义管理员 3 班主任)
|
||||
*/
|
||||
export const listAvailableTeachers = (managerType: string): AxiosPromise<TeacherData[]> => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager/listAvailableTeachers',
|
||||
method: 'get',
|
||||
params: { managerType }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询宿舍管理员信息列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStudentManager = (query?: StudentManagerQuery): AxiosPromise<StudentManagerVO[]> => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询宿舍管理员信息详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStudentManager = (id: string | number): AxiosPromise<StudentManagerVO> => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增宿舍管理员信息
|
||||
* @param data
|
||||
*/
|
||||
export const addStudentManager = (data: StudentManagerForm) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改宿舍管理员信息
|
||||
* @param data
|
||||
*/
|
||||
export const updateStudentManager = (data: StudentManagerForm) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除宿舍管理员信息
|
||||
* @param id
|
||||
*/
|
||||
export const delStudentManager = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前正在教学的班主任信息
|
||||
* @param params 包含 phone 和 name 字段
|
||||
*/
|
||||
export const listActiveClassTeachers = (params: { phone?: string; name?: string }) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager/listActiveClassTeachers',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
export interface StudentManagerVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 管理员姓名
|
||||
*/
|
||||
managerName: string;
|
||||
|
||||
/**
|
||||
* 管理员类型(1 全校管理员 2 自定义管理员 3 班主任)
|
||||
*/
|
||||
managerType: string;
|
||||
|
||||
/**
|
||||
* 管理级别(1-3,1为最高)
|
||||
*/
|
||||
managerLevel: number;
|
||||
|
||||
/**
|
||||
* 关联用户ID
|
||||
*/
|
||||
userId: string | number;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phone: string;
|
||||
|
||||
/**
|
||||
* 性别(0男 1女 2未知)
|
||||
*/
|
||||
gender: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StudentManagerForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 管理员姓名
|
||||
*/
|
||||
managerName?: string;
|
||||
|
||||
/**
|
||||
* 管理员类型(1 全校管理员 2 自定义管理员 3 班主任)
|
||||
*/
|
||||
managerType?: string;
|
||||
|
||||
/**
|
||||
* 管理级别(1-3,1为最高)
|
||||
*/
|
||||
managerLevel?: number;
|
||||
|
||||
/**
|
||||
* 关联用户ID
|
||||
*/
|
||||
userId?: string | number;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phone?: string;
|
||||
|
||||
/**
|
||||
* 性别(0男 1女 2未知)
|
||||
*/
|
||||
gender?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StudentManagerQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 管理员姓名
|
||||
*/
|
||||
managerName?: string;
|
||||
|
||||
/**
|
||||
* 管理员类型(1 全校管理员 2 自定义管理员 3 班主任)
|
||||
*/
|
||||
managerType?: string;
|
||||
|
||||
/**
|
||||
* 管理级别(1-3,1为最高)
|
||||
*/
|
||||
managerLevel?: number;
|
||||
|
||||
/**
|
||||
* 关联用户ID
|
||||
*/
|
||||
userId?: string | number;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phone?: string;
|
||||
|
||||
/**
|
||||
* 性别(0男 1女 2未知)
|
||||
*/
|
||||
gender?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StudentManagerSourceVO, StudentManagerSourceForm, StudentManagerSourceQuery } from '@/api/xg/xsjcxx/xgStudentManagerSource/studentManagerSource/types';
|
||||
|
||||
/**
|
||||
* 查询学生管理员授权关系列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStudentManagerSource = (query?: StudentManagerSourceQuery): AxiosPromise<StudentManagerSourceVO[]> => {
|
||||
return request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生管理员授权关系详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStudentManagerSource = (id: string | number): AxiosPromise<StudentManagerSourceVO> => {
|
||||
return request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生管理员授权关系
|
||||
* @param data
|
||||
*/
|
||||
export const addStudentManagerSource = (data: StudentManagerSourceForm) => {
|
||||
return request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生管理员授权关系
|
||||
* @param data
|
||||
*/
|
||||
export const updateStudentManagerSource = (data: StudentManagerSourceForm) => {
|
||||
return request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生管理员授权关系
|
||||
* @param id
|
||||
*/
|
||||
export const delStudentManagerSource = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
export interface StudentManagerSourceVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
managerId: string | number;
|
||||
|
||||
/**
|
||||
* 权限类型(1查看 2编辑 3完全管理)
|
||||
*/
|
||||
permissionType: string;
|
||||
|
||||
/**
|
||||
* 授权类型(1全校级 2自定义 3班主任)
|
||||
*/
|
||||
authType: string;
|
||||
|
||||
/**
|
||||
* 授权来源ID(对应班级id)
|
||||
*/
|
||||
authSource: number;
|
||||
|
||||
/**
|
||||
* 状态(0有效 1已撤销)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StudentManagerSourceForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
managerId?: string | number;
|
||||
|
||||
/**
|
||||
* 权限类型(1查看 2编辑 3完全管理)
|
||||
*/
|
||||
permissionType?: string;
|
||||
|
||||
/**
|
||||
* 授权类型(1全校级 2自定义 3班主任)
|
||||
*/
|
||||
authType?: string;
|
||||
|
||||
/**
|
||||
* 授权来源ID(对应班级id)
|
||||
*/
|
||||
authSource?: number;
|
||||
|
||||
/**
|
||||
* 状态(0有效 1已撤销)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StudentManagerSourceQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
managerId?: string | number;
|
||||
|
||||
/**
|
||||
* 权限类型(1查看 2编辑 3完全管理)
|
||||
*/
|
||||
permissionType?: string;
|
||||
|
||||
/**
|
||||
* 授权类型(1全校级 2自定义 3班主任)
|
||||
*/
|
||||
authType?: string;
|
||||
|
||||
/**
|
||||
* 授权来源ID(对应班级id)
|
||||
*/
|
||||
authSource?: number;
|
||||
|
||||
/**
|
||||
* 状态(0有效 1已撤销)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ export interface XljkEvaluationDetailVO {
|
||||
studentName: string;
|
||||
|
||||
/**
|
||||
* 性别(M-男,F-女)
|
||||
* 性别(0-男,1-女)
|
||||
*/
|
||||
gender: string;
|
||||
|
||||
@@ -328,7 +328,7 @@ export interface XljkEvaluationDetailForm extends BaseEntity {
|
||||
studentName?: string;
|
||||
|
||||
/**
|
||||
* 性别(M-男,F-女)
|
||||
* 性别(0-男,1-女)
|
||||
*/
|
||||
gender?: string;
|
||||
|
||||
@@ -407,7 +407,7 @@ export interface StudentInfo {
|
||||
studentName: string;
|
||||
|
||||
/**
|
||||
* 性别(M-男,F-女)
|
||||
* 性别(0-男,1-女)
|
||||
*/
|
||||
gender: string;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface EnrollmentInfoVO {
|
||||
idCard: string;
|
||||
|
||||
/**
|
||||
* 性别(M-男 F-女)
|
||||
* 性别(0-男 1-女)
|
||||
*/
|
||||
gender: string;
|
||||
|
||||
@@ -197,7 +197,7 @@ export interface EnrollmentInfoForm extends BaseEntity {
|
||||
idCard?: string;
|
||||
|
||||
/**
|
||||
* 性别(M-男 F-女)
|
||||
* 性别(0-男 1-女)
|
||||
*/
|
||||
gender?: string;
|
||||
|
||||
@@ -354,7 +354,7 @@ export interface EnrollmentInfoQuery extends PageQuery {
|
||||
idCard?: string;
|
||||
|
||||
/**
|
||||
* 性别(M-男 F-女)
|
||||
* 性别(0-男 1-女)
|
||||
*/
|
||||
gender?: string;
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ export interface EnrollmentProcessVO {
|
||||
updateTime: string
|
||||
|
||||
/**
|
||||
* 性别(M-男 F-女)
|
||||
* 性别(0-男 1-女)
|
||||
*/
|
||||
gender?: string
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 企业信息管理 API
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-25
|
||||
*/
|
||||
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { EnterpriseVO, EnterpriseForm, EnterpriseQuery } from './types';
|
||||
|
||||
// TableDataInfo 接口定义
|
||||
export interface TableDataInfo<T> {
|
||||
total: number;
|
||||
rows: T[];
|
||||
code?: number;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询企业信息列表
|
||||
* @param query 查询参数
|
||||
* @returns Promise<TableDataInfo<EnterpriseVO>>
|
||||
*/
|
||||
export function listEnterprise(query?: EnterpriseQuery): AxiosPromise<TableDataInfo<EnterpriseVO>> {
|
||||
return request({
|
||||
url: '/zsjy/enterprise/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询企业信息详细
|
||||
* @param id 企业主键
|
||||
* @returns Promise<EnterpriseVO>
|
||||
*/
|
||||
export function getEnterprise(id: string | number): AxiosPromise<EnterpriseVO> {
|
||||
return request({
|
||||
url: `/zsjy/enterprise/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增企业信息
|
||||
* @param data 企业信息
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
export function addEnterprise(data: EnterpriseForm): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/zsjy/enterprise',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改企业信息
|
||||
* @param data 企业信息
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
export function updateEnterprise(data: EnterpriseForm): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/zsjy/enterprise',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除企业信息
|
||||
* @param id 企业主键
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
export function delEnterprise(id: string | number | Array<string | number>): AxiosPromise<any> {
|
||||
return request({
|
||||
url: `/zsjy/enterprise/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出企业信息
|
||||
* @param query 查询参数
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
export function exportEnterprise(query: EnterpriseQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/zsjy/enterprise/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import {
|
||||
GraduateInfoVO,
|
||||
GraduateInfoForm,
|
||||
GraduateInfoQuery,
|
||||
GraduateStatisticsVO
|
||||
} from '@/api/zsjy/types';
|
||||
|
||||
/**
|
||||
* 查询毕业生信息列表
|
||||
* @param query 查询参数
|
||||
*/
|
||||
export const listGraduateInfo = (query?: GraduateInfoQuery): AxiosPromise<GraduateInfoVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询毕业生信息详细信息
|
||||
* @param id 毕业生信息ID
|
||||
*/
|
||||
export const getGraduateInfo = (id: string | number): AxiosPromise<GraduateInfoVO> => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增毕业生信息
|
||||
* @param data 毕业生信息数据
|
||||
*/
|
||||
export const addGraduateInfo = (data: GraduateInfoForm) => {
|
||||
return request({
|
||||
url: '/zsjy/graduate',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改毕业生信息
|
||||
* @param data 毕业生信息数据
|
||||
*/
|
||||
export const updateGraduateInfo = (data: GraduateInfoForm) => {
|
||||
return request({
|
||||
url: '/zsjy/graduate',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除毕业生信息
|
||||
* @param id 毕业生信息ID
|
||||
*/
|
||||
export const delGraduateInfo = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出毕业生信息列表
|
||||
* @param query 查询参数
|
||||
*/
|
||||
export const exportGraduateInfo = (query: GraduateInfoQuery) => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据学号查询毕业生信息
|
||||
* @param studentNo 学号
|
||||
*/
|
||||
export const getGraduateInfoByStudentNo = (studentNo: string): AxiosPromise<GraduateInfoVO> => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/student/${studentNo}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据身份证号查询毕业生信息
|
||||
* @param idCard 身份证号
|
||||
*/
|
||||
export const getGraduateInfoByIdCard = (idCard: string): AxiosPromise<GraduateInfoVO> => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/idcard/${idCard}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 校验学号是否唯一
|
||||
* @param studentNo 学号
|
||||
* @param id 主键ID(修改时传入)
|
||||
*/
|
||||
export const checkStudentNoUnique = (studentNo: string, id?: string | number): AxiosPromise<boolean> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/checkStudentNo',
|
||||
method: 'get',
|
||||
params: { studentNo, id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 校验身份证号是否唯一
|
||||
* @param idCard 身份证号
|
||||
* @param id 主键ID(修改时传入)
|
||||
*/
|
||||
export const checkIdCardUnique = (idCard: string, id?: string | number): AxiosPromise<boolean> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/checkIdCard',
|
||||
method: 'get',
|
||||
params: { idCard, id }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核毕业生信息
|
||||
* @param id 主键
|
||||
* @param auditStatus 审核状态
|
||||
* @param auditRemark 审核意见
|
||||
*/
|
||||
export const auditGraduateInfo = (id: string | number, auditStatus: string, auditRemark?: string) => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/audit/${id}`,
|
||||
method: 'put',
|
||||
params: { auditStatus, auditRemark }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量审核毕业生信息
|
||||
* @param ids 主键数组
|
||||
* @param auditStatus 审核状态
|
||||
* @param auditRemark 审核意见
|
||||
*/
|
||||
export const batchAuditGraduateInfo = (ids: (string | number)[], auditStatus: string, auditRemark?: string) => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/batchAudit',
|
||||
method: 'put',
|
||||
data: ids,
|
||||
params: { auditStatus, auditRemark }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 统计总体就业情况
|
||||
*/
|
||||
export const getOverallStatistics = (): AxiosPromise<GraduateStatisticsVO> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/overall',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 按专业统计就业情况
|
||||
* @param graduationYear 毕业年份
|
||||
*/
|
||||
export const getStatisticsByMajor = (graduationYear?: number): AxiosPromise<GraduateStatisticsVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/major',
|
||||
method: 'get',
|
||||
params: { graduationYear }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 按学院统计就业情况
|
||||
* @param graduationYear 毕业年份
|
||||
*/
|
||||
export const getStatisticsByCollege = (graduationYear?: number): AxiosPromise<GraduateStatisticsVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/college',
|
||||
method: 'get',
|
||||
params: { graduationYear }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 按就业状态统计
|
||||
* @param graduationYear 毕业年份
|
||||
*/
|
||||
export const getStatisticsByEmploymentStatus = (graduationYear?: number): AxiosPromise<GraduateStatisticsVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/employment',
|
||||
method: 'get',
|
||||
params: { graduationYear }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 按单位性质统计
|
||||
* @param graduationYear 毕业年份
|
||||
*/
|
||||
export const getStatisticsByCompanyType = (graduationYear?: number): AxiosPromise<GraduateStatisticsVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/company',
|
||||
method: 'get',
|
||||
params: { graduationYear }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 按地域分布统计
|
||||
* @param graduationYear 毕业年份
|
||||
*/
|
||||
export const getStatisticsByRegion = (graduationYear?: number): AxiosPromise<GraduateStatisticsVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/region',
|
||||
method: 'get',
|
||||
params: { graduationYear }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 按薪资范围统计
|
||||
* @param graduationYear 毕业年份
|
||||
*/
|
||||
export const getStatisticsBySalaryRange = (graduationYear?: number): AxiosPromise<GraduateStatisticsVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/salary',
|
||||
method: 'get',
|
||||
params: { graduationYear }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 按年份统计历年就业情况
|
||||
*/
|
||||
export const getStatisticsByYear = (): AxiosPromise<GraduateStatisticsVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/statistics/year',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询待审核的毕业生信息数量
|
||||
*/
|
||||
export const getPendingAuditCount = (): AxiosPromise<number> => {
|
||||
return request({
|
||||
url: '/zsjy/graduate/pendingCount',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询指定学院的毕业生信息列表
|
||||
* @param collegeId 学院ID
|
||||
* @param query 查询条件
|
||||
*/
|
||||
export const getListByCollegeId = (collegeId: string | number, query?: GraduateInfoQuery): AxiosPromise<GraduateInfoVO[]> => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/college/${collegeId}`,
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询指定专业的毕业生信息列表
|
||||
* @param majorId 专业ID
|
||||
* @param query 查询条件
|
||||
*/
|
||||
export const getListByMajorId = (majorId: string | number, query?: GraduateInfoQuery): AxiosPromise<GraduateInfoVO[]> => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/major/${majorId}`,
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询指定班级的毕业生信息列表
|
||||
* @param classId 班级ID
|
||||
* @param query 查询条件
|
||||
*/
|
||||
export const getListByClassId = (classId: string | number, query?: GraduateInfoQuery): AxiosPromise<GraduateInfoVO[]> => {
|
||||
return request({
|
||||
url: `/zsjy/graduate/class/${classId}`,
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
@@ -6,13 +6,15 @@
|
||||
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 此处导出招生就业管理模块相关的所有API接口
|
||||
// 示例:
|
||||
// export * from './enrollment' // 招生管理
|
||||
// export * from './employment' // 就业管理
|
||||
// export * from './enterprise' // 企业管理
|
||||
// export * from './career' // 职业规划
|
||||
// export * from './statistics' // 统计分析
|
||||
// 导出招生就业管理模块相关的所有API接口
|
||||
export * from './enterprise'; // 企业信息管理
|
||||
// export * from './jobPosition'; // 招聘岗位管理 (待开发)
|
||||
// export * from './recruitmentPoster'; // 招聘海报管理 (待开发)
|
||||
|
||||
// 临时导出一个空对象,防止编译错误
|
||||
export default {};
|
||||
// 现有导出
|
||||
export * from './statistics'; // 统计分析
|
||||
export * from './enrollmentProcess'; // 录取流程管理
|
||||
export * from './enrollmentStatistics'; // 报名统计
|
||||
|
||||
// 类型导出
|
||||
export * from './types'; // 类型定义
|
||||
@@ -0,0 +1,91 @@
|
||||
import request from '@/utils/request'
|
||||
import { JobPositionVO, JobPositionForm, JobPositionQuery, TableDataInfo } from './types'
|
||||
import { AxiosPromise } from 'axios'
|
||||
|
||||
/**
|
||||
* 查询招聘岗位列表
|
||||
* @param query 查询参数
|
||||
* @returns 招聘岗位列表
|
||||
*/
|
||||
export function listJobPosition(query?: JobPositionQuery): AxiosPromise<TableDataInfo<JobPositionVO>> {
|
||||
return request({
|
||||
url: '/zsjy/position/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询招聘岗位详细
|
||||
* @param id 招聘岗位ID
|
||||
* @returns 招聘岗位详情
|
||||
*/
|
||||
export function getJobPosition(id: string | number): AxiosPromise<JobPositionVO> {
|
||||
return request({
|
||||
url: '/zsjy/position/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增招聘岗位
|
||||
* @param data 招聘岗位表单
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function addJobPosition(data: JobPositionForm) {
|
||||
return request({
|
||||
url: '/zsjy/position',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改招聘岗位
|
||||
* @param data 招聘岗位表单
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function updateJobPosition(data: JobPositionForm) {
|
||||
return request({
|
||||
url: '/zsjy/position',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除招聘岗位
|
||||
* @param id 招聘岗位ID
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function delJobPosition(id: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: '/zsjy/position/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出招聘岗位
|
||||
* @param query 查询参数
|
||||
* @returns 导出结果
|
||||
*/
|
||||
export function exportJobPosition(query: JobPositionQuery) {
|
||||
return request({
|
||||
url: '/zsjy/position/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询企业下拉选项
|
||||
* @returns 企业列表
|
||||
*/
|
||||
export function listEnterpriseOptions() {
|
||||
return request({
|
||||
url: '/zsjy/enterprise/options',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import request from '@/utils/request'
|
||||
import { RecruitmentPosterVO, RecruitmentPosterForm, RecruitmentPosterQuery, TableDataInfo } from './types'
|
||||
import { AxiosPromise } from 'axios'
|
||||
|
||||
/**
|
||||
* 查询招聘海报列表
|
||||
* @param query 查询参数
|
||||
* @returns 招聘海报列表
|
||||
*/
|
||||
export function listRecruitmentPoster(query?: RecruitmentPosterQuery): AxiosPromise<TableDataInfo<RecruitmentPosterVO>> {
|
||||
return request({
|
||||
url: '/zsjy/poster/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询招聘海报详细
|
||||
* @param id 招聘海报ID
|
||||
* @returns 招聘海报详情
|
||||
*/
|
||||
export function getRecruitmentPoster(id: string | number): AxiosPromise<RecruitmentPosterVO> {
|
||||
return request({
|
||||
url: '/zsjy/poster/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增招聘海报
|
||||
* @param data 招聘海报表单
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function addRecruitmentPoster(data: RecruitmentPosterForm) {
|
||||
return request({
|
||||
url: '/zsjy/poster',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改招聘海报
|
||||
* @param data 招聘海报表单
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function updateRecruitmentPoster(data: RecruitmentPosterForm) {
|
||||
return request({
|
||||
url: '/zsjy/poster',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除招聘海报
|
||||
* @param id 招聘海报ID
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function delRecruitmentPoster(id: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: '/zsjy/poster/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出招聘海报
|
||||
* @param query 查询参数
|
||||
* @returns 导出结果
|
||||
*/
|
||||
export function exportRecruitmentPoster(query: RecruitmentPosterQuery) {
|
||||
return request({
|
||||
url: '/zsjy/poster/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询岗位下拉选项
|
||||
* @param enterpriseId 企业ID
|
||||
* @returns 岗位列表
|
||||
*/
|
||||
export function listPositionOptions(enterpriseId?: number | string) {
|
||||
return request({
|
||||
url: '/zsjy/position/options',
|
||||
method: 'get',
|
||||
params: { enterpriseId }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,743 @@
|
||||
/**
|
||||
* 招生就业管理模块 - 类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 企业信息 VO
|
||||
*/
|
||||
export interface EnterpriseVO {
|
||||
/**
|
||||
* 企业ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 企业名称
|
||||
*/
|
||||
enterpriseName?: string;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
*/
|
||||
industry?: string;
|
||||
|
||||
/**
|
||||
* 行业名称
|
||||
*/
|
||||
industryName?: string;
|
||||
|
||||
/**
|
||||
* 主营业务
|
||||
*/
|
||||
mainBusiness?: string;
|
||||
|
||||
/**
|
||||
* 企业规模
|
||||
*/
|
||||
enterpriseScale?: string;
|
||||
|
||||
/**
|
||||
* 企业规模名称
|
||||
*/
|
||||
enterpriseScaleName?: string;
|
||||
|
||||
/**
|
||||
* 企业联系人姓名
|
||||
*/
|
||||
contactName?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
contactPhone?: string;
|
||||
|
||||
/**
|
||||
* 企业邮箱
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* 企业地址
|
||||
*/
|
||||
address?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
tenantId?: string;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 企业信息表单对象
|
||||
*/
|
||||
export interface EnterpriseForm extends BaseEntity {
|
||||
/**
|
||||
* 企业ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 企业名称
|
||||
*/
|
||||
enterpriseName: string;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
*/
|
||||
industry: string;
|
||||
|
||||
/**
|
||||
* 主营业务
|
||||
*/
|
||||
mainBusiness: string;
|
||||
|
||||
/**
|
||||
* 企业规模
|
||||
*/
|
||||
enterpriseScale: string;
|
||||
|
||||
/**
|
||||
* 企业联系人姓名
|
||||
*/
|
||||
contactName: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
contactPhone: string;
|
||||
|
||||
/**
|
||||
* 企业邮箱
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* 企业地址
|
||||
*/
|
||||
address: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 企业信息查询条件
|
||||
*/
|
||||
export interface EnterpriseQuery extends PageQuery {
|
||||
/**
|
||||
* 企业名称
|
||||
*/
|
||||
enterpriseName?: string;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
*/
|
||||
industry?: string;
|
||||
|
||||
/**
|
||||
* 主营业务
|
||||
*/
|
||||
mainBusiness?: string;
|
||||
|
||||
/**
|
||||
* 企业规模
|
||||
*/
|
||||
enterpriseScale?: string;
|
||||
|
||||
/**
|
||||
* 企业联系人姓名
|
||||
*/
|
||||
contactName?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
contactPhone?: string;
|
||||
|
||||
/**
|
||||
* 企业邮箱
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* 企业地址
|
||||
*/
|
||||
address?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 招聘岗位VO对象
|
||||
*/
|
||||
export interface JobPositionVO {
|
||||
/**
|
||||
* 岗位ID
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* 所属企业ID
|
||||
*/
|
||||
enterpriseId: number | string;
|
||||
|
||||
/**
|
||||
* 企业名称(关联字段)
|
||||
*/
|
||||
enterpriseName?: string;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
positionName: string;
|
||||
|
||||
/**
|
||||
* 工作地址
|
||||
*/
|
||||
workAddress: string;
|
||||
|
||||
/**
|
||||
* 工作时间
|
||||
*/
|
||||
workTime: string;
|
||||
|
||||
/**
|
||||
* 薪资
|
||||
*/
|
||||
salary: string;
|
||||
|
||||
/**
|
||||
* 所需技能
|
||||
*/
|
||||
requiredSkills?: string;
|
||||
|
||||
/**
|
||||
* 有效期开始日期
|
||||
*/
|
||||
startDate: string;
|
||||
|
||||
/**
|
||||
* 有效期结束日期
|
||||
*/
|
||||
endDate: string;
|
||||
|
||||
/**
|
||||
* 是否永久有效(0否 1是)
|
||||
*/
|
||||
isPermanent: number;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
tenantId?: string;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 招聘岗位Form对象
|
||||
*/
|
||||
export interface JobPositionForm extends BaseEntity {
|
||||
/**
|
||||
* 岗位ID
|
||||
*/
|
||||
id?: number | string;
|
||||
|
||||
/**
|
||||
* 所属企业ID
|
||||
*/
|
||||
enterpriseId: number | string;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
positionName: string;
|
||||
|
||||
/**
|
||||
* 工作地址
|
||||
*/
|
||||
workAddress: string;
|
||||
|
||||
/**
|
||||
* 工作时间
|
||||
*/
|
||||
workTime: string;
|
||||
|
||||
/**
|
||||
* 薪资
|
||||
*/
|
||||
salary: string;
|
||||
|
||||
/**
|
||||
* 所需技能
|
||||
*/
|
||||
requiredSkills?: string;
|
||||
|
||||
/**
|
||||
* 有效期开始日期
|
||||
*/
|
||||
startDate: string;
|
||||
|
||||
/**
|
||||
* 有效期结束日期
|
||||
*/
|
||||
endDate: string;
|
||||
|
||||
/**
|
||||
* 是否永久有效(0否 1是)
|
||||
*/
|
||||
isPermanent: number;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 招聘岗位Query对象
|
||||
*/
|
||||
export interface JobPositionQuery extends PageQuery {
|
||||
/**
|
||||
* 所属企业ID
|
||||
*/
|
||||
enterpriseId?: number | string;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
positionName?: string;
|
||||
|
||||
/**
|
||||
* 工作地址
|
||||
*/
|
||||
workAddress?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 是否永久有效
|
||||
*/
|
||||
isPermanent?: number;
|
||||
|
||||
/**
|
||||
* 创建时间范围
|
||||
*/
|
||||
params?: {
|
||||
beginCreateTime?: string;
|
||||
endCreateTime?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 招聘海报VO对象
|
||||
*/
|
||||
export interface RecruitmentPosterVO {
|
||||
/**
|
||||
* 海报ID
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* 企业ID
|
||||
*/
|
||||
enterpriseId: number | string;
|
||||
|
||||
/**
|
||||
* 企业名称(关联字段)
|
||||
*/
|
||||
enterpriseName?: string;
|
||||
|
||||
/**
|
||||
* 岗位ID(为空表示企业整体海报)
|
||||
*/
|
||||
positionId?: number | string;
|
||||
|
||||
/**
|
||||
* 岗位名称(关联字段)
|
||||
*/
|
||||
positionName?: string;
|
||||
|
||||
/**
|
||||
* 海报文件路径
|
||||
*/
|
||||
posterPath: string;
|
||||
|
||||
/**
|
||||
* 海报类型(1企业海报 2岗位海报)
|
||||
*/
|
||||
posterType: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
tenantId?: string;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 招聘海报Form对象
|
||||
*/
|
||||
export interface RecruitmentPosterForm extends BaseEntity {
|
||||
/**
|
||||
* 海报ID
|
||||
*/
|
||||
id?: number | string;
|
||||
|
||||
/**
|
||||
* 企业ID
|
||||
*/
|
||||
enterpriseId: number | string;
|
||||
|
||||
/**
|
||||
* 岗位ID(为空表示企业整体海报)
|
||||
*/
|
||||
positionId?: number | string;
|
||||
|
||||
/**
|
||||
* 海报文件路径
|
||||
*/
|
||||
posterPath: string;
|
||||
|
||||
/**
|
||||
* 海报类型(1企业海报 2岗位海报)
|
||||
*/
|
||||
posterType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 招聘海报Query对象
|
||||
*/
|
||||
export interface RecruitmentPosterQuery extends PageQuery {
|
||||
/**
|
||||
* 企业ID
|
||||
*/
|
||||
enterpriseId?: number | string;
|
||||
|
||||
/**
|
||||
* 岗位ID
|
||||
*/
|
||||
positionId?: number | string;
|
||||
|
||||
/**
|
||||
* 海报类型(1企业海报 2岗位海报)
|
||||
*/
|
||||
posterType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页响应数据
|
||||
*/
|
||||
export interface TableDataInfo<T> {
|
||||
total: number;
|
||||
rows: T[];
|
||||
code?: number;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 毕业生信息VO
|
||||
*/
|
||||
export interface GraduateInfoVO {
|
||||
id?: string | number;
|
||||
studentNo?: string;
|
||||
name?: string;
|
||||
gender?: string;
|
||||
genderName?: string;
|
||||
idCard?: string;
|
||||
nation?: string;
|
||||
politicalStatus?: string;
|
||||
birthDate?: string;
|
||||
enrollmentYear?: number;
|
||||
graduationYear?: number;
|
||||
collegeId?: string | number;
|
||||
collegeName?: string;
|
||||
majorId?: string | number;
|
||||
majorName?: string;
|
||||
classId?: string | number;
|
||||
className?: string;
|
||||
educationLevel?: string;
|
||||
educationLevelName?: string;
|
||||
trainingMode?: string;
|
||||
phoneNumber?: string;
|
||||
email?: string;
|
||||
qqNumber?: string;
|
||||
wechatNumber?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
detailAddress?: string;
|
||||
fullAddress?: string;
|
||||
postalCode?: string;
|
||||
employmentStatus?: string;
|
||||
employmentStatusName?: string;
|
||||
employmentCompany?: string;
|
||||
companyType?: string;
|
||||
companyTypeName?: string;
|
||||
jobPosition?: string;
|
||||
workProvince?: string;
|
||||
workCity?: string;
|
||||
workLocation?: string;
|
||||
salaryRange?: string;
|
||||
salaryRangeName?: string;
|
||||
furtherSchool?: string;
|
||||
furtherMajor?: string;
|
||||
skills?: string;
|
||||
awards?: string;
|
||||
jobIntention?: string;
|
||||
auditStatus?: string;
|
||||
auditStatusName?: string;
|
||||
auditTime?: string;
|
||||
auditBy?: string | number;
|
||||
auditByName?: string;
|
||||
auditRemark?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
createBy?: string | number;
|
||||
createTime?: string;
|
||||
updateBy?: string | number;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 毕业生信息表单
|
||||
*/
|
||||
export interface GraduateInfoForm {
|
||||
id?: string | number;
|
||||
studentNo?: string;
|
||||
name?: string;
|
||||
gender?: string;
|
||||
idCard?: string;
|
||||
nation?: string;
|
||||
politicalStatus?: string;
|
||||
birthDate?: string;
|
||||
enrollmentYear?: number;
|
||||
graduationYear?: number;
|
||||
collegeId?: string | number;
|
||||
collegeName?: string;
|
||||
majorId?: string | number;
|
||||
majorName?: string;
|
||||
classId?: string | number;
|
||||
className?: string;
|
||||
educationLevel?: string;
|
||||
trainingMode?: string;
|
||||
phoneNumber?: string;
|
||||
email?: string;
|
||||
qqNumber?: string;
|
||||
wechatNumber?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
detailAddress?: string;
|
||||
postalCode?: string;
|
||||
employmentStatus?: string;
|
||||
employmentCompany?: string;
|
||||
companyType?: string;
|
||||
jobPosition?: string;
|
||||
workProvince?: string;
|
||||
workCity?: string;
|
||||
salaryRange?: string;
|
||||
furtherSchool?: string;
|
||||
furtherMajor?: string;
|
||||
skills?: string;
|
||||
awards?: string;
|
||||
jobIntention?: string;
|
||||
auditStatus?: string;
|
||||
auditTime?: string;
|
||||
auditBy?: string | number;
|
||||
auditRemark?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 毕业生信息查询
|
||||
*/
|
||||
export interface GraduateInfoQuery extends PageQuery {
|
||||
studentNo?: string;
|
||||
name?: string;
|
||||
gender?: string;
|
||||
idCard?: string;
|
||||
nation?: string;
|
||||
politicalStatus?: string;
|
||||
birthDate?: string;
|
||||
enrollmentYear?: number;
|
||||
graduationYear?: number;
|
||||
collegeId?: string | number;
|
||||
collegeName?: string;
|
||||
majorId?: string | number;
|
||||
majorName?: string;
|
||||
classId?: string | number;
|
||||
className?: string;
|
||||
educationLevel?: string;
|
||||
trainingMode?: string;
|
||||
phoneNumber?: string;
|
||||
email?: string;
|
||||
employmentStatus?: string;
|
||||
employmentCompany?: string;
|
||||
companyType?: string;
|
||||
jobPosition?: string;
|
||||
workProvince?: string;
|
||||
workCity?: string;
|
||||
salaryRange?: string;
|
||||
furtherSchool?: string;
|
||||
furtherMajor?: string;
|
||||
auditStatus?: string;
|
||||
auditBy?: string | number;
|
||||
status?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 毕业生信息统计VO
|
||||
*/
|
||||
export interface GraduateStatisticsVO {
|
||||
name?: string;
|
||||
value?: number;
|
||||
total?: number;
|
||||
percentage?: number;
|
||||
employmentCount?: number;
|
||||
employmentRate?: number;
|
||||
furtherStudyCount?: number;
|
||||
furtherStudyRate?: number;
|
||||
unemploymentCount?: number;
|
||||
type?: string;
|
||||
year?: number;
|
||||
collegeId?: string | number;
|
||||
collegeName?: string;
|
||||
majorId?: string | number;
|
||||
majorName?: string;
|
||||
employmentStatus?: string;
|
||||
employmentStatusName?: string;
|
||||
companyType?: string;
|
||||
companyTypeName?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
salaryRange?: string;
|
||||
salaryRangeName?: string;
|
||||
}
|
||||
@@ -229,3 +229,7 @@ aside {
|
||||
.el-overlay .el-overlay-dialog .el-dialog .el-dialog__header {
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
z-index: 4000 !important;
|
||||
}
|
||||
@@ -33,11 +33,12 @@
|
||||
<!-- 文件列表 -->
|
||||
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
|
||||
<li v-for="(file, index) in fileList" :key="file.uid" class="el-upload-list__item ele-upload-list__item-content">
|
||||
<el-link :href="`${file.url}`" :underline="false" target="_blank">
|
||||
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
|
||||
</el-link>
|
||||
<!-- <el-link :href="`${file.url}`" :underline="false" target="_blank"> -->
|
||||
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
|
||||
<!-- </el-link> -->
|
||||
<div class="ele-upload-list__item-content-action">
|
||||
<el-button type="danger" v-if="!disabled" link @click="handleDelete(index)">删除</el-button>
|
||||
<el-button type="danger" v-if="!disabled" link @click="handleDownloadAttachment(file)">下载</el-button>
|
||||
</div>
|
||||
</li>
|
||||
</transition-group>
|
||||
@@ -48,6 +49,8 @@
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { delOss, listByIds } from '@/api/system/oss';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
import request from '@/utils/request';
|
||||
import { vLoading, ElLoading } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -116,6 +119,64 @@ watch(
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
/** 下载附件 */
|
||||
const handleDownloadAttachment = async (file: any) => {
|
||||
let downloadUrl = file.ossId;
|
||||
let fileName = file.name || 'attachment';
|
||||
|
||||
if (!downloadUrl) {
|
||||
proxy?.$modal.msgError('无法获取文件下载地址');
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于服务器文件,使用fetch获取内容强制下载
|
||||
try {
|
||||
const loading = ElLoading.service({
|
||||
text: '正在下载文件...',
|
||||
target: document.body
|
||||
});
|
||||
|
||||
// const response = await fetch(downloadUrl);
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`HTTP error! status: ${response.status}`);
|
||||
// }
|
||||
// 通过fetch获取文件内容,强制下载
|
||||
// const response = await req('/resource/oss/download/' + attachment.filePath);
|
||||
const response = await request({
|
||||
url: '/resource/oss/download/' + downloadUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
// const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
window.URL.revokeObjectURL(url);
|
||||
loading?.close();
|
||||
console.log('下载服务器文件成功:', fileName);
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
proxy?.$modal.msgError('下载文件失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 上传前校检格式和大小
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
// 校检文件类型
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
<template>
|
||||
<el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="realSrcList" preview-teleported>
|
||||
<el-image
|
||||
:src="realSrc"
|
||||
fit="cover"
|
||||
:style="`width:${realWidth};height:${realHeight};`"
|
||||
:preview-src-list="realSrcList"
|
||||
preview-teleported
|
||||
v-loading="loading"
|
||||
@error="handleImageError"
|
||||
@load="handleImageLoad"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon><picture-filled /></el-icon>
|
||||
<div class="error-text">图片加载失败</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { computed, ref, watch, onBeforeUnmount } from 'vue';
|
||||
import { listByIds } from '@/api/system/oss';
|
||||
import type { OssVO } from '@/api/system/oss/types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const props = defineProps({
|
||||
src: propTypes.string.def(''),
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
@@ -20,31 +36,264 @@ const props = defineProps({
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否为ossid模式
|
||||
isOssId: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const realSrc = computed(() => {
|
||||
if (!props.src) {
|
||||
return;
|
||||
}
|
||||
const real_src = props.src.split(',')[0];
|
||||
return real_src;
|
||||
const loading = ref(false);
|
||||
const urlCache = ref<Map<string, string>>(new Map());
|
||||
const blobUrls = ref<string[]>([]);
|
||||
|
||||
// 图片加载成功事件
|
||||
const handleImageLoad = () => {
|
||||
// 图片加载成功
|
||||
};
|
||||
|
||||
// 图片加载失败事件
|
||||
const handleImageError = (error: any) => {
|
||||
// 图片加载失败处理
|
||||
};
|
||||
|
||||
// 清理blob URL,防止内存泄漏
|
||||
const cleanupBlobUrls = () => {
|
||||
blobUrls.value.forEach((url) => {
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
blobUrls.value = [];
|
||||
urlCache.value.clear();
|
||||
};
|
||||
|
||||
// 组件卸载时清理blob URLs
|
||||
onBeforeUnmount(() => {
|
||||
cleanupBlobUrls();
|
||||
});
|
||||
|
||||
const realSrcList = computed(() => {
|
||||
if (!props.src) {
|
||||
return [];
|
||||
// 验证blob数据是否为有效图片
|
||||
const validateImageBlob = (blob: Blob): boolean => {
|
||||
// 允许的图片类型,包括通用的octet-stream类型(OSS服务器常返回此类型)
|
||||
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'application/octet-stream'];
|
||||
|
||||
// 基本验证:类型检查和大小检查
|
||||
const hasValidType = validTypes.includes(blob.type) || blob.type === '' || blob.type === 'application/octet-stream';
|
||||
const hasValidSize = blob.size > 0;
|
||||
|
||||
return hasValidType && hasValidSize;
|
||||
};
|
||||
|
||||
// 通过认证请求获取图片数据并转换为blob URL
|
||||
const getImageBlobUrl = async (imageUrl: string): Promise<string> => {
|
||||
try {
|
||||
// 检查是否已有缓存的blob URL
|
||||
const cachedUrl = urlCache.value.get(imageUrl);
|
||||
if (cachedUrl) {
|
||||
return cachedUrl;
|
||||
}
|
||||
|
||||
// 通过axios请求图片数据,这样会自动带上token和clientId
|
||||
const response = await request({
|
||||
url: imageUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
if (!(blob instanceof Blob)) {
|
||||
// 如果不是Blob,尝试获取实际内容用于调试
|
||||
let actualContent = 'Unknown';
|
||||
try {
|
||||
if (typeof blob === 'string') {
|
||||
actualContent = (blob as string).substring(0, 200);
|
||||
} else if (blob && typeof blob.toString === 'function') {
|
||||
actualContent = blob.toString().substring(0, 200);
|
||||
}
|
||||
} catch (e) {
|
||||
actualContent = 'Cannot convert to string';
|
||||
}
|
||||
|
||||
throw new Error(`响应数据不是有效的Blob对象。实际类型: ${typeof blob}, 内容: ${actualContent}`);
|
||||
}
|
||||
|
||||
// 检查blob内容类型
|
||||
// const contentType = blob.type;
|
||||
|
||||
// 如果Content-Type明确表明这不是图片数据且不是通用二进制格式,进行检查
|
||||
// if (
|
||||
// contentType &&
|
||||
// !contentType.toLowerCase().startsWith('image/') &&
|
||||
// !contentType.toLowerCase().includes('octet-stream') &&
|
||||
// (contentType.toLowerCase().includes('text/') || contentType.toLowerCase().includes('html'))
|
||||
// ) {
|
||||
// const text = await blob.text();
|
||||
// const preview = text.substring(0, 300);
|
||||
// throw new Error(`响应Content-Type不是图片格式: ${contentType}。响应内容预览: ${preview}`);
|
||||
// }
|
||||
|
||||
// 验证是否为有效图片
|
||||
// if (!validateImageBlob(blob)) {
|
||||
// throw new Error(`无效的图片格式: ${blob.type}, 大小: ${blob.size}`);
|
||||
// }
|
||||
|
||||
// 创建blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 缓存blob URL
|
||||
// urlCache.value.set(imageUrl, blobUrl);
|
||||
// blobUrls.value.push(blobUrl);
|
||||
|
||||
return blobUrl;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
const real_src_list = props.src.split(',');
|
||||
const srcList: string[] = [];
|
||||
real_src_list.forEach((item: string) => {
|
||||
if (item.trim() === '') {
|
||||
};
|
||||
|
||||
// 通过ossid获取真实URL
|
||||
const getUrlByOssId = async (ossId: string): Promise<string> => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// const response = await listByIds(ossId);
|
||||
// // RuoYi框架的响应格式:{code, msg, data},实际数据在data字段中
|
||||
|
||||
// // 正确提取数据,考虑可能的响应格式
|
||||
// const ossList: OssVO[] = (response as any)?.data || response;
|
||||
|
||||
// if (!ossList || ossList.length === 0) {
|
||||
// throw new Error(`OSS ID ${ossId} 在数据库中不存在或已被删除。可能原因:1) OSS ID不正确 2) 文件已被删除 3) 权限不足`);
|
||||
// }
|
||||
|
||||
// const ossFile = ossList[0];
|
||||
|
||||
// // 额外验证ossFile对象的完整性
|
||||
// if (!ossFile || typeof ossFile !== 'object') {
|
||||
// throw new Error(`OSS数据格式异常:期望对象,实际获得 ${typeof ossFile}`);
|
||||
// }
|
||||
|
||||
// if (!ossFile.url) {
|
||||
// throw new Error(`OSS文件缺少url属性。完整数据:${JSON.stringify(ossFile)}`);
|
||||
// }
|
||||
// const imageUrl = ossFile.url;
|
||||
|
||||
// // 检查URL是否有效
|
||||
// if (!imageUrl || typeof imageUrl !== 'string') {
|
||||
// throw new Error(`OSS接口返回的URL无效。URL: ${imageUrl}, 类型: ${typeof imageUrl}`);
|
||||
// }
|
||||
|
||||
// // 如果URL不是以/开头的下载路径,可能是直接的OSS URL
|
||||
// if (!imageUrl.startsWith('/resource/oss/download/')) {
|
||||
// // 对于直接的OSS URL,我们仍然通过认证请求获取
|
||||
// }
|
||||
|
||||
// 获取认证的blob URL
|
||||
return await getImageBlobUrl('/resource/oss/download/' + ossId);
|
||||
} catch (error) {
|
||||
return '';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理多个ossid的情况
|
||||
const getUrlsByOssIds = async (ossIds: string[]): Promise<string[]> => {
|
||||
const urls: string[] = [];
|
||||
|
||||
for (const ossId of ossIds) {
|
||||
if (ossId.trim()) {
|
||||
const url = await getUrlByOssId(ossId.trim());
|
||||
if (url) {
|
||||
urls.push(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urls;
|
||||
};
|
||||
|
||||
// 处理普通URL模式(非ossId)
|
||||
const getUrlsByDirectUrls = async (urls: string[]): Promise<string[]> => {
|
||||
const blobUrls: string[] = [];
|
||||
|
||||
for (const url of urls) {
|
||||
if (url.trim()) {
|
||||
const blobUrl = await getImageBlobUrl(url.trim());
|
||||
if (blobUrl) {
|
||||
blobUrls.push(blobUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blobUrls;
|
||||
};
|
||||
|
||||
const realSrc = ref('');
|
||||
const realSrcList = ref<string[]>([]);
|
||||
|
||||
// 处理src变化
|
||||
watch(
|
||||
() => props.src,
|
||||
async (newSrc) => {
|
||||
// 清理之前的blob URLs
|
||||
cleanupBlobUrls();
|
||||
|
||||
if (!newSrc) {
|
||||
realSrc.value = '';
|
||||
realSrcList.value = [];
|
||||
return;
|
||||
}
|
||||
return srcList.push(item);
|
||||
});
|
||||
return srcList;
|
||||
});
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
if (props.isOssId) {
|
||||
// console.log('ossid模式');
|
||||
// console.log(newSrc);
|
||||
// ossid模式:通过接口获取真实URL,然后转换为认证的blob URL
|
||||
const ossIds = newSrc.split(',').filter((id) => id.trim());
|
||||
|
||||
const urls = await getUrlsByOssIds(ossIds);
|
||||
|
||||
if (urls.length > 0) {
|
||||
realSrc.value = urls[0];
|
||||
realSrcList.value = urls;
|
||||
} else {
|
||||
realSrc.value = '';
|
||||
realSrcList.value = [];
|
||||
}
|
||||
} else {
|
||||
// 普通模式:直接使用src作为URL,但仍需要通过认证请求转换为blob URL
|
||||
const urls = newSrc.split(',').filter((url) => url.trim());
|
||||
|
||||
const blobUrls = await getUrlsByDirectUrls(urls);
|
||||
|
||||
if (blobUrls.length > 0) {
|
||||
realSrc.value = blobUrls[0];
|
||||
realSrcList.value = blobUrls;
|
||||
} else {
|
||||
realSrc.value = '';
|
||||
realSrcList.value = [];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
realSrc.value = '';
|
||||
realSrcList.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const realWidth = computed(() => (typeof props.width == 'string' ? props.width : `${props.width}px`));
|
||||
|
||||
@@ -68,12 +317,19 @@ const realHeight = computed(() => (typeof props.height == 'string' ? props.heigh
|
||||
|
||||
:deep(.image-slot) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #909399;
|
||||
font-size: 30px;
|
||||
|
||||
.error-text {
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<div class="component-upload-image">
|
||||
<el-upload
|
||||
ref="imageUploadRef"
|
||||
multiple
|
||||
:action="uploadImgUrl"
|
||||
list-type="picture-card"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:limit="limit"
|
||||
:accept="fileAccept"
|
||||
:on-error="handleUploadError"
|
||||
:on-exceed="handleExceed"
|
||||
:before-remove="handleDelete"
|
||||
:show-file-list="true"
|
||||
:headers="headers"
|
||||
:file-list="fileList"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:class="{ hide: fileList.length >= limit }"
|
||||
:http-request="customUpload"
|
||||
>
|
||||
<el-icon class="avatar-uploader-icon">
|
||||
<plus />
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
<!-- 上传提示 -->
|
||||
<div v-if="showTip" class="el-upload__tip">
|
||||
请上传
|
||||
<template v-if="fileSize">
|
||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
||||
</template>
|
||||
<template v-if="fileType">
|
||||
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
|
||||
</template>
|
||||
的文件
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
|
||||
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- Bucket密码输入对话框 -->
|
||||
<el-dialog v-model="passwordDialogVisible" title="Bucket访问密码" width="400px" append-to-body>
|
||||
<el-form :model="passwordForm" label-width="100px">
|
||||
<el-form-item label="Bucket密码" required>
|
||||
<el-input
|
||||
v-model="passwordForm.bucketPassword"
|
||||
type="password"
|
||||
placeholder="请输入bucket访问密码"
|
||||
show-password
|
||||
@keyup.enter="confirmPassword"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="passwordDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmPassword">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, getCurrentInstance } from 'vue';
|
||||
import { compressAccurately } from 'image-conversion';
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 定义props类型
|
||||
interface Props {
|
||||
modelValue?: string | any[] | any;
|
||||
limit?: number;
|
||||
fileSize?: number;
|
||||
fileType?: string[];
|
||||
isShowTip?: boolean;
|
||||
compressSupport?: boolean;
|
||||
compressTargetSize?: number;
|
||||
bucketPassword?: string;
|
||||
enablePasswordValidation?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: () => [],
|
||||
limit: 15,
|
||||
fileSize: 10,
|
||||
fileType: () => ['png', 'jpg', 'jpeg'],
|
||||
isShowTip: true,
|
||||
compressSupport: false,
|
||||
compressTargetSize: 300,
|
||||
bucketPassword: '',
|
||||
enablePasswordValidation: false
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const number = ref(0);
|
||||
const uploadList = ref<any[]>([]);
|
||||
const dialogImageUrl = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
// 密码相关
|
||||
const passwordDialogVisible = ref(false);
|
||||
const passwordForm = ref({
|
||||
bucketPassword: ''
|
||||
});
|
||||
const pendingUploadFile = ref<any>(null);
|
||||
|
||||
// 上传的图片服务器地址(用于el-upload组件的action属性)
|
||||
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload');
|
||||
|
||||
// 设置正确的headers(用于el-upload组件)
|
||||
const headers = ref({
|
||||
'Authorization': 'Bearer ' + (localStorage.getItem('token') || ''),
|
||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
'Content-Language': localStorage.getItem('language') || 'zh-CN',
|
||||
'referer': window.location.origin + '/login?redirect=' + encodeURIComponent(window.location.pathname)
|
||||
});
|
||||
|
||||
const fileList = ref<any[]>([]);
|
||||
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
|
||||
|
||||
const imageUploadRef = ref();
|
||||
|
||||
// 监听 fileType 变化,更新 fileAccept
|
||||
const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(','));
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (val: any) => {
|
||||
if (val) {
|
||||
// 首先将值转为数组
|
||||
let list: any[] = [];
|
||||
if (Array.isArray(val)) {
|
||||
list = val;
|
||||
} else if (typeof val === 'string') {
|
||||
// 检查是否包含逗号分隔的URL
|
||||
if (val.includes(',')) {
|
||||
// 多个URL用逗号分隔
|
||||
const urls = val.split(',').filter((url) => url.trim());
|
||||
list = urls.map((url, index) => ({
|
||||
ossId: url,
|
||||
url: url.trim(),
|
||||
originalName: `image_${index + 1}`
|
||||
}));
|
||||
} else if (val.startsWith('http://') || val.startsWith('https://')) {
|
||||
// 单个URL
|
||||
list = [{ ossId: val, url: val, originalName: 'image' }];
|
||||
} else {
|
||||
// 如果是OSS ID,暂时跳过接口调用
|
||||
list = [{ ossId: val, url: val, originalName: 'image' }];
|
||||
}
|
||||
}
|
||||
// 然后将数组转为对象数组
|
||||
fileList.value = list.map((item, index) => {
|
||||
// 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
|
||||
let itemData;
|
||||
if (typeof item === 'string') {
|
||||
itemData = { name: item, url: item };
|
||||
} else {
|
||||
// 此处name使用url 防止删除出现重名,对于URL格式使用url_index的格式
|
||||
const name = item.url && (item.url.startsWith('http://') || item.url.startsWith('https://')) ? `${item.url}_${index}` : item.ossId;
|
||||
itemData = { name: name, url: item.url, ossId: item.ossId };
|
||||
}
|
||||
return itemData;
|
||||
});
|
||||
} else {
|
||||
fileList.value = [];
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
/** 上传前loading加载 */
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
let isImg = false;
|
||||
if (props.fileType.length) {
|
||||
let fileExtension = '';
|
||||
if (file.name.lastIndexOf('.') > -1) {
|
||||
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1);
|
||||
}
|
||||
isImg = props.fileType.some((type: any) => {
|
||||
if (file.type.indexOf(type) > -1) return true;
|
||||
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
isImg = file.type.indexOf('image') > -1;
|
||||
}
|
||||
if (!isImg) {
|
||||
proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}图片格式文件!`);
|
||||
return false;
|
||||
}
|
||||
if (file.name.includes(',')) {
|
||||
proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
|
||||
return false;
|
||||
}
|
||||
if (props.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize;
|
||||
if (!isLt) {
|
||||
proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//压缩图片,开启压缩并且大于指定的压缩大小时才压缩
|
||||
if (props.compressSupport && file.size / 1024 > props.compressTargetSize) {
|
||||
proxy?.$modal.loading('正在上传图片,请稍候...');
|
||||
number.value++;
|
||||
return compressAccurately(file, props.compressTargetSize);
|
||||
} else {
|
||||
proxy?.$modal.loading('正在上传图片,请稍候...');
|
||||
number.value++;
|
||||
}
|
||||
};
|
||||
|
||||
// 文件个数超出
|
||||
const handleExceed = () => {
|
||||
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
|
||||
};
|
||||
|
||||
// 自定义上传方法
|
||||
const customUpload = async (options: any) => {
|
||||
try {
|
||||
// 检查是否需要bucket密码验证
|
||||
if (props.enablePasswordValidation && !props.bucketPassword) {
|
||||
// 显示密码输入对话框
|
||||
pendingUploadFile.value = options;
|
||||
passwordDialogVisible.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加小延迟,避免并发请求冲突
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// 直接上传
|
||||
await performUpload(options);
|
||||
} catch (error) {
|
||||
console.error('上传失败6:', error);
|
||||
proxy?.$modal.msgError('上传失败7');
|
||||
proxy?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 确认密码
|
||||
const confirmPassword = async () => {
|
||||
if (!passwordForm.value.bucketPassword) {
|
||||
proxy?.$modal.msgError('请输入bucket访问密码');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 密码验证成功,执行上传
|
||||
if (pendingUploadFile.value) {
|
||||
await performUpload(pendingUploadFile.value);
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
passwordDialogVisible.value = false;
|
||||
passwordForm.value.bucketPassword = '';
|
||||
pendingUploadFile.value = null;
|
||||
} catch (error) {
|
||||
console.error('密码验证失败:', error);
|
||||
proxy?.$modal.msgError('密码验证失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 执行上传
|
||||
const performUpload = async (options: any) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', options.file);
|
||||
|
||||
// 添加bucket密码到FormData中
|
||||
if (props.bucketPassword || passwordForm.value.bucketPassword) {
|
||||
formData.append('bucket-password', props.bucketPassword || passwordForm.value.bucketPassword || '');
|
||||
}
|
||||
|
||||
// 使用request方式调用接口,禁用防重复提交检查
|
||||
const result = await request.post('/resource/oss/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'repeatSubmit': false // 禁用防重复提交检查
|
||||
}
|
||||
});
|
||||
|
||||
if (result.code === 200) {
|
||||
// 上传成功
|
||||
const uploadResult = {
|
||||
name: result.data.fileName,
|
||||
url: result.data.url,
|
||||
ossId: result.data.ossId
|
||||
};
|
||||
|
||||
uploadList.value.push(uploadResult);
|
||||
uploadedSuccessfully();
|
||||
} else {
|
||||
throw new Error(result.msg || '上传失败1');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('上传失败2:', error);
|
||||
// 处理防重复提交错误
|
||||
if (error.message && error.message.includes('数据正在处理,请勿重复提交')) {
|
||||
proxy?.$modal.msgError('上传过于频繁,请稍后再试');
|
||||
} else {
|
||||
proxy?.$modal.msgError(error.message || '上传失败3');
|
||||
}
|
||||
proxy?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 上传成功回调
|
||||
const handleUploadSuccess = (res: any, file: any) => {
|
||||
|
||||
|
||||
// 确保res存在且有正确的结构
|
||||
if (res && res.code === 200) {
|
||||
// 避免重复添加,检查是否已经存在
|
||||
const existingIndex = uploadList.value.findIndex((item) => item.name === res.data.fileName || item.ossId === res.data.ossId);
|
||||
|
||||
if (existingIndex === -1) {
|
||||
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
|
||||
uploadedSuccessfully();
|
||||
}
|
||||
} else {
|
||||
number.value--;
|
||||
proxy?.$modal.closeLoading();
|
||||
proxy?.$modal.msgError(res?.msg || '上传失败4');
|
||||
if (file && imageUploadRef.value) {
|
||||
imageUploadRef.value?.handleRemove(file);
|
||||
}
|
||||
uploadedSuccessfully();
|
||||
}
|
||||
};
|
||||
|
||||
// 删除图片
|
||||
const handleDelete = (file: any): boolean => {
|
||||
const findex = fileList.value.map((f) => f.name).indexOf(file.name);
|
||||
if (findex > -1 && uploadList.value.length === number.value) {
|
||||
const ossId = fileList.value[findex].ossId;
|
||||
// 这里可以调用删除接口
|
||||
fileList.value.splice(findex, 1);
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 上传结束处理
|
||||
const uploadedSuccessfully = () => {
|
||||
if (number.value > 0 && uploadList.value.length === number.value) {
|
||||
fileList.value = fileList.value.filter((f) => f.ossId !== undefined).concat(uploadList.value);
|
||||
uploadList.value = [];
|
||||
number.value = 0;
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
proxy?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 上传失败5
|
||||
const handleUploadError = () => {
|
||||
proxy?.$modal.msgError('上传图片失败');
|
||||
proxy?.$modal.closeLoading();
|
||||
};
|
||||
|
||||
// 预览 - 使用安全预览
|
||||
const handlePictureCardPreview = async (file: any) => {
|
||||
try {
|
||||
if (file.ossId) {
|
||||
// 使用安全预览,这里简化处理
|
||||
dialogImageUrl.value = file.url;
|
||||
} else {
|
||||
// 直接使用URL预览
|
||||
dialogImageUrl.value = file.url;
|
||||
}
|
||||
dialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('预览失败:', error);
|
||||
proxy?.$modal.msgError('预览失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 对象转成指定字符串分隔
|
||||
const listToString = (list: any[], separator?: string) => {
|
||||
let strs = '';
|
||||
separator = separator || ',';
|
||||
for (const i in list) {
|
||||
if (undefined !== list[i].ossId && list[i].ossId.indexOf('blob:') !== 0) {
|
||||
// 返回ossId
|
||||
strs += list[i].ossId + separator;
|
||||
}
|
||||
}
|
||||
return strs != '' ? strs.substring(0, strs.length - 1) : '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// .el-upload--picture-card 控制加号部分
|
||||
:deep(.hide .el-upload--picture-card) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -35,17 +35,32 @@
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
|
||||
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
|
||||
<div v-loading="previewLoading" style="text-align: center; min-height: 200px">
|
||||
<img
|
||||
v-if="dialogImageUrl"
|
||||
:src="dialogImageUrl"
|
||||
style="display: block; max-width: 100%; margin: 0 auto"
|
||||
@error="handlePreviewError"
|
||||
@load="handlePreviewLoad"
|
||||
/>
|
||||
<div v-if="previewError" class="preview-error">
|
||||
<el-icon size="48"><picture-filled /></el-icon>
|
||||
<div class="error-text">图片预览失败</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick, getCurrentInstance, onBeforeUnmount, type ComponentInternalInstance } from 'vue';
|
||||
import { listByIds, delOss } from '@/api/system/oss';
|
||||
import { OssVO } from '@/api/system/oss/types';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
import { compressAccurately } from 'image-conversion';
|
||||
import { PictureFilled, ZoomIn, Delete } from '@element-plus/icons-vue';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -79,6 +94,17 @@ const uploadList = ref<any[]>([]);
|
||||
const dialogImageUrl = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
// 预览相关状态
|
||||
const previewLoading = ref(false);
|
||||
const previewError = ref(false);
|
||||
const previewUrlCache = ref<Map<string, string>>(new Map());
|
||||
const previewBlobUrls = ref<string[]>([]);
|
||||
|
||||
// 异步加载相关状态
|
||||
const loadingStates = ref<Map<string, boolean>>(new Map());
|
||||
const loadErrorStates = ref<Map<string, boolean>>(new Map());
|
||||
|
||||
// 使用环境变量
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // 上传的图片服务器地址
|
||||
const headers = ref(globalHeaders());
|
||||
@@ -86,34 +112,280 @@ const headers = ref(globalHeaders());
|
||||
const fileList = ref<any[]>([]);
|
||||
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
|
||||
|
||||
const imageUploadRef = ref<ElUploadInstance>();
|
||||
const imageUploadRef = ref<any>();
|
||||
|
||||
// 添加标志位,避免上传成功后重复处理
|
||||
const isInternalUpdate = ref(false);
|
||||
|
||||
// 监听 fileType 变化,更新 fileAccept
|
||||
const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(','));
|
||||
|
||||
// 清理预览blob URL,防止内存泄漏
|
||||
const cleanupPreviewBlobUrls = () => {
|
||||
previewBlobUrls.value.forEach((url) => {
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
previewBlobUrls.value = [];
|
||||
previewUrlCache.value.clear();
|
||||
};
|
||||
|
||||
// 组件卸载时清理blob URLs
|
||||
onBeforeUnmount(() => {
|
||||
cleanupPreviewBlobUrls();
|
||||
});
|
||||
|
||||
// 验证blob数据是否为有效图片
|
||||
const validateImageBlob = (blob: Blob): boolean => {
|
||||
// 允许的图片类型,包括通用的octet-stream类型(OSS服务器常返回此类型)
|
||||
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'application/octet-stream'];
|
||||
|
||||
// 基本验证:类型检查和大小检查
|
||||
const hasValidType = validTypes.includes(blob.type) || blob.type === '' || blob.type === 'application/octet-stream';
|
||||
const hasValidSize = blob.size > 0;
|
||||
|
||||
return hasValidType && hasValidSize;
|
||||
};
|
||||
|
||||
// 通过认证请求获取图片数据并转换为blob URL
|
||||
const getPreviewImageBlobUrl = async (imageUrl: string): Promise<string> => {
|
||||
try {
|
||||
// 检查是否已有缓存的blob URL
|
||||
const cachedUrl = previewUrlCache.value.get(imageUrl);
|
||||
if (cachedUrl) {
|
||||
return cachedUrl;
|
||||
}
|
||||
|
||||
// 通过request请求图片数据,这样会自动带上token和clientId
|
||||
const response = await request({
|
||||
url: imageUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
|
||||
// 由于request响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
if (!(blob instanceof Blob)) {
|
||||
// 如果不是Blob,尝试获取实际内容用于调试
|
||||
let actualContent = 'Unknown';
|
||||
try {
|
||||
if (typeof blob === 'string') {
|
||||
actualContent = (blob as string).substring(0, 200);
|
||||
} else if (blob && typeof blob.toString === 'function') {
|
||||
actualContent = blob.toString().substring(0, 200);
|
||||
}
|
||||
} catch (e) {
|
||||
actualContent = 'Cannot convert to string';
|
||||
}
|
||||
|
||||
throw new Error(`响应数据不是有效的Blob对象。实际类型: ${typeof blob}, 内容: ${actualContent}`);
|
||||
}
|
||||
|
||||
// 检查blob内容类型
|
||||
// const contentType = blob.type;
|
||||
|
||||
// 如果Content-Type明确表明这不是图片数据且不是通用二进制格式,进行检查
|
||||
// if (
|
||||
// contentType &&
|
||||
// !contentType.toLowerCase().startsWith('image/') &&
|
||||
// !contentType.toLowerCase().includes('octet-stream') &&
|
||||
// (contentType.toLowerCase().includes('text/') || contentType.toLowerCase().includes('html'))
|
||||
// ) {
|
||||
// const text = await blob.text();
|
||||
// const preview = text.substring(0, 300);
|
||||
// throw new Error(`响应Content-Type不是图片格式: ${contentType}。响应内容预览: ${preview}`);
|
||||
// }
|
||||
|
||||
// 验证是否为有效图片
|
||||
// if (!validateImageBlob(blob)) {
|
||||
// throw new Error(`无效的图片格式: ${blob.type}, 大小: ${blob.size}`);
|
||||
// }
|
||||
|
||||
// 创建blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 缓存blob URL
|
||||
previewUrlCache.value.set(imageUrl, blobUrl);
|
||||
previewBlobUrls.value.push(blobUrl);
|
||||
|
||||
return blobUrl;
|
||||
} catch (error) {
|
||||
console.error('获取预览图片失败:', error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 通过ossid获取预览URL
|
||||
const getPreviewUrlByOssId = async (ossId: string): Promise<string> => {
|
||||
try {
|
||||
// const response = await listByIds(ossId);
|
||||
// // RuoYi框架的响应格式:{code, msg, data},实际数据在data字段中
|
||||
// const ossList: OssVO[] = (response as any)?.data || response;
|
||||
|
||||
// if (!ossList || ossList.length === 0) {
|
||||
// throw new Error(`OSS ID ${ossId} 在数据库中不存在或已被删除`);
|
||||
// }
|
||||
|
||||
// const ossFile = ossList[0];
|
||||
|
||||
// if (!ossFile || !ossFile.url) {
|
||||
// throw new Error(`OSS文件缺少url属性`);
|
||||
// }
|
||||
|
||||
// const imageUrl = ossFile.url;
|
||||
|
||||
// // 检查URL是否有效
|
||||
// if (!imageUrl || typeof imageUrl !== 'string') {
|
||||
// throw new Error(`OSS接口返回的URL无效`);
|
||||
// }
|
||||
|
||||
// 获取认证的blob URL
|
||||
return await getPreviewImageBlobUrl('/resource/oss/download/' + ossId);
|
||||
} catch (error) {
|
||||
console.error('通过ossId获取预览图片失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 异步加载单个文件的图片
|
||||
const loadFileImageAsync = async (item: any, index: number): Promise<void> => {
|
||||
const fileKey = item.ossId || item.url || `file_${index}`;
|
||||
|
||||
// 设置加载状态
|
||||
loadingStates.value.set(fileKey, true);
|
||||
loadErrorStates.value.set(fileKey, false);
|
||||
|
||||
try {
|
||||
let blobUrl = '';
|
||||
|
||||
if (item.ossId) {
|
||||
// 优先通过OSS ID获取预览URL
|
||||
console.log(`loadFileImageAsync: 通过OSS ID获取预览URL for ${item.ossId}`);
|
||||
blobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
} else if (typeof item === 'string') {
|
||||
// 直接URL情况
|
||||
console.log(`loadFileImageAsync: 直接URL情况 for ${item}`);
|
||||
blobUrl = await getPreviewImageBlobUrl(item);
|
||||
} else if (item.url) {
|
||||
// OSS对象情况(降级处理)
|
||||
console.log(`loadFileImageAsync: OSS对象情况 for ${item.url}`);
|
||||
blobUrl = await getPreviewImageBlobUrl(item.url);
|
||||
}
|
||||
|
||||
if (blobUrl) {
|
||||
// 更新文件列表中的对应项
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId || f.url === item.url || f.originalUrl === item.originalUrl);
|
||||
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = blobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`loadFileImageAsync: 成功更新文件 ${item.ossId || item.url}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('获取图片blob URL失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('异步加载图片失败:', error);
|
||||
loadErrorStates.value.set(fileKey, true);
|
||||
|
||||
// 降级处理:尝试通过OSS ID重新获取
|
||||
if (item.ossId) {
|
||||
try {
|
||||
console.log(`loadFileImageAsync: 降级处理,重新通过OSS ID获取 ${item.ossId}`);
|
||||
const fallbackBlobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1 && fallbackBlobUrl) {
|
||||
fileList.value[fileIndex].url = fallbackBlobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`loadFileImageAsync: 降级处理成功 for ${item.ossId}`);
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error('loadFileImageAsync: 降级处理也失败:', fallbackError);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loadingStates.value.set(fileKey, false);
|
||||
}
|
||||
};
|
||||
|
||||
// 批量异步加载图片
|
||||
const loadImagesAsync = async (items: any[]): Promise<void> => {
|
||||
// 先创建占位框,使用一个占位图片URL
|
||||
const placeholderItems = items.map((item, index) => {
|
||||
const name =
|
||||
item.url && (item.url.startsWith('http://') || item.url.startsWith('https://')) ? `${item.url}_${index}` : item.ossId || `file_${index}`;
|
||||
|
||||
return {
|
||||
name: name,
|
||||
url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBmaWxsPSIjZjVmN2ZhIi8+CjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjE1IiBzdHJva2U9IiM5MDkzOTkiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtZGFzaGFycmF5PSI2LCAzIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4KCTxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIvPgo8L2NpcmNsZT4KPC9zdmc+', // 小圆圈浅灰色加载中占位图片
|
||||
originalUrl: item.url,
|
||||
ossId: item.ossId,
|
||||
isLoaded: false, // 标记为未加载
|
||||
isPlaceholder: true // 标记为占位符
|
||||
};
|
||||
});
|
||||
|
||||
// 立即更新文件列表显示占位框
|
||||
fileList.value = placeholderItems;
|
||||
|
||||
// 异步加载所有图片
|
||||
const loadPromises = items.map((item, index) => loadFileImageAsync(item, index));
|
||||
|
||||
// 等待所有图片加载完成(使用Promise.all替代allSettled以兼容旧版本)
|
||||
await Promise.all(loadPromises);
|
||||
};
|
||||
|
||||
// 图片预览加载成功
|
||||
const handlePreviewLoad = () => {
|
||||
previewError.value = false;
|
||||
};
|
||||
|
||||
// 图片预览加载失败
|
||||
const handlePreviewError = () => {
|
||||
previewError.value = true;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (val: string) => {
|
||||
// 如果是内部更新触发的,跳过处理
|
||||
if (isInternalUpdate.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
// 首先将值转为数组
|
||||
let list: OssVO[] = [];
|
||||
if (Array.isArray(val)) {
|
||||
list = val as OssVO[];
|
||||
} else if (typeof val === 'string') {
|
||||
// 检查是否包含逗号分隔的URL
|
||||
console.log('val', val);
|
||||
// 检查是否包含逗号分隔的OSS ID
|
||||
if (val.includes(',')) {
|
||||
// 多个URL用逗号分隔
|
||||
const urls = val.split(',').filter((url) => url.trim());
|
||||
list = urls.map((url, index) => ({
|
||||
ossId: url,
|
||||
url: url.trim(),
|
||||
originalName: `image_${index + 1}`
|
||||
})) as any;
|
||||
// 多个OSS ID用逗号分隔,需要调用接口获取详细信息
|
||||
const ossIds = val.split(',').filter((id) => id.trim());
|
||||
try {
|
||||
const res = await listByIds(ossIds.join(','));
|
||||
list = res.data;
|
||||
} catch (error) {
|
||||
console.error('调用listByIds接口失败:', error);
|
||||
list = [];
|
||||
}
|
||||
} else if (val.startsWith('http://') || val.startsWith('https://')) {
|
||||
// 单个URL
|
||||
list = [{ ossId: val, url: val, originalName: 'image' }] as any;
|
||||
} else {
|
||||
// 如果是OSS ID,调用接口获取详细信息
|
||||
// 单个OSS ID,调用接口获取详细信息
|
||||
try {
|
||||
const res = await listByIds(val);
|
||||
list = res.data;
|
||||
@@ -123,19 +395,26 @@ watch(
|
||||
}
|
||||
}
|
||||
}
|
||||
// 然后将数组转为对象数组
|
||||
fileList.value = list.map((item, index) => {
|
||||
// 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
|
||||
let itemData;
|
||||
if (typeof item === 'string') {
|
||||
itemData = { name: item, url: item };
|
||||
} else {
|
||||
// 此处name使用url 防止删除出现重名,对于URL格式使用url_index的格式
|
||||
const name = item.url && (item.url.startsWith('http://') || item.url.startsWith('https://')) ? `${item.url}_${index}` : item.ossId;
|
||||
itemData = { name: name, url: item.url, ossId: item.ossId };
|
||||
}
|
||||
return itemData;
|
||||
});
|
||||
|
||||
// 使用异步加载方式处理文件列表
|
||||
try {
|
||||
await loadImagesAsync(list);
|
||||
console.log('fileList with async loaded images', fileList.value);
|
||||
} catch (error) {
|
||||
console.error('异步加载文件列表失败:', error);
|
||||
// 降级处理:使用原始URL
|
||||
fileList.value = list.map((item, index) => {
|
||||
let itemData;
|
||||
if (typeof item === 'string') {
|
||||
itemData = { name: item, url: item, originalUrl: item, isLoaded: true };
|
||||
} else {
|
||||
const name = item.url && (item.url.startsWith('http://') || item.url.startsWith('https://')) ? `${item.url}_${index}` : item.ossId;
|
||||
itemData = { name: name, url: item.url, originalUrl: item.url, ossId: item.ossId, isLoaded: true };
|
||||
}
|
||||
return itemData;
|
||||
});
|
||||
console.log('fileList (fallback)', fileList.value);
|
||||
}
|
||||
} else {
|
||||
fileList.value = [];
|
||||
return [];
|
||||
@@ -161,85 +440,261 @@ const handleBeforeUpload = (file: any) => {
|
||||
isImg = file.type.indexOf('image') > -1;
|
||||
}
|
||||
if (!isImg) {
|
||||
proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}图片格式文件!`);
|
||||
(proxy as any)?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}图片格式文件!`);
|
||||
return false;
|
||||
}
|
||||
if (file.name.includes(',')) {
|
||||
proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
|
||||
(proxy as any)?.$modal.msgError('文件名不正确,不能包含英文逗号!');
|
||||
return false;
|
||||
}
|
||||
if (props.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize;
|
||||
if (!isLt) {
|
||||
proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
|
||||
(proxy as any)?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//压缩图片,开启压缩并且大于指定的压缩大小时才压缩
|
||||
if (props.compressSupport && file.size / 1024 > props.compressTargetSize) {
|
||||
proxy?.$modal.loading('正在上传图片,请稍候...');
|
||||
(proxy as any)?.$modal.loading('正在上传图片,请稍候...');
|
||||
number.value++;
|
||||
return compressAccurately(file, props.compressTargetSize);
|
||||
} else {
|
||||
proxy?.$modal.loading('正在上传图片,请稍候...');
|
||||
(proxy as any)?.$modal.loading('正在上传图片,请稍候...');
|
||||
number.value++;
|
||||
}
|
||||
};
|
||||
|
||||
// 文件个数超出
|
||||
const handleExceed = () => {
|
||||
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
|
||||
(proxy as any)?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
|
||||
};
|
||||
|
||||
// 上传成功回调
|
||||
const handleUploadSuccess = (res: any, file: UploadFile) => {
|
||||
const handleUploadSuccess = (res: any, file: any) => {
|
||||
console.log('handleUploadSuccess:res', res);
|
||||
console.log('handleUploadSuccess:file', file);
|
||||
|
||||
if (res.code === 200) {
|
||||
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
|
||||
const uploadItem = {
|
||||
name: res.data.fileName,
|
||||
url: res.data.url,
|
||||
ossId: res.data.ossId
|
||||
};
|
||||
console.log('添加上传项到uploadList:', uploadItem);
|
||||
uploadList.value.push(uploadItem);
|
||||
uploadedSuccessfully();
|
||||
} else {
|
||||
number.value--;
|
||||
proxy?.$modal.closeLoading();
|
||||
proxy?.$modal.msgError(res.msg);
|
||||
(proxy as any)?.$modal.closeLoading();
|
||||
(proxy as any)?.$modal.msgError(res.msg);
|
||||
imageUploadRef.value?.handleRemove(file);
|
||||
uploadedSuccessfully();
|
||||
}
|
||||
};
|
||||
|
||||
// 删除图片
|
||||
const handleDelete = (file: UploadFile): boolean => {
|
||||
const handleDelete = (file: any): boolean => {
|
||||
const findex = fileList.value.map((f) => f.name).indexOf(file.name);
|
||||
if (findex > -1 && uploadList.value.length === number.value) {
|
||||
const ossId = fileList.value[findex].ossId;
|
||||
delOss(ossId);
|
||||
if (ossId) {
|
||||
// delOss(ossId);
|
||||
}
|
||||
|
||||
// 清理对应的blob URL
|
||||
const fileToDelete = fileList.value[findex];
|
||||
if (fileToDelete.url && fileToDelete.url.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(fileToDelete.url);
|
||||
}
|
||||
|
||||
fileList.value.splice(findex, 1);
|
||||
// 设置内部更新标志
|
||||
isInternalUpdate.value = true;
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
// 重置标志
|
||||
nextTick(() => {
|
||||
isInternalUpdate.value = false;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 上传结束处理
|
||||
const uploadedSuccessfully = () => {
|
||||
const uploadedSuccessfully = async () => {
|
||||
console.log('uploadedSuccessfully called, number:', number.value, 'uploadList length:', uploadList.value.length);
|
||||
|
||||
if (number.value > 0 && uploadList.value.length === number.value) {
|
||||
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
|
||||
console.log('开始处理新上传的文件:', uploadList.value);
|
||||
|
||||
// 对新上传的文件进行异步加载处理
|
||||
const newUploadedFiles = uploadList.value.map((item) => ({
|
||||
...item,
|
||||
isLoaded: false,
|
||||
isPlaceholder: false
|
||||
}));
|
||||
|
||||
console.log('处理后的新上传文件:', newUploadedFiles);
|
||||
|
||||
// 先显示新上传的文件(使用占位符)
|
||||
const placeholderItems = newUploadedFiles.map((item, index) => ({
|
||||
name: item.name,
|
||||
url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBmaWxsPSIjZjVmN2ZhIi8+CjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjE1IiBzdHJva2U9IiM5MDkzOTkiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtZGFzaGFycmF5PSI2LCAzIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4KCTxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIvPgo8L2NpcmNsZT4KPC9zdmc+',
|
||||
originalUrl: item.url,
|
||||
ossId: item.ossId,
|
||||
isLoaded: false,
|
||||
isPlaceholder: true
|
||||
}));
|
||||
|
||||
console.log('占位符项目:', placeholderItems);
|
||||
|
||||
// 合并现有文件和新上传的文件
|
||||
const existingFiles = fileList.value.filter((f) => f.url !== undefined);
|
||||
console.log('现有文件:', existingFiles);
|
||||
|
||||
fileList.value = existingFiles.concat(placeholderItems);
|
||||
console.log('合并后的文件列表:', fileList.value);
|
||||
|
||||
// 异步加载新上传的图片
|
||||
const loadPromises = newUploadedFiles.map((item, index) => {
|
||||
const fileKey = item.ossId || item.url || `file_${index}`;
|
||||
|
||||
console.log(`开始加载文件 ${index}:`, item);
|
||||
|
||||
// 设置加载状态
|
||||
loadingStates.value.set(fileKey, true);
|
||||
loadErrorStates.value.set(fileKey, false);
|
||||
|
||||
return (async () => {
|
||||
try {
|
||||
let blobUrl = '';
|
||||
|
||||
if (item.ossId) {
|
||||
console.log(`通过OSS ID获取预览URL for ${item.ossId}`);
|
||||
// 通过OSS ID获取预览URL
|
||||
blobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
console.log(`获取到的blob URL for ${item.ossId}:`, blobUrl);
|
||||
} else if (item.url) {
|
||||
console.log(`通过URL获取blob URL for ${item.ossId}:`, item.url);
|
||||
// 直接URL情况(降级处理)
|
||||
blobUrl = await getPreviewImageBlobUrl(item.url);
|
||||
console.log(`获取到的blob URL for ${item.ossId}:`, blobUrl);
|
||||
}
|
||||
|
||||
if (blobUrl) {
|
||||
// 更新文件列表中的对应项
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
console.log(`更新文件列表索引 ${fileIndex} for ${item.ossId}`);
|
||||
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = blobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`成功更新文件 ${item.ossId}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('获取图片blob URL失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('异步加载新上传图片失败:', error);
|
||||
loadErrorStates.value.set(fileKey, true);
|
||||
|
||||
// 降级处理:通过OSS ID重新获取
|
||||
try {
|
||||
console.log(`降级处理:重新通过OSS ID获取 ${item.ossId}`);
|
||||
const fallbackBlobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1 && fallbackBlobUrl) {
|
||||
fileList.value[fileIndex].url = fallbackBlobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`降级处理成功,通过OSS ID重新获取 for ${item.ossId}`);
|
||||
} else {
|
||||
// 最后的降级:使用原始URL
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = item.url;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`最终降级处理,使用原始URL for ${item.ossId}`);
|
||||
}
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error('降级处理也失败:', fallbackError);
|
||||
// 最后的降级:使用原始URL
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = item.url;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`最终降级处理,使用原始URL for ${item.ossId}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loadingStates.value.set(fileKey, false);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
// 等待所有图片加载完成
|
||||
await Promise.all(loadPromises);
|
||||
console.log('所有图片加载完成,最终文件列表:', fileList.value);
|
||||
|
||||
uploadList.value = [];
|
||||
number.value = 0;
|
||||
// 设置内部更新标志
|
||||
isInternalUpdate.value = true;
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
proxy?.$modal.closeLoading();
|
||||
// 重置标志
|
||||
nextTick(() => {
|
||||
isInternalUpdate.value = false;
|
||||
});
|
||||
(proxy as any)?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 上传失败
|
||||
const handleUploadError = () => {
|
||||
proxy?.$modal.msgError('上传图片失败');
|
||||
proxy?.$modal.closeLoading();
|
||||
(proxy as any)?.$modal.msgError('上传图片失败');
|
||||
(proxy as any)?.$modal.closeLoading();
|
||||
};
|
||||
|
||||
// 预览
|
||||
const handlePictureCardPreview = (file: any) => {
|
||||
dialogImageUrl.value = file.url;
|
||||
const handlePictureCardPreview = async (file: any) => {
|
||||
console.log('预览文件:', file);
|
||||
previewLoading.value = true;
|
||||
previewError.value = false;
|
||||
dialogVisible.value = true;
|
||||
|
||||
try {
|
||||
let previewUrl = '';
|
||||
|
||||
// 判断是否为ossId
|
||||
if (file.ossId) {
|
||||
// 通过ossId获取预览URL
|
||||
previewUrl = await getPreviewUrlByOssId(file.ossId);
|
||||
} else if (file.originalUrl) {
|
||||
// 使用原始URL进行预览(避免重复转换)
|
||||
previewUrl = await getPreviewImageBlobUrl(file.originalUrl);
|
||||
} else if (file.url) {
|
||||
// 直接URL预览
|
||||
previewUrl = await getPreviewImageBlobUrl(file.url);
|
||||
} else {
|
||||
throw new Error('文件缺少必要的URL或ossId信息');
|
||||
}
|
||||
|
||||
dialogImageUrl.value = previewUrl;
|
||||
} catch (error) {
|
||||
console.error('图片预览失败:', error);
|
||||
previewError.value = true;
|
||||
dialogImageUrl.value = '';
|
||||
(proxy as any)?.$modal.msgError('图片预览失败');
|
||||
} finally {
|
||||
previewLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 对象转成指定字符串分隔
|
||||
@@ -247,9 +702,9 @@ const listToString = (list: any[], separator?: string) => {
|
||||
let strs = '';
|
||||
separator = separator || ',';
|
||||
for (const i in list) {
|
||||
if (undefined !== list[i].url && list[i].url.indexOf('blob:') !== 0) {
|
||||
// 始终返回URL而不是ossId
|
||||
strs += list[i].url + separator;
|
||||
if (undefined !== list[i].ossId && list[i].ossId.indexOf('blob:') !== 0) {
|
||||
// 返回ossId用于数据存储
|
||||
strs += list[i].ossId + separator;
|
||||
}
|
||||
}
|
||||
return strs != '' ? strs.substring(0, strs.length - 1) : '';
|
||||
@@ -261,4 +716,87 @@ const listToString = (list: any[], separator?: string) => {
|
||||
:deep(.hide .el-upload--picture-card) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 预览错误状态样式
|
||||
.preview-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #909399;
|
||||
min-height: 200px;
|
||||
|
||||
.error-text {
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 异步加载优化样式
|
||||
:deep(.el-upload-list--picture-card .el-upload-list__item) {
|
||||
// 占位图片加载时的样式
|
||||
.el-upload-list__item-thumbnail {
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// 占位符图片样式优化
|
||||
&.placeholder-item {
|
||||
.el-upload-list__item-thumbnail {
|
||||
background-color: #f5f7fa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .el-upload-list__item-thumbnail img {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载完成后的样式
|
||||
&.loaded-item {
|
||||
.el-upload-list__item-thumbnail {
|
||||
img {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载动画
|
||||
@keyframes loading-rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 占位符加载动画
|
||||
@keyframes placeholder-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-loading {
|
||||
animation: placeholder-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,461 @@
|
||||
<template>
|
||||
<div class="poster-preview">
|
||||
<!-- 海报画布容器 -->
|
||||
<div
|
||||
ref="posterRef"
|
||||
class="poster-canvas"
|
||||
:style="canvasStyle"
|
||||
>
|
||||
<!-- 企业信息叠加 -->
|
||||
<div class="info-overlay">
|
||||
<!-- 顶部企业标题 -->
|
||||
<div class="company-header">
|
||||
<h1 class="company-name">{{ posterData.enterpriseName }}</h1>
|
||||
<div class="company-tagline">诚聘英才 · 共创未来</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间内容区域 - 根据海报类型显示不同内容 -->
|
||||
|
||||
<!-- 企业海报:展示企业文化和优势 -->
|
||||
<div v-if="posterData.posterType === '1'" class="position-info">
|
||||
<div class="position-title">加入我们 · 共创辉煌</div>
|
||||
<div v-if="enterpriseDetail" class="position-details">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div class="detail-item" v-if="enterpriseDetail.mainBusiness">
|
||||
<span class="icon">🏢</span>
|
||||
<span>{{ enterpriseDetail.mainBusiness }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="enterpriseDetail.enterpriseScale">
|
||||
<span class="icon">👥</span>
|
||||
<span>{{ getScaleLabel(enterpriseDetail.enterpriseScale) }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="enterpriseDetail.address">
|
||||
<span class="icon">📍</span>
|
||||
<span>{{ enterpriseDetail.address }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 岗位海报:展示具体岗位信息 -->
|
||||
<div v-else-if="posterData.posterType === '2'" class="position-info">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<div class="position-title">
|
||||
{{ posterData.positionName || positionDetail?.positionName || '招聘岗位' }}
|
||||
</div>
|
||||
<div v-if="positionDetail && Object.keys(positionDetail).length > 0" class="position-details">
|
||||
<div class="position-details-grid">
|
||||
<div class="detail-item" v-if="positionDetail.workAddress">
|
||||
<span class="icon">📍</span>
|
||||
<span>{{ positionDetail.workAddress }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="positionDetail.salary">
|
||||
<span class="icon">💰</span>
|
||||
<span>{{ positionDetail.salary }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="positionDetail.workTime">
|
||||
<span class="icon">⏰</span>
|
||||
<span>{{ positionDetail.workTime }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="positionDetail.requiredSkills">
|
||||
<span class="icon">🎯</span>
|
||||
<span>{{ positionDetail.requiredSkills }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 如果没有岗位详情,显示提示 -->
|
||||
<div v-else class="position-details">
|
||||
<div class="detail-item">
|
||||
<span class="icon">💼</span>
|
||||
<span>岗位详情加载中或暂无数据</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部联系信息 - 根据海报类型显示不同内容 -->
|
||||
<div class="company-footer">
|
||||
<div v-if="enterpriseDetail" class="company-info">
|
||||
|
||||
<!-- 企业海报:重点显示联系方式 -->
|
||||
<template v-if="posterData.posterType === '1'">
|
||||
<div class="info-row" v-if="enterpriseDetail.contactPhone">
|
||||
<span class="label">📞 联系电话:</span>
|
||||
<span class="value">{{ enterpriseDetail.contactPhone }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="enterpriseDetail.email">
|
||||
<span class="label">📧 电子邮箱:</span>
|
||||
<span class="value">{{ enterpriseDetail.email }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="enterpriseDetail.address">
|
||||
<span class="label">🏢 公司地址:</span>
|
||||
<span class="value">{{ enterpriseDetail.address }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 岗位海报:显示企业背景信息 -->
|
||||
<template v-else-if="posterData.posterType === '2'">
|
||||
<div class="info-row" v-if="enterpriseDetail.mainBusiness">
|
||||
<span class="label">主营业务:</span>
|
||||
<span class="value">{{ enterpriseDetail.mainBusiness }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="enterpriseDetail.enterpriseScale">
|
||||
<span class="label">企业规模:</span>
|
||||
<span class="value">{{ getScaleLabel(enterpriseDetail.enterpriseScale) }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="enterpriseDetail.contactPhone">
|
||||
<span class="label">联系电话:</span>
|
||||
<span class="value">{{ enterpriseDetail.contactPhone }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="enterpriseDetail.email">
|
||||
<span class="label">电子邮箱:</span>
|
||||
<span class="value">{{ enterpriseDetail.email }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" icon="Download" @click="exportToPDF" :loading="exporting">
|
||||
导出PDF
|
||||
</el-button>
|
||||
<el-button icon="Picture" @click="exportToImage" :loading="exporting">
|
||||
导出图片
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import html2canvas from 'html2canvas'
|
||||
import jsPDF from 'jspdf'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// 定义接口
|
||||
interface PosterData {
|
||||
posterPath: string
|
||||
enterpriseName: string
|
||||
positionName?: string
|
||||
posterType: string
|
||||
}
|
||||
|
||||
interface EnterpriseDetail {
|
||||
mainBusiness?: string
|
||||
enterpriseScale?: string
|
||||
contactPhone?: string
|
||||
email?: string
|
||||
address?: string
|
||||
}
|
||||
|
||||
interface PositionDetail {
|
||||
positionName?: string
|
||||
workAddress?: string
|
||||
salary?: string
|
||||
workTime?: string
|
||||
requiredSkills?: string
|
||||
}
|
||||
|
||||
// Props定义
|
||||
const props = defineProps<{
|
||||
posterData: PosterData
|
||||
enterpriseDetail?: EnterpriseDetail
|
||||
positionDetail?: PositionDetail
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const posterRef = ref<HTMLElement>()
|
||||
const exporting = ref(false)
|
||||
|
||||
// 计算样式
|
||||
const canvasStyle = computed(() => ({
|
||||
backgroundImage: `url(${props.posterData.posterPath})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}))
|
||||
|
||||
// 企业规模字典
|
||||
const scaleDict = {
|
||||
'1': '50人以下',
|
||||
'2': '50-200人',
|
||||
'3': '200-500人',
|
||||
'4': '500-1000人',
|
||||
'5': '1000人以上'
|
||||
}
|
||||
|
||||
// 获取规模标签
|
||||
const getScaleLabel = (value: string) => {
|
||||
return scaleDict[value as keyof typeof scaleDict] || value
|
||||
}
|
||||
|
||||
// 导出为PDF
|
||||
const exportToPDF = async () => {
|
||||
if (!posterRef.value) return
|
||||
|
||||
try {
|
||||
exporting.value = true
|
||||
|
||||
const canvas = await html2canvas(posterRef.value, {
|
||||
backgroundColor: null,
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
foreignObjectRendering: false
|
||||
})
|
||||
|
||||
const imgData = canvas.toDataURL('image/png')
|
||||
const pdf = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'mm',
|
||||
format: 'a4'
|
||||
})
|
||||
|
||||
const imgWidth = 190
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight)
|
||||
|
||||
const fileName = `${props.posterData.enterpriseName}_招聘海报_${new Date().getTime()}.pdf`
|
||||
pdf.save(fileName)
|
||||
|
||||
ElMessage.success('PDF导出成功')
|
||||
} catch (error) {
|
||||
console.error('PDF导出失败:', error)
|
||||
ElMessage.error('PDF导出失败,请重试')
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 导出为图片
|
||||
const exportToImage = async () => {
|
||||
if (!posterRef.value) return
|
||||
|
||||
try {
|
||||
exporting.value = true
|
||||
|
||||
const canvas = await html2canvas(posterRef.value, {
|
||||
backgroundColor: null,
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
allowTaint: true
|
||||
})
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.download = `${props.posterData.enterpriseName}_招聘海报_${new Date().getTime()}.png`
|
||||
link.href = canvas.toDataURL('image/png')
|
||||
link.click()
|
||||
|
||||
ElMessage.success('图片导出成功')
|
||||
} catch (error) {
|
||||
console.error('图片导出失败:', error)
|
||||
ElMessage.error('图片导出失败,请重试')
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.poster-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.poster-canvas {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
height: 800px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.info-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 40px;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.3) 0%,
|
||||
rgba(0, 0, 0, 0.1) 50%,
|
||||
rgba(0, 0, 0, 0.4) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.company-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 10px 0;
|
||||
letter-spacing: 2px;
|
||||
background: linear-gradient(135deg, #ffd700, #ffed4e);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: none;
|
||||
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8));
|
||||
}
|
||||
|
||||
.company-tagline {
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 4px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.position-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.position-title {
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
background: linear-gradient(135deg, #ff6b6b, #ffd93d);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: none;
|
||||
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8));
|
||||
}
|
||||
|
||||
.position-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.position-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 15px;
|
||||
border-radius: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
font-size: 14px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
margin-right: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.company-footer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.company-info {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
min-width: 80px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.poster-canvas {
|
||||
width: 90vw;
|
||||
height: calc(90vw * 4 / 3);
|
||||
max-width: 450px;
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
.info-overlay {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.position-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 14px;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -44,7 +44,7 @@
|
||||
<el-table-column prop="originalName" width="202" :show-overflow-tooltip="true" label="附件名称"></el-table-column>
|
||||
<el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="操作">
|
||||
<template #default="tool">
|
||||
<el-button type="text" @click="handleDownload(tool.row.ossId)">下载</el-button>
|
||||
<el-button type="text" @click="handleDownload(tool.row.ossId, tool.row.originalName)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -106,8 +106,9 @@ const getIds = async (ids: string | number) => {
|
||||
};
|
||||
|
||||
/** 下载按钮操作 */
|
||||
const handleDownload = (ossId: string) => {
|
||||
proxy?.$download.oss(ossId);
|
||||
const handleDownload = (ossId: string, originalName: string) => {
|
||||
console.log(ossId);
|
||||
proxy?.$download.secureOss(ossId, originalName);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<div class="right-menu flex align-center">
|
||||
<template v-if="appStore.device !== 'mobile'">
|
||||
<el-select
|
||||
<!-- <el-select
|
||||
v-if="userId === 1 && tenantEnabled"
|
||||
v-model="companyName"
|
||||
class="min-w-244px"
|
||||
@@ -19,7 +19,7 @@
|
||||
>
|
||||
<el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
|
||||
<template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
|
||||
</el-select>
|
||||
</el-select> -->
|
||||
|
||||
<search-menu ref="searchMenuRef" />
|
||||
<el-tooltip content="搜索" effect="dark" placement="bottom">
|
||||
|
||||
+55
-11
@@ -4,27 +4,35 @@ import errorCode from '@/utils/errorCode';
|
||||
import { blobValidate } from '@/utils/ruoyi';
|
||||
import { LoadingInstance } from 'element-plus/es/components/loading/src/loading.mjs';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
import { getFileDownloadUrl } from '@/api/system/file';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const baseURL = import.meta.env.VITE_APP_BASE_API;
|
||||
let downloadLoadingInstance: LoadingInstance;
|
||||
export default {
|
||||
async oss(ossId: string | number) {
|
||||
const url = baseURL + '/resource/oss/download/' + ossId;
|
||||
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
|
||||
downloadLoadingInstance = ElLoading.service({ text: '正在获取文件访问链接,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
|
||||
try {
|
||||
const res = await axios({
|
||||
// 使用新的安全下载方法,避免Number()转换导致的大整数精度丢失
|
||||
// const { data: downloadUrl } = await getFileDownloadUrl(String(ossId));
|
||||
|
||||
const response = await request({
|
||||
url: "/resource/oss/download/" + ossId,
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'blob',
|
||||
headers: globalHeaders()
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
const isBlob = blobValidate(res.data);
|
||||
if (isBlob) {
|
||||
const blob = new Blob([res.data], { type: 'application/octet-stream' });
|
||||
FileSaver.saveAs(blob, decodeURIComponent(res.headers['download-filename'] as string));
|
||||
} else {
|
||||
this.printErrMsg(res.data);
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
const filename = response.headers.get('content-disposition')?.split('filename=')[1]?.replace(/"/g, '') || `file_${ossId}`;
|
||||
|
||||
FileSaver.saveAs(blob, decodeURIComponent(filename));
|
||||
downloadLoadingInstance.close();
|
||||
} catch (r) {
|
||||
console.error(r);
|
||||
@@ -32,6 +40,41 @@ export default {
|
||||
downloadLoadingInstance.close();
|
||||
}
|
||||
},
|
||||
|
||||
// 新增:安全下载文件方法
|
||||
async secureOss(ossId: string | number, fileName?: string) {
|
||||
console.log(ossId, fileName);
|
||||
downloadLoadingInstance = ElLoading.service({ text: '正在获取文件访问链接,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
|
||||
try {
|
||||
// 获取临时下载URL,避免Number()转换导致的大整数精度丢失
|
||||
// const { data: downloadUrl } = await getFileDownloadUrl(String(ossId));
|
||||
|
||||
const response = await request({
|
||||
url: "/resource/oss/download/" + ossId,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
const finalFileName = fileName || response.headers.get('content-disposition')?.split('filename=')[1]?.replace(/"/g, '') || `file_${ossId}`;
|
||||
|
||||
FileSaver.saveAs(blob, decodeURIComponent(finalFileName));
|
||||
downloadLoadingInstance.close();
|
||||
} catch (r) {
|
||||
console.error(r);
|
||||
ElMessage.error('下载文件出现错误,请联系管理员!');
|
||||
downloadLoadingInstance.close();
|
||||
}
|
||||
},
|
||||
|
||||
async zip(url: string, name: string) {
|
||||
url = baseURL + url;
|
||||
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
|
||||
@@ -40,6 +83,7 @@ export default {
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'blob',
|
||||
timeout: 300000 ,// 300秒超时
|
||||
headers: globalHeaders()
|
||||
});
|
||||
const isBlob = blobValidate(res.data);
|
||||
|
||||
@@ -6,7 +6,7 @@ import auth from './auth';
|
||||
// 预设动画
|
||||
import animate from '@/animate';
|
||||
|
||||
import { download as dl } from '@/utils/request';
|
||||
import { download as dl, downloadJson as dlJson } from '@/utils/request';
|
||||
import { useDict } from '@/utils/dict';
|
||||
import { getConfigKey, updateConfigByKey } from '@/api/system/config';
|
||||
import { parseTime, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi';
|
||||
@@ -34,6 +34,7 @@ export default function installPlugin(app: App) {
|
||||
app.config.globalProperties.getConfigKey = getConfigKey;
|
||||
app.config.globalProperties.updateConfigByKey = updateConfigByKey;
|
||||
app.config.globalProperties.download = dl;
|
||||
app.config.globalProperties.downloadJson = dlJson;
|
||||
app.config.globalProperties.parseTime = parseTime;
|
||||
app.config.globalProperties.handleTree = handleTree;
|
||||
app.config.globalProperties.addDateRange = addDateRange;
|
||||
|
||||
@@ -89,6 +89,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
{
|
||||
path: '/xljk',
|
||||
component: Layout,
|
||||
@@ -139,6 +140,12 @@ export const dynamicRoutes: RouteRecordRaw[] = [
|
||||
]
|
||||
}
|
||||
];
|
||||
=======
|
||||
];
|
||||
|
||||
// 动态路由,基于用户权限动态去加载
|
||||
export const dynamicRoutes: RouteRecordRaw[] = [];
|
||||
>>>>>>> e450a8ffe73adab129a8f940bff3316c0361aa7a
|
||||
|
||||
/**
|
||||
* 创建路由
|
||||
|
||||
@@ -28,7 +28,7 @@ axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 50000
|
||||
timeout: 300000 // 增加到5分钟,适合大数据量导出
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
@@ -205,7 +205,36 @@ export function download(url: string, params: any, fileName: string) {
|
||||
}
|
||||
],
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
responseType: 'blob'
|
||||
responseType: 'blob',
|
||||
timeout: 1200000 // 导出接口单独设置20分钟超时
|
||||
}).then(async (resp: any) => {
|
||||
const isLogin = blobValidate(resp);
|
||||
if (isLogin) {
|
||||
const blob = new Blob([resp]);
|
||||
FileSaver.saveAs(blob, fileName);
|
||||
} else {
|
||||
const blob = new Blob([resp]);
|
||||
const resText = await blob.text();
|
||||
const rspObj = JSON.parse(resText);
|
||||
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
|
||||
ElMessage.error(errMsg);
|
||||
}
|
||||
downloadLoadingInstance.close();
|
||||
}).catch((r: any) => {
|
||||
console.error(r);
|
||||
ElMessage.error('下载文件出现错误,请联系管理员!');
|
||||
downloadLoadingInstance.close();
|
||||
});
|
||||
}
|
||||
|
||||
// 通用下载方法(JSON格式入参)
|
||||
export function downloadJson(url: string, params: any, fileName: string) {
|
||||
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
|
||||
// prettier-ignore
|
||||
return service.post(url, params, {
|
||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||
responseType: 'blob',
|
||||
timeout: 1200000 // 导出接口单独设置20分钟超时
|
||||
}).then(async (resp: any) => {
|
||||
const isLogin = blobValidate(resp);
|
||||
if (isLogin) {
|
||||
|
||||
@@ -0,0 +1,748 @@
|
||||
<template>
|
||||
<div class="performance-indicators">
|
||||
<el-alert
|
||||
title="绩效指标设置"
|
||||
type="info"
|
||||
show-icon
|
||||
description="请按需要填写各类绩效指标,未使用的指标可以不填写。分值占比总和必须等于100%。"
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
|
||||
<!-- 产出指标 -->
|
||||
<el-card class="indicator-category" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="category-header">
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
<span>产出指标</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 数量指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">数量指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.quantity.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.quantity.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.quantity.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.quantity.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.quantity.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 质量指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">质量指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.quality.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.quality.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.quality.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.quality.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.quality.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 时效指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">时效指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.timeliness.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 成本指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">成本指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.cost.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.cost.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.cost.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.cost.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.cost.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 效益指标 -->
|
||||
<el-card class="indicator-category" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="category-header">
|
||||
<el-icon><Money /></el-icon>
|
||||
<span>效益指标</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 经济效益指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">经济效益指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.economic.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.economic.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.economic.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.economic.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.economic.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 社会效益指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">社会效益指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.social.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.social.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.social.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.social.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.social.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 生态效益指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">生态效益指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.ecological.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.ecological.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.ecological.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.ecological.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.ecological.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 满意度指标 -->
|
||||
<el-card class="indicator-category" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="category-header">
|
||||
<el-icon><Star /></el-icon>
|
||||
<span>满意度指标</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 服务对象满意度指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">服务对象满意度指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.service_satisfaction.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 分值占比统计 -->
|
||||
<el-alert
|
||||
:title="`当前分值占比总和:${totalWeight}%`"
|
||||
:type="totalWeight === 100 ? 'success' : totalWeight === 0 ? 'info' : 'warning'"
|
||||
show-icon
|
||||
:description="totalWeight === 100 ? '分值占比设置正确' : totalWeight === 0 ? '请填写绩效指标的分值占比' : '分值占比总和必须等于100%'"
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch } from 'vue'
|
||||
import { TrendCharts, Money, Star } from '@element-plus/icons-vue'
|
||||
|
||||
interface PerformanceIndicator {
|
||||
indicatorType: string
|
||||
indicatorTypeName: string
|
||||
goalName: string
|
||||
targetValue: string
|
||||
unit: string
|
||||
weightPercentage: number
|
||||
description: string
|
||||
measurementMethod: string
|
||||
dataSource: string
|
||||
isQuantitative: boolean
|
||||
sortOrder: number
|
||||
status: string
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PerformanceIndicators',
|
||||
components: {
|
||||
TrendCharts,
|
||||
Money,
|
||||
Star
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
// 创建默认指标对象
|
||||
const createIndicator = (type: string, typeName: string): PerformanceIndicator => ({
|
||||
indicatorType: type,
|
||||
indicatorTypeName: typeName,
|
||||
goalName: '',
|
||||
targetValue: '',
|
||||
unit: '',
|
||||
weightPercentage: 0,
|
||||
description: '',
|
||||
measurementMethod: '',
|
||||
dataSource: '',
|
||||
isQuantitative: true,
|
||||
sortOrder: 0,
|
||||
status: '1'
|
||||
})
|
||||
|
||||
// 8种固定的指标类型
|
||||
const indicators = ref({
|
||||
quantity: createIndicator('quantity', '数量指标'),
|
||||
quality: createIndicator('quality', '质量指标'),
|
||||
timeliness: createIndicator('timeliness', '时效指标'),
|
||||
cost: createIndicator('cost', '成本指标'),
|
||||
economic: createIndicator('economic', '经济效益指标'),
|
||||
social: createIndicator('social', '社会效益指标'),
|
||||
ecological: createIndicator('ecological', '生态效益指标'),
|
||||
service_satisfaction: createIndicator('service_satisfaction', '服务对象满意度指标')
|
||||
})
|
||||
|
||||
// 计算总分值占比
|
||||
const totalWeight = computed(() => {
|
||||
let total = 0
|
||||
Object.values(indicators.value).forEach(indicator => {
|
||||
total += indicator.weightPercentage || 0
|
||||
})
|
||||
return Math.round(total * 10) / 10
|
||||
})
|
||||
|
||||
// 收集有效的指标数据
|
||||
const collectValidIndicators = () => {
|
||||
const validIndicators: PerformanceIndicator[] = []
|
||||
let sortOrder = 0
|
||||
|
||||
Object.values(indicators.value).forEach(indicator => {
|
||||
// 只有填写了目标名称的指标才被认为是有效的
|
||||
if (indicator.goalName?.trim()) {
|
||||
validIndicators.push({
|
||||
...indicator,
|
||||
sortOrder: sortOrder++
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return validIndicators
|
||||
}
|
||||
|
||||
// 发出变化事件
|
||||
const emitChange = () => {
|
||||
const validIndicators = collectValidIndicators()
|
||||
emit('update:modelValue', validIndicators)
|
||||
emit('change', validIndicators)
|
||||
}
|
||||
|
||||
// 从传入的数据初始化指标
|
||||
const initializeFromData = (data: any[]) => {
|
||||
if (!data || data.length === 0) return
|
||||
|
||||
// 重置所有指标
|
||||
indicators.value = {
|
||||
quantity: createIndicator('quantity', '数量指标'),
|
||||
quality: createIndicator('quality', '质量指标'),
|
||||
timeliness: createIndicator('timeliness', '时效指标'),
|
||||
cost: createIndicator('cost', '成本指标'),
|
||||
economic: createIndicator('economic', '经济效益指标'),
|
||||
social: createIndicator('social', '社会效益指标'),
|
||||
ecological: createIndicator('ecological', '生态效益指标'),
|
||||
service_satisfaction: createIndicator('service_satisfaction', '服务对象满意度指标')
|
||||
}
|
||||
|
||||
// 将数据填入对应的指标类型
|
||||
data.forEach(item => {
|
||||
if (indicators.value[item.indicatorType]) {
|
||||
indicators.value[item.indicatorType] = { ...item }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 监听外部数据变化
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
initializeFromData(newValue)
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
// 校验方法
|
||||
const validate = () => {
|
||||
const validIndicators = collectValidIndicators()
|
||||
const errors = []
|
||||
|
||||
if (validIndicators.length === 0) {
|
||||
errors.push('请至少填写一个绩效指标')
|
||||
}
|
||||
|
||||
validIndicators.forEach((indicator, index) => {
|
||||
if (!indicator.goalName?.trim()) {
|
||||
errors.push(`第${index + 1}个指标未填写绩效目标名称`)
|
||||
}
|
||||
if (!indicator.targetValue?.trim()) {
|
||||
errors.push(`第${index + 1}个指标未填写目标值`)
|
||||
}
|
||||
if (!indicator.weightPercentage || indicator.weightPercentage <= 0) {
|
||||
errors.push(`第${index + 1}个指标未设置分值占比`)
|
||||
}
|
||||
})
|
||||
|
||||
if (Math.abs(totalWeight.value - 100) > 0.1) {
|
||||
errors.push(`分值占比总和必须等于100%,当前为${totalWeight.value}%`)
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return { valid: false, message: errors.join(';') }
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
// 获取所有有效指标数据
|
||||
const getAllIndicators = () => {
|
||||
return collectValidIndicators()
|
||||
}
|
||||
|
||||
return {
|
||||
indicators,
|
||||
totalWeight,
|
||||
emitChange,
|
||||
validate,
|
||||
getAllIndicators
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.performance-indicators {
|
||||
.category-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-item {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #409eff;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-title {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目预算申报详情</span>
|
||||
<el-tag :type="getStatusType(detail.approvalStatus)" class="ml10">
|
||||
{{ getStatusText(detail.approvalStatus) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 申报摘要 -->
|
||||
<application-summary :application="detail" />
|
||||
|
||||
<!-- 审批流程 -->
|
||||
<el-divider content-position="left">审批流程</el-divider>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in approvalTimeline"
|
||||
:key="index"
|
||||
:type="item.type"
|
||||
:timestamp="item.timestamp"
|
||||
>
|
||||
<div class="timeline-content">
|
||||
<h4>{{ item.title }}</h4>
|
||||
<p>{{ item.description }}</p>
|
||||
<span class="timeline-user" v-if="item.user">{{ item.user }}</span>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<!-- 操作日志 -->
|
||||
<el-divider content-position="left">操作日志</el-divider>
|
||||
<el-table :data="operationLogs" style="width: 100%">
|
||||
<el-table-column prop="operation" label="操作" width="120"/>
|
||||
<el-table-column prop="operator" label="操作人" width="100"/>
|
||||
<el-table-column prop="operateTime" label="操作时间" width="180">
|
||||
<template #default="scope">
|
||||
{{ parseTime(scope.row.operateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" show-overflow-tooltip/>
|
||||
</el-table>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="operation-buttons">
|
||||
<el-button
|
||||
v-if="detail.approvalStatus === 'draft'"
|
||||
type="primary"
|
||||
@click="handleEdit"
|
||||
v-hasPermi="['cw:application:edit']"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
v-if="detail.approvalStatus === 'draft'"
|
||||
type="success"
|
||||
@click="handleSubmit"
|
||||
v-hasPermi="['cw:application:submit']"
|
||||
>提交</el-button>
|
||||
<el-button
|
||||
v-if="detail.approvalStatus === 'pending'"
|
||||
type="warning"
|
||||
@click="handleWithdraw"
|
||||
v-hasPermi="['cw:application:withdraw']"
|
||||
>撤回</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CwApplicationDetail">
|
||||
import { ref, getCurrentInstance, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getProjectApplication, submitApplication, withdrawApplication } from "@/api/cw/projectApplication";
|
||||
import ApplicationSummary from "./summary.vue";
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
|
||||
console.log('=== detail.vue 组件开始加载 ===');
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const detail = ref({});
|
||||
const approvalTimeline = ref([]);
|
||||
const operationLogs = ref([]);
|
||||
|
||||
console.log('=== detail.vue 组件初始化完成 ===');
|
||||
console.log('route对象:', route);
|
||||
console.log('初始查询参数:', route.query);
|
||||
|
||||
/** 获取状态类型 */
|
||||
function getStatusType(status) {
|
||||
const statusMap = {
|
||||
'draft': 'info',
|
||||
'pending': 'warning',
|
||||
'approving': 'primary',
|
||||
'approved': 'success',
|
||||
'rejected': 'danger',
|
||||
'withdrawn': 'info',
|
||||
'cancelled': 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
}
|
||||
|
||||
/** 获取状态文本 */
|
||||
function getStatusText(status) {
|
||||
const statusMap = {
|
||||
'draft': '草稿',
|
||||
'pending': '待审批',
|
||||
'approving': '审批中',
|
||||
'approved': '已通过',
|
||||
'rejected': '已拒绝',
|
||||
'withdrawn': '已撤回',
|
||||
'cancelled': '已取消'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
}
|
||||
|
||||
/** 编辑按钮操作 */
|
||||
function handleEdit() {
|
||||
router.push("/cw/application/form?id=" + detail.value.id);
|
||||
}
|
||||
|
||||
/** 提交按钮操作 */
|
||||
function handleSubmit() {
|
||||
proxy.$modal.confirm('是否确认提交申报编号为"' + detail.value.applicationNo + '"的数据项?').then(function() {
|
||||
return submitApplication(detail.value.id);
|
||||
}).then(() => {
|
||||
getDetail();
|
||||
proxy.$modal.msgSuccess("提交成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/** 撤回按钮操作 */
|
||||
function handleWithdraw() {
|
||||
proxy.$modal.confirm('是否确认撤回申报编号为"' + detail.value.applicationNo + '"的数据项?').then(function() {
|
||||
return withdrawApplication(detail.value.id);
|
||||
}).then(() => {
|
||||
getDetail();
|
||||
proxy.$modal.msgSuccess("撤回成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/** 返回按钮操作 */
|
||||
function goBack() {
|
||||
router.push("/cw/application");
|
||||
}
|
||||
|
||||
/** 获取详情数据 */
|
||||
function getDetail() {
|
||||
const id = route.query.id;
|
||||
console.log('=== 开始获取详情数据 ===');
|
||||
console.log('URL参数中的ID:', id);
|
||||
console.log('完整query参数:', route.query);
|
||||
|
||||
if (id) {
|
||||
console.log('开始调用API:getProjectApplication, ID:', id);
|
||||
getProjectApplication(id).then(response => {
|
||||
console.log('=== API调用成功 ===');
|
||||
console.log('API响应:', response);
|
||||
console.log('响应代码:', response.code);
|
||||
console.log('响应数据:', response.data);
|
||||
|
||||
detail.value = response.data;
|
||||
|
||||
// 调试日志:查看返回的数据
|
||||
console.log('=== 项目申报详情数据 ===');
|
||||
console.log('完整数据:', response.data);
|
||||
console.log('绩效指标数据:', response.data.performanceIndicators);
|
||||
console.log('绩效指标数量:', response.data.performanceIndicators ? response.data.performanceIndicators.length : 0);
|
||||
|
||||
// 模拟审批流程数据
|
||||
approvalTimeline.value = [
|
||||
{
|
||||
type: 'success',
|
||||
timestamp: detail.value.createTime,
|
||||
title: '申报提交',
|
||||
description: '申报人提交预算申报',
|
||||
user: detail.value.applicantName
|
||||
}
|
||||
];
|
||||
|
||||
if (detail.value.submitTime) {
|
||||
approvalTimeline.value.push({
|
||||
type: 'primary',
|
||||
timestamp: detail.value.submitTime,
|
||||
title: '提交审批',
|
||||
description: '申报已提交,等待部门审批',
|
||||
user: detail.value.applicantName
|
||||
});
|
||||
}
|
||||
|
||||
if (detail.value.approveTime && detail.value.approvalStatus === 'approved') {
|
||||
approvalTimeline.value.push({
|
||||
type: 'success',
|
||||
timestamp: detail.value.approveTime,
|
||||
title: '审批通过',
|
||||
description: '所有审批节点已通过',
|
||||
user: '系统管理员'
|
||||
});
|
||||
}
|
||||
|
||||
// 模拟操作日志数据
|
||||
operationLogs.value = [
|
||||
{
|
||||
operation: '创建申报',
|
||||
operator: detail.value.applicantName,
|
||||
operateTime: detail.value.createTime,
|
||||
remark: '创建项目预算申报'
|
||||
}
|
||||
];
|
||||
|
||||
if (detail.value.submitTime) {
|
||||
operationLogs.value.push({
|
||||
operation: '提交审批',
|
||||
operator: detail.value.applicantName,
|
||||
operateTime: detail.value.submitTime,
|
||||
remark: '提交预算申报进入审批流程'
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('=== API调用失败 ===');
|
||||
console.error('错误信息:', error);
|
||||
console.error('错误响应:', error.response);
|
||||
proxy.$modal.msgError('获取申报详情失败:' + (error.message || '未知错误'));
|
||||
});
|
||||
} else {
|
||||
console.error('=== URL参数错误 ===');
|
||||
console.error('缺少ID参数,无法获取详情数据');
|
||||
proxy.$modal.msgError('缺少申报ID参数');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('=== detail.vue 页面已挂载 ===');
|
||||
console.log('当前路由:', route);
|
||||
console.log('当前路由路径:', route.path);
|
||||
console.log('当前路由查询参数:', route.query);
|
||||
getDetail();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.timeline-content h4 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.timeline-content p {
|
||||
margin: 0 0 8px 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.timeline-user {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.operation-buttons .el-button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,784 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 统计卡片区域 -->
|
||||
<el-row :gutter="20" class="stats-row">
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-primary">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ totalCount }}</div>
|
||||
<div class="stat-label">总申报数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-success">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Check /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ approvedCount }}</div>
|
||||
<div class="stat-label">已通过</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-warning">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ pendingCount }}</div>
|
||||
<div class="stat-label">审批中</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-info">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ draftCount }}</div>
|
||||
<div class="stat-label">草稿</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="button-card" v-show="showSearch">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">项目申报查询</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="queryParams" ref="queryRef" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报编号" prop="applicationNo">
|
||||
<el-input
|
||||
v-model="queryParams.applicationNo"
|
||||
placeholder="请输入申报编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="queryParams.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目负责人" prop="principal">
|
||||
<el-input
|
||||
v-model="queryParams.principal"
|
||||
placeholder="请输入项目负责人"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option label="草稿" value="draft"/>
|
||||
<el-option label="已提交" value="submitted"/>
|
||||
<el-option label="审批中" value="approving"/>
|
||||
<el-option label="已通过" value="approved"/>
|
||||
<el-option label="已拒绝" value="rejected"/>
|
||||
<el-option label="已退回" value="returned"/>
|
||||
<el-option label="已取消" value="cancelled"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预算年度" prop="year">
|
||||
<el-date-picker
|
||||
v-model="queryParams.year"
|
||||
type="year"
|
||||
placeholder="选择年份"
|
||||
value-format="YYYY"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card class="button-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">项目申报管理</span>
|
||||
<div class="toolbar-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['cw:application:add']"
|
||||
>新增申报</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
icon="Edit"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['cw:application:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['cw:application:remove']"
|
||||
>删除</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
icon="Upload"
|
||||
@click="handleSubmit"
|
||||
:disabled="multiple"
|
||||
v-hasPermi="['cw:application:submit']"
|
||||
>提交审批</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['cw:application:export']"
|
||||
>导出</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格区域 -->
|
||||
<el-card class="table-card">
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="applicationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
class="scroll-table"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申报编号" align="center" prop="applicationNo" min-width="140" />
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="项目负责人" align="center" prop="principal" min-width="100" />
|
||||
<el-table-column label="所属部门" align="center" prop="departmentName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="预算年度" align="center" prop="year" width="100" />
|
||||
<el-table-column label="项目总投资" align="center" prop="totalInvestment" min-width="120">
|
||||
<template #default="scope">
|
||||
<span class="amount-text">{{ formatAmount(scope.row.totalInvestment) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="getStatusTagType(scope.row.approvalStatus)"
|
||||
effect="light"
|
||||
>
|
||||
{{ getStatusText(scope.row.approvalStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="280" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="View"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['cw:application:query']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="编辑" placement="top" v-if="scope.row.approvalStatus === 'draft'">
|
||||
<el-button
|
||||
type="success"
|
||||
icon="Edit"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['cw:application:edit']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="提交审批" placement="top" v-if="scope.row.approvalStatus === 'draft'">
|
||||
<el-button
|
||||
type="warning"
|
||||
icon="Upload"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleSubmit(scope.row)"
|
||||
v-hasPermi="['cw:application:submit']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="撤回" placement="top" v-if="scope.row.approvalStatus === 'submitted'">
|
||||
<el-button
|
||||
type="info"
|
||||
icon="RefreshLeft"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleWithdraw(scope.row)"
|
||||
v-hasPermi="['cw:application:withdraw']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="['draft', 'cancelled'].includes(scope.row.approvalStatus)">
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['cw:application:remove']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改项目申报对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="1200px" append-to-body>
|
||||
<ApplicationForm ref="applicationFormRef" :form-data="form" @submit="submitForm" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详情对话框 -->
|
||||
<el-dialog title="项目申报详情" v-model="viewOpen" width="1200px" append-to-body>
|
||||
<ApplicationSummary :application-id="currentId" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="viewOpen = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProjectApplication, getProjectApplication, delProjectApplication, addProjectApplication, updateProjectApplication, submitApplication, getApplicationStatistics } from "@/api/cw/projectApplication"
|
||||
import ApplicationForm from "./form.vue"
|
||||
import ApplicationSummary from "./summary.vue"
|
||||
|
||||
export default {
|
||||
name: "ProjectApplication",
|
||||
components: {
|
||||
ApplicationForm,
|
||||
ApplicationSummary
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 项目申报表格数据
|
||||
applicationList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 是否显示查看对话框
|
||||
viewOpen: false,
|
||||
// 当前查看的ID
|
||||
currentId: null,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationNo: null,
|
||||
projectName: null,
|
||||
principal: null,
|
||||
approvalStatus: null,
|
||||
year: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {},
|
||||
// 统计数据
|
||||
totalCount: 0,
|
||||
approvedCount: 0,
|
||||
pendingCount: 0,
|
||||
draftCount: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getStatistics()
|
||||
},
|
||||
methods: {
|
||||
/** 查询项目申报列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listProjectApplication(this.queryParams).then(response => {
|
||||
this.applicationList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 获取统计数据 */
|
||||
getStatistics() {
|
||||
// 调用专门的统计API接口
|
||||
const year = this.queryParams.year ? parseInt(this.queryParams.year) : null
|
||||
getApplicationStatistics(year).then(response => {
|
||||
const data = response.data
|
||||
this.totalCount = data.totalCount || 0
|
||||
this.approvedCount = data.approvedCount || 0
|
||||
this.pendingCount = data.approvingCount || 0 // 注意:后端返回的是approvingCount
|
||||
this.draftCount = data.draftCount || 0
|
||||
}).catch(error => {
|
||||
console.error('获取统计数据失败:', error)
|
||||
// 如果API失败,使用本地计算作为后备方案
|
||||
this.totalCount = this.applicationList.length
|
||||
this.approvedCount = this.applicationList.filter(item => item.approvalStatus === 'approved').length
|
||||
this.pendingCount = this.applicationList.filter(item => item.approvalStatus === 'approving').length
|
||||
this.draftCount = this.applicationList.filter(item => item.approvalStatus === 'draft').length
|
||||
})
|
||||
},
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
id: null,
|
||||
applicationNo: null,
|
||||
projectName: null,
|
||||
principal: null,
|
||||
year: new Date().getFullYear(),
|
||||
departmentId: null,
|
||||
departmentName: null,
|
||||
projectCategory: null,
|
||||
implementationPeriod: null,
|
||||
projectBackground: null,
|
||||
projectObjective: null,
|
||||
projectScope: null,
|
||||
projectContent: null,
|
||||
implementationPlan: null,
|
||||
expectedResults: null,
|
||||
riskAssessment: null,
|
||||
totalPerformanceGoal: null,
|
||||
currentYearGoal: null,
|
||||
totalInvestment: 0,
|
||||
totalMunicipalFiscal: 0,
|
||||
totalSelfFunding: 0,
|
||||
totalOtherFunding: 0,
|
||||
currentYearTotal: 0,
|
||||
currentYearMunicipal: 0,
|
||||
currentYearSelf: 0,
|
||||
currentYearOther: 0,
|
||||
nextYearTotal: 0,
|
||||
nextYearMunicipal: 0,
|
||||
nextYearSelf: 0,
|
||||
nextYearOther: 0,
|
||||
futureYearTotal: 0,
|
||||
futureYearMunicipal: 0,
|
||||
futureYearSelf: 0,
|
||||
futureYearOther: 0,
|
||||
performanceIndicators: [],
|
||||
approvalStatus: 'draft'
|
||||
}
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
this.getStatistics() // 搜索时也更新统计数据
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryRef")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.id)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加项目申报"
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const id = row.id || this.ids
|
||||
getProjectApplication(id).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改项目申报"
|
||||
})
|
||||
},
|
||||
/** 查看详情 */
|
||||
handleView(row) {
|
||||
this.currentId = row.id
|
||||
this.viewOpen = true
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["applicationFormRef"].validate((valid) => {
|
||||
if (valid) {
|
||||
if (this.form.id != null) {
|
||||
updateProjectApplication(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
this.getStatistics() // 修改后更新统计
|
||||
})
|
||||
} else {
|
||||
addProjectApplication(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
this.getStatistics() // 新增后更新统计
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const ids = row.id || this.ids
|
||||
this.$modal.confirm('是否确认删除项目申报编号为"' + ids + '"的数据项?').then(function() {
|
||||
return delProjectApplication(ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.getStatistics() // 删除后更新统计
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 提交审批 */
|
||||
handleSubmit(row) {
|
||||
const applications = row?.applicationNo ? [row.applicationNo] : this.applicationList.filter(item => this.ids.includes(item.id)).map(item => item.applicationNo)
|
||||
|
||||
this.$modal.confirm('是否确认提交申报编号为"' + applications.join('", "') + '"的数据项?').then(() => {
|
||||
return submitApplication(row?.id || this.ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.getStatistics() // 提交后更新统计
|
||||
this.$modal.msgSuccess("提交成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 撤回申请 */
|
||||
handleWithdraw(row) {
|
||||
this.$modal.confirm('是否确认撤回该申报?').then(() => {
|
||||
// 调用撤回接口
|
||||
this.$modal.msgSuccess("撤回成功")
|
||||
this.getList()
|
||||
this.getStatistics() // 撤回后更新统计
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('cw/application/export', {
|
||||
...this.queryParams
|
||||
}, `application_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0.00'
|
||||
return parseFloat(amount).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
},
|
||||
/** 获取状态标签类型 */
|
||||
getStatusTagType(status) {
|
||||
const statusMap = {
|
||||
draft: 'info',
|
||||
submitted: 'warning',
|
||||
approving: 'warning',
|
||||
approved: 'success',
|
||||
rejected: 'danger',
|
||||
returned: 'warning',
|
||||
cancelled: 'info'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
},
|
||||
/** 获取状态文本 */
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
draft: '草稿',
|
||||
submitted: '已提交',
|
||||
approving: '审批中',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
returned: '已退回',
|
||||
cancelled: '已取消'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 继承后勤管理模块的样式风格 */
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.stats-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
height: 90px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 16px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.stat-icon {
|
||||
font-size: 28px;
|
||||
margin-right: 15px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-card-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-success {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-warning {
|
||||
background: linear-gradient(135deg, #ff9a56 0%, #ffad56 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-info {
|
||||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.button-card, .table-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
|
||||
:deep(.el-card__header) {
|
||||
border-bottom: 1px solid rgba(93, 135, 255, 0.1);
|
||||
background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.toolbar-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, rgb(93, 135, 255), rgb(73, 190, 255));
|
||||
border-radius: 2px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.scroll-table {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-table__header-wrapper) {
|
||||
.el-table__header {
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
.el-button {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 金额文本样式 */
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 0 0;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-container {
|
||||
padding: 10px;
|
||||
|
||||
.stats-row {
|
||||
.stat-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<div class="application-summary" v-loading="loading">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="summary-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目基本信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="申报编号">
|
||||
{{ currentApplication.applicationNo || '系统自动生成' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">
|
||||
{{ currentApplication.projectName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="项目负责人">
|
||||
{{ currentApplication.principal }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">
|
||||
{{ currentApplication.contactPhone }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预算年度">
|
||||
{{ currentApplication.year }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="项目周期">
|
||||
{{ formatDateRange(currentApplication.startDate, currentApplication.endDate) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 项目特征 -->
|
||||
<el-card class="summary-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目特征</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-tag :type="currentApplication.targetSme ? 'success' : 'info'">
|
||||
小微企业发展: {{ currentApplication.targetSme ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tag :type="currentApplication.targetMicroSme ? 'success' : 'info'">
|
||||
微小企业成长: {{ currentApplication.targetMicroSme ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tag :type="currentApplication.importEquipment ? 'success' : 'info'">
|
||||
进口设备: {{ currentApplication.importEquipment ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 项目内容 -->
|
||||
<el-card class="summary-card" v-if="hasProjectContent">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目内容</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="申报理由" v-if="currentApplication.applicationReason">
|
||||
<div class="content-text">{{ currentApplication.applicationReason }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="建设内容" v-if="currentApplication.constructionContent">
|
||||
<div class="content-text">{{ currentApplication.constructionContent }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总体绩效目标" v-if="currentApplication.totalPerformanceGoal">
|
||||
<div class="content-text">{{ currentApplication.totalPerformanceGoal }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当年完成目标" v-if="currentApplication.currentYearGoal">
|
||||
<div class="content-text">{{ currentApplication.currentYearGoal }}</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 资金预算汇总 -->
|
||||
<el-card class="summary-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>资金预算汇总</span>
|
||||
<el-tag type="warning" size="small">单位:万元</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 总体预算 -->
|
||||
<div class="budget-section">
|
||||
<h4>总体预算</h4>
|
||||
<el-row :gutter="20" class="budget-row">
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">总投资</div>
|
||||
<div class="budget-value highlight">{{ formatAmount(currentApplication.totalInvestment) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">市财政资金</div>
|
||||
<div class="budget-value">{{ formatAmount(currentApplication.totalMunicipalFiscal) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">单位自筹</div>
|
||||
<div class="budget-value">{{ formatAmount(currentApplication.totalSelfFunding) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">其他资金</div>
|
||||
<div class="budget-value">{{ formatAmount(currentApplication.totalOtherFunding) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 分年度预算 -->
|
||||
<div class="budget-section">
|
||||
<h4>分年度预算明细</h4>
|
||||
<el-table :data="budgetBreakdown" border>
|
||||
<el-table-column prop="year" label="年度" width="120" align="center"/>
|
||||
<el-table-column prop="total" label="总投资" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.total) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="municipal" label="市财政资金" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.municipal) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="self" label="单位自筹" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.self) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="other" label="其他资金" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.other) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 绩效指标设置 -->
|
||||
<el-card class="summary-card" v-if="hasPerformanceIndicators">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>绩效指标设置</span>
|
||||
<el-tag type="warning" size="small">分值占比总和:{{ performanceIndicatorTotal }}%</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="validPerformanceIndicators" border style="width: 100%">
|
||||
<el-table-column label="指标类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag>{{ scope.row.indicatorTypeName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="绩效目标名称" min-width="200">
|
||||
<template #default="scope">
|
||||
<div class="content-text">{{ scope.row.goalName }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目标值" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<div class="content-text">{{ scope.row.targetValue }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值占比" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag type="primary">{{ parseFloat(scope.row.weightPercentage) || 0 }}%</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="详细说明" min-width="150">
|
||||
<template #default="scope">
|
||||
<div class="content-text" v-if="scope.row.description">{{ scope.row.description }}</div>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<el-card class="summary-card" v-if="currentApplication.remark">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>备注信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="content-text">{{ currentApplication.remark }}</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { getProjectApplication } from '@/api/cw/projectApplication';
|
||||
|
||||
defineOptions({
|
||||
name: 'ApplicationSummary'
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
application: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
applicationId: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
// 内部数据状态
|
||||
const internalApplication = ref({});
|
||||
const loading = ref(false);
|
||||
|
||||
// 计算属性:优先使用传入的application,如果没有则使用内部获取的数据
|
||||
const currentApplication = computed(() => {
|
||||
// 如果直接传入了application对象且不为空,则使用它
|
||||
if (props.application && Object.keys(props.application).length > 0) {
|
||||
console.log('=== 使用传入的application对象 ===');
|
||||
console.log('application数据:', props.application);
|
||||
return props.application;
|
||||
}
|
||||
|
||||
// 否则使用内部获取的数据
|
||||
console.log('=== 使用内部获取的application数据 ===');
|
||||
console.log('internalApplication数据:', internalApplication.value);
|
||||
return internalApplication.value;
|
||||
});
|
||||
|
||||
// 获取申报详情数据
|
||||
const fetchApplicationData = async (id) => {
|
||||
if (!id) return;
|
||||
|
||||
console.log('=== ApplicationSummary 开始获取数据 ===');
|
||||
console.log('applicationId:', id);
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getProjectApplication(id);
|
||||
console.log('=== ApplicationSummary API调用成功 ===');
|
||||
console.log('API响应:', response);
|
||||
console.log('响应数据:', response.data);
|
||||
console.log('响应数据类型:', typeof response.data);
|
||||
|
||||
// 特别检查绩效指标字段
|
||||
if (response.data) {
|
||||
console.log('=== API响应中的绩效指标检查 ===');
|
||||
console.log('response.data.performanceIndicators:', response.data.performanceIndicators);
|
||||
console.log('performanceIndicators类型:', typeof response.data.performanceIndicators);
|
||||
console.log('是否为数组:', Array.isArray(response.data.performanceIndicators));
|
||||
|
||||
// 检查所有可能的绩效指标字段名
|
||||
console.log('可能的字段名检查:');
|
||||
console.log('- performanceIndicators:', response.data.performanceIndicators);
|
||||
console.log('- performance_indicators:', response.data.performance_indicators);
|
||||
console.log('- indicators:', response.data.indicators);
|
||||
console.log('- performanceIndicatorList:', response.data.performanceIndicatorList);
|
||||
|
||||
// 列出所有字段
|
||||
console.log('响应数据的所有字段:', Object.keys(response.data));
|
||||
}
|
||||
|
||||
internalApplication.value = response.data || {};
|
||||
} catch (error) {
|
||||
console.error('=== ApplicationSummary API调用失败 ===');
|
||||
console.error('错误信息:', error);
|
||||
internalApplication.value = {};
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听applicationId变化
|
||||
watch(() => props.applicationId, (newId) => {
|
||||
if (newId && (!props.application || Object.keys(props.application).length === 0)) {
|
||||
fetchApplicationData(newId);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 组件挂载时检查是否需要获取数据
|
||||
onMounted(() => {
|
||||
console.log('=== ApplicationSummary 组件挂载 ===');
|
||||
console.log('props.application:', props.application);
|
||||
console.log('props.applicationId:', props.applicationId);
|
||||
|
||||
// 如果没有传入application但有applicationId,则获取数据
|
||||
if (props.applicationId && (!props.application || Object.keys(props.application).length === 0)) {
|
||||
fetchApplicationData(props.applicationId);
|
||||
}
|
||||
});
|
||||
|
||||
// 判断是否有项目内容
|
||||
const hasProjectContent = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
return app.applicationReason ||
|
||||
app.constructionContent ||
|
||||
app.totalPerformanceGoal ||
|
||||
app.currentYearGoal;
|
||||
});
|
||||
|
||||
// 判断是否有绩效指标
|
||||
const hasPerformanceIndicators = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
console.log('=== 绩效指标详细检查 ===');
|
||||
console.log('完整app对象:', app);
|
||||
console.log('app.performanceIndicators类型:', typeof app.performanceIndicators);
|
||||
console.log('app.performanceIndicators值:', app.performanceIndicators);
|
||||
console.log('是否为数组:', Array.isArray(app.performanceIndicators));
|
||||
|
||||
if (app.performanceIndicators) {
|
||||
console.log('绩效指标数组长度:', app.performanceIndicators.length);
|
||||
console.log('绩效指标数组内容:', JSON.stringify(app.performanceIndicators, null, 2));
|
||||
} else {
|
||||
console.log('绩效指标数据为空或未定义');
|
||||
}
|
||||
|
||||
const hasIndicators = app.performanceIndicators && app.performanceIndicators.length > 0;
|
||||
console.log('最终判断 - 是否有绩效指标:', hasIndicators);
|
||||
return hasIndicators;
|
||||
});
|
||||
|
||||
// 计算绩效指标总分值占比
|
||||
const performanceIndicatorTotal = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
if (!app.performanceIndicators || app.performanceIndicators.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const total = app.performanceIndicators.reduce((sum, indicator) => {
|
||||
const weight = parseFloat(indicator.weightPercentage) || 0;
|
||||
return sum + weight;
|
||||
}, 0);
|
||||
return Math.round(total * 100) / 100; // 保留两位小数
|
||||
});
|
||||
|
||||
// 过滤掉无效的绩效指标(分值占比为0的)
|
||||
const validPerformanceIndicators = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
console.log('=== validPerformanceIndicators 计算 ===');
|
||||
console.log('app.performanceIndicators:', app.performanceIndicators);
|
||||
|
||||
if (!app.performanceIndicators) {
|
||||
console.log('没有绩效指标数据,返回空数组');
|
||||
return [];
|
||||
}
|
||||
|
||||
const validIndicators = app.performanceIndicators.filter(indicator => {
|
||||
const weight = parseFloat(indicator.weightPercentage) || 0;
|
||||
console.log(`指标 ${indicator.goalName}: 权重=${indicator.weightPercentage}, 解析后权重=${weight}, 是否有效=${weight > 0}`);
|
||||
return weight > 0;
|
||||
});
|
||||
|
||||
console.log('有效绩效指标数量:', validIndicators.length);
|
||||
console.log('有效绩效指标详情:', validIndicators);
|
||||
return validIndicators;
|
||||
});
|
||||
|
||||
// 分年度预算明细
|
||||
const budgetBreakdown = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
const currentYear = app.year || new Date().getFullYear();
|
||||
console.log('=== 预算数据检查 ===');
|
||||
console.log('application数据:', app);
|
||||
console.log('当年预算字段:', {
|
||||
currentYearTotal: app.currentYearTotal,
|
||||
currentYearMunicipal: app.currentYearMunicipal,
|
||||
currentYearSelf: app.currentYearSelf,
|
||||
currentYearOther: app.currentYearOther
|
||||
});
|
||||
return [
|
||||
{
|
||||
year: `${currentYear}年`,
|
||||
total: app.currentYearTotal || 0,
|
||||
municipal: app.currentYearMunicipal || 0,
|
||||
self: app.currentYearSelf || 0,
|
||||
other: app.currentYearOther || 0
|
||||
},
|
||||
{
|
||||
year: `${parseInt(currentYear) + 1}年`,
|
||||
total: app.nextYearTotal || 0,
|
||||
municipal: app.nextYearMunicipal || 0,
|
||||
self: app.nextYearSelf || 0,
|
||||
other: app.nextYearOther || 0
|
||||
},
|
||||
{
|
||||
year: '以后年度',
|
||||
total: app.futureYearTotal || 0,
|
||||
municipal: app.futureYearMunicipal || 0,
|
||||
self: app.futureYearSelf || 0,
|
||||
other: app.futureYearOther || 0
|
||||
}
|
||||
].filter(item => item.total > 0); // 只显示有预算的年度
|
||||
});
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount) => {
|
||||
if (!amount || amount === 0) return '0.00';
|
||||
return Number(amount).toFixed(2);
|
||||
};
|
||||
|
||||
// 格式化日期范围
|
||||
const formatDateRange = (startDate, endDate) => {
|
||||
if (!startDate && !endDate) return '未设置';
|
||||
if (!startDate) return `截止 ${endDate}`;
|
||||
if (!endDate) return `${startDate} 开始`;
|
||||
return `${startDate} 至 ${endDate}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.application-summary {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.summary-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.budget-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.budget-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.budget-section h4 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
border-left: 4px solid #409eff;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.budget-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.budget-item {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.budget-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.budget-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.budget-value.highlight {
|
||||
color: #409eff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-table td, .el-table th) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table .cell) {
|
||||
padding: 0 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,869 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报编号" prop="applicationNo">
|
||||
<el-input v-model="queryParams.applicationNo" placeholder="请输入申报编号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报人" prop="applicantName">
|
||||
<el-input v-model="queryParams.applicantName" placeholder="请输入申报人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<template v-if="cw_approval_status && cw_approval_status.length > 0">
|
||||
<el-option v-for="dict in cw_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批人" prop="approverName">
|
||||
<el-input v-model="queryParams.approverName" placeholder="请输入审批人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="提交时间">
|
||||
<el-date-picker
|
||||
v-model="daterangeSubmitTime"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@change="handleQuery"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 快捷操作按钮 -->
|
||||
<el-row :gutter="10" class="mb-[10px]">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showPendingApprovals">
|
||||
<el-icon class="quick-icon"><Clock /></el-icon>
|
||||
<span>待我审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showMyApprovals">
|
||||
<el-icon class="quick-icon"><User /></el-icon>
|
||||
<span>我已审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showDepartmentApprovals">
|
||||
<el-icon class="quick-icon"><OfficeBuilding /></el-icon>
|
||||
<span>部门审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showApprovalStatistics">
|
||||
<el-icon class="quick-icon"><DataAnalysis /></el-icon>
|
||||
<span>审批统计</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Check"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchApprove"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
>批量通过</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Close"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchReject"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
>批量拒绝</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['cw:approvalRecord:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="approvalList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申报编号" align="center" prop="applicationNo" width="140" show-overflow-tooltip />
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="申报人" align="center" prop="applicantName" width="100" />
|
||||
<el-table-column label="申报部门" align="center" prop="deptName" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="总投资(万元)" align="center" prop="totalInvestment" width="120">
|
||||
<template #default="scope">
|
||||
<span class="amount-text">{{ formatAmount(scope.row.totalInvestment) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批人" align="center" prop="currentApproverName" width="120" />
|
||||
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="cw_approval_status" :value="scope.row.approvalStatus" v-if="cw_approval_status && cw_approval_status.length > 0" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="submitTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.submitTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.approvalTime ? parseTime(scope.row.approvalTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="handleView(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 待审批状态可以进行审批操作 -->
|
||||
<template v-if="scope.row.approvalStatus === 'submitted' || scope.row.approvalStatus === 'approving'">
|
||||
<el-tooltip content="通过" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="拒绝" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Close"
|
||||
@click="handleReject(scope.row)"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="退回" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
icon="RefreshLeft"
|
||||
@click="handleReturn(scope.row)"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 所有状态都可以查看审批记录 -->
|
||||
<el-tooltip content="审批记录" placement="top">
|
||||
<el-button link type="primary" icon="Document" @click="handleApprovalHistory(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@update:page="queryParams.pageNum = $event"
|
||||
@update:limit="queryParams.pageSize = $event"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<el-dialog
|
||||
v-model="approvalDialog.visible"
|
||||
:title="approvalDialog.title"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="closeApprovalDialog"
|
||||
>
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="120px">
|
||||
<!-- 申报信息展示 -->
|
||||
<el-form-item label="申报信息">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="申报编号">{{ currentApplication.applicationNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">{{ currentApplication.projectName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申报人">{{ currentApplication.applicantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申报部门">{{ currentApplication.deptName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总投资">{{ formatAmount(currentApplication.totalInvestment) }} 万元</el-descriptions-item>
|
||||
<el-descriptions-item label="预算年度">{{ currentApplication.year }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 审批决定 -->
|
||||
<el-form-item label="审批决定" prop="approvalResult">
|
||||
<el-radio-group v-model="approvalForm.approvalResult">
|
||||
<el-radio value="approved">
|
||||
<el-icon class="approve-icon"><Check /></el-icon>
|
||||
通过
|
||||
</el-radio>
|
||||
<el-radio value="rejected">
|
||||
<el-icon class="reject-icon"><Close /></el-icon>
|
||||
拒绝
|
||||
</el-radio>
|
||||
<el-radio value="returned">
|
||||
<el-icon class="return-icon"><RefreshLeft /></el-icon>
|
||||
退回
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 审批意见 -->
|
||||
<el-form-item label="审批意见" prop="approvalComment">
|
||||
<el-input
|
||||
v-model="approvalForm.approvalComment"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审批意见(必填)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 下一审批人(如果需要) -->
|
||||
<el-form-item label="下一审批人" prop="nextApprover" v-if="showNextApprover">
|
||||
<el-select v-model="approvalForm.nextApprover" placeholder="请选择下一审批人" style="width: 100%">
|
||||
<el-option
|
||||
v-for="approver in approverList"
|
||||
:key="approver.userId"
|
||||
:label="approver.userName"
|
||||
:value="approver.userId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeApprovalDialog">取消</el-button>
|
||||
<el-button type="primary" @click="submitApproval" :loading="approvalSubmitting">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="detailDialog.visible"
|
||||
:title="detailDialog.title"
|
||||
width="90%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<application-summary :application="currentApplication" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button
|
||||
v-if="canApprove(currentApplication)"
|
||||
type="primary"
|
||||
@click="approveFromDetail"
|
||||
>审批</el-button>
|
||||
<el-button @click="detailDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批记录对话框 -->
|
||||
<el-dialog
|
||||
v-model="historyDialog.visible"
|
||||
title="审批记录"
|
||||
width="900px"
|
||||
append-to-body
|
||||
>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(record, index) in approvalHistory"
|
||||
:key="index"
|
||||
:icon="getHistoryIcon(record.approvalResult)"
|
||||
:type="getHistoryType(record.approvalResult)"
|
||||
:timestamp="parseTime(record.approvalTime, '{y}-{m}-{d} {h}:{i}')"
|
||||
placement="top"
|
||||
>
|
||||
<el-card shadow="hover">
|
||||
<div class="history-header">
|
||||
<span class="approver-name">{{ record.approverName }}</span>
|
||||
<el-tag :type="getStatusTagType(record.approvalResult)">
|
||||
{{ getApprovalResultText(record.approvalResult) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="history-content" v-if="record.approvalComment">
|
||||
<p>{{ record.approvalComment }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="historyDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listApprovalRecord,
|
||||
getApprovalRecord,
|
||||
approveApplication,
|
||||
batchApproveApplication,
|
||||
getApprovalHistory,
|
||||
getPendingApprovals,
|
||||
getMyApprovals,
|
||||
getDepartmentApprovals,
|
||||
getAvailableApprovers,
|
||||
exportApprovalRecord
|
||||
} from "@/api/cw/approvalRecord";
|
||||
import { useDict } from "@/utils/dict";
|
||||
import ApplicationSummary from "../application/summary.vue";
|
||||
|
||||
export default {
|
||||
name: 'CwApprovalRecord',
|
||||
components: {
|
||||
ApplicationSummary
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 审批列表
|
||||
approvalList: [],
|
||||
// 时间范围
|
||||
daterangeSubmitTime: [],
|
||||
// 动画配置
|
||||
animate: {
|
||||
searchAnimate: {
|
||||
enter: 'animate__animated animate__fadeIn',
|
||||
leave: 'animate__animated animate__fadeOut'
|
||||
}
|
||||
},
|
||||
// 字典数据
|
||||
cw_approval_status: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationNo: '',
|
||||
projectName: '',
|
||||
applicantName: '',
|
||||
approvalStatus: '',
|
||||
approverName: '',
|
||||
submitTimeBegin: '',
|
||||
submitTimeEnd: ''
|
||||
},
|
||||
// 审批对话框
|
||||
approvalDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 详情对话框
|
||||
detailDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 历史记录对话框
|
||||
historyDialog: {
|
||||
visible: false
|
||||
},
|
||||
// 当前申报信息
|
||||
currentApplication: {},
|
||||
// 审批表单
|
||||
approvalForm: {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
},
|
||||
// 审批表单验证
|
||||
approvalRules: {
|
||||
approvalResult: [
|
||||
{ required: true, message: '请选择审批结果', trigger: 'change' }
|
||||
],
|
||||
approvalComment: [
|
||||
{ required: true, message: '请输入审批意见', trigger: 'blur' },
|
||||
{ min: 5, max: 500, message: '审批意见长度在 5 到 500 个字符', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
// 审批提交状态
|
||||
approvalSubmitting: false,
|
||||
// 审批人列表
|
||||
approverList: [],
|
||||
// 审批历史
|
||||
approvalHistory: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 是否显示下一审批人选择
|
||||
showNextApprover() {
|
||||
return this.approvalForm.approvalResult === 'approved';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDictData();
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 加载字典数据 */
|
||||
async loadDictData() {
|
||||
try {
|
||||
const dictData = useDict('cw_approval_status');
|
||||
// 监听字典数据变化
|
||||
this.$watch(() => dictData.cw_approval_status, (newVal) => {
|
||||
this.cw_approval_status = newVal || [];
|
||||
}, { immediate: true });
|
||||
} catch (error) {
|
||||
console.error('加载审批状态字典数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/** 查询审批列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 处理时间范围
|
||||
this.queryParams.submitTimeBegin = this.daterangeSubmitTime[0] || '';
|
||||
this.queryParams.submitTimeEnd = this.daterangeSubmitTime[1] || '';
|
||||
|
||||
listApprovalRecord(this.queryParams).then(response => {
|
||||
this.approvalList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
console.error('获取审批列表失败:', error);
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.daterangeSubmitTime = [];
|
||||
this.$refs.queryFormRef.resetFields();
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.id);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
},
|
||||
|
||||
/** 快捷操作 */
|
||||
showPendingApprovals() {
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
approvalStatus: 'submitted',
|
||||
currentApprover: 'current'
|
||||
};
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
showMyApprovals() {
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
approverName: 'current'
|
||||
};
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
showDepartmentApprovals() {
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
deptId: 'current'
|
||||
};
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
showApprovalStatistics() {
|
||||
this.$router.push('/cw/approvalRecord/statistics');
|
||||
},
|
||||
|
||||
/** 查看详情 */
|
||||
handleView(row) {
|
||||
const id = row.id || row.applicationId;
|
||||
getApprovalRecord(id).then(response => {
|
||||
this.currentApplication = response.data;
|
||||
this.detailDialog.visible = true;
|
||||
this.detailDialog.title = "申报详情";
|
||||
}).catch(error => {
|
||||
console.error('获取详情失败:', error);
|
||||
this.$modal.msgError('获取详情失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 从详情页审批 */
|
||||
approveFromDetail() {
|
||||
this.detailDialog.visible = false;
|
||||
this.handleApprove(this.currentApplication);
|
||||
},
|
||||
|
||||
/** 单个审批通过 */
|
||||
handleApprove(row) {
|
||||
this.openApprovalDialog('approved', row, '审批通过');
|
||||
},
|
||||
|
||||
/** 单个审批拒绝 */
|
||||
handleReject(row) {
|
||||
this.openApprovalDialog('rejected', row, '审批拒绝');
|
||||
},
|
||||
|
||||
/** 单个审批退回 */
|
||||
handleReturn(row) {
|
||||
this.openApprovalDialog('returned', row, '审批退回');
|
||||
},
|
||||
|
||||
/** 批量审批通过 */
|
||||
handleBatchApprove() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgWarning('请选择要审批的记录');
|
||||
return;
|
||||
}
|
||||
this.openBatchApprovalDialog('approved', '批量审批通过');
|
||||
},
|
||||
|
||||
/** 批量审批拒绝 */
|
||||
handleBatchReject() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgWarning('请选择要审批的记录');
|
||||
return;
|
||||
}
|
||||
this.openBatchApprovalDialog('rejected', '批量审批拒绝');
|
||||
},
|
||||
|
||||
/** 打开审批对话框 */
|
||||
openApprovalDialog(result, row, title) {
|
||||
this.currentApplication = row;
|
||||
this.approvalForm = {
|
||||
applicationId: row.id,
|
||||
approvalResult: result,
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
};
|
||||
|
||||
// 如果是通过状态,加载可选审批人
|
||||
if (result === 'approved') {
|
||||
this.loadAvailableApprovers(row.id);
|
||||
}
|
||||
|
||||
this.approvalDialog.visible = true;
|
||||
this.approvalDialog.title = title;
|
||||
},
|
||||
|
||||
/** 打开批量审批对话框 */
|
||||
openBatchApprovalDialog(result, title) {
|
||||
this.approvalForm = {
|
||||
applicationIds: this.ids,
|
||||
approvalResult: result,
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
};
|
||||
this.approvalDialog.visible = true;
|
||||
this.approvalDialog.title = title;
|
||||
},
|
||||
|
||||
/** 加载可选审批人 */
|
||||
loadAvailableApprovers(applicationId) {
|
||||
getAvailableApprovers(applicationId).then(response => {
|
||||
this.approverList = response.data || [];
|
||||
}).catch(error => {
|
||||
console.error('获取审批人列表失败:', error);
|
||||
this.approverList = [];
|
||||
});
|
||||
},
|
||||
|
||||
/** 关闭审批对话框 */
|
||||
closeApprovalDialog() {
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalForm = {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
};
|
||||
this.approverList = [];
|
||||
if (this.$refs.approvalFormRef) {
|
||||
this.$refs.approvalFormRef.resetFields();
|
||||
}
|
||||
},
|
||||
|
||||
/** 提交审批 */
|
||||
submitApproval() {
|
||||
this.$refs.approvalFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
this.approvalSubmitting = true;
|
||||
|
||||
const apiCall = this.approvalForm.applicationIds
|
||||
? batchApproveApplication(this.approvalForm)
|
||||
: approveApplication(this.approvalForm);
|
||||
|
||||
apiCall.then(response => {
|
||||
this.$modal.msgSuccess("审批成功");
|
||||
this.closeApprovalDialog();
|
||||
this.getList();
|
||||
}).catch(error => {
|
||||
console.error('审批失败:', error);
|
||||
this.$modal.msgError('审批失败');
|
||||
}).finally(() => {
|
||||
this.approvalSubmitting = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** 查看审批历史 */
|
||||
handleApprovalHistory(row) {
|
||||
getApprovalHistory(row.id || row.applicationId).then(response => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
}).catch(error => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
// 显示模拟数据
|
||||
this.approvalHistory = [
|
||||
{
|
||||
approverName: '系统',
|
||||
approvalResult: 'submitted',
|
||||
approvalComment: '申报材料已提交,等待审批',
|
||||
approvalTime: new Date().toISOString(),
|
||||
stepName: '申报提交',
|
||||
stepOrder: 1
|
||||
}
|
||||
];
|
||||
this.historyDialog.visible = true;
|
||||
});
|
||||
},
|
||||
|
||||
/** 导出 */
|
||||
handleExport() {
|
||||
exportApprovalRecord(this.queryParams).then(() => {
|
||||
this.$modal.msgSuccess('导出成功');
|
||||
}).catch(error => {
|
||||
console.error('导出失败:', error);
|
||||
this.$modal.msgError('导出失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 判断是否可以审批 */
|
||||
canApprove(row) {
|
||||
return row.approvalStatus === 'submitted' || row.approvalStatus === 'approving';
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
return amount ? amount.toFixed(2) : '0.00';
|
||||
},
|
||||
|
||||
/** 获取历史记录图标 */
|
||||
getHistoryIcon(result) {
|
||||
const iconMap = {
|
||||
'submitted': 'Upload',
|
||||
'approved': 'Check',
|
||||
'rejected': 'Close',
|
||||
'returned': 'RefreshLeft'
|
||||
};
|
||||
return iconMap[result] || 'Document';
|
||||
},
|
||||
|
||||
/** 获取历史记录类型 */
|
||||
getHistoryType(result) {
|
||||
const typeMap = {
|
||||
'submitted': 'primary',
|
||||
'approved': 'success',
|
||||
'rejected': 'danger',
|
||||
'returned': 'warning'
|
||||
};
|
||||
return typeMap[result] || 'info';
|
||||
},
|
||||
|
||||
/** 获取状态标签类型 */
|
||||
getStatusTagType(status) {
|
||||
const statusMap = {
|
||||
'submitted': 'warning',
|
||||
'approved': 'success',
|
||||
'rejected': 'danger',
|
||||
'returned': 'warning'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
|
||||
/** 获取审批结果文本 */
|
||||
getApprovalResultText(result) {
|
||||
const textMap = {
|
||||
'submitted': '已提交',
|
||||
'approved': '通过',
|
||||
'rejected': '拒绝',
|
||||
'returned': '退回'
|
||||
};
|
||||
return textMap[result] || result;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: calc(100vh - 84px);
|
||||
}
|
||||
|
||||
// 快捷操作卡片
|
||||
.quick-action-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
border-color: #409EFF;
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 28px;
|
||||
color: #409EFF;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表格卡片
|
||||
.button-card.table-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
// 金额文本
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: #E6A23C;
|
||||
}
|
||||
|
||||
// 审批对话框样式
|
||||
.approve-icon {
|
||||
color: #67C23A;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.reject-icon {
|
||||
color: #F56C6C;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.return-icon {
|
||||
color: #E6A23C;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
// 审批历史样式
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.approver-name {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
.history-content {
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.quick-action {
|
||||
padding: 15px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div class="config-detail">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="detail-card mb-4">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="配置名称">
|
||||
{{ config.configName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批类型">
|
||||
<el-tag :type="getApprovalTypeTag(config.approvalType)">
|
||||
{{ getApprovalTypeText(config.approvalType) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="适用范围">
|
||||
{{ getScopeText(config.applicableScope) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="config.status === '1' ? 'success' : 'danger'">
|
||||
{{ config.status === '1' ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="排序">
|
||||
{{ config.orderNum }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ parseTime(config.createTime, '{y}-{m}-{d} {h}:{i}') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div v-if="config.description" class="mt-4">
|
||||
<h4 class="section-title">配置描述</h4>
|
||||
<p class="description-text">{{ config.description }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 审批流程 -->
|
||||
<el-card class="detail-card" v-if="config.steps && config.steps.length > 0">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon><Share /></el-icon>
|
||||
<span>审批流程</span>
|
||||
<el-tag type="info" size="small">共 {{ config.steps.length }} 个步骤</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-steps :active="config.steps.length" finish-status="success" align-center>
|
||||
<el-step
|
||||
v-for="(step, index) in config.steps"
|
||||
:key="index"
|
||||
:title="step.stepName"
|
||||
:description="`${getApprovalModeText(step.approvalMode)} | ${step.timeoutHours}小时`"
|
||||
/>
|
||||
</el-steps>
|
||||
|
||||
<div class="steps-detail mt-6">
|
||||
<div
|
||||
v-for="(step, index) in config.steps"
|
||||
:key="index"
|
||||
class="step-detail-item"
|
||||
>
|
||||
<el-card shadow="hover" class="step-detail-card">
|
||||
<template #header>
|
||||
<div class="step-header">
|
||||
<span class="step-number">步骤 {{ index + 1 }}</span>
|
||||
<span class="step-name">{{ step.stepName }}</span>
|
||||
<el-tag :type="getModeTagType(step.approvalMode)" size="small">
|
||||
{{ getApprovalModeText(step.approvalMode) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<div class="step-info">
|
||||
<label class="info-label">审批方式:</label>
|
||||
<span>{{ getApprovalModeText(step.approvalMode) }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="step-info">
|
||||
<label class="info-label">超时时间:</label>
|
||||
<span>{{ step.timeoutHours }} 小时</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="step-info">
|
||||
<label class="info-label">审批人数:</label>
|
||||
<span>{{ step.approvers ? step.approvers.length : 0 }} 人</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="approvers-section mt-4" v-if="step.approvers && step.approvers.length > 0">
|
||||
<label class="info-label">审批人员:</label>
|
||||
<div class="approvers-list">
|
||||
<el-tag
|
||||
v-for="(approver, approverIndex) in step.approverNames"
|
||||
:key="approverIndex"
|
||||
class="approver-tag"
|
||||
>
|
||||
{{ approver }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="config.approvalType === 'custom'" class="custom-approver-notice">
|
||||
<el-alert
|
||||
title="自选审批人"
|
||||
description="申报人在提交时自行选择审批人"
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 无流程步骤时的提示 -->
|
||||
<el-card class="detail-card" v-else>
|
||||
<el-empty description="暂无配置的审批流程步骤" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ApproverConfigDetail',
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取审批类型标签 */
|
||||
getApprovalTypeTag(type) {
|
||||
const typeMap = {
|
||||
'fixed': 'success',
|
||||
'role': 'warning',
|
||||
'dept': 'info',
|
||||
'custom': 'primary'
|
||||
};
|
||||
return typeMap[type] || 'info';
|
||||
},
|
||||
|
||||
/** 获取审批类型文本 */
|
||||
getApprovalTypeText(type) {
|
||||
const textMap = {
|
||||
'fixed': '固定审批人',
|
||||
'role': '角色审批',
|
||||
'dept': '部门审批',
|
||||
'custom': '自选审批人'
|
||||
};
|
||||
return textMap[type] || type;
|
||||
},
|
||||
|
||||
/** 获取适用范围文本 */
|
||||
getScopeText(scope) {
|
||||
const scopeMap = {
|
||||
'all': '全部',
|
||||
'dept': '部门',
|
||||
'project_type': '项目类型',
|
||||
'amount_range': '金额范围'
|
||||
};
|
||||
return scopeMap[scope] || scope;
|
||||
},
|
||||
|
||||
/** 获取审批方式文本 */
|
||||
getApprovalModeText(mode) {
|
||||
const modeMap = {
|
||||
'single': '单人审批',
|
||||
'multi_sign': '多人会签',
|
||||
'multi_or': '多人或签'
|
||||
};
|
||||
return modeMap[mode] || mode;
|
||||
},
|
||||
|
||||
/** 获取审批方式标签类型 */
|
||||
getModeTagType(mode) {
|
||||
const typeMap = {
|
||||
'single': 'primary',
|
||||
'multi_sign': 'success',
|
||||
'multi_or': 'warning'
|
||||
};
|
||||
return typeMap[mode] || 'info';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.config-detail {
|
||||
.detail-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.steps-detail {
|
||||
.step-detail-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.step-detail-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.step-number {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
margin-right: 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.approvers-section {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.approvers-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.approver-tag {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-approver-notice {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mt-6 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
Loaded 100 of 662 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user