hdc,绩效和工资单

This commit is contained in:
何德超 2025-07-21 10:04:56 +08:00
parent ba041b3f3a
commit a3b11c9971
18 changed files with 1305 additions and 165 deletions

View File

@ -40,7 +40,7 @@ export function createHealthRecord(data: HealthRecord) {
return request({
url: '/health-record',
method: 'post',
data
data,
})
}
@ -49,7 +49,7 @@ export function getHealthRecordList(params: HealthRecordListParams) {
return request<HealthRecordListResponse>({
url: '/health-record/list',
method: 'get',
params
params,
})
}
@ -57,7 +57,7 @@ export function getHealthRecordList(params: HealthRecordListParams) {
export function getHealthRecordDetail(id: string) {
return request<HealthRecord>({
url: `/health-record/detail/${id}`,
method: 'get'
method: 'get',
})
}
@ -66,7 +66,7 @@ export function updateHealthRecord(id: string, data: HealthRecord) {
return request({
url: `/health-record/${id}`,
method: 'put',
data
data,
})
}
@ -74,7 +74,7 @@ export function updateHealthRecord(id: string, data: HealthRecord) {
export function deleteHealthRecord(id: string) {
return request({
url: `/health-record/${id}`,
method: 'delete'
method: 'delete',
})
}
@ -89,8 +89,8 @@ export function uploadHealthReport(file: File, recordId: string) {
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
'Content-Type': 'multipart/form-data',
},
})
}
@ -99,7 +99,7 @@ export function downloadHealthReport(fileId: string) {
return request({
url: `/health-record/download-report/${fileId}`,
method: 'get',
responseType: 'blob'
responseType: 'blob',
})
}
@ -107,7 +107,7 @@ export function downloadHealthReport(fileId: string) {
export function getEmployeeHealthHistory(employeeId: string) {
return request<HealthRecord[]>({
url: `/health-record/employee/${employeeId}`,
method: 'get'
method: 'get',
})
}
@ -117,7 +117,7 @@ export function exportHealthRecords(params: HealthRecordListParams) {
url: '/health-record/export',
method: 'get',
params,
responseType: 'blob'
responseType: 'blob',
})
}
@ -131,6 +131,6 @@ export function scheduleHealthCheck(data: {
return request({
url: '/health-record/schedule',
method: 'post',
data
data,
})
}

View File

@ -9,6 +9,7 @@ export * from './project'
export * from './project/task'
export * from './attach-info'
export * from './model-config'
export * from './performance'
// 保险相关模块
export * as InsuranceAPI from './insurance'
export * as InsuranceCompanyAPI from './insurance-company'
@ -27,3 +28,5 @@ export * from './schedule/type'
export * from './project/type'
export * from './attach-info/type'
export * from './model-config/type'
export * from './performance/type'
export * from './salary'

View File

@ -0,0 +1,59 @@
import type * as T from './type'
import http from '@/utils/http'
const BASE_URL = '/performance'
/* ===== 维度 ===== */
export const getDimensionList = () => http.get<T.DimensionResp[]>(`${BASE_URL}/dimension`)
export const addDimension = (data: T.DimensionAddReq) =>
http.post<boolean>(`${BASE_URL}/dimension`, data)
export const updateDimension = (id: string, data: T.DimensionUpdateReq) =>
http.put<boolean>(`${BASE_URL}/dimension/${id}`, data)
export const deleteDimension = (id: string) =>
http.del<boolean>(`${BASE_URL}/dimension/${id}`)
/** 维度详情RuleDrawer.vue 需要) */
export const getDimensionDetail = (id: string) =>
http.get<T.DimensionResp>(`${BASE_URL}/dimension/${id}`)
/* ===== 细则 ===== */
export const getRuleList = (dimensionId: string) =>
http.get<T.RuleResp[]>(`${BASE_URL}/rule`, { dimensionId })
export const addRule = (data: T.RuleAddReq) =>
http.post<boolean>(`${BASE_URL}/rule`, data)
export const updateRule = (id: string, data: T.RuleUpdateReq) =>
http.put<boolean>(`${BASE_URL}/rule/${id}`, data)
export const deleteRule = (id: string) =>
http.del<boolean>(`${BASE_URL}/rule/${id}`)
/** 细则详情RuleDrawer.vue 需要) */
export const getRuleDetail = (id: string) =>
http.get<T.RuleResp>(`${BASE_URL}/rule/${id}`)
/* ===== 周期 ===== */
export const getPeriodList = () => http.get<T.PeriodResp[]>(`${BASE_URL}/period`)
export const addPeriod = (data: T.PeriodResp) => http.post<boolean>(`${BASE_URL}/period`, data)
export const deletePeriod = (id: string) => http.del<boolean>(`${BASE_URL}/period/${id}`)
export const updataPeriod = (id: string, data: T.PeriodResp) => http.post<boolean>(`${BASE_URL}/period/${id}`, data)
/* ===== 评估 ===== */
export const startEvaluate = (data: T.EvaluateReq) =>
http.post<boolean>(`${BASE_URL}/evaluate`, data)
export const getEvaluatePage = (query?: T.EvaluateQuery) =>
http.get<PageRes<T.EvaluateResp[]>>(`${BASE_URL}/evaluate/page`, query)
// 同样的修改和删除评估接口
/** 员工查看自己的绩效 */
export const getMyEvaluate = (query?: T.EvaluateQuery) =>
http.get<PageRes<T.EvaluateResp[]>>(`${BASE_URL}/evaluate/my`, query)
/* ===== 反馈 ===== */
export const submitFeedback = (data: T.FeedbackReq) =>
http.post<boolean>(`${BASE_URL}/feedback`, data)

View File

@ -0,0 +1,87 @@
// ========== 维度 ==========
export interface DimensionResp {
id: string
name: string
desc: string
weight: number
status: 0 | 1
ruleCount: number
deptId: string
}
export interface DimensionAddReq {
name: string
desc: string
weight: number
deptId: string
}
export interface DimensionUpdateReq extends DimensionAddReq {
status: 0 | 1
}
// ========== 细则 ==========
export interface RuleResp {
id: string
dimensionId: string
name: string
ruleDesc: string
score: number
weight: number
isExtra: boolean
}
export interface RuleAddReq {
dimensionId: string
name: string
ruleDesc: string
score: number
weight: number
isExtra: boolean
}
export interface RuleUpdateReq extends RuleAddReq {}
// ========== 绩效周期 ==========
export interface PeriodResp {
id: string
name: string
startDate: string
endDate: string
}
// ========== 评估 ==========
export interface EvaluateReq {
userId: string
dimensionId: string
ruleId: string
periodId: string
}
export interface EvaluateResp {
id: string // 细则id
userId: string
userName: string
dimensionName: string
ruleName: string
periodName: string
score: number
aiComment: string
status: 0 | 1 // 0待反馈 1已反馈
}
// ========== 查询 ==========
export interface EvaluateQuery {
keyword?: string
periodId?: string
dimensionId?: string
status?: 0 | 1
}
// ========== 反馈 ==========
export interface FeedbackReq {
evaluateId: string
level: 0 | 1 | 2
content: string
userId: string
}

64
src/apis/salary/index.ts Normal file
View File

@ -0,0 +1,64 @@
import type { SalaryRecord, SalaryQuery, SalaryCreateRequest } from '@/views/salary-management/types'
import http from '@/utils/http'
const BASE_URL = '/salary'
// 获取工资单列表
export const getSalaryList = (query?: SalaryQuery) => {
return http.get<PageRes<SalaryRecord[]>>(`${BASE_URL}/list`, query)
}
// 获取工资单详情
export const getSalaryDetail = (id: string) => {
return http.get<SalaryRecord>(`${BASE_URL}/${id}`)
}
// 创建工资单
export const createSalary = (data: SalaryCreateRequest) => {
return http.post<SalaryRecord>(`${BASE_URL}`, data)
}
// 更新工资单
export const updateSalary = (id: string, data: Partial<SalaryRecord>) => {
return http.put<SalaryRecord>(`${BASE_URL}/${id}`, data)
}
// 删除工资单
export const deleteSalary = (id: string) => {
return http.del<boolean>(`${BASE_URL}/${id}`)
}
// 提交审批
export const submitApproval = (id: string) => {
return http.post<boolean>(`${BASE_URL}/${id}/submit`)
}
// 审批工资单
export const approveSalary = (id: string, data: { status: string; comment?: string }) => {
return http.put<boolean>(`${BASE_URL}/${id}/approve`, data)
}
// 导出工资单
export const exportSalary = (id: string, format: 'excel' | 'pdf' = 'excel') => {
return http.download(`${BASE_URL}/${id}/export`, { format })
}
// 批量导出
export const exportSalaryBatch = (ids: string[], format: 'excel' | 'pdf' = 'excel') => {
return http.download(`${BASE_URL}/export/batch`, { ids, format })
}
// 获取实习生配置
export const getInternConfig = () => {
return http.get<any>(`${BASE_URL}/config/intern`)
}
// 获取员工列表
export const getEmployeeList = (type?: string) => {
return http.get<any[]>(`${BASE_URL}/employees`, { type })
}
// 获取项目列表
export const getProjectList = () => {
return http.get<any[]>(`${BASE_URL}/projects`)
}

View File

@ -67,7 +67,7 @@ export const systemRoutes: RouteRecordRaw[] = [
{
path: '/organization/hr/performance',
name: 'HRPerformance',
component: () => import('@/views/hr/performance/index.vue'),
component: () => import('@/views/performance/index.vue'),
meta: { title: '绩效', icon: 'performance', hidden: false },
},
{
@ -83,7 +83,7 @@ export const systemRoutes: RouteRecordRaw[] = [
component: () => import('@/views/hr/salary/index.vue'),
meta: { title: '工资概览', icon: 'salary', hidden: false },
},
]
],
},
// {
// path: '/organization/hr/salary/insurance',
@ -166,8 +166,8 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'HRSystemTypeManagement',
component: () => import('@/views/hr/salary/system-insurance/type-management/index.vue'),
meta: { title: '保险类型管理', icon: 'category', hidden: false },
}
]
},
],
},
{
path: '/organization/hr/salary/certification',
@ -180,15 +180,54 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'HRContribution',
component: () => import('@/views/hr/contribution/index.vue'),
meta: { title: '责献积分制度', icon: 'contribution', hidden: false },
}
]
},
],
},
{
path: '/organization/role',
name: 'OrganizationRole',
component: () => import('@/views/system/role/index.vue'),
meta: { title: '角色管理', icon: 'role', hidden: false },
}
},
],
},
{
path: '/performance',
name: 'Performance',
component: Layout,
redirect: '/performance/dimension',
meta: { title: '绩效管理', icon: 'chart', hidden: false, sort: 3 },
children: [
{
path: '/performance/dimension',
name: 'PerformanceDimension',
component: () => import('@/views/performance/dimension.vue'),
meta: { title: '绩效维度管理', icon: 'dashboard', hidden: false },
},
{
path: '/performance/rule',
name: 'PerformanceRule',
component: () => import('@/views/performance/rule.vue'),
meta: { title: '细则管理', icon: 'setting', hidden: false },
},
{
path: '/performance/evaluate',
name: 'PerformanceEvaluate',
component: () => import('@/views/performance/evaluate.vue'),
meta: { title: '员工评估', icon: 'user', hidden: false },
},
{
path: '/performance/my',
name: 'PerformanceMy',
component: () => import('@/views/performance/my.vue'),
meta: { title: '我的绩效', icon: 'user', hidden: false },
},
{
path: '/salary-management',
name: 'SalaryManagement',
component: () => import('@/views/salary-management/index.vue'),
meta: { title: '工资管理', hidden: false },
},
],
},
{
@ -203,7 +242,7 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'IntellectualProperty',
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { title: '其他资产', icon: 'copyright', hidden: false },
}
},
],
},
{
@ -250,8 +289,8 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'BladeRobot',
component: () => import('@/views/service/lightning-detection/index.vue'),
meta: { title: '叶片维修机器人', icon: 'robot', hidden: false },
}
]
},
],
},
{
path: '/products-services/products/software',
@ -277,10 +316,10 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'GroundStation',
component: () => import('@/views/service/lightning-detection/index.vue'),
meta: { title: '无人机地面站软件', icon: 'station', hidden: false },
}
]
}
]
},
],
},
],
},
{
path: '/products-services/services',
@ -342,9 +381,9 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'BladeMaintenance',
component: () => import('@/views/service/lightning-detection/index.vue'),
meta: { title: '叶片维修', icon: 'maintenance', hidden: false },
}
]
}
},
],
},
],
},
{
@ -362,7 +401,7 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '施工立项',
icon: 'file-protect',
hidden: false
hidden: false,
},
children: [
{
@ -372,8 +411,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '招标文件',
icon: 'file-text',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/project-template/bid-documents',
@ -382,8 +421,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '投标文件',
icon: 'file-text',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/project-template/award-notice',
@ -392,8 +431,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '中标通知书',
icon: 'trophy',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/projects/initiation',
@ -402,10 +441,10 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '立项管理',
icon: 'plus-circle',
hidden: false
}
hidden: false,
},
},
]
],
},
{
path: '/project-management/contract',
@ -415,7 +454,7 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '市场商务管理',
icon: 'file-text',
hidden: false
hidden: false,
},
children: [
{
@ -425,8 +464,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '收入合同',
icon: 'dollar',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/contract/expense-contract',
@ -435,8 +474,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '支出合同',
icon: 'credit-card',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/contract/cost-management',
@ -445,10 +484,10 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '成本费用',
icon: 'bar-chart',
hidden: false
}
}
]
hidden: false,
},
},
],
},
{
path: '/project-management/projects',
@ -458,7 +497,7 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '组织实施管理',
icon: 'briefcase',
hidden: false
hidden: false,
},
children: [
@ -469,8 +508,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '进度管理',
icon: 'schedule',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/projects/budget',
@ -479,8 +518,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '预算管理',
icon: 'fund',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/projects/personnel-distribution',
@ -489,8 +528,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '人员分布图',
icon: 'team',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/projects/device',
@ -499,8 +538,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '设备管理',
icon: 'plus-circle',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/projects/safety',
@ -509,8 +548,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '安全管理',
icon: 'safety',
hidden: false
}
hidden: false,
},
},
{
path: '/project-management/projects/quality',
@ -519,11 +558,11 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '质量管理',
icon: 'audit',
hidden: false
}
}
]
}
hidden: false,
},
},
],
},
],
},
{
@ -554,16 +593,16 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '项目列表',
icon: 'unordered-list',
hidden: false
}
hidden: false,
},
},
{
path: '/construction-operation-platform/implementation-workflow/field-construction/technology',
name: 'FieldConstructionTechnology',
component: () => import('@/views/project-operation-platform/implementation-workflow/field-construction/project-list/index.vue'),
meta: { title: '现场工艺', icon: 'tool', hidden: false },
}
]
},
],
},
{
path: '/construction-operation-platform/implementation-workflow/data-processing',
@ -604,7 +643,7 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: { title: '模型配置', icon: 'robot', hidden: false },
},
]
],
},
{
path: '/construction-operation-platform/implementation-workflow/data-processing/intelligent-inspection',
@ -654,10 +693,10 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'ReportReview',
component: () => import('@/views/project-operation-platform/data-processing/report-review/index.vue'),
meta: { title: '报告修改审核', icon: 'audit', hidden: false },
}
]
}
]
},
],
},
],
},
{
path: '/construction-operation-platform/implementation-workflow/tower-monitoring-video',
@ -683,8 +722,8 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'ImageDetection',
component: () => import('@/views/project-operation-platform/data-processing/wide-angle-video/index.vue'),
meta: { title: '图像检测', icon: 'picture', hidden: false },
}
]
},
],
},
{
path: '/construction-operation-platform/implementation-workflow/project-delivery',
@ -734,11 +773,11 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'ReportTemplateOther',
component: () => import('@/views/project-operation-platform/data-processing/report-template/index.vue'),
meta: { title: '报告模版库', icon: 'book', hidden: false },
}
]
}
]
}
},
],
},
],
},
],
},
{
@ -758,7 +797,7 @@ export const systemRoutes: RouteRecordRaw[] = [
// hidden: false
// }
// }
]
],
},
{
path: '/enterprise-settings',
@ -773,9 +812,9 @@ export const systemRoutes: RouteRecordRaw[] = [
component: () => import('@/views/enterprise-settings/company-info/index.vue'),
meta: {
title: '企业信息',
icon: 'info-circle',
hidden: false
}
icon: 'info-circle',
hidden: false,
},
},
{
path: '/enterprise-settings/admin-permissions',
@ -784,8 +823,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '管理员权限',
icon: 'lock',
hidden: false
}
hidden: false,
},
},
{
path: '/enterprise-settings/data-migration',
@ -794,8 +833,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '数据迁移',
icon: 'database',
hidden: false
}
hidden: false,
},
},
{
path: '/enterprise-settings/version-upgrade',
@ -804,10 +843,10 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '版本升级提醒',
icon: 'upgrade',
hidden: false
}
}
]
hidden: false,
},
},
],
},
{
path: '/enterprise-dashboard',
@ -823,8 +862,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '数据概览',
icon: 'bar-chart',
hidden: false
}
hidden: false,
},
},
{
path: '/enterprise-dashboard/member-data',
@ -833,8 +872,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '成员活跃数据',
icon: 'team',
hidden: false
}
hidden: false,
},
},
{
path: '/enterprise-dashboard/function-usage',
@ -843,8 +882,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '功能使用情况',
icon: 'appstore',
hidden: false
}
hidden: false,
},
},
{
path: '/enterprise-dashboard/application-data',
@ -853,10 +892,10 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '应用使用数据',
icon: 'pie-chart',
hidden: false
}
}
]
hidden: false,
},
},
],
},
{
path: '/system-resource',
@ -865,60 +904,59 @@ export const systemRoutes: RouteRecordRaw[] = [
redirect: '/system-resource/device-management/warehouse',
meta: { title: '关于平台', icon: 'server', hidden: false, sort: 9 },
children: [
{
path: '/system-resource/device-management/warehouse',
name: 'DeviceWarehouse',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '库存管理',
icon: 'warehouse',
hidden: false,
},
},
{
path: '/system-resource/device-management/online',
name: 'DeviceOnline',
component: () => import('@/components/ParentView/index.vue'),
redirect: '/system-resource/device-management/online/drone',
meta: {
title: '在线管理',
icon: 'cloud',
hidden: false,
},
children: [
{
path: '/system-resource/device-management/warehouse',
name: 'DeviceWarehouse',
path: '/system-resource/device-management/online/drone',
name: 'DeviceDrone',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '库存管理',
icon: 'warehouse',
hidden: false
}
title: '无人机',
icon: 'drone',
hidden: false,
},
},
{
path: '/system-resource/device-management/online',
name: 'DeviceOnline',
component: () => import('@/components/ParentView/index.vue'),
redirect: '/system-resource/device-management/online/drone',
path: '/system-resource/device-management/online/nest',
name: 'DeviceNest',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '在线管理',
icon: 'cloud',
hidden: false
title: '机巢',
icon: 'nest',
hidden: false,
},
children: [
{
path: '/system-resource/device-management/online/drone',
name: 'DeviceDrone',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '无人机',
icon: 'drone',
hidden: false
}
},
{
path: '/system-resource/device-management/online/nest',
name: 'DeviceNest',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '机巢',
icon: 'nest',
hidden: false
}
},
{
path: '/system-resource/device-management/online/smart-terminal',
name: 'DeviceSmartTerminal',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '其他智能终端',
icon: 'terminal',
hidden: false
}
}
]
}
,
},
{
path: '/system-resource/device-management/online/smart-terminal',
name: 'DeviceSmartTerminal',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '其他智能终端',
icon: 'terminal',
hidden: false,
},
},
],
},
{
path: '/system-resource/information-system',
name: 'InformationSystem',
@ -927,7 +965,7 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '信息化系统管理',
icon: 'code',
hidden: false
hidden: false,
},
children: [
{
@ -937,8 +975,8 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '软件管理',
icon: 'appstore',
hidden: false
}
hidden: false,
},
},
{
path: '/system-resource/information-system/system-backup',
@ -947,10 +985,10 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '系统备份管理',
icon: 'save',
hidden: false
}
}
]
hidden: false,
},
},
],
},
{
path: '/system-resource/about',
@ -959,10 +997,10 @@ export const systemRoutes: RouteRecordRaw[] = [
meta: {
title: '关于我们',
icon: 'info-circle',
hidden: false
}
}
]
hidden: false,
},
},
],
},
{
@ -1001,4 +1039,3 @@ export const constantRoutes: RouteRecordRaw[] = [
meta: { hidden: true },
},
]

