src/views/hrm/salary/calculation/index.vue
@@ -7,7 +7,7 @@
import { Page } from '#/packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '#/packages/utils/src';
import { message, Modal, Form, FormItem, DatePicker, TreeSelect, Table, InputNumber, Alert, Tag } from 'ant-design-vue';
import { message, Modal, Form, FormItem, DatePicker, TreeSelect, Table, Input, InputNumber, Alert, Tag } from 'ant-design-vue';
import { DICT_TYPE } from '#/packages/constants/src';
import { getDictLabel } from '#/packages/effects/hooks/src';
@@ -22,6 +22,7 @@
  previewSalaryCalculation,
  revokeSalaryCalculation,
  saveSalaryCalculation,
  updateSalaryCalculation,
} from '#/api/hrm/salary/calculation';
import { getDeptList } from '#/api/system/dept';
import { handleTree } from '#/packages/utils/src';
@@ -32,6 +33,7 @@
// 核算弹窗相关
const formModalRef = ref();
const saving = ref(false);
const calculateVisible = ref(false);
const calculatePeriod = ref<string>();
const calculateDeptId = ref<number>();
@@ -226,6 +228,55 @@
async function handleExport() {
  const data = await exportSalaryCalculation(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '薪酬核算表.xls', source: data });
}
/** 列表可编辑的数值字段 */
const EDITABLE_NUMBER_FIELDS = [
  'baseSalary',
  'performanceSalary',
  'overtimePay',
  'mealAllowance',
  'transportAllowance',
  'otherAllowance',
  'socialSecurityDeduction',
  'housingFundDeduction',
  'taxDeduction',
  'otherDeduction',
  'leaveDeduction',
  'workDays',
  'overtimeHours',
];
/** 文本输入转数字(空值按 0) */
function toNumberOrZero(val: any) {
  if (val === '' || val === null || val === undefined) {
    return 0;
  }
  const num = Number(String(val).replace(/,/g, ''));
  return Number.isNaN(num) ? 0 : num;
}
/** 保存列表内联修改(仅待确认状态) */
async function handleSaveEdits() {
  const rows = (gridApi.grid.getData() as any[]).filter((r) => r.status === 0);
  if (rows.length === 0) {
    message.warning('当前页没有待确认的记录可修改');
    return;
  }
  saving.value = true;
  try {
    for (const row of rows) {
      const payload: any = { ...row };
      EDITABLE_NUMBER_FIELDS.forEach((f) => {
        payload[f] = toNumberOrZero(payload[f]);
      });
      await updateSalaryCalculation(payload);
    }
    message.success(`已保存 ${rows.length} 条核算记录`);
    handleRefresh();
  } finally {
    saving.value = false;
  }
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -496,6 +547,13 @@
              onClick: handleBatchSave,
            },
            {
              label: '保存修改',
              type: 'primary',
              auth: ['hrm:salary-calculation:calculate'],
              loading: saving,
              onClick: handleSaveEdits,
            },
            {
              label: $t('ui.actionTitle.export'),
              type: 'primary',
              icon: ACTION_ICON.DOWNLOAD,
@@ -505,6 +563,15 @@
          ]"
        />
      </template>
      <template #editableCell="{ row, column }">
        <Input
          v-if="row.status === 0"
          v-model:value="row[column.field]"
          size="small"
          :style="{ width: '100%' }"
        />
        <span v-else>{{ row[column.field] ?? '' }}</span>
      </template>
      <template #status="{ row }">
        <Tag :color="row.status === 0 ? 'warning' : row.status === 10 ? 'success' : 'processing'">
          {{ getDictLabel(DICT_TYPE.HRM_SALARY_STATUS, row.status) }}