Files
art-design/src/views/crmpc/stocking copy/final_add.vue
T
2025-09-08 19:09:55 +08:00

1568 lines
56 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="stocking-add-page">
<div class="header boxshadow">
<div class="title">合塑实业·汽车事业部-备货单(客户下定)</div>
<div class="subtitle">业务员:{{ getUserInfo.user.name }}</div>
</div>
<div class="boxshadow">
<div class="form-title">
<span class="border"></span>
<span class="title">基础信息</span>
</div>
<ElForm :model="baseForm" class="base-form">
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="阿里订单号/PN号">
<ElInput v-model="baseForm.pi_no" placeholder="请输入阿里订单号/PN号" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="日期">
<ElDatePicker
v-model="baseForm.biz_date"
type="date"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
placeholder="请选择日期"
style="width: 100%"
/>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="清关目的国">
<el-select
v-model="baseForm.clearance_country_id"
filterable
placeholder="请选择国家"
@change="
(val) =>
(baseForm.clearance_country_snapshot =
(countryOptions || []).find((x: any) => x.id === val)?.cn_addr || '')
"
>
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.cn_addr"
:value="item.id"
/>
</el-select>
<ElInput v-model="baseForm.clearance_country_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="客户来源">
<el-select
v-model="baseForm.customer_source_id"
filterable
placeholder="请选择客户渠道"
@change="
(val) =>
(baseForm.customer_source_snapshot =
(channelOptions || []).find((x: any) => x.id === val)?.name || '')
"
>
<el-option
v-for="item in channelOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
<ElInput v-model="baseForm.customer_source_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="客户名称">
<ElInput
v-model="baseForm.customer_snapshot"
placeholder="请选择客户"
readonly
@click="openCustomerDialog"
>
<template #append>
<ElButton type="primary" text @click.stop="openCustomerDialog">选择</ElButton>
</template>
</ElInput>
<ElInput v-model="baseForm.customer_id" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="客户所在国">
<el-select
v-model="baseForm.customer_country_id"
filterable
placeholder="请选择国家"
@change="
(val) =>
(baseForm.customer_country_snapshot =
(countryOptions || []).find((x: any) => x.id === val)?.cn_addr || '')
"
>
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.cn_addr"
:value="item.id"
/>
</el-select>
<ElInput v-model="baseForm.customer_country_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
</ElRow>
</ElForm>
</div>
<!-- 明细行 -->
<div class="boxshadow">
<div class="form-title">
<span class="border"></span>
<span class="title">订单详情</span>
</div>
<div class="items-wrap">
<div class="item-row" v-for="(item, idx) in items" :key="idx">
<ElRow :gutter="12">
<ElCol :span="8">
<ElFormItem label="车型" label-width="64px">
<ElInput
v-model="item.model_snapshot"
placeholder="请选择车型"
readonly
disabled
@click="openVehicleDialog(idx)"
>
<!-- <template #append>
<ElButton type="primary" text @click.stop="openVehicleDialog(idx)"
>选择</ElButton
>
</template> -->
</ElInput>
<ElInput v-model="item.sku_id" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="颜色/外饰" label-width="72px">
<el-select
v-model="item.color_exterior_snapshot"
filterable
placeholder="请选择内容"
@change="
(val) =>
(item.color_exterior_code =
(wcolorOptions || []).find((x: any) => x.color_text === val)?.color_code ||
'')
"
>
<ElOption
v-for="item in wcolorOptions"
:key="item.color_code"
:label="item.color_text"
:value="item.color_text"
/>
</el-select>
<ElInput v-model="item.color_exterior_code" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="颜色/内饰" label-width="72px">
<el-select
v-model="item.color_interior_snapshot"
filterable
placeholder="请选择内容"
@change="
(val) =>
(item.color_interior_code =
(ncolorOptions || []).find((x: any) => x.color_text === val)?.color_code ||
'')
"
>
<ElOption
v-for="item in ncolorOptions"
:key="item.color_code"
:label="item.color_text"
:value="item.color_text"
/>
</el-select>
<ElInput v-model="item.color_interior_code" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem label="改装" label-width="52px">
<el-select
v-model="item.modify_ids"
filterable
multiple
placeholder="请选择内容"
@change="
(vals) =>
(item.modify_list = (modificationOptions || [])
.filter((x: any) => (vals || []).includes(x.id))
.map((x: any) => ({ option_id: x.id, option_name_snapshot: x.name })))
"
>
<el-option
v-for="item in modificationOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="改装费" label-width="60px">
<ElInput v-model.number="item.retrofit_fee" placeholder="请输入改装费">
<template #append>
<ElSelect v-model="item.modify_fee_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.modify_fee_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="FOB段运费" label-width="84px">
<ElInput v-model.number="item.fob_transport_fee" placeholder="请输入FOB段运费">
<template #append>
<ElSelect v-model="item.fob_fee_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.fob_fee_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="单价" label-width="52px">
<ElInput v-model.number="item.unit_price" placeholder="请输入单价">
<template #append>
<ElSelect v-model="item.unit_price_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.unit_price_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="总价" label-width="52px">
<ElInput v-model.number="item.total_price" placeholder="请输入总价">
<template #append>
<ElSelect v-model="item.total_price_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.total_price_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="官方指导价" label-width="85px">
<ElInput v-model.number="item.official_price" placeholder="请输入官方指导价">
<template #append>
<ElSelect v-model="item.guide_price_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.guide_price_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="优惠" label-width="52px">
<ElInput v-model.number="item.discount_price" placeholder="请输入优惠额">
<template #append>
<ElSelect v-model="item.discount_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.discount_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="手续费" label-width="60px">
<ElInput v-model.number="item.handling_fee" placeholder="请输入手续费">
<template #append>
<ElSelect v-model="item.helper_fee_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.helper_fee_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<!-- <ElCol :span="4">
<ElFormItem label="国内运费" label-width="72px">
<ElInput v-model.number="item.domestic_freight" placeholder="请输入国内运费">
<template #append>
<ElSelect v-model="item.domestic_freight_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.domestic_freight_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol> -->
<ElCol :span="4">
<ElFormItem label="港杂费" label-width="60px">
<ElInput v-model.number="item.port_misc_fee" placeholder="请输入港杂费">
<template #append>
<ElSelect v-model="item.port_fee_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.port_fee_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="利润" label-width="52px">
<ElInput v-model="item.profit" placeholder="正在计算利润" disabled></ElInput>
<ElInput v-model="item.profit_type_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem label="车架号" label-width="70px">
<ElInput v-model.number="item.vin" placeholder="请输入" />
</ElFormItem>
</ElCol>
</ElRow>
</div>
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="合计到岸价" label-width="85px">
<ElInput v-model.number="baseForm.total_cif" placeholder="请输入合计到岸价">
<template #append>
<ElSelect v-model="baseForm.total_cif_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="baseForm.total_cif_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="合同汇率" label-width="70px">
<ElInput v-model="baseForm.contract_exchange_rate" placeholder="请输入" />
</ElFormItem>
</ElCol>
</ElRow>
</div>
</div>
<div class="boxshadow">
<div class="form-title">
<span class="border"></span>
<span class="title">运输详情</span>
</div>
<ElForm :model="baseForm" class="foot-form">
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="清关人">
<ElInput v-model="baseForm.declarant_name" placeholder="请输入清关人" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="预计发送日">
<ElDatePicker
v-model="baseForm.eta_ship_date"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
type="date"
placeholder="请选择日期"
style="width: 100%"
/>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="运输方式">
<ElSelect
v-model="baseForm.shipping_method_id"
placeholder="请选择运输方式"
@change="
(val) =>
(baseForm.shipping_method_snapshot =
(transportOptions || []).find((x: any) => x.id === val)?.item_label || '')
"
>
<ElOption
v-for="t in transportOptions"
:key="t.id"
:label="t.item_label"
:value="t.id"
/>
</ElSelect>
<ElInput v-model="baseForm.shipping_method_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="国内收货港">
<ElSelect
v-model="baseForm.domestic_port_id"
placeholder="请选择国内收货商"
@change="
(val) =>
(baseForm.domestic_port_snapshot =
(portOptions || []).find((x: any) => x.id === val)?.port_name || '')
"
>
<ElOption v-for="p in portOptions" :key="p.id" :label="p.port_name" :value="p.id" />
</ElSelect>
<ElInput v-model="baseForm.domestic_port_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="贸易条款">
<ElSelect
v-model="baseForm.trade_term_id"
placeholder="请选择"
@change="
(val) =>
(baseForm.trade_term_snapshot =
(trade_termsOptions || []).find((x: any) => x.id === val)?.item_label || '')
"
>
<ElOption
v-for="t in trade_termsOptions"
:key="t.id"
:label="t.item_label"
:value="t.id"
/>
</ElSelect>
<ElInput v-model="baseForm.trade_term_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="清关目的港">
<ElSelect
v-model="baseForm.clearance_port_id"
placeholder="请选择"
@change="
(val) =>
(baseForm.clearance_port_snapshot =
(portOptionsall || []).find((x: any) => x.id === val)?.port_name || '')
"
>
<ElOption
v-for="p in portOptionsall"
:key="p.id"
:label="p.port_name"
:value="p.id"
/>
</ElSelect>
<ElInput v-model="baseForm.clearance_port_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12">
<ElCol :span="6">
<ElFormItem label="最迟到港时间">
<ElDatePicker
v-model="baseForm.latest_arrival_date"
type="date"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
placeholder="请选择日期"
style="width: 100%"
/>
</ElFormItem>
</ElCol>
</ElRow>
</ElForm>
</div>
<div class="boxshadow">
<div class="form-title">
<span class="border"></span>
<span class="title">计划收款信息</span>
</div>
<div class="payment-section">
<div class="payment-items">
<div v-for="(payment, idx) in paymentItems" :key="idx" class="payment-item">
<ElForm :model="payment" label-width="55px" class="payment-form">
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="汇款国">
<el-select v-model="payment.remit_country_id" filterable placeholder="请选择汇款国" @change="
(val) =>
(payment.remit_country_name_snapshot =
(countryOptions || []).find((x: any) => x.id === val)?.cn_addr || '')
">
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.cn_addr"
:value="item.id"
/>
</el-select>
<ElInput v-model="payment.remit_country_name_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="日期">
<ElDatePicker
v-model="payment.remit_date"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择日期"
style="width: 100%"
/>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="币种">
<ElSelect v-model="payment.currency_code" placeholder="请选择币种" @change="
(val) => {
const found = (currencyOptions || []).find((x: any) => x.currency_code === val)
payment.currency_name_snapshot = found?.currency_name || ''
}
">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
<ElInput v-model="payment.currency_name_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="账户">
<ElSelect v-model="payment.account_name_snapshot" placeholder="请选择账户" @change="
(val) =>
(payment.account_id =
(accountOptions || []).find((x: any) => x.account_number === val)?.id || '')
">
<ElOption
v-for="item in accountOptions"
:key="item.id"
:label="item.account_number"
:value="item.account_number"
/>
</ElSelect>
<ElInput v-model="payment.account_id" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="汇款人">
<ElInput v-model="payment.remitter_name" placeholder="请选择汇款人" />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="金额">
<ElInput v-model.number="payment.amount" placeholder="请输入金额" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12">
<ElCol :span="24" style="text-align: right;">
<ElButton type="danger" size="small" @click="removePaymentItem(idx)" :disabled="paymentItems.length === 1">删除</ElButton>
<ElButton type="primary" size="small" @click="addPaymentItem" v-if="idx === paymentItems.length - 1">添加收款信息</ElButton>
</ElCol>
</ElRow>
</ElForm>
</div>
</div>
</div>
</div>
<!-- 上传合同 -->
<div class="boxshadow">
<div class="form-title">
<span class="border"></span>
<span class="title">上传合同</span>
</div>
<el-upload
class="upload-box"
:action="actionImageUplode"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleContractUpload"
:before-upload="beforeContractUpload"
accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.txt"
>
<div class="qrcode-upload-box">
<input type="hidden" v-model="baseForm.contract"/>
<img v-if="!contractFile" class="avatar" src="@/assets/img/crm/contract.png" />
<div v-else class="file-preview">
<img v-if="isImageFile(contractFile)" :src="contractFile.url" class="preview-image" />
<div v-else class="file-icon">
<i class="iconfont-sys">&#xe814;</i>
<span class="file-name">{{ contractFile.name }}</span>
</div>
<div class="file-actions">
<el-button type="text" size="small" @click.stop="removeContractFile">删除</el-button>
</div>
</div>
</div>
</el-upload>
</div>
<div class="form-card">
<!-- 底部信息 -->
<div class="submit-wrap">
<ElButton type="primary" size="large" class="submit-btn" @click="handleSubmit"
>提交</ElButton
>
</div>
</div>
<!-- 选择车型 弹框 -->
<ElDialog
v-model="vehicleDialog.visible"
title="选择车辆"
width="900px"
:close-on-click-modal="false"
top="40px"
>
<div
class="search-toolbar"
style="margin-bottom: 12px; display: flex; justify-content: flex-end; gap: 8px"
>
<el-input
v-model="vehicleDialog.search.keyword"
placeholder="请输入关键字"
clearable
style="width: 300px"
@keyup.enter="getVehicleList"
>
<template #suffix>
<el-icon class="clickable" @click="getVehicleList"><Search /></el-icon>
</template>
</el-input>
</div>
<ArtTable
:loading="vehicleDialog.loading"
:data="vehicleDialog.tableData"
:currentPage="vehicleDialog.pagination.currentPage"
:pageSize="vehicleDialog.pagination.pageSize"
:total="vehicleDialog.pagination.total"
@size-change="handleVehicleSizeChange"
@current-change="handleVehicleCurrentChange"
>
<template #default>
<ElTableColumn label="品牌" prop="brand_name" align="center" />
<ElTableColumn label="车系" prop="series_name" align="center" />
<ElTableColumn label="车型/配置" prop="model_name" min-width="160">
<template #default="scope">
<span class="primary">{{ scope.row.model_name }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="年款" prop="model_year" width="80" align="center" />
<ElTableColumn label="指导价" prop="msrp" width="100" align="center" />
<ElTableColumn label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<ElButton link type="primary" @click="chooseVehicle(scope.row)">选择</ElButton>
</template>
</ElTableColumn>
</template>
</ArtTable>
</ElDialog>
<!-- 选择客户 弹框 -->
<ElDialog
v-model="customerDialog.visible"
title="选择客户"
width="900px"
:close-on-click-modal="false"
top="40px"
>
<div
class="search-toolbar"
style="margin-bottom: 12px; display: flex; justify-content: flex-end; gap: 8px"
>
<el-input
v-model="customerDialog.search.keyword"
placeholder="请输入客户名称/电话"
clearable
style="width: 300px"
@keyup.enter="getCustomerList"
>
<template #suffix>
<el-icon class="clickable" @click="getCustomerList"><Search /></el-icon>
</template>
</el-input>
</div>
<ArtTable
:loading="customerDialog.loading"
:data="customerDialog.tableData"
:currentPage="customerDialog.pagination.currentPage"
:pageSize="customerDialog.pagination.pageSize"
:total="customerDialog.pagination.total"
@size-change="handleCustomerSizeChange"
@current-change="handleCustomerCurrentChange"
>
<template #default>
<ElTableColumn label="客户名称" prop="contacts" />
<ElTableColumn label="客户编码" prop="code" />
<ElTableColumn label="电话" prop="phone" />
<ElTableColumn label="公司" prop="company" />
<ElTableColumn label="操作" width="80" align="center" fixed="right">
<template #default="scope">
<ElButton link type="primary" @click="chooseCustomer(scope.row)">选择</ElButton>
</template>
</ElTableColumn>
</template>
</ArtTable>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { CrmService } from '@/api/crmApi'
import { commonService } from '@/api/commonApi'
import { vehicleService } from '@/api/vehicleApi'
import { useUserStore } from '@/store/modules/user'
import { watch } from 'vue'
import { useUploadHeaders } from '@/composables/useUploadHeaders'
const { uploadHeaders } = useUploadHeaders()
let actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload?scene=stock_orders`
const route = useRoute()
const stocking_id = route.query.id
const { getUserInfo } = useUserStore()
const channelOptions = ref<any>()
const countryOptions = ref<any>()
const modificationOptions = ref<any>()
const currencyOptions = ref<any>()
const accountOptions = ref<any>()
const customerOptions = ref<any>()
const portOptions = ref<any>()
const portOptionsall = ref<any>()
// 外观颜色
const wcolorOptions = ref<Array<any>>([])
// 内饰颜色
const ncolorOptions = ref<Array<any>>([])
// 合同文件相关
const contractFile = ref<any>(null)
//渠道
const getgetChannel = async () => {
const res = await CrmService.getChannel()
channelOptions.value = res || []
}
//客户下拉
const getcustomer = async () => {
const res = await commonService.getCustomerSelect()
customerOptions.value = res || []
}
//国家
const getarea = async () => {
const res = await CrmService.getCountry()
countryOptions.value = res || []
}
//改装项
const getmodification = async () => {
const res = await commonService.getModificationSelect()
modificationOptions.value = res || []
}
//颜色下拉列表
const loadColorOptions = async () => {
try {
const res = await commonService.getColorSelect({ type: 1 })
wcolorOptions.value = res
const response = await commonService.getColorSelect({ type: 2 })
ncolorOptions.value = response
} catch (error) {
console.error('获取颜色列表失败:', error)
ElMessage.error('获取颜色列表失败')
}
}
//账户
const loadAccountOptions = async () => {
try {
const res = await commonService.getAccountSelect()
accountOptions.value = res
} catch (error) {
console.error('获取账户列表失败:', error)
ElMessage.error('获取账户列表失败')
}
}
//币种
const loadCurrencyOptions = async () => {
try {
const res = await commonService.getCurrencySelect()
currencyOptions.value = res
} catch (error) {
console.error('获取币种列表失败:', error)
ElMessage.error('获取币种列表失败')
}
}
//港口
const loadPortOptions = async () => {
try {
const res = await commonService.getPortSelect({ scene: 'domestic' })
const resall = await commonService.getPortSelect({})
portOptions.value = res
portOptionsall.value = resall
} catch (error) {
console.error('获取港口列表失败:', error)
ElMessage.error('获取港口列表失败')
}
}
//字典下拉框
const transportOptions = ref<Array<any>>([])
const trade_termsOptions = ref<Array<any>>([])
const loadDictOptions = async () => {
const response = await commonService.getDictSelect({ scope: '2,15' })
transportOptions.value = response.transport
trade_termsOptions.value = response.trade_terms
}
// 合同文件相关方法
const isImageFile = (file: any) => {
if (!file) return false
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp']
// 检查文件名
const fileName = file.name || file.file_name || ''
if (fileName && imageExtensions.some(ext => fileName.toLowerCase().endsWith(ext))) {
return true
}
// 检查URL中的文件扩展名(去除查询参数)
if (file.url) {
const urlWithoutParams = file.url.split('?')[0] // 移除查询参数
if (imageExtensions.some(ext => urlWithoutParams.toLowerCase().endsWith(ext))) {
return true
}
}
return false
}
const beforeContractUpload = (file: any) => {
const isValidType = [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/webp',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/plain'
].includes(file.type)
if (!isValidType) {
ElMessage.error('只能上传图片、PDF、Word、Excel或文本文件!')
return false
}
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
ElMessage.error('文件大小不能超过 10MB!')
return false
}
return true
}
const handleContractUpload = (response: any) => {
if (response && response.code === 200) {
contractFile.value = {
name: response.data.original_name || '合同文件',
url: response.data.file_url,
file_name: response.data.file_name
}
baseForm.contract = response.data.file_name || ''
ElMessage.success('合同文件上传成功')
} else {
ElMessage.error('合同文件上传失败')
}
}
const removeContractFile = () => {
contractFile.value = null
ElMessage.success('合同文件已删除')
}
const stockingInfo = ref<any>()
const detailId = ref<string | number>('')
const populateFromDetail = (res: any) => {
if (!res) return
detailId.value = res.id || ''
// 基础信息
baseForm.pi_no = res.pi_no || ''
baseForm.contract = res.contract || ''
baseForm.biz_date = res.biz_date || ''
baseForm.clearance_country_id = res.clearance_country_id || ''
baseForm.clearance_country_snapshot = res.clearance_country_snapshot || ''
baseForm.customer_source_id = res.customer_source_id || ''
baseForm.customer_source_snapshot = res.customer_source_snapshot || ''
baseForm.customer_id = res.customer_id || ''
baseForm.customer_snapshot = res.customer_snapshot || ''
baseForm.customer_country_id = res.customer_country_id || ''
baseForm.customer_country_snapshot = res.customer_country_snapshot || ''
baseForm.declarant_name = res.declarant_name || ''
baseForm.eta_ship_date = res.eta_ship_date || ''
baseForm.shipping_method_id = res.shipping_method_id || ''
baseForm.shipping_method_snapshot = res.shipping_method_snapshot || ''
baseForm.domestic_port_id = res.domestic_port_id || ''
baseForm.domestic_port_snapshot = res.domestic_port_snapshot || ''
baseForm.trade_term_id = res.trade_term_id || ''
baseForm.trade_term_snapshot = res.trade_term_snapshot || ''
baseForm.clearance_port_id = res.clearance_port_id || ''
baseForm.clearance_port_snapshot = res.clearance_port_snapshot || ''
baseForm.latest_arrival_date = res.latest_arrival_date || ''
baseForm.remark = res.remark || ''
// 合计与汇率
baseForm.total_cif = res.cif_total_amount ? Number(res.cif_total_amount) : undefined
baseForm.total_cif_currency = res.settlement_currency || baseForm.total_cif_currency
baseForm.contract_exchange_rate = res.contract_exchange_rate || getCurrencyRate(baseForm.total_cif_currency)
// 明细
const list = Array.isArray(res.veh_list) ? res.veh_list : []
items.value = list.map((it: any) => ({
sku_id: it.sku_id ? String(it.sku_id) : '',
vin: it.vin || '',
model_snapshot: it.model_snapshot || '',
color_interior_code: it.color_interior_code || '',
color_interior_snapshot: it.color_interior_snapshot || '',
color_exterior_code: it.color_exterior_code || '',
color_exterior_snapshot: it.color_exterior_snapshot || '',
unit_price: it.unit_price?.amount ? Number(it.unit_price.amount) : undefined,
handling_fee: it.handling_fee?.amount ?? '',
fob_transport_fee: it.fob_transport_fee?.amount ? Number(it.fob_transport_fee.amount) : undefined,
port_misc_fee: it.port_misc_fee?.amount ?? '',
retrofit_fee: it.retrofit_fee?.amount ?? '',
total_price: it.total_price?.amount ? Number(it.total_price.amount) : undefined,
official_price: it.official_price?.amount ? Number(it.official_price.amount) : undefined,
discount_price: it.discount_price?.amount ? Number(it.discount_price.amount) : undefined,
fob_fee: undefined,
fob_fee_currency: it.fob_transport_fee?.currency || 'USD',
fob_fee_currency_snapshot: '',
helper_fee: undefined,
helper_fee_currency: it.handling_fee?.currency || 'USD',
helper_fee_currency_snapshot: '',
domestic_freight: undefined,
domestic_freight_currency: it.domestic_freight?.currency || 'USD',
domestic_freight_currency_snapshot: '',
port_fee: undefined,
port_fee_currency: it.port_misc_fee?.currency || 'USD',
port_fee_currency_snapshot: '',
unit_price_currency: it.unit_price?.currency || 'USD',
unit_price_currency_snapshot: '',
total_price_currency: it.total_price?.currency || 'USD',
total_price_currency_snapshot: '',
guide_price_currency: it.official_price?.currency || 'USD',
guide_price_currency_snapshot: '',
discount: undefined,
discount_currency: it.discount_price?.currency || 'USD',
discount_currency_snapshot: '',
contract_rate: baseForm.contract_exchange_rate ? Number(baseForm.contract_exchange_rate) : undefined,
profit: undefined,
profit_type_snapshot: '',
modify_ids: Array.isArray(it.modify_list) ? it.modify_list.map((m: any) => Number(m.option_id)) : [],
modify_list: Array.isArray(it.modify_list)
? it.modify_list.map((m: any) => ({ option_id: String(m.option_id), option_name_snapshot: m.option_name_snapshot }))
: [],
modify_fee_currency: it.retrofit_fee?.currency || 'USD',
modify_fee_currency_snapshot: ''
}))
// 计划收款
const pays = Array.isArray(res.account_list) ? res.account_list : []
paymentItems.value = pays.map((p: any) => ({
remit_country_id: p.remit_country_id ? Number(p.remit_country_id) : '',
remit_country_name_snapshot: p.remit_country_name_snapshot || '',
remit_date: p.remit_date || '',
currency_code: p.currency_code || '',
currency_name_snapshot: p.currency_name_snapshot || '',
account_id: p.account_id ? String(p.account_id) : '',
account_name_snapshot: p.account_name_snapshot || '',
remitter_name: p.remitter_name || '',
amount: p.amount ? Number(p.amount) : undefined
}))
// 合同文件回显
if (res.contract) {
contractFile.value = {
name: '合同文件',
url: res.contract_url || res.contract,
file_name: res.contract
}
}
}
//获取详情并映射到回显模型
const getdetilfn = async () => {
const res = await CrmService.getStockDetail({ id: stocking_id })
stockingInfo.value = res
populateFromDetail(res)
}
onMounted(async () => {
getcustomer()
await loadCurrencyOptions()
getgetChannel()
getarea()
getmodification()
loadColorOptions()
loadAccountOptions()
loadPortOptions()
loadDictOptions()
// 设置默认汇率
baseForm.contract_exchange_rate = getCurrencyRate('USD')
// 如果有ID,加载详情数据
if (stocking_id) {
await getdetilfn()
}
})
const getCurrencyRate = (code: string): string => {
try {
const found = (currencyOptions?.value || []).find((c: any) => c.currency_code === code)
return found?.rate || found?.exchange_rate || ''
} catch (e) {
return ''
}
}
const baseForm = reactive({
pi_no: '',
biz_date: '',
clearance_country_id: '',
clearance_country_snapshot: '',
customer_source_id: '',
customer_source_snapshot: '',
customer_id: '',
customer_snapshot: '',
customer_country_id: '',
customer_country_snapshot: '',
declarant_name: '',
eta_ship_date: '',
shipping_method_id: '',
shipping_method_snapshot: '',
domestic_port_id: '',
domestic_port_snapshot: '',
trade_term_id: '',
trade_term_snapshot: '',
clearance_port_id: '',
clearance_port_snapshot: '',
latest_arrival_date: '',
contract_exchange_rate: '',
remark: '',
total_cif: undefined as number | undefined,
total_cif_currency: 'USD',
total_cif_currency_snapshot: '',
is_final: '1',
object_id: stocking_id,
contract: '',
contract_url: ''
})
type ItemRow = {
sku_id: string
vin: string
model_snapshot: string
color_interior_code: string
color_interior_snapshot: string
color_exterior_code: string
color_exterior_snapshot: string
unit_price: number | undefined
handling_fee: string
fob_transport_fee: number | undefined
port_misc_fee: string
retrofit_fee: string
total_price: number | undefined
official_price: number | undefined
discount_price: number | undefined
fob_fee: number | undefined
fob_fee_currency: string
fob_fee_currency_snapshot: string
helper_fee: number | undefined
helper_fee_currency: string
helper_fee_currency_snapshot: string
domestic_freight: number | undefined
domestic_freight_currency: string
domestic_freight_currency_snapshot: string
port_fee: number | undefined
port_fee_currency: string
port_fee_currency_snapshot: string
unit_price_currency: string
unit_price_currency_snapshot: string
total_price_currency: string
total_price_currency_snapshot: string
guide_price_currency: string
guide_price_currency_snapshot: string
discount: number | undefined
discount_currency: string
discount_currency_snapshot: string
contract_rate: number | undefined
profit: number | undefined
profit_type_snapshot: string
modify_ids: string[]
modify_list: Array<{ option_id: string; option_name_snapshot: string }>
modify_fee_currency: string
modify_fee_currency_snapshot: string
}
const newItem = (): ItemRow => ({
sku_id: '',
vin: '',
model_snapshot: '',
color_interior_code: '',
color_interior_snapshot: '',
color_exterior_code: '',
color_exterior_snapshot: '',
unit_price: undefined,
handling_fee: '',
fob_transport_fee: undefined,
port_misc_fee: '',
retrofit_fee: '',
total_price: undefined,
official_price: undefined,
discount_price: undefined,
fob_fee: undefined,
fob_fee_currency: 'USD',
fob_fee_currency_snapshot: '',
helper_fee: undefined,
helper_fee_currency: 'USD',
helper_fee_currency_snapshot: '',
domestic_freight: undefined,
domestic_freight_currency: 'USD',
domestic_freight_currency_snapshot: '',
port_fee: undefined,
port_fee_currency: 'USD',
port_fee_currency_snapshot: '',
unit_price_currency: 'USD',
unit_price_currency_snapshot: '',
total_price_currency: 'USD',
total_price_currency_snapshot: '',
guide_price_currency: 'USD',
guide_price_currency_snapshot: '',
discount: undefined,
discount_currency: 'USD',
discount_currency_snapshot: '',
contract_rate: 7.16,
profit: undefined,
profit_type_snapshot: '',
modify_ids: [],
modify_list: [],
modify_fee_currency: 'USD',
modify_fee_currency_snapshot: ''
})
const items = ref<ItemRow[]>([newItem()])
// 监听币种变化,自动更新汇率
watch(
() => items.value[0].unit_price_currency,
(newCurrency) => {
if (newCurrency) {
baseForm.contract_exchange_rate = getCurrencyRate(newCurrency)
}
}
)
// 同时监听合计到岸价币种变化,联动更新合同汇率
watch(
() => baseForm.total_cif_currency,
(newCurrency) => {
if (newCurrency) {
baseForm.contract_exchange_rate = getCurrencyRate(newCurrency)
}
}
)
type PaymentItem = {
remit_country_id: string
remit_country_name_snapshot: string
remit_date: string
currency_code: string
currency_name_snapshot: string
account_id: string
account_name_snapshot: string
remitter_name: string
amount: number | undefined
}
const newPaymentItem = (): PaymentItem => ({
remit_country_id: '',
remit_country_name_snapshot: '',
remit_date: '',
currency_code: '',
currency_name_snapshot: '',
account_id: '',
account_name_snapshot: '',
remitter_name: '',
amount: undefined
})
const paymentItems = ref<PaymentItem[]>([newPaymentItem()])
const addPaymentItem = () => {
paymentItems.value.push(newPaymentItem())
}
const removePaymentItem = (idx: number) => {
if (paymentItems.value.length > 1) {
paymentItems.value.splice(idx, 1)
}
}
const wrapMoney = (amount: any, currency: string, remark: string) => ({
currency: currency || '',
amount: amount === undefined || amount === null ? '' : String(amount),
rate: getCurrencyRate(currency),
remark
})
// 计算单行利润 = 单价 - (改装费+FOB段运费+官方指导价+优惠+手续费+港杂费)
const calcProfit = (it: any): number => {
const toNum = (v: any) => (v === undefined || v === null || v === '' ? 0 : Number(v))
const unit = toNum(it.unit_price)
const retrofit = toNum(it.retrofit_fee)
const fob = toNum(it.fob_transport_fee ?? (it as any).fob_fee)
const guide = toNum(it.official_price ?? (it as any).guide_price)
const discount = toNum(it.discount_price ?? (it as any).discount)
const handling = toNum(it.handling_fee ?? (it as any).helper_fee)
const port = toNum(it.port_misc_fee ?? (it as any).port_fee)
return unit - (retrofit + fob + guide + discount + handling + port)
}
watch(
items,
(val) => {
val.forEach((it: any, index: number) => {
const profit = calcProfit(it)
if (items.value[index]) items.value[index].profit = isNaN(profit) ? 0 : Number(profit.toFixed(2))
})
const sum = (val || []).reduce((acc: number, cur: any) => {
const n = cur?.unit_price === undefined || cur?.unit_price === null || cur?.unit_price === '' ? 0 : Number(cur.unit_price)
return acc + (isNaN(n) ? 0 : n)
}, 0)
baseForm.total_cif = Number(sum.toFixed(2))
if (items.value.length > 0) {
const c = items.value[0]?.unit_price_currency
if (c) baseForm.total_cif_currency = c
}
},
{ deep: true }
)
const handleSubmit = async () => {
const mappedItems = items.value.map((it) => ({
sku_id: it.sku_id,
vin: it.vin,
model_snapshot: it.model_snapshot,
color_interior_code: it.color_interior_code,
color_interior_snapshot: it.color_interior_snapshot,
color_exterior_code: it.color_exterior_code,
color_exterior_snapshot: it.color_exterior_snapshot,
unit_price: wrapMoney(it.unit_price, (it as any).unit_price_currency, '单价'),
handling_fee: wrapMoney(it.handling_fee, (it as any).helper_fee_currency, '手续费'),
fob_transport_fee: wrapMoney(
it.fob_transport_fee ?? (it as any).fob_fee,
(it as any).fob_fee_currency,
'FOB运输费'
),
port_misc_fee: wrapMoney(
it.port_misc_fee ?? (it as any).port_fee,
(it as any).port_fee_currency,
'港杂费'
),
retrofit_fee: wrapMoney((it as any).retrofit_fee, (it as any).modify_fee_currency, '单价'),
modify_list: it.modify_list,
total_price: wrapMoney(it.total_price, (it as any).total_price_currency, '总价'),
official_price: wrapMoney(
(it as any).official_price ?? (it as any).guide_price,
(it as any).guide_price_currency,
'官方指导价'
),
discount_price: wrapMoney(
(it as any).discount_price ?? (it as any).discount,
(it as any).discount_currency,
'优惠额'
)
}))
const payload = {
...baseForm,
veh_list: mappedItems,
account_list: paymentItems.value
}
try {
const { message } = await CrmService.addStock(payload as any)
ElMessage.success(message || '保存成功')
// 使用window.location进行强制跳转
setTimeout(() => {
window.location.href = '/crmpc/stocking/stocking_list'
}, 1000)
} catch (e) {
ElMessage.error('保存失败')
}
}
// 选择客户弹框逻辑
const customerDialog = reactive({
visible: false,
search: { keyword: '' },
loading: false,
tableData: [] as any[],
pagination: { currentPage: 1, pageSize: 10, total: 0 }
})
const openCustomerDialog = () => {
customerDialog.visible = true
customerDialog.pagination.currentPage = 1
getCustomerList()
}
const getCustomerList = async () => {
customerDialog.loading = true
try {
const { currentPage, pageSize } = customerDialog.pagination
const params = { current: currentPage, size: pageSize, keyword: customerDialog.search.keyword }
const { data, current, size, total } = await CrmService.getcustomerList(params as any)
customerDialog.tableData = data || []
Object.assign(customerDialog.pagination, { currentPage: current, pageSize: size, total })
} catch (e) {
} finally {
customerDialog.loading = false
}
}
const handleCustomerSizeChange = (size: number) => {
customerDialog.pagination.pageSize = size
getCustomerList()
}
const handleCustomerCurrentChange = (page: number) => {
customerDialog.pagination.currentPage = page
getCustomerList()
}
const chooseCustomer = (row: any) => {
baseForm.customer_id = row.id || ''
baseForm.customer_snapshot = row.contacts || row.name || ''
customerDialog.visible = false
}
// 选择车型弹框逻辑
const vehicleDialog = reactive({
visible: false,
targetIndex: -1,
search: { keyword: '' },
loading: false,
tableData: [] as any[],
pagination: { currentPage: 1, pageSize: 10, total: 0 }
})
const openVehicleDialog = (index: number) => {
vehicleDialog.targetIndex = index
vehicleDialog.visible = true
vehicleDialog.pagination.currentPage = 1
getVehicleList()
}
const getVehicleList = async () => {
vehicleDialog.loading = true
try {
const { currentPage, pageSize } = vehicleDialog.pagination
const params = { current: currentPage, size: pageSize, keyword: vehicleDialog.search.keyword }
const { records, current, size, total } = await vehicleService.getVehicleList(params)
vehicleDialog.tableData = records || []
Object.assign(vehicleDialog.pagination, { currentPage: current, pageSize: size, total })
} catch {
ElMessage.error('获取车辆列表失败')
} finally {
vehicleDialog.loading = false
}
}
const handleVehicleSizeChange = (size: number) => {
vehicleDialog.pagination.pageSize = size
getVehicleList()
}
const handleVehicleCurrentChange = (page: number) => {
vehicleDialog.pagination.currentPage = page
getVehicleList()
}
const chooseVehicle = (row: any) => {
const idx = vehicleDialog.targetIndex
if (idx >= 0 && idx < items.value.length) {
const target = items.value[idx]
target.sku_id = row.id || ''
target.model_snapshot = row.model_name || row.name || ''
// 回显颜色与指导价
target.color_exterior_code = row.color_exterior_code || ''
target.color_exterior_snapshot = row.color_exterior_text || ''
target.color_interior_code = row.color_interior_code || ''
target.color_interior_snapshot = row.color_interior_text || ''
target.official_price = row.msrp ?? target.official_price
if (row.currency) {
target.guide_price_currency = row.currency
}
// 可按需带出更多字段回显
}
vehicleDialog.visible = false
}
</script>
<style scoped lang="scss">
:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
padding: 4px;
}
:deep(.el-select__selected-item span) {
font-size: 12px;
}
.form-title {
font-weight: bold;
color: #222;
margin-bottom: 15px;
}
.form-title .title {
margin-left: 6px;
}
.stocking-add-page {
// background: var(--art-main-bg-color);
min-height: 100vh;
}
.header {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 20px;
margin-bottom: 12px;
.title {
font-size: 18px;
font-weight: 600;
margin-bottom: 15px;
}
.subtitle {
color: var(--el-text-color-secondary);
}
}
.items-wrap {
margin-top: 6px;
}
.item-row {
padding: 8px 0;
}
.row-actions {
display: flex;
align-items: center;
gap: 8px;
justify-content: flex-end;
}
.row-actions span {
font-size: 12px;
cursor: pointer;
}
.row-actions .primary {
color: var(--el-color-primary);
cursor: pointer;
}
.row-actions .danger {
color: var(--el-color-danger);
cursor: pointer;
}
.submit-wrap {
display: flex;
justify-content: center;
padding: 24px 0 8px;
}
.submit-btn {
width: 220px;
height: 44px;
font-size: 16px;
}
.payment-item {
margin-bottom: 12px;
}
.upload-box { width: 200px; }
.file-preview {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
}
.file-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #f5f7fa;
border-radius: 4px;
border: 1px dashed #d9d9d9;
}
.file-icon i {
font-size: 32px;
color: #409eff;
margin-bottom: 8px;
}
.file-name {
font-size: 12px;
color: #606266;
text-align: center;
word-break: break-all;
padding: 0 8px;
}
.file-actions {
position: absolute;
top: 4px;
right: 4px;
opacity: 0;
transition: opacity 0.3s;
}
.file-preview:hover .file-actions {
opacity: 1;
}
.file-preview {
position: relative;
}
</style>