初步完成可视化页面
parent
050d4f9fcf
commit
510fca9b8e
273
cs_chart.html
273
cs_chart.html
|
|
@ -720,6 +720,54 @@
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #9e9e9e;
|
background: #9e9e9e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 司机搜索下拉框样式 */
|
||||||
|
.driver-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1000;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.driver-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.driver-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.driver-item:hover {
|
||||||
|
background-color: var(--light-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.driver-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.driver-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark-green);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.driver-phone {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -756,8 +804,17 @@
|
||||||
<input type="text" id="order-number" v-model="searchParams.orderNumber" placeholder="订单号">
|
<input type="text" id="order-number" v-model="searchParams.orderNumber" placeholder="订单号">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search-field" style="min-width: 180px;">
|
<div class="search-field" style="min-width: 180px; position: relative;">
|
||||||
<input type="text" id="fuzzy-search" v-model="searchParams.fuzzySearch" placeholder="输入司机姓名">
|
<input type="text" id="fuzzy-search" v-model="searchParams.fuzzySearch" @input="handleDriverSearch" placeholder="输入司机姓名">
|
||||||
|
<div v-if="uiState.showDriverList && driverSearchResults.length > 0" class="driver-dropdown">
|
||||||
|
<div v-for="driver in driverSearchResults" :key="driver.uid"
|
||||||
|
class="driver-item" @click="selectDriver(driver)">
|
||||||
|
<div class="driver-info">
|
||||||
|
<span class="driver-name">{{ driver.realname }}</span>
|
||||||
|
<span class="driver-phone">{{ driver.username }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="date-range">
|
<div class="date-range">
|
||||||
|
|
@ -826,13 +883,13 @@
|
||||||
|
|
||||||
<!-- 归属客服下拉框 -->
|
<!-- 归属客服下拉框 -->
|
||||||
<div style="display: flex; flex-direction: column; min-width: 220px; flex-shrink: 0;">
|
<div style="display: flex; flex-direction: column; min-width: 220px; flex-shrink: 0;">
|
||||||
<select style="padding: 7px 9px; border: 1px solid var(--border-color); border-radius: 3px; font-size: 0.85rem; height: 32px; margin-bottom: 6px;" v-model="searchParams.customerService">
|
<select style="padding: 7px 9px; border: 1px solid var(--border-color); border-radius: 3px; font-size: 0.85rem; height: 32px; margin-bottom: 6px;" v-model="searchParams.belongKefu">
|
||||||
<option value="">全部客服</option>
|
<option value="">全部客服</option>
|
||||||
<option v-for="item in staffOptions.customers" :key="item.id" :value="item.id">
|
<option v-for="item in staffOptions.customers" :key="item.id" :value="item.id">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<select style="padding: 7px 9px; border: 1px solid var(--border-color); border-radius: 3px; font-size: 0.85rem; height: 32px;" v-model="searchParams.salesman">
|
<select style="padding: 7px 9px; border: 1px solid var(--border-color); border-radius: 3px; font-size: 0.85rem; height: 32px;" v-model="searchParams.belongSale">
|
||||||
<option value="">全部业务员</option>
|
<option value="">全部业务员</option>
|
||||||
<option v-for="item in staffOptions.salesmen" :key="item.id" :value="item.id">
|
<option v-for="item in staffOptions.salesmen" :key="item.id" :value="item.id">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
|
|
@ -1057,8 +1114,9 @@
|
||||||
timeType: '',
|
timeType: '',
|
||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: '',
|
endDate: '',
|
||||||
customerService: '',
|
belongKefu: '',
|
||||||
salesman: ''
|
belongSale: '',
|
||||||
|
driverUid: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
// 界面状态
|
// 界面状态
|
||||||
|
|
@ -1066,6 +1124,7 @@
|
||||||
showQueryBar: false,
|
showQueryBar: false,
|
||||||
showKpiBar: false,
|
showKpiBar: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
showDriverList: false,
|
||||||
modals: {
|
modals: {
|
||||||
chat: false,
|
chat: false,
|
||||||
review: false,
|
review: false,
|
||||||
|
|
@ -1084,11 +1143,15 @@
|
||||||
salesmen: []
|
salesmen: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 司机搜索结果
|
||||||
|
const driverSearchResults = ref([]);
|
||||||
|
let driverSearchTimer = null;
|
||||||
|
|
||||||
const kpiList = ref([
|
const kpiList = ref([
|
||||||
{ value: '-', label: '司机数', isDriver: true },
|
{ value: '-', label: '司机数', isDriver: true },
|
||||||
{ value: '156', label: '续费数' },
|
{ value: '156', label: '续费数' },
|
||||||
{ value: '45', label: '流失数' },
|
{ value: '45', label: '流失数' },
|
||||||
{ value: '987', subValue: '98.7%', label: '接单数/率' },
|
{ value: '-', subValue: '-', label: '接单数/率' },
|
||||||
{ value: '2,541', subValue: '95.2%', label: '订单数/率' },
|
{ value: '2,541', subValue: '95.2%', label: '订单数/率' },
|
||||||
{ value: '56', subValue: '2.2%', label: '不符数/率' },
|
{ value: '56', subValue: '2.2%', label: '不符数/率' },
|
||||||
{ value: '78', subValue: '3.1%', label: '超时数/率' },
|
{ value: '78', subValue: '3.1%', label: '超时数/率' },
|
||||||
|
|
@ -1156,6 +1219,14 @@
|
||||||
|
|
||||||
// 获取初始数据
|
// 获取初始数据
|
||||||
fetchInitialData();
|
fetchInitialData();
|
||||||
|
|
||||||
|
// 点击页面其他区域关闭司机下拉列表
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const fuzzySearch = document.getElementById('fuzzy-search');
|
||||||
|
if (fuzzySearch && !fuzzySearch.contains(e.target)) {
|
||||||
|
uiState.showDriverList = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizeProvinces = (list) => {
|
const normalizeProvinces = (list) => {
|
||||||
|
|
@ -1261,9 +1332,14 @@
|
||||||
if (Array.isArray(d.province)) {
|
if (Array.isArray(d.province)) {
|
||||||
areaList.provinces = normalizeProvinces(d.province);
|
areaList.provinces = normalizeProvinces(d.province);
|
||||||
}
|
}
|
||||||
if (d.salerss) {
|
// 填充客服列表
|
||||||
|
if (d.customers) {
|
||||||
|
staffOptions.customers = normalizeStaffMap(d.customers);
|
||||||
|
} else if (d.salerss) {
|
||||||
staffOptions.customers = normalizeStaffMap(d.salerss);
|
staffOptions.customers = normalizeStaffMap(d.salerss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 填充业务员列表
|
||||||
if (d.salers) {
|
if (d.salers) {
|
||||||
staffOptions.salesmen = normalizeStaffMap(d.salers);
|
staffOptions.salesmen = normalizeStaffMap(d.salers);
|
||||||
}
|
}
|
||||||
|
|
@ -1331,6 +1407,12 @@
|
||||||
|
|
||||||
// 获取在线沟通数据
|
// 获取在线沟通数据
|
||||||
await fetchOnlineChatData();
|
await fetchOnlineChatData();
|
||||||
|
|
||||||
|
// 默认选中待回访按钮
|
||||||
|
const revisitTask = taskList.value.find(task => task.type === 'revisit');
|
||||||
|
if (revisitTask) {
|
||||||
|
handleTaskClick(revisitTask);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching initial data:', error);
|
console.error('Error fetching initial data:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -1387,12 +1469,126 @@
|
||||||
uiState.isLoading = true;
|
uiState.isLoading = true;
|
||||||
console.log('正在搜索数据...');
|
console.log('正在搜索数据...');
|
||||||
|
|
||||||
// 重新请求接口数据
|
// 构建请求参数
|
||||||
await fetchInitialData();
|
const params = {
|
||||||
|
time_start: searchParams.startDate,
|
||||||
|
time_end: searchParams.endDate
|
||||||
|
};
|
||||||
|
|
||||||
// 清空当前选中的任务类型和案件列表
|
// 添加省市区参数
|
||||||
activeTaskType.value = '';
|
if (searchParams.province) {
|
||||||
caseList.value = [];
|
params.province = searchParams.province;
|
||||||
|
}
|
||||||
|
if (searchParams.city) {
|
||||||
|
params.city = searchParams.city;
|
||||||
|
}
|
||||||
|
if (searchParams.district) {
|
||||||
|
params.county = searchParams.district;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加其他参数
|
||||||
|
if (searchParams.belongKefu) {
|
||||||
|
params.belong_kefu = searchParams.belongKefu;
|
||||||
|
}
|
||||||
|
if (searchParams.belongSale) {
|
||||||
|
params.belong_sale = searchParams.belongSale;
|
||||||
|
}
|
||||||
|
if (searchParams.driverUid) {
|
||||||
|
params.driver_uid = searchParams.driverUid;
|
||||||
|
}
|
||||||
|
if (searchParams.timeType) {
|
||||||
|
params.apptimes = searchParams.timeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('搜索参数:', params);
|
||||||
|
|
||||||
|
const res = await axios.get(BASE_URL + 'third-api/cs-chart-info', {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.data && res.data.code === 200 && res.data.data) {
|
||||||
|
const d = res.data.data;
|
||||||
|
|
||||||
|
// 更新KPI数据
|
||||||
|
if (kpiList.value.length > 0 && typeof d.driverNum !== 'undefined') {
|
||||||
|
kpiList.value[0].value = String(d.driverNum);
|
||||||
|
fetchFollowUpCount(d.driverNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 1 && typeof d.subscribeNum !== 'undefined') {
|
||||||
|
kpiList.value[1].value = String(d.subscribeNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 2 && typeof d.lostNum !== 'undefined') {
|
||||||
|
kpiList.value[2].value = String(d.lostNum);
|
||||||
|
}
|
||||||
|
// Update '接单数/率'
|
||||||
|
if (kpiList.value.length > 3 && typeof d.driverAcceptNum !== 'undefined') {
|
||||||
|
kpiList.value[3].value = String(d.driverAcceptNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 3 && typeof d.driverAcceptPer !== 'undefined') {
|
||||||
|
kpiList.value[3].subValue = formatPercent(d.driverAcceptPer);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 4 && typeof d.orderNum !== 'undefined') {
|
||||||
|
kpiList.value[4].value = String(d.orderNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 4 && typeof d.orderPer !== 'undefined') {
|
||||||
|
kpiList.value[4].subValue = formatPercent(d.orderPer);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 5 && typeof d.reconsiderNum !== 'undefined') {
|
||||||
|
kpiList.value[5].value = String(d.reconsiderNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 5 && typeof d.reconsiderPer !== 'undefined') {
|
||||||
|
kpiList.value[5].subValue = formatPercent(d.reconsiderPer);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 6 && typeof d.overNum !== 'undefined') {
|
||||||
|
kpiList.value[6].value = String(d.overNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 6 && typeof d.overPer !== 'undefined') {
|
||||||
|
kpiList.value[6].subValue = formatPercent(d.overPer);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 8 && typeof d.satisfiedNum !== 'undefined') {
|
||||||
|
kpiList.value[8].value = String(d.satisfiedNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 8 && typeof d.satisfiedPer !== 'undefined') {
|
||||||
|
kpiList.value[8].subValue = formatPercent(d.satisfiedPer);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 9 && typeof d.complainNum !== 'undefined') {
|
||||||
|
kpiList.value[9].value = String(d.complainNum);
|
||||||
|
}
|
||||||
|
if (kpiList.value.length > 9 && typeof d.complainPer !== 'undefined') {
|
||||||
|
kpiList.value[9].subValue = formatPercent(d.complainPer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新任务栏数据
|
||||||
|
const taskMap = {
|
||||||
|
'revisit': { numKey: 'revisitNum', listKey: 'revisitList' },
|
||||||
|
'reconsider': { numKey: 'reconsiderNum', listKey: 'reconsiderList' },
|
||||||
|
'over': { numKey: 'overNum', listKey: 'overList' },
|
||||||
|
'complain': { numKey: 'complainNum', listKey: 'complainList' }
|
||||||
|
};
|
||||||
|
|
||||||
|
taskList.value.forEach(task => {
|
||||||
|
if (taskMap[task.type]) {
|
||||||
|
const { numKey, listKey } = taskMap[task.type];
|
||||||
|
if (typeof d[numKey] !== 'undefined') {
|
||||||
|
task.count = d[numKey];
|
||||||
|
}
|
||||||
|
if (Array.isArray(d[listKey])) {
|
||||||
|
task.listData = d[listKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新在线沟通数据
|
||||||
|
await fetchOnlineChatData();
|
||||||
|
|
||||||
|
// 如果当前有选中的任务类型,刷新对应列表
|
||||||
|
if (activeTaskType.value) {
|
||||||
|
const currentTask = taskList.value.find(task => task.type === activeTaskType.value);
|
||||||
|
if (currentTask) {
|
||||||
|
handleTaskClick(currentTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log('数据刷新成功');
|
console.log('数据刷新成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1519,6 +1715,52 @@
|
||||||
currentInput.complaintNotes = '';
|
currentInput.complaintNotes = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 司机搜索功能
|
||||||
|
const handleDriverSearch = () => {
|
||||||
|
const keyword = searchParams.fuzzySearch.trim();
|
||||||
|
|
||||||
|
// 清除之前的定时器
|
||||||
|
if (driverSearchTimer) {
|
||||||
|
clearTimeout(driverSearchTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keyword) {
|
||||||
|
uiState.showDriverList = false;
|
||||||
|
driverSearchResults.value = [];
|
||||||
|
searchParams.driverUid = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖处理,延迟300ms后发送请求
|
||||||
|
driverSearchTimer = setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(BASE_URL + 'third-api/search-driver', {
|
||||||
|
params: { keyword }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.data && res.data.code === 200 && Array.isArray(res.data.data)) {
|
||||||
|
driverSearchResults.value = res.data.data;
|
||||||
|
uiState.showDriverList = driverSearchResults.value.length > 0;
|
||||||
|
} else {
|
||||||
|
driverSearchResults.value = [];
|
||||||
|
uiState.showDriverList = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('司机搜索失败:', error);
|
||||||
|
driverSearchResults.value = [];
|
||||||
|
uiState.showDriverList = false;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择司机
|
||||||
|
const selectDriver = (driver) => {
|
||||||
|
searchParams.fuzzySearch = driver.realname;
|
||||||
|
searchParams.driverUid = driver.uid;
|
||||||
|
uiState.showDriverList = false;
|
||||||
|
driverSearchResults.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchParams,
|
searchParams,
|
||||||
uiState,
|
uiState,
|
||||||
|
|
@ -1533,6 +1775,7 @@
|
||||||
currentInput,
|
currentInput,
|
||||||
chatMessages,
|
chatMessages,
|
||||||
chatBoxRef,
|
chatBoxRef,
|
||||||
|
driverSearchResults,
|
||||||
handleProvinceChange,
|
handleProvinceChange,
|
||||||
handleCityChange,
|
handleCityChange,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
|
|
@ -1546,7 +1789,9 @@
|
||||||
closeModal,
|
closeModal,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
submitReview,
|
submitReview,
|
||||||
submitComplaint
|
submitComplaint,
|
||||||
|
handleDriverSearch,
|
||||||
|
selectDriver
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue