<template>
|
<div class="app-container">
|
<el-form :model="filters" :inline="true">
|
<el-form-item label="客户:">
|
<el-select v-model="filters.customerId" placeholder="请选择客户" clearable style="width: 200px;">
|
<el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="对账期间:">
|
<el-date-picker v-model="filters.startMonth" type="month" placeholder="开始月份" value-format="YYYY-MM" style="width: 140px;" />
|
<span style="margin: 0 10px;">至</span>
|
<el-date-picker v-model="filters.endMonth" type="month" placeholder="结束月份" value-format="YYYY-MM" style="width: 140px;" />
|
</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>
|
<el-button type="primary" @click="generateStatement" icon="Document">生成对账单</el-button>
|
</div>
|
<div>
|
<el-button @click="handleOut" icon="Download">导出对账单</el-button>
|
</div>
|
</div>
|
<PIMTable
|
rowKey="id"
|
:column="columns"
|
:tableData="dataList"
|
:page="{
|
current: pagination.currentPage,
|
size: pagination.pageSize,
|
total: pagination.total,
|
}"
|
@pagination="changePage"
|
>
|
<template #beginBalance="{ row }">
|
<span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.beginBalance) }}</span>
|
</template>
|
<template #currentReceivable="{ row }">
|
<span class="text-primary">¥{{ formatMoney(row.currentReceivable) }}</span>
|
</template>
|
<template #currentReceipt="{ row }">
|
<span class="text-success">¥{{ formatMoney(row.currentReceipt) }}</span>
|
</template>
|
<template #endBalance="{ row }">
|
<span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.endBalance) }}</span>
|
</template>
|
<template #operation="{ row }">
|
<el-button type="primary" link @click="viewDetail(row)">查看明细</el-button>
|
<el-button type="primary" link @click="printStatement(row)">打印</el-button>
|
</template>
|
</PIMTable>
|
</div>
|
|
<FormDialog title="对账明细" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail">
|
<div class="statement-header">
|
<h3>{{ currentCustomer }} 应收对账单</h3>
|
<p>对账期间: {{ currentPeriod }}</p>
|
</div>
|
<el-table :data="detailData" border style="width: 100%">
|
<el-table-column prop="date" label="日期" width="120" />
|
<el-table-column prop="type" label="类型" width="100">
|
<template #default="{ row }">
|
<el-tag :type="row.type === '出库' ? 'success' : row.type === '退货' ? 'danger' : 'primary'">{{ row.type }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="code" label="单据编号" width="150" />
|
<el-table-column prop="debit" label="借方(应收)" width="120">
|
<template #default="{ row }">
|
<span v-if="row.debit > 0" class="text-danger">¥{{ formatMoney(row.debit) }}</span>
|
<span v-else>-</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="credit" label="贷方(收款)" width="120">
|
<template #default="{ row }">
|
<span v-if="row.credit > 0" class="text-success">¥{{ formatMoney(row.credit) }}</span>
|
<span v-else>-</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="balance" label="余额" width="120">
|
<template #default="{ row }">
|
<span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.balance) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="remark" label="备注" show-overflow-tooltip />
|
</el-table>
|
<template #footer>
|
<el-button type="primary" @click="printDetail">打印</el-button>
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
</template>
|
</FormDialog>
|
|
<FormDialog title="生成对账单" v-model="generateDialogVisible" width="1000px" @confirm="confirmGenerate" @cancel="generateDialogVisible = false">
|
<el-form :model="generateForm" label-width="100px">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="选择客户" prop="customerId">
|
<el-select v-model="generateForm.customerId" placeholder="请选择客户" style="width: 100%;" @change="onCustomerChange">
|
<el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="对账月份" prop="period">
|
<el-date-picker v-model="generateForm.period" type="month" placeholder="选择月份" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
|
<div v-if="salesData.length > 0" class="sales-section">
|
<div class="section-title">本月销售数据</div>
|
<el-table :data="salesData" border style="width: 100%; margin-bottom: 15px;" v-loading="salesLoading" @selection-change="handleSalesSelectionChange">
|
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column prop="date" label="日期" width="120" />
|
<el-table-column prop="code" label="单据编号" width="150" />
|
<el-table-column prop="type" label="类型" width="100">
|
<template #default="{ row }">
|
<el-tag :type="row.type === '出库' ? 'success' : row.type === '收款' ? 'primary' : 'danger'">{{ row.type }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="amount" label="金额" width="120">
|
<template #default="{ row }">
|
<span :class="row.type === '出库' ? 'text-primary' : row.type === '收款' ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.amount) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="remark" label="备注" />
|
</el-table>
|
|
<div class="summary-row">
|
<span>期初余额: <strong class="text-primary">¥{{ formatMoney(generateForm.beginBalance) }}</strong></span>
|
<span>本期应收: <strong class="text-primary">¥{{ formatMoney(generateForm.currentReceivable) }}</strong></span>
|
<span>本期收款: <strong class="text-success">¥{{ formatMoney(generateForm.currentReceipt) }}</strong></span>
|
<span>期末余额: <strong :class="calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt) >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt)) }}</strong></span>
|
</div>
|
</div>
|
|
<div v-else-if="generateForm.customerId && !salesLoading" class="empty-tip">
|
<el-empty description="该客户本月暂无销售数据" />
|
</div>
|
|
<template #footer>
|
<el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">确认生成</el-button>
|
<el-button @click="generateDialogVisible = false">取消</el-button>
|
</template>
|
</FormDialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, computed } from "vue";
|
import { ElMessage } from "element-plus";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
|
defineOptions({
|
name: "应收对账",
|
});
|
|
const filters = reactive({
|
customerId: "",
|
startMonth: "",
|
endMonth: "",
|
});
|
|
const pagination = reactive({
|
currentPage: 1,
|
pageSize: 10,
|
total: 0,
|
});
|
|
const columns = [
|
{ label: "对账单号", prop: "statementCode", width: "150" },
|
{ label: "客户名称", prop: "customerName", width: "180" },
|
{ label: "对账期间", prop: "period", width: "150" },
|
{ label: "期初余额", prop: "beginBalance", slot: "beginBalance" },
|
{ label: "本期应收", prop: "currentReceivable", slot: "currentReceivable" },
|
{ label: "本期收款", prop: "currentReceipt", slot: "currentReceipt" },
|
{ label: "期末余额", prop: "endBalance", slot: "endBalance" },
|
{ label: "操作", prop: "operation", slot: "operation", width: "150", fixed: "right" },
|
];
|
|
const dataList = ref([]);
|
const detailDialogVisible = ref(false);
|
const currentCustomer = ref("");
|
const currentPeriod = ref("");
|
const detailData = ref([]);
|
|
const generateDialogVisible = ref(false);
|
const salesLoading = ref(false);
|
const salesData = ref([]);
|
const selectedSales = ref([]);
|
|
const generateForm = reactive({
|
customerId: "",
|
customerName: "",
|
period: "",
|
beginBalance: 0,
|
currentReceivable: 0,
|
currentReceipt: 0,
|
});
|
|
const canGenerate = computed(() => {
|
return generateForm.customerId && generateForm.period && selectedSales.value.length > 0;
|
});
|
|
const customerList = [
|
{ id: 1, name: "北京科技有限公司" },
|
{ id: 2, name: "上海贸易公司" },
|
{ id: 3, name: "广州实业有限公司" },
|
{ id: 4, name: "深圳电子公司" },
|
];
|
|
const mockData = [
|
{ id: 1, statementCode: "DZ202401001", customerId: 1, customerName: "北京科技有限公司", period: "2024-01", beginBalance: 10000, currentReceivable: 15000, currentReceipt: 8000, endBalance: 17000 },
|
{ id: 2, statementCode: "DZ202401002", customerId: 2, customerName: "上海贸易公司", period: "2024-01", beginBalance: 5000, currentReceivable: 12000, currentReceipt: 10000, endBalance: 7000 },
|
{ id: 3, statementCode: "DZ202402001", customerId: 1, customerName: "北京科技有限公司", period: "2024-02", beginBalance: 17000, currentReceivable: 20000, currentReceipt: 15000, endBalance: 22000 },
|
];
|
|
const calculateEndBalance = (beginBalance, currentReceivable, currentReceipt) => {
|
return beginBalance + currentReceivable - currentReceipt;
|
};
|
|
const formatMoney = (value) => {
|
if (value === undefined || value === null) return "0.00";
|
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
};
|
|
const getTableData = () => {
|
let result = [...mockData];
|
if (filters.customerId) {
|
result = result.filter(item => item.customerId === filters.customerId);
|
}
|
if (filters.startMonth && filters.endMonth) {
|
result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth);
|
}
|
pagination.total = result.length;
|
dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
|
};
|
|
const resetFilters = () => {
|
filters.customerId = "";
|
filters.startMonth = "";
|
filters.endMonth = "";
|
pagination.currentPage = 1;
|
getTableData();
|
};
|
|
const changePage = ({ current, size }) => {
|
pagination.currentPage = current;
|
pagination.pageSize = size;
|
getTableData();
|
};
|
|
const generateStatement = () => {
|
generateForm.customerId = "";
|
generateForm.customerName = "";
|
generateForm.period = "";
|
generateForm.beginBalance = 0;
|
generateForm.currentReceivable = 0;
|
generateForm.currentReceipt = 0;
|
salesData.value = [];
|
selectedSales.value = [];
|
generateDialogVisible.value = true;
|
};
|
|
const onCustomerChange = (customerId) => {
|
const customer = customerList.find(item => item.id === customerId);
|
if (customer) {
|
generateForm.customerName = customer.name;
|
}
|
loadSalesData();
|
};
|
|
const onPeriodChange = () => {
|
loadSalesData();
|
};
|
|
const loadSalesData = () => {
|
if (!generateForm.customerId || !generateForm.period) {
|
salesData.value = [];
|
return;
|
}
|
|
salesLoading.value = true;
|
|
setTimeout(() => {
|
const mockSalesData = [
|
{ id: 1, date: generateForm.period + "-03", code: "CK2024001", type: "出库", amount: 8000, remark: "产品A销售" },
|
{ id: 2, date: generateForm.period + "-08", code: "SK2024001", type: "收款", amount: 5000, remark: "客户回款" },
|
{ id: 3, date: generateForm.period + "-12", code: "CK2024002", type: "出库", amount: 12000, remark: "产品B销售" },
|
{ id: 4, date: generateForm.period + "-15", code: "TH2024001", type: "退货", amount: 2000, remark: "质量问题退货" },
|
{ id: 5, date: generateForm.period + "-20", code: "CK2024003", type: "出库", amount: 5000, remark: "产品C销售" },
|
{ id: 6, date: generateForm.period + "-25", code: "SK2024002", type: "收款", amount: 8000, remark: "客户回款" },
|
];
|
|
salesData.value = mockSalesData;
|
|
const lastPeriod = getLastPeriod(generateForm.period);
|
const lastStatement = mockData.find(item =>
|
item.customerId === generateForm.customerId && item.period === lastPeriod
|
);
|
generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0;
|
|
calculateSummary();
|
|
salesLoading.value = false;
|
}, 500);
|
};
|
|
const getLastPeriod = (period) => {
|
const [year, month] = period.split("-").map(Number);
|
if (month === 1) {
|
return `${year - 1}-12`;
|
}
|
return `${year}-${String(month - 1).padStart(2, "0")}`;
|
};
|
|
const calculateSummary = () => {
|
let receivable = 0;
|
let receipt = 0;
|
|
selectedSales.value.forEach(item => {
|
if (item.type === "出库") {
|
receivable += item.amount;
|
} else if (item.type === "退货") {
|
receivable -= item.amount;
|
} else if (item.type === "收款") {
|
receipt += item.amount;
|
}
|
});
|
|
generateForm.currentReceivable = receivable;
|
generateForm.currentReceipt = receipt;
|
};
|
|
const handleSalesSelectionChange = (selection) => {
|
selectedSales.value = selection;
|
calculateSummary();
|
};
|
|
const confirmGenerate = () => {
|
const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
|
const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt);
|
|
mockData.unshift({
|
id: newId,
|
statementCode: "DZ" + Date.now(),
|
customerId: generateForm.customerId,
|
customerName: generateForm.customerName,
|
period: generateForm.period,
|
beginBalance: generateForm.beginBalance,
|
currentReceivable: generateForm.currentReceivable,
|
currentReceipt: generateForm.currentReceipt,
|
endBalance,
|
});
|
|
generateDialogVisible.value = false;
|
ElMessage.success("对账单生成成功");
|
getTableData();
|
};
|
|
const viewDetail = (row) => {
|
currentCustomer.value = row.customerName;
|
currentPeriod.value = row.period;
|
|
const saleOutAmount = Math.floor(row.currentReceivable * 0.6);
|
const returnAmount = Math.floor(row.currentReceivable * 0.1);
|
const firstReceipt = Math.floor(row.currentReceipt * 0.4);
|
const secondReceipt = row.currentReceipt - firstReceipt;
|
|
let runningBalance = row.beginBalance;
|
|
detailData.value = [
|
{ date: row.period + "-01", type: "期初", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "期初余额" },
|
{ date: row.period + "-05", type: "出库", code: "CK2024001", debit: saleOutAmount, credit: 0, balance: runningBalance += saleOutAmount, remark: "销售出库" },
|
{ date: row.period + "-10", type: "收款", code: "SK2024001", debit: 0, credit: firstReceipt, balance: runningBalance -= firstReceipt, remark: "客户回款" },
|
{ date: row.period + "-15", type: "出库", code: "CK2024002", debit: row.currentReceivable - saleOutAmount - returnAmount, credit: 0, balance: runningBalance += (row.currentReceivable - saleOutAmount - returnAmount), remark: "销售出库" },
|
{ date: row.period + "-20", type: "退货", code: "TH2024001", debit: 0, credit: returnAmount, balance: runningBalance -= returnAmount, remark: "销售退货" },
|
{ date: row.period + "-25", type: "收款", code: "SK2024002", debit: 0, credit: secondReceipt, balance: runningBalance -= secondReceipt, remark: "客户回款" },
|
];
|
|
detailDialogVisible.value = true;
|
};
|
|
const printStatement = (row) => {
|
ElMessage.info(`打印对账单: ${row.statementCode}`);
|
};
|
|
const printDetail = () => {
|
ElMessage.info("打印明细");
|
};
|
|
const handleOut = () => {
|
ElMessage.success("导出成功");
|
};
|
|
onMounted(() => {
|
getTableData();
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 15px;
|
}
|
|
.text-success {
|
color: #67c23a;
|
}
|
|
.text-danger {
|
color: #f56c6c;
|
}
|
|
.text-primary {
|
color: #409eff;
|
}
|
|
.statement-header {
|
text-align: center;
|
margin-bottom: 20px;
|
h3 {
|
margin: 0 0 10px 0;
|
}
|
p {
|
color: #909399;
|
margin: 0;
|
}
|
}
|
|
.sales-section {
|
margin-top: 20px;
|
|
.section-title {
|
font-size: 16px;
|
font-weight: bold;
|
margin-bottom: 15px;
|
padding-left: 10px;
|
border-left: 4px solid #409eff;
|
}
|
}
|
|
.summary-row {
|
display: flex;
|
justify-content: space-around;
|
padding: 15px;
|
background-color: #f5f7fa;
|
border-radius: 4px;
|
margin-top: 15px;
|
|
span {
|
font-size: 14px;
|
|
strong {
|
font-size: 16px;
|
margin-left: 5px;
|
}
|
}
|
}
|
|
.empty-tip {
|
margin-top: 30px;
|
}
|
</style>
|