修复快捷工具无法显示

This commit is contained in:
hero920103 committed 2026-10-30 17:29:28 +08:00
1 parent 9898962836
commit 2e2b837a20
23 files changed
+1941 -38

No files matched your search

@@ -238,6 +238,7 @@
<el-table :data="schemes" max-height="420">
<el-table-column prop="scheme_name" label="方案名称" min-width="150" />
<el-table-column label="员工" width="130"><template #default="{ row }">{{ employeeName(row.employee_id) }}</template></el-table-column>
<el-table-column label="职级" width="130"><template #default="{ row }"><el-tag v-if="row.rank_name" size="small" type="primary">{{ row.rank_name }}</el-tag><span v-else>-</span></template></el-table-column>
<el-table-column prop="effective_date" label="生效日期" width="115" />
<el-table-column prop="expiry_date" label="失效日期" width="115"><template #default="{ row }">{{ row.expiry_date || "长期" }}</template></el-table-column>
<el-table-column label="固定月薪" width="130" align="right"><template #default="{ row }">¥{{ money(fixedSchemeAmount(row)) }}</template></el-table-column>
@@ -251,7 +252,13 @@
<el-form ref="schemeFormRef" :model="schemeForm" :rules="schemeRules" label-width="120px">
<div class="form-grid">
<el-form-item label="方案名称" prop="scheme_name"><el-input v-model="schemeForm.scheme_name" /></el-form-item>
<el-form-item label="适用员工" prop="employee_id"><el-select v-model="schemeForm.employee_id" filterable style="width: 100%"><el-option v-for="employee in employees" :key="employee.id" :label="employeeLabel(employee)" :value="employee.id" /></el-select></el-form-item>
<el-form-item label="适用员工" prop="employee_id"><el-select v-model="schemeForm.employee_id" filterable style="width: 100%" @change="onSchemeEmployeeChange"><el-option v-for="employee in employees" :key="employee.id" :label="employeeLabel(employee)" :value="employee.id" /></el-select></el-form-item>
<el-form-item label="职级" prop="rank_id">
<el-select v-model="schemeForm.rank_id" clearable placeholder="按职级带出标准薪资" style="width: 100%" :loading="rankLoading">
<el-option v-for="item in schemeRankOptions" :key="item.id" :label="rankLabel(item)" :value="item.id" />
</el-select>
<div v-if="schemeRankHint" class="form-tip">{{ schemeRankHint }}</div>
</el-form-item>
<el-form-item label="生效日期" prop="effective_date"><el-date-picker v-model="schemeForm.effective_date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item>
<el-form-item label="失效日期"><el-date-picker v-model="schemeForm.expiry_date" value-format="YYYY-MM-DD" clearable style="width: 100%" /></el-form-item>
</div>
@@ -278,6 +285,7 @@ import {
getCompensationDashboard, getCompensationEmployees, getCompensationSchemes,
getPayrollDetail, getPayrollList, updateCompensationScheme, updatePayroll, updatePayrollStatus
} from "@/api/oaCompensation"
import { getJobRankList } from "@/api/jobRank"
const MoneyInput = defineComponent({
props: { modelValue: Number, label: String },
@@ -315,13 +323,58 @@ function emptyPayroll() {
return { employee_id: null, scheme_id: null, payroll_month: currentMonth(), base_salary: 0, post_allowance: 0, performance_salary: 0, transport_allowance: 0, meal_allowance: 0, communication_allowance: 0, overtime_pay: 0, bonus: 0, other_addition: 0, leave_deduction: 0, late_deduction: 0, other_deduction: 0, social_insurance: 0, housing_fund: 0, personal_income_tax: 0, remark: "", items: [] }
}
function emptyScheme() {
return { employee_id: null, scheme_name: "", effective_date: new Date().toISOString().slice(0, 10), expiry_date: "", base_salary: 0, post_allowance: 0, performance_salary: 0, transport_allowance: 0, meal_allowance: 0, communication_allowance: 0, social_insurance_base: 0, housing_fund_base: 0, social_insurance_rate: 0, housing_fund_rate: 0, status: 0, remark: "" }
return { employee_id: null, rank_id: null, scheme_name: "", effective_date: new Date().toISOString().slice(0, 10), expiry_date: "", base_salary: 0, post_allowance: 0, performance_salary: 0, transport_allowance: 0, meal_allowance: 0, communication_allowance: 0, social_insurance_base: 0, housing_fund_base: 0, social_insurance_rate: 0, housing_fund_rate: 0, status: 0, remark: "" }
}
const payrollForm = reactive(emptyPayroll())
const schemeForm = reactive(emptyScheme())
const payrollRules = { employee_id: [{ required: true, message: "请选择员工", trigger: "change" }], payroll_month: [{ required: true, message: "请选择工资月份", trigger: "change" }] }
const schemeRules = { employee_id: [{ required: true, message: "请选择员工", trigger: "change" }], scheme_name: [{ required: true, message: "请输入方案名称", trigger: "blur" }], effective_date: [{ required: true, message: "请选择生效日期", trigger: "change" }] }
// ---------- 职级 ----------
// 职级决定职位等级,薪酬方案选定职级后按该职级的标准薪资区间给出参考薪资。
const jobRanks = ref([])
const rankLoading = ref(false)
/** 当前方案可选的职级:员工所在部门的职级 + 全公司通用职级 */
const schemeRankOptions = computed(() => {
const employee = employees.value.find(item => item.id === schemeForm.employee_id)
const departmentId = Number(employee?.department || 0)
return jobRanks.value.filter(item => !item.department_id || !departmentId || Number(item.department_id) === departmentId)
})
/** 职级下拉文案:编码 + 名称,全公司通用的带标识 */
function rankLabel(item) {
const scope = item.department_id === 0 ? "(全公司)" : ""
return `${item.rank_code} ${item.rank_name}${scope}`
}
/** 已选职级的标准薪资提示 */
const schemeRankHint = computed(() => {
const rank = jobRanks.value.find(item => item.id === schemeForm.rank_id)
if (!rank) return ""
const min = Number(rank.salary_min || 0)
const max = Number(rank.salary_max || 0)
if (min <= 0 && max <= 0) return "该职级未配置标准薪资区间"
const range = max > 0 ? `¥${money(min)} ~ ¥${money(max)}` : `¥${money(min)} 起`
return `该职级标准薪资区间:${range}(基本工资留空时自动取下限)`
})
async function loadJobRanks() {
rankLoading.value = true
try {
const res = await getJobRankList({})
if (res?.code === 200) jobRanks.value = res.data || []
} finally { rankLoading.value = false }
}
/** 切换员工:职级可选范围随部门变化,已选职级若不再适用则清空 */
function onSchemeEmployeeChange() {
const options = schemeRankOptions.value
if (schemeForm.rank_id && !options.some(item => item.id === schemeForm.rank_id)) {
schemeForm.rank_id = null
}
}
const matchingSchemes = computed(() => schemes.value.filter(item => item.employee_id === payrollForm.employee_id && item.status === 0))
const grossTotal = computed(() => sum(payrollForm, ["base_salary", "post_allowance", "performance_salary", "transport_allowance", "meal_allowance", "communication_allowance", "overtime_pay", "bonus", "other_addition"]) + customTotal(1))
const deductionTotal = computed(() => sum(payrollForm, ["leave_deduction", "late_deduction", "other_deduction", "social_insurance", "housing_fund", "personal_income_tax"]) + customTotal(2))
@@ -416,7 +469,7 @@ async function removePayroll(row) {
}
function resetSchemeForm() { Object.assign(schemeForm, emptyScheme()) }
function openSchemeCreate() { schemeEditingID.value = 0; resetSchemeForm(); schemeFormVisible.value = true }
function openSchemeEdit(row) { schemeEditingID.value = row.id; Object.assign(schemeForm, emptyScheme(), row); schemeFormVisible.value = true }
function openSchemeEdit(row) { schemeEditingID.value = row.id; Object.assign(schemeForm, emptyScheme(), row); schemeForm.rank_id = row.rank_id ? Number(row.rank_id) : null; schemeFormVisible.value = true }
async function saveScheme() {
await schemeFormRef.value?.validate()
schemeSaving.value = true
@@ -431,7 +484,7 @@ async function removeScheme(row) {
if (res?.code === 200) { ElMessage.success("删除成功"); loadSchemes() } else ElMessage.error(res?.msg || "删除失败")
}
onMounted(async () => { await Promise.all([loadEmployees(), loadSchemes(), loadDashboard(), loadPayrolls()]) })
onMounted(async () => { await Promise.all([loadEmployees(), loadSchemes(), loadDashboard(), loadPayrolls(), loadJobRanks()]) })
</script>
<style lang="less" scoped>
@@ -449,6 +502,7 @@ onMounted(async () => { await Promise.all([loadEmployees(), loadSchemes(), loadD
.table-card { padding: 16px; background: #fff; border: 1px solid #ebeef5; border-radius: 8px; }.filter-bar { flex-wrap: wrap; margin-bottom: 14px; }.pagination { display: flex; justify-content: flex-end; margin-top: 16px; }
.net-money, .addition-text { color: #e6a23c; }.deduction-text { color: #f56c6c; }.more-icon { margin-left: 3px; vertical-align: -2px; }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 24px; }.main-fields { margin-bottom: 4px; }
.form-tip { font-size: 12px; color: #909399; line-height: 1.6; margin-top: 2px; }
.salary-section { border-top: 1px solid #ebeef5; padding: 14px 0 2px; margin-top: 4px; }.section-title { justify-content: space-between; gap: 8px; margin-bottom: 13px; font-weight: 600; color: #303133; }.dot { width: 8px; height: 8px; border-radius: 50%; }.dot.addition { background: #67c23a; }.dot.deduction { background: #f56c6c; }.section-title:has(.dot) { justify-content: flex-start; }
.salary-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 20px; }
.custom-items { padding-bottom: 10px; }.custom-item { gap: 10px; margin-bottom: 10px; }.custom-item :deep(.el-input) { flex: 1; }