View File

@ -0,0 +1,75 @@
<template>
<a-drawer
v-model:visible="visible"
:title="isUpdate ? '修改维度' : '新增维度'"
@before-ok="save"
@cancel="reset"
>
<a-form ref="formRef" :model="form" auto-label-width>
<a-form-item label="维度名称" field="name" required>
<a-input v-model="form.name" />
</a-form-item>
<a-form-item label="权重%" field="weight" required>
<a-input-number v-model="form.weight" :min="1" :max="100" />
</a-form-item>
<a-form-item label="描述" field="desc">
<a-textarea v-model="form.desc" :rows="3" />
</a-form-item>
<a-form-item label="状态" field="status">
<a-switch v-model="form.status" :checked-value="0" :unchecked-value="1" />
</a-form-item>
</a-form>
</a-drawer>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { addDimension, getDimensionDetail, updateDimension } from '@/apis/performance'
const emit = defineEmits<{
(e: 'success'): void
}>()
const visible = ref(false)
const isUpdate = ref(false)
const formRef = ref()
const id = ref('')
const form = reactive({
name: '',
weight: 50,
desc: '',
status: 0 as 0 | 1,
})
const reset = () => {
formRef.value?.resetFields()
Object.assign(form, { name: '', weight: 50, desc: '', status: 0 })
id.value = ''
}
const save = async () => {
if (isUpdate.value) {
await updateDimension(id.value, form)
} else {
await addDimension(form)
}
visible.value = false
emit('success')
}
const onAdd = () => {
reset()
isUpdate.value = false
visible.value = true
}
const onUpdate = async (dimensionId: string) => {
reset()
isUpdate.value = true
id.value = dimensionId
const data = await getDimensionDetail(dimensionId)
Object.assign(form, data)
visible.value = true
}
defineExpose({ onAdd, onUpdate })
</script>

