<template>
|
<div class="app-container">
|
<el-form :model="filters" :inline="true">
|
<el-form-item label="凭证字号:">
|
<el-input v-model="filters.voucherNo" placeholder="请输入凭证字号" clearable style="width: 200px;" />
|
</el-form-item>
|
<el-form-item label="凭证日期:">
|
<el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" clearable />
|
</el-form-item>
|
<el-form-item label="制单人:">
|
<el-select v-model="filters.creator" placeholder="请选择制单人" clearable style="width: 150px;">
|
<el-option label="张三" value="张三" />
|
<el-option label="李四" value="李四" />
|
<el-option label="王五" value="王五" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="状态:">
|
<el-select v-model="filters.status" placeholder="请选择状态" clearable style="width: 150px;">
|
<el-option label="未过账" value="unposted" />
|
<el-option label="已过账" value="posted" />
|
<el-option label="已作废" value="cancelled" />
|
</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>
|
<el-statistic title="借方合计" :value="totalDebit" precision="2" prefix="¥" />
|
<el-statistic title="贷方合计" :value="totalCredit" precision="2" prefix="¥" style="margin-left: 30px;" />
|
</div>
|
<div>
|
<el-button type="primary" @click="add" icon="Plus">新增凭证</el-button>
|
<el-button @click="handleImport" icon="Upload">导入</el-button>
|
<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 #debit="{ row }">
|
<span class="text-danger" v-if="row.debit > 0">¥{{ formatMoney(row.debit) }}</span>
|
<span v-else>-</span>
|
</template>
|
<template #credit="{ row }">
|
<span class="text-success" v-if="row.credit > 0">¥{{ formatMoney(row.credit) }}</span>
|
<span v-else>-</span>
|
</template>
|
<template #status="{ row }">
|
<el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
|
</template>
|
<template #operation="{ row }">
|
<el-button type="primary" link @click="view(row)">查看</el-button>
|
<el-button type="primary" link @click="edit(row)" v-if="row.status === 'unposted'">编辑</el-button>
|
<el-button type="success" link @click="handlePost(row)" v-if="row.status === 'unposted'">过账</el-button>
|
<el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'unposted'">作废</el-button>
|
</template>
|
</PIMTable>
|
</div>
|
|
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="900px" append-to-body>
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="凭证字号" prop="voucherNo">
|
<el-input v-model="form.voucherNo" placeholder="系统自动生成" disabled />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="凭证日期" prop="voucherDate">
|
<el-date-picker v-model="form.voucherDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%;" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="附件张数" prop="attachmentCount">
|
<el-input-number v-model="form.attachmentCount" :min="0" style="width: 100%;" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="凭证分录" prop="entries">
|
<el-table :data="form.entries" border style="width: 100%">
|
<el-table-column type="index" label="序号" width="60" />
|
<el-table-column prop="subjectCode" label="科目编码" width="120">
|
<template #default="{ $index }">
|
<el-select v-model="form.entries[$index].subjectCode" placeholder="选择科目" filterable style="width: 100%;" @change="(val) => handleSubjectChange(val, $index)">
|
<el-option v-for="item in subjectList" :key="item.code" :label="item.code" :value="item.code" />
|
</el-select>
|
</template>
|
</el-table-column>
|
<el-table-column prop="subjectName" label="科目名称" width="150">
|
<template #default="{ $index }">
|
<el-input v-model="form.entries[$index].subjectName" disabled />
|
</template>
|
</el-table-column>
|
<el-table-column prop="summary" label="摘要">
|
<template #default="{ $index }">
|
<el-input v-model="form.entries[$index].summary" placeholder="请输入摘要" />
|
</template>
|
</el-table-column>
|
<el-table-column prop="debit" label="借方金额" width="130">
|
<template #default="{ $index }">
|
<el-input-number v-model="form.entries[$index].debit" :min="0" :precision="2" style="width: 100%;" @change="calculateTotal" />
|
</template>
|
</el-table-column>
|
<el-table-column prop="credit" label="贷方金额" width="130">
|
<template #default="{ $index }">
|
<el-input-number v-model="form.entries[$index].credit" :min="0" :precision="2" style="width: 100%;" @change="calculateTotal" />
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="80">
|
<template #default="{ $index }">
|
<el-button type="danger" link @click="removeEntry($index)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<div style="display: flex; justify-content: space-between; margin-top: 10px;">
|
<el-button type="primary" link @click="addEntry">+ 添加分录</el-button>
|
<div>
|
<span style="margin-right: 20px;">合计: 借方 <span :class="totalDebitEntry === totalCreditEntry ? 'text-success' : 'text-danger'">¥{{ formatMoney(totalDebitEntry) }}</span></span>
|
<span>贷方 <span :class="totalDebitEntry === totalCreditEntry ? 'text-success' : 'text-danger'">¥{{ formatMoney(totalCreditEntry) }}</span></span>
|
</div>
|
</div>
|
</el-form-item>
|
<el-form-item label="制单人" prop="creator">
|
<el-input v-model="form.creator" disabled />
|
</el-form-item>
|
<el-form-item label="备注" prop="remark">
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="submitForm" :disabled="totalDebitEntry !== totalCreditEntry">确定</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, computed } from "vue";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
defineOptions({
|
name: "凭证管理",
|
});
|
|
const filters = reactive({
|
voucherNo: "",
|
dateRange: [],
|
creator: "",
|
status: "",
|
});
|
|
const pagination = reactive({
|
currentPage: 1,
|
pageSize: 10,
|
total: 0,
|
});
|
|
const columns = [
|
{ label: "凭证字号", prop: "voucherNo", width: "120" },
|
{ label: "凭证日期", prop: "voucherDate", width: "120" },
|
{ label: "摘要", prop: "summary", showOverflowTooltip: true },
|
{ label: "借方金额", prop: "debit", slot: "debit" },
|
{ label: "贷方金额", prop: "credit", slot: "credit" },
|
{ label: "制单人", prop: "creator", width: "100" },
|
{ label: "状态", prop: "status", slot: "status" },
|
{ label: "操作", prop: "operation", slot: "operation", width: "220", fixed: "right" },
|
];
|
|
const dataList = ref([]);
|
const dialogVisible = ref(false);
|
const dialogTitle = ref("");
|
const formRef = ref(null);
|
const isEdit = ref(false);
|
const currentId = ref(null);
|
|
const subjectList = [
|
{ code: "1001", name: "库存现金" },
|
{ code: "1002", name: "银行存款" },
|
{ code: "1122", name: "应收账款" },
|
{ code: "2202", name: "应付账款" },
|
{ code: "5001", name: "生产成本" },
|
{ code: "6001", name: "主营业务收入" },
|
{ code: "6401", name: "主营业务成本" },
|
];
|
|
const form = reactive({
|
voucherNo: "",
|
voucherDate: "",
|
attachmentCount: 0,
|
entries: [],
|
creator: "张三",
|
remark: "",
|
});
|
|
const rules = {
|
voucherDate: [{ required: true, message: "请选择凭证日期", trigger: "change" }],
|
};
|
|
const mockData = [
|
{ id: 1, voucherNo: "记-0001", voucherDate: "2024-01-15", summary: "销售收入", debit: 5650, credit: 5650, creator: "张三", status: "posted", entries: [{ subjectCode: "1002", subjectName: "银行存款", summary: "销售收入", debit: 5650, credit: 0 }, { subjectCode: "6001", subjectName: "主营业务收入", summary: "销售收入", debit: 0, credit: 5000 }, { subjectCode: "2221", subjectName: "应交税费", summary: "销项税额", debit: 0, credit: 650 }] },
|
{ id: 2, voucherNo: "记-0002", voucherDate: "2024-01-16", summary: "采购原材料", debit: 9040, credit: 9040, creator: "李四", status: "unposted", entries: [{ subjectCode: "5001", subjectName: "生产成本", summary: "采购原材料", debit: 8000, credit: 0 }, { subjectCode: "2221", subjectName: "应交税费", summary: "进项税额", debit: 1040, credit: 0 }, { subjectCode: "2202", subjectName: "应付账款", summary: "采购原材料", debit: 0, credit: 9040 }] },
|
{ id: 3, voucherNo: "记-0003", voucherDate: "2024-01-18", summary: "支付货款", debit: 5000, credit: 5000, creator: "张三", status: "posted", entries: [{ subjectCode: "2202", subjectName: "应付账款", summary: "支付货款", debit: 5000, credit: 0 }, { subjectCode: "1002", subjectName: "银行存款", summary: "支付货款", debit: 0, credit: 5000 }] },
|
];
|
|
const totalDebit = computed(() => {
|
return dataList.value.reduce((sum, item) => sum + Number(item.debit), 0);
|
});
|
|
const totalCredit = computed(() => {
|
return dataList.value.reduce((sum, item) => sum + Number(item.credit), 0);
|
});
|
|
const totalDebitEntry = computed(() => {
|
return form.entries.reduce((sum, item) => sum + Number(item.debit || 0), 0);
|
});
|
|
const totalCreditEntry = computed(() => {
|
return form.entries.reduce((sum, item) => sum + Number(item.credit || 0), 0);
|
});
|
|
const formatMoney = (value) => {
|
if (value === undefined || value === null) return "0.00";
|
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
};
|
|
const getStatusLabel = (status) => {
|
const map = { unposted: "未过账", posted: "已过账", cancelled: "已作废" };
|
return map[status] || status;
|
};
|
|
const getStatusType = (status) => {
|
const map = { unposted: "warning", posted: "success", cancelled: "info" };
|
return map[status] || "";
|
};
|
|
const getTableData = () => {
|
let result = [...mockData];
|
if (filters.voucherNo) {
|
result = result.filter(item => item.voucherNo.includes(filters.voucherNo));
|
}
|
if (filters.dateRange && filters.dateRange.length === 2) {
|
result = result.filter(item => item.voucherDate >= filters.dateRange[0] && item.voucherDate <= filters.dateRange[1]);
|
}
|
if (filters.creator) {
|
result = result.filter(item => item.creator === filters.creator);
|
}
|
if (filters.status) {
|
result = result.filter(item => item.status === filters.status);
|
}
|
pagination.total = result.length;
|
dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
|
};
|
|
const resetFilters = () => {
|
filters.voucherNo = "";
|
filters.dateRange = [];
|
filters.creator = "";
|
filters.status = "";
|
pagination.currentPage = 1;
|
getTableData();
|
};
|
|
const changePage = ({ current, size }) => {
|
pagination.currentPage = current;
|
pagination.pageSize = size;
|
getTableData();
|
};
|
|
const addEntry = () => {
|
form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
|
};
|
|
const removeEntry = (index) => {
|
form.entries.splice(index, 1);
|
calculateTotal();
|
};
|
|
const handleSubjectChange = (val, index) => {
|
const subject = subjectList.find(item => item.code === val);
|
if (subject) {
|
form.entries[index].subjectName = subject.name;
|
}
|
};
|
|
const calculateTotal = () => {
|
// 自动计算,由computed属性处理
|
};
|
|
const add = () => {
|
isEdit.value = false;
|
dialogTitle.value = "新增凭证";
|
Object.assign(form, {
|
voucherNo: "记-" + String(mockData.length + 1).padStart(4, "0"),
|
voucherDate: new Date().toISOString().split('T')[0],
|
attachmentCount: 0,
|
entries: [{ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 }],
|
creator: "张三",
|
remark: "",
|
});
|
dialogVisible.value = true;
|
};
|
|
const edit = (row) => {
|
isEdit.value = true;
|
currentId.value = row.id;
|
dialogTitle.value = "编辑凭证";
|
Object.assign(form, row);
|
dialogVisible.value = true;
|
};
|
|
const view = (row) => {
|
ElMessage.info(`查看凭证: ${row.voucherNo}`);
|
};
|
|
const handlePost = (row) => {
|
ElMessageBox.confirm("确认过账该凭证吗?", "提示", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "info",
|
}).then(() => {
|
const index = mockData.findIndex(item => item.id === row.id);
|
if (index !== -1) {
|
mockData[index].status = "posted";
|
}
|
ElMessage.success("过账成功");
|
getTableData();
|
});
|
};
|
|
const handleCancel = (row) => {
|
ElMessageBox.confirm("确认作废该凭证吗?", "提示", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
}).then(() => {
|
const index = mockData.findIndex(item => item.id === row.id);
|
if (index !== -1) {
|
mockData[index].status = "cancelled";
|
}
|
ElMessage.success("作废成功");
|
getTableData();
|
});
|
};
|
|
const handleImport = () => {
|
ElMessage.info("导入功能");
|
};
|
|
const handleOut = () => {
|
ElMessage.success("导出成功");
|
};
|
|
const submitForm = () => {
|
formRef.value.validate((valid) => {
|
if (valid) {
|
if (totalDebitEntry.value !== totalCreditEntry.value) {
|
ElMessage.error("借贷不平衡,请检查分录");
|
return;
|
}
|
const summary = form.entries.find(e => e.debit > 0)?.summary || "";
|
if (isEdit.value) {
|
const index = mockData.findIndex(item => item.id === currentId.value);
|
if (index !== -1) {
|
mockData[index] = { ...mockData[index], ...form, summary, debit: totalDebitEntry.value, credit: totalCreditEntry.value };
|
}
|
ElMessage.success("编辑成功");
|
} else {
|
const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
|
mockData.push({ id: newId, ...form, summary, debit: totalDebitEntry.value, credit: totalCreditEntry.value, status: "unposted" });
|
ElMessage.success("新增成功");
|
}
|
dialogVisible.value = false;
|
getTableData();
|
}
|
});
|
};
|
|
onMounted(() => {
|
getTableData();
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
|
> div:first-child {
|
display: flex;
|
align-items: center;
|
}
|
}
|
|
.text-success {
|
color: #67c23a;
|
font-weight: bold;
|
}
|
|
.text-danger {
|
color: #f56c6c;
|
font-weight: bold;
|
}
|
</style>
|