1.4.0前端:

feat(分款):完成财务分款所有功能
dev
xs 3 weeks ago
parent 47ed3f7b76
commit 12d10643cd

@ -74,3 +74,18 @@ export function getErpFinAccountInstallmentList (query) {
params: query
});
};
/**
*
* @param data
*/
export const importFinAccountInstallment = (data: any) => {
return request({
url: '/oa/erp/finAccountInstallment/import',
method: 'post',
data: data,
headers: {
'Content-Type': 'application/json'
}
});
};

@ -5,7 +5,7 @@ export interface FinAccountInstallmentVO {
accountInstallmentId: string | number;
/**
*
*
*/
installmentCode: string;
@ -39,21 +39,6 @@ export interface FinAccountInstallmentVO {
*/
remark: string;
/**
*
*/
dispatchUserId: string | number;
/**
*
*/
dispatchDeptId: string | number;
/**
*
*/
dispatchDate: string;
/**
* 0稿 1 2
*/
@ -73,7 +58,7 @@ export interface FinAccountInstallmentForm extends BaseEntity {
accountInstallmentId?: string | number;
/**
*
*
*/
installmentCode?: string;
@ -107,21 +92,6 @@ export interface FinAccountInstallmentForm extends BaseEntity {
*/
remark?: string;
/**
*
*/
dispatchUserId?: string | number;
/**
*
*/
dispatchDeptId?: string | number;
/**
*
*/
dispatchDate?: string;
/**
* 0稿 1 2
*/
@ -137,7 +107,7 @@ export interface FinAccountInstallmentForm extends BaseEntity {
export interface FinAccountInstallmentQuery extends PageQuery {
/**
*
*
*/
installmentCode?: string;
@ -166,21 +136,6 @@ export interface FinAccountInstallmentQuery extends PageQuery {
*/
paymentDate?: string;
/**
*
*/
dispatchUserId?: string | number;
/**
*
*/
dispatchDeptId?: string | number;
/**
*
*/
dispatchDate?: string;
/**
* 0稿 1 2
*/

@ -0,0 +1,89 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { FinAccountInstallmentDetailVO, FinAccountInstallmentDetailForm, FinAccountInstallmentDetailQuery } from '@/api/oa/erp/finAccountInstallmentDetail/types';
/**
*
* @param query
* @returns {*}
*/
export const listFinAccountInstallmentDetail = (query?: FinAccountInstallmentDetailQuery): AxiosPromise<FinAccountInstallmentDetailVO[]> => {
return request({
url: '/oa/erp/finAccountInstallmentDetail/list',
method: 'get',
params: query
});
};
/**
*
* @param installmentDetailId
*/
export const getFinAccountInstallmentDetail = (installmentDetailId: string | number): AxiosPromise<FinAccountInstallmentDetailVO> => {
return request({
url: '/oa/erp/finAccountInstallmentDetail/' + installmentDetailId,
method: 'get'
});
};
/**
*
* @param data
*/
export const addFinAccountInstallmentDetail = (data: FinAccountInstallmentDetailForm) => {
return request({
url: '/oa/erp/finAccountInstallmentDetail',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateFinAccountInstallmentDetail = (data: FinAccountInstallmentDetailForm) => {
return request({
url: '/oa/erp/finAccountInstallmentDetail',
method: 'put',
data: data
});
};
/**
*
* @param installmentDetailId
*/
export const delFinAccountInstallmentDetail = (installmentDetailId: string | number | Array<string | number>) => {
return request({
url: '/oa/erp/finAccountInstallmentDetail/' + installmentDetailId,
method: 'delete'
});
};
/**
*
* @param query
* @returns {*}
*/
export function getErpFinAccountInstallmentDetailList (query) {
return request({
url: '/oa/erp/finAccountInstallmentDetail/getErpFinAccountInstallmentDetailList',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const deleteInstallmentDetailByBo = (data: FinAccountInstallmentDetailForm) => {
return request({
url: '/oa/erp/finAccountInstallmentDetail/deleteInstallmentDetailByBo',
method: 'post',
data: data
});
};

@ -0,0 +1,171 @@
export interface FinAccountInstallmentDetailVO {
/**
* ID
*/
installmentDetailId: string | number;
/**
* ID
*/
accountInstallmentId: string | number;
/**
* ID
*/
projectId: string | number;
/**
*
*/
projectCode: string;
/**
*
*/
projectName: string;
/**
* IDerp_contract_info
*/
contractId: string | number;
/**
*
*/
contractCode: string;
/**
*
*/
contractName: string;
/**
* IDbase_payment_stage
*/
paymentStageId: string | number;
/**
*
*/
paymentStageName: string;
/**
*
*/
detailAmount: number;
/**
*
*/
remark: string;
}
export interface FinAccountInstallmentDetailForm extends BaseEntity {
/**
* ID
*/
installmentDetailId?: string | number;
/**
* ID
*/
accountInstallmentId?: string | number;
/**
* ID
*/
projectId?: string | number;
/**
*
*/
projectCode?: string;
/**
*
*/
projectName?: string;
/**
* IDerp_contract_info
*/
contractId?: string | number;
/**
*
*/
contractCode?: string;
/**
*
*/
contractName?: string;
/**
* IDbase_payment_stage
*/
paymentStageId?: string | number;
/**
*
*/
paymentStageName?: string;
/**
*
*/
detailAmount?: number;
/**
*
*/
remark?: string;
}
export interface FinAccountInstallmentDetailQuery extends PageQuery {
/**
* ID
*/
accountInstallmentId?: string | number;
/**
* ID
*/
projectId?: string | number;
/**
*
*/
projectCode?: string;
/**
*
*/
projectName?: string;
/**
* IDerp_contract_info
*/
contractId?: string | number;
/**
* IDbase_payment_stage
*/
paymentStageId?: string | number;
/**
*
*/
detailAmount?: number;
/**
*
*/
params?: any;
}

@ -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>仅允许导入xlsxlsx格式文件</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 = () => {
// computedformRemainingAmount
};
//
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) => {
//
};
// ExcelMock
const previewExcel = (file: File) => {
const reader = new FileReader();
reader.onload = (e) => {
// ExcelMock
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>
Loading…
Cancel
Save