View File

@ -0,0 +1,100 @@
<template>
<a-spin :loading="loading">
<a-descriptions :data="detailData" bordered>
<a-descriptions-item label="员工">
{{ data.userName }}
</a-descriptions-item>
<a-descriptions-item label="维度">
{{ data.dimensionName }}
</a-descriptions-item>
<a-descriptions-item label="周期">
{{ data.periodName }}
</a-descriptions-item>
<a-descriptions-item label="评分">
<a-tag color="blue" size="large">{{ data.score }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="AI评价" :span="2">
{{ data.aiComment }}
</a-descriptions-item>
</a-descriptions>
<!-- 反馈表单 -->
<a-divider orientation="left">评价反馈</a-divider>
<a-form ref="feedbackRef" :model="feedback" :rules="rules" layout="vertical">
<a-form-item label="是否有异议" field="level" required>
<a-radio-group v-model="feedback.level">
<a-radio :value="0">无异议</a-radio>
<a-radio :value="1">部分有异议</a-radio>
<a-radio :value="2">完全不同意</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="请说明理由" field="content" required>
<a-textarea
v-model="feedback.content"
:rows="4"
placeholder="请具体描述您对评价结果的看法..."
/>
</a-form-item>
<a-form-item>
<a-button type="primary" :loading="submitting" @click="handleSubmit">
提交反馈
</a-button>
</a-form-item>
</a-form>
</a-spin>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import { submitFeedback } from '@/apis/performance'
import type { EvaluateResp, FeedbackReq } from '@/apis/performance/type'
interface Props {
data: EvaluateResp
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'feedback'): void
}>()
const loading = ref(false)
const submitting = ref(false)
const feedbackRef = ref()
const feedback = reactive<FeedbackReq>({
evaluateId: props.data.id,
level: 0,
content: '',
})
const rules = {
level: [{ required: true, message: '请选择异议等级' }],
content: [{ required: true, message: '请填写反馈内容' }],
}
const detailData = computed(() => [
{ label: '员工', value: props.data.userName },
{ label: '维度', value: props.data.dimensionName },
{ label: '周期', value: props.data.periodName },
{ label: '评分', value: props.data.score },
{ label: 'AI评价', value: props.data.aiComment },
])
const handleSubmit = async () => {
const ok = await feedbackRef.value?.validate()
if (ok) return
submitting.value = true
try {
await submitFeedback(feedback)
Message.success('反馈已提交')
emit('feedback')
} finally {
submitting.value = false
}
}
</script>

