Files
klp-oa/klp-ui/src/views/perf/index/GenerateDialog.vue

348 lines
11 KiB
Vue
Raw Normal View History

<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>