更新各项功能
This commit is contained in:
@@ -2,7 +2,13 @@
|
||||
<div class="contract-payback">
|
||||
<div class="section-head">
|
||||
<span class="section-title">回款计划</span>
|
||||
<el-button type="primary" size="small" :icon="Plus" @click="openCreate">新建回款计划</el-button>
|
||||
<el-tooltip :disabled="!paidOff" content="回款已全部完成(进度 100%),不可再新建回款计划" placement="top">
|
||||
<span class="btn-wrap">
|
||||
<el-button type="primary" size="small" :icon="Plus" :disabled="paidOff" @click="openCreate">
|
||||
新建回款计划
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border size="small">
|
||||
@@ -43,6 +49,77 @@
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- ============================ 回款进度 ============================ -->
|
||||
<div class="section-head progress-head">
|
||||
<span class="section-title">回款进度</span>
|
||||
<div class="progress-summary">
|
||||
<span>计划回款总额:<b class="primary">¥{{ formatMoney(progressSummary.total_planned) }}</b></span>
|
||||
<span>已回款:<b class="success">¥{{ formatMoney(progressSummary.total_received) }}</b></span>
|
||||
<span>待回款:<b class="warning">¥{{ formatMoney((progressSummary.total_planned || 0) - (progressSummary.total_received || 0)) }}</b></span>
|
||||
</div>
|
||||
<el-tooltip :disabled="!paidOff" content="回款已全部完成(进度 100%),不可再新建回款计划" placement="top">
|
||||
<span class="btn-wrap">
|
||||
<el-button type="primary" size="small" :icon="Plus" :disabled="paidOff" @click="openCreate">
|
||||
新建回款计划
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="progressLoading" :data="progressList" border size="small" row-key="rowKey">
|
||||
<el-table-column label="回款周期" width="90" align="center">
|
||||
<template #default="{ row }">{{ paybackCycleText(row.plan_type) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="明细" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.name || `第${row.seq}期` }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="进度百分比" width="100" align="center">
|
||||
<template #default="{ row }">{{ row.percent ? `${row.percent}%` : "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划金额" width="120" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划日期" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.plan_date || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已回款" width="120" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.received_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回款日期" width="110" align="center">
|
||||
<template #default="{ row }">{{ (row.received_date && row.received_date !== "<nil>") ? row.received_date : "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="逾期" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="paybackItemOverdue(row).overdue" type="danger" size="small" effect="dark">
|
||||
{{ overdueText(paybackItemOverdue(row).days) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="paybackItemStatusTag(row.status)" size="small">{{ paybackItemStatusText(row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流水回执" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="row.receipt_url" type="primary" :href="row.receipt_url" target="_blank" :underline="false">查看回执</el-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="负责人" width="90">
|
||||
<template #default="{ row }">{{ row.owner_user_name || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openReceive(row)">回款登记</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="该合同暂无回款进度,先新建回款计划" :image-size="60" />
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- 新建 / 编辑回款计划(预设当前合同) -->
|
||||
<PaybackCreate
|
||||
v-model:visible="createVisible"
|
||||
@@ -53,13 +130,68 @@
|
||||
|
||||
<!-- 回款计划详情 -->
|
||||
<PaybackDetail v-model:visible="detailVisible" :payback="currentRow" @edit="handleEdit" />
|
||||
|
||||
<!-- 回款登记 -->
|
||||
<el-dialog v-model="receiveVisible" title="回款登记" width="440px">
|
||||
<el-form :model="receiveForm" label-width="90px">
|
||||
<el-form-item label="明细">
|
||||
<span>{{ receiveForm.name || `第${receiveForm.seq}期` }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划金额">
|
||||
<span class="amount">¥{{ formatMoney(receiveForm.amount) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="实收金额">
|
||||
<el-input-number
|
||||
v-model="receiveForm.received_amount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回款日期">
|
||||
<el-date-picker
|
||||
v-model="receiveForm.received_date"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流水回执">
|
||||
<div class="receipt-block">
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
:http-request="handleReceiptUpload"
|
||||
:disabled="uploading"
|
||||
accept=".jpg,.jpeg,.png,.pdf,.doc,.docx,.xls,.xlsx"
|
||||
>
|
||||
<el-button :icon="Upload" :loading="uploading" size="small">上传流水回执</el-button>
|
||||
</el-upload>
|
||||
<div v-if="receiveForm.receipt_url" class="receipt-file">
|
||||
<el-link type="primary" :href="receiveForm.receipt_url" target="_blank" :underline="false">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="receipt-name">{{ receiveForm.receipt_name || "流水回执" }}</span>
|
||||
</el-link>
|
||||
<el-button link type="danger" size="small" :icon="Delete" @click="clearReceipt">移除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="receiveVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="receiveSaving" @click="submitReceive">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import { getPaybackList } from "@/api/crmPayback";
|
||||
import { computed, ref, reactive } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Plus, Upload, Document, Delete } from "@element-plus/icons-vue";
|
||||
import { getPaybackList, getPaybackProgress, registerPaybackReceive } from "@/api/crmPayback";
|
||||
import { uploadFile } from "@/api/file";
|
||||
import PaybackCreate from "../../payback/components/create.vue";
|
||||
import PaybackDetail from "../../payback/components/detail.vue";
|
||||
import {
|
||||
@@ -67,6 +199,10 @@ import {
|
||||
paybackMethodText,
|
||||
paybackStatusText,
|
||||
paybackStatusTag,
|
||||
paybackItemStatusText,
|
||||
paybackItemStatusTag,
|
||||
paybackItemOverdue,
|
||||
overdueText,
|
||||
formatMoney,
|
||||
} from "../../dict";
|
||||
|
||||
@@ -84,6 +220,18 @@ const detailVisible = ref(false);
|
||||
const currentRow = ref(null);
|
||||
const editRow = ref(null);
|
||||
|
||||
/** 回款进度(摊平的分期明细) */
|
||||
const progressLoading = ref(false);
|
||||
const progressList = ref([]);
|
||||
const progressSummary = ref({ total_planned: 0, total_received: 0 });
|
||||
|
||||
/** 回款计划是否已全部收齐(总进度达到 100%),收齐后禁止再新建回款计划 */
|
||||
const paidOff = computed(() => {
|
||||
const planned = Number(progressSummary.value.total_planned) || 0;
|
||||
if (planned <= 0) return false;
|
||||
return (Number(progressSummary.value.total_received) || 0) >= planned;
|
||||
});
|
||||
|
||||
/** 回款进度百分比 */
|
||||
function paybackPercent(row) {
|
||||
const total = Number(row.total_amount) || 0;
|
||||
@@ -92,6 +240,11 @@ function paybackPercent(row) {
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
// 兜底拦截:回款已收齐时不允许再建计划
|
||||
if (paidOff.value) {
|
||||
ElMessage.warning("回款已全部完成(进度 100%),不可再新建回款计划");
|
||||
return;
|
||||
}
|
||||
editRow.value = null;
|
||||
createVisible.value = true;
|
||||
}
|
||||
@@ -118,6 +271,8 @@ async function reload() {
|
||||
const contractId = props.contract?.id;
|
||||
if (!contractId) {
|
||||
list.value = [];
|
||||
progressList.value = [];
|
||||
progressSummary.value = { total_planned: 0, total_received: 0 };
|
||||
emit("count", 0);
|
||||
return;
|
||||
}
|
||||
@@ -131,6 +286,102 @@ async function reload() {
|
||||
loading.value = false;
|
||||
emit("count", list.value.length);
|
||||
}
|
||||
await fetchProgress();
|
||||
}
|
||||
|
||||
/** 拉取本合同回款进度 */
|
||||
async function fetchProgress() {
|
||||
const contractId = props.contract?.id;
|
||||
if (!contractId) return;
|
||||
progressLoading.value = true;
|
||||
try {
|
||||
const res = await getPaybackProgress({ contract_id: contractId });
|
||||
progressList.value = (res?.data?.list || []).map((r) => ({ ...r, rowKey: `${r.payback_id}_${r.seq}` }));
|
||||
progressSummary.value = {
|
||||
total_planned: res?.data?.total_planned || 0,
|
||||
total_received: res?.data?.total_received || 0,
|
||||
};
|
||||
} catch (e) {
|
||||
progressList.value = [];
|
||||
progressSummary.value = { total_planned: 0, total_received: 0 };
|
||||
ElMessage.error(e.message || "查询回款进度失败");
|
||||
} finally {
|
||||
progressLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------- 回款登记 ---------------------------- */
|
||||
const receiveVisible = ref(false);
|
||||
const receiveSaving = ref(false);
|
||||
const receiveForm = reactive({
|
||||
payback_id: null,
|
||||
seq: 0,
|
||||
name: "",
|
||||
amount: 0,
|
||||
received_amount: 0,
|
||||
received_date: "",
|
||||
receipt_url: "",
|
||||
receipt_name: "",
|
||||
});
|
||||
|
||||
function openReceive(row) {
|
||||
Object.assign(receiveForm, {
|
||||
payback_id: row.payback_id,
|
||||
seq: row.seq,
|
||||
name: row.name,
|
||||
amount: row.amount,
|
||||
received_amount: Number(row.received_amount) || 0,
|
||||
received_date: row.received_date && row.received_date !== "<nil>" ? row.received_date : "",
|
||||
receipt_url: row.receipt_url || "",
|
||||
receipt_name: row.receipt_name || "",
|
||||
});
|
||||
receiveVisible.value = true;
|
||||
}
|
||||
|
||||
const uploading = ref(false);
|
||||
async function handleReceiptUpload(options) {
|
||||
const file = options.file;
|
||||
uploading.value = true;
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
const res = await uploadFile(fd);
|
||||
const data = res?.data || {};
|
||||
receiveForm.receipt_url = data.url || "";
|
||||
receiveForm.receipt_name = data.name || file.name;
|
||||
ElMessage.success("上传成功");
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message || "上传失败");
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function clearReceipt() {
|
||||
receiveForm.receipt_url = "";
|
||||
receiveForm.receipt_name = "";
|
||||
}
|
||||
|
||||
async function submitReceive() {
|
||||
receiveSaving.value = true;
|
||||
try {
|
||||
await registerPaybackReceive(receiveForm.payback_id, {
|
||||
seq: receiveForm.seq,
|
||||
received_amount: Number(receiveForm.received_amount) || 0,
|
||||
received_date: receiveForm.received_date || "",
|
||||
receipt_url: receiveForm.receipt_url,
|
||||
receipt_name: receiveForm.receipt_name,
|
||||
});
|
||||
ElMessage.success("回款登记成功");
|
||||
receiveVisible.value = false;
|
||||
// 回款计划(含各期已回款金额、总进度)与回款进度明细一并刷新,无需重开抽屉
|
||||
await reload();
|
||||
emit("saved");
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message || "登记失败");
|
||||
} finally {
|
||||
receiveSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ reload });
|
||||
@@ -141,11 +392,57 @@ defineExpose({ reload });
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
margin: 18px 0 12px;
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-head {
|
||||
margin-top: 22px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.progress-summary {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
|
||||
b {
|
||||
font-weight: 600;
|
||||
}
|
||||
.primary { color: #409eff; }
|
||||
.success { color: #67c23a; }
|
||||
.warning { color: #e6a23c; }
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrap {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.receipt-block {
|
||||
width: 100%;
|
||||
|
||||
.receipt-file {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.receipt-name {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user