View File

@ -0,0 +1,18 @@
<template>
<a-menu
:default-selected-keys="['dimension']"
@menu-item-click="handleClick"
>
<a-menu-item key="dimension">绩效维度</a-menu-item>
<a-menu-item key="evaluate">员工评估</a-menu-item>
<a-menu-item key="my">我的绩效</a-menu-item>
</a-menu>
</template>
<script setup lang="ts">
const emit = defineEmits<{
(e: 'select', key: string): void
}>()
const handleClick = (key: string) => emit('select', key)
</script>

View File

@ -0,0 +1,131 @@
<template>
<a-drawer
v-model:visible="visible"
:title="isUpdate ? '编辑细则' : '新增细则'"
:width="480"
@before-ok="handleSave"
@cancel="reset"
>
<a-form ref="formRef" :model="form" auto-label-width>
<a-form-item label="所属维度" field="dimensionId" required>
<a-select v-model="form.dimensionId" placeholder="请选择维度">
<a-option
v-for="item in dimensionOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
<a-form-item label="细则名称" field="name" required>
<a-input v-model="form.name" placeholder="如:持有基础资质证书" />
</a-form-item>
<a-form-item label="评分规则描述" field="ruleDesc" required>
<a-textarea
v-model="form.ruleDesc"
:rows="4"
placeholder="详细说明评分标准"
/>
</a-form-item>
<a-form-item label="满分" field="score" required>
<a-input-number v-model="form.score" :min="0" :precision="0" />
</a-form-item>
<a-form-item label="权重" field="weight" required>
<a-input-number v-model="form.weight" :min="0" :max="100" />
</a-form-item>
<a-form-item label="是否加分项" field="isExtra">
<a-switch v-model="form.isExtra" />
</a-form-item>
</a-form>
</a-drawer>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import {
addRule,
getDimensionList,
getRuleDetail,
updateRule,
} from '@/apis/performance'
import type { DimensionResp, RuleAddReq, RuleUpdateReq } from '@/apis/performance/type'
const emit = defineEmits<{
(e: 'success'): void
}>()
const visible = ref(false)
const isUpdate = ref(false)
const id = ref('')
const formRef = ref()
const dimensionOptions = ref<DimensionResp[]>([])
const form = reactive<RuleAddReq>({
dimensionId: '',
name: '',
ruleDesc: '',
score: 100,
weight: 10,
isExtra: false,
})
const reset = () => {
formRef.value?.resetFields()
Object.assign(form, {
dimensionId: '',
name: '',
ruleDesc: '',
score: 100,
weight: 10,
isExtra: false,
})
id.value = ''
}
const openAdd = () => {
reset()
isUpdate.value = false
visible.value = true
}
const openUpdate = async (ruleId: string) => {
reset()
isUpdate.value = true
id.value = ruleId
const detail = await getRuleDetail(ruleId)
Object.assign(form, detail)
visible.value = true
}
const handleSave = async () => {
const ok = await formRef.value?.validate()
if (ok) return false
try {
if (isUpdate.value) {
await updateRule(id.value, form as RuleUpdateReq)
Message.success('细则已更新')
} else {
await addRule(form)
Message.success('细则已新增')
}
visible.value = false
emit('success')
return true
} catch {
return false
}
}
onMounted(async () => {
dimensionOptions.value = await getDimensionList()
})
defineExpose({ openAdd, openUpdate })
</script>

