优化 支持多跟单员

新增 新增客户 联系邮箱字段
This commit is contained in:
袁锋 2025-12-06 22:11:20 +08:00
parent cb54921eaf
commit 25439fc0b1
11 changed files with 266 additions and 55 deletions

View File

@ -41,6 +41,12 @@ public class CustomerInfoVO implements Serializable {
* 联系人
*/
private String contacts;
/**
* 联系人邮箱
*/
private String contactEmails;
/**
* 联系人手机号
*/

View File

@ -42,6 +42,13 @@ public class CustomerRespVO {
@ExcelProperty("邮箱")
private String email;
/**
* 联系人 邮箱多个 ; 号连接
*/
@Schema(description = "联系邮箱")
@ExcelProperty("联系邮箱")
private String contactEmails;
@Schema(description = "联系人")
@ExcelProperty("联系人")
private String contacts;

View File

@ -30,6 +30,9 @@ public class CustomerSaveReqVO {
@Schema(description = "邮箱")
private String email;
@Schema(description = "联系邮箱 多个;连接")
private String contactEmails;
@Schema(description = "联系人")
private String contacts;

View File

@ -51,6 +51,10 @@ public class CustomerDO extends BaseDO {
* 邮箱
*/
private String email;
/**
* 联系人 邮箱多个 ; 号连接
*/
private String contactEmails;
/**
* 联系人
*/

View File

@ -80,6 +80,9 @@ public class CustomerServiceImpl implements CustomerService {
if(user!=null){
throw exception(CUSTOMER_EMAIL_EXISTS);
}
if(FuncUtil.isEmpty(createReqVO.getContactEmails())){
customer.setContactEmails(createReqVO.getEmail());
}
//新增用户账号
UserSaveReqVO userSaveReqVO = new UserSaveReqVO();
@ -116,7 +119,9 @@ public class CustomerServiceImpl implements CustomerService {
}else {
updateReqVO.setNumber(customerDO.getNumber());
}
if(FuncUtil.isEmpty(updateReqVO.getContactEmails())){
updateReqVO.setContactEmails(updateReqVO.getEmail());
}
// 更新
CustomerDO updateObj = BeanUtils.toBean(updateReqVO, CustomerDO.class);
customerMapper.updateById(updateObj);

View File

@ -17,6 +17,8 @@ export interface CustomerVO {
areaName?: string // 所在地名称
type: string // 类型
remarks: string // 备注
customerAddresss: string // 客户地址
contactEmails: string // 联系人 邮箱多个 ;号连接
}
// 客户 API

View File

@ -1,5 +1,5 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1500">
<Dialog :title="dialogTitle" v-model="dialogVisible" width="80vw">
<el-form
ref="formRef"
:model="formData"
@ -54,31 +54,80 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="跟单员" prop="gdperson">
<el-input v-model="formData.gdperson" placeholder="请输入跟单员" />
</el-form-item>
<el-form-item label="联系邮箱" prop="">
<div>
<div class="flex">
<el-input
ref="InputRef"
v-model="that.inputValue"
style="width: 100%; min-width: 340px"
placeholder="输入联系邮箱,按下回车或者点击添加"
@keyup.enter="handleInputConfirm(that.inputValue)"
autocomplete="email"
type="email"
name="email"
id="contact-email"
/>
<el-button @click="handleInputConfirm(that.inputValue)">添加</el-button>
</div>
<div style="padding: 4px">
<span :key="tag" style="padding-right: 4px" v-for="tag in that.inputContactEmails">
<el-tag
closable
:disable-transitions="false"
@close="handleClose(tag)"
>
{{ tag }}
</el-tag>
</span>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="销售员" prop="saleperson">
<el-input v-model="formData.saleperson" placeholder="请输入销售员" />
</el-form-item>
<el-form-item label="跟单员" prop="gdperson">
<el-input v-model="formData.gdperson" placeholder="请输入跟单员" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属地区" prop="areaId">
<el-cascader
v-model="formData.areaId"
:options="areaList"
:props="defaultProps"
class="w-1/1"
clearable
filterable
placeholder="请选择城市"
/>
</el-form-item>
<el-col :span="12">
<el-form-item label="销售员" prop="saleperson">
<el-input v-model="formData.saleperson" placeholder="请输入销售员" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="所属地区" prop="areaId">
<el-cascader
v-model="formData.areaId"
:options="areaList"
:props="defaultProps"
class="w-1/1"
clearable
filterable
placeholder="请选择城市"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据状态" prop="status">
<el-switch
v-model="formData.status"
:active-text="formData.status==1?'启用':'禁用'"
active-value="1"
inactive-value="0"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
@ -98,14 +147,7 @@
<el-input v-model="formData.invoiceAddress" placeholder="请输入发票地址" />
</el-form-item>
<el-form-item label="数据状态" prop="status">
<el-switch
v-model="formData.status"
:active-text="formData.status==1?'启用':'禁用'"
active-value="1"
inactive-value="0"
/>
</el-form-item>
<!-- <el-form-item label="类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择类型">
<el-option label="请选择字典生成" value="" />
@ -161,6 +203,7 @@ const formData = ref({
invoiceCode: undefined,
invoiceName: undefined,
invoiceAddress: undefined,
contactEmails: "",
})
const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
@ -191,7 +234,49 @@ const customerAddressFormRef = ref()
const inputCode = ref(false)
const that = reactive({
inputContactEmails: [],
inputValue: '',
})
const handleClose = async (tag:string)=>{
//
await message.delConfirm()
const tmp = `${tag}`.trim();
const index = that.inputContactEmails.indexOf(tmp);
if (index != -1) {
that.inputContactEmails.splice(index, 1);
}
}
function validateEmail(email) {
// RFC5322
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
//
if (typeof email !== 'string' || email.trim() === '') {
return false;
}
return emailRegex.test(email.trim());
}
const handleInputConfirm =(tag:string)=>{
const tmp = `${tag}`.trim();
if(tmp.length > 0){
if(!validateEmail(tmp)){
message.confirm("邮箱格式不正确");
return;
}
const index = that.inputContactEmails.indexOf(tmp);
if (index != -1) {
that.inputContactEmails.splice(index, 1);
//
that.inputContactEmails.unshift(tmp);
} else {
that.inputContactEmails.push(tmp);
}
message.success("添加成功")
that.inputValue = ''
}
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
@ -199,11 +284,19 @@ const open = async (type: string, id?: number) => {
formType.value = type
inputCode.value = false
resetForm()
that.inputValue = '';
that.inputContactEmails = []
//
if (id) {
formLoading.value = true
try {
formData.value = await CustomerApi.getCustomer(id)
if(formData.value.contactEmails && formData.value.contactEmails.length > 0){
//@ts-ignore
that.inputContactEmails = formData.value.contactEmails.split(";")
}
} finally {
formLoading.value = false
}
@ -232,6 +325,7 @@ const submitForm = async () => {
const data = formData.value as unknown as CustomerVO
//
data.customerAddresss = customerAddressFormRef.value.getData()
data.contactEmails = that.inputContactEmails.join(";")
if (formType.value === 'create') {
await CustomerApi.createCustomer(data)
message.success(t('common.createSuccess'))

View File

@ -693,7 +693,10 @@ export default {
"backHome": "BackHome",
"downloadSuccess": "downloadSuccess",
"downloadFile": "Download of working papers",
"labelAccessoryFile": "Attachment"
"labelAccessoryFile": "Attachment",
"tipsFollowerUserEmptyError": 'Please contact the business personnel to assign an order follower for this brand',
"cmnEmailLabel": 'Commonly Used Email',
"tipsDelEmail": 'Keep at least one contact email'
},
"productDialogList": {
"title": "Product List",

View File

@ -689,7 +689,10 @@ export default {
backHome: "返回首页",
downloadSuccess: "下载成功",
downloadFile: "底稿下载",
labelAccessoryFile: "附件"
labelAccessoryFile: "附件",
tipsFollowerUserEmptyError: '请联系业务人员,设置该品牌的跟单员',
cmnEmailLabel: '常用邮箱',
tipsDelEmail: '至少保留一个联系邮箱'
},
productDialogList:{
title: '产品列表',

View File

@ -199,8 +199,10 @@
:label="t('createOrder.labelOrderFollowerUser')"
prop="orderFollowerUser">
<el-select
v-model="formData.orderFollowerUser"
v-model="that.editOrderFollowerUsers"
filterable
multiple
@change="changeSelectUser"
:placeholder="t('createOrder.placeOrderFollowerUser')">
<el-option
v-for="user in that.followerUserList"
@ -229,7 +231,7 @@
<AccessoryUpload
:fileType="['zip', 'rar','xls', 'doc', 'xlsx', 'docx','pdf','png','jpg', 'jpeg']"
:limit="10"
v-model="formData.accessoryFileIds" />
v-model="formData.accessoryFileIds"/>
</el-form-item>
</el-col>
@ -263,8 +265,22 @@
</div>
</el-col>
</el-row>
<el-form-item label="" prop="" v-if="that.commonUseList && that.commonUseList.length > 0">
<div style="max-width: 80%">
{{ t('createOrder.cmnEmailLabel') }}: <span
v-for="(item, index) in that.commonUseList"
:key="index"
style="padding: 4px; cursor: pointer"
@click="clickUse(item)">
<el-tag :type=" that.tmpFormData.emailList.includes(item) ? 'primary' : 'info'">
<span>{{ item }}</span>
</el-tag>
</span>
</div>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="" prop="">
<div style="border: 1px solid #dcdfe6;width:100%;height: 200px;">
@ -276,13 +292,20 @@
:class="{'selected': selectedIndex === index}"
style="list-style-type: none"
@click="selectItem(index)">
{{ item }}
<span style="display: flex">
<span>{{ item }}</span>
<span @click="onDelEmail" class="del-email-icon"
v-if="that.tmpFormData.emailList.length > 1">
<Icon icon="ep:delete"/>
</span>
</span>
</li>
</ul>
</el-scrollbar>
</div>
</el-form-item>
</el-col>
</el-row>
</el-card>
@ -443,7 +466,7 @@
style="margin-left: 10px;"
type="primary"
@click="backStep()">
{{t('createOrder.btnPrevStep')}}
{{ t('createOrder.btnPrevStep') }}
</el-button>
</div>
<div v-if="hasLastIndex">
@ -476,6 +499,7 @@ import {STEP0_FINISH_EVENT, UPDATE_ADDRESS_EVENT} from "@/constants/EmitEventNam
import {formatDate} from "@/utils/formatTime";
import {ElLoading} from "element-plus";
import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
import {useMessage} from "@/hooks/web/useMessage";
const {t} = useI18n() //
@ -518,6 +542,7 @@ const that = reactive({
pageLoading: {},
addressList: [],
currencyList: [], //
editOrderFollowerUsers: [], //
addressId: null,
tmpFormData: {
inputEmail: '',
@ -525,6 +550,7 @@ const that = reactive({
planDate: calculateDateAfterDays(7),
},
followerUserList: [],
commonUseList: [],
subListSize: 0,
})
const formData = ref({
@ -562,7 +588,11 @@ const isEditState = computed(() => {
})
const formRules = reactive({
contactName: [{required: true, message: t('createOrder.ruleMsgContactName'), trigger: 'blur'}],
orderFollowerUser: [{required: true, message: t('createOrder.placeOrderFollowerUser'), trigger: 'blur'}],
orderFollowerUser: [{
required: true,
message: t('createOrder.placeOrderFollowerUser'),
trigger: 'blur'
}],
brandId: [{required: true, message: t('createOrder.ruleMsgBrandId'), trigger: 'blur'}],
contractCode: [{required: true, message: t('createOrder.ruleMsgContractCode'), trigger: 'blur'}],
// phone: [{required: true, message: t('createOrder.ruleMsgPhone'), trigger: 'blur'},
@ -575,8 +605,24 @@ const formRules = reactive({
})
const activeNames = ref(['1', '2', '3'])
function validateEmail(email) {
//
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
//
if (typeof email !== 'string' || email.trim() === '') {
return false;
}
return emailRegex.test(email.trim());
}
const changeSelectUser= ()=>{
formData.value.orderFollowerUser = that.editOrderFollowerUsers.join(";");
}
const pushEmail = (input: string) => {
const tmp = input.trim();
if(!validateEmail(tmp)){
message.confirm("邮箱格式不正确");
return;
}
const index = that.tmpFormData.emailList.indexOf(tmp);
if (index != -1) {
that.tmpFormData.emailList.splice(index, 1);
@ -586,6 +632,14 @@ const pushEmail = (input: string) => {
that.tmpFormData.emailList.push(tmp);
}
}
const clickUse = (item: string) => {
const index = that.tmpFormData.emailList.indexOf(item);
if(index != -1){
delEmail(index);
return;
}
pushEmail(item);
}
const onAddEmail = () => {
if (!that.tmpFormData.inputEmail || that.tmpFormData.inputEmail.trim() === '') {
useMessage().warning(t('createOrder.ruleMsgEmail'));
@ -594,10 +648,18 @@ const onAddEmail = () => {
pushEmail(that.tmpFormData.inputEmail);
that.tmpFormData.inputEmail = ''; //
}
const delEmail = (index) =>{
if(that.tmpFormData.emailList.length <= 1){
useMessage().confirm(t("createOrder.tipsDelEmail"))
return;
}
if(index >= 0 && index < that.tmpFormData.emailList.length){
that.tmpFormData.emailList.splice(index, 1);
}
}
const onDelEmail = () => {
if (selectedIndex.value >= 0) {
that.tmpFormData.emailList.splice(selectedIndex.value, 1); //
delEmail(selectedIndex.value)
//
if (selectedIndex.value === selectedIndex.value) {
selectedIndex.value = -1;
@ -678,7 +740,7 @@ const submitPreHandler = (showMsg = true) => {
tableData.forEach((item, index) => {
if (!item.productSkuList || item.productSkuList.length === 0) {
const msg = `${ t("createOrder.tipsSkuError1")} "${item.productName}" ${index + 1} ${t("createOrder.tipsSkuError2")}`;
const msg = `${t("createOrder.tipsSkuError1")} "${item.productName}" ${index + 1} ${t("createOrder.tipsSkuError2")}`;
if (showMsg) {
useMessage().warning(msg)
}
@ -686,7 +748,7 @@ const submitPreHandler = (showMsg = true) => {
return;
}
item.productSkuList.forEach((item2, i2) => {
console.log("item2",item2)
console.log("item2", item2)
if (item2.productTemplateType === '1' && !item2.previewImage) {
const t = `${t("createOrder.tipsSkuError3")} ${index + 1} ${item.productName}SKU ${i2 + 1}`;
if (showMsg) {
@ -711,7 +773,7 @@ const addNewBill = () => {
formData.value.type = queryParams.type
console.log("formData222", formData.value)
debugger;
if (formData.value.id && formData.value.type==undefined) {
if (formData.value.id && formData.value.type == undefined) {
SaleOrderApi.editOrder(formData.value.id, {
...formData.value
}).then(res => {
@ -720,15 +782,15 @@ const addNewBill = () => {
// text: t('createOrder.tipsLoadingEditing'),
// background: 'rgba(0,0,0,0.5)'
// })
useMessage().confirm(t('createOrder.tipsLoadingEditing'),{
useMessage().confirm(t('createOrder.tipsLoadingEditing'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.close'),
type: 'success',
center: true,
}).then(res=>{
if(res){
}).then(res => {
if (res) {
push("/")
}else {
} else {
push("/order/sale-order")
}
}).catch(e => {
@ -743,15 +805,15 @@ const addNewBill = () => {
SaleOrderApi.placeOrder({
...formData.value
}).then(res => {
useMessage().confirm(t('createOrder.tipsLoadingOrderCompleted'),{
useMessage().confirm(t('createOrder.tipsLoadingOrderCompleted'), {
confirmButtonText: t('createOrder.backHome'),
cancelButtonText: t('createOrder.viewOrder'),
type: 'success',
center: true,
}).then(res=>{
if(res){
}).then(res => {
if (res) {
push("/")
}else {
} else {
push("/order/sale-order")
}
}).catch(e => {
@ -798,9 +860,12 @@ const brandList = ref<any[]>([]) // 品牌列表
watch(() => formData.value.brandId, async (val) => {
if (val) {
const data = await SaleOrderApi.getFollowerUser(val)
that.followerUserList = data
that.followerUserList = data;
if (that.followerUserList.length > 0 && !formData.value.orderFollowerUser) {
formData.value.orderFollowerUser = `${that.followerUserList[0].id}`
that.editOrderFollowerUsers = [`${that.followerUserList[0].id}`]
changeSelectUser()
}else if(that.followerUserList.length == 0 ){
await useMessage().confirm(t("createOrder.tipsFollowerUserEmptyError"))
}
}
})
@ -820,7 +885,8 @@ onMounted(async () => {
formData.value.brandId = brandList.value[0].id
}
})
const customerData = await CustomerApi.getCustomerInfo()
const customerData = await CustomerApi.getCustomerInfo();
that.commonUseList = []
if (customerData) {
that.addressList = customerData.addressList;
formData.value.customerId = customerData.id;
@ -828,8 +894,16 @@ onMounted(async () => {
formData.value.company = customerData.company;
formData.value.contactName = customerData.contacts;
formData.value.phone = customerData.phone;
if (customerData.email) {
if(customerData.contactEmails && `${customerData.contactEmails}`.length > 0){
const arr = `${customerData.contactEmails}`.split(";");
arr.forEach(e =>{
pushEmail(e);
that.commonUseList.push(e)
})
}else if (customerData.email) {
pushEmail(customerData.email);
that.commonUseList.push(customerData.email)
}
formData.value.invoiceCode = customerData.invoiceCode;
formData.value.invoiceName = customerData.invoiceName;
@ -841,7 +915,7 @@ onMounted(async () => {
}
//
if (queryParams.id) {
const res = await SaleOrderApi.queryEditById(queryParams.id,queryParams.type);
const res = await SaleOrderApi.queryEditById(queryParams.id, queryParams.type);
formData.value = res
formData.value.bizdate = formatDate(new Date(res.bizdate), 'YYYY-MM-DD hh:mm:ss');
that.tmpFormData.planDate = formatDate(new Date(res.plansenddate), 'YYYY-MM-DD');
@ -850,7 +924,7 @@ onMounted(async () => {
...formData.value,
id: queryParams.id
}
if(copyData){ //
if (copyData) { //
formData.value.contractCode = null;
formData.value.bizdate = null;
formData.value.id = '';
@ -858,7 +932,7 @@ onMounted(async () => {
that.tmpFormData.planDate = calculateDateAfterDays(7);
}
console.log("res.saleOrderEntry",res.saleOrderEntry)
console.log("res.saleOrderEntry", res.saleOrderEntry)
stepRef.value.init(res.saleOrderEntry ?? []);
}
@ -921,4 +995,11 @@ const resFrom = (init = {}) => {
.selected {
background-color: #dddee0; /* 浅绿色,可以根据需要调整 */
}
.del-email-icon {
padding-left: 10px;
padding-top: 4px;
color: #ff0000;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,3 @@
-- 客户添加 联系邮箱字段
ALTER TABLE oms_customer
ADD COLUMN contact_emails VARCHAR(4000) NULL COMMENT '联系邮箱,多个使用;拼接';