1. 新增部门产线绑定字段,优化部门列表展示 2. 添加薪资、考核、维度明细的批量新增/修改接口与实现 3. 新增绩效考核得分计算引擎与WMS数据拉取服务 4. 新增一键生成薪资与考核记录的弹窗功能 5. 优化配置页面UI与新增批量导入项目功能 6. 隐藏 coil/ship.vue 页面的校正按钮
348 lines
11 KiB
Vue
348 lines
11 KiB
Vue
<template>
|
|
<el-dialog
|
|
title="一键生成薪资记录"
|
|
:visible="visible"
|
|
width="520px"
|
|
:close-on-click-modal="false"
|
|
:close-on-press-escape="false"
|
|
:show-close="!running"
|
|
append-to-body
|
|
@close="handleClose"
|
|
>
|
|
<div class="generate-progress">
|
|
<!-- 总体进度条 -->
|
|
<el-progress
|
|
:percentage="overallPercent"
|
|
:status="overallStatus"
|
|
:show-text="false"
|
|
style="margin-bottom: 16px"
|
|
/>
|
|
|
|
<!-- 步骤列表 -->
|
|
<div
|
|
v-for="(step, idx) in steps"
|
|
:key="idx"
|
|
class="step-row"
|
|
:class="'step--' + step.status"
|
|
>
|
|
<div class="step-icon">
|
|
<i v-if="step.status === 'pending'" class="el-icon-more-outline"></i>
|
|
<i v-else-if="step.status === 'in_progress'" class="el-icon-loading"></i>
|
|
<i v-else-if="step.status === 'completed'" class="el-icon-circle-check"></i>
|
|
<i v-else-if="step.status === 'error'" class="el-icon-circle-close"></i>
|
|
</div>
|
|
<div class="step-body">
|
|
<div class="step-label">{{ step.label }}</div>
|
|
<div v-if="step.detail" class="step-detail">{{ step.detail }}</div>
|
|
<div v-if="step.status === 'error' && step.errorMsg" class="step-error">{{ step.errorMsg }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<span slot="footer">
|
|
<el-button size="small" @click="handleClose" :disabled="running">关闭</el-button>
|
|
</span>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script>
|
|
import { listPerfSalary, delPerfSalary, batchAddPerfSalary } from '@/api/perf/salary'
|
|
import { listPerfAppraisal, delPerfAppraisal, batchAddPerfAppraisal } from '@/api/perf/appraisal'
|
|
import { listPerfPositionTemplate } from '@/api/perf/positionTemplate'
|
|
import { listPerfTemplateDimension } from '@/api/perf/templateDimension'
|
|
import { listPerfDeptConfig } from '@/api/perf/deptConfig'
|
|
import { listEmployeeInfo } from '@/api/wms/employeeInfo'
|
|
|
|
export default {
|
|
name: 'GenerateDialog',
|
|
props: {
|
|
visible: { type: Boolean, default: false },
|
|
deptId: { type: [Number, String], default: null },
|
|
deptName: { type: String, default: '' },
|
|
period: { type: String, default: '' }
|
|
},
|
|
data() {
|
|
return {
|
|
running: false,
|
|
steps: [
|
|
{ label: '查询已有记录', status: 'pending', detail: '' },
|
|
{ label: '清除旧记录', status: 'pending', detail: '' },
|
|
{ label: '生成薪资基础数据', status: 'pending', detail: '' },
|
|
{ label: '生成绩效考核评分', status: 'pending', detail: '' }
|
|
]
|
|
}
|
|
},
|
|
computed: {
|
|
overallPercent() {
|
|
const done = this.steps.filter(s => s.status === 'completed').length
|
|
return Math.round((done / this.steps.length) * 100)
|
|
},
|
|
overallStatus() {
|
|
const hasError = this.steps.some(s => s.status === 'error')
|
|
if (hasError) return 'exception'
|
|
if (this.overallPercent === 100) return 'success'
|
|
return ''
|
|
}
|
|
},
|
|
watch: {
|
|
visible(val) {
|
|
if (val) {
|
|
if (!this.deptId || !this.period) {
|
|
this.$message.warning('缺少部门或周期参数')
|
|
this.$emit('update:visible', false)
|
|
return
|
|
}
|
|
this.startGenerate()
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
handleClose() {
|
|
if (this.running) return
|
|
this.$emit('update:visible', false)
|
|
},
|
|
|
|
resetSteps() {
|
|
this.steps.forEach(s => {
|
|
s.status = 'pending'
|
|
s.detail = ''
|
|
s.errorMsg = ''
|
|
})
|
|
},
|
|
|
|
setStep(idx, status, detail, errorMsg) {
|
|
const s = this.steps[idx]
|
|
s.status = status
|
|
if (detail !== undefined) s.detail = detail
|
|
if (errorMsg !== undefined) s.errorMsg = errorMsg
|
|
},
|
|
|
|
async startGenerate() {
|
|
this.running = true
|
|
this.resetSteps()
|
|
|
|
try {
|
|
// ====== 前置检查:是否已设定考核参数 ======
|
|
const cfgRes = await listPerfDeptConfig({ deptId: this.deptId, pageNum: 1, pageSize: 1 })
|
|
if (!cfgRes.rows || cfgRes.rows.length === 0) {
|
|
this.$message.warning('请先在"考核配置"页中设定考核参数,再一键生成')
|
|
this.running = false
|
|
// 重置所有步骤为 pending
|
|
this.resetSteps()
|
|
return
|
|
}
|
|
const deptCoeff = cfgRes.rows[0].deptCoeff || 1
|
|
|
|
// ====== Step 0: 查询已有记录 ======
|
|
const [salaryRes, appRes] = await Promise.all([
|
|
listPerfSalary({ deptId: this.deptId, period: this.period, pageNum: 1, pageSize: 9999 }),
|
|
listPerfAppraisal({ deptId: this.deptId, period: this.period, pageNum: 1, pageSize: 9999 })
|
|
])
|
|
const salaryIds = (salaryRes.rows || []).map(r => r.id)
|
|
const appIds = (appRes.rows || []).map(r => r.id)
|
|
const totalExisting = salaryIds.length + appIds.length
|
|
this.setStep(0, 'completed', totalExisting > 0
|
|
? `查询到 ${salaryIds.length} 条薪资记录、${appIds.length} 条考核记录`
|
|
: '暂无已有记录')
|
|
|
|
// ====== Step 1: 清除旧记录 ======
|
|
this.setStep(1, 'in_progress')
|
|
if (totalExisting === 0) {
|
|
this.setStep(1, 'completed', '无需清除')
|
|
} else {
|
|
const delTasks = []
|
|
if (salaryIds.length) delTasks.push(delPerfSalary(salaryIds.join(',')))
|
|
if (appIds.length) delTasks.push(delPerfAppraisal(appIds.join(',')))
|
|
await Promise.all(delTasks)
|
|
const parts = []
|
|
if (salaryIds.length) parts.push(`${salaryIds.length} 条薪资记录`)
|
|
if (appIds.length) parts.push(`${appIds.length} 条考核记录`)
|
|
this.setStep(1, 'completed', `已删除 ${parts.join('、')}`)
|
|
}
|
|
|
|
// ====== Step 2: 生成薪资基础数据 ======
|
|
this.setStep(2, 'in_progress')
|
|
const empRes = await listEmployeeInfo({ dept: this.deptName, isLeave: 0, pageSize: 9999 })
|
|
const employees = empRes.rows || []
|
|
if (employees.length === 0) {
|
|
this.setStep(2, 'completed', '该部门下暂无在职员工')
|
|
this.setStep(3, 'completed', '无需生成')
|
|
this.running = false
|
|
return
|
|
}
|
|
|
|
const emptyJson = JSON.stringify({})
|
|
const salaryList = employees.map(emp => ({
|
|
employeeId: emp.employeeId || emp.infoId || '',
|
|
deptId: this.deptId,
|
|
period: this.period,
|
|
baseSalary: emp.baseSalary || 0,
|
|
perfBase: emp.perfBase || 0,
|
|
posCoeff: emp.posCoeffDefault || 0,
|
|
perfScore: 0,
|
|
perfCoeff: 0,
|
|
deptCoeff: deptCoeff,
|
|
fixedCoeff: 0,
|
|
adjCoeff: 0,
|
|
adjCoeffManual: 0,
|
|
totalCoeff: 0,
|
|
perfWage: 0,
|
|
deductionsJson: emptyJson,
|
|
deductionsTotal: 0,
|
|
bonusesJson: emptyJson,
|
|
bonusesTotal: 0,
|
|
otherAmount: 0,
|
|
actualSalary: 0,
|
|
fixedSalaryRef: '',
|
|
adjSalaryRef: '',
|
|
baoPerShiftRef: '',
|
|
baoSalaryRef: '',
|
|
totalSalaryRef: '',
|
|
status: 0,
|
|
remark: ''
|
|
}))
|
|
const batchRes = await batchAddPerfSalary(salaryList)
|
|
this.setStep(2, 'completed', `已为 ${batchRes.data || 0} 名员工生成薪资记录`)
|
|
|
|
// ====== Step 3: 生成绩效考核评分 ======
|
|
this.setStep(3, 'in_progress')
|
|
|
|
// 加载岗位模板
|
|
const tplRes = await listPerfPositionTemplate({ deptId: this.deptId })
|
|
const templates = tplRes.rows || []
|
|
const tplMap = {}
|
|
templates.forEach(t => { tplMap[t.positionName] = t })
|
|
|
|
// 按模板分组员工,并预加载维度
|
|
const tplDimensions = {} // templateId → dimensions[]
|
|
const appraisalList = [] // 最终批量插入数组
|
|
|
|
for (const emp of employees) {
|
|
const position = emp.jobType || emp.position || emp.posName || emp.postName || ''
|
|
const template = tplMap[position]
|
|
if (!template) continue
|
|
|
|
// 懒加载维度
|
|
if (!tplDimensions[template.id]) {
|
|
const dimRes = await listPerfTemplateDimension({ templateId: template.id })
|
|
tplDimensions[template.id] = (dimRes.rows || []).filter(d => d.isEnabled !== 0)
|
|
}
|
|
const dimensions = tplDimensions[template.id]
|
|
if (dimensions.length === 0) continue
|
|
|
|
// 构建嵌套明细
|
|
const details = dimensions.map(dim => ({
|
|
dimensionId: dim.id,
|
|
seq: dim.seq || '',
|
|
dimName: dim.dimName,
|
|
dimScore: 0,
|
|
targetValue: dim.targetValue || '100',
|
|
weight: dim.weight || '0',
|
|
quantitativeIndicator: dim.indicator || '',
|
|
scoreFormula: dim.scoreFormula || '',
|
|
scoreRule: dim.scoreRule || ''
|
|
}))
|
|
|
|
appraisalList.push({
|
|
employeeId: emp.employeeId || emp.infoId || '',
|
|
deptId: this.deptId,
|
|
period: this.period,
|
|
templateId: template.id,
|
|
finalScore: 0,
|
|
status: 0,
|
|
details: details
|
|
})
|
|
}
|
|
|
|
if (appraisalList.length > 0) {
|
|
await batchAddPerfAppraisal(appraisalList)
|
|
const totalDims = appraisalList.reduce((sum, a) => sum + (a.details ? a.details.length : 0), 0)
|
|
this.setStep(3, 'completed',
|
|
`已为 ${appraisalList.length} 名员工生成绩效考核(共 ${totalDims} 个维度)`)
|
|
} else {
|
|
this.setStep(3, 'completed', '无匹配的岗位模板,跳过考核生成')
|
|
}
|
|
|
|
// 完成
|
|
this.running = false
|
|
this.$emit('done')
|
|
} catch (err) {
|
|
// 找出当前进行中的步骤标记为错误
|
|
const activeIdx = this.steps.findIndex(s => s.status === 'in_progress')
|
|
if (activeIdx >= 0) {
|
|
this.setStep(activeIdx, 'error', '', (err && err.message) || '操作失败')
|
|
}
|
|
this.running = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.generate-progress {
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.step-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
padding: 10px 4px;
|
|
border-left: 2px solid #dcdfe6;
|
|
margin-left: 12px;
|
|
|
|
&.step--in_progress {
|
|
border-left-color: #409eff;
|
|
.step-icon { color: #409eff; }
|
|
}
|
|
&.step--completed {
|
|
border-left-color: #67c23a;
|
|
.step-icon { color: #67c23a; }
|
|
}
|
|
&.step--error {
|
|
border-left-color: #f56c6c;
|
|
.step-icon { color: #f56c6c; }
|
|
}
|
|
}
|
|
|
|
.step-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
line-height: 24px;
|
|
text-align: center;
|
|
margin-left: -14px;
|
|
margin-right: 10px;
|
|
font-size: 16px;
|
|
color: #c0c4cc;
|
|
background: #fff;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.step-body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.step-label {
|
|
font-size: 14px;
|
|
color: #303133;
|
|
line-height: 24px;
|
|
.step--pending & { color: #909399; }
|
|
}
|
|
|
|
.step-detail {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
margin-top: 2px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.step-error {
|
|
font-size: 12px;
|
|
color: #f56c6c;
|
|
margin-top: 2px;
|
|
line-height: 1.4;
|
|
}
|
|
</style>
|