View File

@ -0,0 +1,67 @@
<template>
<a-modal
v-model:visible="visible"
title="细则管理"
:width="800"
@before-ok="handleSave"
@cancel="reset"
>
<a-table :data="rules" :columns="columns" row-key="id">
<template #action="{ record }">
<a-space>
<a-button type="text" @click="RuleDrawerRef?.openUpdate(record.id)">
编辑
</a-button>
<a-button type="text" status="danger" @click="handleDelete(record.id)">
删除
</a-button>
</a-space>
</template>
</a-table>
<a-button type="primary" @click="RuleDrawerRef?.openAdd()">
<icon-plus /> 新增细则
</a-button>
</a-modal>
<RuleDrawer ref="RuleDrawerRef" @success="load" />
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import RuleDrawer from './RuleDrawer.vue'
import { deleteRule, getRuleList } from '@/apis/performance'
import type { RuleResp } from '@/apis/performance/type'
import { Message } from '@arco-design/web-vue'
const visible = ref(false)
const rules = ref<RuleResp[]>([])
const RuleDrawerRef = ref()
const columns = [
{ title: '细则名称', dataIndex: 'name' },
{ title: '评分规则描述', dataIndex: 'ruleDesc' },
{ title: '满分', dataIndex: 'score' },
{ title: '权重', dataIndex: 'weight' },
{ title: '是否加分项', dataIndex: 'isExtra', render: ({ record }) => record.isExtra ? '是' : '否' },
{ title: '操作', slotName: 'action' },
]
const open = async (dimensionId: string) => {
visible.value = true
rules.value = await getRuleList(dimensionId)
}
const handleDelete = async (id: string) => {
await deleteRule(id)
Message.success('删除成功')
load()
}
const load = async () => {
//
rules.value = await getRuleList(dimensionId.value)
}
defineExpose({ open })
</script>

