绩效统计页面开发
This commit is contained in:
@@ -1,57 +1,92 @@
|
||||
<template>
|
||||
<div class="perf-config-panel">
|
||||
<!-- Section 0: 考核参数 -->
|
||||
<div class="config-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">考核参数</span>
|
||||
<el-button size="small" type="primary" :loading="cfgSubmitting" @click="handleSaveCfg">{{ cfgId ? '保存' : '新增' }}</el-button>
|
||||
<el-card class="config-card mb8" shadow="never">
|
||||
<div slot="header" class="card-header">
|
||||
<div class="card-left">
|
||||
<span class="card-title">考核参数</span>
|
||||
<el-tag size="mini" :type="cfgStatus.type" class="cfg-status">{{ cfgStatus.text }}</el-tag>
|
||||
</div>
|
||||
<el-button size="mini" :type="cfgBtnType" :loading="cfgSubmitting" @click="handleSaveCfg">{{ cfgBtnText }}</el-button>
|
||||
</div>
|
||||
<div class="cfg-form" v-loading="cfgLoading">
|
||||
<el-form v-if="cfgReady" ref="cfgFormRef" :model="cfgForm" :rules="cfgRules" label-width="110px" size="small" inline>
|
||||
<el-form-item label="月产量目标" prop="monthlyProductionTarget">
|
||||
<el-input v-model="cfgForm.monthlyProductionTarget" placeholder="月产量目标" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产量单位" prop="productionUnit">
|
||||
<el-select v-model="cfgForm.productionUnit" style="width: 80px">
|
||||
<el-option label="吨" value="吨" />
|
||||
<el-option label="m" value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="绩效单价(元)" prop="perfUnitPrice">
|
||||
<el-input v-model="cfgForm.perfUnitPrice" placeholder="绩效单价" style="width: 110px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="生产班数" prop="productionShifts">
|
||||
<el-input v-model="cfgForm.productionShifts" placeholder="班数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="保底班数" prop="guaranteedShifts">
|
||||
<el-input v-model="cfgForm.guaranteedShifts" placeholder="保底班数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="车间系数" prop="deptCoeff">
|
||||
<el-input v-model="cfgForm.deptCoeff" placeholder="系数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="薪资模式" prop="salaryMode">
|
||||
<el-radio-group v-model="cfgForm.salaryMode" size="small">
|
||||
<el-radio :label="0">普通</el-radio>
|
||||
<el-radio :label="1">保底</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="保底薪资除数" prop="baseDivisor" v-if="cfgForm.salaryMode === 1">
|
||||
<el-input v-model="cfgForm.baseDivisor" placeholder="除数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="cfgForm.remark" placeholder="备注" style="width: 200px" />
|
||||
</el-form-item>
|
||||
<div v-loading="cfgLoading" class="card-body">
|
||||
<el-form v-if="cfgReady" ref="cfgFormRef" :model="cfgForm" :rules="cfgRules" label-width="96px" size="small" class="cfg-form">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="月产量目标" prop="monthlyProductionTarget">
|
||||
<el-input v-model="cfgForm.monthlyProductionTarget" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="产量单位" prop="productionUnit">
|
||||
<el-select v-model="cfgForm.productionUnit" placeholder="请选择" style="width:100%">
|
||||
<el-option label="吨" value="吨" />
|
||||
<el-option label="m" value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="绩效单价" prop="perfUnitPrice">
|
||||
<el-input v-model="cfgForm.perfUnitPrice" placeholder="元" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="生产班数" prop="productionShifts">
|
||||
<el-input v-model="cfgForm.productionShifts" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="保底班数" prop="guaranteedShifts">
|
||||
<el-input v-model="cfgForm.guaranteedShifts" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="车间系数" prop="deptCoeff">
|
||||
<el-input v-model="cfgForm.deptCoeff" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="薪资模式" prop="salaryMode">
|
||||
<el-radio-group v-model="cfgForm.salaryMode" size="small">
|
||||
<el-radio :label="0">普通</el-radio>
|
||||
<el-radio :label="1">保底</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="保底除数" prop="baseDivisor">
|
||||
<el-input
|
||||
:value="cfgForm.salaryMode === 1 ? cfgForm.baseDivisor : '普通模式不可编辑'"
|
||||
placeholder="请输入"
|
||||
:disabled="cfgForm.salaryMode !== 1"
|
||||
@input="val => cfgForm.baseDivisor = val"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="cfgForm.remark" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- Section 1: 扣款/奖励项目配置 -->
|
||||
<div class="config-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">扣款/奖励项目配置</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddItem">新增项目</el-button>
|
||||
<el-card class="config-card mb8" shadow="never">
|
||||
<div slot="header" class="card-header">
|
||||
<span class="card-title">扣款/奖励项目配置</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="handleAddItem">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="itemList" stripe size="small" v-loading="itemLoading" empty-text="暂无项目配置">
|
||||
<div class="card-body">
|
||||
<el-table :data="itemList" stripe size="mini" v-loading="itemLoading" empty-text="暂无项目配置">
|
||||
<el-table-column label="类型" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.itemType === 2 ? 'success' : 'danger'" size="small">
|
||||
@@ -70,20 +105,23 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditItem(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" style="color: #f56c6c" @click="handleDelItem(scope.row)">删除</el-button>
|
||||
<div class="op-actions">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditItem(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" style="color: #f56c6c" @click="handleDelItem(scope.row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- Section 2: 岗位绩效考核模板 -->
|
||||
<div class="config-section" v-loading="ctxLoading">
|
||||
<div class="section-header">
|
||||
<span class="section-title">岗位绩效考核模板 <span class="section-hint">右键 tab 可编辑或删除</span></span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddTemplate">新增岗位</el-button>
|
||||
<el-card class="config-card" shadow="never" v-loading="ctxLoading">
|
||||
<div slot="header" class="card-header">
|
||||
<span class="card-title">岗位绩效考核模板 <span class="card-hint">右键岗位可编辑或删除</span></span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="handleAddTemplate">新增</el-button>
|
||||
</div>
|
||||
<div v-if="templateList.length" class="template-tabs">
|
||||
<span
|
||||
@@ -100,9 +138,9 @@
|
||||
<div v-if="activeTpl" class="dim-section">
|
||||
<div class="dim-toolbar">
|
||||
<span class="dim-subtitle">{{ activeTpl.positionName }} — 考核维度</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddDim">新增维度</el-button>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="handleAddDim">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="dimList" stripe size="small" v-loading="dimLoading" empty-text="暂无维度配置">
|
||||
<el-table :data="dimList" stripe size="mini" v-loading="dimLoading" empty-text="暂无维度配置">
|
||||
<el-table-column prop="seq" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="dimName" label="考核维度" min-width="120" />
|
||||
<el-table-column prop="weight" label="权重" width="70" align="center" />
|
||||
@@ -114,16 +152,18 @@
|
||||
<el-switch v-model="scope.row.isEnabled" :active-value="1" :inactive-value="0" size="small" @change="handleToggleDimEnabled(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<el-table-column label="操作" width="140" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditDim(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" style="color: #f56c6c" @click="handleDelDim(scope.row)">删除</el-button>
|
||||
<div class="op-actions">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditDim(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" style="color: #f56c6c" @click="handleDelDim(scope.row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-else class="dim-empty">点击 tab 查看维度,右键 tab 可编辑或删除模板</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<div
|
||||
@@ -160,7 +200,7 @@
|
||||
<el-input v-model="itemForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="itemDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="itemSubmitting" @click="submitItem">保存</el-button>
|
||||
</div>
|
||||
@@ -179,7 +219,7 @@
|
||||
<el-input v-model="templateForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="templateDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="templateSubmitting" @click="submitTemplate">保存</el-button>
|
||||
</div>
|
||||
@@ -234,7 +274,7 @@
|
||||
<el-input v-model="dimForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dimFormVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="dimSubmitting" @click="submitDim">保存</el-button>
|
||||
</div>
|
||||
@@ -262,7 +302,13 @@ export default {
|
||||
cfgLoading: false,
|
||||
cfgSubmitting: false,
|
||||
cfgReady: false,
|
||||
// cfgId 存在表示该部门该月份已存在配置记录;为空表示首次配置(未落库)
|
||||
cfgId: null,
|
||||
// 方案C:通过“快照对比”识别是否改动未保存
|
||||
cfgSnapshot: '',
|
||||
cfgDirty: false,
|
||||
// 后端回填表单时锁定快照对比,避免回填过程误判为“已修改未保存”
|
||||
cfgSnapshotLock: false,
|
||||
cfgForm: { monthlyProductionTarget: '', productionUnit: '吨', perfUnitPrice: '', productionShifts: '', guaranteedShifts: '', deptCoeff: '', salaryMode: 0, baseDivisor: '', remark: '' },
|
||||
cfgRules: {
|
||||
monthlyProductionTarget: [{ required: true, message: '请输入月产量目标', trigger: 'blur' }],
|
||||
@@ -315,13 +361,48 @@ export default {
|
||||
computed: {
|
||||
activeTpl() {
|
||||
return this.templateList.find(t => String(t.id) === this.activeTemplateId) || null
|
||||
},
|
||||
cfgStatus() {
|
||||
if (this.cfgDirty) return { type: 'danger', text: '已修改未保存' }
|
||||
if (this.cfgId) return { type: 'success', text: '已保存' }
|
||||
return { type: 'warning', text: '未保存(首次配置)' }
|
||||
},
|
||||
cfgBtnText() {
|
||||
return this.cfgId ? '保存修改' : '新增并保存'
|
||||
},
|
||||
cfgBtnType() {
|
||||
return this.cfgDirty ? 'danger' : 'primary'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptId: { immediate: true, handler: 'loadAll' },
|
||||
period: 'loadAll'
|
||||
period: 'loadAll',
|
||||
cfgForm: {
|
||||
deep: true,
|
||||
handler() {
|
||||
// 表单未就绪或正在回填时,不做“脏状态”判断
|
||||
if (!this.cfgReady) return
|
||||
if (this.cfgSnapshotLock) return
|
||||
this.cfgDirty = this.calcCfgSnapshot() !== this.cfgSnapshot
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 将考核参数表单按字段归一化,用于快照比对(避免 undefined/null 造成误判)
|
||||
calcCfgSnapshot() {
|
||||
const f = this.cfgForm || {}
|
||||
return JSON.stringify({
|
||||
monthlyProductionTarget: f.monthlyProductionTarget ?? '',
|
||||
productionUnit: f.productionUnit ?? '',
|
||||
perfUnitPrice: f.perfUnitPrice ?? '',
|
||||
productionShifts: f.productionShifts ?? '',
|
||||
guaranteedShifts: f.guaranteedShifts ?? '',
|
||||
deptCoeff: f.deptCoeff ?? '',
|
||||
salaryMode: f.salaryMode ?? 0,
|
||||
baseDivisor: f.baseDivisor ?? '',
|
||||
remark: f.remark ?? ''
|
||||
})
|
||||
},
|
||||
loadAll() {
|
||||
if (!this.deptId) return
|
||||
this.loadDeptConfig()
|
||||
@@ -332,6 +413,8 @@ export default {
|
||||
loadDeptConfig() {
|
||||
this.cfgLoading = true
|
||||
this.cfgReady = false
|
||||
this.cfgSnapshotLock = true
|
||||
// 约定:后端按 month=YYYY-MM-01 查询,前端 period 为 YYYY-MM
|
||||
listPerfDeptConfig({ deptId: this.deptId, month: this.period + '-01' }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
@@ -353,13 +436,22 @@ export default {
|
||||
this.cfgForm = { monthlyProductionTarget: '', productionUnit: '吨', perfUnitPrice: '', productionShifts: '', guaranteedShifts: '', deptCoeff: '', salaryMode: 0, baseDivisor: '', remark: '' }
|
||||
}
|
||||
this.cfgReady = true
|
||||
}).finally(() => { this.cfgLoading = false })
|
||||
this.$nextTick(() => {
|
||||
// 表单回填完成后建立快照,并重置“脏状态”
|
||||
this.cfgSnapshot = this.calcCfgSnapshot()
|
||||
this.cfgDirty = false
|
||||
this.cfgSnapshotLock = false
|
||||
})
|
||||
}).finally(() => {
|
||||
this.cfgLoading = false
|
||||
})
|
||||
},
|
||||
handleSaveCfg() {
|
||||
this.$refs.cfgFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.cfgSubmitting = true
|
||||
const data = { ...this.cfgForm, deptId: this.deptId, month: this.period + '-01' }
|
||||
// cfgId 存在走更新;不存在走新增(首次保存)
|
||||
const api = this.cfgId ? updatePerfDeptConfig : addPerfDeptConfig
|
||||
if (this.cfgId) data.id = this.cfgId
|
||||
api(data).then(() => {
|
||||
@@ -539,44 +631,59 @@ export default {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.config-section {
|
||||
background-color: #fff;
|
||||
margin-bottom: 12px;
|
||||
.config-card {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
border: 1px solid #ebeef5;
|
||||
::v-deep .el-card {
|
||||
border-radius: 0;
|
||||
}
|
||||
::v-deep .el-card__header {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
::v-deep .el-card__body {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
.card-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cfg-status {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-hint {
|
||||
.card-hint {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.cfg-form {
|
||||
padding: 12px;
|
||||
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 8px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.card-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::v-deep .el-table {
|
||||
border: none;
|
||||
&::before, &::after { display: none; }
|
||||
.cfg-form {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义模板 tabs */
|
||||
@@ -584,17 +691,17 @@ export default {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
padding: 0 0 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.template-tab {
|
||||
display: inline-block;
|
||||
padding: 4px 14px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
background: #f4f4f5;
|
||||
border-radius: 12px;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.15s;
|
||||
@@ -617,14 +724,14 @@ export default {
|
||||
|
||||
/* 维度区域 */
|
||||
.dim-section {
|
||||
padding: 8px 0 0;
|
||||
padding: 10px 0 0;
|
||||
}
|
||||
|
||||
.dim-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px 8px;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
.dim-subtitle {
|
||||
@@ -645,8 +752,8 @@ export default {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.12);
|
||||
padding: 4px 0;
|
||||
min-width: 120px;
|
||||
@@ -666,4 +773,16 @@ export default {
|
||||
&:hover { background-color: #fef0f0; }
|
||||
}
|
||||
}
|
||||
|
||||
.op-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.op-actions ::v-deep .el-button--text {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-input v-model="query.employeeId" size="small" placeholder="员工ID" clearable style="width: 140px" @keyup.enter.native="loadList" />
|
||||
<el-select v-model="query.status" size="small" placeholder="状态" clearable style="width: 110px" @change="loadList">
|
||||
<!-- 状态筛选:当前阶段为保证统计页能直接聚合展示,临时停用 status 参数筛选 -->
|
||||
<!-- <el-select v-model="query.status" size="small" placeholder="状态" clearable style="width: 110px" @change="loadList">
|
||||
<el-option label="草稿" :value="'0'" />
|
||||
<el-option label="已确认" :value="'1'" />
|
||||
<el-option label="已归档" :value="'2'" />
|
||||
</el-select>
|
||||
</el-select> -->
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="loadList">查询</el-button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
@@ -22,11 +23,12 @@
|
||||
<el-table :data="list" stripe size="small" v-loading="loading" empty-text="暂无薪资记录" class="inline-edit-table" border>
|
||||
<!-- 只读列 -->
|
||||
<!-- <el-table-column prop="employeeId" label="员工ID" width="90" /> -->
|
||||
<el-table-column prop="status" label="状态" width="70" align="center">
|
||||
<!-- 状态列:同上,临时停用 -->
|
||||
<!-- <el-table-column prop="status" label="状态" width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusType(scope.row.status)" size="small">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column> -->
|
||||
|
||||
<!-- 薪资基数 -->
|
||||
<el-table-column label="底薪" width="90" align="right"><template slot-scope="s">
|
||||
@@ -166,7 +168,7 @@ export default {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
employeeId: '',
|
||||
status: ''
|
||||
// status: ''
|
||||
},
|
||||
deductionItems: [],
|
||||
bonusItems: [],
|
||||
@@ -181,12 +183,12 @@ export default {
|
||||
fmt(val) {
|
||||
return val != null ? val : '-'
|
||||
},
|
||||
statusType(status) {
|
||||
return { '0': 'info', '1': 'warning', '2': 'success' }[status] || 'info'
|
||||
},
|
||||
statusLabel(status) {
|
||||
return { '0': '草稿', '1': '已确认', '2': '已归档' }[status] || '草稿'
|
||||
},
|
||||
// statusType(status) {
|
||||
// return { '0': 'info', '1': 'warning', '2': 'success' }[status] || 'info'
|
||||
// },
|
||||
// statusLabel(status) {
|
||||
// return { '0': '草稿', '1': '已确认', '2': '已归档' }[status] || '草稿'
|
||||
// },
|
||||
onDeptChange() {
|
||||
this.loadItemConfig()
|
||||
this.loadList()
|
||||
@@ -236,8 +238,7 @@ export default {
|
||||
pageSize: this.query.pageSize,
|
||||
deptId: this.deptId,
|
||||
period: this.period,
|
||||
employeeId: this.query.employeeId || undefined,
|
||||
status: this.query.status || undefined
|
||||
employeeId: this.query.employeeId || undefined
|
||||
}
|
||||
listPerfSalary(params).then(res => {
|
||||
this.list = (res.rows || []).map(r => {
|
||||
|
||||
@@ -7,113 +7,373 @@
|
||||
</div>
|
||||
|
||||
<template v-if="summary">
|
||||
<!-- Overview Cards -->
|
||||
<div class="stat-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">总人数</div>
|
||||
<div class="stat-value">{{ fmt(summary.totalEmployees) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">系数总计</div>
|
||||
<div class="stat-value">{{ fmt(summary.totalCoeffSum) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">平均考核得分</div>
|
||||
<div class="stat-value highlight">{{ fmt(summary.avgPerfScore) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">平均绩效系数</div>
|
||||
<div class="stat-value">{{ fmt(summary.avgPerfCoeff) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">月产量</div>
|
||||
<div class="stat-value">{{ fmt(summary.monthlyProduction) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">系数单价</div>
|
||||
<div class="stat-value highlight">{{ fmt(summary.unitPrice) }}</div>
|
||||
</div>
|
||||
<div class="page-title">
|
||||
<span>{{ titleText }}</span>
|
||||
<el-button size="mini" type="primary" :loading="saving" @click="saveSummary">保存汇总</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Salary Breakdown Card -->
|
||||
<div class="detail-card">
|
||||
<div class="card-title">薪资构成</div>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">底薪合计</span>
|
||||
<span class="detail-value">{{ fmt(summary.totalBaseSalary) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">绩效工资合计</span>
|
||||
<span class="detail-value highlight">{{ fmt(summary.totalPerfWage) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">扣款合计</span>
|
||||
<span class="detail-value danger">{{ fmt(summary.totalDeductions) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">奖励合计</span>
|
||||
<span class="detail-value success">{{ fmt(summary.totalBonuses) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">其他合计</span>
|
||||
<span class="detail-value">{{ fmt(summary.totalOther) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">实领薪资合计</span>
|
||||
<span class="detail-value total">{{ fmt(summary.totalActualSalary) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">人均实领薪资</span>
|
||||
<span class="detail-value highlight">{{ fmt(summary.avgActualSalary) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="4">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ fmtInt(summary.totalEmployees) }}</div>
|
||||
<div class="stat-label">总人数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value stat-value--primary">{{ fmtMoney(payableSalary) }}</div>
|
||||
<div class="stat-label">应发总薪</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value stat-value--primary">{{ fmtMoney(summary.totalActualSalary) }}</div>
|
||||
<div class="stat-label">实发总薪</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ fmtMoney(summary.avgActualSalary) }}</div>
|
||||
<div class="stat-label">人均薪资</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value stat-value--danger">{{ fmtMoney(summary.totalDeductions) }}</div>
|
||||
<div class="stat-label">扣款总额</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value stat-value--success">{{ fmtMoney(summary.totalBonuses) }}</div>
|
||||
<div class="stat-label">奖励总额</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="status-bar">
|
||||
<span class="status-label">状态</span>
|
||||
<el-tag :type="summary.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ summary.status === 1 ? '已确认' : '草稿' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never" class="mini-card">
|
||||
<div class="mini-row">
|
||||
<span class="mini-label">薪资记录数</span>
|
||||
<span class="mini-value">{{ fmtInt(salaryStats.total) }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never" class="mini-card">
|
||||
<div class="mini-row">
|
||||
<span class="mini-label">当月确认数</span>
|
||||
<span class="mini-value">{{ fmtInt(salaryStats.confirmed) }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never" class="mini-card">
|
||||
<div class="mini-row">
|
||||
<span class="mini-label">状态</span>
|
||||
<span class="mini-value">{{ summary.status === 1 ? '已确认' : '草稿' }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card shadow="never" class="chart-card">
|
||||
<div slot="header" class="chart-header">
|
||||
<span class="chart-title">薪资分布</span>
|
||||
</div>
|
||||
<div ref="salaryChart" class="salary-chart" />
|
||||
</el-card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPerfDeptSummary } from '@/api/perf/deptSummary'
|
||||
import { listPerfSalary } from '@/api/perf/salary'
|
||||
import { listEmployeeInfo } from '@/api/wms/employeeInfo'
|
||||
import * as echarts from 'echarts'
|
||||
import { savePerfDeptSummaryByPeriod } from '@/api/perf/deptSummary'
|
||||
|
||||
export default {
|
||||
name: 'SummaryPanel',
|
||||
props: {
|
||||
deptId: { type: [Number, String], default: null },
|
||||
deptName: { type: String, default: '' },
|
||||
period: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
summary: null
|
||||
saving: false,
|
||||
// 汇总数据来源:
|
||||
// 1) 优先后端汇总表 perf_dept_summary
|
||||
// 2) 若汇总表为空但薪资明细有数据,则在前端基于 perf_salary 聚合生成 summary
|
||||
summary: null,
|
||||
// 薪资明细列表(用于图表展示、以及汇总表缺失时的前端聚合兜底)
|
||||
salaryList: [],
|
||||
// 员工ID -> 员工姓名 映射(用于图表显示更友好)
|
||||
employeeNameMap: {},
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
titleText() {
|
||||
const name = this.deptName || '车间'
|
||||
return `${name} · ${this.period} 车间汇总`
|
||||
},
|
||||
// 应发总薪(展示层计算口径)
|
||||
payableSalary() {
|
||||
if (!this.summary) return null
|
||||
const base = this.toNumber(this.summary.totalBaseSalary)
|
||||
const perf = this.toNumber(this.summary.totalPerfWage)
|
||||
const ded = this.toNumber(this.summary.totalDeductions)
|
||||
const bon = this.toNumber(this.summary.totalBonuses)
|
||||
const oth = this.toNumber(this.summary.totalOther)
|
||||
return base + perf + bon + oth - ded
|
||||
},
|
||||
salaryStats() {
|
||||
const total = this.salaryList.length
|
||||
// 当前阶段已临时停用“状态”筛选与状态列,因此确认数直接按总数展示
|
||||
const confirmed = total
|
||||
return { total, confirmed }
|
||||
},
|
||||
salaryChartData() {
|
||||
// 取实领薪资 Top12(从低到高展示),用于横向柱状图
|
||||
const list = (this.salaryList || []).map(r => {
|
||||
const id = r.employeeId != null ? String(r.employeeId) : ''
|
||||
const name = this.employeeNameMap[id] || id || '-'
|
||||
return {
|
||||
name,
|
||||
value: this.toNumber(r.actualSalary)
|
||||
}
|
||||
})
|
||||
const top = list.sort((a, b) => b.value - a.value).slice(0, 12)
|
||||
top.reverse()
|
||||
return top
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptId: { immediate: true, handler: 'loadSummary' },
|
||||
period: 'loadSummary'
|
||||
deptId: { immediate: true, handler: 'loadAll' },
|
||||
period: 'loadAll',
|
||||
deptName: 'loadAll',
|
||||
salaryChartData() {
|
||||
this.$nextTick(() => this.renderChart())
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fmt(val) {
|
||||
return val != null ? val : '-'
|
||||
toNumber(val) {
|
||||
if (val == null || val === '') return 0
|
||||
const n = typeof val === 'number' ? val : parseFloat(val)
|
||||
return isNaN(n) ? 0 : n
|
||||
},
|
||||
loadSummary() {
|
||||
fmtInt(val) {
|
||||
if (val == null || val === '') return '-'
|
||||
const n = typeof val === 'number' ? val : parseFloat(val)
|
||||
if (isNaN(n)) return '-'
|
||||
return String(Math.round(n))
|
||||
},
|
||||
fmtMoney(val) {
|
||||
if (val == null || val === '') return '-'
|
||||
const n = this.toNumber(val)
|
||||
return new Intl.NumberFormat('zh-CN', {
|
||||
style: 'currency',
|
||||
currency: 'CNY',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2
|
||||
}).format(n)
|
||||
},
|
||||
handleResize() {
|
||||
if (this.chart) this.chart.resize()
|
||||
},
|
||||
// 将当前页面展示的汇总数据落库到 perf_dept_summary
|
||||
// 后端按 deptId + period 做新增或更新(无须前端关心是否已有记录)
|
||||
saveSummary() {
|
||||
if (!this.summary) return
|
||||
this.saving = true
|
||||
const payload = {
|
||||
id: this.summary.id,
|
||||
deptId: this.deptId,
|
||||
period: this.period,
|
||||
totalEmployees: this.summary.totalEmployees,
|
||||
totalCoeffSum: this.summary.totalCoeffSum,
|
||||
avgPerfScore: this.summary.avgPerfScore,
|
||||
avgPerfCoeff: this.summary.avgPerfCoeff,
|
||||
totalBaseSalary: this.summary.totalBaseSalary,
|
||||
totalPerfWage: this.summary.totalPerfWage,
|
||||
totalDeductions: this.summary.totalDeductions,
|
||||
totalBonuses: this.summary.totalBonuses,
|
||||
totalOther: this.summary.totalOther,
|
||||
totalActualSalary: this.summary.totalActualSalary,
|
||||
avgActualSalary: this.summary.avgActualSalary,
|
||||
monthlyProduction: this.summary.monthlyProduction,
|
||||
unitPrice: this.summary.unitPrice,
|
||||
status: this.summary.status,
|
||||
remark: this.summary.remark
|
||||
}
|
||||
savePerfDeptSummaryByPeriod(payload).then(() => {
|
||||
this.$message.success('保存成功')
|
||||
this.loadAll()
|
||||
}).finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
},
|
||||
loadAll() {
|
||||
if (!this.deptId || !this.period) return
|
||||
this.loading = true
|
||||
this.summary = null
|
||||
listPerfDeptSummary({ deptId: this.deptId, period: this.period }).then(res => {
|
||||
const rows = res.rows || []
|
||||
this.salaryList = []
|
||||
this.employeeNameMap = {}
|
||||
|
||||
// p1: 汇总表(可能为空)
|
||||
// p2: 薪资明细(用于图表,也用于汇总表缺失时的前端聚合)
|
||||
// p3: 员工列表(用于 employeeId -> name 的映射,缺 deptName 时跳过)
|
||||
const p1 = listPerfDeptSummary({ deptId: this.deptId, period: this.period })
|
||||
const p2 = listPerfSalary({ deptId: this.deptId, period: this.period, pageSize: 9999 })
|
||||
const p3 = this.deptName
|
||||
? listEmployeeInfo({ dept: this.deptName, pageSize: 9999 })
|
||||
: Promise.resolve({ rows: [] })
|
||||
|
||||
Promise.all([p1, p2, p3]).then(([summaryRes, salaryRes, empRes]) => {
|
||||
const rows = summaryRes.rows || []
|
||||
this.summary = rows.length > 0 ? rows[0] : null
|
||||
|
||||
this.salaryList = salaryRes.rows || []
|
||||
|
||||
const map = {}
|
||||
const empRows = empRes.rows || []
|
||||
empRows.forEach(e => {
|
||||
const id = e.employeeId != null ? String(e.employeeId) : (e.infoId != null ? String(e.infoId) : '')
|
||||
if (id) map[id] = e.name || e.nickName || e.userName || id
|
||||
})
|
||||
this.employeeNameMap = map
|
||||
|
||||
// 后端汇总表未生成时,直接从薪资明细聚合出汇总,保证统计页可用
|
||||
if (!this.summary && this.salaryList.length) {
|
||||
this.summary = this.buildSummaryFromSalary()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
this.$nextTick(() => this.renderChart())
|
||||
})
|
||||
},
|
||||
// 前端聚合汇总(仅在 perf_dept_summary 为空时触发)
|
||||
buildSummaryFromSalary() {
|
||||
const salaryList = this.salaryList || []
|
||||
const empSet = new Set()
|
||||
let totalBaseSalary = 0
|
||||
let totalPerfWage = 0
|
||||
let totalDeductions = 0
|
||||
let totalBonuses = 0
|
||||
let totalOther = 0
|
||||
let totalActualSalary = 0
|
||||
|
||||
salaryList.forEach(r => {
|
||||
if (r.employeeId != null) empSet.add(String(r.employeeId))
|
||||
totalBaseSalary += this.toNumber(r.baseSalary)
|
||||
totalPerfWage += this.toNumber(r.perfWage)
|
||||
totalDeductions += this.toNumber(r.deductionsTotal)
|
||||
totalBonuses += this.toNumber(r.bonusesTotal)
|
||||
totalOther += this.toNumber(r.otherAmount)
|
||||
totalActualSalary += this.toNumber(r.actualSalary)
|
||||
})
|
||||
|
||||
const totalEmployees = empSet.size || this.toNumber(this.salaryList.length)
|
||||
const avgActualSalary = totalEmployees ? totalActualSalary / totalEmployees : 0
|
||||
return {
|
||||
deptId: this.deptId,
|
||||
period: this.period,
|
||||
totalEmployees,
|
||||
totalCoeffSum: 0,
|
||||
avgPerfScore: 0,
|
||||
avgPerfCoeff: 0,
|
||||
totalBaseSalary,
|
||||
totalPerfWage,
|
||||
totalDeductions,
|
||||
totalBonuses,
|
||||
totalOther,
|
||||
totalActualSalary,
|
||||
avgActualSalary,
|
||||
monthlyProduction: 0,
|
||||
unitPrice: 0,
|
||||
status: 0
|
||||
}
|
||||
},
|
||||
renderChart() {
|
||||
if (!this.summary) return
|
||||
const el = this.$refs.salaryChart
|
||||
if (!el) return
|
||||
if (!this.chart) {
|
||||
this.chart = echarts.init(el)
|
||||
}
|
||||
|
||||
const data = this.salaryChartData
|
||||
if (!data.length) {
|
||||
this.chart.clear()
|
||||
return
|
||||
}
|
||||
|
||||
this.chart.setOption({
|
||||
grid: { left: 120, right: 24, top: 10, bottom: 10, containLabel: true },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
formatter: params => {
|
||||
const p = Array.isArray(params) ? params[0] : params
|
||||
return `${p.name}<br/>${this.fmtMoney(p.value)}`
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: v => {
|
||||
const n = this.toNumber(v)
|
||||
return n >= 10000 ? (n / 10000).toFixed(1) + 'w' : String(n)
|
||||
}
|
||||
},
|
||||
splitLine: { lineStyle: { color: '#f0f2f5' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: data.map(d => d.name),
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: false },
|
||||
axisLabel: { color: '#606266', fontSize: 12 }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: data.map(d => d.value),
|
||||
barWidth: 14,
|
||||
itemStyle: {
|
||||
color: '#409eff',
|
||||
borderRadius: [2, 2, 2, 2]
|
||||
}
|
||||
}
|
||||
]
|
||||
}, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,8 +383,7 @@ export default {
|
||||
.summary-panel {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: #f5f7fa;
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
@@ -136,105 +395,109 @@ export default {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.stat-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
.page-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 4px 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px 16px;
|
||||
box-shadow: none;
|
||||
border: 1px solid #ebeef5;
|
||||
::v-deep .el-card__header {
|
||||
border-bottom: none;
|
||||
}
|
||||
::v-deep .el-card__body {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.highlight {
|
||||
color: #409eff;
|
||||
.stat-value--primary {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.stat-value--danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.stat-value--success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.mini-card {
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
border: 1px solid #ebeef5;
|
||||
::v-deep .el-card__header {
|
||||
border-bottom: none;
|
||||
}
|
||||
::v-deep .el-card__body {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
.mini-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 12px;
|
||||
.mini-label {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
|
||||
&.highlight {
|
||||
color: #409eff;
|
||||
.chart-card {
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
::v-deep .el-card__header {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.total {
|
||||
color: #e6a23c;
|
||||
font-size: 18px;
|
||||
::v-deep .el-card__body {
|
||||
padding: 12px;
|
||||
}
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
.chart-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
.chart-title {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.salary-chart {
|
||||
height: 320px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="panel-container" v-if="currentDept">
|
||||
<PerfConfigPanel v-if="activeTab === 'perfConfig'" :dept-id="currentDept.id" :period="periodStr" />
|
||||
<SalaryPanel v-if="activeTab === 'salary'" :dept-id="currentDept.id" :dept-name="currentDept.deptName" :period="periodStr" />
|
||||
<SummaryPanel v-if="activeTab === 'summary'" :dept-id="currentDept.id" :period="periodStr" />
|
||||
<SummaryPanel v-if="activeTab === 'summary'" :dept-id="currentDept.id" :dept-name="currentDept.deptName" :period="periodStr" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user