2025-11-07 11:09:30 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="add-customer-page">
|
|
|
|
|
|
<!-- 自定义导航栏 -->
|
|
|
|
|
|
<view class="custom-navbar">
|
|
|
|
|
|
<view class="navbar-content">
|
|
|
|
|
|
<text class="nav-btn" @click="handleCancel">‹</text>
|
|
|
|
|
|
<text class="nav-title">客户信息</text>
|
|
|
|
|
|
<text class="nav-btn" style="opacity: 0;">占位</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 内容区域 -->
|
|
|
|
|
|
<scroll-view class="content-scroll" scroll-y>
|
|
|
|
|
|
<view class="scroll-content">
|
|
|
|
|
|
<!-- 客户信息部分 -->
|
|
|
|
|
|
<view class="form-section">
|
|
|
|
|
|
<view class="section-title">客户信息</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户类型 -->
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<view class="form-item clickable-item" @click="openCustomerTypePicker">
|
|
|
|
|
|
<text v-if="formData.customerType" class="form-value">{{ getCustomerTypeText(formData.customerType) }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择客户类型</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
2025-11-07 11:09:30 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户名称 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="formData.name"
|
|
|
|
|
|
class="form-input"
|
|
|
|
|
|
placeholder="输入客户名称"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 联系电话 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="formData.mobile"
|
|
|
|
|
|
class="form-input"
|
|
|
|
|
|
placeholder="输入电话号码"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 微信号 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="formData.wechat"
|
|
|
|
|
|
class="form-input"
|
|
|
|
|
|
placeholder="输入微信号"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户来源 -->
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<view class="form-item clickable-item" @click="openSourcePicker">
|
|
|
|
|
|
<text v-if="formData.source" class="form-value">{{ formData.source }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择客户来源</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
2025-11-07 11:09:30 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户意向 -->
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<view class="form-item clickable-item" @click="openIntentPicker">
|
|
|
|
|
|
<text v-if="formData.intents && formData.intents.length > 0" class="form-value">{{ formData.intents.join('、') }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择客户意向(可多选)</text>
|
2025-11-07 11:09:30 +08:00
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 意向强度 -->
|
|
|
|
|
|
<view class="form-item clickable-item" @click="openIntentLevelPicker">
|
|
|
|
|
|
<text v-if="formData.intentLevel" class="form-value">{{ getIntentLevelText(formData.intentLevel) }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择意向强度</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<!-- 客户状态 -->
|
|
|
|
|
|
<view class="form-item clickable-item" @click="openCustomerStatusPicker">
|
|
|
|
|
|
<text v-if="formData.customerStatus" class="form-value">{{ getCustomerStatusText(formData.customerStatus) }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择客户状态</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-11-07 11:09:30 +08:00
|
|
|
|
<!-- 客户地区 -->
|
|
|
|
|
|
<view class="form-item clickable-item" @click="openRegionPicker" @tap="openRegionPicker">
|
|
|
|
|
|
<text v-if="formData.region" class="form-value">{{ formData.region }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择客户地区</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 工作微信 -->
|
|
|
|
|
|
<view class="form-item clickable-item" @click="openWorkWechatPicker">
|
|
|
|
|
|
<text v-if="formData.workWechat" class="form-value">{{ formData.workWechat }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择工作微信</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 其他信息部分 -->
|
|
|
|
|
|
<view class="form-section">
|
|
|
|
|
|
<view class="section-title">其他信息</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户星级 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<text class="form-label">客户星级</text>
|
|
|
|
|
|
<view class="star-rating">
|
|
|
|
|
|
<text
|
|
|
|
|
|
class="star"
|
|
|
|
|
|
v-for="i in 5"
|
|
|
|
|
|
:key="i"
|
|
|
|
|
|
:class="{ 'filled': i <= formData.rating }"
|
|
|
|
|
|
@click="formData.rating = i"
|
|
|
|
|
|
>★</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 备注 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="formData.remark"
|
|
|
|
|
|
class="form-textarea"
|
|
|
|
|
|
placeholder="输入备注"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
auto-height
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 顾虑点 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="formData.concern"
|
|
|
|
|
|
class="form-textarea"
|
|
|
|
|
|
placeholder="输入顾虑点"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
auto-height
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 痛点 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="formData.pain"
|
|
|
|
|
|
class="form-textarea"
|
|
|
|
|
|
placeholder="输入痛点"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
auto-height
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 关注点 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="formData.attention"
|
|
|
|
|
|
class="form-textarea"
|
|
|
|
|
|
placeholder="输入关注点"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
auto-height
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 需求点 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="formData.demand"
|
|
|
|
|
|
class="form-textarea"
|
|
|
|
|
|
placeholder="输入需求点"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
auto-height
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 下次跟进时间 -->
|
|
|
|
|
|
<view class="form-item clickable-item" @click="openNextFollowTimePicker">
|
|
|
|
|
|
<text v-if="formData.nextFollowTime" class="form-value">{{ formData.nextFollowTime }}</text>
|
|
|
|
|
|
<text v-else class="form-placeholder">选择下次跟进时间(可选)</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 保存按钮 -->
|
|
|
|
|
|
<view class="save-button-wrapper">
|
|
|
|
|
|
<button class="save-button" @click="handleSave" :disabled="saving">保存</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<!-- 客户类型选择弹窗 -->
|
|
|
|
|
|
<view v-if="showCustomerTypePicker" class="modal-mask" @click="showCustomerTypePicker = false">
|
2025-11-07 11:09:30 +08:00
|
|
|
|
<view class="modal-content" @click.stop>
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<view class="modal-title">选择客户类型</view>
|
2025-11-07 11:09:30 +08:00
|
|
|
|
<view class="picker-options">
|
|
|
|
|
|
<view
|
2025-11-07 14:56:40 +08:00
|
|
|
|
v-for="item in customerTypeOptions"
|
2025-11-07 11:09:30 +08:00
|
|
|
|
:key="item.value"
|
|
|
|
|
|
class="picker-option"
|
2025-11-07 14:56:40 +08:00
|
|
|
|
:class="{ active: tempCustomerType === item.value }"
|
|
|
|
|
|
@click="selectCustomerType(item.value)"
|
2025-11-07 11:09:30 +08:00
|
|
|
|
>
|
|
|
|
|
|
<text>{{ item.label }}</text>
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<text v-if="tempCustomerType === item.value" class="check">✓</text>
|
2025-11-07 11:09:30 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="modal-buttons">
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<button class="modal-btn" @click="showCustomerTypePicker = false">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="confirmCustomerType">确定</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户来源选择弹窗 -->
|
|
|
|
|
|
<view v-if="showSourcePicker" class="modal-mask" @click="showSourcePicker = false">
|
|
|
|
|
|
<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="showSourcePicker = false">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="confirmSource">确定</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户意向选择弹窗(多选) -->
|
|
|
|
|
|
<view v-if="showIntentPicker" class="modal-mask" @click="showIntentPicker = false">
|
|
|
|
|
|
<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="showIntentPicker = false">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="confirmIntent">确定</button>
|
2025-11-07 11:09:30 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 意向强度选择弹窗 -->
|
|
|
|
|
|
<view v-if="showIntentLevelPicker" class="modal-mask" @click="showIntentLevelPicker = false">
|
|
|
|
|
|
<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="showIntentLevelPicker = false">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="confirmIntentLevel">确定</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
<!-- 客户状态选择弹窗 -->
|
|
|
|
|
|
<view v-if="showCustomerStatusPicker" class="modal-mask" @click="showCustomerStatusPicker = false">
|
|
|
|
|
|
<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="showCustomerStatusPicker = false">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="confirmCustomerStatus">确定</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-11-07 11:09:30 +08:00
|
|
|
|
<!-- 客户地区选择器 - 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="showWorkWechatPicker = false">
|
|
|
|
|
|
<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="showWorkWechatPicker = false">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="confirmWorkWechat">确定</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 下次跟进时间选择弹窗 -->
|
|
|
|
|
|
<view v-if="showNextFollowTimePicker" class="modal-mask" @click="showNextFollowTimePicker = false">
|
|
|
|
|
|
<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="showNextFollowTimePicker = false">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="confirmNextFollowTime">确定</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue';
|
2025-11-07 14:56:40 +08:00
|
|
|
|
import {
|
|
|
|
|
|
createCustomer,
|
|
|
|
|
|
getRegionTree,
|
|
|
|
|
|
getCustomerIntentDict,
|
|
|
|
|
|
getCustomerIntentLevelDict,
|
|
|
|
|
|
getCustomerSourceDict,
|
|
|
|
|
|
getCustomerTypeDict,
|
|
|
|
|
|
getCustomerStatusDict
|
|
|
|
|
|
} from '@/common/api';
|
2025-11-07 11:09:30 +08:00
|
|
|
|
import { useUserStore } from '@/store/user';
|
|
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
const formData = ref({
|
2025-11-07 14:56:40 +08:00
|
|
|
|
customerType: '', // 客户类型(dictValue)
|
2025-11-07 11:09:30 +08:00
|
|
|
|
name: '', // 客户名称
|
|
|
|
|
|
mobile: '', // 联系电话
|
|
|
|
|
|
wechat: '', // 微信号
|
2025-11-07 14:56:40 +08:00
|
|
|
|
source: '', // 客户来源(dictLabel)
|
|
|
|
|
|
intents: [], // 客户意向(数组,dictLabel)
|
|
|
|
|
|
intentLevel: '', // 意向强度(dictValue)
|
|
|
|
|
|
customerStatus: '', // 客户状态(dictValue)
|
2025-11-07 11:09:30 +08:00
|
|
|
|
region: '', // 客户地区(显示名称,如:北京/北京/朝阳)
|
|
|
|
|
|
regionIds: [], // 客户地区ID数组 [省ID, 市ID, 区ID]
|
|
|
|
|
|
workWechat: '', // 工作微信(显示名称)
|
|
|
|
|
|
workWechatId: null, // 工作微信ID
|
|
|
|
|
|
rating: 0, // 客户星级(0-5)
|
|
|
|
|
|
remark: '', // 备注
|
|
|
|
|
|
concern: '', // 顾虑点
|
|
|
|
|
|
pain: '', // 痛点
|
|
|
|
|
|
attention: '', // 关注点
|
|
|
|
|
|
demand: '', // 需求点
|
|
|
|
|
|
nextFollowTime: '' // 下次跟进时间
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 显示状态
|
|
|
|
|
|
const saving = ref(false);
|
2025-11-07 14:56:40 +08:00
|
|
|
|
const showCustomerTypePicker = ref(false);
|
|
|
|
|
|
const showSourcePicker = ref(false);
|
|
|
|
|
|
const showIntentPicker = ref(false);
|
2025-11-07 11:09:30 +08:00
|
|
|
|
const showIntentLevelPicker = ref(false);
|
2025-11-07 14:56:40 +08:00
|
|
|
|
const showCustomerStatusPicker = ref(false);
|
2025-11-07 11:09:30 +08:00
|
|
|
|
const showWorkWechatPicker = ref(false);
|
|
|
|
|
|
const showNextFollowTimePicker = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 临时选择值
|
2025-11-07 14:56:40 +08:00
|
|
|
|
const tempCustomerType = ref('');
|
|
|
|
|
|
const tempSource = ref('');
|
|
|
|
|
|
const tempIntents = ref([]);
|
2025-11-07 11:09:30 +08:00
|
|
|
|
const tempIntentLevel = ref('');
|
2025-11-07 14:56:40 +08:00
|
|
|
|
const tempCustomerStatus = ref('');
|
2025-11-07 11:09:30 +08:00
|
|
|
|
const tempWorkWechat = ref('');
|
|
|
|
|
|
const tempNextFollowDate = ref('');
|
|
|
|
|
|
const tempNextFollowTime = ref('');
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 字典数据
|
|
|
|
|
|
const customerTypeOptions = ref([]); // 客户类型选项
|
|
|
|
|
|
const sourceOptions = ref([]); // 客户来源选项
|
|
|
|
|
|
const intentOptions = ref([]); // 客户意向选项
|
|
|
|
|
|
const intentLevelOptions = ref([]); // 意向强度选项
|
|
|
|
|
|
const customerStatusOptions = ref([]); // 客户状态选项
|
|
|
|
|
|
|
2025-11-07 11:09:30 +08:00
|
|
|
|
// 地区树数据
|
|
|
|
|
|
const regionTree = ref([]);
|
|
|
|
|
|
// uv-picker 的列数据
|
|
|
|
|
|
const provinces = ref([]); // 省
|
|
|
|
|
|
const citys = ref([]); // 市
|
|
|
|
|
|
const areas = ref([]); // 区
|
|
|
|
|
|
const pickerValue = ref([0, 0, 0]); // 当前选中的索引
|
|
|
|
|
|
const regionLoading = ref(true); // 加载状态
|
|
|
|
|
|
// uv-picker 的引用
|
|
|
|
|
|
const regionPicker = ref(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性:返回地址列表
|
|
|
|
|
|
const addressList = computed(() => {
|
|
|
|
|
|
return [provinces.value, citys.value, areas.value];
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 加载字典数据
|
|
|
|
|
|
const loadDictData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 并行加载所有字典数据
|
|
|
|
|
|
const [typeRes, sourceRes, intentRes, intentLevelRes, statusRes] = await Promise.all([
|
|
|
|
|
|
getCustomerTypeDict(),
|
|
|
|
|
|
getCustomerSourceDict(),
|
|
|
|
|
|
getCustomerIntentDict(),
|
|
|
|
|
|
getCustomerIntentLevelDict(),
|
|
|
|
|
|
getCustomerStatusDict()
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 处理客户类型数据
|
|
|
|
|
|
customerTypeOptions.value = (typeRes || []).map(item => ({
|
|
|
|
|
|
label: item.dictLabel,
|
|
|
|
|
|
value: item.dictValue
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 处理客户来源数据
|
|
|
|
|
|
sourceOptions.value = (sourceRes || []).map(item => ({
|
|
|
|
|
|
label: item.dictLabel,
|
|
|
|
|
|
value: item.dictValue
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 处理客户意向数据
|
|
|
|
|
|
intentOptions.value = (intentRes || []).map(item => ({
|
|
|
|
|
|
label: item.dictLabel,
|
|
|
|
|
|
value: item.dictValue
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 处理意向强度数据
|
|
|
|
|
|
intentLevelOptions.value = (intentLevelRes || []).map(item => ({
|
|
|
|
|
|
label: item.dictLabel,
|
|
|
|
|
|
value: item.dictValue
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 处理客户状态数据
|
|
|
|
|
|
customerStatusOptions.value = (statusRes || []).map(item => ({
|
|
|
|
|
|
label: item.dictLabel,
|
|
|
|
|
|
value: item.dictValue
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
console.log('字典数据加载成功:', {
|
|
|
|
|
|
customerType: customerTypeOptions.value,
|
|
|
|
|
|
source: sourceOptions.value,
|
|
|
|
|
|
intent: intentOptions.value,
|
|
|
|
|
|
intentLevel: intentLevelOptions.value,
|
|
|
|
|
|
customerStatus: customerStatusOptions.value
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('加载字典数据失败:', err);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '加载字典数据失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-11-07 11:09:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载地区树数据
|
|
|
|
|
|
const loadRegionTree = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getRegionTree();
|
|
|
|
|
|
console.log('获取的地区数据:', res);
|
|
|
|
|
|
|
|
|
|
|
|
regionTree.value = res;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化省份数据
|
|
|
|
|
|
provinces.value = res.sort((left, right) => (Number(left.code || left.id) > Number(right.code || right.id) ? 1 : -1));
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有已选择的地区,设置默认值
|
|
|
|
|
|
if (formData.value.regionIds && formData.value.regionIds.length > 0) {
|
|
|
|
|
|
handlePickValueDefault();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 初始化默认显示第一个省份的城市和区县
|
|
|
|
|
|
if (provinces.value.length > 0) {
|
|
|
|
|
|
citys.value = provinces.value[0]?.children || [];
|
|
|
|
|
|
if (citys.value.length > 0) {
|
|
|
|
|
|
areas.value = citys.value[0]?.children || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
regionLoading.value = false;
|
|
|
|
|
|
}, 200);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('加载地区树失败:', err);
|
|
|
|
|
|
regionLoading.value = false;
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '加载地区数据失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 根据已选择的ID设置默认选中项
|
|
|
|
|
|
const handlePickValueDefault = () => {
|
|
|
|
|
|
if (!formData.value.regionIds || formData.value.regionIds.length === 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const [provinceId, cityId, districtId] = formData.value.regionIds;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置省
|
|
|
|
|
|
const provinceIndex = provinces.value.findIndex(item => Number(item.id) === Number(provinceId));
|
|
|
|
|
|
if (provinceIndex >= 0) {
|
|
|
|
|
|
pickerValue.value[0] = provinceIndex;
|
|
|
|
|
|
// 设置市
|
|
|
|
|
|
citys.value = provinces.value[provinceIndex]?.children || [];
|
|
|
|
|
|
const cityIndex = citys.value.findIndex(item => Number(item.id) === Number(cityId));
|
|
|
|
|
|
if (cityIndex >= 0) {
|
|
|
|
|
|
pickerValue.value[1] = cityIndex;
|
|
|
|
|
|
// 设置区
|
|
|
|
|
|
areas.value = citys.value[cityIndex]?.children || [];
|
|
|
|
|
|
const districtIndex = areas.value.findIndex(item => Number(item.id) === Number(districtId));
|
|
|
|
|
|
if (districtIndex >= 0) {
|
|
|
|
|
|
pickerValue.value[2] = districtIndex;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置选择器位置
|
|
|
|
|
|
if (regionPicker.value) {
|
|
|
|
|
|
regionPicker.value.setIndexs([pickerValue.value[0], pickerValue.value[1], pickerValue.value[2]], true);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 工作微信选项(示例数据,实际应从API获取,包含ID和名称)
|
|
|
|
|
|
const workWechatOptions = [
|
|
|
|
|
|
{ label: '工作微信1', value: '工作微信1', id: '1' },
|
|
|
|
|
|
{ label: '工作微信2', value: '工作微信2', id: '2' },
|
|
|
|
|
|
{ label: '工作微信3', value: '工作微信3', id: '3' },
|
|
|
|
|
|
{ label: '工作微信4', value: '工作微信4', id: '4' }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 获取客户类型文本
|
|
|
|
|
|
const getCustomerTypeText = (value) => {
|
|
|
|
|
|
const option = customerTypeOptions.value.find(opt => opt.value === value);
|
2025-11-07 11:09:30 +08:00
|
|
|
|
return option ? option.label : '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取意向强度文本
|
2025-11-07 14:56:40 +08:00
|
|
|
|
const getIntentLevelText = (value) => {
|
|
|
|
|
|
const option = intentLevelOptions.value.find(opt => opt.value === value);
|
|
|
|
|
|
return option ? option.label : '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取客户状态文本
|
|
|
|
|
|
const getCustomerStatusText = (value) => {
|
|
|
|
|
|
const option = customerStatusOptions.value.find(opt => opt.value === value);
|
|
|
|
|
|
return option ? option.label : '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开客户类型选择器
|
|
|
|
|
|
const openCustomerTypePicker = () => {
|
|
|
|
|
|
tempCustomerType.value = formData.value.customerType;
|
|
|
|
|
|
showCustomerTypePicker.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择客户类型
|
|
|
|
|
|
const selectCustomerType = (value) => {
|
|
|
|
|
|
tempCustomerType.value = value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认客户类型
|
|
|
|
|
|
const confirmCustomerType = () => {
|
|
|
|
|
|
formData.value.customerType = tempCustomerType.value;
|
|
|
|
|
|
showCustomerTypePicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开客户来源选择器
|
|
|
|
|
|
const openSourcePicker = () => {
|
|
|
|
|
|
tempSource.value = formData.value.source;
|
|
|
|
|
|
showSourcePicker.value = true;
|
2025-11-07 11:09:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 选择客户来源
|
|
|
|
|
|
const selectSource = (label) => {
|
|
|
|
|
|
tempSource.value = label;
|
2025-11-07 11:09:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 确认客户来源
|
|
|
|
|
|
const confirmSource = () => {
|
|
|
|
|
|
formData.value.source = tempSource.value;
|
|
|
|
|
|
showSourcePicker.value = false;
|
2025-11-07 11:09:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 打开客户意向选择器
|
|
|
|
|
|
const openIntentPicker = () => {
|
|
|
|
|
|
tempIntents.value = [...formData.value.intents];
|
|
|
|
|
|
showIntentPicker.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 切换客户意向(多选)
|
|
|
|
|
|
const toggleIntent = (label) => {
|
|
|
|
|
|
const index = tempIntents.value.indexOf(label);
|
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
|
tempIntents.value.splice(index, 1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tempIntents.value.push(label);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认客户意向
|
|
|
|
|
|
const confirmIntent = () => {
|
|
|
|
|
|
formData.value.intents = [...tempIntents.value];
|
|
|
|
|
|
showIntentPicker.value = false;
|
2025-11-07 11:09:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开意向强度选择器
|
|
|
|
|
|
const openIntentLevelPicker = () => {
|
|
|
|
|
|
tempIntentLevel.value = formData.value.intentLevel;
|
|
|
|
|
|
showIntentLevelPicker.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择意向强度
|
|
|
|
|
|
const selectIntentLevel = (value) => {
|
|
|
|
|
|
tempIntentLevel.value = value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认意向强度
|
|
|
|
|
|
const confirmIntentLevel = () => {
|
|
|
|
|
|
formData.value.intentLevel = tempIntentLevel.value;
|
|
|
|
|
|
showIntentLevelPicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 打开客户状态选择器
|
|
|
|
|
|
const openCustomerStatusPicker = () => {
|
|
|
|
|
|
tempCustomerStatus.value = formData.value.customerStatus;
|
|
|
|
|
|
showCustomerStatusPicker.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择客户状态
|
|
|
|
|
|
const selectCustomerStatus = (value) => {
|
|
|
|
|
|
tempCustomerStatus.value = value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认客户状态
|
|
|
|
|
|
const confirmCustomerStatus = () => {
|
|
|
|
|
|
formData.value.customerStatus = tempCustomerStatus.value;
|
|
|
|
|
|
showCustomerStatusPicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-07 11:09:30 +08:00
|
|
|
|
// 打开地区选择器
|
|
|
|
|
|
const openRegionPicker = () => {
|
|
|
|
|
|
console.log('openRegionPicker called');
|
|
|
|
|
|
// 检查数据是否已加载
|
|
|
|
|
|
if (regionLoading.value || !regionTree.value || regionTree.value.length === 0) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '地区数据加载中,请稍候',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有已选择的地区,恢复选择状态
|
|
|
|
|
|
if (formData.value.regionIds && formData.value.regionIds.length > 0) {
|
|
|
|
|
|
handlePickValueDefault();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有已选择的地区,确保城市和区县数据已初始化
|
|
|
|
|
|
if (citys.value.length === 0 && provinces.value.length > 0) {
|
|
|
|
|
|
citys.value = provinces.value[0]?.children || [];
|
|
|
|
|
|
if (citys.value.length > 0) {
|
|
|
|
|
|
areas.value = citys.value[0]?.children || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 打开选择器
|
|
|
|
|
|
if (regionPicker.value) {
|
|
|
|
|
|
regionPicker.value.open();
|
|
|
|
|
|
// 如果没有已选择的地区,在打开后设置默认索引
|
|
|
|
|
|
if (!formData.value.regionIds || formData.value.regionIds.length === 0) {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (regionPicker.value) {
|
|
|
|
|
|
regionPicker.value.setIndexs([0, 0, 0], true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// uv-picker 的 change 事件处理(实现三级联动)
|
|
|
|
|
|
const onRegionChange = (e) => {
|
|
|
|
|
|
if (regionLoading.value) return;
|
|
|
|
|
|
|
|
|
|
|
|
const { columnIndex, index, indexs } = e;
|
|
|
|
|
|
|
|
|
|
|
|
// 改变了省
|
|
|
|
|
|
if (columnIndex === 0) {
|
|
|
|
|
|
citys.value = provinces.value[index]?.children || [];
|
|
|
|
|
|
areas.value = citys.value[0]?.children || [];
|
|
|
|
|
|
if (regionPicker.value) {
|
|
|
|
|
|
regionPicker.value.setIndexs([index, 0, 0], true);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (columnIndex === 1) {
|
|
|
|
|
|
// 改变了市
|
|
|
|
|
|
areas.value = citys.value[index]?.children || [];
|
|
|
|
|
|
if (regionPicker.value) {
|
|
|
|
|
|
regionPicker.value.setIndexs(indexs, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// uv-picker 的 confirm 事件处理
|
|
|
|
|
|
const onRegionConfirm = (e) => {
|
|
|
|
|
|
console.log('确认选择的地区:', e);
|
|
|
|
|
|
|
|
|
|
|
|
const { value } = e;
|
|
|
|
|
|
|
|
|
|
|
|
if (value && value.length > 0) {
|
|
|
|
|
|
const province = value[0];
|
|
|
|
|
|
const city = value[1];
|
|
|
|
|
|
const district = value[2];
|
|
|
|
|
|
|
|
|
|
|
|
// 构建地区名称和ID数组
|
|
|
|
|
|
const regionNames = [];
|
|
|
|
|
|
const regionIds = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (province) {
|
|
|
|
|
|
regionNames.push(province.name);
|
|
|
|
|
|
regionIds.push(province.id);
|
|
|
|
|
|
|
|
|
|
|
|
if (city) {
|
|
|
|
|
|
regionNames.push(city.name);
|
|
|
|
|
|
regionIds.push(city.id);
|
|
|
|
|
|
|
|
|
|
|
|
if (district) {
|
|
|
|
|
|
regionNames.push(district.name);
|
|
|
|
|
|
regionIds.push(district.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
formData.value.region = regionNames.join('/');
|
|
|
|
|
|
formData.value.regionIds = regionIds;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
formData.value.region = '';
|
|
|
|
|
|
formData.value.regionIds = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开工作微信选择器
|
|
|
|
|
|
const openWorkWechatPicker = () => {
|
|
|
|
|
|
tempWorkWechat.value = formData.value.workWechat;
|
|
|
|
|
|
showWorkWechatPicker.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择工作微信
|
|
|
|
|
|
const selectWorkWechat = (value) => {
|
|
|
|
|
|
tempWorkWechat.value = value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认工作微信
|
|
|
|
|
|
const confirmWorkWechat = () => {
|
|
|
|
|
|
const selectedWechat = workWechatOptions.find(w => w.value === tempWorkWechat.value);
|
|
|
|
|
|
if (selectedWechat) {
|
|
|
|
|
|
formData.value.workWechat = selectedWechat.value;
|
|
|
|
|
|
formData.value.workWechatId = selectedWechat.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
showWorkWechatPicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开下次跟进时间选择器
|
|
|
|
|
|
const openNextFollowTimePicker = () => {
|
|
|
|
|
|
if (formData.value.nextFollowTime) {
|
|
|
|
|
|
const [date, time] = formData.value.nextFollowTime.split(' ');
|
|
|
|
|
|
tempNextFollowDate.value = date || '';
|
|
|
|
|
|
tempNextFollowTime.value = time || '';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tempNextFollowDate.value = '';
|
|
|
|
|
|
tempNextFollowTime.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
showNextFollowTimePicker.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 日期改变
|
|
|
|
|
|
const onNextFollowDateChange = (e) => {
|
|
|
|
|
|
tempNextFollowDate.value = e.detail.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 时间改变
|
|
|
|
|
|
const onNextFollowTimeChange = (e) => {
|
|
|
|
|
|
tempNextFollowTime.value = e.detail.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 清除下次跟进时间
|
|
|
|
|
|
const clearNextFollowTime = () => {
|
|
|
|
|
|
formData.value.nextFollowTime = '';
|
|
|
|
|
|
tempNextFollowDate.value = '';
|
|
|
|
|
|
tempNextFollowTime.value = '';
|
|
|
|
|
|
showNextFollowTimePicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认下次跟进时间
|
|
|
|
|
|
const confirmNextFollowTime = () => {
|
|
|
|
|
|
if (tempNextFollowDate.value && tempNextFollowTime.value) {
|
|
|
|
|
|
formData.value.nextFollowTime = `${tempNextFollowDate.value} ${tempNextFollowTime.value}:00`;
|
|
|
|
|
|
} else if (tempNextFollowDate.value) {
|
|
|
|
|
|
formData.value.nextFollowTime = `${tempNextFollowDate.value} 09:00:00`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
formData.value.nextFollowTime = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
showNextFollowTimePicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 取消
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
|
uni.navigateBack();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 组件挂载时加载地区树数据和字典数据
|
2025-11-07 11:09:30 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadRegionTree();
|
2025-11-07 14:56:40 +08:00
|
|
|
|
loadDictData();
|
2025-11-07 11:09:30 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 保存
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
|
// 表单验证
|
|
|
|
|
|
if (!formData.value.name || formData.value.name.trim() === '') {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '请输入客户名称',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!formData.value.mobile || formData.value.mobile.trim() === '') {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '请输入联系电话',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
saving.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取用户信息
|
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
|
const userId = userStore.userInfo?.id || userStore.userInfo?.userId || '1';
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化当前时间
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
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}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 处理意向数组(已经是数组格式,包含dictLabel)
|
|
|
|
|
|
const intentsArray = Array.isArray(formData.value.intents) ? formData.value.intents : [];
|
2025-11-07 11:09:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理地区ID数组(使用已选择的regionIds)
|
|
|
|
|
|
const regionIdsArray = formData.value.regionIds || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 构建提交数据(按照接口示例格式)
|
|
|
|
|
|
const submitData = {
|
|
|
|
|
|
id: null,
|
|
|
|
|
|
code: null,
|
|
|
|
|
|
name: formData.value.name.trim(),
|
|
|
|
|
|
status: null,
|
|
|
|
|
|
intentLevel: null,
|
|
|
|
|
|
mobile: formData.value.mobile.trim(),
|
|
|
|
|
|
wechat: formData.value.wechat.trim() || null,
|
2025-11-07 14:56:40 +08:00
|
|
|
|
source: formData.value.source || null, // 使用dictLabel
|
|
|
|
|
|
intents: intentsArray, // 数组格式,使用dictLabel
|
2025-11-07 11:09:30 +08:00
|
|
|
|
followId: userId, // 跟进人ID
|
|
|
|
|
|
remark: formData.value.remark.trim() || null,
|
2025-11-07 14:56:40 +08:00
|
|
|
|
type: formData.value.customerType || '2', // 使用dictValue
|
2025-11-07 11:09:30 +08:00
|
|
|
|
workWechatId: formData.value.workWechatId || null,
|
|
|
|
|
|
regionIds: regionIdsArray, // 数组格式
|
2025-11-07 13:31:19 +08:00
|
|
|
|
// 添加关注点、顾虑点、需求点、痛点字段
|
|
|
|
|
|
attention: formData.value.attention.trim() || null,
|
|
|
|
|
|
concern: formData.value.concern.trim() || null,
|
|
|
|
|
|
demand: formData.value.demand.trim() || null,
|
|
|
|
|
|
pain: formData.value.pain.trim() || null,
|
2025-11-07 11:09:30 +08:00
|
|
|
|
follow: {
|
|
|
|
|
|
followTime: formatDateTime(now), // 当前时间作为跟进时间
|
|
|
|
|
|
nextFollowTime: formData.value.nextFollowTime || null,
|
2025-11-07 14:56:40 +08:00
|
|
|
|
customerIntentLevel: formData.value.intentLevel || null, // 使用dictValue
|
|
|
|
|
|
customerStatus: formData.value.customerStatus || null // 使用dictValue
|
2025-11-07 11:09:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
nextFollowTime: formData.value.nextFollowTime || null,
|
2025-11-07 14:56:40 +08:00
|
|
|
|
customerIntentLevel: formData.value.intentLevel || null, // 使用dictValue
|
|
|
|
|
|
customerStatus: formData.value.customerStatus || null // 使用dictValue
|
2025-11-07 11:09:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 调用API创建客户
|
|
|
|
|
|
await createCustomer(submitData);
|
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '创建成功',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-07 14:56:40 +08:00
|
|
|
|
// 保存成功后不清空表单,留在当前页面
|
2025-11-07 11:09:30 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('创建客户失败:', error);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: error.message || '创建失败,请重试',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saving.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.add-customer-page {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 自定义导航栏 */
|
|
|
|
|
|
.custom-navbar {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
padding-top: var(--status-bar-height);
|
|
|
|
|
|
border-bottom: 1px solid #eee;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.navbar-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-btn {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
min-width: 44px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-title {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 内容区域 */
|
|
|
|
|
|
.content-scroll {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.scroll-content {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
padding-bottom: 80px; /* 为保存按钮留出空间 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 表单部分 */
|
|
|
|
|
|
.form-section {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
|
border-bottom: 1px solid #eee;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-item {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-label {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
background-color: #f8f8f8;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-textarea {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: 80px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
background-color: #f8f8f8;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.clickable-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
background-color: #f8f8f8;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
pointer-events: auto;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-value {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-placeholder {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.arrow {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 单选框组 */
|
|
|
|
|
|
.radio-group {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.radio-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.radio-dot {
|
|
|
|
|
|
width: 18px;
|
|
|
|
|
|
height: 18px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
border: 2px solid #ddd;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&.checked {
|
|
|
|
|
|
border-color: #1976d2;
|
|
|
|
|
|
background-color: #1976d2;
|
|
|
|
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
|
width: 6px;
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 星级评分 */
|
|
|
|
|
|
.star-rating {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.star {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
color: #ddd;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: color 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
&.filled {
|
|
|
|
|
|
color: #ffc107;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 保存按钮 */
|
|
|
|
|
|
.save-button-wrapper {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-top: 1px solid #eee;
|
|
|
|
|
|
z-index: 100;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.save-button {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
background-color: #1976d2;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
background-color: #ccc;
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 弹窗样式 */
|
|
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|