534 lines
14 KiB
Vue
534 lines
14 KiB
Vue
<template>
|
|
<!-- 客户类型选择弹窗 -->
|
|
<view v-if="showCustomerTypePicker" class="modal-mask" @click="closePicker('customerType')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择客户类型</view>
|
|
<view class="picker-options">
|
|
<view
|
|
v-for="item in customerTypeOptions"
|
|
:key="item.value"
|
|
class="picker-option"
|
|
:class="{ active: tempCustomerType === item.value }"
|
|
@click="selectCustomerType(item.value)"
|
|
>
|
|
<text>{{ item.label }}</text>
|
|
<text v-if="tempCustomerType === item.value" class="check">✓</text>
|
|
</view>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="closePicker('customerType')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmCustomerType">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 客户来源选择弹窗 -->
|
|
<view v-if="showSourcePicker" class="modal-mask" @click="closePicker('source')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择客户来源</view>
|
|
<view class="picker-options">
|
|
<view
|
|
v-for="item in sourceOptions"
|
|
:key="item.value"
|
|
class="picker-option"
|
|
:class="{ active: tempSource === item.label }"
|
|
@click="selectSource(item.label)"
|
|
>
|
|
<text>{{ item.label }}</text>
|
|
<text v-if="tempSource === item.label" class="check">✓</text>
|
|
</view>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="closePicker('source')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmSource">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 客户意向选择弹窗(多选) -->
|
|
<view v-if="showIntentPicker" class="modal-mask" @click="closePicker('intent')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择客户意向(可多选)</view>
|
|
<view class="picker-options">
|
|
<view
|
|
v-for="item in intentOptions"
|
|
:key="item.value"
|
|
class="picker-option"
|
|
:class="{ active: tempIntents.includes(item.label) }"
|
|
@click="toggleIntent(item.label)"
|
|
>
|
|
<text>{{ item.label }}</text>
|
|
<text v-if="tempIntents.includes(item.label)" class="check">✓</text>
|
|
</view>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="closePicker('intent')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmIntent">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 意向强度选择弹窗 -->
|
|
<view v-if="showIntentLevelPicker" class="modal-mask" @click="closePicker('intentLevel')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择意向强度</view>
|
|
<view class="picker-options">
|
|
<view
|
|
v-for="item in intentLevelOptions"
|
|
:key="item.value"
|
|
class="picker-option"
|
|
:class="{ active: tempIntentLevel === item.value }"
|
|
@click="selectIntentLevel(item.value)"
|
|
>
|
|
<text>{{ item.label }}</text>
|
|
<text v-if="tempIntentLevel === item.value" class="check">✓</text>
|
|
</view>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="closePicker('intentLevel')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmIntentLevel">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 客户状态选择弹窗 -->
|
|
<view v-if="showCustomerStatusPicker" class="modal-mask" @click="closePicker('customerStatus')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择客户状态</view>
|
|
<view class="picker-options">
|
|
<view
|
|
v-for="item in customerStatusOptions"
|
|
:key="item.value"
|
|
class="picker-option"
|
|
:class="{ active: tempCustomerStatus === item.value }"
|
|
@click="selectCustomerStatus(item.value)"
|
|
>
|
|
<text>{{ item.label }}</text>
|
|
<text v-if="tempCustomerStatus === item.value" class="check">✓</text>
|
|
</view>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="closePicker('customerStatus')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmCustomerStatus">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 客户地区选择器 - uv-picker -->
|
|
<uv-picker
|
|
ref="regionPicker"
|
|
:columns="addressList"
|
|
:loading="regionLoading"
|
|
keyName="name"
|
|
@confirm="onRegionConfirm"
|
|
@change="onRegionChange"
|
|
></uv-picker>
|
|
|
|
<!-- 微信好友选择弹窗 -->
|
|
<view v-if="showWorkWechatPicker" class="modal-mask" @click="closePicker('workWechat')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择微信好友</view>
|
|
<view class="picker-options">
|
|
<view
|
|
v-for="item in workWechatOptions"
|
|
:key="item.id"
|
|
class="picker-option"
|
|
:class="{ active: tempWorkWechat === item.value }"
|
|
@click="selectWorkWechat(item.value)"
|
|
>
|
|
<text>{{ item.label }}</text>
|
|
<text v-if="tempWorkWechat === item.value" class="check">✓</text>
|
|
</view>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="closePicker('workWechat')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmWorkWechat">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 下次跟进时间选择弹窗 -->
|
|
<view v-if="showNextFollowTimePicker" class="modal-mask" @click="closePicker('nextFollowTime')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择下次跟进时间</view>
|
|
<view class="datetime-picker-wrapper" @click.stop>
|
|
<picker
|
|
mode="date"
|
|
:value="tempNextFollowDate"
|
|
@change="onNextFollowDateChange"
|
|
>
|
|
<view class="picker-display">日期: {{ tempNextFollowDate || '请选择日期' }}</view>
|
|
</picker>
|
|
<picker
|
|
mode="time"
|
|
:value="tempNextFollowTime"
|
|
@change="onNextFollowTimeChange"
|
|
>
|
|
<view class="picker-display">时间: {{ tempNextFollowTime || '请选择时间' }}</view>
|
|
</picker>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="clearNextFollowTime">清除</button>
|
|
<button class="modal-btn" @click="closePicker('nextFollowTime')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmNextFollowTime">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 跟进人选择弹窗 -->
|
|
<view v-if="showFollowUserPicker" class="modal-mask" @click="closePicker('followUser')">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-title">选择跟进人</view>
|
|
<view class="picker-options">
|
|
<view
|
|
v-for="item in followUserOptions"
|
|
:key="item.userId"
|
|
class="picker-option"
|
|
:class="{ active: String(tempFollowUserId) === String(item.userId) }"
|
|
@click="selectFollowUser(item.userId)"
|
|
>
|
|
<text>{{ item.nickName }}</text>
|
|
<text v-if="String(tempFollowUserId) === String(item.userId)" class="check">✓</text>
|
|
</view>
|
|
</view>
|
|
<view class="modal-buttons">
|
|
<button class="modal-btn" @click="closePicker('followUser')">取消</button>
|
|
<button class="modal-btn primary" @click="confirmFollowUser">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch } from 'vue';
|
|
|
|
const props = defineProps({
|
|
showCustomerTypePicker: Boolean,
|
|
showSourcePicker: Boolean,
|
|
showIntentPicker: Boolean,
|
|
showIntentLevelPicker: Boolean,
|
|
showCustomerStatusPicker: Boolean,
|
|
showWorkWechatPicker: Boolean,
|
|
showNextFollowTimePicker: Boolean,
|
|
showFollowUserPicker: Boolean,
|
|
customerTypeOptions: Array,
|
|
sourceOptions: Array,
|
|
intentOptions: Array,
|
|
intentLevelOptions: Array,
|
|
customerStatusOptions: Array,
|
|
workWechatOptions: Array,
|
|
followUserOptions: Array,
|
|
addressList: Array,
|
|
regionLoading: Boolean,
|
|
formData: Object
|
|
});
|
|
|
|
const emit = defineEmits(['update:formData', 'close-picker', 'region-confirm', 'region-change']);
|
|
|
|
// 临时选择值
|
|
const tempCustomerType = ref('');
|
|
const tempSource = ref('');
|
|
const tempIntents = ref([]);
|
|
const tempIntentLevel = ref('');
|
|
const tempCustomerStatus = ref('');
|
|
const tempWorkWechat = ref('');
|
|
const tempNextFollowDate = ref('');
|
|
const tempNextFollowTime = ref('');
|
|
const tempFollowUserId = ref('');
|
|
|
|
const regionPicker = ref(null);
|
|
|
|
// 监听表单数据变化,更新临时值
|
|
watch(() => props.formData, (newData) => {
|
|
if (newData) {
|
|
tempCustomerType.value = newData.customerType || '';
|
|
tempSource.value = newData.source || '';
|
|
tempIntents.value = newData.intents ? [...newData.intents] : [];
|
|
tempIntentLevel.value = newData.intentLevel || '';
|
|
tempCustomerStatus.value = newData.customerStatus || '';
|
|
tempWorkWechat.value = newData.workWechatId || '';
|
|
tempFollowUserId.value = newData.followId || '';
|
|
if (newData.nextFollowTime) {
|
|
const [date, time] = newData.nextFollowTime.split(' ');
|
|
tempNextFollowDate.value = date || '';
|
|
tempNextFollowTime.value = time ? time.substring(0, 5) : '';
|
|
} else {
|
|
tempNextFollowDate.value = '';
|
|
tempNextFollowTime.value = '';
|
|
}
|
|
}
|
|
}, { immediate: true, deep: true });
|
|
|
|
const closePicker = (pickerType) => {
|
|
emit('close-picker', pickerType);
|
|
};
|
|
|
|
// 客户类型
|
|
const selectCustomerType = (value) => {
|
|
tempCustomerType.value = value;
|
|
};
|
|
|
|
const confirmCustomerType = () => {
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
customerType: tempCustomerType.value
|
|
});
|
|
closePicker('customerType');
|
|
};
|
|
|
|
// 客户来源
|
|
const selectSource = (label) => {
|
|
tempSource.value = label;
|
|
};
|
|
|
|
const confirmSource = () => {
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
source: tempSource.value
|
|
});
|
|
closePicker('source');
|
|
};
|
|
|
|
// 客户意向
|
|
const toggleIntent = (label) => {
|
|
const index = tempIntents.value.indexOf(label);
|
|
if (index > -1) {
|
|
tempIntents.value.splice(index, 1);
|
|
} else {
|
|
tempIntents.value.push(label);
|
|
}
|
|
};
|
|
|
|
const confirmIntent = () => {
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
intents: [...tempIntents.value]
|
|
});
|
|
closePicker('intent');
|
|
};
|
|
|
|
// 意向强度
|
|
const selectIntentLevel = (value) => {
|
|
tempIntentLevel.value = value;
|
|
};
|
|
|
|
const confirmIntentLevel = () => {
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
intentLevel: tempIntentLevel.value
|
|
});
|
|
closePicker('intentLevel');
|
|
};
|
|
|
|
// 客户状态
|
|
const selectCustomerStatus = (value) => {
|
|
tempCustomerStatus.value = value;
|
|
};
|
|
|
|
const confirmCustomerStatus = () => {
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
customerStatus: tempCustomerStatus.value
|
|
});
|
|
closePicker('customerStatus');
|
|
};
|
|
|
|
// 微信好友
|
|
const selectWorkWechat = (value) => {
|
|
tempWorkWechat.value = value;
|
|
};
|
|
|
|
const confirmWorkWechat = () => {
|
|
const selectedWechat = props.workWechatOptions.find(w => w.value === tempWorkWechat.value);
|
|
if (selectedWechat) {
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
workWechat: selectedWechat.label,
|
|
workWechatId: selectedWechat.value
|
|
});
|
|
}
|
|
closePicker('workWechat');
|
|
};
|
|
|
|
// 下次跟进时间
|
|
const onNextFollowDateChange = (e) => {
|
|
tempNextFollowDate.value = e.detail.value;
|
|
};
|
|
|
|
const onNextFollowTimeChange = (e) => {
|
|
tempNextFollowTime.value = e.detail.value;
|
|
};
|
|
|
|
const clearNextFollowTime = () => {
|
|
tempNextFollowDate.value = '';
|
|
tempNextFollowTime.value = '';
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
nextFollowTime: ''
|
|
});
|
|
closePicker('nextFollowTime');
|
|
};
|
|
|
|
const confirmNextFollowTime = () => {
|
|
let nextFollowTime = '';
|
|
if (tempNextFollowDate.value && tempNextFollowTime.value) {
|
|
nextFollowTime = `${tempNextFollowDate.value} ${tempNextFollowTime.value}:00`;
|
|
} else if (tempNextFollowDate.value) {
|
|
nextFollowTime = `${tempNextFollowDate.value} 09:00:00`;
|
|
}
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
nextFollowTime
|
|
});
|
|
closePicker('nextFollowTime');
|
|
};
|
|
|
|
// 跟进人
|
|
const selectFollowUser = (userId) => {
|
|
tempFollowUserId.value = String(userId);
|
|
};
|
|
|
|
const confirmFollowUser = () => {
|
|
const selectedUser = props.followUserOptions.find(u => String(u.userId) === String(tempFollowUserId.value));
|
|
if (selectedUser) {
|
|
emit('update:formData', {
|
|
...props.formData,
|
|
followId: String(selectedUser.userId),
|
|
followName: selectedUser.nickName
|
|
});
|
|
}
|
|
closePicker('followUser');
|
|
};
|
|
|
|
// 地区选择器
|
|
const onRegionChange = (e) => {
|
|
emit('region-change', e);
|
|
};
|
|
|
|
const onRegionConfirm = (e) => {
|
|
emit('region-confirm', e);
|
|
};
|
|
|
|
// 暴露方法给父组件
|
|
defineExpose({
|
|
openRegionPicker: () => {
|
|
if (regionPicker.value) {
|
|
regionPicker.value.open();
|
|
}
|
|
},
|
|
setRegionIndexs: (indexs) => {
|
|
if (regionPicker.value) {
|
|
regionPicker.value.setIndexs(indexs, true);
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.modal-mask {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: flex-end;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal-content {
|
|
width: 100%;
|
|
max-height: 70vh;
|
|
background-color: #fff;
|
|
border-radius: 16px 16px 0 0;
|
|
padding: 20px;
|
|
animation: slideUp 0.3s;
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from {
|
|
transform: translateY(100%);
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #333;
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.picker-options {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.picker-option {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px;
|
|
font-size: 15px;
|
|
color: #333;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
&.active {
|
|
color: #1976d2;
|
|
}
|
|
}
|
|
|
|
.check {
|
|
color: #1976d2;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.modal-buttons {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.modal-btn {
|
|
flex: 1;
|
|
height: 44px;
|
|
font-size: 15px;
|
|
border-radius: 6px;
|
|
border: 1px solid #e0e0e0;
|
|
background-color: #fff;
|
|
color: #666;
|
|
|
|
&.primary {
|
|
background-color: #1976d2;
|
|
color: #fff;
|
|
border-color: #1976d2;
|
|
}
|
|
}
|
|
|
|
.datetime-picker-wrapper {
|
|
padding: 20px 0;
|
|
|
|
.picker-display {
|
|
padding: 12px;
|
|
margin-bottom: 12px;
|
|
background-color: #f8f8f8;
|
|
border-radius: 6px;
|
|
font-size: 15px;
|
|
color: #333;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
pointer-events: auto;
|
|
user-select: none;
|
|
}
|
|
}
|
|
</style>
|
|
|