214 lines
6.8 KiB
Vue
214 lines
6.8 KiB
Vue
|
|
<template>
|
||
|
|
<div class="hrm-page">
|
||
|
|
<section class="panel-grid triple">
|
||
|
|
<el-card class="metal-panel" shadow="hover">
|
||
|
|
<div slot="header" class="panel-header">
|
||
|
|
<span>薪酬方案</span>
|
||
|
|
<el-button size="mini" icon="el-icon-refresh" @click="loadPayPlan">刷新</el-button>
|
||
|
|
</div>
|
||
|
|
<el-table :data="payPlanList" v-loading="payPlanLoading" height="320" stripe>
|
||
|
|
<el-table-column label="方案" prop="planName" min-width="140" />
|
||
|
|
<el-table-column label="币种" prop="currency" min-width="90" />
|
||
|
|
<el-table-column label="状态" prop="status" min-width="100">
|
||
|
|
<template slot-scope="scope">
|
||
|
|
<el-tag :type="statusType(scope.row.status)">{{ scope.row.status || '-' }}</el-tag>
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column label="描述" prop="remark" min-width="160" show-overflow-tooltip />
|
||
|
|
</el-table>
|
||
|
|
</el-card>
|
||
|
|
|
||
|
|
<el-card class="metal-panel" shadow="hover">
|
||
|
|
<div slot="header" class="panel-header">
|
||
|
|
<span>薪酬批次</span>
|
||
|
|
<el-button size="mini" icon="el-icon-refresh" @click="loadPayRun">刷新</el-button>
|
||
|
|
</div>
|
||
|
|
<el-table :data="payRunList" v-loading="payRunLoading" height="320" stripe>
|
||
|
|
<el-table-column label="批次" prop="runName" min-width="140" />
|
||
|
|
<el-table-column label="周期" prop="period" min-width="120" />
|
||
|
|
<el-table-column label="状态" prop="status" min-width="100">
|
||
|
|
<template slot-scope="scope">
|
||
|
|
<el-tag :type="statusType(scope.row.status)">{{ scope.row.status || '-' }}</el-tag>
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
|
||
|
|
</el-table>
|
||
|
|
</el-card>
|
||
|
|
|
||
|
|
<el-card class="metal-panel" shadow="hover">
|
||
|
|
<div slot="header" class="panel-header">
|
||
|
|
<span>工资条 & 指标</span>
|
||
|
|
<div class="actions-inline">
|
||
|
|
<el-input
|
||
|
|
v-model="payslipQuery.batchNo"
|
||
|
|
placeholder="批次编号"
|
||
|
|
size="mini"
|
||
|
|
style="width: 150px"
|
||
|
|
clearable
|
||
|
|
@keyup.enter.native="loadPayslip"
|
||
|
|
/>
|
||
|
|
<el-button size="mini" type="primary" @click="loadPayslip">查询</el-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="dual-tables">
|
||
|
|
<div class="table-half">
|
||
|
|
<div class="table-title">工资条</div>
|
||
|
|
<el-table :data="payslipList" v-loading="payslipLoading" height="230" stripe>
|
||
|
|
<el-table-column label="员工" prop="empId" min-width="100" />
|
||
|
|
<el-table-column label="应发" prop="amountGross" min-width="100">
|
||
|
|
<template slot-scope="scope">¥{{ formatNumber(scope.row.amountGross) }}</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column label="实发" prop="amountNet" min-width="100">
|
||
|
|
<template slot-scope="scope">¥{{ formatNumber(scope.row.amountNet) }}</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column label="状态" prop="status" min-width="90">
|
||
|
|
<template slot-scope="scope">
|
||
|
|
<el-tag :type="statusType(scope.row.status)" size="mini">{{ scope.row.status || '-' }}</el-tag>
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
</el-table>
|
||
|
|
</div>
|
||
|
|
<div class="table-half">
|
||
|
|
<div class="table-title">指标快照</div>
|
||
|
|
<el-table :data="statList" v-loading="statLoading" height="230" stripe>
|
||
|
|
<el-table-column label="类型" prop="statType" min-width="120" />
|
||
|
|
<el-table-column label="维度" prop="dimension" min-width="120" show-overflow-tooltip />
|
||
|
|
<el-table-column label="数值" prop="value" min-width="100" />
|
||
|
|
<el-table-column label="日期" prop="statDate" min-width="120" />
|
||
|
|
</el-table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</el-card>
|
||
|
|
</section>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import { listPayPlan, listPayRun, listPayslip, listStatSnapshot } from '@/api/hrm'
|
||
|
|
|
||
|
|
export default {
|
||
|
|
name: 'HrmPayroll',
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
payPlanList: [],
|
||
|
|
payPlanLoading: false,
|
||
|
|
payRunList: [],
|
||
|
|
payRunLoading: false,
|
||
|
|
payslipList: [],
|
||
|
|
payslipLoading: false,
|
||
|
|
payslipQuery: { batchNo: '' },
|
||
|
|
statList: [],
|
||
|
|
statLoading: false
|
||
|
|
}
|
||
|
|
},
|
||
|
|
created() {
|
||
|
|
this.loadPayPlan()
|
||
|
|
this.loadPayRun()
|
||
|
|
this.loadPayslip()
|
||
|
|
this.loadStatSnapshot()
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
statusType(status) {
|
||
|
|
if (!status) return 'info'
|
||
|
|
const map = { pending: 'warning', draft: 'info', approved: 'success', rejected: 'danger', active: 'success', inactive: 'info' }
|
||
|
|
return map[status] || 'info'
|
||
|
|
},
|
||
|
|
formatNumber(num) {
|
||
|
|
if (num === null || num === undefined || isNaN(num)) return '0'
|
||
|
|
return Number(num).toLocaleString(undefined, { maximumFractionDigits: 2 })
|
||
|
|
},
|
||
|
|
loadPayPlan() {
|
||
|
|
this.payPlanLoading = true
|
||
|
|
listPayPlan({ pageNum: 1, pageSize: 50 })
|
||
|
|
.then(res => {
|
||
|
|
this.payPlanList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.payPlanLoading = false
|
||
|
|
})
|
||
|
|
},
|
||
|
|
loadPayRun() {
|
||
|
|
this.payRunLoading = true
|
||
|
|
listPayRun({ pageNum: 1, pageSize: 50 })
|
||
|
|
.then(res => {
|
||
|
|
this.payRunList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.payRunLoading = false
|
||
|
|
})
|
||
|
|
},
|
||
|
|
loadPayslip() {
|
||
|
|
this.payslipLoading = true
|
||
|
|
listPayslip({ pageNum: 1, pageSize: 50, batchNo: this.payslipQuery.batchNo })
|
||
|
|
.then(res => {
|
||
|
|
this.payslipList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.payslipLoading = false
|
||
|
|
})
|
||
|
|
},
|
||
|
|
loadStatSnapshot() {
|
||
|
|
this.statLoading = true
|
||
|
|
listStatSnapshot({ pageNum: 1, pageSize: 50 })
|
||
|
|
.then(res => {
|
||
|
|
this.statList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.statLoading = false
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
.hrm-page {
|
||
|
|
padding: 16px 20px 32px;
|
||
|
|
background: #f8f9fb;
|
||
|
|
}
|
||
|
|
.panel-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(3, 1fr);
|
||
|
|
gap: 14px;
|
||
|
|
}
|
||
|
|
.metal-panel {
|
||
|
|
border: 1px solid #d7d9df;
|
||
|
|
border-radius: 10px;
|
||
|
|
background: #fff;
|
||
|
|
}
|
||
|
|
.panel-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #303133;
|
||
|
|
}
|
||
|
|
.actions-inline {
|
||
|
|
display: flex;
|
||
|
|
gap: 8px;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
.dual-tables {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr;
|
||
|
|
gap: 10px;
|
||
|
|
}
|
||
|
|
.table-half {
|
||
|
|
border: 1px dashed #e6e8ed;
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 8px;
|
||
|
|
}
|
||
|
|
.table-title {
|
||
|
|
font-weight: 600;
|
||
|
|
margin-bottom: 6px;
|
||
|
|
}
|
||
|
|
@media (max-width: 1200px) {
|
||
|
|
.panel-grid {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
.dual-tables {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|