ZN
2026-03-20 7e3160579cb83558c87ce44df0b74784f0c9f916
fix: 修复离职日期编辑和删除按钮状态逻辑,优化表格操作样式

- 离职表单编辑时禁用离职日期选择器,防止误修改
- 销售退货列表删除按钮增加状态校验,仅允许删除待处理订单
- 重构 PIMTable 操作按钮禁用状态和颜色逻辑,统一处理函数
- 移除员工分析页面冗余的流动率计算逻辑,简化数据获取
已修改4个文件
243 ■■■■■ 文件已修改
src/components/PIMTable/PIMTable.vue 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/analytics/index.vue 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue
@@ -135,14 +135,12 @@
            <el-button
              v-show="o.type != 'upload'"
              v-if="o.showHide ? o.showHide(scope.row) : true"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :disabled="isOperationDisabled(o, scope.row)"
              :plain="o.plain"
              type="primary"
              :style="{
                color:
                  o.name === '删除' || o.name === 'delete'
                    ? '#f56c6c'
                    : o.color,
                color: getOperationColor(o, scope.row),
                fontWeight: 'bold',
              }"
              link
              @click.stop="o.clickFun(scope.row)"
@@ -160,7 +158,7 @@
              ref="uploadRef"
              :multiple="o.multiple ? o.multiple : false"
              :limit="1"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :disabled="isOperationDisabled(o, scope.row)"
              :accept="
                o.accept
                  ? o.accept
@@ -188,7 +186,11 @@
              <el-button
                link
                type="primary"
                :disabled="o.disabled ? o.disabled(scope.row) : false"
                :disabled="isOperationDisabled(o, scope.row)"
                :style="{
                  color: getOperationColor(o, scope.row),
                  fontWeight: 'bold',
                }"
                >{{ o.name }}</el-button
              >
            </el-upload>
