<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>
|