初步完成可视化页面

master
songxiangjie 2025-12-15 14:18:08 +08:00
parent 050d4f9fcf
commit 510fca9b8e
1 changed files with 259 additions and 14 deletions

View File

@ -720,6 +720,54 @@
::-webkit-scrollbar-thumb:hover {
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>
</head>
<body>
@ -756,8 +804,17 @@
<input type="text" id="order-number" v-model="searchParams.orderNumber" placeholder="订单号">
</div>
<div class="search-field" style="min-width: 180px;">
<input type="text" id="fuzzy-search" v-model="searchParams.fuzzySearch" placeholder="输入司机姓名">
<div class="search-field" style="min-width: 180px; position: relative;">
<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 class="date-range">
@ -826,13 +883,13 @@
<!-- 归属客服下拉框 -->
<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 v-for="item in staffOptions.customers" :key="item.id" :value="item.id">
{{ item.name }}
</option>
</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 v-for="item in staffOptions.salesmen" :key="item.id" :value="item.id">
{{ item.name }}
@ -1057,8 +1114,9 @@
timeType: '',
startDate: '',
endDate: '',
customerService: '',
salesman: ''
belongKefu: '',
belongSale: '',
driverUid: ''
});
// 界面状态
@ -1066,6 +1124,7 @@
showQueryBar: false,
showKpiBar: false,
isLoading: false,
showDriverList: false,
modals: {
chat: false,
review: false,
@ -1084,11 +1143,15 @@
salesmen: []
});
// 司机搜索结果
const driverSearchResults = ref([]);
let driverSearchTimer = null;
const kpiList = ref([
{ value: '-', label: '司机数', isDriver: true },
{ value: '156', label: '续费数' },
{ value: '45', label: '流失数' },
{ value: '987', subValue: '98.7%', label: '接单数/率' },
{ value: '-', subValue: '-', label: '接单数/率' },
{ value: '2,541', subValue: '95.2%', label: '订单数/率' },
{ value: '56', subValue: '2.2%', label: '不符数/率' },
{ value: '78', subValue: '3.1%', label: '超时数/率' },
@ -1156,6 +1219,14 @@
// 获取初始数据
fetchInitialData();
// 点击页面其他区域关闭司机下拉列表
document.addEventListener('click', (e) => {
const fuzzySearch = document.getElementById('fuzzy-search');
if (fuzzySearch && !fuzzySearch.contains(e.target)) {
uiState.showDriverList = false;
}
});
});
const normalizeProvinces = (list) => {
@ -1261,9 +1332,14 @@
if (Array.isArray(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);
}
// 填充业务员列表
if (d.salers) {
staffOptions.salesmen = normalizeStaffMap(d.salers);
}
@ -1331,6 +1407,12 @@
// 获取在线沟通数据
await fetchOnlineChatData();
// 默认选中待回访按钮
const revisitTask = taskList.value.find(task => task.type === 'revisit');
if (revisitTask) {
handleTaskClick(revisitTask);
}
} catch (error) {
console.error('Error fetching initial data:', error);
}
@ -1387,12 +1469,126 @@
uiState.isLoading = true;
console.log('正在搜索数据...');
// 重新请求接口数据
await fetchInitialData();
// 构建请求参数
const params = {
time_start: searchParams.startDate,
time_end: searchParams.endDate
};
// 添加省市区参数
if (searchParams.province) {
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;
}
// 清空当前选中的任务类型和案件列表
activeTaskType.value = '';
caseList.value = [];
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('数据刷新成功');
} catch (error) {
@ -1519,6 +1715,52 @@
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 {
searchParams,
uiState,
@ -1533,6 +1775,7 @@
currentInput,
chatMessages,
chatBoxRef,
driverSearchResults,
handleProvinceChange,
handleCityChange,
handleSearch,
@ -1546,7 +1789,9 @@
closeModal,
sendMessage,
submitReview,
submitComplaint
submitComplaint,
handleDriverSearch,
selectDriver
};
}
}).mount('#app');