后台获取各类添加客户的状态

This commit is contained in:
WindowBird 2025-11-07 14:56:40 +08:00
parent f9799d2057
commit 1c33bd6c42
3 changed files with 334 additions and 105 deletions

View File

@ -8,7 +8,7 @@
// token token // token token
const token = uni.getStorageSync('token') const token = uni.getStorageSync('token')
if (!token) { if (!token) {
const testToken = 'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImQ0ODJhYzdlLTBiYzgtNGFhNC1hMDI2LWVkMWU4YWQ3YmJkYiJ9._m4s6mcpjAHtk4u9r6LMMfIQHSCXDPCTfiWVOTyopZfbsxVFiLkY4ovec3M3u2E9KKm_jIH2fLhAduW4rfAZHQ' const testToken = 'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjlkN2JjOTU3LTllMTYtNGI5ZC04NWZiLThiZjJlZDI3MjRjNiJ9.hJ0Tf691DnryIMp4TAsKILSy9ueN8SgeMZdp0HwxbgGOIhYl5vOUu9H64tA02jpXkB-Sg5owrBjjJxUG17yA7g'
uni.setStorageSync('token', testToken) uni.setStorageSync('token', testToken)
console.log('已设置测试 token:', testToken) console.log('已设置测试 token:', testToken)
} }

View File

@ -164,3 +164,63 @@ export const createCustomer = (data) => {
}); });
}; };
/**
* 获取客户意向字典数据
* @returns {Promise} 返回字典数据数组包含 dictLabel dictValue
*/
export const getCustomerIntentDict = () => {
return uni.$uv.http.get(`system/dict/data/type/customer_intent`, {
custom: {
auth: true
}
});
};
/**
* 获取客户意向强度字典数据
* @returns {Promise} 返回字典数据数组包含 dictLabel dictValue
*/
export const getCustomerIntentLevelDict = () => {
return uni.$uv.http.get(`system/dict/data/type/customer_intent_level`, {
custom: {
auth: true
}
});
};
/**
* 获取客户来源字典数据
* @returns {Promise} 返回字典数据数组包含 dictLabel dictValue
*/
export const getCustomerSourceDict = () => {
return uni.$uv.http.get(`system/dict/data/type/customer_source`, {
custom: {
auth: true
}
});
};
/**
* 获取客户类型字典数据
* @returns {Promise} 返回字典数据数组包含 dictLabel dictValue
*/
export const getCustomerTypeDict = () => {
return uni.$uv.http.get(`system/dict/data/type/customer_type`, {
custom: {
auth: true
}
});
};
/**
* 获取客户状态字典数据
* @returns {Promise} 返回字典数据数组包含 dictLabel dictValue
*/
export const getCustomerStatusDict = () => {
return uni.$uv.http.get(`system/dict/data/type/customer_status`, {
custom: {
auth: true
}
});
};

View File

