初步完成可视化页面

master
songxiangjie 2025-12-16 15:48:47 +08:00
parent 510fca9b8e
commit 02cea41fb6
4 changed files with 23613 additions and 40 deletions

View File

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>客服工作台 - 优化版</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="./node_modules/axios/dist/axios.min.js"></script>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script src="./js/axios.min.js"></script>
<script src="./js/vue.global.js"></script>
<style>
* {
margin: 0;
@ -444,6 +444,8 @@
border-radius: 6px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08);
overflow: hidden;
max-height: 520px;
overflow-y: auto;
}
.table-header {
@ -913,7 +915,7 @@
</section>
<!-- 案件信息表格 -->
<section class="case-table-container">
<section class="case-table-container" @scroll.passive="handleTableScroll">
<div class="table-header">
<h3><i class="fas fa-list"></i> 案件信息处理列表</h3>
<div class="table-header-actions">
@ -931,7 +933,103 @@
<th style="width: 120px;">司机姓名</th>
<th style="width: 130px;">司机手机号</th>
<th style="width: 120px;">未读消息数</th>
<th style="width: 150px;">操作</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else-if="activeTaskType === 'reconsider'">
<th style="width: 120px;">订单号</th>
<th style="width: 100px;">创建时间</th>
<th style="width: 80px;">客服</th>
<th style="width: 80px;">项目</th>
<th style="width: 100px;">车牌</th>
<th style="width: 100px;">责任公司</th>
<th style="width: 80px;">司机</th>
<th style="width: 160px;">原因</th>
<th style="width: 80px;">扣罚</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else-if="activeTaskType === 'over'">
<th style="width: 120px;">订单号</th>
<th style="width: 130px;">创建时间</th>
<th style="width: 110px;">客服人员</th>
<th style="width: 90px;">服务项目</th>
<th style="width: 120px;">车牌号</th>
<th style="width: 140px;">服务商名称</th>
<th style="width: 180px;">司机账号</th>
<th style="width: 100px;">费用</th>
<th style="width: 120px;">到堪里程</th>
<th style="width: 110px;">补贴金额</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else-if="activeTaskType === 'revisit'">
<th style="width: 100px;">订单号</th>
<th style="width: 80px;">服务项目</th>
<th style="width: 100px;">业务来源</th>
<th style="width: 160px;">车主姓名</th>
<th style="width: 100px;">司机姓名</th>
<th style="width: 160px;">救援点</th>
<th style="width: 130px;">到勘时间</th>
<th style="width: 120px;">到勘时效</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else-if="activeTaskType === 'complain'">
<th style="width: 100px;">订单号</th>
<th style="width: 120px;">建单时间</th>
<th style="width: 80px;">项目</th>
<th style="width: 120px;">业务来源</th>
<th style="width: 100px;">姓名</th>
<th style="width: 110px;">车牌号</th>
<th style="width: 100px;">接单人</th>
<th style="width: 120px;">司机手机</th>
<th style="width: 90px;">状态</th>
<th style="width: 140px;">到堪</th>
<th style="width: 90px;">时效</th>
<th style="width: 80px;">服务分</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else-if="activeTaskType === 'false'">
<th style="width: 100px;">订单号</th>
<th style="width: 120px;">建单时间</th>
<th style="width: 80px;">项目</th>
<th style="width: 120px;">业务来源</th>
<th style="width: 100px;">姓名</th>
<th style="width: 110px;">车牌号</th>
<th style="width: 100px;">接单人</th>
<th style="width: 120px;">司机手机</th>
<th style="width: 90px;">状态</th>
<th style="width: 140px;">到堪</th>
<th style="width: 90px;">时效</th>
<th style="width: 80px;">服务分</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else-if="activeTaskType === 'reject'">
<th style="width: 100px;">订单号</th>
<th style="width: 120px;">建单时间</th>
<th style="width: 80px;">项目</th>
<th style="width: 120px;">业务来源</th>
<th style="width: 100px;">姓名</th>
<th style="width: 110px;">车牌号</th>
<th style="width: 100px;">接单人</th>
<th style="width: 120px;">司机手机</th>
<th style="width: 90px;">状态</th>
<th style="width: 140px;">到堪</th>
<th style="width: 90px;">时效</th>
<th style="width: 80px;">服务分</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else-if="activeTaskType === 'premium'">
<th style="width: 100px;">订单号</th>
<th style="width: 120px;">建单时间</th>
<th style="width: 80px;">项目</th>
<th style="width: 120px;">业务来源</th>
<th style="width: 100px;">姓名</th>
<th style="width: 110px;">车牌号</th>
<th style="width: 100px;">接单人</th>
<th style="width: 120px;">司机手机</th>
<th style="width: 90px;">状态</th>
<th style="width: 140px;">到堪</th>
<th style="width: 90px;">时效</th>
<th style="width: 80px;">服务分</th>
<th style="width: 180px;">操作</th>
</tr>
<tr v-else>
<th style="width: 100px;">订单号</th>
@ -957,14 +1055,180 @@
<td>{{ item.unreadCount }}</td>
<td>
<div class="action-buttons">
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="聊天">
<i class="fas fa-comment-dots"></i> 聊天
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
<button class="action-btn review-btn" @click="openModal('review', item)" title="跟进记录">
<i class="fas fa-phone-alt"></i> 跟进记录
</button>
</div>
</td>
</tr>
<tr v-else-if="activeTaskType === 'reconsider'" v-for="item in caseList" :key="item.orderNo">
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.createTime }}</td>
<td>{{ item.kefu }}</td>
<td>{{ item.service }}</td>
<td>{{ item.carNumber }}</td>
<td>{{ item.company }}</td>
<td>{{ item.driverName }}</td>
<td>{{ item.reason }}</td>
<td>{{ item.money }}</td>
<td>
<div class="action-buttons">
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
<button class="action-btn review-btn" @click="showPunishDetail(item)" title="跟进记录">
<i class="fas fa-phone-alt"></i> 跟进记录
</button>
</div>
</td>
</tr>
<tr v-else-if="activeTaskType === 'over'" v-for="item in caseList" :key="item.orderNo">
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.createTime }}</td>
<td>{{ item.kefu }}</td>
<td>{{ item.service }}</td>
<td>{{ item.carNumber }}</td>
<td>{{ item.company }}</td>
<td>{{ item.driverName }}</td>
<td>{{ item.throwPrice }}</td>
<td>{{ item.takeDistance }}</td>
<td>{{ item.subsidy }}</td>
<td>
<div class="action-buttons">
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
<button class="action-btn review-btn" @click="openModal('review', item)" title="跟进记录">
<i class="fas fa-phone-alt"></i> 跟进记录
</button>
</div>
</td>
</tr>
<tr v-else-if="activeTaskType === 'revisit'" v-for="item in caseList" :key="item.orderNo">
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.service }}</td>
<td>{{ item.source }}</td>
<td>{{ item.ownerName }}</td>
<td>{{ item.driverName }}</td>
<td>{{ item.location }}</td>
<td>{{ item.arriveTime }}</td>
<td v-html="item.expireTime"></td>
<td>
<div class="action-buttons">
<button class="action-btn review-btn" @click="openOrderRating(item.orderNo)" title="回访">
<i class="fas fa-phone-alt"></i> 回访
</button>
</div>
</td>
</tr>
<tr v-else-if="activeTaskType === 'complain'" v-for="item in caseList" :key="item.orderNo">
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.createTime }}</td>
<td>{{ item.service }}</td>
<td>{{ item.source }}</td>
<td>{{ item.ownerName }}</td>
<td>{{ item.carNumber }}</td>
<td>{{ item.driverName }}</td>
<td>{{ item.driverPhone }}</td>
<td>{{ item.statusText }}</td>
<td>{{ item.expireAddress }}</td>
<td v-html="item.expireTime"></td>
<td>{{ item.serviceScore }}</td>
<td>
<div class="action-buttons">
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
<button class="action-btn review-btn" @click="openAppealDetail(item.orderNo)" title="查看工单">
<i class="fas fa-file-alt"></i> 查看工单
</button>
</div>
</td>
</tr>
<tr v-else-if="activeTaskType === 'false'" v-for="item in caseList" :key="item.orderNo">
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.createTime }}</td>
<td>{{ item.service }}</td>
<td>{{ item.source }}</td>
<td>{{ item.ownerName }}</td>
<td>{{ item.carNumber }}</td>
<td>{{ item.driverName }}</td>
<td>{{ item.driverPhone }}</td>
<td>{{ item.statusText }}</td>
<td>{{ item.expireAddress }}</td>
<td v-html="item.expireTime"></td>
<td>{{ item.serviceScore }}</td>
<td>
<div class="action-buttons">
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
</div>
</td>
</tr>
<tr v-else-if="activeTaskType === 'reject'" v-for="item in caseList" :key="item.orderNo">
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.createTime }}</td>
<td>{{ item.service }}</td>
<td>{{ item.source }}</td>
<td>{{ item.ownerName }}</td>
<td>{{ item.carNumber }}</td>
<td>{{ item.driverName }}</td>
<td>{{ item.driverPhone }}</td>
<td>{{ item.statusText }}</td>
<td>{{ item.expireAddress }}</td>
<td v-html="item.expireTime"></td>
<td>{{ item.serviceScore }}</td>
<td>
<div class="action-buttons">
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
</div>
</td>
</tr>
<tr v-else-if="activeTaskType === 'premium'" v-for="item in caseList" :key="item.orderNo">
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.createTime }}</td>
<td>{{ item.service }}</td>
<td>{{ item.source }}</td>
<td>{{ item.ownerName }}</td>
<td>{{ item.carNumber }}</td>
<td>{{ item.driverName }}</td>
<td>{{ item.driverPhone }}</td>
<td>{{ item.statusText }}</td>
<td>{{ item.expireAddress }}</td>
<td v-html="item.expireTime"></td>
<td>{{ item.serviceScore }}</td>
<td>
<div class="action-buttons">
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
</div>
</td>
</tr>
<tr v-else v-for="item in caseList" :key="item.orderNo">
<td>{{ item.orderNo }}</td>
<td>
<a href="javascript:void(0)" @click="openOrderDetail(item.orderNo)">{{ item.orderNo }}</a>
</td>
<td>{{ item.service }}</td>
<td>{{ item.source }}</td>
<td>{{ item.ownerName }}</td>
@ -973,20 +1237,24 @@
<td>{{ item.driverPhone }}</td>
<td>{{ item.location }}</td>
<td>{{ item.distance }}</td>
<td>{{ item.duration }}</td>
<td v-html="item.duration"></td>
<td>{{ item.score }}</td>
<td>{{ item.satisfaction }}</td>
<td>
<div class="action-buttons">
<button class="action-btn review-btn" @click="openModal('review', item)" title="回访">
<i class="fas fa-phone-alt"></i> 回访
</button>
<button class="action-btn chat-btn" @click="openModal('chat', item)" title="聊天">
<i class="fas fa-comment-dots"></i> 聊天
</button>
<button class="action-btn complaint-btn" @click="openModal('complaint', item)" title="投诉">
<i class="fas fa-gavel"></i> 投诉
</button>
<template v-if="activeTaskType === 'revisit'">
<button class="action-btn review-btn" @click="openModal('review', item)" title="回访">
<i class="fas fa-phone-alt"></i> 回访
</button>
</template>
<template v-else>
<button class="action-btn chat-btn" @click="openChatWindow(item.driverId)" title="沟通">
<i class="fas fa-comment-dots"></i> 沟通
</button>
<button class="action-btn review-btn" @click="openModal('review', item)" title="跟进记录">
<i class="fas fa-phone-alt"></i> 跟进记录
</button>
</template>
</div>
</td>
</tr>
@ -1095,6 +1363,47 @@
</div>
</div>
</div>
<!-- 不符跟进-跟进记录模态框 -->
<div v-if="uiState.modals.punish" class="modal">
<div class="modal-content" style="width: 1200px; max-width: 90%;">
<div class="modal-header">
<h3><i class="fas fa-list"></i> 跟进记录</h3>
<button class="close-btn" @click="closeModal('punish')">&times;</button>
</div>
<div class="modal-body">
<table style="table-layout: fixed; width: 100%;">
<thead>
<tr>
<th style="width: 10%;">创建时间</th>
<th style="width: 10%;">订单号</th>
<th style="width: 10%;">服务项目</th>
<th style="width: 10%;">责任司机</th>
<th style="width: 20%;">原因</th>
<th style="width: 10%;">复议情况</th>
<th style="width: 10%;">司机复议</th>
<th style="width: 20%;">客服复议</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in punishRecords" :key="index">
<td style="word-wrap: break-word; white-space: normal;">{{ row.createTime }}</td>
<td style="word-wrap: break-word; white-space: normal;">{{ row.orderId }}</td>
<td style="word-wrap: break-word; white-space: normal;">{{ row.serviceType }}</td>
<td style="word-wrap: break-word; white-space: normal;">{{ row.uid }}</td>
<td style="word-wrap: break-word; white-space: normal;">{{ row.remark }}</td>
<td style="word-wrap: break-word; white-space: normal;">{{ formatReviewStatus(row.type, row.typeId) }}</td>
<td style="word-wrap: break-word; white-space: normal;">{{ row.driverReason }}</td>
<td style="word-wrap: break-word; white-space: normal;">{{ row.reviewReason }}</td>
</tr>
<tr v-if="!punishRecords.length">
<td colspan="8" style="text-align: center;">暂无数据</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
@ -1103,7 +1412,7 @@
createApp({
setup() {
// 基础URL配置
const BASE_URL = 'http://backend.jjdev.cn/';
const BASE_URL = 'https://backend.jjsos.cn/';
const searchParams = reactive({
province: '',
@ -1128,7 +1437,8 @@
modals: {
chat: false,
review: false,
complaint: false
complaint: false,
punish: false
}
});
@ -1143,21 +1453,23 @@
salesmen: []
});
const punishRecords = ref([]);
// 司机搜索结果
const driverSearchResults = ref([]);
let driverSearchTimer = null;
const kpiList = ref([
{ value: '-', label: '司机数', isDriver: true },
{ value: '156', label: '续费数' },
{ value: '45', label: '流失数' },
{ value: '-', label: '续费数' },
{ value: '-', label: '流失数' },
{ value: '-', subValue: '-', label: '接单数/率' },
{ value: '2,541', subValue: '95.2%', label: '订单数/率' },
{ value: '56', subValue: '2.2%', label: '不符数/率' },
{ value: '78', subValue: '3.1%', label: '超时数/率' },
{ value: '342', subValue: '13.4%', label: '跟进数/率' },
{ value: '2,123', subValue: '83.5%', label: '好评数/率' },
{ value: '34', subValue: '1.3%', label: '投诉数/率' }
{ value: '-', subValue: '-', label: '订单数/率' },
{ value: '-', subValue: '-', label: '不符数/率' },
{ value: '-', subValue: '-', label: '超时数/率' },
{ value: '-', subValue: '-', label: '跟进数/率' },
{ value: '-', subValue: '-', label: '好评数/率' },
{ value: '-', subValue: '-', label: '投诉数/率' }
]);
// 任务数据
@ -1186,6 +1498,11 @@
// 案件列表数据 - 初始为空,将从任务按钮点击后填充
const caseList = ref([]);
const pagination = reactive({
page: 1,
pageSize: 20,
hasMore: true
});
// 当前激活的任务类型
const activeTaskType = ref('');
@ -1269,6 +1586,39 @@
return `${num}%`;
};
const formatDateYMD = (value) => {
if (!value) return '-';
let timestamp = Number(value);
if (Number.isNaN(timestamp)) return String(value);
if (String(timestamp).length === 10) {
timestamp = timestamp * 1000;
}
const date = new Date(timestamp);
if (Number.isNaN(date.getTime())) return '-';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const formatDateTime = (value) => {
if (!value) return '-';
let timestamp = Number(value);
if (Number.isNaN(timestamp)) return String(value);
if (String(timestamp).length === 10) {
timestamp = timestamp * 1000;
}
const date = new Date(timestamp);
if (Number.isNaN(date.getTime())) return '-';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const second = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
};
const fetchFollowUpCount = async (driverTotal) => {
try {
const res = await axios.get('http://sos.chat.cn/src/getChatDriverCount.php');
@ -1389,20 +1739,35 @@
'revisit': { numKey: 'revisitNum', listKey: 'revisitList' },
'reconsider': { numKey: 'reconsiderNum', listKey: 'reconsiderList' },
'over': { numKey: 'overNum', listKey: 'overList' },
'complain': { numKey: 'complainNum', listKey: 'complainList' }
'complain': { numKey: 'complainNum', listKey: 'complainList' },
'false': { numKey: 'fakeNum', listKey: 'fakeList' },
'reject': { numKey: 'rejectNum', listKey: 'rejectList' },
'premium': { numKey: 'premiumNum', listKey: 'premiumList' }
};
taskList.value.forEach(task => {
if (taskMap[task.type]) {
const { numKey, listKey } = taskMap[task.type];
if (typeof d[numKey] !== 'undefined') {
if (typeof d[numKey] !== 'undefined' && pagination.page === 1) {
task.count = d[numKey];
}
if (Array.isArray(d[listKey])) {
task.listData = d[listKey];
if (pagination.page === 1) {
task.listData = d[listKey];
} else if (task.type === activeTaskType.value) {
task.listData = [...task.listData, ...d[listKey]];
}
}
}
});
// 更新是否还有更多数据
const currentTaskConfig = taskMap[activeTaskType.value];
if (currentTaskConfig) {
const { listKey } = currentTaskConfig;
const currentList = Array.isArray(d[listKey]) ? d[listKey] : [];
pagination.hasMore = currentList.length >= pagination.pageSize;
}
}
// 获取在线沟通数据
@ -1452,6 +1817,103 @@
fetchAreas('county', searchParams.city);
};
const loadMoreCases = async () => {
if (!pagination.hasMore || uiState.isLoading) return;
if (!activeTaskType.value) return;
if (activeTaskType.value === 'online') return;
const cateMap = {
revisit: 'revisit',
reconsider: 'reconsider',
over: 'over',
complain: 'complain',
false: 'fake',
reject: 'reject',
premium: 'premium'
};
const cate = cateMap[activeTaskType.value];
if (!cate) return;
try {
uiState.isLoading = true;
pagination.page += 1;
const params = {
time_start: searchParams.startDate,
time_end: searchParams.endDate,
page: pagination.page,
page_size: pagination.pageSize,
cate
};
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;
}
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;
const taskMap = {
'revisit': { numKey: 'revisitNum', listKey: 'revisitList' },
'reconsider': { numKey: 'reconsiderNum', listKey: 'reconsiderList' },
'over': { numKey: 'overNum', listKey: 'overList' },
'complain': { numKey: 'complainNum', listKey: 'complainList' },
'false': { numKey: 'fakeNum', listKey: 'fakeList' },
'reject': { numKey: 'rejectNum', listKey: 'rejectList' },
'premium': { numKey: 'premiumNum', listKey: 'premiumList' }
};
const currentTaskConfig = taskMap[activeTaskType.value];
if (currentTaskConfig) {
const { listKey } = currentTaskConfig;
const newList = Array.isArray(d[listKey]) ? d[listKey] : [];
const currentTask = taskList.value.find(task => task.type === activeTaskType.value);
if (currentTask) {
currentTask.listData = [...currentTask.listData, ...newList];
handleTaskClick(currentTask);
}
pagination.hasMore = newList.length >= pagination.pageSize;
}
}
} catch (error) {
console.error('加载更多失败:', error);
} finally {
uiState.isLoading = false;
}
};
const handleTableScroll = (e) => {
const el = e.target;
if (el.scrollHeight - el.scrollTop - el.clientHeight < 50) {
loadMoreCases();
}
};
const handleSearch = async () => {
// 验证时间范围
if (!searchParams.startDate || !searchParams.endDate) {
@ -1469,6 +1931,10 @@
uiState.isLoading = true;
console.log('正在搜索数据...');
// 重置分页
pagination.page = 1;
pagination.hasMore = true;
// 构建请求参数
const params = {
time_start: searchParams.startDate,
@ -1500,6 +1966,10 @@
params.apptimes = searchParams.timeType;
}
// 分页参数
params.page = pagination.page;
params.page_size = pagination.pageSize;
console.log('搜索参数:', params);
const res = await axios.get(BASE_URL + 'third-api/cs-chart-info', {
@ -1563,7 +2033,10 @@
'revisit': { numKey: 'revisitNum', listKey: 'revisitList' },
'reconsider': { numKey: 'reconsiderNum', listKey: 'reconsiderList' },
'over': { numKey: 'overNum', listKey: 'overList' },
'complain': { numKey: 'complainNum', listKey: 'complainList' }
'complain': { numKey: 'complainNum', listKey: 'complainList' },
'false': { numKey: 'fakeNum', listKey: 'fakeList' },
'reject': { numKey: 'rejectNum', listKey: 'rejectList' },
'premium': { numKey: 'premiumNum', listKey: 'premiumList' }
};
taskList.value.forEach(task => {
@ -1611,24 +2084,107 @@
const navigateTo = (url) => {
if (!url) return;
window.open(url, '_blank');
};
const showPunishDetail = async (item) => {
const punishId = item.id || item.punish_id || item.punishId;
if (!punishId) {
alert('未找到该记录的ID');
return;
}
try {
const res = await axios.get(BASE_URL + 'third-api/show-punish', {
params: { id: punishId }
});
let raw = null;
if (res.data && res.data.data !== undefined) {
raw = res.data.data;
} else {
raw = res.data;
}
let list = [];
if (Array.isArray(raw)) {
list = raw;
} else if (raw && typeof raw === 'object') {
list = [raw];
} else {
list = [];
}
punishRecords.value = list.map(row => ({
createTime: row.create_time ? formatDateTime(row.create_time) : (row.createTime || '-'),
orderId: row.order_id || row.orderId || '-',
serviceType: row.service_type || row.serviceType || '-',
uid: row.uid || row.driver_uid || '-',
remark: row.remark || '-',
type: row.type || '-', // 添加 type 字段
typeId: row.type_id || row.typeId || '-',
driverReason: row.driver_reason || row.driverReason || '-',
reviewReason: row.review_reason || row.reviewReason || '-'
}));
uiState.modals.punish = true;
} catch (error) {
console.error('获取跟进记录失败:', error);
alert('获取跟进记录失败,请稍后重试');
}
};
const openOrderDetail = (orderNo) => {
if (!orderNo || orderNo === '-') {
alert('订单号无效');
return;
}
const url = `https://backendt.jjsos.cn/disp5/pop-order-detail?order_id=${encodeURIComponent(orderNo)}`;
window.open(url, '_blank');
};
const openOrderRating = (orderNo) => {
if (!orderNo || orderNo === '-') {
alert('订单号无效');
return;
}
const url = `https://backendt.jjsos.cn/disp5/pop-order-rating?order_id=${encodeURIComponent(orderNo)}`;
window.open(url, '_blank');
};
const openAppealDetail = (orderNo) => {
if (!orderNo || orderNo === '-') {
alert('订单号无效');
return;
}
const url = `http://homenew.jjdev.cn/appeal/details-new?order_id=${encodeURIComponent(orderNo)}&category=2`;
window.open(url, '_blank');
};
// 格式化复议情况
const formatReviewStatus = (type, typeId) => {
if (type == 1) {
if (typeId == 1) {
return '司机同意';
} else {
return '复议通过';
}
} else if (type == 2) {
return '复议拒绝';
} else if (type == 3) {
return '复议中';
}
return '-';
};
// 处理任务按钮点击,加载对应的案件列表
const handleTaskClick = (task) => {
activeTaskType.value = task.type;
// 将列表数据映射到案件列表格式
if (Array.isArray(task.listData)) {
// 在线沟通使用不同的数据映射
if (task.type === 'online') {
caseList.value = task.listData.map(item => ({
driverId: item.driver_id || '-',
orderNo: item.driver_id || '-', // 司机编号
driverName: item.user_name || '-', // 司机姓名
driverPhone: item.mobile || '-', // 司机手机号
unreadCount: item.unread_count || 0, // 未读消息数
// 其他字段设为空,因为在线沟通不需要这些字段
orderNo: item.driver_id || '-',
driverName: item.user_name || '-',
driverPhone: item.mobile || '-',
unreadCount: item.unread_count || 0,
service: '-',
source: '-',
ownerName: '-',
@ -1639,9 +2195,183 @@
score: '-',
satisfaction: '-'
}));
} else if (task.type === 'reconsider') {
caseList.value = task.listData.map(item => {
const id = item.id || item.punish_id || item.punishId || null;
const createTime = formatDateYMD(item.create_time || item.createTime);
const serviceType = item.service_type || item.serviceType || '-';
const driverName = item.realname || item.driver_name || item.driverName || '-';
const driverId = item.uid || item.driver_id || item.driver_uid || item.driverUid || '-';
const orderNo = item.order_id || item.orderId || '-';
const kefu = item.creator_uid || item.kefu || '-';
const carNumber = item.car_number || item.carNumber || '-';
const company = item.cid || item.company || '-';
const reason = item.remark || item.reason || '-';
const money = item.money || '-';
const ownerName = item.username || item.owner_name || item.ownerName || '-';
return {
id,
driverId,
createTime,
orderNo,
kefu,
service: serviceType,
carNumber,
company,
driverName,
reason,
money,
ownerName,
ownerPhone: '-',
source: '-',
location: '-',
distance: '-',
duration: '-',
score: '-',
satisfaction: '-'
};
});
} else if (task.type === 'over') {
caseList.value = task.listData.map(item => {
const orderNo = item.order_id || item.orderId || '-';
const createTime = formatDateYMD(item.create_time || item.createTime);
const serviceType = item.service_type || item.serviceType || '-';
const carNumber = item.car_number || item.carNumber || '-';
const company = item.to_cid || item.company || '-';
const driverAccount = item.to_uid || item.driver_uid || item.driverUid || '-';
const driverId = item.driver_uid || item.driverUid || driverAccount || '-';
const throwPrice = item.throw_price || item.throwPrice || '-';
const takeDistance = item.take_distance || item.takeDistance || '-';
const subsidy = item.subsidy || '-';
const kefu = item.creator_uid || item.kefu || '-';
return {
driverId,
orderNo,
createTime,
kefu,
service: serviceType,
carNumber,
company,
driverName: driverAccount,
throwPrice,
takeDistance,
subsidy,
ownerName: '-',
ownerPhone: '-',
source: '-',
location: '-',
distance: '-',
duration: '-',
score: '-',
satisfaction: '-'
};
});
} else if (task.type === 'revisit') {
caseList.value = task.listData.map(item => {
const orderNo = item.order_id || item.orderId || '-';
const service = item.service_type || item.serviceType || '-';
const source = item.src_cid || item.source || '-';
const ownerMobile = item.owner_mobile || item.ownerMobile || '';
const carNumber = item.car_number || item.carNumber || '';
const ownerName = ownerMobile || carNumber ? [ownerMobile, carNumber].filter(Boolean).join('-') : '-';
const driverName = item.to_uid || item.driver_name || item.driverName || '-';
const driverId = item.to_uid || item.driver_id || item.driverId || '-';
const location = item.car_addr || item.location || item.rescue_point || '-';
const arriveTime = item.arrive_time ? formatDateTime(item.arrive_time) : (item.arriveTime || '-');
const expireTime = item.expire_time || item.expireTime || '-';
return {
driverId,
orderNo,
service,
source,
ownerName,
driverName,
location,
arriveTime,
expireTime
};
});
} else if (task.type === 'complain') {
caseList.value = task.listData.map(item => {
const orderNo = item.order_id || item.orderId || '-';
const createTime = item.create_time ? formatDateYMD(item.create_time) : (item.createTime || '-');
const service = item.service_type || item.serviceType || '-';
const source = item.src_company_name || item.srcCompanyName || item.source || '-';
const ownerName = item.owner_name || item.ownerName || '-';
const carNumber = item.car_number || item.carNumber || '-';
const driverName = item.driver_realname || item.driverRealname || item.driver_name || item.driverName || '-';
const driverNameNew = item.driver_name_new || item.driverNameNew || '';
let driverPhone = '';
if (driverNameNew && driverNameNew.includes('-')) {
const parts = driverNameNew.split('-');
driverPhone = parts[parts.length - 1] || '';
}
if (!driverPhone) {
driverPhone = item.driver_mobile || item.driverMobile || item.driver_phone || item.driverPhone || '-';
}
const driverId = item.driver_uid || item.driverUid || item.to_uid || item.uid || item.driver_id || item.driverId || '-';
const statusText = item.status_text || item.statusText || '-';
const expireAddress = item.expire_address || item.expireAddress || '-';
const expireTime = item.expire_time || item.expireTime || '-';
const serviceScore = item.service_score || item.serviceScore || item.score || '-';
return {
driverId,
orderNo,
createTime,
service,
source,
ownerName,
carNumber,
driverName,
driverPhone,
statusText,
expireAddress,
expireTime,
serviceScore
};
});
} else if (task.type === 'false' || task.type === 'reject' || task.type === 'premium') {
caseList.value = task.listData.map(item => {
const orderNo = item.order_id || item.orderId || '-';
const createTime = item.create_time ? formatDateYMD(item.create_time) : (item.createTime || '-');
const service = item.service_type || item.serviceType || '-';
const source = item.src_company_name || item.srcCompanyName || item.source || '-';
const ownerName = item.owner_name || item.ownerName || '-';
const carNumber = item.car_number || item.carNumber || '-';
const driverName = item.driver_realname || item.driverRealname || item.driver_name || item.driverName || '-';
const driverNameNew = item.driver_name_new || item.driverNameNew || '';
let driverPhone = '';
if (driverNameNew && driverNameNew.includes('-')) {
const parts = driverNameNew.split('-');
driverPhone = parts[parts.length - 1] || '';
}
if (!driverPhone) {
driverPhone = item.driver_mobile || item.driverMobile || item.driver_phone || item.driverPhone || '-';
}
const driverId = item.driver_uid || item.driverUid || item.to_uid || item.uid || item.driver_id || item.driverId || '-';
const statusText = item.status_text || item.statusText || '-';
const expireAddress = item.expire_address || item.expireAddress || '-';
const expireTime = item.expire_time || item.expireTime || '-';
const serviceScore = item.service_score || item.serviceScore || item.score || '-';
return {
driverId,
orderNo,
createTime,
service,
source,
ownerName,
carNumber,
driverName,
driverPhone,
statusText,
expireAddress,
expireTime,
serviceScore
};
});
} else {
// 其他任务类型使用原有的映射
caseList.value = task.listData.map(item => ({
driverId: item.driver_id || item.driverId || item.driver_uid || item.driverUid || '-',
orderNo: item.order_id || item.orderId || '-',
service: item.service_type || item.serviceType || '-',
source: item.source || item.company_name || '-',
@ -1671,7 +2401,7 @@
return;
}
// 打开聊天窗口,固定参数
const chatUrl = `https://sos-chat.jjsos.cn/chatByKefu.php?from_user_id=22635&to_user_id=65031&from_role=3&to_role=2&driver_id=${driverId}`;
const chatUrl = `https://sos-chat.jjsos.cn/chatByKefu.php?from_user_id=22635&to_user_id=${driverId}&from_role=3&to_role=2&driver_id=${driverId}`;
window.open(chatUrl, '_blank');
};
@ -1776,12 +2506,19 @@
chatMessages,
chatBoxRef,
driverSearchResults,
punishRecords,
handleProvinceChange,
handleCityChange,
handleSearch,
toggleKpiBar,
toggleQueryBar,
navigateTo,
showPunishDetail,
openOrderDetail,
openOrderRating,
handleTableScroll,
formatReviewStatus,
openAppealDetail,
handleTaskClick,
openChatWindow,
exportData,

View File

@ -8,7 +8,7 @@
<script src="./js/echarts.min.js"></script>
<script src="./js/dayjs.min.js"></script>
<script src="./js/isoWeek.js"></script>
<script src="./node_modules/axios/dist/axios.min.js"></script>
<script src="./js/axios.min.js"></script>
<style>
:root {
--primary: #3388ff;

4471
js/axios.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

18365
js/vue.global.js Normal file

File diff suppressed because it is too large Load Diff