@@ -373,6 +375,69 @@
  } else return "";
};
const isOperationDisabled = (operation, row) => {
  if (!operation?.disabled) return false;
  return typeof operation.disabled === "function"
    ? !!operation.disabled(row)
    : !!operation.disabled;
};
const parseHexToRgb = (hex) => {
  const normalized = String(hex || "").trim().replace("#", "");
  if (normalized.length === 3) {
    const r = parseInt(normalized[0] + normalized[0], 16);
    const g = parseInt(normalized[1] + normalized[1], 16);
    const b = parseInt(normalized[2] + normalized[2], 16);
    if ([r, g, b].some((n) => Number.isNaN(n))) return null;
    return { r, g, b };
  }
  if (normalized.length === 6 || normalized.length === 8) {
    const r = parseInt(normalized.slice(0, 2), 16);
    const g = parseInt(normalized.slice(2, 4), 16);
    const b = parseInt(normalized.slice(4, 6), 16);
    if ([r, g, b].some((n) => Number.isNaN(n))) return null;
    return { r, g, b };
  }
  return null;
};
const fadeColor = (color, alpha = 0.35) => {
  const c = String(color || "").trim();
  if (!c) return undefined;
  if (c.startsWith("#")) {
    const rgb = parseHexToRgb(c);
    if (!rgb) return c;
    return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
  }
  const rgbMatch = c.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+\s*)?\)$/i);
  if (rgbMatch) {
    const r = Number(rgbMatch[1]);
    const g = Number(rgbMatch[2]);
    const b = Number(rgbMatch[3]);
    if ([r, g, b].some((n) => Number.isNaN(n))) return c;
    return `rgba(${r}, ${g}, ${b}, ${alpha})`;
  }
  if (c.includes("--el-color-primary")) {
    return "var(--el-color-primary-light-5)";
  }
  if (c.includes("--el-color-danger")) {
    return "var(--el-color-danger-light-5)";
  }
  return "var(--el-text-color-disabled)";
};
const getOperationColor = (operation, row) => {
  const baseColor =
    operation?.name === "删除" || operation?.name === "delete"
      ? "#D93025"
      : operation?.color || "var(--el-color-primary)";
  if (isOperationDisabled(operation, row)) {
    return fadeColor(baseColor, 0.35);
  }
  return baseColor;
};
// 文件变化处理
const handleChange = (file, fileList, index) => {
  if (fileList.length > 1) {
src/views/personnelManagement/analytics/index.vue
@@ -86,15 +86,12 @@
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import dayjs from 'dayjs'
import {listDept} from "@/api/system/dept.js";
import {
  findStaffAnalysisMonthlyTurnoverRateFor12Months,
  findStaffLeaveReasonAnalysis,
  findStaffAnalysisTotalStatistic
} from "@/api/personnelManagement/staffAnalytics.js";
import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
import { findStaffLeaveListPage } from "@/api/personnelManagement/staffLeave.js";
// 响应式数据
const loading = ref(false)
@@ -151,43 +148,6 @@
const staffLeaveReasons = ref([])
// 12个月员工流动流失率分析数据
const turnoverRateStatistics = ref([])
const turnoverRateStatisticsRaw = ref([])
const turnoverSeriesNormalized = ref(false)
const staffCounts = ref({
  onJobTotal: 0,
  leaveTotal: 0,
  totalStaff: 0,
  leaveLast12Months: 0,
  joinLeaveRatio: 1
})
const safeNum = (val) => {
  const num = Number(val)
  return Number.isFinite(num) ? num : 0
}
const round2 = (val) => {
  const num = safeNum(val)
  return Number(num.toFixed(2))
}
const getListTotal = (res) => safeNum(res?.data?.total ?? res?.data?.count ?? 0)
const getLast12MonthRanges = () => {
  const end = dayjs()
  const start = end.subtract(11, 'month').startOf('month')
  const ranges = []
  for (let i = 0; i < 12; i++) {
    const cur = start.add(i, 'month')
    ranges.push({
      month: cur.format('YYYY-MM'),
      start: cur.startOf('month').format('YYYY-MM-DD'),
      end: cur.endOf('month').format('YYYY-MM-DD')
    })
  }
  return ranges
}
// 获取部门数据
const getDepartmentData = async () => {
@@ -217,7 +177,7 @@
  try {
    const res = await findStaffAnalysisMonthlyTurnoverRateFor12Months()
    if (res && res.data) {
      turnoverRateStatisticsRaw.value = res.data || []
      turnoverRateStatistics.value = res.data || []
    }
  } catch (error) {
    console.error('获取12个月员工流动流失率分析数据失败:', error)
@@ -226,112 +186,14 @@
const getStaffAnalysisTotalStatistic = async () => {
  try {
    const ranges = getLast12MonthRanges()
    const leave12Range = {
      leaveDateStart: ranges[0].start,
      leaveDateEnd: ranges[ranges.length - 1].end
    const res = await findStaffAnalysisTotalStatistic()
    if (res && res.data) {
      keyMetrics.value[0].value = res.data.totalFlowRate || 0
      keyMetrics.value[1].value = res.data.totalTurnoverRate || 0
      keyMetrics.value[2].value = res.data.currentOnJobCount || 0
    }
    const [totalRes, onJobRes, leaveRes, leave12Res] = await Promise.all([
      findStaffAnalysisTotalStatistic(),
      staffOnJobListPage({ current: 1, size: 1, staffState: 1 }),
      findStaffLeaveListPage({ current: 1, size: 1 }),
      findStaffLeaveListPage({ current: 1, size: 1, ...leave12Range })
    ])
    const totalFlowRate = safeNum(totalRes?.data?.totalFlowRate)
    const totalTurnoverRate = safeNum(totalRes?.data?.totalTurnoverRate)
    const joinLeaveRatio =
      totalTurnoverRate > 0 && totalFlowRate > 0 ? totalFlowRate / totalTurnoverRate : 1
    const onJobTotal = getListTotal(onJobRes)
    const leaveTotal = getListTotal(leaveRes)
    const totalStaff = onJobTotal + leaveTotal
    const leaveLast12Months = getListTotal(leave12Res)
    staffCounts.value = {
      onJobTotal,
      leaveTotal,
      totalStaff,
      leaveLast12Months,
      joinLeaveRatio: safeNum(joinLeaveRatio)
    }
    const turnoverRateNew =
      totalStaff > 0 ? (leaveLast12Months / totalStaff) * 100 : 0
    const flowRateNew =
      totalStaff > 0 ? (leaveLast12Months * staffCounts.value.joinLeaveRatio / totalStaff) * 100 : 0
    keyMetrics.value[0].value = round2(flowRateNew)
    keyMetrics.value[1].value = round2(turnoverRateNew)
    keyMetrics.value[2].value = onJobTotal || safeNum(totalRes?.data?.currentOnJobCount)
  } catch (error) {
    console.error('获取员工分析总统计数据失败:', error)
  }
}
const getLeaveCountsByMonthForLast12Months = async () => {
  const ranges = getLast12MonthRanges()
  const tasks = ranges.map((r) =>
    findStaffLeaveListPage({
      current: 1,
      size: 1,
      leaveDateStart: r.start,
      leaveDateEnd: r.end
    })
  )
  const results = await Promise.allSettled(tasks)
  const counts = {}
  results.forEach((res, idx) => {
    const month = ranges[idx].month
    counts[month] = res.status === 'fulfilled' ? getListTotal(res.value) : 0
  })
  const sum = Object.values(counts).reduce((acc, cur) => acc + safeNum(cur), 0)
  return { counts, sum }
}
const applyNormalizedTurnoverStatistics = async () => {
  const raw = Array.isArray(turnoverRateStatisticsRaw.value) ? turnoverRateStatisticsRaw.value : []
  if (raw.length === 0) {
    turnoverRateStatistics.value = []
    turnoverSeriesNormalized.value = false
    return
  }
  const totalStaff = safeNum(staffCounts.value.totalStaff)
  const leaveLast12Months = safeNum(staffCounts.value.leaveLast12Months)
  const ratio = safeNum(staffCounts.value.joinLeaveRatio) || 1
  if (totalStaff <= 0 || leaveLast12Months < 0) {
    turnoverRateStatistics.value = raw
    turnoverSeriesNormalized.value = false
    return
  }
  try {
    const { counts, sum } = await getLeaveCountsByMonthForLast12Months()
    if (sum > leaveLast12Months) {
      turnoverRateStatistics.value = raw
      turnoverSeriesNormalized.value = false
      return
    }
    turnoverRateStatistics.value = raw.map((item) => {
      const monthKey = String(item?.month ?? '')
      const leaveCount = safeNum(counts[monthKey])
      const turnoverRate = totalStaff > 0 ? (leaveCount / totalStaff) * 100 : 0
      const flowRate = totalStaff > 0 ? (leaveCount * ratio / totalStaff) * 100 : 0
      return {
        ...item,
        flowRate: round2(flowRate),
        turnoverRate: round2(turnoverRate)
      }
    })
    turnoverSeriesNormalized.value = true
  } catch (e) {
    turnoverRateStatistics.value = raw
    turnoverSeriesNormalized.value = false
  }
}
@@ -376,8 +238,6 @@
      getMonthlyTurnoverRateFor12Months(),
      getStaffAnalysisTotalStatistic()
    ])
    await applyNormalizedTurnoverStatistics()
    await nextTick()
    renderAllCharts()
@@ -457,14 +317,7 @@
      data: months,
      boundaryGap: false
    },
    yAxis: turnoverSeriesNormalized.value
      ? {
          type: 'value',
          axisLabel: { formatter: '{value}%' },
          min: 0,
          max: 100
        }
      : {
    yAxis: {
          type: 'value',
          axisLabel: { formatter: '{value}%' }
        },
src/views/personnelManagement/dimission/components/formDia.vue
@@ -101,6 +101,7 @@
                <el-date-picker
                    v-model="form.leaveDate"
                    type="date"
                    :disabled="operationType === 'edit'"
                    placeholder="请选择离职日期"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
src/views/salesManagement/returnOrder/index.vue
@@ -35,7 +35,7 @@
    <div class="table_list">
      <div class="table_header" style="display: flex;justify-content: flex-end;margin-bottom: 10px;">
        <el-button type="primary" @click="openForm('add')">新建销售退货</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
        <el-button type="danger" :disabled="selectedRows.length === 0 || selectedRows.some(row => row.status !== 0)" @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
        rowKey="id"