View File

@ -0,0 +1,72 @@
<template>
<div>
<a-card title="绩效维度管理">
<template #extra>
<a-button type="primary" @click="DimensionDrawerRef?.onAdd()">
<icon-plus /> 添加维度
</a-button>
</template>
<a-table :data="dimensions" :columns="columns" row-key="id">
<template #status="{ record }">
<a-tag :color="record.status === 0 ? 'green' : 'red'">
{{ record.status === 0 ? '启用' : '禁用' }}
</a-tag>
</template>
<template #action="{ record }">
<a-space>
<a-button type="text" @click="DimensionDrawerRef?.onUpdate(record.id)">
编辑
</a-button>
<a-button type="text" status="danger" @click="handleDelete(record.id)">
删除
</a-button>
<a-button type="text" @click="openRuleList(record.id)">
管理细则
</a-button>
</a-space>
</template>
</a-table>
</a-card>
<!-- 维度抽屉 -->
<DimensionDrawer ref="DimensionDrawerRef" @success="load" />
<!-- 细则列表 -->
<RuleList ref="RuleListRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getDimensionList, deleteDimension } from '@/apis/performance'
import DimensionDrawer from './components/DimensionDrawer.vue'
import RuleList from './components/RuleList.vue'
import type { DimensionResp } from '@/apis/performance/type'
const dimensions = ref<DimensionResp[]>([])
const DimensionDrawerRef = ref()
const RuleListRef = ref()
const columns = [
{ title: '维度名称', dataIndex: 'name' },
{ title: '权重', dataIndex: 'weight', render: ({ record }) => `${record.weight}%` },
{ title: '细则数量', dataIndex: 'ruleCount' },
{ title: '状态', slotName: 'status' },
{ title: '操作', slotName: 'action' },
]
const load = async () => {
const response=await getDimensionList()
dimensions.value = await getDimensionList().data||[]
}
onMounted(load)
const handleDelete = async (id: string) => {
await deleteDimension(id)
load()
}
const openRuleList = (dimensionId: string) => {
RuleListRef.value.open(dimensionId)
}
</script>

