2582 lines
108 KiB
HTML
2582 lines
108 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-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="./js/axios.min.js"></script>
|
||
<script src="./js/vue.global.js"></script>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||
}
|
||
|
||
body {
|
||
background-color: #f5f9f5;
|
||
color: #333;
|
||
line-height: 1.5;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1800px;
|
||
margin: 0 auto;
|
||
padding: 15px;
|
||
}
|
||
|
||
/* 绿色主题颜色定义 */
|
||
:root {
|
||
--primary-green: #2e7d32;
|
||
--secondary-green: #4caf50;
|
||
--light-green: #e8f5e9;
|
||
--medium-green: #81c784;
|
||
--dark-green: #1b5e20;
|
||
--text-color: #333;
|
||
--border-color: #c8e6c9;
|
||
}
|
||
|
||
/* 头部样式 */
|
||
header {
|
||
background-color: var(--primary-green);
|
||
color: white;
|
||
padding: 12px 15px;
|
||
border-radius: 6px 6px 0 0;
|
||
box-shadow: 0 2px 8px rgba(46, 125, 50, 0.2);
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
header h1 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
header h1 i {
|
||
color: #e8f5e9;
|
||
}
|
||
|
||
/* 搜索栏紧凑样式 */
|
||
.search-bar {
|
||
background-color: white;
|
||
padding: 15px;
|
||
border-radius: 6px;
|
||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08);
|
||
margin-bottom: 15px;
|
||
border-left: 4px solid var(--secondary-green);
|
||
}
|
||
|
||
.search-row {
|
||
display: flex;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.search-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.search-field.compact {
|
||
min-width: 0;
|
||
}
|
||
|
||
.search-field label {
|
||
font-weight: 600;
|
||
margin-bottom: 5px;
|
||
color: var(--dark-green);
|
||
font-size: 0.85rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.search-field input, .search-field select {
|
||
padding: 7px 9px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 3px;
|
||
font-size: 0.85rem;
|
||
transition: border-color 0.3s;
|
||
min-width: 120px;
|
||
height: 32px;
|
||
}
|
||
|
||
.search-field.compact input, .search-field.compact select {
|
||
min-width: 100px;
|
||
}
|
||
|
||
.search-field input:focus, .search-field select:focus {
|
||
outline: none;
|
||
border-color: var(--secondary-green);
|
||
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
|
||
}
|
||
|
||
.date-range {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 5px;
|
||
}
|
||
|
||
.date-range .search-field {
|
||
min-width: 140px;
|
||
}
|
||
|
||
/* 按钮样式 */
|
||
.btn {
|
||
padding: 7px 12px;
|
||
border: none;
|
||
border-radius: 3px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
transition: all 0.3s;
|
||
font-size: 0.85rem;
|
||
height: 32px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: var(--primary-green);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: var(--dark-green);
|
||
}
|
||
|
||
.btn-primary:disabled {
|
||
background-color: #9e9e9e;
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: var(--light-green);
|
||
color: var(--dark-green);
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background-color: var(--medium-green);
|
||
}
|
||
|
||
.btn-more {
|
||
background-color: #f1f8e9;
|
||
color: var(--dark-green);
|
||
}
|
||
|
||
.btn-kpi {
|
||
background-color: #e3f2fd;
|
||
color: #1565c0;
|
||
}
|
||
|
||
.btn-kpi:hover {
|
||
background-color: #bbdefb;
|
||
}
|
||
|
||
.btn-icon {
|
||
padding: 7px;
|
||
min-width: 32px;
|
||
}
|
||
|
||
/* 高级查询栏 - 默认隐藏 */
|
||
.query-bar {
|
||
background-color: white;
|
||
padding: 0;
|
||
border-radius: 6px;
|
||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08);
|
||
margin-bottom: 15px;
|
||
overflow: hidden;
|
||
max-height: 0;
|
||
opacity: 0;
|
||
transition: all 0.5s ease;
|
||
border-left: 4px solid var(--medium-green);
|
||
}
|
||
|
||
.query-bar.show {
|
||
padding: 15px;
|
||
max-height: 180px;
|
||
opacity: 1;
|
||
margin-bottom: 15px;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.query-bar h3 {
|
||
color: var(--dark-green);
|
||
margin-bottom: 12px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 1rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.query-items {
|
||
display: flex;
|
||
flex-wrap: nowrap;
|
||
gap: 10px;
|
||
min-width: 1500px;
|
||
}
|
||
|
||
.query-btn {
|
||
padding: 8px 15px;
|
||
background-color: var(--light-green);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
color: var(--dark-green);
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 120px;
|
||
text-align: center;
|
||
flex-shrink: 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.query-btn:hover {
|
||
background-color: var(--medium-green);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.query-btn i {
|
||
font-size: 1.2rem;
|
||
margin-bottom: 5px;
|
||
color: var(--primary-green);
|
||
}
|
||
|
||
.query-btn span {
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
/* KPI数据栏 - 默认隐藏 */
|
||
.kpi-bar {
|
||
background-color: white;
|
||
padding: 0;
|
||
border-radius: 6px;
|
||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08);
|
||
margin-bottom: 15px;
|
||
overflow: hidden;
|
||
max-height: 0;
|
||
opacity: 0;
|
||
transition: all 0.5s ease;
|
||
border-left: 4px solid #1565c0;
|
||
}
|
||
|
||
.kpi-bar.show {
|
||
padding: 15px;
|
||
max-height: 500px;
|
||
opacity: 1;
|
||
margin-bottom: 15px;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.kpi-bar h3 {
|
||
color: #1565c0;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid #bbdefb;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 1rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.kpi-items {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
}
|
||
|
||
.kpi-item {
|
||
padding: 10px;
|
||
background-color: #e3f2fd;
|
||
border-radius: 4px;
|
||
text-align: center;
|
||
border: 1px solid #bbdefb;
|
||
min-width: 150px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.kpi-item.driver {
|
||
background-color: #e8f5e9;
|
||
border-color: #c8e6c9;
|
||
}
|
||
|
||
.kpi-item.driver .value {
|
||
color: var(--primary-green);
|
||
}
|
||
|
||
.kpi-item.driver .label {
|
||
color: var(--dark-green);
|
||
}
|
||
|
||
.kpi-value-wrapper {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 5px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.kpi-item .value {
|
||
font-size: 1.4rem;
|
||
font-weight: 700;
|
||
color: #1565c0;
|
||
}
|
||
|
||
.kpi-item .sub-value {
|
||
font-size: 0.9rem;
|
||
color: #2196f3;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.kpi-item .label {
|
||
font-size: 0.85rem;
|
||
color: #0d47a1;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 任务栏样式 */
|
||
.task-bar {
|
||
background-color: white;
|
||
padding: 15px;
|
||
border-radius: 6px;
|
||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08);
|
||
margin-bottom: 15px;
|
||
border-left: 4px solid var(--secondary-green);
|
||
}
|
||
|
||
.task-bar h3 {
|
||
color: var(--dark-green);
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.task-items {
|
||
display: flex;
|
||
flex-wrap: nowrap;
|
||
gap: 10px;
|
||
overflow-x: auto;
|
||
padding-bottom: 5px;
|
||
}
|
||
|
||
.task-item {
|
||
padding: 10px 12px;
|
||
background-color: var(--light-green);
|
||
border-radius: 4px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
border: 1px solid var(--border-color);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 110px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.task-item:hover {
|
||
background-color: var(--medium-green);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.task-item.active {
|
||
background-color: var(--secondary-green);
|
||
border-color: var(--primary-green);
|
||
box-shadow: 0 3px 8px rgba(46, 125, 50, 0.3);
|
||
}
|
||
|
||
.task-item.active span {
|
||
color: white;
|
||
}
|
||
|
||
.task-item.active i {
|
||
color: white;
|
||
}
|
||
|
||
.task-item i {
|
||
font-size: 1.2rem;
|
||
color: var(--primary-green);
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.task-item span {
|
||
display: block;
|
||
font-weight: 600;
|
||
color: var(--dark-green);
|
||
font-size: 0.9rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.task-item small {
|
||
font-size: 0.75rem;
|
||
color: #666;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* 案件信息表格 */
|
||
.case-table-container {
|
||
background-color: white;
|
||
border-radius: 6px;
|
||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08);
|
||
overflow: hidden;
|
||
max-height: 520px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.table-header {
|
||
background-color: var(--primary-green);
|
||
color: white;
|
||
padding: 12px 15px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.table-header h3 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.table-header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
table-layout: fixed;
|
||
}
|
||
|
||
thead {
|
||
background-color: var(--light-green);
|
||
}
|
||
|
||
th {
|
||
padding: 10px 8px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: var(--dark-green);
|
||
border-bottom: 2px solid var(--border-color);
|
||
font-size: 0.85rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
td {
|
||
padding: 8px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
font-size: 0.85rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
tbody tr:hover {
|
||
background-color: #f9fdf9;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 4px 8px;
|
||
border-radius: 3px;
|
||
font-size: 0.8rem;
|
||
cursor: pointer;
|
||
border: none;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
transition: all 0.3s;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.chat-btn {
|
||
background-color: #e8f5e9;
|
||
color: var(--dark-green);
|
||
}
|
||
|
||
.chat-btn:hover {
|
||
background-color: var(--medium-green);
|
||
}
|
||
|
||
.review-btn {
|
||
background-color: #e3f2fd;
|
||
color: #1565c0;
|
||
}
|
||
|
||
.review-btn:hover {
|
||
background-color: #bbdefb;
|
||
}
|
||
|
||
.complaint-btn {
|
||
background-color: #ffebee;
|
||
color: #c62828;
|
||
}
|
||
|
||
.complaint-btn:hover {
|
||
background-color: #ffcdd2;
|
||
}
|
||
|
||
/* 模态框样式 */
|
||
.modal {
|
||
display: flex;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
z-index: 1000;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: white;
|
||
border-radius: 6px;
|
||
width: 90%;
|
||
max-width: 450px;
|
||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.modal-header {
|
||
background-color: var(--primary-green);
|
||
color: white;
|
||
padding: 15px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
color: white;
|
||
font-size: 1.3rem;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
}
|
||
|
||
.chat-box {
|
||
height: 250px;
|
||
overflow-y: auto;
|
||
border: 1px solid #ddd;
|
||
border-radius: 3px;
|
||
padding: 12px;
|
||
margin-bottom: 12px;
|
||
background-color: #fafafa;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.message {
|
||
margin-bottom: 10px;
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
max-width: 80%;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.message.received {
|
||
background-color: #e8f5e9;
|
||
align-self: flex-start;
|
||
}
|
||
|
||
.message.sent {
|
||
background-color: #f1f8e9;
|
||
align-self: flex-end;
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 1600px) {
|
||
.search-row {
|
||
flex-wrap: wrap;
|
||
row-gap: 10px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.container {
|
||
padding: 10px;
|
||
}
|
||
|
||
.query-bar.show {
|
||
max-height: 200px;
|
||
}
|
||
|
||
.query-items {
|
||
flex-wrap: wrap;
|
||
min-width: auto;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 992px) {
|
||
.search-row {
|
||
gap: 10px;
|
||
}
|
||
|
||
table {
|
||
display: block;
|
||
overflow-x: auto;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
padding: 8px;
|
||
}
|
||
|
||
.search-field {
|
||
min-width: 120px !important;
|
||
}
|
||
|
||
.btn {
|
||
padding: 6px 10px;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.action-buttons {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.task-items {
|
||
overflow-x: auto;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.table-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
}
|
||
|
||
.table-header-actions {
|
||
align-self: flex-end;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 576px) {
|
||
.search-actions {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.btn {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
/* 自定义滚动条 */
|
||
::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: #f1f1f1;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: #bdbdbd;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
::-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>
|
||
<div id="app" class="container">
|
||
<header>
|
||
<h1><i class="fas fa-headset"></i> 客服工作台管理系统</h1>
|
||
</header>
|
||
|
||
<!-- 搜索栏 - 紧凑设计 -->
|
||
<section class="search-bar">
|
||
<div class="search-row">
|
||
<div class="search-field compact">
|
||
<select id="province" v-model="searchParams.province" @change="handleProvinceChange">
|
||
<option value="">选择省份</option>
|
||
<option v-for="item in areaList.provinces" :key="item.id" :value="item.id">{{ item.name }}</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="search-field compact">
|
||
<select id="city" v-model="searchParams.city" @change="handleCityChange">
|
||
<option value="">选择城市</option>
|
||
<option v-for="item in areaList.cities" :key="item.id" :value="item.id">{{ item.name }}</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="search-field compact">
|
||
<select id="district" v-model="searchParams.district">
|
||
<option value="">选择区域</option>
|
||
<option v-for="item in areaList.districts" :key="item.id" :value="item.id">{{ item.name }}</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="search-field compact">
|
||
<input type="text" id="order-number" v-model="searchParams.orderNumber" placeholder="订单号">
|
||
</div>
|
||
|
||
<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">
|
||
<div class="search-field" style="min-width: 130px;">
|
||
<select id="time-type" v-model="searchParams.timeType">
|
||
<option value="">选择时间类型</option>
|
||
<option value="1">创建时间</option>
|
||
<option value="2">到期时间</option>
|
||
<option value="3">保险时间</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="search-field" style="min-width: 130px;">
|
||
<input type="date" id="start-date" v-model="searchParams.startDate">
|
||
</div>
|
||
|
||
<span style="margin-bottom: 5px;">至</span>
|
||
|
||
<div class="search-field" style="min-width: 130px;">
|
||
<input type="date" id="end-date" v-model="searchParams.endDate">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="search-actions" style="display: flex; gap: 8px; margin-left: auto;">
|
||
<button class="btn btn-primary" @click="handleSearch" :disabled="uiState.isLoading" title="搜索">
|
||
<i :class="uiState.isLoading ? 'fas fa-spinner fa-spin' : 'fas fa-search'"></i>
|
||
{{ uiState.isLoading ? '加载中...' : '搜索' }}
|
||
</button>
|
||
<button class="btn btn-kpi" @click="toggleKpiBar" :title="uiState.showKpiBar ? '收起KPI数据' : '查看KPI数据'">
|
||
<i :class="uiState.showKpiBar ? 'fas fa-minus' : 'fas fa-chart-bar'"></i> {{ uiState.showKpiBar ? '收起KPI' : 'KPI' }}
|
||
</button>
|
||
<button class="btn btn-more" @click="toggleQueryBar" :title="uiState.showQueryBar ? '收起高级筛选' : '更多筛选'">
|
||
<i :class="uiState.showQueryBar ? 'fas fa-minus' : 'fas fa-ellipsis-h'"></i> {{ uiState.showQueryBar ? '收起' : '更多' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- KPI数据栏 -->
|
||
<section class="kpi-bar" :class="{ show: uiState.showKpiBar }">
|
||
<h3><i class="fas fa-chart-line"></i> KPI数据概览</h3>
|
||
<div class="kpi-items">
|
||
<div v-for="(item, index) in kpiList" :key="index" class="kpi-item" :class="{ driver: item.isDriver }">
|
||
<div v-if="item.subValue" class="kpi-value-wrapper">
|
||
<div class="value">{{ item.value }}</div>
|
||
<div class="sub-value">{{ item.subValue }}</div>
|
||
</div>
|
||
<div v-else class="value">{{ item.value }}</div>
|
||
<div class="label">{{ item.label }}</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 高级查询栏 -->
|
||
<section class="query-bar" :class="{ show: uiState.showQueryBar }">
|
||
<h3><i class="fas fa-filter"></i> 高级查询条件</h3>
|
||
<div class="query-items">
|
||
<div class="query-btn" style="background-color: var(--primary-green); color: white; border-color: var(--dark-green);" @click="navigateTo('http://homenew.auto-sos.net/saler/driver-list')">
|
||
<i class="fas fa-users"></i>
|
||
<span>司机列表</span>
|
||
</div>
|
||
<div v-for="(btn, idx) in advancedQueries" :key="idx" class="query-btn" @click="navigateTo(btn.link)">
|
||
<i :class="btn.icon"></i>
|
||
<span>{{ btn.label }}</span>
|
||
</div>
|
||
|
||
<!-- 归属客服下拉框 -->
|
||
<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.belongKefu" @change="handleKefuChange">
|
||
<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.belongSale">
|
||
<option value="">全部业务员</option>
|
||
<option v-for="item in staffOptions.salesmen" :key="item.id" :value="item.id">
|
||
{{ item.name }}
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 任务栏 -->
|
||
<section class="task-bar">
|
||
<div class="task-items">
|
||
<div v-for="(task, index) in taskList" :key="index" class="task-item"
|
||
:class="{ 'active': task.type === activeTaskType }"
|
||
@click="handleTaskClick(task)">
|
||
<i :class="task.icon"></i>
|
||
<span>{{ task.label }}</span>
|
||
<small>({{ task.count }})</small>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 案件信息表格 -->
|
||
<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">
|
||
<span>共 <strong>{{ caseList.length }}</strong> 条记录</span>
|
||
<button class="btn btn-secondary" @click="exportData" title="导出">
|
||
<i class="fas fa-file-export"></i> 导出数据
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr v-if="activeTaskType === 'online'">
|
||
<th style="width: 120px;">司机编号</th>
|
||
<th style="width: 120px;">司机姓名</th>
|
||
<th style="width: 130px;">司机手机号</th>
|
||
<th style="width: 120px;">未读消息数</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>
|
||
<th style="width: 80px;">服务项目</th>
|
||
<th style="width: 80px;">业务来源</th>
|
||
<th style="width: 80px;">车主姓名</th>
|
||
<th style="width: 110px;">车主号码</th>
|
||
<th style="width: 80px;">司机姓名</th>
|
||
<th style="width: 110px;">司机号码</th>
|
||
<th style="width: 100px;">救援点</th>
|
||
<th style="width: 70px;">到勘</th>
|
||
<th style="width: 70px;">时效</th>
|
||
<th style="width: 70px;">服务分</th>
|
||
<th style="width: 70px;">满意度</th>
|
||
<th style="width: 180px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-if="activeTaskType === 'online'" v-for="item in caseList" :key="item.driverId">
|
||
<td>{{ item.orderNo }}</td>
|
||
<td>{{ item.driverName }}</td>
|
||
<td>{{ item.driverPhone }}</td>
|
||
<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>
|
||
<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>
|
||
<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.ownerPhone }}</td>
|
||
<td>{{ item.driverName }}</td>
|
||
<td>{{ item.driverPhone }}</td>
|
||
<td>{{ item.location }}</td>
|
||
<td>{{ item.distance }}</td>
|
||
<td v-html="item.duration"></td>
|
||
<td>{{ item.score }}</td>
|
||
<td>{{ item.satisfaction }}</td>
|
||
<td>
|
||
<div class="action-buttons">
|
||
<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>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<!-- 聊天模态框 -->
|
||
<div v-if="uiState.modals.chat" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3><i class="fas fa-comments"></i> 在线聊天</h3>
|
||
<button class="close-btn" @click="closeModal('chat')">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="chat-box" ref="chatBoxRef">
|
||
<div v-for="(msg, idx) in chatMessages" :key="idx" class="message" :class="msg.type">
|
||
<strong>{{ msg.sender }}:</strong> {{ msg.content }}
|
||
</div>
|
||
</div>
|
||
<div class="input-group">
|
||
<textarea v-model="currentInput.message" placeholder="请输入消息..." rows="3"
|
||
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 0.85rem;"></textarea>
|
||
<button class="btn btn-primary" @click="sendMessage" style="margin-top: 8px; width: 100%;">
|
||
<i class="fas fa-paper-plane"></i> 发送消息
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 回访模态框 -->
|
||
<div v-if="uiState.modals.review" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3><i class="fas fa-phone-alt"></i> 客户回访</h3>
|
||
<button class="close-btn" @click="closeModal('review')">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p><strong>订单号:</strong> <span>{{ currentOrder.orderNo }}</span></p>
|
||
<p><strong>客户姓名:</strong> {{ currentOrder.ownerName }}</p>
|
||
<p><strong>服务项目:</strong> {{ currentOrder.service }}</p>
|
||
|
||
<div style="margin-top: 15px;">
|
||
<label><strong>回访结果:</strong></label>
|
||
<select v-model="currentInput.reviewResult"
|
||
style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ddd; border-radius: 3px; font-size: 0.85rem;">
|
||
<option value="满意">满意</option>
|
||
<option value="一般">一般</option>
|
||
<option value="不满意">不满意</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div style="margin-top: 12px;">
|
||
<label><strong>回访备注:</strong></label>
|
||
<textarea v-model="currentInput.reviewNotes" rows="3"
|
||
style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ddd; border-radius: 3px; font-size: 0.85rem;"></textarea>
|
||
</div>
|
||
|
||
<button class="btn btn-primary" @click="submitReview" style="margin-top: 15px; width: 100%;">
|
||
<i class="fas fa-check"></i> 提交回访结果
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 投诉处理模态框 -->
|
||
<div v-if="uiState.modals.complaint" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3><i class="fas fa-gavel"></i> 投诉处理</h3>
|
||
<button class="close-btn" @click="closeModal('complaint')">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p><strong>订单号:</strong> <span>{{ currentOrder.orderNo }}</span></p>
|
||
<p><strong>投诉人:</strong> {{ currentOrder.ownerName }}</p>
|
||
<p><strong>投诉类型:</strong> 服务延迟</p>
|
||
|
||
<div style="margin-top: 15px;">
|
||
<label><strong>投诉详情:</strong></label>
|
||
<div style="padding: 10px; background-color: #f5f5f5; border-radius: 3px; margin-top: 5px; font-size: 0.85rem;">
|
||
司机到达时间比预期晚了20分钟,导致我错过了重要会议。
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 12px;">
|
||
<label><strong>处理方案:</strong></label>
|
||
<select v-model="currentInput.complaintSolution"
|
||
style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ddd; border-radius: 3px; font-size: 0.85rem;">
|
||
<option value="退款">部分退款</option>
|
||
<option value="补偿">服务补偿</option>
|
||
<option value="道歉">正式道歉</option>
|
||
<option value="其他">其他方案</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div style="margin-top: 12px;">
|
||
<label><strong>处理备注:</strong></label>
|
||
<textarea v-model="currentInput.complaintNotes" rows="3"
|
||
style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ddd; border-radius: 3px; font-size: 0.85rem;"></textarea>
|
||
</div>
|
||
|
||
<button class="btn btn-primary" @click="submitComplaint" style="margin-top: 15px; width: 100%;">
|
||
<i class="fas fa-check"></i> 提交处理结果
|
||
</button>
|
||
</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')">×</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>
|
||
const { createApp, ref, reactive, onMounted, nextTick } = Vue;
|
||
|
||
createApp({
|
||
setup() {
|
||
// 基础URL配置
|
||
const BASE_URL = 'http://backend.jjdev.cn/';
|
||
|
||
const searchParams = reactive({
|
||
province: '',
|
||
city: '',
|
||
district: '',
|
||
orderNumber: '',
|
||
fuzzySearch: '',
|
||
timeType: '',
|
||
startDate: '',
|
||
endDate: '',
|
||
belongKefu: '',
|
||
belongSale: '',
|
||
driverUid: ''
|
||
});
|
||
|
||
// 界面状态
|
||
const uiState = reactive({
|
||
showQueryBar: false,
|
||
showKpiBar: false,
|
||
isLoading: false,
|
||
showDriverList: false,
|
||
modals: {
|
||
chat: false,
|
||
review: false,
|
||
complaint: false,
|
||
punish: false
|
||
}
|
||
});
|
||
|
||
const areaList = reactive({
|
||
provinces: [],
|
||
cities: [],
|
||
districts: []
|
||
});
|
||
|
||
const staffOptions = reactive({
|
||
customers: [],
|
||
salesmen: []
|
||
});
|
||
|
||
const punishRecords = ref([]);
|
||
|
||
// 司机搜索结果
|
||
const driverSearchResults = ref([]);
|
||
let driverSearchTimer = null;
|
||
const allOnlineDrivers = ref([]);
|
||
|
||
const kpiList = ref([
|
||
{ value: '-', label: '司机数', isDriver: true },
|
||
{ value: '-', label: '续费数' },
|
||
{ value: '-', label: '流失数' },
|
||
{ value: '-', subValue: '-', label: '接单数/率' },
|
||
{ value: '-', subValue: '-', label: '订单数/率' },
|
||
{ value: '-', subValue: '-', label: '不符数/率' },
|
||
{ value: '-', subValue: '-', label: '超时数/率' },
|
||
{ value: '-', subValue: '-', label: '跟进数/率' },
|
||
{ value: '-', subValue: '-', label: '好评数/率' },
|
||
{ value: '-', subValue: '-', label: '投诉数/率' }
|
||
]);
|
||
|
||
// 任务数据
|
||
const taskList = ref([
|
||
{ icon: 'fas fa-phone-alt', label: '待回访', count: 0, type: 'revisit', listData: [] },
|
||
{ icon: 'fas fa-comments', label: '在线沟通', count: 0, type: 'online', listData: [] },
|
||
{ icon: 'fas fa-exclamation-triangle', label: '不符跟进', count: 0, type: 'reconsider', listData: [] },
|
||
{ icon: 'fas fa-clock', label: '到勘超时', count: 0, type: 'over', listData: [] },
|
||
{ icon: 'fas fa-gavel', label: '投诉处理', count: 0, type: 'complain', listData: [] },
|
||
{ icon: 'fas fa-ban', label: '虚假案件', count: 0, type: 'false', listData: [] },
|
||
{ icon: 'fas fa-times-circle', label: '拒单案件', count: 0, type: 'reject', listData: [] },
|
||
{ icon: 'fas fa-chart-line', label: '溢价案件', count: 0, type: 'premium', listData: [] }
|
||
]);
|
||
|
||
// 高级查询按钮
|
||
const advancedQueries = ref([
|
||
{ icon: 'fas fa-star', label: '服务分', link: 'http://homenew.auto-sos.net/rorder/rf-list-average-log' },
|
||
{ icon: 'fas fa-tasks', label: '派单记录', link: 'http://homenew.auto-sos.net/saler/push-order-list' },
|
||
{ icon: 'fas fa-clock', label: '在线时长', link: 'http://homenew.auto-sos.net/saler/online-time-list' },
|
||
{ icon: 'fas fa-times-circle', label: '拒单记录', link: 'http://homenew.auto-sos.net/rorder/rf-list-new?order_id=&company_name=&driver_name=&uid=&create_time_start=&create_time_end=&review_type=4&reason=2' },
|
||
{ icon: 'fas fa-gavel', label: '投诉记录', link: 'http://homenew.auto-sos.net/appeal/list-new' },
|
||
{ icon: 'fas fa-hourglass-end', label: '超时记录', link: 'http://homenew.auto-sos.net/rorder/rf-list-subsidy' },
|
||
{ icon: 'fas fa-ban', label: '虚假记录', link: 'http://homenew.auto-sos.net/rorder/rf-list-average?order_id=&company_name=&driver_name=&uid=&create_time_start=&create_time_end=&review_type=4&reason=9' },
|
||
{ icon: 'fas fa-chart-line', label: '溢价记录', link: 'http://homenew.auto-sos.net/rorder/rf-list-final?order_id=&create_time_start=2025-12-01&create_time_end=2025-12-12&throw_uid=&OrderType=0&review=0&r_status=0&premium=1&page=1&pre-page=25' }
|
||
]);
|
||
|
||
// 案件列表数据 - 初始为空,将从任务按钮点击后填充
|
||
const caseList = ref([]);
|
||
const pagination = reactive({
|
||
page: 1,
|
||
pageSize: 20,
|
||
hasMore: true
|
||
});
|
||
|
||
// 当前激活的任务类型
|
||
const activeTaskType = ref('');
|
||
|
||
// 交互相关
|
||
const currentOrder = ref({});
|
||
const currentInput = reactive({
|
||
message: '',
|
||
reviewResult: '满意',
|
||
reviewNotes: '',
|
||
complaintSolution: '退款',
|
||
complaintNotes: ''
|
||
});
|
||
const chatMessages = ref([
|
||
{ sender: '客服', content: '您好,请问有什么可以帮助您的?', type: 'received' },
|
||
{ sender: '用户', content: '我的拖车服务什么时候能到?', type: 'sent' },
|
||
{ sender: '客服', content: '根据系统显示,司机李四预计在12分钟内到达您的位置。', type: 'received' }
|
||
]);
|
||
const chatBoxRef = ref(null);
|
||
|
||
// 生命周期钩子
|
||
onMounted(() => {
|
||
// 初始化日期
|
||
const today = new Date();
|
||
const year = today.getFullYear();
|
||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||
const day = String(today.getDate()).padStart(2, '0');
|
||
|
||
searchParams.endDate = `${year}-${month}-${day}`;
|
||
searchParams.startDate = `${year}-${month}-01`;
|
||
|
||
// 获取初始数据
|
||
fetchInitialData();
|
||
|
||
// 点击页面其他区域关闭司机下拉列表
|
||
document.addEventListener('click', (e) => {
|
||
const fuzzySearch = document.getElementById('fuzzy-search');
|
||
if (fuzzySearch && !fuzzySearch.contains(e.target)) {
|
||
uiState.showDriverList = false;
|
||
}
|
||
});
|
||
});
|
||
|
||
const normalizeProvinces = (list) => {
|
||
if (!Array.isArray(list)) return [];
|
||
return list.map(item => ({
|
||
id: item.province_id || item.id || item.areaid,
|
||
name: item.province_name || item.name || item.areaname
|
||
})).filter(item => item.id && item.name);
|
||
};
|
||
|
||
const normalizeAreas = (type, list) => {
|
||
if (!Array.isArray(list)) return [];
|
||
if (type === 'city') {
|
||
return list.map(item => ({
|
||
id: item.city_id || item.id || item.areaid,
|
||
name: item.city_name || item.name || item.areaname
|
||
})).filter(item => item.id && item.name);
|
||
}
|
||
if (type === 'county') {
|
||
return list.map(item => ({
|
||
id: item.county_id || item.id || item.areaid,
|
||
name: item.county_name || item.name || item.areaname
|
||
})).filter(item => item.id && item.name);
|
||
}
|
||
return list;
|
||
};
|
||
|
||
const normalizeStaffMap = (obj) => {
|
||
if (!obj || typeof obj !== 'object') return [];
|
||
return Object.keys(obj).map(key => ({
|
||
id: key,
|
||
name: obj[key]
|
||
}));
|
||
};
|
||
|
||
const formatPercent = (value) => {
|
||
if (value === null || value === undefined || value === '') return '-';
|
||
const num = Number(value);
|
||
if (Number.isNaN(num)) return String(value);
|
||
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('https://test-chat.jjsos.cn/src/getChatDriverCount.php');
|
||
let count = null;
|
||
if (Array.isArray(res.data) && res.data.length > 0 && res.data[0].driver_count !== undefined) {
|
||
count = Number(res.data[0].driver_count);
|
||
} else if (res.data && res.data.driver_count !== undefined) {
|
||
count = Number(res.data.driver_count);
|
||
}
|
||
if (count !== null && !Number.isNaN(count) && kpiList.value.length > 7) {
|
||
kpiList.value[7].value = String(count);
|
||
const total = Number(driverTotal);
|
||
if (!Number.isNaN(total) && total > 0) {
|
||
const rate = Math.round((count / total) * 1000) / 10;
|
||
kpiList.value[7].subValue = formatPercent(rate);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching follow-up count:', error);
|
||
}
|
||
};
|
||
|
||
const fetchOnlineChatData = async () => {
|
||
try {
|
||
const res = await axios.get('https://test-chat.jjsos.cn/src/getUnChatCount.php');
|
||
|
||
if (res.data) {
|
||
const onlineTask = taskList.value.find(task => task.type === 'online');
|
||
if (onlineTask) {
|
||
allOnlineDrivers.value = Array.isArray(res.data.drivers) ? res.data.drivers : [];
|
||
onlineTask.listData = allOnlineDrivers.value.slice();
|
||
onlineTask.count = res.data.total_drivers || allOnlineDrivers.value.length || 0;
|
||
}
|
||
console.log('在线沟通数据:', res.data.total_drivers, '条');
|
||
if (searchParams.belongKefu) {
|
||
await filterOnlineDriversByKefu(searchParams.belongKefu);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching online chat data:', error);
|
||
}
|
||
};
|
||
|
||
const filterOnlineDriversByKefu = async (csId) => {
|
||
const onlineTask = taskList.value.find(task => task.type === 'online');
|
||
if (!onlineTask) return;
|
||
|
||
if (!csId) {
|
||
onlineTask.listData = allOnlineDrivers.value.slice();
|
||
onlineTask.count = onlineTask.listData.length;
|
||
if (activeTaskType.value === 'online') {
|
||
handleTaskClick(onlineTask);
|
||
}
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await axios.get(BASE_URL + 'third-api/get-cs-driver', {
|
||
params: { cs_id: csId }
|
||
});
|
||
if (res.data && res.data.code === 200 && Array.isArray(res.data.data)) {
|
||
const allowedIds = new Set(res.data.data.map(String));
|
||
const filtered = allOnlineDrivers.value.filter(d => {
|
||
const id = d.driver_id || d.driverId || d.uid || d.id;
|
||
return allowedIds.has(String(id));
|
||
});
|
||
onlineTask.listData = filtered;
|
||
onlineTask.count = filtered.length;
|
||
} else {
|
||
onlineTask.listData = [];
|
||
onlineTask.count = 0;
|
||
}
|
||
if (activeTaskType.value === 'online') {
|
||
handleTaskClick(onlineTask);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error filtering online drivers by cs:', error);
|
||
}
|
||
};
|
||
|
||
// 方法定义
|
||
const fetchInitialData = async () => {
|
||
try {
|
||
// 构建请求参数 - 只传时间参数
|
||
const params = {
|
||
time_start: searchParams.startDate,
|
||
time_end: searchParams.endDate
|
||
};
|
||
|
||
console.log('请求参数:', params);
|
||
console.log('完整URL:', `${BASE_URL}third-api/cs-chart-info?time_start=${params.time_start}&time_end=${params.time_end}`);
|
||
|
||
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;
|
||
if (Array.isArray(d.province)) {
|
||
areaList.provinces = normalizeProvinces(d.province);
|
||
}
|
||
// 填充客服列表
|
||
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);
|
||
}
|
||
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);
|
||
}
|
||
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' },
|
||
'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' && pagination.page === 1) {
|
||
task.count = d[numKey];
|
||
}
|
||
if (Array.isArray(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;
|
||
}
|
||
}
|
||
|
||
// 获取在线沟通数据
|
||
await fetchOnlineChatData();
|
||
|
||
// 默认选中待回访按钮
|
||
const revisitTask = taskList.value.find(task => task.type === 'revisit');
|
||
if (revisitTask) {
|
||
handleTaskClick(revisitTask);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching initial data:', error);
|
||
}
|
||
};
|
||
|
||
const fetchAreas = async (type, areaId) => {
|
||
if (!areaId) return;
|
||
try {
|
||
const res = await axios.get(BASE_URL + 'ajax/get-areas', {
|
||
params: { type, areaid: areaId }
|
||
});
|
||
if (res.data) {
|
||
const list = Array.isArray(res.data) ? res.data : (res.data.data || []);
|
||
const normalized = normalizeAreas(type, list);
|
||
if (type === 'city') {
|
||
areaList.cities = normalized;
|
||
} else if (type === 'county') {
|
||
areaList.districts = normalized;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error(`Error fetching ${type}:`, error);
|
||
}
|
||
};
|
||
|
||
const handleProvinceChange = () => {
|
||
searchParams.city = '';
|
||
searchParams.district = '';
|
||
areaList.cities = [];
|
||
areaList.districts = [];
|
||
fetchAreas('city', searchParams.province);
|
||
};
|
||
|
||
const handleCityChange = () => {
|
||
searchParams.district = '';
|
||
areaList.districts = [];
|
||
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) {
|
||
alert('请选择时间范围');
|
||
return;
|
||
}
|
||
|
||
if (searchParams.startDate > searchParams.endDate) {
|
||
alert('开始时间不能晚于结束时间');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 显示加载状态
|
||
uiState.isLoading = true;
|
||
console.log('正在搜索数据...');
|
||
|
||
// 重置分页
|
||
pagination.page = 1;
|
||
pagination.hasMore = true;
|
||
|
||
// 构建请求参数
|
||
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;
|
||
}
|
||
|
||
// 分页参数
|
||
params.page = pagination.page;
|
||
params.page_size = pagination.pageSize;
|
||
|
||
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' },
|
||
'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') {
|
||
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) {
|
||
console.error('搜索失败:', error);
|
||
alert('搜索失败,请稍后重试');
|
||
} finally {
|
||
uiState.isLoading = false;
|
||
}
|
||
};
|
||
|
||
const toggleKpiBar = () => {
|
||
uiState.showKpiBar = !uiState.showKpiBar;
|
||
if (uiState.showKpiBar) uiState.showQueryBar = false;
|
||
};
|
||
|
||
const toggleQueryBar = () => {
|
||
uiState.showQueryBar = !uiState.showQueryBar;
|
||
if (uiState.showQueryBar) uiState.showKpiBar = false;
|
||
};
|
||
|
||
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 handleKefuChange = () => {
|
||
filterOnlineDriversByKefu(searchParams.belongKefu);
|
||
};
|
||
|
||
// 格式化复议情况
|
||
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,
|
||
service: '-',
|
||
source: '-',
|
||
ownerName: '-',
|
||
ownerPhone: '-',
|
||
location: '-',
|
||
distance: '-',
|
||
duration: '-',
|
||
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 || '-',
|
||
ownerName: item.owner_name || item.ownerName || '-',
|
||
ownerPhone: item.owner_phone || item.ownerPhone || '-',
|
||
driverName: item.driver_name || item.driverName || '-',
|
||
driverPhone: item.driver_phone || item.driverPhone || '-',
|
||
location: item.location || item.rescue_point || '-',
|
||
distance: item.distance || item.arrival_distance || '-',
|
||
duration: item.duration || item.arrival_time || '-',
|
||
score: item.score || item.service_score || '-',
|
||
satisfaction: item.satisfaction || item.satisfy_level || '-'
|
||
}));
|
||
}
|
||
} else {
|
||
caseList.value = [];
|
||
}
|
||
};
|
||
|
||
const exportData = () => {
|
||
alert('数据导出功能已触发,正在生成Excel文件...');
|
||
};
|
||
|
||
const openChatWindow = (driverId) => {
|
||
if (!driverId || driverId === '-') {
|
||
alert('司机ID无效');
|
||
return;
|
||
}
|
||
// 打开聊天窗口,固定参数
|
||
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');
|
||
};
|
||
|
||
const openModal = (type, item) => {
|
||
currentOrder.value = item;
|
||
uiState.modals[type] = true;
|
||
};
|
||
|
||
const closeModal = (type) => {
|
||
uiState.modals[type] = false;
|
||
};
|
||
|
||
const sendMessage = () => {
|
||
const msg = currentInput.message.trim();
|
||
if (msg) {
|
||
chatMessages.value.push({ sender: '客服', content: msg, type: 'sent' });
|
||
currentInput.message = '';
|
||
|
||
nextTick(() => {
|
||
if (chatBoxRef.value) chatBoxRef.value.scrollTop = chatBoxRef.value.scrollHeight;
|
||
});
|
||
|
||
setTimeout(() => {
|
||
chatMessages.value.push({ sender: '用户', content: '收到,谢谢您的帮助!', type: 'received' });
|
||
nextTick(() => {
|
||
if (chatBoxRef.value) chatBoxRef.value.scrollTop = chatBoxRef.value.scrollHeight;
|
||
});
|
||
}, 1000);
|
||
}
|
||
};
|
||
|
||
const submitReview = () => {
|
||
alert(`回访结果已提交:\n订单: ${currentOrder.value.orderNo}\n结果: ${currentInput.reviewResult}\n备注: ${currentInput.reviewNotes || '无'}`);
|
||
closeModal('review');
|
||
currentInput.reviewNotes = '';
|
||
};
|
||
|
||
const submitComplaint = () => {
|
||
alert(`投诉处理结果已提交:\n订单: ${currentOrder.value.orderNo}\n处理方案: ${currentInput.complaintSolution}\n备注: ${currentInput.complaintNotes || '无'}`);
|
||
closeModal('complaint');
|
||
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,
|
||
areaList,
|
||
staffOptions,
|
||
kpiList,
|
||
taskList,
|
||
advancedQueries,
|
||
caseList,
|
||
activeTaskType,
|
||
currentOrder,
|
||
currentInput,
|
||
chatMessages,
|
||
chatBoxRef,
|
||
driverSearchResults,
|
||
punishRecords,
|
||
handleProvinceChange,
|
||
handleCityChange,
|
||
handleSearch,
|
||
toggleKpiBar,
|
||
toggleQueryBar,
|
||
navigateTo,
|
||
showPunishDetail,
|
||
openOrderDetail,
|
||
openOrderRating,
|
||
handleTableScroll,
|
||
formatReviewStatus,
|
||
openAppealDetail,
|
||
handleKefuChange,
|
||
handleTaskClick,
|
||
openChatWindow,
|
||
exportData,
|
||
openModal,
|
||
closeModal,
|
||
sendMessage,
|
||
submitReview,
|
||
submitComplaint,
|
||
handleDriverSearch,
|
||
selectDriver
|
||
};
|
||
}
|
||
}).mount('#app');
|
||
</script>
|
||
</body>
|
||
</html>
|