@ -17,26 +17,10 @@
<view class="section-title">客户信息</view> <view class="section-title">客户信息</view>
<!-- 客户类型 --> <!-- 客户类型 -->
<view class="form-item"> <view class="form-item clickable-item" @click="openCustomerTypePicker">
<text class="form-label">客户类型</text> <text v-if="formData.customerType" class="form-value">{{ getCustomerTypeText(formData.customerType) }}</text>
<view class="radio-group"> <text v-else class="form-placeholder">选择客户类型</text>
<view <text class="arrow"></text>
class="radio-item"
:class="{ active: formData.customerType === '个人' }"
@click="formData.customerType = '个人'"
>
<view class="radio-dot" :class="{ checked: formData.customerType === '个人' }"></view>
<text>个人</text>
</view>
<view
class="radio-item"
:class="{ active: formData.customerType === '企业' }"
@click="formData.customerType = '企业'"
>
<view class="radio-dot" :class="{ checked: formData.customerType === '企业' }"></view>
<text>企业</text>
</view>
</view>
</view> </view>
<!-- 客户名称 --> <!-- 客户名称 -->
@ -71,29 +55,16 @@
</view> </view>
<!-- 客户来源 --> <!-- 客户来源 -->
<view class="form-item"> <view class="form-item clickable-item" @click="openSourcePicker">
<input <text v-if="formData.source" class="form-value">{{ formData.source }}</text>
v-model="formData.source" <text v-else class="form-placeholder">选择客户来源</text>
class="form-input" <text class="arrow"></text>
placeholder="输入客户来源"
placeholder-style="color: #999;"
/>
</view> </view>
<!-- 客户意向 --> <!-- 客户意向 -->
<view class="form-item"> <view class="form-item clickable-item" @click="openIntentPicker">
<input <text v-if="formData.intents && formData.intents.length > 0" class="form-value">{{ formData.intents.join('') }}</text>
v-model="formData.intents" <text v-else class="form-placeholder">选择客户意向可多选</text>
class="form-input"
placeholder="输入客户意向"
placeholder-style="color: #999;"
/>
</view>
<!-- 客户状态 -->
<view class="form-item clickable-item" @click="openStatusPicker">
<text v-if="formData.status" class="form-value">{{ getStatusText(formData.status) }}</text>
<text v-else class="form-placeholder">选择客户状态</text>
<text class="arrow"></text> <text class="arrow"></text>
</view> </view>
@ -104,6 +75,13 @@
<text class="arrow"></text> <text class="arrow"></text>
</view> </view>
<!-- 客户状态 -->
<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>
<!-- 客户地区 --> <!-- 客户地区 -->
<view class="form-item clickable-item" @click="openRegionPicker" @tap="openRegionPicker"> <view class="form-item clickable-item" @click="openRegionPicker" @tap="openRegionPicker">
<text v-if="formData.region" class="form-value">{{ formData.region }}</text> <text v-if="formData.region" class="form-value">{{ formData.region }}</text>
@ -207,25 +185,71 @@
<button class="save-button" @click="handleSave" :disabled="saving">保存</button> <button class="save-button" @click="handleSave" :disabled="saving">保存</button>
</view> </view>
<!-- 客户状态选择弹窗 --> <!-- 客户类型选择弹窗 -->
<view v-if="showStatusPicker" class="modal-mask" @click="showStatusPicker = false"> <view v-if="showCustomerTypePicker" class="modal-mask" @click="showCustomerTypePicker = false">
<view class="modal-content" @click.stop> <view class="modal-content" @click.stop>
<view class="modal-title">选择客户状态</view> <view class="modal-title">选择客户类型</view>
<view class="picker-options"> <view class="picker-options">
<view <view
v-for="item in statusOptions" v-for="item in customerTypeOptions"
:key="item.value" :key="item.value"
class="picker-option" class="picker-option"
:class="{ active: tempStatus === item.value }" :class="{ active: tempCustomerType === item.value }"
@click="selectStatus(item.value)" @click="selectCustomerType(item.value)"
> >
<text>{{ item.label }}</text> <text>{{ item.label }}</text>
<text v-if="tempStatus === item.value" class="check"></text> <text v-if="tempCustomerType === item.value" class="check"></text>
</view> </view>
</view> </view>
<view class="modal-buttons"> <view class="modal-buttons">
<button class="modal-btn" @click="showStatusPicker = false">取消</button> <button class="modal-btn" @click="showCustomerTypePicker = false">取消</button>
<button class="modal-btn primary" @click="confirmStatus">确定</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>
</view> </view>
</view> </view>
</view> </view>
@ -253,6 +277,29 @@
</view> </view>
</view> </view>
<!-- 客户状态选择弹窗 -->
<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>
<!-- 客户地区选择器 - uv-picker --> <!-- 客户地区选择器 - uv-picker -->
<uv-picker <uv-picker
ref="regionPicker" ref="regionPicker"
@ -318,19 +365,27 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { createCustomer, getRegionTree } from '@/common/api'; import {
createCustomer,
getRegionTree,
getCustomerIntentDict,
getCustomerIntentLevelDict,
getCustomerSourceDict,
getCustomerTypeDict,
getCustomerStatusDict
} from '@/common/api';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
// //
const formData = ref({ const formData = ref({
customerType: '个人', // / customerType: '', // dictValue
name: '', // name: '', //
mobile: '', // mobile: '', //
wechat: '', // wechat: '', //
source: '', // source: '', // dictLabel
intents: '', // intents: [], // dictLabel
status: '', // intentLevel: '', // dictValue
intentLevel: '', // customerStatus: '', // dictValue
region: '', // // region: '', // //
regionIds: [], // ID [ID, ID, ID] regionIds: [], // ID [ID, ID, ID]
workWechat: '', // workWechat: '', //
@ -346,18 +401,31 @@ const formData = ref({
// //
const saving = ref(false); const saving = ref(false);
const showStatusPicker = ref(false); const showCustomerTypePicker = ref(false);
const showSourcePicker = ref(false);
const showIntentPicker = ref(false);
const showIntentLevelPicker = ref(false); const showIntentLevelPicker = ref(false);
const showCustomerStatusPicker = ref(false);
const showWorkWechatPicker = ref(false); const showWorkWechatPicker = ref(false);
const showNextFollowTimePicker = ref(false); const showNextFollowTimePicker = ref(false);
// //
const tempStatus = ref(''); const tempCustomerType = ref('');
const tempSource = ref('');
const tempIntents = ref([]);
const tempIntentLevel = ref(''); const tempIntentLevel = ref('');
const tempCustomerStatus = ref('');
const tempWorkWechat = ref(''); const tempWorkWechat = ref('');
const tempNextFollowDate = ref(''); const tempNextFollowDate = ref('');
const tempNextFollowTime = ref(''); const tempNextFollowTime = ref('');
//
const customerTypeOptions = ref([]); //
const sourceOptions = ref([]); //
const intentOptions = ref([]); //
const intentLevelOptions = ref([]); //
const customerStatusOptions = ref([]); //
// //
const regionTree = ref([]); const regionTree = ref([]);
// uv-picker // uv-picker
@ -374,20 +442,63 @@ const addressList = computed(() => {
return [provinces.value, citys.value, areas.value]; return [provinces.value, citys.value, areas.value];
}); });
// //
const statusOptions = [ const loadDictData = async () => {
{ label: '潜在', value: '1' }, try {
{ label: '意向', value: '2' }, //
{ label: '成交', value: '3' }, const [typeRes, sourceRes, intentRes, intentLevelRes, statusRes] = await Promise.all([
{ label: '失效', value: '4' } getCustomerTypeDict(),
]; getCustomerSourceDict(),
getCustomerIntentDict(),
getCustomerIntentLevelDict(),
getCustomerStatusDict()
]);
// //
const intentLevelOptions = [ customerTypeOptions.value = (typeRes || []).map(item => ({
{ label: '高', value: '1' }, label: item.dictLabel,
{ label: '中', value: '2' }, value: item.dictValue
{ label: '低', value: '3' } }));
];
//
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'
});
}
};
// //
const loadRegionTree = async () => { const loadRegionTree = async () => {
@ -466,32 +577,78 @@ const workWechatOptions = [
{ label: '工作微信4', value: '工作微信4', id: '4' } { label: '工作微信4', value: '工作微信4', id: '4' }
]; ];
// //
const getStatusText = (status) => { const getCustomerTypeText = (value) => {
const option = statusOptions.find(opt => opt.value === status); const option = customerTypeOptions.value.find(opt => opt.value === value);
return option ? option.label : ''; return option ? option.label : '';
}; };
// //
const getIntentLevelText = (level) => { const getIntentLevelText = (value) => {
return level || ''; const option = intentLevelOptions.value.find(opt => opt.value === value);
return option ? option.label : '';
}; };
// //
const openStatusPicker = () => { const getCustomerStatusText = (value) => {
tempStatus.value = formData.value.status; const option = customerStatusOptions.value.find(opt => opt.value === value);
showStatusPicker.value = true; return option ? option.label : '';
}; };
// //
const selectStatus = (value) => { const openCustomerTypePicker = () => {
tempStatus.value = value; tempCustomerType.value = formData.value.customerType;
showCustomerTypePicker.value = true;
}; };
// //
const confirmStatus = () => { const selectCustomerType = (value) => {
formData.value.status = tempStatus.value; tempCustomerType.value = value;
showStatusPicker.value = false; };
//
const confirmCustomerType = () => {
formData.value.customerType = tempCustomerType.value;
showCustomerTypePicker.value = false;
};
//
const openSourcePicker = () => {
tempSource.value = formData.value.source;
showSourcePicker.value = true;
};
//
const selectSource = (label) => {
tempSource.value = label;
};
//
const confirmSource = () => {
formData.value.source = tempSource.value;
showSourcePicker.value = false;
};
//
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;
}; };
// //
@ -511,6 +668,23 @@ const confirmIntentLevel = () => {
showIntentLevelPicker.value = false; showIntentLevelPicker.value = false;
}; };
//
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;
};
// //
const openRegionPicker = () => { const openRegionPicker = () => {
console.log('openRegionPicker called'); console.log('openRegionPicker called');
@ -679,9 +853,10 @@ const handleCancel = () => {
uni.navigateBack(); uni.navigateBack();
}; };
// //
onMounted(() => { onMounted(() => {
loadRegionTree(); loadRegionTree();
loadDictData();
}); });
// //
@ -721,11 +896,8 @@ const handleSave = async () => {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}; };
// // dictLabel
let intentsArray = []; const intentsArray = Array.isArray(formData.value.intents) ? formData.value.intents : [];
if (formData.value.intents && formData.value.intents.trim()) {
intentsArray = formData.value.intents.split(',').map(item => item.trim()).filter(item => item);
}
// ID使regionIds // ID使regionIds
const regionIdsArray = formData.value.regionIds || []; const regionIdsArray = formData.value.regionIds || [];
@ -739,11 +911,11 @@ const handleSave = async () => {
intentLevel: null, intentLevel: null,
mobile: formData.value.mobile.trim(), mobile: formData.value.mobile.trim(),
wechat: formData.value.wechat.trim() || null, wechat: formData.value.wechat.trim() || null,
source: formData.value.source.trim() || null, source: formData.value.source || null, // 使dictLabel
intents: intentsArray, // intents: intentsArray, // 使dictLabel
followId: userId, // ID followId: userId, // ID
remark: formData.value.remark.trim() || null, remark: formData.value.remark.trim() || null,
type: '2', // 2 type: formData.value.customerType || '2', // 使dictValue
workWechatId: formData.value.workWechatId || null, workWechatId: formData.value.workWechatId || null,
regionIds: regionIdsArray, // regionIds: regionIdsArray, //
// //
@ -754,12 +926,12 @@ const handleSave = async () => {
follow: { follow: {
followTime: formatDateTime(now), // followTime: formatDateTime(now), //
nextFollowTime: formData.value.nextFollowTime || null, nextFollowTime: formData.value.nextFollowTime || null,
customerStatus: formData.value.status || '1', customerIntentLevel: formData.value.intentLevel || null, // 使dictValue
customerIntentLevel: formData.value.intentLevel || null customerStatus: formData.value.customerStatus || null // 使dictValue
}, },
customerStatus: formData.value.status || '1',
nextFollowTime: formData.value.nextFollowTime || null, nextFollowTime: formData.value.nextFollowTime || null,
customerIntentLevel: formData.value.intentLevel || null customerIntentLevel: formData.value.intentLevel || null, // 使dictValue
customerStatus: formData.value.customerStatus || null // 使dictValue
}; };
// API // API
@ -770,10 +942,7 @@ const handleSave = async () => {
icon: 'success' icon: 'success'
}); });
// //
setTimeout(() => {
uni.navigateBack();
}, 1500);
} catch (error) { } catch (error) {
console.error('创建客户失败:', error); console.error('创建客户失败:', error);
uni.showToast({ uni.showToast({