spring
2025-08-05 87accd3904fd407bc3b626d88cbeca1c7fefead3
完成财务管理-收入管理
已修改1个文件
已添加7个文件
754 ■■■■■ 文件已修改
src/api/financialManagement/revenueManagement.js 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/filePreview/index.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/financialStatements/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/Form.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/Modal.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/filesDia.vue 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/index.vue 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/revenueManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
import request from "@/utils/request";
// æŸ¥è¯¢åˆ—表
export const listPage = (params) => {
  return request({
    url: "/account/accountIncome/listPage",
    method: "get",
    params,
  });
};
// æ–°å¢ž
export function add(data) {
  return request({
    url: "/account/accountIncome/add",
    method: "post",
    data: data,
  });
}
// ç¼–辑
export function update(data) {
  return request({
    url: "/account/accountIncome/update",
    method: "post",
    data: data,
  });
}
//导出
export const exportAccountIncome = (query) => {
  return request({
    url: "/account/accountIncome/export",
    method: "post",
    data: query,
    responseType: "blob",
  });
};
export const delAccountIncome = (query) => {
  return request({
    url: `account/accountIncome/del`,
    method: "delete",
    data: query,
  });
};
export const getAccountIncome = (id) => {
  return request({
    url: `/account/accountIncome/${id}`,
    method: "get",
  });
};
// æŸ¥è¯¢é™„件列表
export function fileListPage(query) {
  return request({
    url: "/account/accountFile/listPage",
    method: "get",
    params: query,
  });
}
// ä¿å­˜é™„件列表
export function fileAdd(query) {
  return request({
    url: "/account/accountFile/add",
    method: "post",
    data: query,
  });
}
// åˆ é™¤é™„件列表
export function fileDel(query) {
  return request({
    url: "/account/accountFile/del",
    method: "delete",
    data: query,
  });
}
src/components/filePreview/index.vue
@@ -88,6 +88,7 @@
});
const isPdf = computed(() => {
  console.log(fileUrl.value)
  return /\.pdf$/i.test(fileUrl.value);
});
@@ -163,7 +164,7 @@
};
const open = (url) => {
  fileUrl.value = javaApi + url;
  fileUrl.value = window.location.protocol+'//'+window.location.host+ url;
  dialogVisible.value = true;
};
const handleClose = () => {
src/views/financialManagement/expenseManagement/index.vue
src/views/financialManagement/financialStatements/index.vue
src/views/financialManagement/revenueManagement/Form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
<template>
  <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
    <el-form-item label="收入日期" prop="incomeDate">
          <el-date-picker
            style="width: 100%"
            v-model="form.incomeDate"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            type="date"
            placeholder="请选择日期"
            clearable
          />
        </el-form-item>
        <el-form-item label="收入类型" prop="incomeType">
          <el-select
            v-model="form.incomeType"
            placeholder="请选择"
            clearable
          >
            <el-option :label="item.label" :value="item.value" v-for="(item,index) in income_types" :key="index" />
          </el-select>
        </el-form-item>
        <el-form-item label="客户名称" prop="customerName">
          <el-input v-model="form.customerName" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="收入金额" prop="incomeMoney">
          <el-input-number :step="0.01" :min="0" style="width: 100%"
            v-model="form.incomeMoney"
            placeholder="请输入"
          />
        </el-form-item>
        <el-form-item label="收入描述" prop="incomeDescribed">
          <el-input v-model="form.incomeDescribed" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="收款方式" prop="incomeMethod">
          <el-select
            v-model="form.incomeMethod"
            placeholder="请选择"
            clearable
          >
            <el-option :label="item.label" :value="item.value" v-for="(item,index) in payment_methods" :key="index" />
          </el-select>
        </el-form-item>
        <el-form-item label="发票号码" prop="invoiceNumber">
          <el-input v-model="form.invoiceNumber" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="备注" prop="note">
          <el-input
            v-model="form.note"
            placeholder="备注"
          />
        </el-form-item>
  </el-form>
</template>
<script setup>
import useFormData from "@/hooks/useFormData";
import { getAccountIncome } from "@/api/financialManagement/revenueManagement";
import {ref} from "vue";
const { proxy } = getCurrentInstance();
defineOptions({
  name: "新增收入",
});
const { income_types } = proxy.useDict("income_types");
const { payment_methods } = proxy.useDict("payment_methods");
const formRef = ref(null);
const formRules = {
    customerName: [{ required: true, trigger: "blur", message: "请输入" }],
    incomeMoney: [{ required: true, trigger: "blur", message: "请输入" }],
    incomeDescribed: [{ required: true, trigger: "blur", message: "请输入" }],
    incomeDate: [{ required: true, trigger: "change", message: "请选择" }],
  incomeType: [{ required: true, trigger: "change", message: "请选择" }],
  incomeMethod: [{ required: true, trigger: "change", message: "请选择" }],
}
const { form, resetForm } = useFormData({
  incomeDate: undefined, // æ”¶å…¥æ—¥æœŸ
  incomeType: undefined, // æ”¶å…¥ç±»åž‹
  customerName: undefined, // å®¢æˆ·åç§°
  incomeMoney: undefined, // æ”¶å…¥é‡‘额
  incomeDescribed: undefined, // æ”¶å…¥æè¿°
  incomeMethod: undefined, // æ”¶æ¬¾æ–¹å¼
  invoiceNumber: undefined, // å‘票号码
  note: undefined, // å¤‡æ³¨
});
const loadForm = async (id) => {
  const { code, data } = await getAccountIncome(id);
  if (code == 200) {
    form.incomeDate = data.incomeDate;
    form.incomeType = data.incomeType;
    form.customerName = data.customerName;
    form.incomeMoney = data.incomeMoney;
    form.incomeDescribed = data.incomeDescribed;
    form.incomeMethod = data.incomeMethod;
    form.invoiceNumber = data.invoiceNumber;
    form.note = data.note;
  }
};
// æ¸…除表单校验状态
const clearValidate = () => {
  formRef.value?.clearValidate();
};
// é‡ç½®è¡¨å•数据和校验状态
const resetFormAndValidate = () => {
  resetForm();
  clearValidate();
};
defineExpose({
  form,
  loadForm,
  resetForm,
  clearValidate,
  resetFormAndValidate,
    formRef,
});
</script>
src/views/financialManagement/revenueManagement/Modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close" width="30%">
    <Form ref="formRef"></Form>
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import { add, update } from "@/api/financialManagement/revenueManagement";
import Form from "./Form.vue";
import { ElMessage } from "element-plus";
const { proxy } = getCurrentInstance()
defineOptions({
  name: "收入新增编辑",
});
const emits = defineEmits(["success"]);
const formRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "收入" });
const sendForm = () => {
    proxy.$refs.formRef.$refs.formRef.validate(async valid => {
        if (valid) {
            const {code} = id.value
                ? await update({id: id.value, ...formRef.value.form})
                : await add(formRef.value.form);
            if (code == 200) {
                emits("success");
                ElMessage({message: "操作成功", type: "success"});
                close();
            } else {
                loading.value = false;
            }
        }
    })
};
const close = () => {
    formRef.value.resetFormAndValidate();
  closeModal();
};
const loadForm = async (id) => {
  openModal(id);
  await nextTick();
  formRef.value.loadForm(id);
};
defineExpose({
  openModal,
  loadForm,
});
</script>
src/views/financialManagement/revenueManagement/filesDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,202 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="上传附件"
        width="50%"
        @close="closeDia"
    >
      <div style="margin-bottom: 10px;text-align: right">
        <el-upload
            v-model:file-list="fileList"
            class="upload-demo"
            :action="uploadUrl"
            :on-success="handleUploadSuccess"
            :on-error="handleUploadError"
            name="file"
            :show-file-list="false"
            :headers="headers"
            style="display: inline;margin-right: 10px"
        >
          <el-button type="primary">上传附件</el-button>
        </el-upload>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :tableLoading="tableLoading"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          height="500"
      >
      </PIMTable>
            <pagination
                style="margin: 10px 0"
                v-show="total > 0"
                @pagination="paginationSearch"
                :total="total"
                :page="page.current"
                :limit="page.size"
            />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
