1327 lines
47 KiB
HTML
1327 lines
47 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="./node_modules/axios/dist/axios.min.js"></script>
|
||
<script src="./node_modules/vue/dist/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-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 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;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
</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;">
|
||
<input type="text" id="fuzzy-search" v-model="searchParams.fuzzySearch" placeholder="输入司机姓名">
|
||
</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="4">保险时间</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" title="搜索">
|
||
<i class="fas fa-search"></i> 搜索
|
||
</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('driver_list.html')">
|
||
<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.customerService">
|
||
<option value="">全部客服</option>
|
||
<option v-for="item in staffOptions.customers" :key="item.id" :value="item.id">
|
||
{{ item.name }}
|
||
</option>
|
||
</select>
|
||
<select style="padding: 7px 9px; border: 1px solid var(--border-color); border-radius: 3px; font-size: 0.85rem; height: 32px;" v-model="searchParams.salesman">
|
||
<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" @click="navigateTo(task.page)">
|
||
<i :class="task.icon"></i>
|
||
<span>{{ task.label }}</span>
|
||
<small>({{ task.count }})</small>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 案件信息表格 -->
|
||
<section class="case-table-container">
|
||
<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>
|
||
<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-for="item in caseList" :key="item.orderNo">
|
||
<td>{{ item.orderNo }}</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>{{ 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>
|
||
</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>
|
||
|
||
<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: '',
|
||
customerService: '',
|
||
salesman: ''
|
||
});
|
||
|
||
// 界面状态
|
||
const uiState = reactive({
|
||
showQueryBar: false,
|
||
showKpiBar: false,
|
||
modals: {
|
||
chat: false,
|
||
review: false,
|
||
complaint: false
|
||
}
|
||
});
|
||
|
||
const areaList = reactive({
|
||
provinces: [],
|
||
cities: [],
|
||
districts: []
|
||
});
|
||
|
||
const staffOptions = reactive({
|
||
customers: [],
|
||
salesmen: []
|
||
});
|
||
|
||
const kpiList = ref([
|
||
{ value: '-', label: '司机数', isDriver: true },
|
||
{ value: '156', label: '续费数' },
|
||
{ value: '45', label: '流失数' },
|
||
{ value: '987', subValue: '98.7%', 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: '投诉数/率' }
|
||
]);
|
||
|
||
// 任务数据
|
||
const taskList = ref([
|
||
{ icon: 'fas fa-phone-alt', label: '待回访', count: 12, page: 'pending_review.html' },
|
||
{ icon: 'fas fa-comments', label: '在线沟通', count: 8, page: 'online_chat.html' },
|
||
{ icon: 'fas fa-exclamation-triangle', label: '不符跟进', count: 5, page: 'non_compliance.html' },
|
||
{ icon: 'fas fa-clock', label: '到勘超时', count: 3, page: 'arrival_timeout.html' },
|
||
{ icon: 'fas fa-gavel', label: '投诉处理', count: 7, page: 'complaint_handling.html' },
|
||
{ icon: 'fas fa-ban', label: '虚假案件', count: 2, page: 'false_case.html' },
|
||
{ icon: 'fas fa-times-circle', label: '拒单案件', count: 4, page: 'reject_case.html' },
|
||
{ icon: 'fas fa-chart-line', label: '溢价案件', count: 1, page: 'premium_case.html' }
|
||
]);
|
||
|
||
// 高级查询按钮
|
||
const advancedQueries = ref([
|
||
{ icon: 'fas fa-star', label: '服务分', link: 'service_score.html' },
|
||
{ icon: 'fas fa-tasks', label: '派单记录', link: 'dispatch_record.html' },
|
||
{ icon: 'fas fa-clock', label: '在线时长', link: 'online_time.html' },
|
||
{ icon: 'fas fa-times-circle', label: '拒单记录', link: 'reject_record.html' },
|
||
{ icon: 'fas fa-gavel', label: '投诉记录', link: 'complaint_record.html' },
|
||
{ icon: 'fas fa-hourglass-end', label: '超时记录', link: 'timeout_record.html' },
|
||
{ icon: 'fas fa-ban', label: '虚假记录', link: 'false_record.html' },
|
||
{ icon: 'fas fa-chart-line', label: '溢价记录', link: 'premium_record.html' }
|
||
]);
|
||
|
||
// 案件列表数据 (Mock)
|
||
const caseList = ref([
|
||
{ orderNo: '10109089', service: '拖车', source: '壹账通', ownerName: '张山', ownerPhone: '13456788990', driverName: '李四', driverPhone: '13567877890', location: '宁波南站', distance: '3km', duration: '12分', score: 3, satisfaction: '满意' },
|
||
{ orderNo: '10109090', service: '搭电', source: '平安保险', ownerName: '王五', ownerPhone: '13856783210', driverName: '赵六', driverPhone: '13987654321', location: '杭州东站', distance: '5km', duration: '20分', score: 4, satisfaction: '非常满意' },
|
||
{ orderNo: '10109091', service: '换胎', source: '太平洋保险', ownerName: '孙七', ownerPhone: '13765439876', driverName: '周八', driverPhone: '13678901234', location: '上海虹桥', distance: '2km', duration: '15分', score: 5, satisfaction: '满意' },
|
||
{ orderNo: '10109092', service: '拖车', source: '壹账通', ownerName: '刘九', ownerPhone: '13543218765', driverName: '郑十', driverPhone: '13456789012', location: '南京南站', distance: '8km', duration: '25分', score: 3, satisfaction: '一般' },
|
||
{ orderNo: '10109093', service: '送油', source: '人保', ownerName: '钱一', ownerPhone: '13987651234', driverName: '吴二', driverPhone: '13876543210', location: '苏州站', distance: '4km', duration: '18分', score: 4, satisfaction: '非常满意' },
|
||
{ orderNo: '10109094', service: '拖车', source: '平安保险', ownerName: '陈三', ownerPhone: '13678904321', driverName: '林四', driverPhone: '13567894321', location: '无锡站', distance: '6km', duration: '22分', score: 2, satisfaction: '不满意' },
|
||
{ orderNo: '10109095', service: '搭电', source: '太平洋保险', ownerName: '杨五', ownerPhone: '13456781234', driverName: '黄六', driverPhone: '13765432109', location: '常州站', distance: '3km', duration: '14分', score: 5, satisfaction: '非常满意' },
|
||
{ orderNo: '10109096', service: '换胎', source: '壹账通', ownerName: '朱七', ownerPhone: '13876549876', driverName: '秦八', driverPhone: '13678905432', location: '镇江站', distance: '7km', duration: '30分', score: 3, satisfaction: '一般' },
|
||
{ orderNo: '10109097', service: '拖车', source: '人保', ownerName: '何九', ownerPhone: '13567894321', driverName: '许十', driverPhone: '13456789098', location: '嘉兴南站', distance: '5km', duration: '19分', score: 4, satisfaction: '满意' },
|
||
{ orderNo: '10109098', service: '送油', source: '平安保险', ownerName: '吕一', ownerPhone: '13987655678', driverName: '施二', driverPhone: '13876546789', location: '绍兴站', distance: '4km', duration: '17分', score: 5, satisfaction: '非常满意' },
|
||
{ orderNo: '10109099', service: '搭电', source: '太平洋保险', ownerName: '孔三', ownerPhone: '13678906543', driverName: '曹四', driverPhone: '13567897654', location: '金华站', distance: '6km', duration: '23分', score: 3, satisfaction: '一般' }
|
||
]);
|
||
|
||
// 交互相关
|
||
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();
|
||
});
|
||
|
||
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 fetchInitialData = async () => {
|
||
try {
|
||
const params = {
|
||
time_start: searchParams.startDate,
|
||
time_end: searchParams.endDate
|
||
};
|
||
if (searchParams.timeType) {
|
||
params.apptimes = searchParams.timeType;
|
||
}
|
||
|
||
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);
|
||
}
|
||
if (d.salers) {
|
||
staffOptions.salesmen = normalizeStaffMap(d.salers);
|
||
}
|
||
if (typeof d.driverNum !== 'undefined' && kpiList.value.length > 0) {
|
||
kpiList.value[0].value = String(d.driverNum);
|
||
}
|
||
}
|
||
} 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 handleSearch = () => {
|
||
let timeTypeName = "未选择";
|
||
if (searchParams.timeType === "1") timeTypeName = "创建时间";
|
||
else if (searchParams.timeType === "2") timeTypeName = "到期时间";
|
||
else if (searchParams.timeType === "4") timeTypeName = "保险时间";
|
||
|
||
alert(`搜索条件:\n省份ID: ${searchParams.province || '未选择'}\n城市ID: ${searchParams.city || '未选择'}\n区域ID: ${searchParams.district || '未选择'}\n订单号: ${searchParams.orderNumber || '未输入'}\n模糊查询: ${searchParams.fuzzySearch || '未输入'}\n时间类型: ${timeTypeName}\n时间范围: ${searchParams.startDate || '未选择'} 至 ${searchParams.endDate || '未选择'}`);
|
||
};
|
||
|
||
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) => {
|
||
alert(`正在跳转到页面...\nURL: ${url}\n(实际应用中会跳转)`);
|
||
// window.location.href = url;
|
||
};
|
||
|
||
const exportData = () => {
|
||
alert('数据导出功能已触发,正在生成Excel文件...');
|
||
};
|
||
|
||
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 = '';
|
||
};
|
||
|
||
return {
|
||
searchParams,
|
||
uiState,
|
||
areaList,
|
||
staffOptions,
|
||
kpiList,
|
||
taskList,
|
||
advancedQueries,
|
||
caseList,
|
||
currentOrder,
|
||
currentInput,
|
||
chatMessages,
|
||
chatBoxRef,
|
||
handleProvinceChange,
|
||
handleCityChange,
|
||
handleSearch,
|
||
toggleKpiBar,
|
||
toggleQueryBar,
|
||
navigateTo,
|
||
exportData,
|
||
openModal,
|
||
closeModal,
|
||
sendMessage,
|
||
submitReview,
|
||
submitComplaint
|
||
};
|
||
}
|
||
}).mount('#app');
|
||
</script>
|
||
</body>
|
||
</html>
|