huminmin
8 小时以前 cdf8190c92a536dabdbd3dfd6758cf67320ff6df
src/views/financialManagement/expenseManagement/index.vue
@@ -0,0 +1,477 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="支出日期:">
        <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                        placeholder="请选择" clearable @change="changeDaterange" />
      </el-form-item>
      <el-form-item label="付款方式:">
        <el-select
                v-model="filters.expenseMethod"
                placeholder="请选择"
                clearable
                style="width: 200px;"
              >
                <el-option
                  v-for="item in checkout_payment"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> 新增 </el-button>
          <el-button @click="handleOut" icon="download">导出</el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0 || hasBusinessIdInSelection"
            @click="handleBatchDelete"
          >
            批量删除
          </el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
        <template #operation="{ row }">
          <el-button
            type="primary"
            link
            :disabled="!!row.businessId"
            @click="edit(row.id)"
          >
            编辑
          </el-button>
          <el-button
            type="primary"
            link
            @click="openFilesFormDia(row)"
          >
            附件
          </el-button>
        </template>
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <FileListDialog
      ref="fileListRef"
      v-model="fileListDialogVisible"
      :show-upload-button="true"
      :show-delete-button="true"
      :upload-method="handleUpload"
      :delete-method="handleFileDelete"
    />
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { listPage, delAccountExpense, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/expenseManagement";
import { onMounted, getCurrentInstance, ref, computed } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import request from "@/utils/request";
import { getToken } from "@/utils/auth";
defineOptions({
  name: "支出管理",
});
// 表格多选框选中项
const multipleList = ref([]);
const { proxy } = getCurrentInstance();
const modalRef = ref();
const { checkout_payment } = proxy.useDict("checkout_payment");
const { expense_types } = proxy.useDict("expense_types");
const fileListRef = ref(null);
const fileListDialogVisible = ref(false);
const currentFileRow = ref(null);
const accountType = ref('支出');
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  listPage,
  {
    expenseMethod: undefined,
    entryDate: undefined,
  },
  [
    {
      label: "支出日期",
      prop: "expenseDate",
    },
    {
      label: "支出类型",
      align: "center",
      prop: "expenseType",
      dataType: "tag",
      formatData: (params) => {
        if (expense_types.value.find((m) => m.value == params)) {
          return expense_types.value.find((m) => m.value == params).label;
        } else {
          return null
        }
      },
    },
    {
      label: "供应商名称",
      prop: "supplierName",
    },
    {
      label: "支出金额",
      prop: "expenseMoney",
    },
    {
      label: "支出描述",
      prop: "expenseDescribed",
    },
    {
      label: "付款方式",
      align: "center",
      prop: "expenseMethod",
         width: '120',
      dataType: "tag",
      formatData: (params) => {
        if (checkout_payment.value.find((m) => m.value == params)) {
          return checkout_payment.value.find((m) => m.value == params).label;
        } else {
          return null
        }
      },
    },
    {
      label: "发票号码",
      prop: "invoiceNumber",
    },
    {
      label: "备注",
      prop: "note",
    },
    {
      label: "录入人",
      prop: "inputUser",
    },
    {
      label: "录入日期",
      prop: "inputTime",
    },
    {
      fixed: "right",
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "160px",
    },
  ]
);
// 多选后做什么
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
// 判断选中的项中是否有 businessId
const hasBusinessIdInSelection = computed(() => {
  return multipleList.value.some(item => item.businessId);
});
const add = () => {
  modalRef.value.openModal();
};
const edit = (id) => {
  // 检查当前行是否有 businessId
  const row = dataList.value.find(item => item.id === id);
  if (row && row.businessId) {
    proxy.$modal.msgWarning("该记录已关联业务,不能编辑");
    return;
  }
  modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
   pagination.pageSize = limit;
  onCurrentChange(page);
};
const deleteRow = (id) => {
  // 如果是数组,检查是否有 businessId
  if (Array.isArray(id)) {
    const hasBusinessId = id.some(itemId => {
      const row = dataList.value.find(item => item.id === itemId);
      return row && row.businessId;
    });
    if (hasBusinessId) {
      proxy.$modal.msgWarning("选中的记录中包含已关联业务的记录,不能删除");
      return;
    }
  } else {
    // 单个删除,检查是否有 businessId
    const row = dataList.value.find(item => item.id === id);
    if (row && row.businessId) {
      proxy.$modal.msgWarning("该记录已关联业务,不能删除");
      return;
    }
  }
  ElMessageBox.confirm("此操作将永久删除该数据, 是否继续?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    const { code } = await delAccountExpense(id);
    if (code == 200) {
      ElMessage({
        type: "success",
        message: "删除成功",
      });
      getTableData();
    }
  });
};
// 批量删除
const handleBatchDelete = () => {
  if (multipleList.value.length === 0) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  // 检查是否有 businessId
  if (hasBusinessIdInSelection.value) {
    proxy.$modal.msgWarning("选中的记录中包含已关联业务的记录,不能删除");
    return;
  }
  const ids = multipleList.value.map((item) => item.id);
  deleteRow(ids);
};
const changeDaterange = (value) => {
  if (value) {
    filters.entryDate = value;
    filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    filters.entryDate = null;
    filters.entryDateStart = undefined;
    filters.entryDateEnd = undefined;
  }
  getTableData();
};
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download(`/account/accountExpense/export`, {}, "支出台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// 打开附件弹框
const openFilesFormDia = async (row) => {
  currentFileRow.value = row;
  accountType.value = '支出';
  try {
    const res = await fileListPage({
      accountId: row.id,
      accountType: accountType.value,
      current: 1,
      size: 100
    });
    if (res.code === 200 && fileListRef.value) {
      // 将数据转换为 FileListDialog 需要的格式
      const fileList = (res.data?.records || []).map(item => ({
        name: item.name,
        url: item.url,
        id: item.id,
        ...item
      }));
      fileListRef.value.open(fileList);
      fileListDialogVisible.value = true;
    }
  } catch (error) {
    proxy.$modal.msgError("获取附件列表失败");
  }
};
// 上传附件
const handleUpload = async () => {
  if (!currentFileRow.value) {
    proxy.$modal.msgWarning("请先选择数据");
    return null;
  }
  return new Promise((resolve) => {
    // 创建一个隐藏的文件输入元素
    const input = document.createElement('input');
    input.type = 'file';
    input.style.display = 'none';
    input.onchange = async (e) => {
      const file = e.target.files[0];
      if (!file) {
        resolve(null);
        return;
      }
      try {
        // 使用 FormData 上传文件
        const formData = new FormData();
        formData.append('file', file);
        const uploadRes = await request({
          url: '/file/upload',
          method: 'post',
          data: formData,
          headers: {
            'Content-Type': 'multipart/form-data',
            Authorization: `Bearer ${getToken()}`
          }
        });
        if (uploadRes.code === 200) {
          // 保存附件信息
          const fileData = {
            accountId: currentFileRow.value.id,
            accountType: accountType.value,
            name: uploadRes.data.originalName || file.name,
            url: uploadRes.data.tempPath || uploadRes.data.url
          };
          const saveRes = await fileAdd(fileData);
          if (saveRes.code === 200) {
            proxy.$modal.msgSuccess("文件上传成功");
            // 重新加载文件列表
            const listRes = await fileListPage({
              accountId: currentFileRow.value.id,
              accountType: accountType.value,
              current: 1,
              size: 100
            });
            if (listRes.code === 200 && fileListRef.value) {
              const fileList = (listRes.data?.records || []).map(item => ({
                name: item.name,
                url: item.url,
                id: item.id,
                ...item
              }));
              fileListRef.value.setList(fileList);
            }
            // 返回新文件信息
            resolve({
              name: fileData.name,
              url: fileData.url,
              id: saveRes.data?.id
            });
          } else {
            proxy.$modal.msgError(saveRes.msg || "文件保存失败");
            resolve(null);
          }
        } else {
          proxy.$modal.msgError(uploadRes.msg || "文件上传失败");
          resolve(null);
        }
      } catch (error) {
        proxy.$modal.msgError("文件上传失败");
        resolve(null);
      } finally {
        document.body.removeChild(input);
      }
    };
    document.body.appendChild(input);
    input.click();
  });
};
// 删除附件
const handleFileDelete = async (row) => {
  try {
    const res = await fileDel([row.id]);
    if (res.code === 200) {
      proxy.$modal.msgSuccess("删除成功");
      // 重新加载文件列表
      if (currentFileRow.value && fileListRef.value) {
        const listRes = await fileListPage({
          accountId: currentFileRow.value.id,
          accountType: accountType.value,
          current: 1,
          size: 100
        });
        if (listRes.code === 200) {
          const fileList = (listRes.data?.records || []).map(item => ({
            name: item.name,
            url: item.url,
            id: item.id,
            ...item
          }));
          fileListRef.value.setList(fileList);
        }
      }
      return true; // 返回 true 表示删除成功,组件会更新列表
    } else {
      proxy.$modal.msgError(res.msg || "删除失败");
      return false;
    }
  } catch (error) {
    proxy.$modal.msgError("删除失败");
    return false;
  }
};
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>