<template>
|
<div class="app-container">
|
<div class="search_form">
|
<el-form :model="searchForm"
|
:inline="true">
|
<el-form-item label="客户名称:">
|
<el-input v-model="searchForm.customerName"
|
placeholder="请输入"
|
clearable
|
prefix-icon="Search"
|
@change="handleQuery" />
|
</el-form-item>
|
<el-form-item label="销售合同号:">
|
<el-input v-model="searchForm.salesContractNo"
|
placeholder="请输入"
|
clearable
|
prefix-icon="Search"
|
@change="handleQuery" />
|
</el-form-item>
|
<el-form-item label="项目名称:">
|
<el-input v-model="searchForm.projectName"
|
placeholder="请输入"
|
clearable
|
prefix-icon="Search"
|
@change="handleQuery" />
|
</el-form-item>
|
<el-form-item label="客户类型:">
|
<el-select v-model="searchForm.customerType"
|
placeholder="请选择"
|
style="width: 220px"
|
clearable
|
@change="handleQuery">
|
<el-option label="对公"
|
value="1" />
|
<el-option label="对私"
|
value="2" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="录入日期:">
|
<el-date-picker v-model="searchForm.entryDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="daterange"
|
placeholder="请选择"
|
clearable
|
@change="changeDaterange" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary"
|
@click="handleQuery"> 搜索 </el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
<div class="table_list">
|
<div class="actions">
|
<div></div>
|
<div>
|
<el-button type="primary"
|
@click="openForm('add')">
|
新增台账
|
</el-button>
|
<el-button type="primary"
|
plain
|
@click="handleImport">导入</el-button>
|
<el-button @click="handleOut">导出</el-button>
|
<el-button type="danger"
|
plain
|
@click="handleDelete">删除</el-button>
|
<el-button type="primary"
|
plain
|
@click="handlePrint">打印</el-button>
|
</div>
|
</div>
|
<el-table :data="tableData"
|
border
|
v-loading="tableLoading"
|
@selection-change="handleSelectionChange"
|
:expand-row-keys="expandedRowKeys"
|
:row-key="(row) => row.id"
|
:row-class-name="tableRowClassName"
|
show-summary
|
style="width: 100%"
|
:summary-method="summarizeMainTable"
|
@expand-change="expandChange"
|
height="calc(100vh - 18.5em)">
|
<el-table-column align="center"
|
type="selection"
|
width="55"
|
fixed="left" />
|
<el-table-column type="expand"
|
width="60"
|
fixed="left">
|
<template #default="props">
|
<el-table :data="props.row.children"
|
border
|
show-summary
|
:summary-method="summarizeChildrenTable">
|
<el-table-column align="center"
|
label="序号"
|
type="index" />
|
<el-table-column label="产品大类"
|
prop="productCategory" />
|
<el-table-column label="规格型号"
|
prop="specificationModel" />
|
<el-table-column label="单位"
|
prop="unit" />
|
<el-table-column label="产品状态"
|
width="100px"
|
align="center">
|
<template #default="scope">
|
<el-tag v-if="scope.row.approveStatus === 1 && (!scope.row.shippingDate || !scope.row.shippingCarNumber)"
|
type="success">充足</el-tag>
|
<el-tag v-else-if="scope.row.approveStatus === 0 && (scope.row.shippingDate || scope.row.shippingCarNumber)"
|
type="success">已出库</el-tag>
|
<el-tag v-else
|
type="danger">不足</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="发货状态"
|
width="140"
|
align="center">
|
<template #default="scope">
|
<el-tag :type="getShippingStatusType(scope.row)"
|
size="small">
|
{{ getShippingStatusText(scope.row) }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="快递公司"
|
prop="expressCompany"
|
show-overflow-tooltip />
|
<el-table-column label="快递单号"
|
prop="expressNumber"
|
show-overflow-tooltip />
|
<el-table-column label="发货车牌"
|
minWidth="100px"
|
align="center">
|
<template #default="scope">
|
<div>
|
<el-tag type="success"
|
v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag>
|
<el-tag v-else
|
type="info">-</el-tag>
|
</div>
|
</template>
|
</el-table-column>
|
<el-table-column label="发货日期"
|
minWidth="100px"
|
align="center">
|
<template #default="scope">
|
<div>
|
<div v-if="scope.row.shippingDate">{{ scope.row.shippingDate }}</div>
|
<el-tag v-else
|
type="info">-</el-tag>
|
</div>
|
</template>
|
</el-table-column>
|
<el-table-column label="数量"
|
prop="quantity" />
|
<!-- 对公客户显示的字段 -->
|
<template v-if="props.row.customerType =='1'">
|
<el-table-column label="税率(%)"
|
prop="taxRate" />
|
<el-table-column label="含税单价(元)"
|
prop="taxInclusiveUnitPrice"
|
:formatter="formattedNumber" />
|
<el-table-column label="含税总价(元)"
|
prop="taxInclusiveTotalPrice"
|
:formatter="formattedNumber" />
|
<el-table-column label="不含税总价(元)"
|
prop="taxExclusiveTotalPrice"
|
:formatter="formattedNumber" />
|
</template>
|
<!-- 对私客户显示的字段 -->
|
<template v-else-if="props.row.customerType == 2">
|
<el-table-column label="单价"
|
prop="unitPrice"
|
:formatter="formattedNumber" />
|
<el-table-column label="总价"
|
prop="totalPrice"
|
:formatter="formattedNumber" />
|
</template>
|
<!--操作-->
|
<!-- <el-table-column Width="60px"
|
label="操作"
|
align="center">
|
<template #default="scope">
|
<el-button link
|
type="primary"
|
:disabled="!canShip(scope.row)"
|
@click="openDeliveryForm(scope.row)">
|
发货
|
</el-button>
|
</template>
|
</el-table-column> -->
|
</el-table>
|
</template>
|
</el-table-column>
|
<el-table-column align="center"
|
label="序号"
|
type="index"
|
width="60" />
|
<el-table-column label="销售合同号"
|
prop="salesContractNo"
|
width="180"
|
show-overflow-tooltip />
|
<el-table-column label="客户名称"
|
prop="customerName"
|
width="300"
|
show-overflow-tooltip />
|
<el-table-column label="客户类型"
|
prop="customerType"
|
width="100">
|
<template #default="scope">
|
{{scope.row.customerType == 1 ? '对公' : '对私'}}
|
</template>
|
</el-table-column>
|
<el-table-column label="业务员"
|
prop="salesman"
|
width="100"
|
show-overflow-tooltip />
|
<el-table-column label="项目名称"
|
prop="projectName"
|
width="180"
|
show-overflow-tooltip />
|
<el-table-column label="付款方式"
|
prop="paymentMethod"
|
show-overflow-tooltip />
|
<el-table-column label="合同金额(元)"
|
prop="contractAmount"
|
width="220"
|
show-overflow-tooltip
|
:formatter="formattedNumber" />
|
<el-table-column label="录入人"
|
prop="entryPersonName"
|
width="100"
|
show-overflow-tooltip />
|
<el-table-column label="录入日期"
|
prop="entryDate"
|
width="120"
|
show-overflow-tooltip />
|
<el-table-column label="签订日期"
|
prop="executionDate"
|
width="120"
|
show-overflow-tooltip />
|
<el-table-column label="交付日期"
|
prop="deliveryDate"
|
width="120"
|
show-overflow-tooltip />
|
<el-table-column label="备注"
|
prop="remarks"
|
width="200"
|
show-overflow-tooltip />
|
<el-table-column fixed="right"
|
label="操作"
|
width="130"
|
align="center">
|
<template #default="scope">
|
<el-button link
|
type="primary"
|
@click="openForm('edit', scope.row)"
|
:disabled="!scope.row.isEdit">编辑</el-button>
|
<el-button link
|
type="primary"
|
@click="downLoadFile(scope.row)">附件</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<pagination v-show="total > 0"
|
:total="total"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page="page.current"
|
:limit="page.size"
|
@pagination="paginationChange" />
|
</div>
|
<FormDialog v-model="dialogFormVisible"
|
:title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'"
|
:width="'70%'"
|
:operation-type="operationType"
|
@close="closeDia"
|
@confirm="submitForm"
|
@cancel="closeDia">
|
<el-form :model="form"
|
label-width="140px"
|
label-position="top"
|
:rules="rules"
|
ref="formRef">
|
<!-- 报价单导入入口:放在表单顶部,选择后反显客户/业务员等 -->
|
<el-row v-if="operationType === 'add'"
|
style="margin-bottom: 10px;">
|
<el-col :span="24"
|
style="text-align: right;">
|
<el-button type="primary"
|
plain
|
:disabled="!form.customerId"
|
@click="openQuotationDialog">
|
从销售报价导入
|
</el-button>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="销售合同号:"
|
prop="salesContractNo">
|
<el-input v-model="form.salesContractNo"
|
placeholder="自动生成"
|
clearable
|
disabled />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="业务员:"
|
prop="salesman">
|
<el-select v-model="form.salesman"
|
placeholder="请选择"
|
clearable
|
:disabled="operationType === 'view'">
|
<el-option v-for="item in userList"
|
:key="item.nickName"
|
:label="item.nickName"
|
:value="item.nickName" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="客户名称:"
|
prop="customerId">
|
<el-select v-model="form.customerId"
|
placeholder="请选择"
|
clearable
|
:disabled="operationType === 'view'"
|
@change="handleCustomerChange">
|
<el-option v-for="item in customerOption"
|
:key="item.id"
|
:label="item.customerName"
|
:value="item.id">
|
{{ item.customerName + "——" }}
|
{{item.customerType=="1"?item.taxpayerIdentificationNumber || '无':"对私"}}
|
</el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="项目名称:"
|
prop="projectName">
|
<el-input v-model="form.projectName"
|
placeholder="请输入"
|
clearable
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="签订日期:"
|
prop="executionDate">
|
<el-date-picker style="width: 100%"
|
v-model="form.executionDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="付款方式">
|
<el-input v-model="form.paymentMethod"
|
placeholder="请输入"
|
clearable
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="录入人:"
|
prop="entryPerson">
|
<el-select v-model="form.entryPerson"
|
filterable
|
default-first-option
|
:reserve-keyword="false"
|
placeholder="请选择"
|
clearable
|
@change="changs">
|
<el-option v-for="item in userList"
|
:key="item.userId"
|
:label="item.nickName"
|
:value="item.userId" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="录入日期:"
|
prop="entryDate">
|
<el-date-picker style="width: 100%"
|
v-model="form.entryDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="交货日期:"
|
prop="entryDate">
|
<el-date-picker style="width: 100%"
|
v-model="form.deliveryDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-form-item label="产品信息:"
|
prop="entryDate">
|
<el-button v-if="operationType !== 'view' && currentCustomerType"
|
type="primary"
|
@click="openProductForm('add')">添加</el-button>
|
<el-button v-if="operationType !== 'view'"
|
plain
|
type="danger"
|
@click="deleteProduct"
|
:disabled="!productData.length">删除</el-button>
|
</el-form-item>
|
</el-row>
|
<el-table :data="productData"
|
border
|
@selection-change="productSelected"
|
show-summary
|
:summary-method="summarizeMainTable">
|
<el-table-column align="center"
|
type="selection"
|
width="55"
|
v-if="operationType !== 'view'"
|
:selectable="(row) => !isProductShipped(row)" />
|
<el-table-column align="center"
|
label="序号"
|
type="index"
|
width="60" />
|
<el-table-column label="产品大类"
|
prop="productCategory" />
|
<el-table-column label="规格型号"
|
prop="specificationModel" />
|
<el-table-column label="单位"
|
prop="unit" />
|
<el-table-column label="数量"
|
prop="quantity" />
|
<!-- 对公客户显示的字段 -->
|
<template v-if="currentCustomerType == 1">
|
<el-table-column label="税率(%)"
|
prop="taxRate" />
|
<el-table-column label="含税单价(元)"
|
prop="taxInclusiveUnitPrice"
|
:formatter="formattedNumber" />
|
<el-table-column label="含税总价(元)"
|
prop="taxInclusiveTotalPrice"
|
:formatter="formattedNumber" />
|
<el-table-column label="不含税总价(元)"
|
prop="taxExclusiveTotalPrice"
|
:formatter="formattedNumber" />
|
</template>
|
<!-- 对私客户显示的字段 -->
|
<template v-else-if="currentCustomerType == 2">
|
<el-table-column label="单价"
|
prop="unitPrice"
|
:formatter="formattedNumber" />
|
<el-table-column label="总价"
|
prop="totalPrice"
|
:formatter="formattedNumber" />
|
</template>
|
<el-table-column fixed="right"
|
label="操作"
|
min-width="60"
|
align="center"
|
v-if="operationType !== 'view'">
|
<template #default="scope">
|
<el-button link
|
type="primary"
|
size="small"
|
:disabled="isProductShipped(scope.row)"
|
@click="openProductForm('edit', scope.row,scope.$index)">编辑</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<el-row :gutter="30">
|
<el-col :span="24">
|
<el-form-item label="备注:"
|
prop="remarks">
|
<el-input v-model="form.remarks"
|
placeholder="请输入"
|
clearable
|
type="textarea"
|
:rows="2"
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="24">
|
<el-form-item label="附件材料:"
|
prop="salesLedgerFiles">
|
<el-upload v-model:file-list="fileList"
|
:action="upload.url"
|
multiple
|
ref="fileUpload"
|
auto-upload
|
:headers="upload.headers"
|
:before-upload="handleBeforeUpload"
|
:on-error="handleUploadError"
|
:on-success="handleUploadSuccess"
|
:on-remove="handleRemove">
|
<el-button type="primary"
|
v-if="operationType !== 'view'">上传</el-button>
|
<template #tip
|
v-if="operationType !== 'view'">
|
<div class="el-upload__tip">
|
文件格式支持
|
doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
|
</div>
|
</template>
|
</el-upload>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</FormDialog>
|
<!-- 从报价单导入(仅审批通过) -->
|
<el-dialog v-model="quotationDialogVisible"
|
title="选择审批通过的销售报价单"
|
width="80%"
|
:close-on-click-modal="false">
|
<div style="margin-bottom: 12px; display:flex; gap: 12px; align-items:center;">
|
<el-input v-model="quotationSearchForm.quotationNo"
|
placeholder="请输入报价单号"
|
clearable
|
style="max-width: 260px;"
|
@change="fetchQuotationList" />
|
<el-input v-model="quotationSearchForm.customer"
|
placeholder="请输入客户名称"
|
clearable
|
style="max-width: 260px;"
|
@change="fetchQuotationList" />
|
<el-button type="primary"
|
@click="fetchQuotationList">搜索</el-button>
|
<el-button @click="resetQuotationSearch">重置</el-button>
|
</div>
|
<el-table :data="quotationList"
|
border
|
stripe
|
v-loading="quotationLoading"
|
height="420px">
|
<el-table-column align="center"
|
label="序号"
|
type="index"
|
width="60" />
|
<el-table-column prop="quotationNo"
|
label="报价单号"
|
width="180"
|
show-overflow-tooltip />
|
<el-table-column prop="customer"
|
label="客户名称"
|
min-width="220"
|
show-overflow-tooltip />
|
<el-table-column prop="salesperson"
|
label="业务员"
|
width="120"
|
show-overflow-tooltip />
|
<el-table-column prop="quotationDate"
|
label="报价日期"
|
width="140" />
|
<el-table-column prop="status"
|
label="审批状态"
|
width="120"
|
align="center" />
|
<el-table-column prop="totalAmount"
|
label="报价金额(元)"
|
width="160"
|
align="right">
|
<template #default="scope">
|
{{ Number(scope.row.totalAmount ?? 0).toFixed(2) }}
|
</template>
|
</el-table-column>
|
<el-table-column fixed="right"
|
label="操作"
|
width="120"
|
align="center">
|
<template #default="scope">
|
<el-button type="primary"
|
link
|
@click="applyQuotation(scope.row)">选择</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<pagination v-show="quotationPage.total > 0"
|
:total="quotationPage.total"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page="quotationPage.current"
|
:limit="quotationPage.size"
|
@pagination="quotationPaginationChange" />
|
<template #footer>
|
<el-button @click="quotationDialogVisible = false">关闭</el-button>
|
</template>
|
</el-dialog>
|
<FormDialog v-model="productFormVisible"
|
:title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
|
:width="'40%'"
|
:operation-type="productOperationType"
|
@close="closeProductDia"
|
@confirm="submitProduct"
|
@cancel="closeProductDia">
|
<el-form :model="productForm"
|
label-width="140px"
|
label-position="top"
|
:rules="productRules"
|
ref="productFormRef">
|
<el-row :gutter="30">
|
<el-col :span="24">
|
<el-form-item label="产品大类:"
|
prop="productCategory">
|
<!-- <el-select v-model="productForm.productCategory" placeholder="请选择" clearable>
|
<el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
|
</el-select> -->
|
<el-tree-select v-model="productForm.productCategory"
|
placeholder="请选择"
|
clearable
|
check-strictly
|
@change="getModels"
|
:data="productOptions"
|
:render-after-expand="false"
|
style="width: 100%" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="24">
|
<el-form-item label="规格型号:"
|
prop="productModelId">
|
<el-select v-model="productForm.productModelId"
|
placeholder="请选择"
|
clearable
|
@change="getProductModel"
|
filterable>
|
<el-option v-for="item in modelOptions"
|
:key="item.id"
|
:label="item.model"
|
:value="item.id" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="单位:"
|
prop="unit">
|
<el-input v-model="productForm.unit"
|
placeholder="请输入"
|
clearable />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="数量:"
|
prop="quantity">
|
<el-input-number :step="0.1"
|
:min="0"
|
v-model="productForm.quantity"
|
placeholder="请输入"
|
clearable
|
:precision="2"
|
@change="calculateFromQuantity"
|
style="width: 100%" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
</el-row>
|
<!-- 对公客户显示的字段 -->
|
<template v-if="currentCustomerType == 1">
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="税率(%):"
|
prop="taxRate">
|
<el-select v-model="productForm.taxRate"
|
placeholder="请选择"
|
clearable
|
filterable
|
allow-create
|
default-first-option
|
style="width: 100%"
|
@change="calculateFromTaxRate">
|
<el-option label="1"
|
value="1" />
|
<el-option label="6"
|
value="6" />
|
<el-option label="13"
|
value="13" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="含税单价(元):"
|
prop="taxInclusiveUnitPrice">
|
<el-input-number :step="0.01"
|
:min="0"
|
v-model="productForm.taxInclusiveUnitPrice"
|
style="width: 100%"
|
:precision="2"
|
placeholder="请输入"
|
clearable
|
@change="calculateFromUnitPrice" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="含税总价(元):"
|
prop="taxInclusiveTotalPrice">
|
<el-input v-model="productForm.taxInclusiveTotalPrice"
|
placeholder="请输入"
|
clearable
|
@change="calculateFromTotalPrice" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="不含税总价(元):"
|
prop="taxExclusiveTotalPrice">
|
<el-input v-model="productForm.taxExclusiveTotalPrice"
|
placeholder="请输入"
|
clearable
|
@change="calculateFromExclusiveTotalPrice" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="发票类型:"
|
prop="invoiceType">
|
<el-select v-model="productForm.invoiceType"
|
placeholder="请选择"
|
clearable>
|
<el-option label="增普票"
|
value="增普票" />
|
<el-option label="增专票"
|
value="增专票" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</template>
|
<!-- 对私客户显示的字段 -->
|
<template v-else-if="currentCustomerType == 2">
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="单价:"
|
prop="unitPrice">
|
<el-input-number :step="0.01"
|
:min="0"
|
v-model="productForm.unitPrice"
|
style="width: 100%"
|
:precision="2"
|
placeholder="请输入"
|
clearable
|
@change="calculatePrivatePrice" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="总价:"
|
prop="totalPrice">
|
<el-input-number :step="0.01"
|
:min="0"
|
v-model="productForm.totalPrice"
|
style="width: 100%"
|
:precision="2"
|
placeholder="请输入"
|
clearable />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</template>
|
</el-form>
|
</FormDialog>
|
<!-- 导入弹窗 -->
|
<FormDialog v-model="importUpload.open"
|
:title="importUpload.title"
|
:width="'600px'"
|
@close="importUpload.open = false"
|
@confirm="submitImportFile"
|
@cancel="importUpload.open = false">
|
<el-upload ref="importUploadRef"
|
:limit="1"
|
accept=".xlsx,.xls"
|
:action="importUpload.url"
|
:headers="importUpload.headers"
|
:before-upload="importUpload.beforeUpload"
|
:on-success="importUpload.onSuccess"
|
:on-error="importUpload.onError"
|
:on-progress="importUpload.onProgress"
|
:on-change="importUpload.onChange"
|
:auto-upload="false"
|
drag>
|
<i class="el-icon-upload"></i>
|
<div class="el-upload__text">
|
将文件拖到此处,或<em>点击上传</em>
|
</div>
|
<template #tip>
|
<div class="el-upload__tip">
|
仅支持 xls/xlsx,大小不超过 10MB。
|
<el-button link
|
type="primary"
|
@click="downloadTemplate">下载导入模板</el-button>
|
</div>
|
</template>
|
</el-upload>
|
</FormDialog>
|
<!-- 附件列表弹窗 -->
|
<FileListDialog ref="fileListRef"
|
v-model="fileListDialogVisible"
|
title="附件列表" />
|
<!-- 打印预览弹窗 -->
|
<el-dialog v-model="printPreviewVisible"
|
title="打印预览"
|
width="90%"
|
:close-on-click-modal="false"
|
class="print-preview-dialog">
|
<div class="print-preview-container">
|
<div class="print-preview-header">
|
<el-button type="primary"
|
@click="executePrint">执行打印</el-button>
|
<el-button @click="printPreviewVisible = false">关闭预览</el-button>
|
</div>
|
<div class="print-preview-content">
|
<div v-if="printData.length === 0"
|
style="text-align: center; padding: 50px; color: #999;">
|
暂无打印数据
|
</div>
|
<div v-else
|
style="text-align: center; padding: 10px; color: #666; font-size: 14px; background: #e8f4fd; margin-bottom: 10px;">
|
共 {{ printData.length }} 条数据待打印
|
</div>
|
<div v-for="(item, index) in printData"
|
:key="index"
|
class="print-page">
|
<div class="delivery-note">
|
<div class="header">
|
<div class="document-title">零售发货单</div>
|
</div>
|
<div class="info-section">
|
<div class="info-row">
|
<div>
|
<span class="label">发货日期:</span>
|
<span class="value">{{ formatDate(item.createTime) }}</span>
|
</div>
|
<div>
|
<span class="label">发货车牌号:</span>
|
<span class="value">{{ item.shippingCarNumber }}</span>
|
</div>
|
</div>
|
<div class="info-row">
|
<div>
|
<span class="label">客户名称:</span>
|
<span class="value">{{ item.customerName }}</span>
|
</div>
|
<span class="label">单号:</span>
|
<span class="value">{{ item.salesContractNo }}</span>
|
</div>
|
</div>
|
<div class="table-section">
|
<table class="product-table">
|
<thead>
|
<tr>
|
<th>产品名称</th>
|
<th>规格型号</th>
|
<th>单位</th>
|
<th>单价</th>
|
<th>零售数量</th>
|
<th>零售金额</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr v-for="product in item.products"
|
:key="product.id">
|
<td>{{ product.productCategory || '' }}</td>
|
<td>{{ product.specificationModel || '' }}</td>
|
<td>{{ product.unit || '' }}</td>
|
<td>{{ product.taxInclusiveUnitPrice || '0' }}</td>
|
<td>{{ product.quantity || '0' }}</td>
|
<td>{{ product.taxInclusiveTotalPrice || '0' }}</td>
|
</tr>
|
<tr v-if="!item.products || item.products.length === 0">
|
<td colspan="6"
|
style="text-align: center; color: #999;">暂无产品数据</td>
|
</tr>
|
</tbody>
|
<tfoot>
|
<tr>
|
<td class="label">合计</td>
|
<td class="total-value"></td>
|
<td class="total-value"></td>
|
<td class="total-value"></td>
|
<td class="total-value">{{ getTotalQuantity(item.products) }}</td>
|
<td class="total-value">{{ getTotalAmount(item.products) }}</td>
|
</tr>
|
</tfoot>
|
</table>
|
</div>
|
<div class="footer-section">
|
<div class="footer-row">
|
<div class="footer-item">
|
<span class="label">收货电话:</span>
|
<span class="value"></span>
|
</div>
|
<div class="footer-item">
|
<span class="label">收货人:</span>
|
<span class="value"></span>
|
</div>
|
<div class="footer-item address-item">
|
<span class="label">收货地址:</span>
|
<span class="value address-value"></span>
|
</div>
|
</div>
|
<div class="footer-row">
|
<div class="footer-item">
|
<span class="label">操作员:</span>
|
<span class="value">{{ userStore.nickName || '撕开前' }}</span>
|
</div>
|
<div class="footer-item">
|
<span class="label">打印日期:</span>
|
<span class="value">{{ formatDateTime(new Date()) }}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-dialog>
|
<!-- 发货弹框 -->
|
<el-dialog v-model="deliveryFormVisible"
|
title="发货信息"
|
width="40%"
|
@close="closeDeliveryDia">
|
<el-form :model="deliveryForm"
|
label-width="120px"
|
label-position="top"
|
:rules="deliveryRules"
|
ref="deliveryFormRef">
|
<el-row :gutter="30">
|
<el-col :span="24">
|
<el-form-item label="发货类型:"
|
prop="type">
|
<el-select v-model="deliveryForm.type"
|
placeholder="请选择发货类型"
|
style="width: 100%">
|
<el-option label="货车"
|
value="货车" />
|
<el-option label="快递"
|
value="快递" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<!-- 审批人选择(仿协同审批里的审批人节点选择) -->
|
<el-row>
|
<el-col :span="24">
|
<el-form-item>
|
<template #label>
|
<span>审批人选择:</span>
|
<el-button type="primary"
|
@click="addApproverNode"
|
style="margin-left: 8px;">新增节点</el-button>
|
</template>
|
<div style="display: flex; align-items: flex-end; flex-wrap: wrap;">
|
<div v-for="(node, index) in approverNodes"
|
:key="node.id"
|
style="margin-right: 20px; text-align: center; margin-bottom: 10px;">
|
<div>
|
<span>审批人</span>
|
→
|
</div>
|
<el-select v-model="node.userId"
|
placeholder="选择人员"
|
filterable
|
style="width: 140px; margin-bottom: 8px;">
|
<el-option v-for="user in userList"
|
:key="user.userId"
|
:label="user.nickName"
|
:value="user.userId" />
|
</el-select>
|
<div>
|
<el-button type="danger"
|
@click="removeApproverNode(index)"
|
v-if="approverNodes.length > 1">删除</el-button>
|
</div>
|
</div>
|
</div>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary"
|
@click="submitDelivery">确认发货</el-button>
|
<el-button @click="closeDeliveryDia">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { getToken } from "@/utils/auth";
|
import pagination from "@/components/PIMTable/Pagination.vue";
|
import { onMounted, ref, getCurrentInstance } from "vue";
|
import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
import { UploadFilled, Download } from "@element-plus/icons-vue";
|
import useUserStore from "@/store/modules/user";
|
import { userListNoPage } from "@/api/system/user.js";
|
import FileListDialog from "@/components/Dialog/FileListDialog.vue";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
|
import {
|
ledgerListPage,
|
productList,
|
customerList,
|
addOrUpdateSalesLedger,
|
getSalesLedgerWithProducts,
|
delLedger,
|
addOrUpdateSalesLedgerProduct,
|
delProduct,
|
delLedgerFile,
|
getProductInventory,
|
} from "@/api/salesManagement/salesLedger.js";
|
import { modelList, productTreeList } from "@/api/basicData/product.js";
|
import useFormData from "@/hooks/useFormData.js";
|
import dayjs from "dayjs";
|
import { getCurrentDate } from "@/utils/index.js";
|
|
const userStore = useUserStore();
|
const { proxy } = getCurrentInstance();
|
const tableData = ref([]);
|
const productData = ref([]);
|
const selectedRows = ref([]);
|
const productSelectedRows = ref([]);
|
const userList = ref([]);
|
const customerOption = ref([]);
|
const productOptions = ref([]);
|
const modelOptions = ref([]);
|
const tableLoading = ref(false);
|
const page = reactive({
|
current: 1,
|
size: 100,
|
});
|
const total = ref(0);
|
const fileList = ref([]);
|
|
// 用户信息表单弹框数据
|
const operationType = ref("");
|
const dialogFormVisible = ref(false);
|
const currentCustomerType = ref(""); // 当前客户类型:对公/对私
|
const data = reactive({
|
searchForm: {
|
customerName: "", // 客户名称
|
salesContractNo: "", // 销售合同编号
|
entryDate: null, // 录入日期
|
entryDateStart: undefined,
|
entryDateEnd: undefined,
|
customerType: "",
|
},
|
form: {
|
salesContractNo: "",
|
salesman: "",
|
customerId: "",
|
entryPerson: "",
|
entryDate: "",
|
deliveryDate: "",
|
maintenanceTime: "",
|
productData: [],
|
executionDate: "",
|
},
|
rules: {
|
salesman: [{ required: true, message: "请选择", trigger: "change" }],
|
customerId: [{ required: true, message: "请选择", trigger: "change" }],
|
entryPerson: [{ required: true, message: "请选择", trigger: "change" }],
|
entryDate: [{ required: true, message: "请选择", trigger: "change" }],
|
deliveryDate: [{ required: true, message: "请选择", trigger: "change" }],
|
executionDate: [{ required: true, message: "请选择", trigger: "change" }],
|
},
|
});
|
const { form, rules } = toRefs(data);
|
const { form: searchForm } = useFormData(data.searchForm);
|
// 产品表单弹框数据
|
const productFormVisible = ref(false);
|
const productOperationType = ref("");
|
const currentId = ref("");
|
const validateTaxRate = (_rule, value, callback) => {
|
if (value === null || value === undefined || value === "") {
|
callback(new Error("请选择税率"));
|
return;
|
}
|
const taxRateStr = String(value).trim();
|
if (!/^\d+(\.\d+)?$/.test(taxRateStr)) {
|
callback(new Error("税率只能输入数字"));
|
return;
|
}
|
callback();
|
};
|
const productFormData = reactive({
|
productForm: {
|
productCategory: "",
|
specificationModel: "",
|
unit: "",
|
quantity: "",
|
// 对公字段
|
taxInclusiveUnitPrice: "",
|
taxRate: null,
|
taxInclusiveTotalPrice: "",
|
taxExclusiveTotalPrice: "",
|
invoiceType: "",
|
// 对私字段
|
unitPrice: "",
|
totalPrice: "",
|
|
},
|
productRules: {
|
productCategory: [{ required: true, message: "请选择", trigger: "change" }],
|
productModelId: [{ required: true, message: "请选择", trigger: "change" }],
|
specificationModel: [
|
{ required: true, message: "请选择", trigger: "change" },
|
],
|
unit: [{ required: true, message: "请输入", trigger: "blur" }],
|
quantity: [{ required: true, message: "请输入", trigger: "blur" }],
|
// 对公字段验证
|
taxInclusiveUnitPrice: [
|
{ required: true, message: "请输入", trigger: "blur" },
|
],
|
taxRate: [{ validator: validateTaxRate, trigger: "change" }],
|
taxInclusiveTotalPrice: [
|
{ required: true, message: "请输入", trigger: "blur" },
|
],
|
taxExclusiveTotalPrice: [
|
{ required: true, message: "请输入", trigger: "blur" },
|
],
|
invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
|
// 对私字段验证
|
unitPrice: [{ required: true, message: "请输入", trigger: "blur" }],
|
totalPrice: [{ required: true, message: "请输入", trigger: "blur" }],
|
},
|
});
|
const { productForm, productRules } = toRefs(productFormData);
|
// 防止循环计算的标志
|
const isCalculating = ref(false);
|
const upload = reactive({
|
// 上传的地址
|
url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
|
// 设置上传的请求头部
|
headers: { Authorization: "Bearer " + getToken() },
|
});
|
// 打印相关
|
const printPreviewVisible = ref(false);
|
const printData = ref([]);
|
|
// 报价单导入相关
|
const quotationDialogVisible = ref(false);
|
const quotationLoading = ref(false);
|
const quotationList = ref([]);
|
const quotationSearchForm = reactive({
|
quotationNo: "",
|
customer: "",
|
});
|
// 报价单弹框分页
|
const quotationPage = reactive({
|
current: 1,
|
size: 10,
|
total: 0,
|
});
|
const selectedQuotation = ref(null);
|
|
// 发货相关
|
const deliveryFormVisible = ref(false);
|
const currentDeliveryRow = ref(null);
|
const deliveryFormData = reactive({
|
deliveryForm: {
|
type: "货车", // 货车, 快递
|
},
|
deliveryRules: {
|
type: [{ required: true, message: "请选择发货类型", trigger: "change" }],
|
},
|
});
|
const { deliveryForm, deliveryRules } = toRefs(deliveryFormData);
|
|
// 发货审批人节点(仿协同审批 infoFormDia.vue)
|
const approverNodes = ref([{ id: 1, userId: null }]);
|
let nextApproverId = 2;
|
const addApproverNode = () => {
|
approverNodes.value.push({ id: nextApproverId++, userId: null });
|
};
|
const removeApproverNode = index => {
|
approverNodes.value.splice(index, 1);
|
};
|
|
// 导入相关
|
const importUploadRef = ref(null);
|
const importUpload = reactive({
|
title: "导入销售台账",
|
open: false,
|
url: import.meta.env.VITE_APP_BASE_API + "/sales/ledger/import",
|
headers: { Authorization: "Bearer " + getToken() },
|
isUploading: false,
|
beforeUpload: file => {
|
const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
|
const isLt10M = file.size / 1024 / 1024 < 10;
|
if (!isExcel) {
|
proxy.$modal.msgError("上传文件只能是 xlsx/xls 格式!");
|
return false;
|
}
|
if (!isLt10M) {
|
proxy.$modal.msgError("上传文件大小不能超过 10MB!");
|
return false;
|
}
|
return true;
|
},
|
onChange: (file, fileList) => {
|
console.log("文件状态改变", file, fileList);
|
},
|
onProgress: (event, file, fileList) => {
|
console.log("上传中...", event.percent);
|
},
|
onSuccess: (response, file, fileList) => {
|
console.log("上传成功", response, file, fileList);
|
importUpload.isUploading = false;
|
if (response.code === 200) {
|
proxy.$modal.msgSuccess("导入成功");
|
importUpload.open = false;
|
if (importUploadRef.value) {
|
importUploadRef.value.clearFiles();
|
}
|
getList();
|
} else {
|
proxy.$modal.msgError(response.msg || "导入失败");
|
}
|
},
|
onError: (error, file, fileList) => {
|
console.error("上传失败", error, file, fileList);
|
importUpload.isUploading = false;
|
proxy.$modal.msgError("导入失败,请重试");
|
},
|
});
|
|
const changeDaterange = value => {
|
if (value) {
|
searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
|
searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
|
} else {
|
searchForm.entryDateStart = undefined;
|
searchForm.entryDateEnd = undefined;
|
}
|
handleQuery();
|
};
|
|
// 查询列表
|
/** 搜索按钮操作 */
|
const handleQuery = () => {
|
// 只有在点击搜索按钮时才重置页码到第一页
|
// 避免表单字段change事件干扰分页
|
if (arguments.length === 0) {
|
page.current = 1;
|
}
|
expandedRowKeys.value = [];
|
getList();
|
};
|
const paginationChange = obj => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getList();
|
};
|
const getList = () => {
|
tableLoading.value = true;
|
const { entryDate, ...rest } = searchForm;
|
// 将范围日期字段传递给后端
|
const params = { ...rest, ...page };
|
// 移除录入日期的默认值设置,只保留范围日期字段
|
delete params.entryDate;
|
return ledgerListPage(params)
|
.then(res => {
|
tableLoading.value = false;
|
tableData.value = res.records;
|
tableData.value.map(item => {
|
item.children = [];
|
});
|
total.value = res.total;
|
return res;
|
})
|
.catch(() => {
|
tableLoading.value = false;
|
});
|
};
|
// 获取产品大类tree数据
|
const getProductOptions = () => {
|
// 返回 Promise,便于在编辑产品时等待加载完成
|
return productTreeList().then(res => {
|
productOptions.value = convertIdToValue(res);
|
return productOptions.value;
|
});
|
};
|
const formattedNumber = (row, column, cellValue) => {
|
return parseFloat(cellValue).toFixed(2);
|
};
|
// 获取tree子数据
|
const getModels = value => {
|
productForm.value.productCategory = findNodeById(productOptions.value, value);
|
modelList({ id: value }).then(res => {
|
modelOptions.value = res;
|
});
|
};
|
const getProductModel = value => {
|
const index = modelOptions.value.findIndex(item => item.id === value);
|
if (index !== -1) {
|
productForm.value.specificationModel = modelOptions.value[index].model;
|
productForm.value.unit = modelOptions.value[index].unit;
|
} else {
|
productForm.value.specificationModel = null;
|
productForm.value.unit = null;
|
}
|
};
|
const findNodeById = (nodes, productId) => {
|
for (let i = 0; i < nodes.length; i++) {
|
if (nodes[i].value === productId) {
|
return nodes[i].label; // 找到节点,返回该节点
|
}
|
if (nodes[i].children && nodes[i].children.length > 0) {
|
const foundNode = findNodeById(nodes[i].children, productId);
|
if (foundNode) {
|
return foundNode; // 在子节点中找到,返回该节点
|
}
|
}
|
}
|
return null; // 没有找到节点,返回null
|
};
|
function convertIdToValue(data) {
|
return data.map(item => {
|
const { id, children, ...rest } = item;
|
const newItem = {
|
...rest,
|
value: id, // 将 id 改为 value
|
};
|
if (children && children.length > 0) {
|
newItem.children = convertIdToValue(children);
|
}
|
|
return newItem;
|
});
|
}
|
// 根据名称反查产品大类 id,便于仅存名称时的反显
|
function findNodeIdByLabel(nodes, label) {
|
if (!label) return null;
|
for (let i = 0; i < nodes.length; i++) {
|
const node = nodes[i];
|
if (node.label === label) return node.value;
|
if (node.children && node.children.length > 0) {
|
const found = findNodeIdByLabel(node.children, label);
|
if (found !== null && found !== undefined) return found;
|
}
|
}
|
return null;
|
}
|
// 表格选择数据
|
const handleSelectionChange = selection => {
|
// 过滤掉子数据
|
selectedRows.value = selection.filter(item => item.children !== undefined);
|
console.log("selection", selectedRows.value);
|
};
|
const productSelected = selectedRows => {
|
productSelectedRows.value = selectedRows;
|
};
|
const expandedRowKeys = ref([]);
|
// 展开行
|
const expandChange = (row, expandedRows) => {
|
if (expandedRows.length > 0) {
|
expandedRowKeys.value = [];
|
try {
|
productList({ salesLedgerId: row.id, type: 1 }).then(res => {
|
const index = tableData.value.findIndex(item => item.id === row.id);
|
if (index > -1) {
|
// 为子表格数据添加客户类型信息
|
const childrenData = res.data.map(item => ({
|
...item,
|
customerType: row.customerType,
|
}));
|
tableData.value[index].children = childrenData;
|
}
|
expandedRowKeys.value.push(row.id);
|
});
|
} catch (error) {
|
console.log(error);
|
}
|
} else {
|
expandedRowKeys.value = [];
|
}
|
};
|
|
// 添加表行类名方法
|
const tableRowClassName = ({ row }) => {
|
if (!row.deliveryDate) return "";
|
if (row.isFh) return "";
|
|
const diff = row.deliveryDaysDiff;
|
if (diff === 15) {
|
return "yellow";
|
} else if (diff === 10) {
|
return "pink";
|
} else if (diff === 2) {
|
return "purple";
|
} else if (diff < 2) {
|
return "red";
|
}
|
};
|
// 主表合计方法
|
const summarizeMainTable = param => {
|
return proxy.summarizeTable(param, [
|
"contractAmount",
|
"taxInclusiveTotalPrice",
|
"taxExclusiveTotalPrice",
|
]);
|
};
|
// 子表合计方法
|
const summarizeChildrenTable = param => {
|
return proxy.summarizeTable(param, [
|
"taxInclusiveUnitPrice",
|
"taxInclusiveTotalPrice",
|
"taxExclusiveTotalPrice",
|
]);
|
};
|
// 客户选择变化处理
|
const handleCustomerChange = customerId => {
|
if (customerId) {
|
const customer = customerOption.value.find(item => item.id === customerId);
|
if (customer) {
|
// 如果客户类型发生变化,清空表格数据
|
if (
|
currentCustomerType.value &&
|
currentCustomerType.value !== customer.customerType
|
) {
|
productData.value = [];
|
}
|
currentCustomerType.value = customer.customerType;
|
}
|
} else {
|
currentCustomerType.value = "";
|
productData.value = [];
|
}
|
};
|
|
// 打开弹框
|
const openForm = async (type, row) => {
|
operationType.value = type;
|
form.value = {};
|
productData.value = [];
|
selectedQuotation.value = null;
|
currentCustomerType.value = "";
|
let userLists = await userListNoPage();
|
userList.value = userLists.data;
|
customerList().then(res => {
|
customerOption.value = res;
|
// 如果是编辑模式且有客户ID,设置客户类型
|
if (type === "edit" && form.value.customerId) {
|
handleCustomerChange(form.value.customerId);
|
}
|
});
|
form.value.entryPerson = userStore.id;
|
if (type === "add") {
|
// 新增时设置录入日期为当天
|
form.value.entryDate = getCurrentDate();
|
// 签订日期默认为当天
|
form.value.executionDate = getCurrentDate();
|
} else {
|
currentId.value = row.id;
|
getSalesLedgerWithProducts({ id: row.id, type: 1 }).then(res => {
|
form.value = { ...res };
|
form.value.entryPerson = Number(res.entryPerson);
|
productData.value = JSON.parse(
|
JSON.stringify(form.value.productData || [])
|
);
|
fileList.value = form.value.salesLedgerFiles
|
? form.value.salesLedgerFiles
|
: [];
|
// 设置客户类型
|
handleCustomerChange(form.value.customerId);
|
});
|
}
|
// let userAll = await userStore.getInfo()
|
// userList.value.forEach(element => {
|
// if(userAll.user.nickName === element.nickName && userAll.user.userName === element.userName) {
|
// form.value.entryPerson = userAll.user.userId // 设置默认业务员为当前用户
|
// }
|
// });
|
form.value.entryDate = getCurrentDate(); // 设置默认录入日期为当前日期
|
dialogFormVisible.value = true;
|
};
|
|
// 打开报价单选择弹窗(仅审批通过)
|
const openQuotationDialog = async () => {
|
if (operationType.value === "view") return;
|
if (!form.value.customerId) {
|
proxy.$modal.msgWarning("请先选择客户名称");
|
return;
|
}
|
quotationDialogVisible.value = true;
|
// 打开弹窗时重置分页到第一页
|
quotationPage.current = 1;
|
// 先确保客户列表已加载,便于后续回填 customerId
|
if (!customerOption.value || customerOption.value.length === 0) {
|
try {
|
const res = await customerList();
|
customerOption.value = res;
|
} catch (e) {
|
// ignore,允许用户后续手动选择客户
|
}
|
}
|
await fetchQuotationList();
|
};
|
|
const fetchQuotationList = async () => {
|
quotationLoading.value = true;
|
try {
|
const params = {
|
// 后端分页字段:current / size
|
current: quotationPage.current,
|
size: quotationPage.size,
|
...quotationSearchForm,
|
status: "通过",
|
};
|
const res = await getQuotationList(params);
|
quotationList.value = res?.data?.records || [];
|
quotationPage.total = res?.data?.total || 0;
|
} finally {
|
quotationLoading.value = false;
|
}
|
};
|
|
const resetQuotationSearch = async () => {
|
quotationSearchForm.quotationNo = "";
|
quotationSearchForm.customer = "";
|
quotationPage.current = 1;
|
await fetchQuotationList();
|
};
|
|
// 报价单弹框分页切换
|
const quotationPaginationChange = obj => {
|
quotationPage.current = obj.page;
|
quotationPage.size = obj.limit;
|
fetchQuotationList();
|
};
|
|
// 选中报价单后回填到台账表单
|
const applyQuotation = row => {
|
if (!row) return;
|
selectedQuotation.value = row;
|
|
// 业务员
|
form.value.salesman = (row.salesperson || "").trim();
|
|
// 客户名称 -> customerId
|
// 如果表单里还没有选客户,则尝试通过报价单客户名称匹配;
|
// 如果已经选了客户,则保持用户当前选择,不被报价单覆盖。
|
if (!form.value.customerId) {
|
const qCustomerName = String(row.customer || "").trim();
|
const customer = (customerOption.value || []).find(c => {
|
const name = String(c.customerName || "").trim();
|
return (
|
name === qCustomerName ||
|
name.includes(qCustomerName) ||
|
qCustomerName.includes(name)
|
);
|
});
|
if (customer?.id) {
|
form.value.customerId = customer.id;
|
}
|
}
|
|
// 产品信息映射:报价 products -> 台账 productData
|
const products = Array.isArray(row.products) ? row.products : [];
|
productData.value = products.map(p => {
|
const quantity = Number(p.quantity ?? 0) || 0;
|
const unitPrice = Number(p.unitPrice ?? 0) || 0;
|
const taxRate = "13"; // 默认 13%,便于直接提交(如需可在产品中自行修改)
|
const taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
|
const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(
|
taxInclusiveTotalPrice,
|
taxRate
|
);
|
return {
|
// 台账字段
|
productCategory: p.product || p.productName || "",
|
specificationModel: p.specification || "",
|
unit: p.unit || "",
|
quantity: quantity,
|
taxRate: taxRate,
|
taxInclusiveUnitPrice: unitPrice.toFixed(2),
|
taxInclusiveTotalPrice: taxInclusiveTotalPrice,
|
taxExclusiveTotalPrice: taxExclusiveTotalPrice,
|
invoiceType: "增普票",
|
};
|
});
|
|
quotationDialogVisible.value = false;
|
};
|
function changs(val) {
|
console.log(val);
|
}
|
// 上传前校检
|
function handleBeforeUpload(file) {
|
// 校检文件大小
|
// if (file.size > 1024 * 1024 * 10) {
|
// proxy.$modal.msgError("上传文件大小不能超过10MB!");
|
// return false;
|
// }
|
proxy.$modal.loading("正在上传文件,请稍候...");
|
return true;
|
}
|
// 上传失败
|
function handleUploadError(err) {
|
proxy.$modal.msgError("上传文件失败");
|
proxy.$modal.closeLoading();
|
}
|
// 上传成功回调
|
function handleUploadSuccess(res, file, uploadFiles) {
|
proxy.$modal.closeLoading();
|
if (res.code === 200) {
|
file.tempId = res.data.tempId;
|
proxy.$modal.msgSuccess("上传成功");
|
} else {
|
proxy.$modal.msgError(res.msg);
|
proxy.$refs.fileUpload.handleRemove(file);
|
}
|
}
|
// 移除文件
|
function handleRemove(file) {
|
if (operationType.value === "edit") {
|
let ids = [];
|
ids.push(file.id);
|
delLedgerFile(ids).then(res => {
|
proxy.$modal.msgSuccess("删除成功");
|
});
|
}
|
}
|
// 提交表单
|
const submitForm = () => {
|
proxy.$refs["formRef"].validate(valid => {
|
if (valid) {
|
console.log("productData.value--", productData.value);
|
if (productData.value !== null && productData.value.length > 0) {
|
form.value.productData = proxy.HaveJson(productData.value);
|
} else {
|
proxy.$modal.msgWarning("请添加产品信息");
|
return;
|
}
|
let tempFileIds = [];
|
if (fileList.value !== null && fileList.value.length > 0) {
|
tempFileIds = fileList.value.map(item => item.tempId);
|
}
|
form.value.tempFileIds = tempFileIds;
|
form.value.type = 1;
|
form.value.salesType = currentCustomerType.value;
|
addOrUpdateSalesLedger(form.value).then(res => {
|
proxy.$modal.msgSuccess("提交成功");
|
closeDia();
|
getList();
|
});
|
}
|
});
|
};
|
// 关闭弹框
|
const closeDia = () => {
|
proxy.resetForm("formRef");
|
dialogFormVisible.value = false;
|
};
|
|
const productIndex = ref(0);
|
// 打开产品弹框
|
const openProductForm = async (type, row, index) => {
|
// 编辑时检查产品是否已发货或审核通过
|
if (type === "edit" && isProductShipped(row)) {
|
proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑");
|
return;
|
}
|
|
productOperationType.value = type;
|
productForm.value = {};
|
proxy.resetForm("productFormRef");
|
if (type === "edit") {
|
productForm.value = { ...row };
|
if (productForm.value.taxRate !== undefined && productForm.value.taxRate !== null && productForm.value.taxRate !== "") {
|
productForm.value.taxRate = String(productForm.value.taxRate);
|
}
|
productIndex.value = index;
|
// 编辑时根据产品大类名称反查 tree 节点 id,并加载规格型号列表
|
try {
|
const options =
|
productOptions.value && productOptions.value.length > 0
|
? productOptions.value
|
: await getProductOptions();
|
const categoryId = findNodeIdByLabel(
|
options,
|
productForm.value.productCategory
|
);
|
if (categoryId) {
|
const models = await modelList({ id: categoryId });
|
modelOptions.value = models || [];
|
// 根据当前规格型号名称反查并设置 productModelId,便于下拉框显示已选值
|
const currentModel = (modelOptions.value || []).find(
|
m => m.model === productForm.value.specificationModel
|
);
|
if (currentModel) {
|
productForm.value.productModelId = currentModel.id;
|
}
|
}
|
} catch (e) {
|
// 加载失败时保持可编辑,不中断弹窗
|
console.error("加载产品规格型号失败", e);
|
}
|
} else {
|
getProductOptions();
|
}
|
productFormVisible.value = true;
|
};
|
// 提交产品表单
|
const submitProduct = () => {
|
// 动态验证规则
|
const dynamicRules = { ...productRules.value };
|
|
// 根据客户类型调整验证规则
|
if (currentCustomerType.value == 1) {
|
// 对私字段不需要验证
|
delete dynamicRules.unitPrice;
|
delete dynamicRules.totalPrice;
|
} else if (currentCustomerType.value == 2) {
|
// 对公字段不需要验证
|
delete dynamicRules.taxInclusiveUnitPrice;
|
delete dynamicRules.taxRate;
|
delete dynamicRules.taxInclusiveTotalPrice;
|
delete dynamicRules.taxExclusiveTotalPrice;
|
delete dynamicRules.invoiceType;
|
}
|
|
proxy.$refs["productFormRef"].validate((valid, fields) => {
|
if (valid) {
|
if (operationType.value === "edit") {
|
// 编辑模式下直接修改本地表格数据
|
if (productOperationType.value === "add") {
|
productData.value.push({ ...productForm.value });
|
} else {
|
productData.value.splice(productIndex.value, 1, {
|
...productForm.value,
|
});
|
}
|
closeProductDia();
|
} else {
|
if (productOperationType.value === "add") {
|
productData.value.push({ ...productForm.value });
|
} else {
|
productData.value.splice(productIndex.value, 1, {
|
...productForm.value,
|
});
|
}
|
closeProductDia();
|
}
|
}
|
});
|
};
|
const submitProductEdit = () => {
|
// 根据客户类型调整提交的数据
|
const productDataToSubmit = { ...productForm.value };
|
|
if (currentCustomerType.value == 1) {
|
// 对私字段不需要提交
|
delete productDataToSubmit.unitPrice;
|
delete productDataToSubmit.totalPrice;
|
} else if (currentCustomerType.value == 2) {
|
// 对公字段不需要提交
|
delete productDataToSubmit.taxInclusiveUnitPrice;
|
delete productDataToSubmit.taxRate;
|
delete productDataToSubmit.taxInclusiveTotalPrice;
|
delete productDataToSubmit.taxExclusiveTotalPrice;
|
delete productDataToSubmit.invoiceType;
|
}
|
|
productDataToSubmit.salesLedgerId = currentId.value;
|
productDataToSubmit.type = 1;
|
addOrUpdateSalesLedgerProduct(productDataToSubmit).then(res => {
|
proxy.$modal.msgSuccess("提交成功");
|
closeProductDia();
|
getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then(res => {
|
productData.value = JSON.parse(JSON.stringify(res.productData || []));
|
});
|
});
|
};
|
// 删除产品
|
const deleteProduct = () => {
|
if (productSelectedRows.value.length === 0) {
|
proxy.$modal.msgWarning("请选择数据");
|
return;
|
}
|
|
// 检查是否有已发货或审核通过的产品
|
const shippedProducts = productSelectedRows.value.filter(row =>
|
isProductShipped(row)
|
);
|
if (shippedProducts.length > 0) {
|
proxy.$modal.msgWarning("已发货或审核通过的产品不能删除");
|
return;
|
}
|
|
// 无论是新增还是编辑模式,都直接修改本地表格数据
|
productSelectedRows.value.forEach(selectedRow => {
|
const index = productData.value.findIndex(
|
product => product.id === selectedRow.id
|
);
|
if (index !== -1) {
|
productData.value.splice(index, 1);
|
}
|
});
|
};
|
// 关闭产品弹框
|
const closeProductDia = () => {
|
proxy.resetForm("productFormRef");
|
productFormVisible.value = false;
|
};
|
// 导入
|
const handleImport = () => {
|
importUpload.title = "导入销售台账";
|
importUpload.open = true;
|
if (importUploadRef.value) {
|
importUploadRef.value.clearFiles();
|
}
|
};
|
|
// 下载导入模板
|
const downloadTemplate = () => {
|
proxy.download("/sales/ledger/exportTemplate", {}, "销售台账导入模板.xlsx");
|
};
|
|
// 提交导入文件
|
const submitImportFile = () => {
|
importUpload.isUploading = true;
|
proxy.$refs["importUploadRef"].submit();
|
};
|
|
// 导出
|
const handleOut = () => {
|
ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
proxy.download("/sales/ledger/export", {}, "销售台账.xlsx");
|
})
|
.catch(() => {
|
proxy.$modal.msg("已取消");
|
});
|
};
|
/** 判断单个产品是否已发货(根据shippingStatus判断,已发货或审核通过不可编辑和删除) */
|
const isProductShipped = product => {
|
if (!product) return false;
|
const status = String(product.shippingStatus || "").trim();
|
// 如果发货状态是"已发货"或"审核通过",则不可编辑和删除
|
return status === "已发货" || status === "审核通过";
|
};
|
|
/** 判断销售订单下是否存在已发货/发货完成的产品(不可删除) */
|
const hasShippedProducts = products => {
|
if (!products || !products.length) return false;
|
return products.some(p => {
|
const status = String(p.shippingStatus || "").trim();
|
// 有发货日期或车牌号视为已发货
|
if (p.shippingDate || p.shippingCarNumber) return true;
|
// 已进行发货、发货完成、已发货 均不可删除
|
return (
|
status === "已进行发货" || status === "发货完成" || status === "已发货"
|
);
|
});
|
};
|
|
// 删除
|
const handleDelete = async () => {
|
if (selectedRows.value.length === 0) {
|
proxy.$modal.msgWarning("请选择数据");
|
return;
|
}
|
const ids = selectedRows.value.map(item => item.id);
|
|
// 检查是否有已进行发货或发货完成的销售订单,若有则不允许删除
|
const cannotDeleteNames = [];
|
for (const row of selectedRows.value) {
|
let products =
|
row.children && row.children.length > 0 ? row.children : null;
|
if (!products) {
|
try {
|
const res = await productList({ salesLedgerId: row.id, type: 1 });
|
products = res.data || [];
|
} catch {
|
products = [];
|
}
|
}
|
if (hasShippedProducts(products)) {
|
cannotDeleteNames.push(row.salesContractNo || `ID:${row.id}`);
|
}
|
}
|
if (cannotDeleteNames.length > 0) {
|
proxy.$modal.msgWarning(
|
"已进行发货或发货完成的销售订单不能删除:" + cannotDeleteNames.join("、")
|
);
|
return;
|
}
|
|
ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
delLedger(ids).then(res => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
});
|
})
|
.catch(() => {
|
proxy.$modal.msg("已取消");
|
});
|
};
|
|
// 打印功能
|
const handlePrint = async () => {
|
if (selectedRows.value.length === 0) {
|
proxy.$modal.msgWarning("请选择要打印的数据");
|
return;
|
}
|
|
// 显示加载状态
|
proxy.$modal.loading("正在获取产品数据,请稍候...");
|
|
try {
|
// 为每个选中的销售台账记录查询对应的产品数据
|
const printDataWithProducts = [];
|
|
for (const row of selectedRows.value) {
|
try {
|
// 调用productList接口查询产品数据
|
const productRes = await productList({
|
salesLedgerId: row.id,
|
type: 1,
|
});
|
|
// 将产品数据整合到销售台账记录中
|
const rowWithProducts = {
|
...row,
|
products: productRes.data || [],
|
};
|
|
printDataWithProducts.push(rowWithProducts);
|
} catch (error) {
|
console.error(`获取销售台账 ${row.id} 的产品数据失败:`, error);
|
// 即使某个记录的产品数据获取失败,也要包含该记录
|
printDataWithProducts.push({
|
...row,
|
products: [],
|
});
|
}
|
}
|
|
printData.value = printDataWithProducts;
|
console.log("打印数据(包含产品):", printData.value);
|
printPreviewVisible.value = true;
|
} catch (error) {
|
console.error("获取产品数据失败:", error);
|
proxy.$modal.msgError("获取产品数据失败,请重试");
|
} finally {
|
proxy.$modal.closeLoading();
|
}
|
};
|
// 执行打印
|
const executePrint = () => {
|
console.log("开始执行打印,数据条数:", printData.value.length);
|
console.log("打印数据:", printData.value);
|
|
// 创建一个新的打印窗口
|
const printWindow = window.open("", "_blank", "width=800,height=600");
|
|
// 构建打印内容
|
let printContent = `
|
<!DOCTYPE html>
|
<html>
|
<head>
|
<meta charset="UTF-8">
|
<title>打印预览</title>
|
<style>
|
body {
|
margin: 0;
|
padding: 0;
|
font-family: "SimSun", serif;
|
background: white;
|
}
|
.print-page {
|
width: 200mm;
|
height: 75mm;
|
padding: 10mm;
|
padding-left: 20mm;
|
background: white;
|
box-sizing: border-box;
|
page-break-after: always;
|
page-break-inside: avoid;
|
}
|
.print-page:last-child {
|
page-break-after: avoid;
|
}
|
.delivery-note {
|
width: 100%;
|
height: 100%;
|
font-size: 12px;
|
line-height: 1.2;
|
display: flex;
|
flex-direction: column;
|
color: #000;
|
}
|
.header {
|
text-align: center;
|
margin-bottom: 8px;
|
}
|
.company-name {
|
font-size: 18px;
|
font-weight: bold;
|
margin-bottom: 4px;
|
}
|
.document-title {
|
font-size: 16px;
|
font-weight: bold;
|
}
|
.info-section {
|
margin-bottom: 8px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
.info-row {
|
line-height: 20px;
|
}
|
.label {
|
font-weight: bold;
|
width: 60px;
|
font-size: 12px;
|
}
|
.value {
|
margin-right: 20px;
|
min-width: 80px;
|
font-size: 12px;
|
}
|
.table-section {
|
margin-bottom: 40px;
|
// flex: 0.6;
|
}
|
.product-table {
|
width: 100%;
|
border-collapse: collapse;
|
border: 1px solid #000;
|
}
|
.product-table th, .product-table td {
|
border: 1px solid #000;
|
padding: 6px;
|
text-align: center;
|
font-size: 12px;
|
line-height: 1.4;
|
}
|
.product-table th {
|
font-weight: bold;
|
}
|
.total-value {
|
font-weight: bold;
|
}
|
.footer-section {
|
margin-top: auto;
|
}
|
.footer-row {
|
display: flex;
|
margin-bottom: 3px;
|
line-height: 22px;
|
justify-content: space-between;
|
}
|
.footer-item {
|
display: flex;
|
margin-right: 20px;
|
}
|
.footer-item .label {
|
font-weight: bold;
|
width: 80px;
|
font-size: 12px;
|
}
|
.footer-item .value {
|
min-width: 80px;
|
font-size: 12px;
|
}
|
.address-item .address-value {
|
min-width: 200px;
|
}
|
@media print {
|
body {
|
margin: 0;
|
padding: 0;
|
}
|
.print-page {
|
margin: 0;
|
padding: 10mm;
|
/* padding-left: 20mm; */
|
page-break-inside: avoid;
|
page-break-after: always;
|
}
|
.print-page:last-child {
|
page-break-after: avoid;
|
}
|
}
|
</style>
|
</head>
|
<body>
|
`;
|
|
// 为每条数据生成打印页面
|
printData.value.forEach((item, index) => {
|
printContent += `
|
<div class="print-page">
|
<div class="delivery-note">
|
<div class="header">
|
<div class="document-title">零售发货单</div>
|
</div>
|
|
<div class="info-section">
|
<div class="info-row">
|
<div>
|
<span class="label">发货日期:</span>
|
<span class="value">${formatDate(
|
item.createTime
|
)}</span>
|
</div>
|
<div>
|
<span class="label">客户名称:</span>
|
<span class="value">${
|
item.customerName
|
}</span>
|
</div>
|
</div>
|
<div class="info-row">
|
<span class="label">单号:</span>
|
<span class="value">${
|
item.salesContractNo ||
|
""
|
}</span>
|
</div>
|
</div>
|
|
<div class="table-section">
|
<table class="product-table">
|
<thead>
|
<tr>
|
<th>产品名称</th>
|
<th>规格型号</th>
|
<th>单位</th>
|
<th>单价</th>
|
<th>零售数量</th>
|
<th>零售金额</th>
|
</tr>
|
</thead>
|
<tbody>
|
${
|
item.products &&
|
item
|
.products
|
.length >
|
0
|
? item.products
|
.map(
|
product => `
|
<tr>
|
<td>${
|
product.productCategory ||
|
""
|
}</td>
|
<td>${
|
product.specificationModel ||
|
""
|
}</td>
|
<td>${
|
product.unit ||
|
""
|
}</td>
|
<td>${
|
product.taxInclusiveUnitPrice ||
|
"0"
|
}</td>
|
<td>${
|
product.quantity ||
|
"0"
|
}</td>
|
<td>${
|
product.taxInclusiveTotalPrice ||
|
"0"
|
}</td>
|
</tr>
|
`
|
)
|
.join(
|
""
|
)
|
: '<tr><td colspan="6" style="text-align: center; color: #999;">暂无产品数据</td></tr>'
|
}
|
</tbody>
|
<tfoot>
|
<tr>
|
<td class="label">合计</td>
|
<td class="total-value"></td>
|
<td class="total-value"></td>
|
<td class="total-value"></td>
|
<td class="total-value">${getTotalQuantityForPrint(
|
item.products
|
)}</td>
|
<td class="total-value">${getTotalAmountForPrint(
|
item.products
|
)}</td>
|
</tr>
|
</tfoot>
|
</table>
|
</div>
|
|
<div class="footer-section">
|
<div class="footer-row">
|
<div class="footer-item">
|
<span class="label">收货电话:</span>
|
<span class="value"></span>
|
</div>
|
<div class="footer-item">
|
<span class="label">收货人:</span>
|
<span class="value"></span>
|
</div>
|
<div class="footer-item address-item">
|
<span class="label">收货地址:</span>
|
<span class="value address-value"></span>
|
</div>
|
</div>
|
<div class="footer-row">
|
<div class="footer-item">
|
<span class="label">操作员:</span>
|
<span class="value">${
|
userStore.nickName ||
|
"撕开前"
|
}</span>
|
</div>
|
<div class="footer-item">
|
<span class="label">打印日期:</span>
|
<span class="value">${formatDateTime(
|
new Date()
|
)}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
`;
|
});
|
|
printContent += `
|
</body>
|
</html>
|
`;
|
|
// 写入内容到新窗口
|
printWindow.document.write(printContent);
|
printWindow.document.close();
|
|
// 等待内容加载完成后打印
|
printWindow.onload = () => {
|
setTimeout(() => {
|
printWindow.print();
|
printWindow.close();
|
printPreviewVisible.value = false;
|
}, 500);
|
};
|
};
|
// 格式化日期
|
const formatDate = dateString => {
|
if (!dateString) return getCurrentDate();
|
const date = new Date(dateString);
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
const day = String(date.getDate()).padStart(2, "0");
|
return `${year}/${month}/${day}`;
|
};
|
// 格式化日期时间
|
const formatDateTime = date => {
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
const day = String(date.getDate()).padStart(2, "0");
|
const hours = String(date.getHours()).padStart(2, "0");
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
const seconds = String(date.getSeconds()).padStart(2, "0");
|
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
|
};
|
// 计算产品总数量
|
const getTotalQuantity = products => {
|
if (!products || products.length === 0) return "0";
|
const total = products.reduce((sum, product) => {
|
return sum + (parseFloat(product.quantity) || 0);
|
}, 0);
|
return total.toFixed(2);
|
};
|
|
// 计算产品总金额
|
const getTotalAmount = products => {
|
if (!products || products.length === 0) return "0";
|
const total = products.reduce((sum, product) => {
|
return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
|
}, 0);
|
return total.toFixed(2);
|
};
|
|
// 用于打印的计算函数
|
const getTotalQuantityForPrint = products => {
|
if (!products || products.length === 0) return "0";
|
const total = products.reduce((sum, product) => {
|
return sum + (parseFloat(product.quantity) || 0);
|
}, 0);
|
return total.toFixed(2);
|
};
|
|
const getTotalAmountForPrint = products => {
|
if (!products || products.length === 0) return "0";
|
const total = products.reduce((sum, product) => {
|
return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
|
}, 0);
|
return total.toFixed(2);
|
};
|
|
const mathNum = () => {
|
console.log("productForm.value", productForm.value);
|
if (!productForm.value.taxInclusiveUnitPrice) {
|
return;
|
}
|
if (!productForm.value.quantity) {
|
return;
|
}
|
// 含税总价计算
|
productForm.value.taxInclusiveTotalPrice =
|
proxy.calculateTaxIncludeTotalPrice(
|
productForm.value.taxInclusiveUnitPrice,
|
productForm.value.quantity
|
);
|
if (productForm.value.taxRate) {
|
// 不含税总价计算
|
productForm.value.taxExclusiveTotalPrice =
|
proxy.calculateTaxExclusiveTotalPrice(
|
productForm.value.taxInclusiveTotalPrice,
|
productForm.value.taxRate
|
);
|
}
|
};
|
|
// 根据含税总价计算含税单价和数量
|
const calculateFromTotalPrice = () => {
|
if (isCalculating.value) return;
|
|
const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
|
const quantity = parseFloat(productForm.value.quantity);
|
|
if (!totalPrice || !quantity || quantity <= 0) {
|
return;
|
}
|
|
isCalculating.value = true;
|
|
// 计算含税单价 = 含税总价 / 数量
|
productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
|
|
// 如果有税率,计算不含税总价
|
if (productForm.value.taxRate) {
|
productForm.value.taxExclusiveTotalPrice =
|
proxy.calculateTaxExclusiveTotalPrice(
|
totalPrice,
|
productForm.value.taxRate
|
);
|
}
|
|
isCalculating.value = false;
|
};
|
|
// 根据不含税总价计算含税单价和数量
|
const calculateFromExclusiveTotalPrice = () => {
|
if (!productForm.value.taxRate) {
|
proxy.$modal.msgWarning("请先选择税率");
|
return;
|
}
|
if (isCalculating.value) return;
|
|
const exclusiveTotalPrice = parseFloat(
|
productForm.value.taxExclusiveTotalPrice
|
);
|
const quantity = parseFloat(productForm.value.quantity);
|
const taxRate = parseFloat(productForm.value.taxRate);
|
|
if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
|
return;
|
}
|
|
isCalculating.value = true;
|
|
// 先计算含税总价 = 不含税总价 / (1 - 税率/100)
|
const taxRateDecimal = taxRate / 100;
|
const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
|
productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
|
|
// 计算含税单价 = 含税总价 / 数量
|
productForm.value.taxInclusiveUnitPrice = (
|
inclusiveTotalPrice / quantity
|
).toFixed(2);
|
|
isCalculating.value = false;
|
};
|
|
// 对私客户价格计算:单价变化时计算总价
|
const calculatePrivatePrice = () => {
|
if (currentCustomerType.value == 2) {
|
const unitPrice = parseFloat(productForm.value.unitPrice) || 0;
|
const quantity = parseFloat(productForm.value.quantity) || 0;
|
|
// 计算总价
|
productForm.value.totalPrice = (unitPrice * quantity).toFixed(2);
|
}
|
};
|
|
// 根据数量变化计算总价
|
const calculateFromQuantity = () => {
|
// 对私客户使用对私计算逻辑
|
if (currentCustomerType.value == 2) {
|
calculatePrivatePrice();
|
return;
|
}
|
|
// 对公客户使用原有的计算逻辑
|
if (!productForm.value.taxRate) {
|
proxy.$modal.msgWarning("请先选择税率");
|
return;
|
}
|
if (isCalculating.value) return;
|
|
const quantity = parseFloat(productForm.value.quantity);
|
const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
|
|
if (!quantity || quantity <= 0 || !unitPrice) {
|
return;
|
}
|
|
isCalculating.value = true;
|
|
// 计算含税总价
|
productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
|
|
// 如果有税率,计算不含税总价
|
if (productForm.value.taxRate) {
|
productForm.value.taxExclusiveTotalPrice =
|
proxy.calculateTaxExclusiveTotalPrice(
|
productForm.value.taxInclusiveTotalPrice,
|
productForm.value.taxRate
|
);
|
}
|
|
isCalculating.value = false;
|
};
|
|
// 根据含税单价变化计算总价
|
const calculateFromUnitPrice = () => {
|
// 对私客户使用对私计算逻辑
|
if (currentCustomerType.value == 2) {
|
calculatePrivatePrice();
|
return;
|
}
|
|
// 对公客户使用原有的计算逻辑
|
if (!productForm.value.taxRate) {
|
proxy.$modal.msgWarning("请先选择税率");
|
return;
|
}
|
if (isCalculating.value) return;
|
|
const quantity = parseFloat(productForm.value.quantity);
|
const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
|
|
if (!quantity || quantity <= 0 || !unitPrice) {
|
return;
|
}
|
|
isCalculating.value = true;
|
|
// 计算含税总价
|
productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
|
|
// 如果有税率,计算不含税总价
|
if (productForm.value.taxRate) {
|
productForm.value.taxExclusiveTotalPrice =
|
proxy.calculateTaxExclusiveTotalPrice(
|
productForm.value.taxInclusiveTotalPrice,
|
productForm.value.taxRate
|
);
|
}
|
|
isCalculating.value = false;
|
};
|
|
// 根据税率变化计算不含税总价
|
const calculateFromTaxRate = () => {
|
// 对私客户不需要税率计算
|
if (currentCustomerType.value == 2) {
|
return;
|
}
|
|
// 对公客户使用原有的计算逻辑
|
if (productForm.value.taxRate === null || productForm.value.taxRate === undefined || productForm.value.taxRate === "") {
|
proxy.$modal.msgWarning("请先选择税率");
|
return;
|
}
|
const taxRateStr = String(productForm.value.taxRate).trim();
|
if (!/^\d+(\.\d+)?$/.test(taxRateStr)) {
|
proxy.$modal.msgWarning("税率只能输入数字");
|
return;
|
}
|
if (isCalculating.value) return;
|
|
const inclusiveTotalPrice = parseFloat(
|
productForm.value.taxInclusiveTotalPrice
|
);
|
const taxRate = parseFloat(productForm.value.taxRate);
|
|
if (!inclusiveTotalPrice || !taxRate) {
|
return;
|
}
|
|
isCalculating.value = true;
|
|
// 计算不含税总价
|
productForm.value.taxExclusiveTotalPrice =
|
proxy.calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate);
|
|
isCalculating.value = false;
|
};
|
|
/**
|
* 获取发货状态文本
|
* @param row 行数据
|
*/
|
const getShippingStatusText = row => {
|
// 如果已发货(有发货日期或车牌号),显示"已发货"
|
if (row.shippingDate || row.shippingCarNumber) {
|
return "已发货";
|
}
|
|
// 获取发货状态字段
|
const status = row.shippingStatus;
|
|
// 如果状态为空或未定义,默认为"待发货"
|
if (status === null || status === undefined || status === "") {
|
return "待发货";
|
}
|
|
// 状态是字符串
|
const statusStr = String(status).trim();
|
const statusTextMap = {
|
待发货: "待发货",
|
待审核: "待审核",
|
审核中: "审核中",
|
审核拒绝: "审核拒绝",
|
审核通过: "审核通过",
|
已发货: "已发货",
|
};
|
return statusTextMap[statusStr] || "待发货";
|
};
|
|
/**
|
* 获取发货状态标签类型(颜色)
|
* @param row 行数据
|
*/
|
const getShippingStatusType = row => {
|
// 如果已发货(有发货日期或车牌号),显示绿色
|
if (row.shippingDate || row.shippingCarNumber) {
|
return "success";
|
}
|
|
// 获取发货状态字段
|
const status = row.shippingStatus;
|
|
// 如果状态为空或未定义,默认为灰色(待发货)
|
if (status === null || status === undefined || status === "") {
|
return "info";
|
}
|
|
// 状态是字符串
|
const statusStr = String(status).trim();
|
const typeTextMap = {
|
待发货: "info",
|
待审核: "info",
|
审核中: "warning",
|
审核拒绝: "danger",
|
审核通过: "success",
|
已发货: "success",
|
};
|
return typeTextMap[statusStr] || "info";
|
};
|
|
/**
|
* 判断是否可以发货
|
* 只有在产品状态是充足,发货状态是待发货和审核拒绝的时候才可以发货
|
* @param row 行数据
|
*/
|
const canShip = row => {
|
// 产品状态必须是充足(approveStatus === 1)
|
if (row.approveStatus !== 1) {
|
return false;
|
}
|
|
// 获取发货状态
|
const shippingStatus = row.shippingStatus;
|
|
// 如果已发货(有发货日期或车牌号),不能再次发货
|
if (row.shippingDate || row.shippingCarNumber) {
|
return false;
|
}
|
|
// 发货状态必须是"待发货"或"审核拒绝"
|
const statusStr = shippingStatus ? String(shippingStatus).trim() : "";
|
return statusStr === "待发货" || statusStr === "审核拒绝";
|
};
|
|
/**
|
* 下载文件
|
*
|
* @param row 下载文件的相关信息对象
|
*/
|
const fileListRef = ref(null);
|
const fileListDialogVisible = ref(false);
|
const downLoadFile = row => {
|
getSalesLedgerWithProducts({ id: row.id, type: 1 }).then(res => {
|
if (fileListRef.value) {
|
fileListRef.value.open(res.salesLedgerFiles);
|
}
|
});
|
};
|
|
// 打开发货弹框
|
const openDeliveryForm = row => {
|
// 检查是否可以发货
|
if (!canShip(row)) {
|
proxy.$modal.msgWarning(
|
"只有在产品状态是充足,发货状态是待发货或审核拒绝的时候才可以发货"
|
);
|
return;
|
}
|
|
currentDeliveryRow.value = row;
|
deliveryForm.value = {
|
type: "货车",
|
};
|
// 重置审批人节点(默认一个空节点)
|
approverNodes.value = [{ id: 1, userId: null }];
|
nextApproverId = 2;
|
deliveryFormVisible.value = true;
|
};
|
|
// 提交发货表单
|
const submitDelivery = () => {
|
proxy.$refs["deliveryFormRef"].validate(valid => {
|
if (valid) {
|
// 审批人必填校验(所有节点都要选人)
|
const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
|
if (hasEmptyApprover) {
|
proxy.$modal.msgError("请为所有审批节点选择审批人!");
|
return;
|
}
|
const approveUserIds = approverNodes.value
|
.map(node => node.userId)
|
.join(",");
|
// 保存当前展开的行ID,以便发货后重新加载子表格数据
|
const currentExpandedKeys = [...expandedRowKeys.value];
|
const salesLedgerId = currentDeliveryRow.value.salesLedgerId;
|
addShippingInfo({
|
salesLedgerId: salesLedgerId,
|
salesLedgerProductId: currentDeliveryRow.value.id,
|
type: deliveryForm.value.type,
|
approveUserIds,
|
}).then(() => {
|
proxy.$modal.msgSuccess("发货成功");
|
closeDeliveryDia();
|
// 刷新主表数据
|
getList().then(() => {
|
// 如果之前有展开的行,重新加载这些行的子表格数据
|
if (currentExpandedKeys.length > 0) {
|
// 使用 Promise.all 并行加载所有展开行的子表格数据
|
const loadPromises = currentExpandedKeys.map(ledgerId => {
|
return productList({ salesLedgerId: ledgerId, type: 1 }).then(
|
res => {
|
const index = tableData.value.findIndex(
|
item => item.id === ledgerId
|
);
|
if (index > -1) {
|
tableData.value[index].children = res.data;
|
}
|
}
|
);
|
});
|
Promise.all(loadPromises).then(() => {
|
// 恢复展开状态
|
expandedRowKeys.value = currentExpandedKeys;
|
});
|
}
|
});
|
});
|
}
|
});
|
};
|
|
// 关闭发货弹框
|
const closeDeliveryDia = () => {
|
proxy.resetForm("deliveryFormRef");
|
deliveryFormVisible.value = false;
|
currentDeliveryRow.value = null;
|
};
|
const currentFactoryName = ref("");
|
const getCurrentFactoryName = async () => {
|
let res = await userStore.getInfo();
|
currentFactoryName.value = res.user.currentFactoryName;
|
};
|
onMounted(() => {
|
getList();
|
userListNoPage().then(res => {
|
userList.value = res.data;
|
});
|
getCurrentFactoryName();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.ml-10 {
|
margin-left: 10px;
|
}
|
|
::v-deep .yellow {
|
background-color: #faf0de;
|
}
|
|
::v-deep .pink {
|
background-color: #fae1de;
|
}
|
|
::v-deep .red {
|
background-color: #fae1de;
|
}
|
|
::v-deep .purple {
|
background-color: #f4defa;
|
}
|
|
.table_list {
|
margin-top: unset;
|
}
|
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 10px;
|
}
|
|
.print-preview-dialog {
|
.el-dialog__body {
|
padding: 0;
|
max-height: 80vh;
|
overflow-y: auto;
|
}
|
}
|
|
.print-preview-container {
|
.print-preview-header {
|
padding: 15px;
|
border-bottom: 1px solid #e4e7ed;
|
text-align: center;
|
|
.el-button {
|
margin: 0 10px;
|
}
|
}
|
|
.print-preview-content {
|
padding: 20px;
|
background-color: #f5f5f5;
|
min-height: 400px;
|
}
|
}
|
|
.print-page {
|
width: 220mm;
|
height: 90mm;
|
padding: 10mm;
|
margin: 0 auto;
|
background: white;
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
margin-bottom: 10px;
|
box-sizing: border-box;
|
}
|
|
.delivery-note {
|
width: 100%;
|
height: 100%;
|
font-family: "SimSun", serif;
|
font-size: 10px;
|
line-height: 1.2;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.header {
|
text-align: center;
|
margin-bottom: 8px;
|
|
.company-name {
|
font-size: 18px;
|
font-weight: bold;
|
margin-bottom: 4px;
|
}
|
|
.document-title {
|
font-size: 16px;
|
font-weight: bold;
|
}
|
}
|
|
.info-section {
|
margin-bottom: 8px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.info-row {
|
line-height: 20px;
|
|
.label {
|
font-weight: bold;
|
width: 60px;
|
font-size: 14px;
|
}
|
|
.value {
|
margin-right: 20px;
|
min-width: 80px;
|
font-size: 14px;
|
}
|
}
|
}
|
|
.table-section {
|
margin-bottom: 4px;
|
flex: 1;
|
|
.product-table {
|
width: 100%;
|
border-collapse: collapse;
|
border: 1px solid #000;
|
|
th,
|
td {
|
border: 1px solid #000;
|
padding: 6px;
|
text-align: center;
|
font-size: 14px;
|
line-height: 1.4;
|
}
|
|
th {
|
font-weight: bold;
|
}
|
|
.total-label {
|
text-align: right;
|
font-weight: bold;
|
}
|
|
.total-value {
|
font-weight: bold;
|
}
|
}
|
}
|
|
.footer-section {
|
.footer-row {
|
display: flex;
|
margin-bottom: 3px;
|
line-height: 20px;
|
justify-content: space-between;
|
|
.footer-item {
|
display: flex;
|
margin-right: 20px;
|
|
.label {
|
font-weight: bold;
|
width: 80px;
|
font-size: 14px;
|
}
|
|
.value {
|
min-width: 80px;
|
font-size: 14px;
|
}
|
|
&.address-item {
|
.address-value {
|
min-width: 200px;
|
}
|
}
|
}
|
}
|
}
|
|
@media print {
|
.app-container {
|
display: none;
|
}
|
|
.print-page {
|
box-shadow: none;
|
margin: 0;
|
padding: 10mm;
|
padding-left: 20mm;
|
page-break-inside: avoid;
|
page-break-after: always;
|
}
|
.print-page:last-child {
|
page-break-after: avoid;
|
}
|
}
|
</style>
|