View File

@ -0,0 +1,70 @@
<template>
<a-card title="员工绩效评估">
<a-form layout="inline" style="margin-bottom: 16px">
<a-form-item label="绩效周期">
<a-select v-model="query.periodId" placeholder="请选择周期" allow-clear>
<a-option v-for="p in periods" :key="p.id" :value="p.id">{{ p.name }}</a-option>
</a-select>
</a-form-item>
<a-form-item label="维度">
<a-select v-model="query.dimensionId" placeholder="请选择维度" allow-clear>
<a-option v-for="d in dimensions" :key="d.id" :value="d.id">{{ d.name }}</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-input v-model="query.keyword" placeholder="姓名/工号" />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="load">查询</a-button>
</a-form-item>
</a-form>
<a-table :data="data" :columns="columns" row-key="id">
<template #action="{ record }">
<a-button type="text" @click="openEvaluate(record)">
开始评估
</a-button>
</template>
</a-table>
</a-card>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getDimensionList, getEvaluatePage, getPeriodList, startEvaluate } from '@/apis/performance'
import type { EvaluateQuery, EvaluateResp } from '@/apis/performance/type'
const periods = ref([])
const dimensions = ref([])
const query = ref<EvaluateQuery>({})
const data = ref<EvaluateResp[]>([])
const columns = [
{ title: '员工', dataIndex: 'userName' },
{ title: '维度', dataIndex: 'dimensionName' },
{ title: '周期', dataIndex: 'periodName' },
{ title: '评分', dataIndex: 'score' },
{ title: '操作', slotName: 'action' },
]
const load = async () => {
data.value = await getEvaluatePage(query.value)
}
onMounted(async () => {
periods.value = await getPeriodList()
dimensions.value = await getDimensionList()
load()
})
const openEvaluate = async (row: EvaluateResp) => {
await startEvaluate({
userId: row.userId,
dimensionId: row.dimensionId,
ruleId: row.ruleId,
periodId: row.periodId,
})
Message.success('评估已启动')
load()
}
</script>

View File

@ -0,0 +1,19 @@
<template>
<GiPageLayout>
<template #left>
<PerformanceMenu @select="handleSelectMenu" />
</template>
<router-view />
</GiPageLayout>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import PerformanceMenu from './components/PerformanceMenu.vue'
const router = useRouter()
const handleSelectMenu = (key: string) => {
router.push(`/performance/${key}`)
}
</script>

View File

@ -0,0 +1,48 @@
<template>
<a-card title="我的绩效">
<a-table :data="data" :columns="columns" row-key="id">
<template #action="{ record }">
<a-button type="text" @click="openDetail(record)">查看详情</a-button>
</template>
</a-table>
</a-card>
<a-modal v-model:visible="visible" title="绩效详情">
<EvaluateDetail v-if="selected" :data="selected" @feedback="handleFeedback" />
</a-modal>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import EvaluateDetail from './components/EvaluateDetail.vue'
import { getMyEvaluate } from '@/apis/performance'
import type { EvaluateResp } from '@/apis/performance/type'
const data = ref<EvaluateResp[]>([])
const visible = ref(false)
const selected = ref<EvaluateResp>()
const columns = [
{ title: '维度', dataIndex: 'dimensionName' },
{ title: '周期', dataIndex: 'periodName' },
{ title: '评分', dataIndex: 'score' },
{ title: '操作', slotName: 'action' },
]
const load = async () => {
const response = await getMyEvaluate()
data.value = response.data?.list || []
}
const openDetail = (row: EvaluateResp) => {
selected.value = row
visible.value = true
}
const handleFeedback = () => {
visible.value = false
load()
}
onMounted(load)
</script>

View File

@ -0,0 +1,61 @@
<template>
<div>
<a-card title="绩效细则管理">
<template #extra>
<a-button type="primary" @click="RuleDrawerRef?.openAdd()">
<icon-plus /> 新增细则
</a-button>
</template>
<a-table :data="rules" :columns="columns" row-key="id">
<template #action="{ record }">
<a-space>
<a-button type="text" @click="RuleDrawerRef?.openUpdate(record.id)">
编辑
</a-button>
<a-button type="text" status="danger" @click="handleDelete(record.id)">
删除
</a-button>
</a-space>
</template>
</a-table>
</a-card>
<!-- 细则抽屉 -->
<RuleDrawer ref="RuleDrawerRef" @success="load" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import RuleDrawer from './components/RuleDrawer.vue'
import { deleteRule, getRuleList } from '@/apis/performance'
import type { RuleResp } from '@/apis/performance/type'
const rules = ref<RuleResp[]>([])
const RuleDrawerRef = ref()
const columns = [
{ title: '细则名称', dataIndex: 'name' },
{ title: '评分规则描述', dataIndex: 'ruleDesc' },
{ title: '满分', dataIndex: 'score' },
{ title: '权重', dataIndex: 'weight' },
{ title: '是否加分项', dataIndex: 'isExtra', render: ({ record }) => record.isExtra ? '是' : '否' },
{ title: '操作', slotName: 'action' },
]
const load = async (dimensionId: string) => {
rules.value = await getRuleList(dimensionId)
}
onMounted(() => {
//
load('default-dimension-id')
})
const handleDelete = async (id: string) => {
await deleteRule(id)
Message.success('删除成功')
load('default-dimension-id') //
}
</script>

View File

