|
|
|
|
@ -0,0 +1,773 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="p-2 h-full flex flex-col">
|
|
|
|
|
<!-- 主体布局:左右分栏 -->
|
|
|
|
|
<el-card shadow="never" class="flex-1 overflow-hidden">
|
|
|
|
|
<div class="flex h-full" style="min-height: 600px">
|
|
|
|
|
<!-- 左侧:回款信息(可展开/收缩) -->
|
|
|
|
|
<div :class="['transition-all duration-300 relative', leftCollapsed ? 'w-12' : 'w-[527px]']" class="border-r border-gray-200 flex flex-col">
|
|
|
|
|
<!-- 左侧头部 -->
|
|
|
|
|
<div class="p-3 border-b border-gray-200 flex items-center justify-between bg-gray-50">
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<el-button link @click="leftCollapsed = !leftCollapsed" class="mr-2">
|
|
|
|
|
<el-icon :class="leftCollapsed ? 'rotate-180' : ''">
|
|
|
|
|
<DArrowLeft />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</el-button>
|
|
|
|
|
<span v-show="!leftCollapsed" class="font-medium text-base">回款信息</span>
|
|
|
|
|
</div>
|
|
|
|
|
<el-button v-if="!leftCollapsed" type="primary" size="small" @click="upload.open = true">
|
|
|
|
|
<el-icon class="mr-1"><Upload /></el-icon>
|
|
|
|
|
导入Excel
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 左侧内容区 -->
|
|
|
|
|
<div v-show="!leftCollapsed" class="flex-1 flex flex-col overflow-hidden">
|
|
|
|
|
<!-- 搜索区域 -->
|
|
|
|
|
<div class="p-3 border-b border-gray-100">
|
|
|
|
|
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="80px" class="mb-0">
|
|
|
|
|
<el-form-item label="客户名称" prop="customerName" class="mb-2">
|
|
|
|
|
<el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable @keyup.enter="handleQuery" size="small" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="状态" prop="installmentStatus" class="mb-2">
|
|
|
|
|
<el-select v-model="queryParams.installmentStatus" placeholder="请选择状态" clearable @keyup.enter="handleQuery">
|
|
|
|
|
<el-option v-for="dict in installment_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item class="mb-2">
|
|
|
|
|
<el-button type="primary" size="small" @click="handleQuery">搜索</el-button>
|
|
|
|
|
<el-button size="small" @click="resetQuery">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 回款列表 -->
|
|
|
|
|
<div class="flex-1 overflow-auto p-2">
|
|
|
|
|
<el-table
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
:data="finAccountInstallmentList"
|
|
|
|
|
highlight-current-row
|
|
|
|
|
@row-click="handleRowClick"
|
|
|
|
|
:row-class-name="getRowClassName"
|
|
|
|
|
border
|
|
|
|
|
size="small"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column label="回款编号" align="center" prop="installmentCode" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="客户名称" align="center" prop="customerName" width="100" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="回款金额" align="center" prop="paymentAmount" width="90">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span class="font-medium">¥{{ formatMoney(row.paymentAmount) }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="回款日期" align="center" prop="paymentDate" width="100">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ parseTime(row.paymentDate, '{y}-{m}-{d}') }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="状态" align="center" prop="installmentStatus" width="100">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag :options="installment_status" :value="scope.row.installmentStatus" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<div class="flex justify-end mt-2">
|
|
|
|
|
<pagination
|
|
|
|
|
v-show="total > 0"
|
|
|
|
|
:total="total"
|
|
|
|
|
v-model:page="queryParams.pageNum"
|
|
|
|
|
v-model:limit="queryParams.pageSize"
|
|
|
|
|
@pagination="getList"
|
|
|
|
|
small
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 右侧:分款明细 -->
|
|
|
|
|
<div class="flex-1 flex flex-col overflow-hidden">
|
|
|
|
|
<!-- 右侧头部 -->
|
|
|
|
|
<div class="p-3 border-b border-gray-200 flex items-center justify-between bg-gray-50">
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<span class="font-medium text-base">分款明细</span>
|
|
|
|
|
<span v-if="selectedInstallment" class="ml-2 text-gray-500 text-sm">
|
|
|
|
|
({{ selectedInstallment.installmentCode }}-{{ selectedInstallment.customerName }})
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<el-button v-if="selectedInstallment" type="primary" size="small" @click="handleAddDetail">
|
|
|
|
|
<el-icon class="mr-1"><Plus /></el-icon>
|
|
|
|
|
新增
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 右侧内容区 -->
|
|
|
|
|
<div class="flex-1 overflow-auto p-3">
|
|
|
|
|
<template v-if="selectedInstallment">
|
|
|
|
|
<!-- 回款信息卡片 -->
|
|
|
|
|
<el-card shadow="never" class="mb-3 bg-blue-50">
|
|
|
|
|
<el-descriptions :column="4" border size="small">
|
|
|
|
|
<el-descriptions-item label="回款金额">
|
|
|
|
|
<span class="font-medium text-lg text-blue-600">¥{{ formatMoney(selectedInstallment.paymentAmount) }}</span>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="已分款金额">
|
|
|
|
|
<span class="font-medium text-orange-600">¥{{ formatMoney(allocatedAmount) }}</span>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="剩余可分">
|
|
|
|
|
<span :class="remainingAmount < 0 ? 'text-red-600' : 'text-green-600'" class="font-medium">
|
|
|
|
|
¥{{ formatMoney(remainingAmount) }}
|
|
|
|
|
</span>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="状态">
|
|
|
|
|
<dict-tag :options="installment_status" :value="selectedInstallment.installmentStatus" />
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<!-- 金额校验警告 -->
|
|
|
|
|
<el-alert
|
|
|
|
|
v-if="remainingAmount !== 0"
|
|
|
|
|
:title="remainingAmount < 0 ? '分款金额超出回款金额' : '分款金额不足回款金额'"
|
|
|
|
|
type="error"
|
|
|
|
|
:closable="false"
|
|
|
|
|
class="mb-3"
|
|
|
|
|
show-icon
|
|
|
|
|
>
|
|
|
|
|
<template #default>
|
|
|
|
|
<span class="font-medium">
|
|
|
|
|
{{ remainingAmount < 0 ? '已超出' : '还差' }} ¥{{ formatMoney(Math.abs(remainingAmount)) }},分款金额不能超出回款金额
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-alert>
|
|
|
|
|
|
|
|
|
|
<!-- 分款明细表格 -->
|
|
|
|
|
<el-table v-loading="detailLoading" :data="finAccountInstallmentDetailList" border size="small">
|
|
|
|
|
<el-table-column label="项目编号" align="center" prop="projectCode" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="项目名称" align="center" prop="projectName" width="150" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="合同编号" align="center" prop="contractCode" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="合同名称" align="center" prop="contractName" width="150" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="分款金额" align="center" width="100">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span class="font-medium">¥{{ formatMoney(row.detailAmount) }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="分款阶段" align="center" prop="stageName" width="100" />
|
|
|
|
|
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="操作" align="center" width="100" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" @click="handleEditDetail(row)">编辑</el-button>
|
|
|
|
|
<el-button link type="danger" size="small" @click="handleDeleteDetail(row)">删除</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<!-- 无数据提示 -->
|
|
|
|
|
<el-empty
|
|
|
|
|
v-if="!detailLoading && finAccountInstallmentDetailList && finAccountInstallmentDetailList.length === 0"
|
|
|
|
|
description="暂无分款明细,请点击新增添加"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<el-empty v-else description="请选择左侧回款记录查看分款明细" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<!-- Excel导入对话框 -->
|
|
|
|
|
<el-dialog title="导入Excel" v-model="upload.open" width="600px" append-to-body>
|
|
|
|
|
<el-upload
|
|
|
|
|
ref="uploadRef"
|
|
|
|
|
:limit="1"
|
|
|
|
|
accept=".xlsx, .xls"
|
|
|
|
|
:headers="upload.headers"
|
|
|
|
|
:action="upload.url + '?updateSupport=' + upload.updateSupport"
|
|
|
|
|
:disabled="upload.isUploading"
|
|
|
|
|
:on-progress="handleFileUploadProgress"
|
|
|
|
|
:on-success="handleFileSuccess"
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
drag
|
|
|
|
|
>
|
|
|
|
|
<el-icon class="el-icon--upload">
|
|
|
|
|
<i-ep-upload-filled />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
|
|
|
<template #tip>
|
|
|
|
|
<div class="text-center el-upload__tip">
|
|
|
|
|
<div class="el-upload__tip"><el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据</div>
|
|
|
|
|
<span>仅允许导入xls、xlsx格式文件。</span>
|
|
|
|
|
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
|
|
|
|
<!-- 预览表格 -->
|
|
|
|
|
<!-- <div v-if="importPreviewData.length > 0" class="mt-4">-->
|
|
|
|
|
<!-- <div class="mb-2 text-base font-medium">预览数据 ({{ importPreviewData.length }} 条)</div>-->
|
|
|
|
|
<!-- <el-table :data="importPreviewData" border max-height="300" size="small">-->
|
|
|
|
|
<!-- <el-table-column type="index" label="序号" width="60" align="center" />-->
|
|
|
|
|
<!-- <el-table-column label="客户名称" prop="customerName" align="center" />-->
|
|
|
|
|
<!-- <el-table-column label="回款金额" prop="paymentAmount" align="center" />-->
|
|
|
|
|
<!-- <el-table-column label="回款日期" prop="paymentDate" align="center" />-->
|
|
|
|
|
<!-- <el-table-column label="备注" prop="remark" align="center" show-overflow-tooltip />-->
|
|
|
|
|
<!-- <el-table-column label="状态" align="center" width="80">-->
|
|
|
|
|
<!-- <template #default="{ row }">-->
|
|
|
|
|
<!-- <el-tag v-if="row.isRepeat" type="warning" size="small">重复</el-tag>-->
|
|
|
|
|
<!-- <el-tag v-else type="success" size="small">正常</el-tag>-->
|
|
|
|
|
<!-- </template>-->
|
|
|
|
|
<!-- </el-table-column>-->
|
|
|
|
|
<!-- </el-table>-->
|
|
|
|
|
<!-- <div v-if="duplicateCount > 0" class="mt-2 text-orange-500 text-sm">提示:检测到 {{ duplicateCount }} 条重复数据</div>-->
|
|
|
|
|
<!-- </div>-->
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
<el-button @click="upload.open = false">取 消</el-button>
|
|
|
|
|
<el-button :loading="upload.isUploading" type="primary" @click="submitImport">确定导入</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 项目选择对话框 -->
|
|
|
|
|
<ProjectSelectDialog v-model:visible="showProjectDialog" @project-selected="handleProjectSelected" />
|
|
|
|
|
|
|
|
|
|
<!-- 合同选择对话框(无合同时选择) -->
|
|
|
|
|
<ContractSelectDialog v-model:visible="showContractDialog" @contract-selected="handleContractSelected" />
|
|
|
|
|
|
|
|
|
|
<!-- 分款明细表单对话框 -->
|
|
|
|
|
<el-dialog :title="detailDialog.title" v-model="detailDialog.visible" width="500px" append-to-body>
|
|
|
|
|
<el-form ref="detailFormRef" :model="detailForm" :rules="detailRules" label-width="100px">
|
|
|
|
|
<el-form-item label="项目编号">
|
|
|
|
|
<el-input v-model="detailForm.projectCode" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="项目名称">
|
|
|
|
|
<el-input v-model="detailForm.projectName" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="合同编号">
|
|
|
|
|
<el-input v-model="detailForm.contractCode" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="合同名称">
|
|
|
|
|
<el-input v-model="detailForm.contractName" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="分款节点" prop="paymentStageId">
|
|
|
|
|
<el-select v-model="detailForm.paymentStageId" placeholder="请选择分款节点" style="width: 100%">
|
|
|
|
|
<el-option v-for="stage in paymentStageList" :key="stage.paymentStageId" :label="stage.stageName" :value="stage.paymentStageId" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="分款金额" prop="detailAmount">
|
|
|
|
|
<el-input-number
|
|
|
|
|
v-model="detailForm.detailAmount"
|
|
|
|
|
:precision="2"
|
|
|
|
|
:min="0.01"
|
|
|
|
|
:max="999999999"
|
|
|
|
|
placeholder="请输入分款金额"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
controls-position="right"
|
|
|
|
|
@change="handleDetailAmountChange"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="备注">
|
|
|
|
|
<el-input v-model="detailForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<!-- 金额校验提示 -->
|
|
|
|
|
<el-alert
|
|
|
|
|
v-if="formRemainingAmount !== 0"
|
|
|
|
|
:title="formRemainingAmount < 0 ? '分款金额超出回款金额' : '分款金额不足回款金额'"
|
|
|
|
|
:type="formRemainingAmount < 0 ? 'error' : 'warning'"
|
|
|
|
|
:closable="false"
|
|
|
|
|
show-icon
|
|
|
|
|
class="mb-2"
|
|
|
|
|
>
|
|
|
|
|
<template #default>
|
|
|
|
|
<span class="font-medium">
|
|
|
|
|
{{ formRemainingAmount < 0 ? '已超出' : '还差' }} ¥{{ formatMoney(Math.abs(formRemainingAmount)) }},分款总金额不能超出回款金额
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-alert>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
<el-button :loading="detailButtonLoading" type="primary" @click="submitDetailForm">确 定</el-button>
|
|
|
|
|
<el-button @click="cancelDetail">取 消</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup name="FinAccountInstallment" lang="ts">
|
|
|
|
|
import { importFinAccountInstallment, listFinAccountInstallment } from '@/api/oa/erp/finAccountInstallment';
|
|
|
|
|
import {
|
|
|
|
|
addFinAccountInstallmentDetail,
|
|
|
|
|
delFinAccountInstallmentDetail,
|
|
|
|
|
getErpFinAccountInstallmentDetailList,
|
|
|
|
|
updateFinAccountInstallmentDetail,
|
|
|
|
|
deleteInstallmentDetailByBo
|
|
|
|
|
} from '@/api/oa/erp/finAccountInstallmentDetail';
|
|
|
|
|
import { FinAccountInstallmentQuery, FinAccountInstallmentVO } from '@/api/oa/erp/finAccountInstallment/types';
|
|
|
|
|
import { FinAccountInstallmentDetailForm, FinAccountInstallmentDetailVO } from '@/api/oa/erp/finAccountInstallmentDetail/types';
|
|
|
|
|
import { getErpContractPaymentMethodJoinList } from '@/api/oa/erp/finAccountReceivable';
|
|
|
|
|
import { PaymentStageVO } from '@/api/oa/base/paymentStage/types';
|
|
|
|
|
|
|
|
|
|
import { getToken } from '@/utils/auth';
|
|
|
|
|
import { DArrowLeft, UploadFilled } from '@element-plus/icons-vue';
|
|
|
|
|
import ProjectSelectDialog from '@/views/oa/components/ProjectSelectDialog.vue';
|
|
|
|
|
import ContractSelectDialog from '@/views/oa/components/ContractSelectDialog.vue';
|
|
|
|
|
import { globalHeaders } from '@/utils/request';
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
|
|
|
|
const { installment_status } = toRefs<any>(proxy?.useDict('installment_status'));
|
|
|
|
|
|
|
|
|
|
// 左侧回款列表
|
|
|
|
|
const finAccountInstallmentList = ref<FinAccountInstallmentVO[]>([]);
|
|
|
|
|
const loading = ref(true);
|
|
|
|
|
const total = ref(0);
|
|
|
|
|
const leftCollapsed = ref(false);
|
|
|
|
|
const selectedInstallment = ref<FinAccountInstallmentVO | null>(null);
|
|
|
|
|
|
|
|
|
|
// 查询参数
|
|
|
|
|
const queryParams = reactive<FinAccountInstallmentQuery>({
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
customerName: undefined,
|
|
|
|
|
installmentStatus: undefined
|
|
|
|
|
});
|
|
|
|
|
const queryFormRef = ref<ElFormInstance>();
|
|
|
|
|
|
|
|
|
|
// 右侧分款明细
|
|
|
|
|
const finAccountInstallmentDetailList = ref<FinAccountInstallmentDetailVO[]>([]);
|
|
|
|
|
const detailLoading = ref(false);
|
|
|
|
|
|
|
|
|
|
// 计算金额
|
|
|
|
|
const allocatedAmount = computed(() => {
|
|
|
|
|
return Number((finAccountInstallmentDetailList.value || []).reduce((sum: number, item: any) => sum + Number(item.detailAmount || 0), 0));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const remainingAmount = computed(() => {
|
|
|
|
|
if (!selectedInstallment.value) return 0;
|
|
|
|
|
return Number(selectedInstallment.value.paymentAmount || 0) - allocatedAmount.value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 表单中的剩余金额(考虑当前正在编辑的金额)
|
|
|
|
|
const formRemainingAmount = computed(() => {
|
|
|
|
|
if (!selectedInstallment.value) return 0;
|
|
|
|
|
const currentDetailAmount = Number(detailForm.detailAmount) || 0;
|
|
|
|
|
const existingTotal = Number(
|
|
|
|
|
(finAccountInstallmentDetailList.value || []).reduce((sum: number, item: any) => {
|
|
|
|
|
// 如果是编辑模式,排除当前正在编辑的记录
|
|
|
|
|
if (detailForm.installmentDetailId && item.installmentDetailId === detailForm.installmentDetailId) {
|
|
|
|
|
return sum;
|
|
|
|
|
}
|
|
|
|
|
return sum + Number(item.detailAmount || 0);
|
|
|
|
|
}, 0)
|
|
|
|
|
);
|
|
|
|
|
return Number(selectedInstallment.value.paymentAmount || 0) - existingTotal - currentDetailAmount;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 导入对话框
|
|
|
|
|
const showImportDialog = ref(false);
|
|
|
|
|
const uploadRef = ref();
|
|
|
|
|
const importLoading = ref(false);
|
|
|
|
|
const importPreviewData = ref<any[]>([]);
|
|
|
|
|
const duplicateCount = ref(0);
|
|
|
|
|
const uploadHeaders = ref({ Authorization: 'Bearer ' + getToken() });
|
|
|
|
|
const importFile = ref<File | null>(null);
|
|
|
|
|
|
|
|
|
|
// 项目选择对话框
|
|
|
|
|
const showProjectDialog = ref(false);
|
|
|
|
|
const showContractDialog = ref(false);
|
|
|
|
|
const selectedProject = ref<any>(null);
|
|
|
|
|
|
|
|
|
|
const paymentStageList = ref<PaymentStageVO[]>([]);
|
|
|
|
|
/** 新增时查询应收款节点信息列表 */
|
|
|
|
|
const getPaymentStages = async (contractId) => {
|
|
|
|
|
paymentStageList.value = [];
|
|
|
|
|
if (contractId && contractId !== '') {
|
|
|
|
|
const res = await getErpContractPaymentMethodJoinList({ contractId: contractId });
|
|
|
|
|
console.log(res.data);
|
|
|
|
|
paymentStageList.value = res.data;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 付款阶段Mock数据
|
|
|
|
|
// const paymentStageList = ref([
|
|
|
|
|
// { id: 1, stageName: '预付款', stageOrder: 1 },
|
|
|
|
|
// { id: 2, stageName: '进度款', stageOrder: 2 },
|
|
|
|
|
// { id: 3, stageName: '验收款', stageOrder: 3 },
|
|
|
|
|
// { id: 4, stageName: '尾款', stageOrder: 4 },
|
|
|
|
|
// { id: 5, stageName: '质保金', stageOrder: 5 }
|
|
|
|
|
// ]);
|
|
|
|
|
|
|
|
|
|
// 分款明细表单
|
|
|
|
|
const detailDialog = reactive({
|
|
|
|
|
visible: false,
|
|
|
|
|
title: '新增分款明细'
|
|
|
|
|
});
|
|
|
|
|
const detailButtonLoading = ref(false);
|
|
|
|
|
const detailFormRef = ref<ElFormInstance>();
|
|
|
|
|
const initDetailForm: FinAccountInstallmentDetailForm = {
|
|
|
|
|
installmentDetailId: undefined,
|
|
|
|
|
accountInstallmentId: undefined,
|
|
|
|
|
projectId: undefined,
|
|
|
|
|
projectCode: undefined,
|
|
|
|
|
projectName: undefined,
|
|
|
|
|
contractId: undefined,
|
|
|
|
|
contractCode: undefined,
|
|
|
|
|
contractName: undefined,
|
|
|
|
|
paymentStageId: undefined,
|
|
|
|
|
detailAmount: undefined,
|
|
|
|
|
remark: undefined
|
|
|
|
|
};
|
|
|
|
|
const detailForm = reactive<FinAccountInstallmentDetailForm>({ ...initDetailForm });
|
|
|
|
|
|
|
|
|
|
const detailRules = {
|
|
|
|
|
paymentStageId: [{ required: true, message: '请选择分款节点', trigger: 'change' }],
|
|
|
|
|
detailAmount: [{ required: true, message: '请输入分款金额', trigger: 'blur' }]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 格式化金额
|
|
|
|
|
const formatMoney = (money: number | undefined | null) => {
|
|
|
|
|
if (money === undefined || money === null) return '0.00';
|
|
|
|
|
return money.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const INSTALLMENT_STATUS = {
|
|
|
|
|
NOT: '0', //未分款
|
|
|
|
|
PARTIALLY: '1', //部分分款
|
|
|
|
|
COMPLETE: '2' //分款完成
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取状态类型
|
|
|
|
|
const getStatusType = (status: string | undefined) => {
|
|
|
|
|
if (!status) return 'info';
|
|
|
|
|
const map: Record<string, string> = {
|
|
|
|
|
'0': 'warning', // 未分款 - 黄色
|
|
|
|
|
'1': 'primary', // 部分分款 - 蓝色
|
|
|
|
|
'2': 'success' // 已分款完成 - 绿色
|
|
|
|
|
};
|
|
|
|
|
return map[status] || 'info';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取状态文本
|
|
|
|
|
const getStatusText = (status: string | undefined) => {
|
|
|
|
|
if (!status) return '未知';
|
|
|
|
|
const map: Record<string, string> = {
|
|
|
|
|
'0': '未分款',
|
|
|
|
|
'1': '部分分款',
|
|
|
|
|
'2': '已分款完成'
|
|
|
|
|
};
|
|
|
|
|
return map[status] || '未知';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*** 用户导入参数 */
|
|
|
|
|
const upload = reactive<ImportOption>({
|
|
|
|
|
// 是否显示弹出层(用户导入)
|
|
|
|
|
open: false,
|
|
|
|
|
// 弹出层标题(用户导入)
|
|
|
|
|
title: '',
|
|
|
|
|
// 是否禁用上传
|
|
|
|
|
isUploading: false,
|
|
|
|
|
// 是否更新已经存在的用户数据
|
|
|
|
|
updateSupport: 0,
|
|
|
|
|
// 设置上传的请求头部
|
|
|
|
|
headers: globalHeaders(),
|
|
|
|
|
// 上传的地址
|
|
|
|
|
url: import.meta.env.VITE_APP_BASE_API + '/oa/erp/finAccountInstallment/importData'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 下载模板操作 */
|
|
|
|
|
const importTemplate = () => {
|
|
|
|
|
proxy?.download('system/user/importTemplate', {}, `user_template_${new Date().getTime()}.xlsx`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**文件上传中处理 */
|
|
|
|
|
const handleFileUploadProgress = () => {
|
|
|
|
|
upload.isUploading = true;
|
|
|
|
|
};
|
|
|
|
|
/** 文件上传成功处理 */
|
|
|
|
|
const handleFileSuccess = (response: any, file: UploadFile) => {
|
|
|
|
|
upload.open = false;
|
|
|
|
|
upload.isUploading = false;
|
|
|
|
|
uploadRef.value?.handleRemove(file);
|
|
|
|
|
ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
|
|
|
|
|
dangerouslyUseHTMLString: true
|
|
|
|
|
});
|
|
|
|
|
getList();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 行样式
|
|
|
|
|
const getRowClassName = ({ row }: { row: FinAccountInstallmentVO }) => {
|
|
|
|
|
return selectedInstallment.value?.accountInstallmentId === row.accountInstallmentId ? 'selected-row' : '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 解析时间
|
|
|
|
|
const parseTime = (time: string | undefined, pattern: string) => {
|
|
|
|
|
if (!time) return '';
|
|
|
|
|
const date = new Date(time);
|
|
|
|
|
const format = (str: string) =>
|
|
|
|
|
str
|
|
|
|
|
.replace('{y}', date.getFullYear().toString())
|
|
|
|
|
.replace('{m}', (date.getMonth() + 1).toString().padStart(2, '0'))
|
|
|
|
|
.replace('{d}', date.getDate().toString().padStart(2, '0'))
|
|
|
|
|
.replace('{h}', date.getHours().toString().padStart(2, '0'))
|
|
|
|
|
.replace('{i}', date.getMinutes().toString().padStart(2, '0'))
|
|
|
|
|
.replace('{s}', date.getSeconds().toString().padStart(2, '0'));
|
|
|
|
|
return format(pattern);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 查询回款列表
|
|
|
|
|
const getList = async () => {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const res = await listFinAccountInstallment(queryParams);
|
|
|
|
|
finAccountInstallmentList.value = res.rows;
|
|
|
|
|
total.value = res.total;
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 搜索
|
|
|
|
|
const handleQuery = () => {
|
|
|
|
|
queryParams.pageNum = 1;
|
|
|
|
|
getList();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 重置
|
|
|
|
|
const resetQuery = () => {
|
|
|
|
|
queryFormRef.value?.resetFields();
|
|
|
|
|
handleQuery();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 点击回款行
|
|
|
|
|
const handleRowClick = async (row: FinAccountInstallmentVO) => {
|
|
|
|
|
selectedInstallment.value = row;
|
|
|
|
|
await getDetailList();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 查询分款明细
|
|
|
|
|
const getDetailList = async () => {
|
|
|
|
|
if (!selectedInstallment.value) return;
|
|
|
|
|
detailLoading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const res = await getErpFinAccountInstallmentDetailList({
|
|
|
|
|
accountInstallmentId: selectedInstallment.value.accountInstallmentId
|
|
|
|
|
});
|
|
|
|
|
finAccountInstallmentDetailList.value = res.data;
|
|
|
|
|
} finally {
|
|
|
|
|
detailLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 新增分款明细 - 打开项目选择
|
|
|
|
|
const handleAddDetail = () => {
|
|
|
|
|
if (!selectedInstallment.value) {
|
|
|
|
|
proxy?.$modal.msgWarning('请先选择回款记录');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 打开项目选择对话框
|
|
|
|
|
showProjectDialog.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 项目选择回调
|
|
|
|
|
const handleProjectSelected = (data: any) => {
|
|
|
|
|
selectedProject.value = data.project;
|
|
|
|
|
|
|
|
|
|
console.log(data);
|
|
|
|
|
// 如果是无合同项目(contractFlag !== '1'),需要选择合同
|
|
|
|
|
if (data.project.contractFlag !== '1') {
|
|
|
|
|
showContractDialog.value = true;
|
|
|
|
|
} else {
|
|
|
|
|
// 有合同的情况,使用项目自带的合同信息或选中的合同
|
|
|
|
|
fillDetailForm(data.project, data.contract);
|
|
|
|
|
getPaymentStages(data.contract.contractId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 合同选择回调
|
|
|
|
|
const handleContractSelected = (contract: any) => {
|
|
|
|
|
if (selectedProject.value) {
|
|
|
|
|
fillDetailForm(selectedProject.value, contract);
|
|
|
|
|
getPaymentStages(contract.contractId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 填充分款明细表单
|
|
|
|
|
const fillDetailForm = (project: any, contract: any) => {
|
|
|
|
|
Object.assign(detailForm, {
|
|
|
|
|
installmentDetailId: undefined,
|
|
|
|
|
accountInstallmentId: selectedInstallment.value?.accountInstallmentId,
|
|
|
|
|
projectId: project.projectId || project.id,
|
|
|
|
|
projectCode: project.projectCode,
|
|
|
|
|
projectName: project.projectName,
|
|
|
|
|
contractId: contract?.contractId || contract?.id,
|
|
|
|
|
contractCode: contract?.contractCode || '',
|
|
|
|
|
contractName: contract?.contractName || '',
|
|
|
|
|
paymentStageId: undefined,
|
|
|
|
|
detailAmount: undefined,
|
|
|
|
|
remark: undefined
|
|
|
|
|
});
|
|
|
|
|
detailDialog.title = '新增分款明细';
|
|
|
|
|
detailDialog.visible = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 编辑分款明细
|
|
|
|
|
const handleEditDetail = (row: FinAccountInstallmentDetailVO) => {
|
|
|
|
|
Object.assign(detailForm, row);
|
|
|
|
|
detailDialog.title = '编辑分款明细';
|
|
|
|
|
detailDialog.visible = true;
|
|
|
|
|
getPaymentStages(detailForm.contractId);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 分款金额变化时触发校验提示更新
|
|
|
|
|
const handleDetailAmountChange = () => {
|
|
|
|
|
// 通过computed自动更新formRemainingAmount
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 删除分款明细
|
|
|
|
|
const handleDeleteDetail = async (row: FinAccountInstallmentDetailVO) => {
|
|
|
|
|
await proxy?.$modal.confirm('是否确认删除该分款明细?');
|
|
|
|
|
const deleteForm = {
|
|
|
|
|
installmentDetailId: row.installmentDetailId,
|
|
|
|
|
accountInstallmentId: selectedInstallment.value.accountInstallmentId
|
|
|
|
|
};
|
|
|
|
|
await deleteInstallmentDetailByBo(deleteForm);
|
|
|
|
|
proxy?.$modal.msgSuccess('删除成功');
|
|
|
|
|
await getDetailList();
|
|
|
|
|
// 更新左侧选中行的状态
|
|
|
|
|
if (selectedInstallment.value) {
|
|
|
|
|
const newStatus = remainingAmount.value === 0 ? INSTALLMENT_STATUS.COMPLETE : (allocatedAmount.value > 0 ? INSTALLMENT_STATUS.PARTIALLY : INSTALLMENT_STATUS.NOT);
|
|
|
|
|
selectedInstallment.value.installmentStatus = newStatus;
|
|
|
|
|
// 同时更新左侧列表中对应记录的状态
|
|
|
|
|
const index = finAccountInstallmentList.value.findIndex((item) => item.accountInstallmentId === selectedInstallment.value?.accountInstallmentId);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
finAccountInstallmentList.value[index].installmentStatus = newStatus;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 提交分款明细表单
|
|
|
|
|
const submitDetailForm = async () => {
|
|
|
|
|
const valid = await detailFormRef.value?.validate();
|
|
|
|
|
if (!valid) return;
|
|
|
|
|
|
|
|
|
|
detailButtonLoading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
detailForm.installmentStatus = formRemainingAmount.value === 0 ? INSTALLMENT_STATUS.COMPLETE : INSTALLMENT_STATUS.PARTIALLY;
|
|
|
|
|
if (detailForm.installmentDetailId) {
|
|
|
|
|
await updateFinAccountInstallmentDetail(detailForm);
|
|
|
|
|
proxy?.$modal.msgSuccess('修改成功');
|
|
|
|
|
} else {
|
|
|
|
|
await addFinAccountInstallmentDetail(detailForm);
|
|
|
|
|
proxy?.$modal.msgSuccess('新增成功');
|
|
|
|
|
}
|
|
|
|
|
detailDialog.visible = false;
|
|
|
|
|
await getDetailList();
|
|
|
|
|
// 更新左侧选中行的状态
|
|
|
|
|
if (selectedInstallment.value) {
|
|
|
|
|
const newStatus = remainingAmount.value === 0 ? INSTALLMENT_STATUS.COMPLETE : (allocatedAmount.value > 0 ? INSTALLMENT_STATUS.PARTIALLY : INSTALLMENT_STATUS.NOT);
|
|
|
|
|
selectedInstallment.value.installmentStatus = newStatus;
|
|
|
|
|
// 同时更新左侧列表中对应记录的状态
|
|
|
|
|
const index = finAccountInstallmentList.value.findIndex((item) => item.accountInstallmentId === selectedInstallment.value?.accountInstallmentId);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
finAccountInstallmentList.value[index].installmentStatus = newStatus;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
detailButtonLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 取消分款明细
|
|
|
|
|
const cancelDetail = () => {
|
|
|
|
|
detailDialog.visible = false;
|
|
|
|
|
Object.assign(detailForm, initDetailForm);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Excel导入相关
|
|
|
|
|
const handleFileChange = (file: any) => {
|
|
|
|
|
importFile.value = file.raw;
|
|
|
|
|
// 解析Excel预览
|
|
|
|
|
previewExcel(file.raw);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleExceed = () => {
|
|
|
|
|
proxy?.$modal.msgWarning('只能上传一个文件');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleUploadRequest = (options: any) => {
|
|
|
|
|
// 自定义上传
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 预览Excel数据(Mock实现)
|
|
|
|
|
const previewExcel = (file: File) => {
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.onload = (e) => {
|
|
|
|
|
// 这里应该是实际解析Excel的逻辑,暂时用Mock数据
|
|
|
|
|
importPreviewData.value = [
|
|
|
|
|
{ customerName: '测试客户A', paymentAmount: 100000, paymentDate: '2026-01-15', remark: '测试备注1', isRepeat: false },
|
|
|
|
|
{ customerName: '测试客户B', paymentAmount: 50000, paymentDate: '2026-01-20', remark: '测试备注2', isRepeat: false },
|
|
|
|
|
{ customerName: '测试客户A', paymentAmount: 100000, paymentDate: '2026-01-15', remark: '测试备注1', isRepeat: true }
|
|
|
|
|
];
|
|
|
|
|
duplicateCount.value = 1;
|
|
|
|
|
};
|
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 提交导入
|
|
|
|
|
const submitImport = async () => {
|
|
|
|
|
uploadRef.value?.submit();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 监听导入对话框关闭
|
|
|
|
|
watch(showImportDialog, (val) => {
|
|
|
|
|
if (!val) {
|
|
|
|
|
importPreviewData.value = [];
|
|
|
|
|
importFile.value = null;
|
|
|
|
|
uploadRef.value?.clearFiles();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 监听分款明细对话框关闭
|
|
|
|
|
watch(
|
|
|
|
|
() => detailDialog.visible,
|
|
|
|
|
(val) => {
|
|
|
|
|
if (!val) {
|
|
|
|
|
Object.assign(detailForm, initDetailForm);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
getList();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.selected-row {
|
|
|
|
|
background-color: #ecf5ff !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-table .selected-row td) {
|
|
|
|
|
background-color: #ecf5ff !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-table__row) {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
transition: transform 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rotate-180 {
|
|
|
|
|
transform: rotate(180deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.writing-mode-vertical {
|
|
|
|
|
writing-mode: vertical-rl;
|
|
|
|
|
text-orientation: mixed;
|
|
|
|
|
}
|
|
|
|
|
</style>
|