<template>
|
<div class="reconciliation-workspace">
|
<div class="workspace-heading">
|
<h2>财务对账</h2>
|
<p>比对收支、资金流水与外部凭证,记录差异处理方案并归档。</p>
|
</div>
|
<el-form inline :model="query">
|
<el-form-item label="对账类型"
|
><el-select v-model="query.reconType" clearable style="width: 150px"
|
><el-option
|
v-for="(label, key) in reconTypeText"
|
:key="key"
|
:label="label"
|
:value="key" /></el-select
|
></el-form-item>
|
<el-form-item label="状态"
|
><el-select v-model="query.status" clearable style="width: 130px"
|
><el-option
|
v-for="(label, key) in statusText"
|
:key="key"
|
:label="label"
|
:value="key" /></el-select
|
></el-form-item>
|
<el-form-item label="对象"
|
><el-input v-model="query.target" clearable
|
/></el-form-item>
|
<el-form-item
|
><el-button type="primary" @click="loadTasks">查询</el-button
|
><el-button @click="openCreate">发起对账</el-button></el-form-item
|
>
|
</el-form>
|
|
<el-alert
|
v-if="loadError"
|
:title="loadError"
|
type="error"
|
show-icon
|
:closable="false"
|
class="load-error"
|
/>
|
<el-table
|
:data="tasks"
|
border
|
v-loading="loading"
|
empty-text="暂无对账任务,可点击“发起对账”创建"
|
>
|
<el-table-column prop="id" label="任务号" width="80" />
|
<el-table-column label="类型"
|
><template #default="{ row }">{{
|
reconTypeText[row.reconType]
|
}}</template></el-table-column
|
>
|
<el-table-column label="期间" min-width="190"
|
><template #default="{ row }"
|
>{{ row.periodStart }} 至 {{ row.periodEnd }}</template
|
></el-table-column
|
>
|
<el-table-column prop="target" label="对账对象" />
|
<el-table-column prop="systemAmount" label="系统金额" />
|
<el-table-column prop="externalAmount" label="对方金额" />
|
<el-table-column prop="diffAmount" label="差额" />
|
<el-table-column label="结果"
|
><template #default="{ row }"
|
><el-tag :type="row.result === '0' ? 'success' : 'danger'">{{
|
row.result === "0" ? "一致" : "有差异"
|
}}</el-tag></template
|
></el-table-column
|
>
|
<el-table-column label="状态"
|
><template #default="{ row }">{{
|
statusText[row.status]
|
}}</template></el-table-column
|
>
|
<el-table-column label="操作" width="260" fixed="right"
|
><template #default="{ row }">
|
<el-button link type="primary" @click="showDiff(row)"
|
>差异明细</el-button
|
>
|
<el-button
|
link
|
type="primary"
|
:disabled="row.status === '2'"
|
@click="openHandle(row)"
|
>处理</el-button
|
>
|
<el-button
|
link
|
type="success"
|
:disabled="row.status === '2'"
|
@click="archive(row)"
|
>归档</el-button
|
>
|
<el-button link @click="exportDiff(row)">导出</el-button>
|
</template></el-table-column
|
>
|
</el-table>
|
<pagination
|
v-show="total > 0"
|
:total="total"
|
:page="query.current"
|
:limit="query.size"
|
@pagination="
|
(p) => {
|
query.current = p.page;
|
query.size = p.limit;
|
loadTasks();
|
}
|
"
|
/>
|
|
<el-dialog v-model="createVisible" title="发起对账" width="760px">
|
<el-form :model="createForm" label-width="100px">
|
<el-form-item label="对账类型"
|
><el-select v-model="createForm.reconType"
|
><el-option
|
v-for="(label, key) in reconTypeText"
|
:key="key"
|
:label="label"
|
:value="key" /></el-select
|
></el-form-item>
|
<el-form-item label="对账期间"
|
><el-date-picker
|
v-model="period"
|
type="daterange"
|
value-format="YYYY-MM-DD"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
/></el-form-item>
|
<el-form-item label="资金账户"
|
><el-select v-model="createForm.accountId" clearable
|
><el-option
|
v-for="a in accounts"
|
:key="a.id"
|
:label="a.accountName"
|
:value="a.id" /></el-select
|
></el-form-item>
|
<el-form-item label="对账对象"
|
><el-input
|
v-model="createForm.target"
|
placeholder="银行/供应商/客户名称"
|
/></el-form-item>
|
</el-form>
|
<template v-if="createForm.reconType !== '0'">
|
<el-divider content-position="left">外部流水(手工录入)</el-divider>
|
<div class="external-actions">
|
<el-button size="small" @click="addExternal">新增外部流水</el-button>
|
</div>
|
<el-table :data="createForm.externalList" border max-height="260">
|
<el-table-column label="日期" width="145"
|
><template #default="{ row }"
|
><el-date-picker
|
v-model="row.extDate"
|
value-format="YYYY-MM-DD"
|
style="width: 125px" /></template
|
></el-table-column>
|
<el-table-column label="往来单位"
|
><template #default="{ row }"
|
><el-input v-model="row.extCounterparty" /></template
|
></el-table-column>
|
<el-table-column label="方向" width="100"
|
><template #default="{ row }"
|
><el-select v-model="row.direction"
|
><el-option label="收入" :value="1" /><el-option
|
label="支出"
|
:value="-1" /></el-select></template
|
></el-table-column>
|
<el-table-column label="金额" width="140"
|
><template #default="{ row }"
|
><el-input-number
|
v-model="row.amount"
|
:min="0.01"
|
:precision="2"
|
style="width: 120px" /></template
|
></el-table-column>
|
<el-table-column label="凭证号"
|
><template #default="{ row }"
|
><el-input v-model="row.voucherNo" /></template
|
></el-table-column>
|
<el-table-column width="60"
|
><template #default="{ $index }"
|
><el-button
|
link
|
type="danger"
|
@click="createForm.externalList.splice($index, 1)"
|
>删</el-button
|
></template
|
></el-table-column
|
>
|
</el-table>
|
</template>
|
<template #footer
|
><el-button @click="createVisible = false">取消</el-button
|
><el-button type="primary" @click="submitTask"
|
>开始比对</el-button
|
></template
|
>
|
</el-dialog>
|
|
<el-drawer v-model="diffVisible" title="对账差异明细" size="70%">
|
<el-descriptions v-if="currentTask" :column="4" border class="summary">
|
<el-descriptions-item label="任务号">{{
|
currentTask.id
|
}}</el-descriptions-item
|
><el-descriptions-item label="系统金额">{{
|
currentTask.systemAmount
|
}}</el-descriptions-item
|
><el-descriptions-item label="对方金额">{{
|
currentTask.externalAmount
|
}}</el-descriptions-item
|
><el-descriptions-item label="差额">{{
|
currentTask.diffAmount
|
}}</el-descriptions-item>
|
</el-descriptions>
|
<el-descriptions
|
v-if="currentTask?.handleNote"
|
:column="1"
|
border
|
class="summary"
|
>
|
<el-descriptions-item label="处理方案">{{
|
currentTask.handleNote
|
}}</el-descriptions-item>
|
</el-descriptions>
|
<el-table :data="diffs" border>
|
<el-table-column label="差异类型"
|
><template #default="{ row }">{{
|
diffTypeText[row.diffType]
|
}}</template></el-table-column
|
>
|
<el-table-column prop="bizType" label="业务类型" /><el-table-column
|
prop="bizId"
|
label="业务ID"
|
/><el-table-column prop="bizDate" label="日期" /><el-table-column
|
prop="counterparty"
|
label="往来单位"
|
/><el-table-column
|
prop="systemAmount"
|
label="系统金额"
|
/><el-table-column
|
prop="externalAmount"
|
label="对方金额"
|
/><el-table-column prop="remark" label="说明" min-width="190" />
|
</el-table>
|
<el-divider content-position="left">外部凭证</el-divider>
|
<el-table :data="externals" border
|
><el-table-column prop="extDate" label="日期" /><el-table-column
|
prop="extCounterparty"
|
label="往来单位" /><el-table-column
|
prop="amount"
|
label="金额" /><el-table-column prop="direction" label="方向"
|
><template #default="{ row }">{{
|
row.direction === 1 ? "收入" : "支出"
|
}}</template></el-table-column
|
><el-table-column prop="voucherNo" label="凭证号"
|
/></el-table>
|
</el-drawer>
|
|
<el-dialog v-model="handleVisible" title="填写处理方案" width="520px">
|
<el-input
|
v-model="handleForm.handleNote"
|
type="textarea"
|
:rows="5"
|
placeholder="请输入差异处理方案"
|
/>
|
<template #footer
|
><el-button @click="handleVisible = false">取消</el-button
|
><el-button type="primary" @click="saveHandle"
|
>保存并标记处理中</el-button
|
></template
|
>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, nextTick } from "vue";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
import { saveAs } from "file-saver";
|
import pagination from "@/components/PIMTable/Pagination.vue";
|
import { getFinanceAccountList } from "@/api/financialManagement/fundsManagement";
|
import {
|
getReconciliationTaskPage,
|
createReconciliationTask,
|
getReconciliationDiff,
|
getReconciliationExternal,
|
handleReconciliationTask,
|
archiveReconciliationTask,
|
exportReconciliationDiff,
|
} from "@/api/financialManagement/financialReconciliation";
|
|
const reconTypeText = {
|
0: "内部对账",
|
1: "银行对账",
|
2: "供应商对账",
|
3: "客户对账",
|
},
|
statusText = { 0: "待处理", 1: "处理中", 2: "已归档" },
|
diffTypeText = {
|
0: "系统有外部无",
|
1: "外部有系统无",
|
2: "金额不符",
|
3: "日期不符",
|
};
|
const tasks = ref([]),
|
total = ref(0),
|
loading = ref(false),
|
loadError = ref(""),
|
accounts = ref([]),
|
createVisible = ref(false),
|
diffVisible = ref(false),
|
handleVisible = ref(false),
|
period = ref([]),
|
diffs = ref([]),
|
externals = ref([]),
|
currentTask = ref(null);
|
const query = reactive({
|
current: 1,
|
size: 10,
|
reconType: "",
|
status: "",
|
target: "",
|
}),
|
createForm = reactive({
|
reconType: "0",
|
accountId: null,
|
target: "",
|
externalList: [],
|
}),
|
handleForm = reactive({ id: null, handleNote: "" });
|
const loadTasks = async () => {
|
loading.value = true;
|
loadError.value = "";
|
try {
|
const r = await getReconciliationTaskPage(query);
|
tasks.value = r.data?.records || [];
|
total.value = r.data?.total || 0;
|
} catch (error) {
|
tasks.value = [];
|
total.value = 0;
|
loadError.value =
|
error?.message ||
|
error?.msg ||
|
"对账任务加载失败,请检查后端服务及数据库脚本";
|
} finally {
|
loading.value = false;
|
}
|
};
|
const openCreate = () => {
|
Object.assign(createForm, {
|
reconType: "0",
|
accountId: null,
|
target: "",
|
externalList: [],
|
});
|
period.value = [];
|
createVisible.value = true;
|
};
|
const addExternal = () =>
|
createForm.externalList.push({
|
extDate: "",
|
extCounterparty: "",
|
amount: null,
|
direction: 1,
|
voucherNo: "",
|
remark: "",
|
});
|
const submitTask = async () => {
|
if (period.value.length !== 2) {
|
ElMessage.warning("请选择对账期间");
|
return;
|
}
|
if (createForm.reconType !== "0" && !createForm.target.trim()) {
|
ElMessage.warning("请输入银行、供应商或客户名称");
|
return;
|
}
|
if (
|
createForm.reconType !== "0" &&
|
createForm.externalList.some(
|
(row) => !row.extDate || !row.amount || row.amount <= 0
|
)
|
) {
|
ElMessage.warning("请填写外部凭证的日期和正数金额");
|
return;
|
}
|
try {
|
await createReconciliationTask({
|
...createForm,
|
periodStart: period.value[0],
|
periodEnd: period.value[1],
|
});
|
ElMessage.success("对账任务已生成");
|
createVisible.value = false;
|
await loadTasks();
|
} catch (error) {
|
ElMessage.error(error?.message || "对账任务生成失败");
|
}
|
};
|
const showDiff = async (row) => {
|
currentTask.value = row;
|
const [d, e] = await Promise.all([
|
getReconciliationDiff(row.id),
|
getReconciliationExternal(row.id),
|
]);
|
diffs.value = d.data || [];
|
externals.value = e.data || [];
|
diffVisible.value = true;
|
};
|
const openHandle = (row) => {
|
handleForm.id = row.id;
|
handleForm.handleNote = row.handleNote || "";
|
handleVisible.value = true;
|
};
|
const saveHandle = async () => {
|
await handleReconciliationTask(handleForm.id, {
|
handleNote: handleForm.handleNote,
|
status: "1",
|
});
|
ElMessage.success("处理方案已保存");
|
handleVisible.value = false;
|
loadTasks();
|
};
|
const archive = async (row) => {
|
if (row.result === "1" && !row.handleNote?.trim()) {
|
ElMessage.warning("请先填写差异处理方案");
|
return;
|
}
|
try {
|
await ElMessageBox.confirm(
|
"归档后该任务将进入已归档状态,是否继续?",
|
"归档"
|
);
|
await archiveReconciliationTask(row.id);
|
ElMessage.success("已归档");
|
await loadTasks();
|
} catch (error) {
|
if (error !== "cancel" && error !== "close") {
|
ElMessage.error(error?.message || "归档失败");
|
}
|
}
|
};
|
const exportDiff = async (row) => {
|
const blob = await exportReconciliationDiff(row.id);
|
saveAs(blob, `对账差异-${row.id}.xlsx`);
|
};
|
onMounted(() => {
|
nextTick(() => {
|
loadTasks();
|
getFinanceAccountList()
|
.then((r) => {
|
accounts.value = r.data || [];
|
})
|
.catch((error) => {
|
accounts.value = [];
|
console.error("资金账户加载失败:", error);
|
});
|
});
|
});
|
</script>
|
|
<style scoped>
|
.reconciliation-workspace {
|
padding: 4px;
|
}
|
.workspace-heading {
|
margin-bottom: 16px;
|
}
|
.workspace-heading h2 {
|
margin: 0 0 6px;
|
font-size: 20px;
|
}
|
.workspace-heading p {
|
margin: 0;
|
color: #606266;
|
}
|
.load-error {
|
margin-bottom: 12px;
|
}
|
.external-actions {
|
margin-bottom: 8px;
|
}
|
.summary {
|
margin-bottom: 16px;
|
}
|
</style>
|