@ -0,0 +1,229 @@
<template>
<div class="salary-management">
<GiPageLayout>
<template #header>
<a-row justify="space-between" align="center">
<a-col>
<h1>工资管理系统</h1>
</a-col>
<a-col>
<a-space>
<a-button type="primary" @click="handleAdd">
<template #icon><icon-plus /></template>
新建工资单
</a-button>
<a-button @click="handleExport">
<template #icon><icon-download /></template>
导出Excel
</a-button>
</a-space>
</a-col>
</a-row>
</template>
<a-card>
<a-row :gutter="16" class="search-section">
<a-col :span="6">
<a-input
v-model="searchParams.keyword"
placeholder="搜索员工姓名/工号"
allow-clear
/>
</a-col>
<a-col :span="6">
<a-select
v-model="searchParams.employeeType"
placeholder="员工类型"
allow-clear
>
<a-option value="intern">实习生</a-option>
<a-option value="fulltime">正式员工</a-option>
<a-option value="parttime">兼职员工</a-option>
</a-select>
</a-col>
<a-col :span="6">
<a-range-picker
v-model="searchParams.dateRange"
style="width: 100%"
/>
</a-col>
<a-col :span="6">
<a-space>
<a-button type="primary" @click="handleSearch">查询</a-button>
<a-button @click="handleReset">重置</a-button>
</a-space>
</a-col>
</a-row>
<a-table
:data="tableData"
:columns="columns"
:loading="loading"
row-key="id"
:scroll="{ x: 1500 }"
>
<template #action="{ record }">
<a-space>
<a-button type="text" @click="handleEdit(record)">编辑</a-button>
<a-button type="text" @click="handleDetail(record)">详情</a-button>
<a-button type="text" status="danger" @click="handleDelete(record)">删除</a-button>
</a-space>
</template>
</a-table>
</a-card>
</GiPageLayout>
<SalaryFormDrawer
v-model:visible="drawerVisible"
:data="currentData"
:mode="drawerMode"
@success="handleSuccess"
/>
<SalaryDetailModal
v-model:visible="detailVisible"
:data="currentData"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import SalaryFormDrawer from './components/SalaryFormDrawer.vue'
import SalaryDetailModal from './components/SalaryDetailModal.vue'
import type { SalaryRecord } from './types'
const loading = ref(false)
const drawerVisible = ref(false)
const detailVisible = ref(false)
const drawerMode = ref<'add' | 'edit'>('add')
const currentData = ref<SalaryRecord | null>(null)
const searchParams = reactive({
keyword: '',
employeeType: '',
dateRange: [],
})
const tableData = ref<SalaryRecord[]>([])
const columns = [
{ title: '员工姓名', dataIndex: 'employeeName', fixed: 'left', width: 100 },
{ title: '员工类型', dataIndex: 'employeeType', width: 100 },
{ title: '项目名称', dataIndex: 'projectName', width: 150 },
{ title: '工作天数', dataIndex: 'workDays', width: 80 },
{ title: '基本工资', dataIndex: 'baseSalary', width: 100 },
{ title: '绩效奖金', dataIndex: 'performanceBonus', width: 100 },
{ title: '各类补助', dataIndex: 'allowances', width: 100 },
{ title: '应发总额', dataIndex: 'totalPayable', width: 100 },
{ title: '实发金额', dataIndex: 'netPay', width: 100 },
{ title: '状态', dataIndex: 'status', width: 80 },
{ title: '创建时间', dataIndex: 'createdAt', width: 150 },
{ title: '操作', slotName: 'action', fixed: 'right', width: 150 },
]
const loadData = async () => {
loading.value = true
try {
//
tableData.value = [
{
id: '1',
employeeId: 'E001',
employeeName: '张三',
employeeType: 'intern',
idCard: '123456789012345678',
phone: '13800000000',
bankCard: '6222021234567890',
bankName: '中国银行',
projectName: '海上风电项目A',
projectPeriod: '2025-06',
workDays: 22,
offshoreDays: 5,
baseSalary: 3500,
baseSalaryStandard: 3500,
performanceBonus: 2200,
allowances: 660,
performanceItems: [],
allowanceItems: [],
totalPayable: 6360,
deductions: 0,
netPay: 6360,
status: 'approved',
approvalFlow: [],
salaryMonth: '2025-07',
createdAt: '2025-07-01',
updatedAt: '2025-07-01',
},
]
} finally {
loading.value = false
}
}
const handleAdd = () => {
drawerMode.value = 'add'
currentData.value = null
drawerVisible.value = true
}
const handleEdit = (record: SalaryRecord) => {
drawerMode.value = 'edit'
currentData.value = record
drawerVisible.value = true
}
const handleDetail = (record: SalaryRecord) => {
currentData.value = record
detailVisible.value = true
}
const handleDelete = async (_record: SalaryRecord) => {
try {
await Modal.confirm({
title: '确认删除',
content: '确定要删除这条记录吗?',
})
Message.success('删除成功')
loadData()
} catch {
//
}
}
const handleSearch = () => {
loadData()
}
const handleReset = () => {
searchParams.keyword = ''
searchParams.employeeType = ''
searchParams.dateRange = []
loadData()
}
const handleExport = () => {
// Excel
Message.success('导出功能开发中...')
}
const handleSuccess = () => {
drawerVisible.value = false
loadData()
}
onMounted(() => {
loadData()
})
</script>
<style scoped>
.salary-management {
padding: 16px;
}
.search-section {
margin-bottom: 16px;
}
</style>