OfficeSystem/components/customer-form/CustomerPickers.vue
2025-11-10 09:07:13 +08:00

490 lines
13 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>
</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,
customerTypeOptions: Array,
sourceOptions: Array,
intentOptions: Array,
intentLevelOptions: Array,
customerStatusOptions: Array,
workWechatOptions: 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 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 || '';
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 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>