<script setup>
import {ref} from "vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import filePreview from '@/components/filePreview/index.vue'
import {
  fileAdd,
  fileDel,
  fileListPage
} from "@/api/financialManagement/revenueManagement.js";
import Pagination from "@/components/PIMTable/Pagination.vue";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const currentId = ref('')
const selectedRows = ref([]);
const filePreviewRef = ref()
const tableColumn = ref([
  {
    label: "文件名称",
    prop: "name",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    operation: [
      {
        name: "下载",
        type: "text",
        clickFun: (row) => {
          downLoadFile(row);
        },
      },
      {
        name: "预览",
        type: "text",
        clickFun: (row) => {
          lookFile(row);
        },
      }
    ],
  },
]);
const page = reactive({
    current: 1,
    size: 100,
});
const total = ref(0);
const tableData = ref([]);
const fileList = ref([]);
const tableLoading = ref(false);
const accountType = ref('')
const headers = ref({
  Authorization: "Bearer " + getToken(),
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // ä¸Šä¼ çš„图片服务器地址
// æ‰“开弹框
const openDialog = (row,type) => {
  accountType.value = type;
  dialogFormVisible.value = true;
  currentId.value = row.id;
  getList()
}
const paginationSearch = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
  fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => {
    tableData.value = res.data.records;
        total.value = res.data.total;
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
  emit('close')
};
// ä¸Šä¼ æˆåŠŸå¤„ç†
function handleUploadSuccess(res, file) {
  // å¦‚果上传成功
  if (res.code == 200) {
    const fileRow = {}
    fileRow.name = res.data.originalName
    fileRow.url = res.data.tempPath
    uploadFile(fileRow)
  } else {
    proxy.$modal.msgError("文件上传失败");
  }
}
function uploadFile(file) {
  file.accountId = currentId.value;
  file.accountType = accountType.value;
  fileAdd(file).then(res => {
    proxy.$modal.msgSuccess("文件上传成功");
    getList()
  })
}
// ä¸Šä¼ å¤±è´¥å¤„理
function handleUploadError() {
  proxy.$modal.msgError("文件上传失败");
}
// ä¸‹è½½é™„ä»¶
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    fileDel(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
      getList();
    });
  }).catch(() => {
    proxy.$modal.msg("已取消");
  });
};
// é¢„览附件
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/financialManagement/revenueManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,279 @@
<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.incomeMethod"
                placeholder="请选择"
                clearable
                style="width: 200px;"
              >
                <el-option
                  v-for="item in payment_methods"
                  :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"
            @click="deleteRow(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
          </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" text @click="edit(row.id)" icon="editPen">
            ç¼–辑
          </el-button>
          <el-button
            type="primary"
            text
            @click="openFilesFormDia(row)"
          >
            é™„ä»¶
          </el-button>
        </template>
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <files-dia ref="filesDia" @close="handleQuery"></files-dia>
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { listPage, delAccountIncome } from "@/api/financialManagement/revenueManagement";
import { onMounted, getCurrentInstance } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import FilesDia from "./filesDia.vue";
defineOptions({
  name: "收入管理",
});
// è¡¨æ ¼å¤šé€‰æ¡†é€‰ä¸­é¡¹
const multipleList = ref([]);
const { proxy } = getCurrentInstance();
const modalRef = ref();
const { payment_methods } = proxy.useDict("payment_methods");
const { income_types } = proxy.useDict("income_types");
const filesDia = ref()
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  listPage,
  {
    searchText: undefined,
  },
  [
    {
      label: "收入日期",
      align: "center",
      prop: "incomeDate",
    },
    {
      label: "收入类型",
      align: "center",
      prop: "incomeType",
      dataType: "tag",
      formatData: (params) => {
        if (income_types.value.find((m) => m.value == params)) {
          return income_types.value.find((m) => m.value == params).label;
        } else {
          return null
        }
      },
    },
    {
      label: "客户名称",
      align: "center",
      prop: "customerName",
    },
    {
      label: "收入金额",
      align: "center",
      prop: "incomeMoney",
    },
    {
      label: "收入描述",
      align: "center",
      prop: "incomeDescribed",
    },
    {
      label: "收款方式",
      align: "center",
      prop: "incomeMethod",
      dataType: "tag",
      formatData: (params) => {
        if (payment_methods.value.find((m) => m.value == params)) {
          return payment_methods.value.find((m) => m.value == params).label;
        } else {
          return null
        }
      },
    },
    {
      label: "发票号码",
      align: "center",
      prop: "invoiceNumber",
    },
    {
      label: "备注",
      align: "center",
      prop: "note",
    },
    {
      label: "录入人",
      align: "center",
      prop: "inputUser",
    },
    {
      label: "录入日期",
      align: "center",
      prop: "inputTime",
    },
    {
      fixed: "right",
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
    },
  ]
);
// å¤šé€‰åŽåšä»€ä¹ˆ
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
const add = () => {
  modalRef.value.openModal();
};
const edit = (id) => {
  modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
    pagination.pageSize = limit;
  onCurrentChange(page);
};
const deleteRow = (id) => {
  ElMessageBox.confirm("此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    const { code } = await delAccountIncome(id);
    if (code == 200) {
      ElMessage({
        type: "success",
        message: "删除成功",
      });
      getTableData();
    }
  });
};
const changeDaterange = (value) => {
  if (value) {
    filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    filters.entryDateStart = undefined;
    filters.entryDateEnd = undefined;
  }
  getTableData();
};
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download(`/account/accountIncome/export`, {}, "收入台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  nextTick(() => {
    filesDia.value?.openDialog( row,'收入')
  })
};
onMounted(() => {
  filters.entryDate = [
    dayjs().format("YYYY-MM-DD"),
    dayjs().add(1, "day").format("YYYY-MM-DD"),
  ]
  filters.entryDateStart = dayjs().format("YYYY-MM-DD")
  filters.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>